From 4e956b3bd4e703fcd3dbe8778e912ac3e52a7ef7 Mon Sep 17 00:00:00 2001 From: Xiaochun Tong Date: Tue, 17 Dec 2024 10:49:38 -0500 Subject: [PATCH] __getitem__ works --- luisa_lang/_builtin_decor.py | 90 ++++++++--- luisa_lang/codegen/cpp.py | 31 +++- luisa_lang/codegen/cpp_lib.py | 2 +- luisa_lang/hir.py | 103 ++++++++++-- luisa_lang/lang_builtins.py | 41 +++-- luisa_lang/parse.py | 287 +++++++++++++++++----------------- scripts/cpp_lib.hpp | 17 +- scripts/gen_cpp_lib.py | 18 ++- 8 files changed, 385 insertions(+), 204 deletions(-) diff --git a/luisa_lang/_builtin_decor.py b/luisa_lang/_builtin_decor.py index a3eb212..4f559f1 100644 --- a/luisa_lang/_builtin_decor.py +++ b/luisa_lang/_builtin_decor.py @@ -68,7 +68,9 @@ class _ObjKind(Enum): KERNEL = auto() -def _make_func_template(f: Callable[..., Any], func_name: str, func_sig: Optional[MethodType], func_globals: Dict[str, Any], foreign_type_var_ns: Dict[TypeVar, hir.Type | hir.ComptimeValue], props: hir.FuncProperties, self_type: Optional[hir.Type] = None): +def _make_func_template(f: Callable[..., Any], func_name: str, func_sig: Optional[MethodType], + func_globals: Dict[str, Any], foreign_type_var_ns: Dict[TypeVar, hir.Type | hir.ComptimeValue], + props: hir.FuncProperties, self_type: Optional[hir.Type] = None): # parsing_ctx = _parse.ParsingContext(func_name, func_globals) # func_sig_parser = _parse.FuncParser(func_name, f, parsing_ctx, self_type) # func_sig = func_sig_parser.parsed_func @@ -91,7 +93,8 @@ def parsing_func(args: hir.FunctionTemplateResolvingArgs) -> hir.Function: mapped_implicit_type_params: Dict[str, hir.Type] = dict() assert func_sig is not None - type_parser = parse.TypeParser(func_name, func_globals, type_var_ns, self_type, 'instantiate') + type_parser = parse.TypeParser( + func_name, func_globals, type_var_ns, self_type, 'instantiate') for (tv, t) in func_sig.env.items(): type_var_ns[tv] = unwrap(type_parser.parse_type(t)) if is_generic: @@ -115,7 +118,7 @@ def parsing_func(args: hir.FunctionTemplateResolvingArgs) -> hir.Function: mapped_type = mapping[gp] assert isinstance(mapped_type, hir.Type) mapped_implicit_type_params[name] = mapped_type - + func_sig_instantiated, _p = parse.convert_func_signature( func_sig, func_name, func_globals, type_var_ns, mapped_implicit_type_params, self_type, mode='instantiate') # print(func_name, func_sig) @@ -124,10 +127,10 @@ def parsing_func(args: hir.FunctionTemplateResolvingArgs) -> hir.Function: assert not isinstance( func_sig_instantiated.return_type, hir.SymbolicType) func_parser = parse.FuncParser( - func_name, f, func_sig_instantiated, func_globals, type_var_ns, self_type) + func_name, f, func_sig_instantiated, func_globals, type_var_ns, self_type, props.returning_ref) ret = func_parser.parse_body() ret.inline_hint = props.inline - ret.export = props.export + ret.export = props.export return ret params = [v[0] for v in func_sig.args] is_generic = len(func_sig_converted.generic_params) > 0 @@ -162,10 +165,14 @@ def _dsl_func_impl(f: _TT, kind: _ObjKind, attrs: Dict[str, Any]) -> _TT: # return cast(_T, f) -def _dsl_struct_impl(cls: type[_TT], attrs: Dict[str, Any], ir_ty_override: hir.Type | None = None) -> type[_TT]: - ctx = hir.GlobalContext.get() +_MakeTemplateFn = Callable[[List[hir.GenericParameter]], hir.Type] +_InstantiateFn = Callable[[List[Any]], hir.Type] + +def _dsl_struct_impl(cls: type[_TT], attrs: Dict[str, Any], ir_ty_override: hir.Type | Tuple[_MakeTemplateFn, _InstantiateFn] | None = None, opqaue_override: str | None = None) -> type[_TT]: + ctx = hir.GlobalContext.get() register_class(cls) + assert not (ir_ty_override is not None and opqaue_override is not None) cls_info = class_typeinfo(cls) globalns = _get_cls_globalns(cls) globalns[cls.__name__] = cls @@ -173,6 +180,8 @@ def _dsl_struct_impl(cls: type[_TT], attrs: Dict[str, Any], ir_ty_override: hir. for type_var in cls_info.type_vars: type_var_to_generic_param[type_var] = hir.GenericParameter( type_var.__name__, cls.__qualname__) + generic_params = [type_var_to_generic_param[tv] + for tv in cls_info.type_vars] def parse_fields(tp: parse.TypeParser, self_ty: hir.Type): fields: List[Tuple[str, hir.Type]] = [] @@ -182,13 +191,14 @@ def parse_fields(tp: parse.TypeParser, self_ty: hir.Type): raise hir.TypeInferenceError( None, f"Cannot infer type for field {name} of {cls.__name__}") fields.append((name, field_ty)) - if isinstance(self_ty, hir.StructType): - self_ty.fields = fields - elif isinstance(self_ty, hir.BoundType): - assert isinstance(self_ty.instantiated, hir.StructType) - self_ty.instantiated.fields = fields - else: - raise NotImplementedError() + if len(fields) > 0: + if isinstance(self_ty, hir.StructType): + self_ty.fields = fields + elif isinstance(self_ty, hir.BoundType): + assert isinstance(self_ty.instantiated, hir.StructType) + self_ty.instantiated.fields = fields + else: + raise NotImplementedError() def parse_methods(type_var_ns: Dict[TypeVar, hir.Type | Any], self_ty: hir.Type,): for name in cls_info.methods: @@ -198,16 +208,24 @@ def parse_methods(type_var_ns: Dict[TypeVar, hir.Type | Any], self_ty: hir.Type, props = getattr(method_object, '__luisa_func_props__') else: props = hir.FuncProperties() + if name == '__getitem__': + props.returning_ref = True template = _make_func_template( method_object, get_full_name(method_object), cls_info.methods[name], globalns, type_var_ns, props, self_type=self_ty) if isinstance(self_ty, hir.BoundType): - assert isinstance(self_ty.instantiated, hir.StructType) + assert isinstance(self_ty.instantiated, + (hir.StructType, hir.OpaqueType)) self_ty.instantiated.methods[name] = template else: self_ty.methods[name] = template ir_ty: hir.Type if ir_ty_override is not None: - ir_ty = ir_ty_override + if isinstance(ir_ty_override, hir.Type): + ir_ty = ir_ty_override + else: + ir_ty = ir_ty_override[0](generic_params) + elif opqaue_override is not None: + ir_ty = hir.OpaqueType(opqaue_override) else: ir_ty = hir.StructType( f'{cls.__name__}_{unique_hash(cls.__qualname__)}', cls.__qualname__, []) @@ -226,8 +244,15 @@ def monomorphization_func(args: List[hir.Type | Any]) -> hir.Type: for i, arg in enumerate(args): type_var_ns[cls_info.type_vars[i]] = arg hash_s = unique_hash(f'{cls.__qualname__}_{args}') - inner_ty = hir.StructType( - f'{cls.__name__}_{hash_s}M', f'{cls.__qualname__}[{",".join([str(a) for a in args])}]', []) + inner_ty: hir.Type + if ir_ty_override is not None: + assert isinstance(ir_ty_override, tuple) + inner_ty = ir_ty_override[1](args) + elif opqaue_override: + inner_ty = hir.OpaqueType(opqaue_override, args[:]) + else: + inner_ty = hir.StructType( + f'{cls.__name__}_{hash_s}M', f'{cls.__qualname__}[{",".join([str(a) for a in args])}]', []) mono_self_ty = hir.BoundType(ir_ty, args, inner_ty) mono_type_parser = parse.TypeParser( cls.__qualname__, globalns, type_var_ns, mono_self_ty, 'instantiate') @@ -253,6 +278,22 @@ def _dsl_decorator_impl(obj: _TT, kind: _ObjKind, attrs: Dict[str, Any]) -> _TT: raise NotImplementedError() +def opaque(name: str) -> Callable[[type[_TT]], type[_TT]]: + """ + Mark a class as a DSL opaque type. + + Example: + ```python + @luisa.opaque("Buffer") + class Buffer(Generic[T]): + pass + ``` + """ + def wrapper(cls: type[_TT]) -> type[_TT]: + return _dsl_struct_impl(cls, {}, opqaue_override=name) + return wrapper + + def struct(cls: type[_TT]) -> type[_TT]: """ Mark a class as a DSL struct. @@ -277,6 +318,12 @@ def decorator(cls: type[_TT]) -> type[_TT]: return decorator +def builtin_generic_type(make_template: _MakeTemplateFn, instantiate: _InstantiateFn) -> Callable[[type[_TT]], type[_TT]]: + def decorator(cls: type[_TT]) -> type[_TT]: + return typing.cast(type[_TT], _dsl_struct_impl(cls, {}, ir_ty_override=(make_template, instantiate))) + return decorator + + _KernelType = TypeVar("_KernelType", bound=Callable[..., None]) @@ -310,6 +357,13 @@ def __init__(self, value: str): def _parse_func_kwargs(kwargs: Dict[str, Any]) -> hir.FuncProperties: props = hir.FuncProperties() props.byref = set() + return_ = kwargs.get("return", None) + if return_ is not None: + if return_ == 'ref': + props.returning_ref = True + else: + raise ValueError( + f"invalid value for return: {return_}, expected 'ref'") inline = kwargs.get("inline", False) if isinstance(inline, bool): props.inline = inline diff --git a/luisa_lang/codegen/cpp.py b/luisa_lang/codegen/cpp.py index cef8eb9..068f68f 100644 --- a/luisa_lang/codegen/cpp.py +++ b/luisa_lang/codegen/cpp.py @@ -34,10 +34,16 @@ def gen(self, ty: hir.Type) -> str: def gen_impl(self, ty: hir.Type) -> str: match ty: case hir.IntType(bits=bits, signed=signed): + int_names = { + '8':'byte', + '16':'short', + '32':'int', + '64':'long', + } if signed: - return f"i{bits}" + return f"lc_{int_names[str(bits)]}" else: - return f"u{bits}" + return f"lc_u{int_names[str(bits)]}" case hir.FloatType(bits=bits): match bits: case 16: @@ -77,6 +83,15 @@ def do(): return '' case hir.TypeConstructorType(): return '' + case hir.OpaqueType(): + def do(): + match ty.name: + case 'Buffer': + elem_ty = self.gen(ty.extra_args[0]) + return f'__builtin__Buffer<{elem_ty}>' + case _: + raise NotImplementedError(f"unsupported opaque type: {ty.name}") + return do() case _: raise NotImplementedError(f"unsupported type: {ty}") @@ -167,6 +182,8 @@ def mangle_impl(self, obj: Union[hir.Type, hir.Function]) -> str: case hir.BoundType(): assert obj.instantiated return self.mangle(obj.instantiated) + case hir.OpaqueType(): + return obj.name case _: raise NotImplementedError(f"unsupported object: {obj}") @@ -263,6 +280,16 @@ def gen_ref(self, ref: hir.Ref) -> str: base = self.gen_ref(index.base) idx = self.gen_expr(index.index) return f"{base}[{idx}]" + case hir.IntrinsicRef() as intrin: + def do(): + intrin_name = intrin.name + gened_args = [self.gen_value_or_ref( + arg) for arg in intrin.args] + if intrin_name == 'buffer_ref': + return f"{gened_args[0]}[{gened_args[1]}]" + else: + raise RuntimeError(f"unsupported intrinsic reference: {intrin_name}") + return do() case _: raise NotImplementedError(f"unsupported reference: {ref}") diff --git a/luisa_lang/codegen/cpp_lib.py b/luisa_lang/codegen/cpp_lib.py index 41dfb4e..3e713bc 100644 --- a/luisa_lang/codegen/cpp_lib.py +++ b/luisa_lang/codegen/cpp_lib.py @@ -1 +1 @@ -CPP_LIB_COMPRESSED = """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""" \ No newline at end of file +CPP_LIB_COMPRESSED = """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nfc9ZaDBElH1TrHqo6NJ9b5MnrqUx11D2vthrsRT66I6PXr11OhFDSlKY9vrVJd5Sl3739UpRJJLrLK+++spSSSSSXHB6z7CESUfVOn1YdGk+uMmT11KY6wzFr/rojo/t/d9vfv7RH3fc5kTLbbcQ25kTLbbcQ32+32SXvbq8Xi+4kb7/XGhGh444444iOOHMiZbbbiG9QO3bn70x97PP8E7hatajPOtdyNzWtddaxFauZEy223EN76kkk9+9a33k100rfeSSSTWtc8613I3Na111rEVq5kTLbbcQ3vvqkkkktda1pSiSr27VpSiSSSSSVazExMWGmldyNzWtddaxFauZEy223ENuZEy223EN666pLfbe8Xi+4kb7240I0PHHHHHERxw5kTLbbcQ3rcO3a1p2zztO4WrWozzrXcjc1rXXWsRWrmRMtttxDe+pJJJPfvWt99dNK33kkkk1rXPOtdyNzWtddaxFauZEy223EN776pJJJJJa61rSlK9u1aUokkkkklWuQA6+95ARCKIJCKK3snZOya3u9WsuVay5Vu6bpumMZmZgAAAB9t9fu+342/G+32+34fh9oj16cyJlttuIbcyJlttuIb111SXO3i8Xi+4kb78caEaHjjjjjiI44cyJlttuIb1F1oi2uvr1FwtzyMMOediNjzzzttzEc8uZEy223EN7akkkkk84487bc844kkkk884Yc87EbHnnnbbmI55cyJlttuIb221SSSSSSSXLfO+/PLbSSSSSS+OmmmMY+Ca1rMzGMlrRpPLy8vLaOeXIoAAB8+fPnngAAfPkitttttttt+fMMKUokkkkklYaafkAFwGW5G5rWvfvWIrVzImW224htzImW224hvbbZJeNx+D9b0cBgMLiRzzTx2I7Hx48ePHiI8eHMiZbbbiG9RdaItMa6+vU7vNrcm1g3fbgjg2tbji0RazmRMtttxDfGpJJJJNscbX3k7bWtfeSTa1qmzc7cEcG1rccWiLWcyJlttuIb441SSSSSSSVm7UpRJb72tSlEkkrWtY+Rll58EeD58+fHjzEefLmRMtttxDbmRMtttxDfjx4SXjYeOPBwGAwuJHjxTx2I7Hx48ePHiI8eHMiZbbbiG9Rd5iPM7tdfPmY82tybWDd9uCODa1uOLRFrOZEy223EN8akkkkk2xxtfeSTtta195NrWqbNzt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QoPNEKDzW2vK215W2vK215o15W2vK215W2vK215W2vK215W2vNGvK215W2vK215W2vK215o15W2vK215W2vK215W2vK215W2vNGvK215W2vK215W2vNGvK215W2vK215W2vK215W2vK215W2vK215W2vK215W2vK215o15W2vK215W2vK215W2vK215W2vNGvK215W2vK215W2vK215o15W2vK215W2vK215W2vK215o15W2vK215W2vK215W2vK215o15W2vK215XMzU1l1m3KQfhxPx8PgsOZISdgTf4b59fty/XeIA57Iy5YZcsMrKMsyyZa5bmua5blc25uVcuaufiq++vS+CpV/kJOuW3kfIXqHRX/n/6/9PVRXqkvX933drp7DicVew4mDE9hkwyfhTp4cvquU6g8KfRdJpJNEaTSNJJoj0b1uyrjDLcXMVlxYnvgWWB/wz2iHbtOynuKTCaGyJH1IbobpE3SJuQ8fanR9DumjH6j9b9Zs2Nmxs2Nnqn6fafbmFp7lXl83zXzXzPEk/kNjwPAvAvAeEfV2uF6ydzqvC+yO1ee3buO1d99R0rrLxO68Lo444cx5VH8zp7zw8PNk971v5M1NMa001rWtdJJ6p9j0jf0/Nl80mpPZlivSTxPT08iPHr38eOk1PE508SeHK5a5/UAXDTT7tH20uEuwuiQ7XXdhgNgu/bAC4aaZRnkBryhzRNPI8PDw4STw9Y8deMvUGh4yyvCePHjwR48u/jx0mpXSddevj18iPLy9fHn5+carzTz8/LwR48duuujXR3d3cJ7I4HCcOOOAd+HDhxxwDjjdvvuDf2E3N27cJ2jmTsnDjjgHbhw4cccA443b77g337JbItC222hyMIYGDEJOTly5Cdo5k5ThxxwDtw4cOOOAccbt99wb9hMTtHMnZNGMYZTsdnZoJxG8nCcOOOAduHDhxxwDjjdvvuDfY2bNgnMcScpw444Bzw4cOOOAccbt99wb7GzZsE5jiTlOHHHAOeHDhxxwDjjdvvuDfmRxA5SMTEMJkmRGJMyYwYJgfFNDQmgwYJgYMEwMGCYGJiGEwYJgZIyBiRgwTAwYJgcXEcLlck4rlck4rnLjxTqmWWrVthhhhhhhhhhllhhqamGGGGWWrVq1baDqDIyNJpNawsLCwsLCwsLCwsjIwsLQaDCwsLCyMjSaTSaT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""" \ No newline at end of file diff --git a/luisa_lang/hir.py b/luisa_lang/hir.py index 0f68431..889976a 100644 --- a/luisa_lang/hir.py +++ b/luisa_lang/hir.py @@ -17,7 +17,7 @@ import typing from typing_extensions import override from luisa_lang import classinfo -from luisa_lang.utils import Span, round_to_align +from luisa_lang.utils import Span, round_to_align, unwrap from abc import ABC, abstractmethod PATH_PREFIX = "luisa_lang" @@ -45,11 +45,13 @@ class FuncProperties: inline: bool | Literal["never", "always"] export: bool byref: Set[str] + returning_ref: bool def __init__(self): self.inline = False self.export = False self.byref = set() + self.returning_ref = False class FunctionTemplate: @@ -94,6 +96,11 @@ def resolve(self, args: FunctionTemplateResolvingArgs | None) -> Union["Function def reset(self) -> None: self.__resolved = {} + def inline_hint(self) -> bool | Literal['always', 'never']: + if self.props is None: + return False + return self.props.inline + class DynamicIndex: pass @@ -600,10 +607,12 @@ def __repr__(self) -> str: class OpaqueType(Type): name: str + extra_args: List[Any] - def __init__(self, name: str) -> None: + def __init__(self, name: str, extra: List[Any] | None = None) -> None: super().__init__() self.name = name + self.extra_args = extra or [] def size(self) -> int: raise RuntimeError("OpaqueType has no size") @@ -612,10 +621,10 @@ def align(self) -> int: raise RuntimeError("OpaqueType has no align") def __eq__(self, value: object) -> bool: - return isinstance(value, OpaqueType) and value.name == self.name + return isinstance(value, OpaqueType) and value.name == self.name and value.extra_args == self.extra_args def __hash__(self) -> int: - return hash((OpaqueType, self.name)) + return hash((OpaqueType, self.name, tuple(self.extra_args))) def __str__(self) -> str: return self.name @@ -1003,7 +1012,21 @@ def __str__(self) -> str: def __repr__(self) -> str: return f'Intrinsic({self.name}, {self.args})' + +class IntrinsicRef(Ref): + name: str + args: List[Value | Ref] + + def __init__(self, name: str, args: List[Value | Ref], type: Type, span: Optional[Span] = None) -> None: + super().__init__(type, span) + self.name = name + self.args = args + def __str__(self) -> str: + return f'IntrinsicRef({self.name}, {self.args})' + + def __repr__(self) -> str: + return f'IntrinsicRef({self.name}, {self.args})' class Call(Value): op: "Function" @@ -1068,6 +1091,13 @@ def __str__(self) -> str: return f"Parsing error at {self.span}:\n\t{self.message}" +class InlineError(SpannedError): + def __str__(self) -> str: + if self.span is None: + return f"Inline error:\n\t{self.message}" + return f"Inline error at {self.span}:\n\t{self.message}" + + class TypeInferenceError(SpannedError): def __str__(self) -> str: if self.span is None: @@ -1159,6 +1189,14 @@ def __init__(self, value: Optional[Value], span: Optional[Span] = None) -> None: self.value = value +class ReturnRef(Terminator): + value: Ref + + def __init__(self, value: Ref, span: Optional[Span] = None) -> None: + super().__init__(span) + self.value = value + + class Range(Value): start: Value step: Value @@ -1170,6 +1208,13 @@ def __init__(self, start: Value, stop: Value, step: Value, span: Optional[Span] self.stop = stop self.step = step + def value_type(self) -> Type: + types = [self.start.type, self.stop.type, self.step.type] + for ty in types: + if not isinstance(ty, GenericIntType): + return unwrap(ty) + return unwrap(types[0]) + class ComptimeValue: value: Any @@ -1209,7 +1254,8 @@ class Function: locals: List[Var] complete: bool is_method: bool - inline_hint: bool | Literal['always', 'never'] + _inline_hint: bool | Literal['always', 'never'] + returning_ref: bool def __init__( self, @@ -1217,6 +1263,7 @@ def __init__( params: List[Var], return_type: Type | None, is_method: bool, + returning_ref: bool, ) -> None: self.name = name self.params = params @@ -1226,7 +1273,11 @@ def __init__( self.locals = [] self.complete = False self.is_method = is_method - self.inline_hint = False + self._inline_hint = False + self.returning_ref = returning_ref + + def inline_hint(self) -> bool | Literal['always', 'never']: + return self._inline_hint def match_template_args( @@ -1415,10 +1466,11 @@ def is_type_compatible_to(ty: Type, target: Type) -> bool: class FunctionInliner: mapping: Dict[Ref | Value, Ref | Value] - ret: Value | None + ret: Value | Ref | None def __init__(self, func: Function, args: List[Value | Ref], body: BasicBlock, span: Optional[Span] = None) -> None: self.mapping = {} + self.ret = None for param, arg in zip(func.params, args): self.mapping[param] = arg assert func.body @@ -1435,8 +1487,11 @@ def do_inline(self, func_body: BasicBlock, body: BasicBlock) -> None: self.mapping[node] = Alloca(node.type, node.span) case Load(): mapped_var = self.mapping[node.ref] - assert isinstance(mapped_var, Ref) - body.append(Load(mapped_var)) + if isinstance(node.ref, Ref) and isinstance(mapped_var, Value): + self.mapping[node] = mapped_var + else: + assert isinstance(mapped_var, Ref) + self.mapping[node] = body.append(Load(mapped_var)) case Index(): base = self.mapping.get(node.base) assert isinstance(base, Value) @@ -1471,7 +1526,8 @@ def do(): for arg in call.args: mapped_arg = self.mapping.get(arg) if mapped_arg is None: - raise ParsingError(node, "unable to inline call") + raise InlineError( + node, "unable to inline call") args.append(mapped_arg) assert call.type self.mapping[call] = body.append( @@ -1483,26 +1539,39 @@ def do(): for arg in intrin.args: mapped_arg = self.mapping.get(arg) if mapped_arg is None: - raise ParsingError( + raise InlineError( node, "unable to inline intrinsic") args.append(mapped_arg) assert intrin.type self.mapping[intrin] = body.append( Intrinsic(intrin.name, args, intrin.type, node.span)) do() - case Return(): + case IntrinsicRef() as intrin: + def do(): + args: List[Ref | Value] = [] + for arg in intrin.args: + mapped_arg = self.mapping.get(arg) + if mapped_arg is None: + raise InlineError( + node, "unable to inline intrinsic") + args.append(mapped_arg) + assert intrin.type + self.mapping[intrin] = body.append( + IntrinsicRef(intrin.name, args, intrin.type, node.span)) + do() + case ReturnRef() | Return(): if self.ret is not None: - raise ParsingError(node, "multiple return statement") + raise InlineError(node, "multiple return statement") assert node.value is not None mapped_value = self.mapping.get(node.value) - if mapped_value is None or isinstance(mapped_value, Ref): - raise ParsingError(node, "unable to inline return") + if mapped_value is None: + raise InlineError(node, "unable to inline return") self.ret = mapped_value case _: - raise ParsingError(node, "invalid node for inlining") + raise ParsingError(node, f"invalid node {node} for inlining") @staticmethod - def inline(func: Function, args: List[Value | Ref], body: BasicBlock, span: Optional[Span] = None) -> Value: + def inline(func: Function, args: List[Value | Ref], body: BasicBlock, span: Optional[Span] = None) -> Value | Ref: inliner = FunctionInliner(func, args, body, span) assert inliner.ret return inliner.ret diff --git a/luisa_lang/lang_builtins.py b/luisa_lang/lang_builtins.py index 89960e9..2460eaa 100644 --- a/luisa_lang/lang_builtins.py +++ b/luisa_lang/lang_builtins.py @@ -20,7 +20,7 @@ overload, Any, ) -from luisa_lang._builtin_decor import func, intrinsic +from luisa_lang._builtin_decor import func, intrinsic, opaque from luisa_lang import parse T = TypeVar("T") @@ -105,6 +105,7 @@ def comptime(a): return ComptimeBlock() return a + @func def trap() -> None: """ @@ -120,6 +121,16 @@ def device_assert(cond: bool, msg: str = "") -> typing.NoReturn: raise NotImplementedError( "device_assert should not be called in host-side Python code. ") +@overload +def range(n:T) -> List[T]: ... +@overload +def range(start: T, end: T) -> List[T]: ... +@overload +def range(start: T, end: T, step: T) -> List[T]: ... +def range(*args): + raise NotImplementedError( + "range should not be called in host-side Python code. ") + parse._add_special_function("comptime", comptime) parse._add_special_function("intrinsic", intrinsic) @@ -166,8 +177,8 @@ def typeof(value: Any) -> hir.Type: "_N", "luisa_lang.lang")), typeof(u32) -# @_builtin_type( -# hir.ParametricType( +# @builtin_type( +# hir.ParametricType([_t, _b] # "Array", [hir.TypeParameter(_t, bound=[])], hir.ArrayType(_t, _n) # ) # ) @@ -192,21 +203,22 @@ def typeof(value: Any) -> hir.Type: # ) # @builtin_type( -# # hir.ParametricType( -# # "Buffer", [hir.TypeParameter(_t, bound=[])], hir.OpaqueType("Buffer") -# # ) +# hir.ParametricType( +# [_t], [hir.TypeParameter(_t, bound=[])], hir.OpaqueType("Buffer") +# ) # ) -# class Buffer(Generic[T]): -# def __getitem__(self, index: int | u32 | u64) -> T: -# return _intrinsic_impl() +@opaque("Buffer") +class Buffer(Generic[T]): + def __getitem__(self, index: int | u32 | u64) -> T: + return intrinsic("buffer_ref", T, self, index) # type: ignore -# def __setitem__(self, index: int | u32 | u64, value: T) -> None: -# return _intrinsic_impl() + def __setitem__(self, index: int | u32 | u64, value: T) -> None: + pass -# def __len__(self) -> u32 | u64: -# return _intrinsic_impl() + def __len__(self) -> u64: + return intrinsic("buffer_size", u64, self) # type: ignore # @builtin_type( @@ -232,8 +244,9 @@ def typeof(value: Any) -> hir.Type: __all__: List[str] = [ # 'Pointer', - # 'Buffer', + 'Buffer', # 'Array', + 'range', 'comptime', 'address_of', 'unroll', diff --git a/luisa_lang/parse.py b/luisa_lang/parse.py index 5f9a86b..5a3ffaf 100644 --- a/luisa_lang/parse.py +++ b/luisa_lang/parse.py @@ -185,6 +185,12 @@ def _add_special_function(name: str, f: Callable[..., Any]) -> None: NewVarHint = Literal[False, 'dsl', 'comptime'] +def _friendly_error_message_for_unrecognized_type(ty: Any) -> str: + if ty is range: + return 'expected builtin function range, use lc.range instead' + return f"expected DSL type but got {ty}" + + class FuncParser: name: str @@ -198,19 +204,23 @@ class FuncParser: bb_stack: List[hir.BasicBlock] type_parser: TypeParser break_and_continues: List[hir.Break | hir.Continue] | None + returning_ref: bool def __init__(self, name: str, func: object, signature: hir.FunctionSignature, globalns: Dict[str, Any], type_var_ns: Dict[typing.TypeVar, hir.Type | ComptimeValue], - self_type: Optional[Type]) -> None: + self_type: Optional[Type], + return_ref: bool + ) -> None: self.type_parser = TypeParser( name, globalns, type_var_ns, self_type, 'instantiate') self.name = name self.func = func self.signature = signature self.globalns = copy(globalns) + self.returning_ref = return_ref obj_ast, _obj_file = retrieve_ast_and_filename(func) # print(ast.dump(obj_ast)) assert isinstance(obj_ast, ast.Module), f"{obj_ast} is not a module" @@ -218,7 +228,8 @@ def __init__(self, name: str, raise RuntimeError("Function definition expected.") self.func_def = obj_ast.body[0] self.vars = {} - self.parsed_func = hir.Function(name, [], None, self_type is not None) + self.parsed_func = hir.Function( + name, [], None, self_type is not None, return_ref) self.type_var_ns = type_var_ns self.bb_stack = [] self.break_and_continues = None @@ -262,7 +273,7 @@ def convert_constexpr(self, comptime_val: ComptimeValue, span: Optional[hir.Span dsl_type = get_dsl_type(value) if dsl_type is None: raise hir.ParsingError( - span, f"expected DSL type but got {value}") + span, _friendly_error_message_for_unrecognized_type(value)) return hir.TypeValue(dsl_type) return None @@ -391,12 +402,16 @@ def parse_type_arg(expr: ast.expr) -> hir.Type: else: # check __getitem__ if (method := value.type.method("__getitem__")) and method: - ret = self.parse_call_impl( - span, method, [value, index]) + try: + ret = self.parse_call_impl_ref( + span, method, [value, index]) + except hir.InlineError as e: + raise hir.InlineError( + expr, f"error during inlining of __getitem__, note that __getitem__ must be inlineable {e}") from e if isinstance(ret, hir.TemplateMatchingError): raise hir.TypeInferenceError( expr, f"error calling __getitem__: {ret.message}") - return self.cur_bb().append(hir.LocalRef(ret)) + return ret else: raise hir.TypeInferenceError( expr, f"indexing not supported for type {value.type}") @@ -441,72 +456,25 @@ def do(expr: ast.Attribute): # print(type(expr), type(expr) is ast.Attribute) raise NotImplementedError() # unreachable - # def parse_access(self, expr: ast.Subscript | ast.Attribute) -> hir.Value | ComptimeValue: - # span = hir.Span.from_ast(expr) - # if isinstance(expr, ast.Subscript): - # value = self.parse_expr(expr.value) - # if isinstance(value, ComptimeValue): - # raise hir.ParsingError( - # expr, "attempt to access comptime value in DSL code; wrap it in lc.comptime(...) if you intead to use it as a compile-time expression") - # if isinstance(value, hir.TypeValue): - # type_args: List[hir.Type] = [] - - # def parse_type_arg(expr: ast.expr) -> hir.Type: - # type_annotation = self.eval_expr(expr) - # type_hint = classinfo.parse_type_hint(type_annotation) - # ty = self.parse_type(type_hint) - # assert ty - # return ty - - # match expr.slice: - # case ast.Tuple(): - # for e in expr.slice.elts: - # type_args.append(parse_type_arg(e)) - # case _: - # type_args.append(parse_type_arg(expr.slice)) - # # print(f"Type args: {type_args}") - # assert isinstance(value.type, hir.TypeConstructorType) and isinstance( - # value.type.inner, hir.ParametricType) - # return hir.TypeValue( - # hir.BoundType(value.type.inner, type_args, value.type.inner.instantiate(type_args))) - - # assert value.type - # index = self.parse_expr(expr.slice) - # index = self.convert_to_value(index, span) - # index_ty = self.get_index_type(span, value.type, index) - # if index_ty is not None: - # return self.cur_bb().append(hir.Index(value, index, type=index_ty, span=span)) - # else: - # # check __getitem__ - # if (method := value.type.method("__getitem__")) and method: - # ret = self.parse_call_impl( - # span, method, [value, index]) - # if isinstance(ret, hir.TemplateMatchingError): - # raise hir.TypeInferenceError( - # expr, f"error calling __getitem__: {ret.message}") - # return ret - # else: - # raise hir.TypeInferenceError( - # expr, f"indexing not supported for type {value.type}") - # elif isinstance(expr, ast.Attribute): - # def do() -> ComptimeValue | hir.Value: - # value = self.parse_ref(expr.value) - # attr_name = expr.attr - # if isinstance(value, ComptimeValue): - # return ComptimeValue(getattr(value.value, attr_name), None) - # assert value.type - # member_ty = value.type.member(attr_name) - # if not member_ty: - # raise hir.ParsingError( - # expr, f"member {attr_name} not found in type {value.type}") - # if isinstance(member_ty, hir.FunctionType): - # if not isinstance(value, hir.TypeValue): - # member_ty.bound_object = value - # return self.cur_bb().append(hir.Member(self.convert_to_value(value, span), attr_name, type=member_ty, span=span)) - # return do() - # raise NotImplementedError() # unreachable - def parse_call_impl(self, span: hir.Span | None, f: hir.Function | hir.FunctionTemplate, args: List[hir.Value | hir.Ref], inline=False) -> hir.Value | hir.TemplateMatchingError: + ret = self.parse_call_impl_ex(span, f, args, inline) + if isinstance(ret, hir.Ref): + raise hir.ParsingError( + span, f"expected value but got reference") + return ret + + def parse_call_impl_ref(self, span: hir.Span | None, f: hir.Function | hir.FunctionTemplate, args: List[hir.Value | hir.Ref]) -> hir.Ref | hir.TemplateMatchingError: + ret = self.parse_call_impl_ex(span, f, args, True, True) + if isinstance(ret, hir.Value): + raise hir.ParsingError( + span, f"expected reference but got value") + return ret + + def parse_call_impl_ex(self, span: hir.Span | None, f: hir.Function | hir.FunctionTemplate, args: List[hir.Value | hir.Ref], inline=False, expect_ref=False) -> hir.Value | hir.Ref | hir.TemplateMatchingError: + if expect_ref: + if not inline: + raise hir.ParsingError( + span, "a function returning local reference must be inlined") if isinstance(f, hir.FunctionTemplate): if f.is_generic: template_resolve_args: hir.FunctionTemplateResolvingArgs = [] @@ -520,15 +488,22 @@ def parse_call_impl(self, span: hir.Span | None, f: hir.Function | hir.FunctionT raise hir.TypeInferenceError( span, f"failed to infer type of argument {i}") template_resolve_args.append((param, arg.type)) - resolved_f = f.resolve(template_resolve_args) - if isinstance(resolved_f, hir.TemplateMatchingError): - return resolved_f + try: + resolved_f = f.resolve(template_resolve_args) + if isinstance(resolved_f, hir.TemplateMatchingError): + return resolved_f + except hir.TypeInferenceError as e: + if e.span is None: + e.span = span + raise e from e else: resolved_f = f.resolve(None) assert not isinstance(resolved_f, hir.TemplateMatchingError) else: resolved_f = f - + if expect_ref and not resolved_f.returning_ref: + raise hir.ParsingError( + span, "expected a function returning local reference but got a function returning value") param_tys = [] for p in resolved_f.params: assert p.type, f"Parameter {p.name} has no type" @@ -559,39 +534,46 @@ def parse_call_impl(self, span: hir.Span | None, f: hir.Function | hir.FunctionT else: return hir.FunctionInliner.inline(resolved_f, args, self.cur_bb(), span) - def handle_special_functions(self, f: Callable[..., Any], expr: ast.Call) -> hir.Value | ComptimeValue: + def handle_intrinsic(self, expr: ast.Call, is_ref: bool) -> hir.Value | hir.Ref: + intrinsic_name = expr.args[0] + if not isinstance(intrinsic_name, ast.Constant) or not isinstance(intrinsic_name.value, str): + raise hir.ParsingError( + expr, "intrinsic function expects a string literal as its first argument") + args: List[hir.Ref | hir.Value | hir.ComptimeValue] = [] + for a in expr.args[1:]: + if isinstance(a, ast.Call) and isinstance(a.func, ast.Name) and a.func.id == 'byref': + r = self.parse_ref(a.args[0]) + if isinstance(r, hir.Ref): + args.append(r) + else: + raise hir.ParsingError( + a, "expected reference but got value") + else: + args.append(self.parse_expr(a)) + ret_type = args[0] + if isinstance(ret_type, ComptimeValue): + ret_type = self.try_convert_comptime_value( + ret_type, hir.Span.from_ast(expr.args[0])) + if not isinstance(ret_type, hir.TypeValue): + raise hir.ParsingError( + expr, f"intrinsic function expects a type as its second argument but found {ret_type}") + if any([not isinstance(arg, (hir.Value, hir.Ref)) for arg in args[1:]]): + raise hir.ParsingError( + expr, "intrinsic function expects values/refs as its arguments") + if is_ref: + return self.cur_bb().append( + hir.IntrinsicRef(intrinsic_name.value, cast(List[hir.Value | hir.Ref], args[1:]), + ret_type.inner_type(), hir.Span.from_ast(expr))) + else: + return self.cur_bb().append( + hir.Intrinsic(intrinsic_name.value, cast(List[hir.Value | hir.Ref], args[1:]), + ret_type.inner_type(), hir.Span.from_ast(expr))) + def handle_special_functions(self, f: Callable[..., Any], expr: ast.Call) -> hir.Value | ComptimeValue: if f is SPECIAL_FUNCTIONS_DICT['intrinsic']: - def do() -> hir.Intrinsic: - intrinsic_name = expr.args[0] - if not isinstance(intrinsic_name, ast.Constant) or not isinstance(intrinsic_name.value, str): - raise hir.ParsingError( - expr, "intrinsic function expects a string literal as its first argument") - args: List[hir.Ref | hir.Value | hir.ComptimeValue] = [] - for a in expr.args[1:]: - if isinstance(a, ast.Call) and isinstance(a.func, ast.Name) and a.func.id == 'byref': - r = self.parse_ref(a.args[0]) - if isinstance(r, hir.Ref): - args.append(r) - else: - raise hir.ParsingError( - a, "expected reference but got value") - else: - args.append(self.parse_expr(a)) - ret_type = args[0] - if isinstance(ret_type, ComptimeValue): - ret_type = self.try_convert_comptime_value( - ret_type, hir.Span.from_ast(expr.args[0])) - if not isinstance(ret_type, hir.TypeValue): - raise hir.ParsingError( - expr, f"intrinsic function expects a type as its second argument but found {ret_type}") - if any([not isinstance(arg, (hir.Value, hir.Ref)) for arg in args[1:]]): - raise hir.ParsingError( - expr, "intrinsic function expects values/refs as its arguments") - return self.cur_bb().append( - hir.Intrinsic(intrinsic_name.value, cast(List[hir.Value | hir.Ref], args[1:]), - ret_type.inner_type(), hir.Span.from_ast(expr))) - return do() + intrin_ret = self.handle_intrinsic(expr, False) + assert isinstance(intrin_ret, hir.Value) + return intrin_ret elif f is SPECIAL_FUNCTIONS_DICT['cast'] or f is SPECIAL_FUNCTIONS_DICT['bitcast']: def do() -> hir.Intrinsic: if len(expr.args) != 2: @@ -645,7 +627,7 @@ def do() -> hir.Intrinsic: else: print(f"Type of {unparsed_arg} is {value.type}") return hir.Unit() - elif f is range: + elif f is SPECIAL_FUNCTIONS_DICT['range']: def handle_range() -> hir.Value | ComptimeValue: if 1 <= len(expr.args) <= 3: args = [self.parse_expr(arg) for arg in expr.args] @@ -667,6 +649,7 @@ def handle_range() -> hir.Value | ComptimeValue: def make_int(i: int) -> hir.Value: return hir.Constant(i, type=hir.GenericIntType()) + # TODO: check type consistency if len(args) == 1: return hir.Range(make_int(0), converted_args[0], make_int(1)) elif len(args) == 2: @@ -683,6 +666,23 @@ def make_int(i: int) -> hir.Value: else: raise RuntimeError(f"Unsupported special function {f}") + def parse_call_ref(self, expr: ast.Call) -> hir.Ref: + func: hir.Ref | ComptimeValue | hir.TypeValue | hir.Value = self.parse_ref( + expr.func) + if isinstance(func, ComptimeValue): + if func.value is not SPECIAL_FUNCTIONS_DICT['intrinsic']: + raise hir.ParsingError( + expr, f"expected intrinsic function but got {func}") + intrin_ref = self.handle_intrinsic(expr, True) + assert isinstance(intrin_ref, hir.Ref) + return intrin_ref + + span = hir.Span.from_ast(expr) + if not isinstance(func, hir.FunctionValue): + raise hir.ParsingError( + expr, f"expected function but got {func}") + raise NotImplementedError() + def parse_call(self, expr: ast.Call) -> hir.Value | ComptimeValue: func: hir.Ref | ComptimeValue | hir.TypeValue | hir.Value = self.parse_ref( expr.func) # TODO: this should be a parse_ref @@ -747,33 +747,6 @@ def collect_args() -> List[hir.Value | hir.Ref]: else: raise hir.ParsingError( expr, f"function call not supported for type {func.type}") - # raise hir.ParsingError( - # expr, f"function expected but got {func.type}") - # elif not isinstance(func, hir.Constant) or not isinstance(func.value, (hir.Function, hir.FunctionTemplate)): - # raise hir.ParsingError(expr, f"function expected") - # else: - # func_like = func.value - # if not isinstance(func, hir.FunctionValue): - # raise hir.ParsingError(expr, f"function expected but got {func}") - # else: - # func_like = func.func - - # def parse_compare(self, expr: ast.Compare) -> hir.Value | ComptimeValue: - # cmpop_to_str: Dict[type, str] = { - # ast.Eq: "==", - # ast.NotEq: "!=", - # ast.Lt: "<", - # ast.LtE: "<=", - # ast.Gt: ">", - # ast.GtE: ">=" - # } - # if len(expr.ops) != 1: - # raise hir.ParsingError(expr, "only one comparison operator is allowed") - # op = expr.ops[0] - # if type(op) not in cmpop_to_str: - # raise hir.ParsingError(expr, f"unsupported comparison operator {type(op)}") - # op_str = cmpop_to_str[type(op)] - # method_name = BINOP_TO_METHOD_NAMES[type(op)] def parse_binop(self, expr: ast.BinOp | ast.Compare) -> hir.Value: binop_to_op_str: Dict[type, str] = { @@ -868,6 +841,12 @@ def parse_ref(self, expr: ast.expr, new_var_hint: NewVarHint = False) -> hir.Ref return ret case ast.Subscript() | ast.Attribute(): return self.parse_access_ref(expr) + case ast.Call() as call: + return self.parse_call_ref(call) + # if call.func is SPECIAL_FUNCTIONS_DICT['intrinsic']: + # return self.handle_special_functions( + # call.func, call) + # return self.parse_call_impl_ref(hir.Span.from_ast(expr), self.parse_ref(call.func), [self.parse_expr(arg) for arg in call.args]) case _: raise hir.ParsingError( expr, f"expression {ast.dump(expr)} cannot be parsed as reference") @@ -1117,6 +1096,7 @@ def parse_stmt(self, stmt: ast.stmt) -> None: if not isinstance(iter_val, hir.Value) or not isinstance(iter_val, hir.Range): raise hir.ParsingError( stmt, f"for loop iterable must be a range object but found {iter_val}") + loop_range: hir.Range = iter_val pred_bb = self.cur_bb() self.bb_stack.pop() loop_var = self.parse_ref(stmt.target, new_var_hint='dsl') @@ -1124,11 +1104,14 @@ def parse_stmt(self, stmt: ast.stmt) -> None: raise hir.ParsingError( stmt, "for loop target must be a DSL variable") if not loop_var.type: - loop_var.type = luisa_lang.typeof(luisa_lang.i32) + loop_ty = loop_range.value_type() + if not isinstance(loop_ty, hir.GenericIntType): + loop_var.type = loop_ty + else: + loop_var.type = luisa_lang.typeof(luisa_lang.i32) if not isinstance(loop_var.type, hir.IntType): raise hir.ParsingError( stmt, "for loop target must be an integer variable") - loop_range: hir.Range = iter_val prepare = hir.BasicBlock(span) self.bb_stack.append(prepare) @@ -1184,15 +1167,31 @@ def check_return_type(ty: hir.Type) -> None: if not hir.is_type_compatible_to(ty, self.parsed_func.return_type): raise hir.ParsingError( stmt, f"return type mismatch: expected {self.parsed_func.return_type}, got {ty}") - if stmt.value: - value = self.parse_expr(stmt.value) - value = self.convert_to_value(value, span) - assert value.type is not None - check_return_type(value.type) - self.cur_bb().append(hir.Return(value)) + if self.returning_ref: + def do(): + if not stmt.value: + raise hir.ParsingError( + stmt, "if a function is returning local references, the return value must be provided") + value = self.parse_ref(stmt.value) + if not isinstance(value, hir.Ref): + raise hir.ParsingError( + stmt, "invalid return target") + assert value.type + check_return_type(value.type) + self.cur_bb().append(hir.ReturnRef(value)) + do() else: - check_return_type(hir.UnitType()) - self.cur_bb().append(hir.Return(None)) + def do(): + if stmt.value: + value = self.parse_expr(stmt.value) + value = self.convert_to_value(value, span) + assert value.type is not None + check_return_type(value.type) + self.cur_bb().append(hir.Return(value)) + else: + check_return_type(hir.UnitType()) + self.cur_bb().append(hir.Return(None)) + do() case ast.Assign(): assert len(stmt.targets) == 1 target = stmt.targets[0] diff --git a/scripts/cpp_lib.hpp b/scripts/cpp_lib.hpp index bbcdf79..4d39985 100644 --- a/scripts/cpp_lib.hpp +++ b/scripts/cpp_lib.hpp @@ -14,10 +14,10 @@ #endif -int __float_as_int(float x) noexcept { return std::bit_cast(x); } -float __int_as_float(int x) noexcept { return std::bit_cast(x); } -float exp10f(float x) noexcept { return std::pow(10.0f, x); } -float rsqrtf(float x) noexcept { return 1.0f / std::sqrt(x); } +inline int __float_as_int(float x) noexcept { return std::bit_cast(x); } +inline float __int_as_float(int x) noexcept { return std::bit_cast(x); } +inline float exp10f(float x) noexcept { return std::pow(10.0f, x); } +inline float rsqrtf(float x) noexcept { return 1.0f / std::sqrt(x); } inline int __clz(unsigned int x) { return __builtin_clz(x); } @@ -3945,3 +3945,12 @@ template<> struct element_type_ { using type = lc_long; }; template<> struct element_type_ { using type = lc_ulong; }; template<> struct element_type_ { using type = lc_ulong; }; template<> struct element_type_ { using type = lc_ulong; }; + +template +struct __builtin__Buffer { + T *data{}; + size_t size{}; + __device__ T &operator[](size_t i) noexcept { return data[i]; } + __device__ T &operator[](size_t i) const noexcept { return data[i]; } +}; + diff --git a/scripts/gen_cpp_lib.py b/scripts/gen_cpp_lib.py index d9850ee..2e7d88c 100644 --- a/scripts/gen_cpp_lib.py +++ b/scripts/gen_cpp_lib.py @@ -33,10 +33,10 @@ def gen_cpp_lib(): #endif -int __float_as_int(float x) noexcept { return std::bit_cast(x); } -float __int_as_float(int x) noexcept { return std::bit_cast(x); } -float exp10f(float x) noexcept { return std::pow(10.0f, x); } -float rsqrtf(float x) noexcept { return 1.0f / std::sqrt(x); } +inline int __float_as_int(float x) noexcept { return std::bit_cast(x); } +inline float __int_as_float(int x) noexcept { return std::bit_cast(x); } +inline float exp10f(float x) noexcept { return std::pow(10.0f, x); } +inline float rsqrtf(float x) noexcept { return 1.0f / std::sqrt(x); } inline int __clz(unsigned int x) { return __builtin_clz(x); } @@ -955,6 +955,16 @@ def gen_element_type(vt, et): for vt in ['lc_ulong2', 'lc_ulong3', 'lc_ulong4']: gen_element_type(vt, 'lc_ulong') + print(''' +template +struct __builtin__Buffer { + T *data{}; + size_t size{}; + __device__ T &operator[](size_t i) noexcept { return data[i]; } + __device__ T &operator[](size_t i) const noexcept { return data[i]; } +}; +''', file=CPP_LIB_SRC) + gen_cpp_lib()