Skip to content

Commit 0f3463a

Browse files
[lang] Fix inline ptx pointer outputs and reject read-write operands
Signed-off-by: Asher Mancinelli <amancinelli@nvidia.com>
1 parent f82589c commit 0f3463a

5 files changed

Lines changed: 191 additions & 40 deletions

File tree

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,7 @@
168168
memory_barrier,
169169
bitcast,
170170
assert_,
171+
clock,
171172
)
172173
from cuda.tile._stub import (
173174
Constant,
@@ -500,4 +501,5 @@
500501
"MatrixLoadSourceFormat",
501502
"MatrixStoreShape",
502503
"_debug",
504+
"clock",
503505
)

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

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ def require_inline_ptx_pair(var: Var) -> tuple[Var, Var]:
6262
"+": InlinePTX.RMWMode.READ_WRITE,
6363
}
6464

65-
_INLINE_PTX_TYPECODES = {
65+
_INLINE_PTX_TYPECODES = (
6666
"h",
6767
"r",
6868
"l",
6969
"f",
7070
"d",
71-
"C",
72-
}
71+
"p",
72+
)
7373

7474
_INLINE_PTX_SCALAR_DTYPE_FROM_TYPECODE = {
7575
"h": datatype.int16,
@@ -97,12 +97,13 @@ def parse_inline_ptx_constraint(var: Var) -> tuple[str, InlinePTX.RMWMode, str]:
9797
f"Unknown constraint rmw modifier {prefix!r}, expected "
9898
"'' (meaning readonly), '+' (meaning readwrite), or '=' (meaning writeonly)"
9999
)
100-
101100
if type_char not in _INLINE_PTX_TYPECODES:
102101
expected = ", ".join(_INLINE_PTX_TYPECODES)
103102
raise TypeCheckingError(
104103
f"Unknown constraint dtype {type_char!r}, expected one of {expected}"
105104
)
105+
if mode is InlinePTX.RMWMode.READ_WRITE:
106+
raise TypeCheckingError("Read-write inline_ptx constraints are not supported")
106107

107108
return constraint_str, mode, type_char
108109

@@ -111,14 +112,17 @@ def validate_inline_ptx_operand(
111112
constraint_str: str, mode: InlinePTX.RMWMode, type_char: str, value: Var
112113
) -> InlinePTXOperand:
113114
if mode is InlinePTX.RMWMode.WRITE_ONLY:
114-
if type_char == "C":
115-
# write-only arguments require specifying the output data type, but we don't
116-
# expose a dtype for pointers. Disallow this for now.
117-
raise TypeCheckingError(
118-
"Write-only pointer outputs are not supported for inline_ptx"
115+
actual_dtype = require_dtype_spec(value)
116+
if type_char == "p":
117+
if not is_pointer_dtype(actual_dtype):
118+
raise TypeCheckingError(
119+
f"Expected a pointer dtype for constraint {constraint_str}, "
120+
f"got {actual_dtype}"
121+
)
122+
return InlinePTXOperand(
123+
mode=mode, type_code=type_char, value=actual_dtype
119124
)
120125

121-
actual_dtype = require_dtype_spec(value)
122126
expected_dtype = _INLINE_PTX_SCALAR_DTYPE_FROM_TYPECODE[type_char]
123127
if actual_dtype != expected_dtype:
124128
raise TypeCheckingError(
@@ -127,7 +131,7 @@ def validate_inline_ptx_operand(
127131
)
128132
return InlinePTXOperand(mode=mode, type_code=type_char, value=actual_dtype)
129133

130-
if type_char == "C":
134+
if type_char == "p":
131135
require_pointer_type(value)
132136
return InlinePTXOperand(mode=mode, type_code=type_char, value=value)
133137

@@ -192,7 +196,15 @@ def rewrite(match: re.Match[str]) -> str:
192196

193197
return ptx_interpolation_replacements[index]
194198

195-
mlir_ptx_code = _INLINE_PTX_PLACEHOLDER_RE.sub(rewrite, ptx_code)
199+
ptx_fragments = []
200+
for fragment in ptx_code.split("%%"):
201+
fragment = _INLINE_PTX_PLACEHOLDER_RE.sub(rewrite, fragment)
202+
if "%" in fragment:
203+
raise TypeCheckingError(
204+
"Literal percent signs in inline_ptx must be escaped as '%%'"
205+
)
206+
ptx_fragments.append(fragment)
207+
mlir_ptx_code = "%".join(ptx_fragments)
196208
return (
197209
mlir_ptx_code,
198210
tuple(ro_args),

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

Lines changed: 0 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -41,22 +41,6 @@ def matrix_impl_registry() -> ImplRegistry:
4141
return _registry
4242

4343

44-
def ldmatrix_intrinsic_name(
45-
shape: MatrixLoadShape,
46-
count: int,
47-
transpose: bool,
48-
source_format: MatrixLoadSourceFormat | None,
49-
) -> str:
50-
name = f"llvm.nvvm.ldmatrix.sync.aligned.{shape.value}.x{count}"
51-
if transpose:
52-
name += ".trans"
53-
if source_format is MatrixLoadSourceFormat.B6X16_P32:
54-
return name + ".b8x16.b6x16_p32"
55-
if source_format is MatrixLoadSourceFormat.B4X16_P64:
56-
return name + ".b8x16.b4x16_p64"
57-
return name + (".b16" if shape is MatrixLoadShape.M8N8 else ".b8")
58-
59-
6044
@impl(load_store_matrix.load_matrix)
6145
def load_matrix_impl(
6246
src: Var,

experimental/cuda-lang/src/cuda/lang/_stub/core_api.py

Lines changed: 26 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,15 @@
77
import cuda.lang as cl
88
from cuda.lang._execution import stub, function
99
from cuda.lang._exception import TypeCheckingError
10-
from cuda.tile._stub import Array as TileArray, cdiv as tile_cdiv
10+
from cuda.tile._stub import (
11+
Array as TileArray,
12+
cdiv as tile_cdiv,
13+
static_assert,
14+
static_eval,
15+
)
1116
from cuda.lang._enums import MemoryOrder
1217
from cuda.tile._memory_model import MemoryScope, MemorySpace
13-
from cuda.lang._datatype import DType
18+
from cuda.lang._datatype import DType, uint32, uint64
1419
from .types import Pointer, Scalar, Vector
1520

1621
T = TypeVar("T")
@@ -408,7 +413,7 @@ def vote_ballot_sync(predicate: bool, mask: int = FULL_MASK) -> int:
408413
def _inline_ptx(ptx_code: str, *constraint_pairs: tuple) -> tuple:
409414
"""Execute inline PTX.
410415
411-
The API mirrors CUDA C++'s device-side `asm` statement:
416+
The API follows CUDA C++'s device-side `asm` statement:
412417
`cl._inline_ptx(ptx_code, (constraint1, value1), (constraint2, value2), ...)`.
413418
414419
Args:
@@ -419,14 +424,12 @@ def _inline_ptx(ptx_code: str, *constraint_pairs: tuple) -> tuple:
419424
Constraints must be compile-time constant strings.
420425
421426
Read-only operands use constraints ``"h"``, ``"r"``, ``"l"``,
422-
``"f"``, ``"d"``, or ``"C"`` and are paired with runtime values.
427+
``"f"``, ``"d"``, or ``"p"`` and are paired with runtime values.
423428
424429
Write-only operands use constraints ``"=h"``, ``"=r"``, ``"=l"``,
425-
``"=f"``, or ``"=d"`` and are paired with dtype specs.
426-
This determines the type of the output.
427-
428-
Read-write operands use constraints ``"+h"``, ``"+r"``, ``"+l"``,
429-
``"+f"``, ``"+d"``, or ``"+C"`` and are paired with runtime values.
430+
``"=f"``, ``"=d"``, or ``"=p"`` and are paired with dtype specs.
431+
Use a pointer dtype with ``"=p"``. The dtype determines the type
432+
of the output.
430433
431434
Returns:
432435
@@ -467,7 +470,11 @@ def _inline_ptx(ptx_code: str, *constraint_pairs: tuple) -> tuple:
467470
- ``l``: ``cl.int64``
468471
- ``f``: ``cl.float32``
469472
- ``d``: ``cl.float64``
470-
- ``C``: pointer value from ``array.get_base_pointer()``
473+
- ``p``: pointer value, or a pointer dtype for an output
474+
- ``p`` is not in CUDA C++'s inline ptx. The compiler selects the
475+
correct register size for the pointer based on its address space.
476+
- Use ``%0``, ``%1``, and so on for operands. Escape literal percent
477+
signs with a second percent sign, as in ``%%clock``.
471478
472479
"""
473480

@@ -477,6 +484,15 @@ def ptx_comment(comment: str):
477484
_inline_ptx(cl.static_eval("// " + comment))
478485

479486

487+
@function()
488+
def clock(dtype=uint32):
489+
static_assert(dtype in (uint32, uint64))
490+
fn = static_eval(
491+
nvvm.read_ptx_sreg_clock if dtype is uint32 else nvvm.read_ptx_sreg_clock64
492+
)
493+
return fn()
494+
495+
480496
@stub
481497
def atomic_add(
482498
ptr: Pointer[T],

experimental/cuda-lang/test/test_inline_ptx.py

Lines changed: 139 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import pytest
66
import cuda.lang as cl
7-
from cuda.lang._exception import TypeCheckingError
7+
from cuda.lang._exception import TypeCheckingError, StaticAssertionError
88
import torch
99

1010
from .util import compile_kernel
@@ -59,7 +59,7 @@ def kernel(inp, out):
5959
(value,) = cl._inline_ptx(
6060
"ld.global.u32 %0, [%1];",
6161
("=r", cl.int32),
62-
("C", inp_ptr),
62+
("p", inp_ptr),
6363
)
6464
out[0] = value
6565

@@ -69,6 +69,74 @@ def kernel(inp, out):
6969
assert out.cpu().tolist() == [42]
7070

7171

72+
def test_inline_ptx_pointer_output():
73+
@cl.kernel
74+
def kernel(inp, out):
75+
inp_ptr = inp.get_base_pointer()
76+
dtype = cl.pointer_dtype(cl.int32)
77+
(ptr,) = cl._inline_ptx(
78+
"mov.u64 %0, %1;",
79+
("=p", dtype),
80+
("p", inp_ptr),
81+
)
82+
cl.static_assert(cl.dtype_of(ptr) == dtype)
83+
out[0] = ptr.load()
84+
85+
inp = torch.tensor([42], dtype=torch.int32, device="cuda")
86+
out = torch.zeros(1, dtype=torch.int32, device="cuda")
87+
cl.launch(torch.cuda.current_stream(), (1,), (1,), kernel, (inp, out))
88+
assert out.cpu().tolist() == [42]
89+
90+
91+
def test_inline_ptx_shared_pointer_output():
92+
@cl.kernel
93+
def kernel(out):
94+
shared = cl.shared_array(1, cl.int32)
95+
shared[0] = 42
96+
shared_ptr = shared.get_base_pointer()
97+
dtype = cl.pointer_dtype(cl.int32, cl.MemorySpace.SHARED)
98+
(result,) = cl._inline_ptx(
99+
"mov.u32 %0, %1;",
100+
("=p", dtype),
101+
("p", shared_ptr),
102+
)
103+
cl.static_assert(cl.dtype_of(result) == dtype)
104+
out[0] = result.load()
105+
106+
out = torch.zeros(1, dtype=torch.int32, device="cuda")
107+
cl.launch(torch.cuda.current_stream(), (1,), (1,), kernel, (out,))
108+
assert out.cpu().tolist() == [42]
109+
110+
111+
def test_inline_ptx_special_register_operand():
112+
@cl.kernel
113+
def kernel():
114+
clock = cl._nvvm.read_ptx_sreg_clock()
115+
cl._inline_ptx(
116+
"mov.u32 %0, %1;",
117+
("=r", cl.int32),
118+
("r", clock),
119+
)
120+
121+
compile_kernel(kernel, assert_in_ptx="%clock")
122+
123+
124+
@pytest.mark.xfail(
125+
strict=True,
126+
reason="needs llvm version bump",
127+
)
128+
def test_inline_ptx_escaped_special_register():
129+
@cl.kernel
130+
def kernel():
131+
cl._inline_ptx("mov.u32 %0, %%clock;", ("=r", cl.int32))
132+
133+
compile_kernel(
134+
kernel,
135+
assert_in_ptx="%clock",
136+
assert_not_in_ptx="%%clock",
137+
)
138+
139+
72140
class TestInlinePTXErrors:
73141

74142
def test_invalid_type_constraint(self):
@@ -82,6 +150,53 @@ def kernel():
82150
),
83151
)
84152

153+
def test_cuda_c_constraint_is_not_supported(self):
154+
def kernel():
155+
cl._inline_ptx("// no operation", ("C", 0))
156+
157+
compile_kernel(
158+
kernel,
159+
raises=pytest.raises(
160+
TypeCheckingError, match="Unknown constraint dtype 'C'"
161+
),
162+
)
163+
164+
def test_pointer_output_requires_pointer_dtype(self):
165+
def kernel():
166+
cl._inline_ptx("mov.u64 %0, 0;", ("=p", cl.int64))
167+
168+
compile_kernel(
169+
kernel,
170+
raises=pytest.raises(
171+
TypeCheckingError,
172+
match="Expected a pointer dtype for constraint =p, got int64",
173+
),
174+
)
175+
176+
def test_read_write_constraint_is_not_supported(self):
177+
def kernel():
178+
cl._inline_ptx("add.u32 %0, %0, 1;", ("+r", 2))
179+
180+
compile_kernel(
181+
kernel,
182+
raises=pytest.raises(
183+
TypeCheckingError,
184+
match="Read-write inline_ptx constraints are not supported",
185+
),
186+
)
187+
188+
def test_special_register_is_not_supported(self):
189+
def kernel():
190+
cl._inline_ptx("mov.u32 %0, %clock;", ("=r", cl.int32))
191+
192+
compile_kernel(
193+
kernel,
194+
raises=pytest.raises(
195+
TypeCheckingError,
196+
match="Literal percent signs in inline_ptx must be escaped",
197+
),
198+
)
199+
85200
def test_invalid_rmw_constraint(self):
86201
def kernel():
87202
cl._inline_ptx(
@@ -96,3 +211,25 @@ def kernel():
96211
TypeCheckingError, match="Unknown constraint rmw modifier '@'"
97212
),
98213
)
214+
215+
216+
@pytest.mark.parametrize("dtype", (cl.uint32, cl.uint64))
217+
def test_clock(dtype):
218+
@cl.kernel
219+
def kernel():
220+
cl.shared_array(1, dtype)[0] = cl.clock(dtype)
221+
222+
check = "%clock"
223+
if dtype is cl.uint64:
224+
check += "64"
225+
226+
compile_kernel(kernel, assert_in_ptx=check)
227+
228+
229+
@pytest.mark.parametrize("dtype", (cl.int16, cl.int32, cl.int64, cl.float32, cl.bool_))
230+
def test_clock_invalid_dtype(dtype):
231+
@cl.kernel
232+
def kernel():
233+
cl.shared_array(1, dtype)[0] = cl.clock(dtype)
234+
235+
compile_kernel(kernel, raises=pytest.raises(StaticAssertionError))

0 commit comments

Comments
 (0)