# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. """Statement AST Node in TVM. Each statement node have subfields that can be visited from python side. .. code-block:: python x = tvm.tirx.Var("n", "int32") buffer = tvm.tirx.decl_buffer((16,), "float32") st = tvm.tirx.stmt.BufferStore(buffer, 1, (x,)) assert isinstance(st, tvm.tirx.stmt.BufferStore) assert(st.buffer == buffer) """ from collections.abc import Mapping from enum import IntEnum from typing import TYPE_CHECKING, Any, ClassVar import tvm_ffi from tvm.ir import Expr, Op, Range, Span, is_prim_expr from tvm.runtime import Object, Scriptable, const from tvm.tirx import FloatImm, IntImm from . import _ffi_api from .buffer import Buffer from .exec_scope import ExecScope, ScopeIdDef from .expr import IterVar, StringImm, Var if TYPE_CHECKING: from tvm.tirx.operator.tile_primitive.dispatch_context import DispatchContext @tvm_ffi.register_object("tirx.Stmt") class Stmt(Object, Scriptable): """Base class of all the statements.""" def _normalize_legacy_stmt(stmt: Stmt | None) -> Stmt | None: """Expand legacy body-carrying leaf stmt wrappers into SeqStmt form. Legacy python compatibility may attach a `body` attribute to leaf statements (Bind/DeclBuffer/AllocBuffer). This helper converts such wrappers to the new leaf + SeqStmt representation when embedding inside another statement node. """ if stmt is None: return None prefix: list[Stmt] = [] cur = stmt while True: if isinstance(cur, DeclBuffer) and hasattr(cur, "body"): prefix.append(DeclBuffer(cur.buffer, cur.span)) cur = cur.body continue if isinstance(cur, AllocBuffer) and hasattr(cur, "body"): prefix.append(AllocBuffer(cur.buffer, cur.annotations, cur.span)) cur = cur.body continue break if not prefix: return stmt normalized_tail = _normalize_legacy_stmt(cur) if normalized_tail is not None: prefix.append(normalized_tail) if len(prefix) == 1: return prefix[0] return SeqStmt(prefix) @tvm_ffi.register_object("tirx.Bind") class Bind(Stmt): """Bind node. Bind a variable to a value in the enclosing scope. Bind has no body field. The bound variable is visible in all subsequent statements within the same enclosing scope (SeqStmt, ForNode.body, etc.). Parameters ---------- var : Var The variable in the binding. value : Expr The value to be bound. span : Optional[Span] The location of the stmt in the source code. """ var: Var value: Expr span: Span | None def __init__(self, var: Var, value: Expr, span: Span | None = None) -> None: self.__init_handle_by_constructor__( _ffi_api.Bind, var, value, span, # type: ignore ) @tvm_ffi.register_object("tirx.AssertStmt") class AssertStmt(Stmt): """AssertStmt node. Parameters ---------- kind : StringImm The error kind, e.g. "RuntimeError", "TypeError", "ValueError". condition : Expr The assert condition. message_parts : list[StringImm] Error message fragments, concatenated at runtime when assertion fails. span : Span | None The location of the stmt in the source code. """ kind: StringImm condition: Expr message_parts: list span: Span | None def __init__( self, kind: StringImm, condition: Expr, message_parts: list | None = None, span: Span | None = None, ) -> None: if message_parts is None: message_parts = [] self.__init_handle_by_constructor__( _ffi_api.AssertStmt, kind, condition, message_parts, span, # type: ignore ) class ForKind(IntEnum): """The kind of the for loop. note ---- ForKind can change the control flow semantics of the loop and need to be considered in all TIR passes. """ SERIAL = 0 PARALLEL = 1 VECTORIZED = 2 UNROLLED = 3 THREAD_BINDING = 4 # pylint: disable=invalid-name @tvm_ffi.register_object("tirx.For") class For(Stmt): """For node. Parameters ---------- loop_var : Var The loop variable. min : Expr The beginning value. extent : Expr The length of the loop. kind : ForKind The type of the for. body : Stmt The body statement. thread_binding: Optional[tirx.IterVar] The thread this loop binds to. Only valid if kind is ThreadBinding step : Expr The loop step. Default to none which represent one. annotations: Optional[Mapping[str, Object]] Additional annotation hints. span : Optional[Span] The location of the stmt in the source code. """ loop_var: Var min: Expr extent: Expr kind: ForKind body: Stmt thread_binding: IterVar | None annotations: Mapping[str, Object] step: Expr | None span: Span | None def __init__( self, loop_var: Var, min: Expr, # pylint: disable=redefined-builtin extent: Expr, kind: ForKind, body: Stmt, thread_binding: IterVar | None = None, annotations: Mapping[str, Object] | None = None, step: Expr | None = None, span: Span | None = None, ) -> None: body = _normalize_legacy_stmt(body) self.__init_handle_by_constructor__( _ffi_api.For, # type: ignore loop_var, min, extent, kind, body, thread_binding, annotations, step, span, ) @tvm_ffi.register_object("tirx.While") class While(Stmt): """While node. Parameters ---------- condition : Expr The termination condition. body : Stmt The body statement. span : Optional[Span] The location of the stmt in the source code. """ condition: Expr body: Stmt span: Span | None def __init__(self, condition: Expr, body: Stmt, span: Span | None = None) -> None: body = _normalize_legacy_stmt(body) self.__init_handle_by_constructor__(_ffi_api.While, condition, body, span) # type: ignore @tvm_ffi.register_object("tirx.BufferStore") class BufferStore(Stmt): """Buffer store node. Parameters ---------- buffer : Buffer The buffer. value : Expr The value we to be stored. indices : List[Expr] The indices location to be stored. predicate : Optional[Expr] A vector mask of boolean values indicating which lanes of a vector are to be stored. The number lanes of the mask must be equal to the number of lanes in value. span : Optional[Span] The location of the stmt in the source code. """ buffer: Buffer value: Expr indices: list[Expr] predicate: Expr | None span: Span | None def __init__( self, buffer: Buffer, value: Expr, indices: list[Expr], predicate: Expr | None = None, span: Span | None = None, ) -> None: self.__init_handle_by_constructor__( _ffi_api.BufferStore, buffer, value, indices, predicate, span, # type: ignore ) @tvm_ffi.register_object("tirx.AllocBuffer") class AllocBuffer(Stmt): """AllocBuffer node. Allocates a buffer and declares it in scope. Parameters ---------- buffer: Buffer The buffer being allocated and declared. annotations: Optional[dict] Additional annotations about the allocation. span: Optional[Span] The location of this AllocBuffer in the source code. """ buffer: Buffer span: Span | None def __init__(self, buffer: Buffer, *args, **kwargs) -> None: body: Stmt | None = None annotations: dict | None = None span: Span | None = None idx = 0 argc = len(args) # Legacy form: AllocBuffer(buffer, body[, annotations][, span]) if idx < argc and isinstance(args[idx], Stmt): body = args[idx] idx += 1 if idx < argc: arg = args[idx] if isinstance(arg, Mapping): annotations = dict(arg) idx += 1 elif arg is None: annotations = None idx += 1 elif isinstance(arg, Span): span = arg idx += 1 else: raise TypeError( "AllocBuffer expects (buffer[, annotations][, span]) or " "legacy (buffer, body[, annotations][, span])" ) if idx < argc: arg = args[idx] if arg is None or isinstance(arg, Span): span = arg idx += 1 else: raise TypeError("AllocBuffer span must be a Span or None") if idx != argc: raise TypeError( "AllocBuffer expects (buffer[, annotations][, span]) or " "legacy (buffer, body[, annotations][, span])" ) if kwargs: invalid_keys = set(kwargs.keys()) - {"body", "annotations", "span"} if invalid_keys: raise TypeError(f"Unexpected keyword arguments for AllocBuffer: {invalid_keys}") if "body" in kwargs: kw_body = kwargs["body"] if kw_body is not None and not isinstance(kw_body, Stmt): raise TypeError("AllocBuffer body must be a Stmt or None") if body is not None and kw_body is not None and body is not kw_body: raise TypeError("AllocBuffer body specified by both args and kwargs") body = kw_body if kw_body is not None else body if "annotations" in kwargs: kw_ann = kwargs["annotations"] if kw_ann is not None and not isinstance(kw_ann, Mapping): raise TypeError("AllocBuffer annotations must be Mapping or None") if annotations is not None and kw_ann is not None and annotations != dict(kw_ann): raise TypeError("AllocBuffer annotations specified by both args and kwargs") annotations = dict(kw_ann) if kw_ann is not None else annotations if "span" in kwargs: kw_span = kwargs["span"] if kw_span is not None and not isinstance(kw_span, Span): raise TypeError("AllocBuffer span must be a Span or None") if span is not None and kw_span is not None and span is not kw_span: raise TypeError("AllocBuffer span specified by both args and kwargs") span = kw_span if kw_span is not None else span self.__init_handle_by_constructor__(_ffi_api.AllocBuffer, buffer, annotations, span) # Legacy compatibility. Body is carried on python side only. if body is not None: self.body = body @tvm_ffi.register_object("tirx.DeclBuffer") class DeclBuffer(Stmt): """DeclBuffer node. Parameters ---------- buffer: Buffer The buffer being declared. span: Optional[Span] The location of this DeclBuffer in the source code. """ buffer: Buffer span: Span | None def __init__(self, buffer: Buffer, *args, **kwargs) -> None: body: Stmt | None = None span: Span | None = None if len(args) == 1: arg0 = args[0] if isinstance(arg0, Stmt): body = arg0 elif arg0 is None or isinstance(arg0, Span): span = arg0 else: raise TypeError( "DeclBuffer expects (buffer[, span]) or legacy (buffer, body[, span])" ) elif len(args) == 2: body, span = args if body is not None and not isinstance(body, Stmt): raise TypeError("Legacy DeclBuffer body must be a Stmt or None") if span is not None and not isinstance(span, Span): raise TypeError("DeclBuffer span must be a Span or None") elif len(args) > 2: raise TypeError("DeclBuffer expects (buffer[, span]) or legacy (buffer, body[, span])") if kwargs: invalid_keys = set(kwargs.keys()) - {"body", "span"} if invalid_keys: raise TypeError(f"Unexpected keyword arguments for DeclBuffer: {invalid_keys}") if "body" in kwargs: kw_body = kwargs["body"] if kw_body is not None and not isinstance(kw_body, Stmt): raise TypeError("DeclBuffer body must be a Stmt or None") if body is not None and kw_body is not None and body is not kw_body: raise TypeError("DeclBuffer body specified by both args and kwargs") body = kw_body if kw_body is not None else body if "span" in kwargs: kw_span = kwargs["span"] if kw_span is not None and not isinstance(kw_span, Span): raise TypeError("DeclBuffer span must be a Span or None") if span is not None and kw_span is not None and span is not kw_span: raise TypeError("DeclBuffer span specified by both args and kwargs") span = kw_span if kw_span is not None else span self.__init_handle_by_constructor__(_ffi_api.DeclBuffer, buffer, span) # Legacy compatibility. Body is carried on python side only. if body is not None: self.body = body @tvm_ffi.register_object("tirx.AttrStmt") class AttrStmt(Stmt): """AttrStmt node. Parameters ---------- node : Object The node to annotate the attribute attr_key : str Attribute type key. value : Expr The value of the attribute body : Stmt The body statement. span : Optional[Span] The location of the stmt in the source code. """ node: Object attr_key: str value: Expr body: Stmt span: Span | None def __init__( self, node: Object, attr_key: str, value: Expr, body: Stmt, span: Span | None = None ) -> None: body = _normalize_legacy_stmt(body) self.__init_handle_by_constructor__( _ffi_api.AttrStmt, node, attr_key, value, body, span, # type: ignore ) @tvm_ffi.register_object("tirx.SeqStmt") class SeqStmt(Stmt): """Sequence of statements. Parameters ---------- seq : List[Stmt] The statements span : Optional[Span] The location of the stmt in the source code. """ seq: list[Stmt] span: Span | None def __init__(self, seq: list[Stmt], span: Span | None = None) -> None: seq = [_normalize_legacy_stmt(s) for s in seq] self.__init_handle_by_constructor__(_ffi_api.SeqStmt, seq, span) # type: ignore def __getitem__(self, i: int): return self.seq[i] def __len__(self): return len(self.seq) @tvm_ffi.register_object("tirx.IfThenElse") class IfThenElse(Stmt): """IfThenElse node. Parameters ---------- condition : Expr The expression then_case : Stmt The statement to execute if condition is true. else_case : Optional[Stmt] The statement to execute if condition is false. span : Optional[Span] The location of the stmt in the source code. """ condition: Expr then_case: Stmt else_case: Stmt | None def __init__( self, condition: Expr, then_case: Stmt, else_case: Stmt | None, span: Span | None = None ) -> None: then_case = _normalize_legacy_stmt(then_case) else_case = _normalize_legacy_stmt(else_case) self.__init_handle_by_constructor__( _ffi_api.IfThenElse, condition, then_case, else_case, span, # type: ignore ) @tvm_ffi.register_object("tirx.Evaluate") class Evaluate(Stmt): """Evaluate node. Parameters ---------- value : Expr The expression to be evaluated. span : Optional[Span] The location of the stmt in the source code. """ value: Expr span: Span | None def __init__(self, value: Expr, span: Span | None = None) -> None: self.__init_handle_by_constructor__(_ffi_api.Evaluate, value, span) # type: ignore @tvm_ffi.register_object("tirx.BufferRegion") class BufferRegion(Object, Scriptable): """BufferRegion node. Parameters ---------- buffer : Buffer The buffer of the buffer region region : List[Range] The region array of the buffer region """ buffer: Buffer region: list[Range] def __init__(self, buffer: Buffer, region: list[Range]) -> None: self.__init_handle_by_constructor__(_ffi_api.BufferRegion, buffer, region) # type: ignore def __getitem__(self, indices): from ..arith import Analyzer if not isinstance(indices, tuple | list): indices = [indices] has_step = any( isinstance(i, slice) and (i.step is not None and i.step != 1) for i in indices ) if has_step: raise ValueError("BufferRegion slicing does not support steps") analyzer = Analyzer() new_region = [] for i, index in enumerate(indices): old_range = self.region[i] if isinstance(index, slice): start = 0 if index.start is None else index.start stop = old_range.extent if index.stop is None else index.stop new_min = old_range.min + start new_extent = analyzer.simplify(stop - start) new_region.append(Range.from_min_extent(new_min, new_extent)) else: new_min = old_range.min + index new_region.append( Range.from_min_extent( new_min, IntImm(index.ty, 1) if is_prim_expr(index) else 1 ) ) # Fill remaining dimensions with their original ranges for i in range(len(indices), len(self.region)): new_region.append(self.region[i]) return BufferRegion(self.buffer, new_region) @tvm_ffi.register_object("tirx.MatchBufferRegion") class MatchBufferRegion(Object, Scriptable): """MatchBufferRegion node. Parameters ---------- buffer : Buffer The target buffer source : BufferRegion The region of source buffer """ buffer: Buffer source: BufferRegion def __init__(self, buffer: Buffer, source: BufferRegion) -> None: self.__init_handle_by_constructor__( _ffi_api.MatchBufferRegion, buffer, source, # type: ignore ) @tvm_ffi.register_object("tirx.SBlock") class SBlock(Stmt): """SBlock node. Parameters ---------- iter_vars : List[IterVar] The block Variable. reads : List[BufferRegion] The read buffer regions of the block. writes: List[BufferRegion] The write buffer regions of the block. name_hint: str the name_hint of the block. body: Stmt The body of the block. init: Optional[Stmt] The init block of the reduction block alloc_buffers: Optional[list[Buffer]] The buffer allocations match_buffers: Optional[List[MatchBufferRegion]] The subregion buffer match annotations: Optional[Mapping[str, Object]] Additional annotation hints. span : Optional[Span] The location of this block in the source code. """ iter_vars: list[IterVar] reads: list[BufferRegion] writes: list[BufferRegion] name_hint: str body: Stmt init: Stmt | None alloc_buffers: list[Buffer] match_buffers: list[MatchBufferRegion] annotations: Mapping[str, Object] span: Span | None def __init__( self, iter_vars: list[IterVar], reads: list[BufferRegion], writes: list[BufferRegion], name_hint: str, body: Stmt, init: Stmt | None = None, alloc_buffers: list[Buffer] | None = None, match_buffers: list[MatchBufferRegion] | None = None, annotations: Mapping[str, Object] | None = None, span: Span | None = None, ) -> None: if alloc_buffers is None: alloc_buffers = [] if match_buffers is None: match_buffers = [] if annotations is None: annotations = {} body = _normalize_legacy_stmt(body) init = _normalize_legacy_stmt(init) self.__init_handle_by_constructor__( _ffi_api.SBlock, # type: ignore iter_vars, reads, writes, name_hint, body, init, alloc_buffers, match_buffers, annotations, span, ) # type: ignore @tvm_ffi.register_object("tirx.SBlockRealize") class SBlockRealize(Stmt): """SBlockRealize node. Parameters ---------- iter_values : List[Expr] The binding values of the block var. predicate : Union[Expr, bool] The predicate of the block. block : SBlock The block to realize span : Optional[Span] The location of this block_realize in the source code. """ iter_values: list[Expr] predicate: Expr block: SBlock span: Span | None def __init__( self, iter_values: list[Expr], predicate: Expr | bool, block: SBlock, span: Span | None = None, ) -> None: if isinstance(predicate, bool): predicate = const(predicate, "bool") self.__init_handle_by_constructor__( _ffi_api.SBlockRealize, # type: ignore iter_values, predicate, block, span, ) # type: ignore @tvm_ffi.register_object("tirx.ScopeIdDefStmt") class ScopeIdDefStmt(Stmt): """ScopeIdDefStmt node. Leaf statement that introduces scope-identifier vars (``wg_id = Tx.warpgroup_id([N])``, ``warp_id = Tx.warp_id_in_wg([4])``, ``lane_id = Tx.lane_id([32])``, …) at the kernel-body top level. The underlying ``ScopeIdDef`` carries the def vars, their extents, and the parent/child scope binding. Note: the C++ field is named ``def`` (a Python keyword). Access it via ``getattr(stmt, "def")`` or ``stmt.__getattribute__("def")`` — the type-annotation alias here is purely for documentation. Parameters ---------- def_ : ScopeIdDef The scope-id definition (def vars, extents, scope binding). span : Optional[Span] The location of this statement in the source code. """ span: Span | None def __init__(self, def_: ScopeIdDef, span: Span | None = None) -> None: self.__init_handle_by_constructor__( _ffi_api.ScopeIdDefStmt, # type: ignore def_, span, ) # type: ignore @tvm_ffi.register_object("tirx.Break") class Break(Stmt): """Break node. Parameters ---------- """ def __init__(self, span: Span | None = None) -> None: self.__init_handle_by_constructor__(_ffi_api.Break, span) # type: ignore @tvm_ffi.register_object("tirx.Continue") class Continue(Stmt): """Continue node. Parameters ---------- """ def __init__(self, span: Span | None = None) -> None: self.__init_handle_by_constructor__(_ffi_api.Continue, span) # type: ignore def stmt_seq(*args: Expr | Stmt) -> SeqStmt: """Make sequence of statements Parameters ---------- *args : Union[Expr, Stmt] List of statements to be combined as sequence. Returns ------- stmt : Stmt The combined statement. """ ret = [] for value in args: if not isinstance(value, Stmt): value = Evaluate(value) ret.append(value) if len(ret) == 1: return ret[0] return SeqStmt(ret) def stmt_list(stmt: Stmt) -> list[Stmt]: """Make list of stmt from blocks. Parameters ---------- stmt : Stmt The input statement. Returns ------- stmt_list : List[Stmt] The unpacked list of statements """ if isinstance(stmt, SeqStmt): res = [] for x in stmt: res += stmt_list(x) return res return [stmt] def normalize_const_arg(arg) -> Expr: if isinstance(arg, float): return FloatImm("float32", arg) return arg @tvm_ffi.register_object("tirx.TilePrimitiveCall") class TilePrimitiveCall(Stmt): """TilePrimitiveCall node. Parameters ---------- op : Op The operator. args : List[Expr] The arguments. workspace : Map[str, Buffer] The workspace. config : Map[str, ObjectRef] The scheduler/config dictionary. dispatch : Optional[str] The explicit variant name to dispatch to. scope : ExecScope The cooperation scope of this call. Defaults to ``thread`` (an unscoped call). """ args: list[Expr] workspace: dict[str, Buffer] config: dict[str, Any] dispatch: str | None scope: ExecScope _registry: ClassVar[dict[Op, type["TilePrimitiveCall"]]] = {} def __init__( self, *args: list[Expr], op: Op | None = None, workspace: dict[str, Buffer] | None = None, config: dict[str, Any] | None = None, dispatch: str | None = None, scope: ExecScope | None = None, ) -> None: if workspace is None: workspace = {} if config is None: config = {} if scope is None: scope = ExecScope("thread") if op is None: assert self.__class__ != TilePrimitiveCall, ( "Directly instantiating TilePrimitiveCall needs to specify the op" ) op = self.__class__.op args = list(map(normalize_const_arg, args)) self.__init_handle_by_constructor__( _ffi_api.TilePrimitiveCall, op, args, workspace, config, dispatch, scope, # pylint: disable=no-member ) def __init_subclass__(cls, **kwargs): super().__init_subclass__(**kwargs) if hasattr(cls, "op"): cls._registry[cls.op] = cls @classmethod def downcast(cls, instance: "TilePrimitiveCall") -> "TilePrimitiveCall": subclass = cls._registry.get(instance.op) if subclass is None: return instance # Unknown op: return as-is new_instance = subclass.__new__(subclass) new_instance.__init_handle_by_constructor__( _ffi_api.TilePrimitiveCallCopyHandle, instance, # pylint: disable=no-member ) return new_instance def replace(self, **changes: Any) -> "TilePrimitiveCall": """Return a copy of this call with selected fields replaced. Every field that is not overridden in ``changes`` is preserved from ``self`` (including ``scope``), so rebuilds never silently drop fields. The returned node is downcast to the registered subclass for ``op``. Parameters ---------- **changes : Any Field overrides; any of ``op``, ``args``, ``workspace``, ``config``, ``dispatch``, ``scope``. Returns ------- new_call : TilePrimitiveCall A new call with the requested fields replaced. """ unknown = set(changes) - {"op", "args", "workspace", "config", "dispatch", "scope"} if unknown: raise TypeError(f"Unknown field(s) for TilePrimitiveCall.replace: {sorted(unknown)}") new_call = TilePrimitiveCall( *changes.get("args", self.args), op=changes.get("op", self.op), workspace=changes.get("workspace", self.workspace), config=changes.get("config", self.config), dispatch=changes.get("dispatch", self.dispatch), scope=changes.get("scope", self.scope), ) return TilePrimitiveCall.downcast(new_call) def with_workspace(self, workspace: dict[str, Buffer]) -> "TilePrimitiveCall": """Return a copy with ``workspace`` replaced, preserving all other fields.""" return self.replace(workspace=workspace) @property def srcs(self) -> list[Expr]: raise NotImplementedError("Subclass must implement this method") @property def dsts(self) -> list[Expr]: raise NotImplementedError("Subclass must implement this method") def get_private_buffers( self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext" ) -> dict[str, Any]: """ Create private (intermediate) buffers needed in this operator. Parameters ---------- buffer_dict: Dict[Any, Tuple[Buffer, Optional[Stmt]]] A dictionary containing private buffers (and their init stmts) in other operators. Key can be anything to reference the buffer. This is used to reuse private buffers in other operators (like identity tensor etc.). If the buffer is not found in the buffer_dict, it will be created and added to the buffer_dict. If the buffer is found in the buffer_dict but smaller than required, it will be enlarged and updated. sctx: DispatchContext The dispatch context. This is used to get the target and reuse op dispatch implementations. Returns: private_buffer_refs: Dict[str, Any] The references to private buffers created in this operator. Key will be the name to add into workspace. private buffer can be accessed by buffer_dict[private_buffer_refs[name]] """ if sctx.target.kind.name == "trn": return self.get_private_buffers_trn(buffer_dict, sctx) elif sctx.target.kind.name == "cuda": return self.get_private_buffers_cuda(buffer_dict, sctx) else: raise ValueError(f"Unsupported target: {sctx.target.kind.name}") def get_private_buffers_trn( self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext" ) -> dict[str, Any]: return {} def get_private_buffers_cuda( self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext" ) -> dict[str, Any]: return {} def validate(self) -> None: pass