Skip to content

Commit ed23c7f

Browse files
[lang] Refactor fence apis
There are two fence apis exposed here: a regular fence() and fence_proxy_bidirectional(). Bidirectional fences are equivalent to four separate unidirectional fences and take a different set of options, so they get their own interface. Unidirectional fences fall into two categories: those with generic proxy arguments, and those without. Those with generic proxy arguments become regular llvm fence instructions with a memory ordering and a memory scope. Those with anything other than generic proxy arguments lower to specific nvvm intrinsics (or they fail to lower). The lowering of the unidirectional non-generic proxy fences is the most error prone, I think. With coverage reports I see 100% coverage of fence_impl.py from running the fence tests and we have negative tests checking for compilation failures with invalid options so I think it's robust enough to merge. The upstream test generation script was used as a reference for both the tests and the regular fence interface itself: https://github.com/llvm/llvm-project/blob/main/llvm/test/CodeGen/NVPTX/fence.py ai-use: updating callsites using the old apis, coming up with some test cases, and debugging lowerings. Signed-off-by: Asher Mancinelli <amancinelli@nvidia.com>
1 parent cc98c05 commit ed23c7f

31 files changed

Lines changed: 871 additions & 434 deletions

experimental/cuda-lang/docs/source/operations.rst

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -287,13 +287,8 @@ Memory Fence
287287
:nosignatures:
288288

289289
memory_barrier
290-
fence_sc_cluster
291-
fence_mbarrier_initialize
292-
fence_sync_restrict
293-
fence_proxy
294-
fence_proxy_acquire
295-
fence_proxy_release
296-
fence_proxy_sync_restrict
290+
fence
291+
fence_proxy_bidirectional
297292

298293

299294
TensorCore (Gen5)

experimental/cuda-lang/src/cuda/lang/__init__.py

Lines changed: 6 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -91,15 +91,10 @@
9191
not_equal
9292
)
9393
from ._stub.fence import (
94-
FenceProxyKind,
94+
FenceProxy,
95+
FenceRestriction,
9596
fence,
96-
fence_sync_restrict,
97-
fence_sc_cluster,
98-
fence_mbarrier_initialize,
99-
fence_proxy_sync_restrict,
100-
fence_proxy,
101-
fence_proxy_acquire,
102-
fence_proxy_release,
97+
fence_proxy_bidirectional,
10398
)
10499
from ._stub.types import (
105100
Scalar,
@@ -365,15 +360,10 @@
365360
"MemoryOrder",
366361
"RoundingMode",
367362
"BarrierReductionKind",
368-
"FenceProxyKind",
363+
"FenceProxy",
364+
"FenceRestriction",
369365
"fence",
370-
"fence_sync_restrict",
371-
"fence_sc_cluster",
372-
"fence_mbarrier_initialize",
373-
"fence_proxy_sync_restrict",
374-
"fence_proxy",
375-
"fence_proxy_acquire",
376-
"fence_proxy_release",
366+
"fence_proxy_bidirectional",
377367
"compile_simt",
378368
"SwizzleMode",
379369
"TensorMapL2Promotion",

experimental/cuda-lang/src/cuda/lang/_enums.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,16 @@ class FenceProxyKind(Enum):
157157
GENERIC = "generic"
158158

159159

160+
class FenceProxy(Enum):
161+
"""Memory access proxy used by a fence."""
162+
163+
ALIAS = "alias"
164+
ASYNC = "async"
165+
TENSORMAP = "tensormap"
166+
GENERIC = "generic"
167+
FABRIC = "fabric"
168+
169+
160170
class BarrierReductionKind(Enum):
161171
POP_COUNT = auto()
162172
AND = auto()
@@ -226,6 +236,7 @@ class MatrixLoadSourceFormat(Enum):
226236
"Tcgen05CopySourceFormat",
227237
"Tcgen05WaitKind",
228238
"FenceProxyKind",
239+
"FenceProxy",
229240
"BarrierReductionKind",
230241
"VectorReduction",
231242
"CachePolicy",

experimental/cuda-lang/src/cuda/lang/_ir/op_defs.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,12 @@ class RMWMode(Enum):
4646
READ_WRITE = auto()
4747

4848

49+
@dataclass(eq=False)
50+
class Fence(Operation, opcode="fence", memory_effect=MemoryEffect.STORE):
51+
memory_order: MemoryOrder = attribute()
52+
memory_scope: MemoryScope = attribute()
53+
54+
4955
@dataclass(eq=False)
5056
class ForeignFunction(
5157
Operation, opcode="foreign_function", memory_effect=MemoryEffect.STORE

experimental/cuda-lang/src/cuda/lang/_ir/op_impl/fence_impl.py

Lines changed: 189 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,60 +2,224 @@
22
#
33
# SPDX-License-Identifier: Apache-2.0
44

5-
from cuda.lang._enums import MemoryOrder, MemoryScope
5+
from cuda.lang._enums import FenceProxy, MemoryOrder, MemoryScope
66
from cuda.lang._exception import TypeCheckingError
7-
from cuda.lang._ir.op_defs import InlinePTX
7+
from cuda.lang._ir.op_defs import Fence, RawNVVMIntrinsic
8+
from cuda.lang._ir.type_checking_helpers import is_none, require_pointer_type
89
from cuda.lang._stub import fence as fence_stub
910
from cuda.tile._ir.ir import add_operation_variadic
10-
from cuda.tile._ir.op_impl import ImplRegistry, require_constant_enum
11+
from cuda.tile._ir.op_impl import (
12+
ImplRegistry,
13+
require_constant_enum,
14+
require_constant_int,
15+
)
16+
from cuda.tile._ir.type import DataclassTy, EnumTy
1117

1218

1319
_registry = ImplRegistry()
1420
impl = _registry.impl
1521

1622

23+
_SCOPE_SUFFIX = {
24+
MemoryScope.BLOCK: "cta",
25+
MemoryScope.CLUSTER: "cluster",
26+
MemoryScope.DEVICE: "gpu",
27+
MemoryScope.SYS: "sys",
28+
}
29+
30+
_ORDER_SUFFIX = {
31+
MemoryOrder.ACQUIRE: "acquire",
32+
MemoryOrder.RELEASE: "release",
33+
MemoryOrder.ACQ_REL: "acq_rel",
34+
MemoryOrder.SEQ_CST: "sc",
35+
}
36+
37+
# The restrictions are spelled differently on bidirectional intrinsics, so we
38+
# can't alwyas map the shared cta enum to the same string. For example:
39+
# @llvm.nvvm.fence.proxy.async.shared_cluster
40+
# @llvm.nvvm.fence.proxy.async_generic.release.sync_restrict.space.cta.scope.cluster
41+
_RESTRICTION_SPACE_SUFFIX = {
42+
fence_stub._FenceRestrictionKind.SHARED_BLOCK: "cta",
43+
fence_stub._FenceRestrictionKind.SHARED_CLUSTER: "cluster",
44+
fence_stub._FenceRestrictionKind.GLOBAL: "global",
45+
}
46+
47+
_BIDIRECTIONAL_RESTRICTION_SUFFIX = {
48+
fence_stub._FenceRestrictionKind.MBARRIER_INIT: "mbarrier_init",
49+
fence_stub._FenceRestrictionKind.SHARED_BLOCK: "shared_cta",
50+
fence_stub._FenceRestrictionKind.SHARED_CLUSTER: "shared_cluster",
51+
fence_stub._FenceRestrictionKind.GLOBAL: "global",
52+
}
53+
54+
1755
def fence_impl_registry() -> ImplRegistry:
1856
return _registry
1957

2058

21-
@impl(fence_stub.fence)
22-
def fence_impl(order, scope) -> None:
23-
order = require_constant_enum(order, MemoryOrder)
24-
scope = require_constant_enum(scope, MemoryScope)
59+
__all__ = ("fence_impl_registry",)
2560

26-
valid_orders = (
61+
62+
def require_fence_order(order):
63+
order = require_constant_enum(order, MemoryOrder)
64+
valid = (
2765
MemoryOrder.ACQUIRE,
2866
MemoryOrder.RELEASE,
2967
MemoryOrder.ACQ_REL,
3068
MemoryOrder.SEQ_CST,
3169
)
32-
if order not in valid_orders:
33-
formatted = ", ".join(str(value) for value in valid_orders)
70+
if order not in valid:
71+
formatted = ", ".join(str(value) for value in valid)
3472
raise TypeCheckingError(
3573
f"Invalid fence memory order {order}, expected one of {formatted}"
3674
)
75+
return order
76+
3777

38-
scope_suffixes = {
39-
MemoryScope.BLOCK: "cta",
40-
MemoryScope.CLUSTER: "cluster",
41-
MemoryScope.DEVICE: "gpu",
42-
MemoryScope.SYS: "sys",
43-
}
44-
if scope not in scope_suffixes:
45-
formatted = ", ".join(str(value) for value in scope_suffixes)
78+
def require_fence_scope(scope):
79+
scope = require_constant_enum(scope, MemoryScope)
80+
valid = (
81+
MemoryScope.BLOCK,
82+
MemoryScope.CLUSTER,
83+
MemoryScope.DEVICE,
84+
MemoryScope.SYS,
85+
)
86+
if scope not in valid:
87+
formatted = ", ".join(str(value) for value in valid)
4688
raise TypeCheckingError(
4789
f"Invalid fence memory scope {scope}, expected one of {formatted}"
4890
)
91+
return scope
92+
93+
94+
def require_fence_restriction(restriction):
95+
if is_none(restriction):
96+
return None
97+
98+
restriction_ty = restriction.get_type()
99+
if isinstance(restriction_ty, EnumTy):
100+
return require_constant_enum(restriction, fence_stub._FenceRestrictionKind)
101+
102+
if (
103+
isinstance(restriction_ty, DataclassTy)
104+
and restriction_ty.cls is fence_stub._FenceAddressRestriction
105+
):
106+
restriction_value = restriction.get_aggregate()
107+
address = restriction_value.get_field("address")
108+
size = restriction_value.get_field("size")
109+
require_pointer_type(address)
110+
size_value = require_constant_int(size)
111+
if size_value != 128:
112+
raise TypeCheckingError(
113+
f"An address restriction must have size 128, got {size_value}"
114+
)
115+
return restriction_value
116+
117+
raise TypeCheckingError(f"Expected FenceRestriction or None, got {restriction_ty}")
118+
119+
120+
def lower_non_proxy_fence(order, scope, restriction):
121+
if restriction is None:
122+
add_operation_variadic(Fence, (), memory_order=order, memory_scope=scope)
123+
return
124+
125+
order_suffix = _ORDER_SUFFIX[order]
126+
scope_suffix = _SCOPE_SUFFIX[scope]
127+
operands = ()
128+
if restriction is fence_stub._FenceRestrictionKind.MBARRIER_INIT:
129+
intrinsic = f"llvm.nvvm.fence.mbarrier_init.{order_suffix}.{scope_suffix}"
130+
elif isinstance(restriction, fence_stub._FenceRestrictionKind):
131+
space_suffix = _RESTRICTION_SPACE_SUFFIX[restriction]
132+
intrinsic = (
133+
f"llvm.nvvm.fence.{order_suffix}.sync_restrict."
134+
f"space.{space_suffix}.scope.{scope_suffix}"
135+
)
136+
else:
137+
intrinsic = f"llvm.nvvm.fence.{order_suffix}.address.scope.{scope_suffix}"
138+
operands = (
139+
restriction.get_field("address"),
140+
restriction.get_field("size"),
141+
)
49142

50-
order_suffix = "sc" if order is MemoryOrder.SEQ_CST else order.value
51143
add_operation_variadic(
52-
InlinePTX,
144+
RawNVVMIntrinsic,
53145
(),
54-
ptx_code=f"fence.{order_suffix}.{scope_suffixes[scope]};",
55-
read_only_operands=(),
56-
write_only_operands=(),
57-
read_write_operands=(),
146+
intrinsic=intrinsic,
147+
operands_=operands,
58148
)
59149

60150

61-
__all__ = ("fence_impl_registry",)
151+
def lower_proxy_fence(order, scope, from_proxy, to_proxy, restriction):
152+
order_suffix = _ORDER_SUFFIX[order]
153+
scope_suffix = _SCOPE_SUFFIX[scope]
154+
proxy_suffix = f"{to_proxy.value}_{from_proxy.value}"
155+
operands = ()
156+
if restriction is None:
157+
intrinsic = (
158+
f"llvm.nvvm.fence.proxy.{proxy_suffix}.{order_suffix}.{scope_suffix}"
159+
)
160+
elif not isinstance(restriction, fence_stub._FenceRestrictionKind):
161+
intrinsic = (
162+
f"llvm.nvvm.fence.proxy.{proxy_suffix}.{order_suffix}.{scope_suffix}"
163+
)
164+
operands = (
165+
restriction.get_field("address"),
166+
restriction.get_field("size"),
167+
)
168+
elif restriction is fence_stub._FenceRestrictionKind.MBARRIER_INIT:
169+
intrinsic = (
170+
f"llvm.nvvm.fence.proxy.{proxy_suffix}.{order_suffix}."
171+
f"op_restrict.mbarrier_init.scope.{scope_suffix}"
172+
)
173+
else:
174+
space_suffix = _RESTRICTION_SPACE_SUFFIX[restriction]
175+
intrinsic = (
176+
f"llvm.nvvm.fence.proxy.{proxy_suffix}.{order_suffix}."
177+
f"sync_restrict.space.{space_suffix}.scope.{scope_suffix}"
178+
)
179+
180+
add_operation_variadic(
181+
RawNVVMIntrinsic,
182+
(),
183+
intrinsic=intrinsic,
184+
operands_=operands,
185+
)
186+
187+
188+
@impl(fence_stub.fence)
189+
def fence_impl(order, scope, from_proxy, to_proxy, restriction) -> None:
190+
order = require_fence_order(order)
191+
scope = require_fence_scope(scope)
192+
from_proxy = require_constant_enum(from_proxy, FenceProxy)
193+
to_proxy = require_constant_enum(to_proxy, FenceProxy)
194+
restriction = require_fence_restriction(restriction)
195+
196+
if from_proxy is FenceProxy.GENERIC and to_proxy is FenceProxy.GENERIC:
197+
lower_non_proxy_fence(order, scope, restriction)
198+
else:
199+
lower_proxy_fence(order, scope, from_proxy, to_proxy, restriction)
200+
201+
202+
@impl(fence_stub.fence_proxy_bidirectional)
203+
def fence_proxy_bidirectional_impl(proxy, restriction) -> None:
204+
proxy = require_constant_enum(proxy, FenceProxy)
205+
restriction = require_fence_restriction(restriction)
206+
207+
operands = ()
208+
if restriction is None:
209+
proxy_suffix = proxy.value
210+
elif isinstance(restriction, fence_stub._FenceRestrictionKind):
211+
restriction_suffix = _BIDIRECTIONAL_RESTRICTION_SUFFIX[restriction]
212+
proxy_suffix = f"{proxy.value}.{restriction_suffix}"
213+
else:
214+
proxy_suffix = f"{proxy.value}.address"
215+
operands = (
216+
restriction.get_field("address"),
217+
restriction.get_field("size"),
218+
)
219+
220+
add_operation_variadic(
221+
RawNVVMIntrinsic,
222+
(),
223+
intrinsic=f"llvm.nvvm.fence.proxy.{proxy_suffix}",
224+
operands_=operands,
225+
)

experimental/cuda-lang/src/cuda/lang/_ir/ops.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
RawNVVMIntrinsic,
9393
RawMLIROperation,
9494
InlinePTX,
95+
Fence,
9596
ForeignFunction,
9697
TensorMapAsOpaquePtr,
9798
VectorGetItem,
@@ -939,6 +940,7 @@ def _call_foreign_function_impl(func: Var, return_type: Var, parameters: Var):
939940
"Unary",
940941
"RawNVVMIntrinsic",
941942
"RawMLIROperation",
943+
"Fence",
942944
"ForeignFunction",
943945
"VectorGetItem",
944946
"VectorReduce",

experimental/cuda-lang/src/cuda/lang/_passes/ir2mlir/pass_definition.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1171,6 +1171,17 @@ def lower_atomic_store(
11711171
return []
11721172

11731173

1174+
@mlir_op_lowering(host=False)
1175+
def lower_fence(
1176+
context: DeviceLoweringContext, operation: ops.Fence
1177+
) -> Sequence[mlir.Value]:
1178+
mlir.llvm.add_FenceOp(
1179+
ordering=_get_llvm_memory_ordering(operation.memory_order),
1180+
syncscope=_get_llvm_syncscope(operation.memory_scope),
1181+
)
1182+
return []
1183+
1184+
11741185
@mlir_op_lowering(host=False)
11751186
def lower_alloc_local_memory(
11761187
context: DeviceLoweringContext, operation: ops.AllocLocalMemory

0 commit comments

Comments
 (0)