Skip to content

Commit 83cb18b

Browse files
[lang] Add stubs for autogenerated ergonomic nvvm intrinsics
Signed-off-by: Asher Mancinelli <amancinelli@nvidia.com>
1 parent a457c0a commit 83cb18b

11 files changed

Lines changed: 1166 additions & 9 deletions

File tree

.flake8

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@ exclude =
1515
docs/source/stubs,
1616
docs/venv,
1717
docs/source/_templates,
18-
experimental/cuda-lang/src/cuda/lang/_stub/nvvm.py
19-
experimental/cuda-lang/src/cuda/lang/_stub/_libdevice.py
18+
experimental/cuda-lang/src/cuda/lang/_stub/nvvm.py,
19+
experimental/cuda-lang/src/cuda/lang/_stub/nvvm_mlir_interfaces.py,
20+
experimental/cuda-lang/src/cuda/lang/_stub/_libdevice.py,
2021
per-file-ignores =
2122
src/cuda/tile/_stub.py: W291

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,10 @@
4141
is_signed,
4242
get_signedness,
4343
default_int_type,
44+
integer_dtype,
4445
is_pointer_dtype,
46+
pointer_dtype,
47+
opaque_pointer_dtype,
4548
PointerInfo,
4649
_define_dtype, _DTypeDefinition,
4750
)
@@ -153,9 +156,13 @@ def satisfies_pointer_constraint(value, constraint: OpaquePointerSpec):
153156
"is_integral",
154157
"is_signed",
155158
"is_any_pointer",
159+
"is_pointer_dtype",
160+
"pointer_dtype",
161+
"opaque_pointer_dtype",
156162
"satisfies_pointer_constraint",
157163
"is_literal_or_exact_dtype",
158164
"get_signedness",
165+
"integer_dtype",
159166
"bool_",
160167
"uint8",
161168
"uint16",

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

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1134,6 +1134,14 @@ class RawNVVMIntrinsic(Operation, opcode="nvvm.call_intrinsic",
11341134
operands_: tuple[Var, ...] = operand()
11351135

11361136

1137+
@dataclass(eq=False)
1138+
class RawMLIROperation(Operation, opcode="mlir.operation",
1139+
memory_effect=MemoryEffect.STORE):
1140+
op_name: str = attribute()
1141+
operands_: tuple[Var, ...] = operand()
1142+
mlir_attributes: tuple[tuple[str, mlir.Attribute], ...] = attribute(default=())
1143+
1144+
11371145
def require_scalar_tile_type(value: Var, valid_dtypes: tuple[datatype.DType, ...] = ()) -> TileTy:
11381146
value_ty = require_tile_type(value)
11391147
if value_ty.ndim != 0:
@@ -1143,12 +1151,6 @@ def require_scalar_tile_type(value: Var, valid_dtypes: tuple[datatype.DType, ...
11431151
return value_ty
11441152

11451153

1146-
def _require_nvvm_intrinsic_name(intrinsic: str) -> str:
1147-
if not intrinsic.startswith("llvm."):
1148-
raise TileTypeError(f"Expected intrinsic name to start with 'llvm.', but got {intrinsic!r}")
1149-
return intrinsic
1150-
1151-
11521154
def shfl_sync_impl(mode: str, mask: Var, value: Var, lane_mask: Var, width: Var) -> Var:
11531155
"""
11541156
Implements the instructions as the psuedocode in the NVVM IR spec.

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -967,6 +967,24 @@ def lower_raw_nvvm_intrinsic(
967967

968968
return tuple(self._lower_intrinsic_result(mlir_values))
969969

970+
@lower_operation.register
971+
def lower_raw_mlir_operation(
972+
self, operation: ops.RawMLIROperation
973+
) -> Sequence[mlir.Value]:
974+
operands = tuple(self.get_var(operand) for operand in operation.operands_)
975+
result_types = tuple(
976+
ir_type_to_mlir_type(result_var.get_type())
977+
for result_var in operation.result_vars
978+
)
979+
results = mlir.add_operation(
980+
name=operation.op_name,
981+
result_type=result_types,
982+
operands=operands,
983+
properties=(),
984+
attributes=operation.mlir_attributes,
985+
)
986+
return tuple(results)
987+
970988
@lower_operation.register
971989
def lower_raw_where(self, operation: ops.RawWhereOperation) -> Sequence[mlir.Value]:
972990
cond_i8 = self.get_var(operation.cond)

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,12 @@
88
static_assert,
99
static_iter,
1010
)
11-
from cuda.tile._datatype import is_pointer_dtype, pointer_dtype, opaque_pointer_dtype, PointerInfo
11+
from cuda.tile._datatype import (
12+
is_pointer_dtype,
13+
pointer_dtype,
14+
opaque_pointer_dtype,
15+
PointerInfo,
16+
)
1217

1318
from . import nvvm
1419
from . import libdevice
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
# SPDX-FileCopyrightText: Copyright (c) <2026> NVIDIA CORPORATION & AFFILIATES. All rights reserved.
2+
#
3+
# SPDX-License-Identifier: Apache-2.0
4+
5+
from dataclasses import dataclass
6+
from enum import Enum
7+
from typing import Any, Callable, Literal, TypeVar
8+
9+
import cuda.lang._datatype as datatype
10+
from cuda.lang._execution import stub
11+
import cuda.lang._mlir as mlir
12+
from cuda.lang._stub._nvvm_support import (
13+
_IntrinsicDTypeAnnotation,
14+
_IntrinsicPredicateAnnotation,
15+
)
16+
from cuda.lang._ir.type import TileTy
17+
from cuda.tile import TileTypeError, TileValueError
18+
from cuda.tile._ir.op_impl import (
19+
require_constant_bool,
20+
require_constant_enum,
21+
require_constant_int,
22+
)
23+
from cuda.tile._ir.ir import Var, add_operation_variadic
24+
from cuda.tile._ir.ops import (
25+
implicit_cast,
26+
build_tuple,
27+
)
28+
29+
30+
FuncTy = TypeVar("FuncTy", bound=Callable[..., Any])
31+
32+
33+
@dataclass(frozen=True)
34+
class ArgSpec:
35+
type: object
36+
kind: Literal["operand", "attribute"] = "operand"
37+
optional: bool = False
38+
variadic: bool = False
39+
unit: bool = False
40+
name: str = ""
41+
42+
43+
@dataclass(frozen=True)
44+
class ResultSpec:
45+
name: str
46+
type: object
47+
optional: bool = False
48+
variadic: bool = False
49+
50+
51+
def is_none_constant(value: Var) -> bool:
52+
return value.is_constant() and value.get_constant() is None
53+
54+
55+
def is_enum_type(ty) -> bool:
56+
return isinstance(ty, type) and issubclass(ty, Enum)
57+
58+
59+
def cast_operand(spec: ArgSpec, arg: Var) -> Var:
60+
target_type = spec.type
61+
src_type = arg.get_type()
62+
ctx = f"Attempting to cast argument to {target_type=}"
63+
match target_type:
64+
case _IntrinsicPredicateAnnotation():
65+
target_type.predicate(arg)
66+
return arg
67+
case _IntrinsicDTypeAnnotation():
68+
return implicit_cast(arg, target_type.dtype, ctx)
69+
case tuple():
70+
for target in target_type:
71+
try:
72+
return implicit_cast(arg, target.dtype, ctx)
73+
except (TileTypeError, TileValueError):
74+
pass
75+
options = ", ".join([str(t) for t in target_type])
76+
raise TileTypeError(
77+
f"Could not cast arg of type {src_type} to any of {options}"
78+
)
79+
case _:
80+
raise TileTypeError("Expected a predicate, a dtype, or a tuple of dtypes")
81+
82+
83+
def make_mlir_attribute(spec: ArgSpec, arg: Var) -> tuple[str, mlir.Attribute] | None:
84+
if spec.optional and is_none_constant(arg):
85+
return None
86+
87+
if is_enum_type(spec.type):
88+
attr_cls = getattr(mlir.nvvm, spec.type.__name__ + "Attr")
89+
arg = require_constant_enum(arg, spec.type)
90+
return spec.name, attr_cls(value=arg)
91+
92+
if spec.unit:
93+
arg = require_constant_bool(arg)
94+
return (spec.name, mlir.UnitAttr()) if arg else None
95+
96+
dtype = spec.type.dtype
97+
if dtype is datatype.bool_:
98+
arg = require_constant_bool(arg)
99+
return spec.name, mlir.BoolAttr(value=arg)
100+
101+
if datatype.is_integral(dtype):
102+
arg = require_constant_int(arg)
103+
ty = mlir.IntegerType.signless(dtype.bitwidth)
104+
attr = mlir.IntegerAttr.make(ty, int(arg))
105+
return spec.name, attr
106+
107+
raise TileTypeError(f"Cannot convert argument into attribute: {spec}")
108+
109+
110+
def get_raw_mlir_parts(
111+
arg_specs, has_operand_segment_sizes, args: tuple[Var, ...]
112+
) -> tuple[tuple[Var, ...], tuple[tuple[str, mlir.Attribute], ...]]:
113+
operands = []
114+
attributes = []
115+
operand_segment_sizes = []
116+
for arg, spec in zip(args, arg_specs, strict=True):
117+
if spec.kind == "attribute":
118+
attr = make_mlir_attribute(spec, arg)
119+
if attr is not None:
120+
attributes.append(attr)
121+
122+
elif spec.optional and is_none_constant(arg):
123+
operand_segment_sizes.append(0)
124+
125+
elif spec.variadic:
126+
assert isinstance(arg, tuple)
127+
operands.extend(cast_operand(spec, item) for item in arg)
128+
operand_segment_sizes.append(len(arg))
129+
else:
130+
operands.append(cast_operand(spec, arg))
131+
operand_segment_sizes.append(1)
132+
133+
if has_operand_segment_sizes:
134+
attributes.append(
135+
("operandSegmentSizes", mlir.DenseI32ArrayAttr(operand_segment_sizes))
136+
)
137+
138+
return tuple(operands), tuple(attributes)
139+
140+
141+
def _raw_nvvm_mlir_operation_impl(stub_func, *args: Var):
142+
from cuda.lang._ir.ops import RawMLIROperation
143+
144+
result_types = tuple(TileTy(ty.type.dtype) for ty in stub_func._results)
145+
operands, attrs = get_raw_mlir_parts(
146+
stub_func._args, stub_func._attr_sized_operand_segments, args
147+
)
148+
results = add_operation_variadic(
149+
RawMLIROperation,
150+
result_types,
151+
op_name=stub_func._op_name,
152+
operands_=operands,
153+
mlir_attributes=attrs,
154+
)
155+
match len(stub_func._results):
156+
case 0:
157+
return None
158+
case 1:
159+
return results[0]
160+
case _:
161+
return build_tuple(results)
162+
163+
164+
_raw_nvvm_mlir_operation_impl._is_coroutine = False
165+
166+
167+
def nvvm_mlir_interface_stub(
168+
*,
169+
op_name: str,
170+
attr_sized_operand_segments: bool = False,
171+
results: tuple[ResultSpec, ...] = (),
172+
args: tuple[ArgSpec, ...] = (),
173+
) -> Callable[[FuncTy], FuncTy]:
174+
def decorate(func: FuncTy) -> FuncTy:
175+
func = stub(func)
176+
func._cutile_custom_implementation_handler = _raw_nvvm_mlir_operation_impl
177+
func._op_name = op_name
178+
func._attr_sized_operand_segments = attr_sized_operand_segments
179+
func._results = results
180+
func._args = args
181+
return func
182+
183+
return decorate
184+
185+
186+
__all__ = ("ArgSpec", "ResultSpec", "nvvm_mlir_interface_stub")

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,10 +119,13 @@ def _get_annotation(type_hint) -> _IntrinsicDTypeAnnotation | _IntrinsicPredicat
119119
I16 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.int16)]
120120
I32 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.int32)]
121121
I64 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.int64)]
122+
U32 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.uint32)]
123+
U64 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.uint64)]
122124
IX = Annotated[Any, _IntrinsicPredicateAnnotation(require_integer_0d_tile_type)]
123125
P0 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype())]
124126
P1 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype(MemorySpace.GLOBAL))]
125127
P3 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype(MemorySpace.SHARED))]
128+
P4 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype(MemorySpace.CONSTANT))]
126129
P5 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype(MemorySpace.LOCAL))]
127130
P6 = Annotated[Any, _IntrinsicDTypeAnnotation(datatype.opaque_pointer_dtype(MemorySpace.TENSOR))]
128131
P7 = Annotated[Any, _IntrinsicDTypeAnnotation(

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,15 @@ def decorate(func):
117117
P0,
118118
P1,
119119
P3,
120+
P4,
120121
P5,
121122
P6,
122123
P7,
123124
PX,
125+
U32,
126+
U64,
127+
VX,
128+
X,
124129
_IntrinsicDTypeAnnotation,
125130
)
126131

0 commit comments

Comments
 (0)