This is an automated email from the ASF dual-hosted git repository.
tqchen pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 71f0271942 [REFACTOR][IR] Unify expression subscription realization
(#20246)
71f0271942 is described below
commit 71f0271942532a8011814092e498f4a61dbb97ea
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Aug 31 19:06:39 2026 -0400
[REFACTOR][IR] Unify expression subscription realization (#20246)
## Summary
- add a lazy `tvm.ir.SubscriptProxy` with type-registered realization
- centralize Python expression operator boundaries through `ExprOperand`
- simplify buffer subscription to full-rank point loads or regions
- preserve tuple, parser, builder, and Relax subscription behavior
---
python/tvm/backend/cuda/ptx/engine.py | 2 +
python/tvm/ir/__init__.py | 2 +
python/tvm/ir/_tensor_expr_overload.py | 4 -
python/tvm/ir/expr.py | 166 +++++++++++++++++++--
python/tvm/relax/expr.py | 29 ----
python/tvm/relax/op/__init__.py | 9 --
python/tvm/script/parser/core/evaluator.py | 3 +
python/tvm/script/parser/core/parser.py | 9 +-
python/tvm/tirx/_buffer_view.py | 4 +-
python/tvm/tirx/buffer.py | 58 +------
python/tvm/tirx/function.py | 5 +
python/tvm/tirx/op.py | 2 +
python/tvm/tirx/script/builder/ir.py | 8 +-
python/tvm/tirx/script/builder/tirx.py | 5 +-
python/tvm/tirx/script/parser/operation.py | 5 +
python/tvm/tirx/script/parser/parser.py | 2 +
python/tvm/tirx/script/tile.py | 2 +
src/ir/subscript_proxy.cc | 71 +++++++++
src/relax/ir/dependent_type.cc | 17 +++
src/tirx/ir/buffer.cc | 69 ++++++++-
src/tirx/script/printer/buffer.cc | 12 +-
.../test_s_tir_analysis_identify_memcpy.py | 6 +-
tests/python/tirx/test_parser_printer.py | 25 +++-
.../python/tvmscript/test_tvmscript_printer_tir.py | 4 +-
tests/python/tvmscript/test_tvmscript_roundtrip.py | 4 +-
25 files changed, 398 insertions(+), 125 deletions(-)
diff --git a/python/tvm/backend/cuda/ptx/engine.py
b/python/tvm/backend/cuda/ptx/engine.py
index 3897b22991..2529065127 100644
--- a/python/tvm/backend/cuda/ptx/engine.py
+++ b/python/tvm/backend/cuda/ptx/engine.py
@@ -41,6 +41,7 @@ from tvm.backend.cuda.codegen.registry import register_codegen
from tvm.backend.cuda.codegen.utils import parse_str
from tvm.backend.cuda.op import cuda_cvta_generic_to_shared, cuda_func_call
from tvm.ir import Call, TensorLoad
+from tvm.ir.expr import _realize_operand
from tvm.ir.op import register_op_attr
from tvm.ir.type import PointerType, PrimType
from tvm.runtime import const
@@ -481,6 +482,7 @@ def _coerce_pred_operand(entry, slot, values):
def _coerce_typed(entry, slot, values, mod_map):
"""Coerce a dtype-carrying register operand (``rw`` any)."""
+ values = [_realize_operand(value) for value in values]
allowed = operand_dtypes(slot, mod_map)
token = operand_type(slot, mod_map)
if slot.rw in ("w", "rw"):
diff --git a/python/tvm/ir/__init__.py b/python/tvm/ir/__init__.py
index 36534b5635..8a2d241cd1 100644
--- a/python/tvm/ir/__init__.py
+++ b/python/tvm/ir/__init__.py
@@ -37,9 +37,11 @@ from .type import FuncType, OpaqueType, PointerType,
PrimType, TupleType, Type
from .expr import (
Call,
Expr,
+ ExprOperand,
GlobalVar,
OpaqueExpr,
Range,
+ SubscriptProxy,
TensorLoad,
Tuple,
TupleGetItem,
diff --git a/python/tvm/ir/_tensor_expr_overload.py
b/python/tvm/ir/_tensor_expr_overload.py
index d3fcf25ca0..1c42a9ebb6 100644
--- a/python/tvm/ir/_tensor_expr_overload.py
+++ b/python/tvm/ir/_tensor_expr_overload.py
@@ -105,9 +105,5 @@ def __call__(_value, *_args, **_kwargs):
return NotImplemented
-def __getitem__(_value, _index):
- return NotImplemented
-
-
def astype(_value, _dtype, _span=None):
return NotImplemented
diff --git a/python/tvm/ir/expr.py b/python/tvm/ir/expr.py
index 263c2f5821..14d467263b 100644
--- a/python/tvm/ir/expr.py
+++ b/python/tvm/ir/expr.py
@@ -22,7 +22,7 @@ import tvm_ffi
import tvm
-from ..runtime import Object, Scriptable
+from ..runtime import Object, ObjectConvertible, Scriptable
from . import _ffi_api, _overload_prim_expr, _tensor_expr_overload
from .base import Node, Span
@@ -34,6 +34,29 @@ class Expr(Node):
span: Span | None
ty: "tvm.ir.Type"
+ def __getitem__(self, index):
+ if isinstance(self.ty, tvm.ir.TupleType):
+ # Tuple subscription is eager so Python's legacy sequence protocol
+ # observes IndexError and terminates tuple iteration/unpacking.
+ try:
+ return _ffi_api.SubscriptExprRealize(self,
[SubscriptProxy._convert_index(index)])
+ except RuntimeError as err:
+ if "Index out of bounds" in err.args[0]:
+ raise IndexError from err
+ raise
+ if self.ty.is_missing():
+ # Preserve Relax's pre-normalization tuple access: operator calls
+ # have a missing result type until the block builder infers it.
+ return TupleGetItem(self, index)
+ if isinstance(self.ty, tvm.relax.TensorType):
+ # Relax tensor subscription retains its legacy TupleGetItem
+ # semantics. Realize each step eagerly so x[i][j] remains two
+ # nested tuple-item accesses rather than one multi-axis slice.
+ return SubscriptProxy(self, index).to_expr()
+ if is_prim_expr(self):
+ raise TypeError("A primitive-valued expression cannot be indexed")
+ return SubscriptProxy(self, index)
+
@tvm_ffi.register_object("ir.OpaqueExpr")
class OpaqueExpr(Expr):
@@ -83,6 +106,8 @@ class GlobalVar(Expr):
"""
from .type import PointerType
+ args = tuple(_realize_operand(arg) for arg in args)
+
def is_tir_arg(x):
return (
isinstance(x, Number)
@@ -100,98 +125,124 @@ class GlobalVar(Expr):
raise RuntimeError(f"Do not know how to handle GlobalVar.__call__ for
types {arg_types}")
-class _ExprWithOp(Expr, Scriptable):
- """Common type-directed operator behavior for core expressions."""
+def _realize_operand(value):
+ return value._operand() if isinstance(value, ExprOperand) else value
- __hash__ = Expr.__hash__
+
+class ExprOperand:
+ """Python operator surface for anything that denotes an expression."""
+
+ __slots__ = ()
+ __hash__ = object.__hash__
+
+ def _operand(self) -> Expr:
+ raise NotImplementedError
def expr_ty(self):
"""Return this expression's primitive result type."""
+ self = _realize_operand(self)
if is_prim_expr(self):
return self.ty
raise TypeError(f"Expected a primitive-valued expression, but result
type is {self.ty}")
def __add__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__add__(self, other)
return _tensor_expr_overload.__add__(self, other)
def __radd__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__radd__(self, other)
return _tensor_expr_overload.__radd__(self, other)
def __sub__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__sub__(self, other)
return _tensor_expr_overload.__sub__(self, other)
def __rsub__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rsub__(self, other)
return _tensor_expr_overload.__rsub__(self, other)
def __mul__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__mul__(self, other)
return _tensor_expr_overload.__mul__(self, other)
def __rmul__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rmul__(self, other)
return _tensor_expr_overload.__rmul__(self, other)
def __div__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__div__(self, other)
return _tensor_expr_overload.__div__(self, other)
def __rdiv__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rdiv__(self, other)
return _tensor_expr_overload.__rdiv__(self, other)
def __truediv__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__truediv__(self, other)
return _tensor_expr_overload.__truediv__(self, other)
def __rtruediv__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rtruediv__(self, other)
return _tensor_expr_overload.__rtruediv__(self, other)
def __floordiv__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__floordiv__(self, other)
return _tensor_expr_overload.__floordiv__(self, other)
def __rfloordiv__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rfloordiv__(self, other)
return _tensor_expr_overload.__rfloordiv__(self, other)
def __mod__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__mod__(self, other)
return _tensor_expr_overload.__mod__(self, other)
def __rmod__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rmod__(self, other)
return _tensor_expr_overload.__rmod__(self, other)
def __pow__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return NotImplemented
return _tensor_expr_overload.__pow__(self, other)
def __rpow__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return NotImplemented
return _tensor_expr_overload.__rpow__(self, other)
def __neg__(self):
+ self = _realize_operand(self)
if is_prim_expr(self):
result = _overload_prim_expr.__neg__(self)
if result is NotImplemented:
@@ -203,56 +254,67 @@ class _ExprWithOp(Expr, Scriptable):
return result
def __lshift__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__lshift__(self, other)
return NotImplemented
def __rlshift__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rlshift__(self, other)
return NotImplemented
def __rshift__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rshift__(self, other)
return NotImplemented
def __rrshift__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rrshift__(self, other)
return NotImplemented
def __and__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__and__(self, other)
return NotImplemented
def __rand__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rand__(self, other)
return NotImplemented
def __or__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__or__(self, other)
return NotImplemented
def __ror__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__ror__(self, other)
return NotImplemented
def __xor__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__xor__(self, other)
return NotImplemented
def __rxor__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rxor__(self, other)
return NotImplemented
def __invert__(self):
+ self = _realize_operand(self)
if is_prim_expr(self):
result = _overload_prim_expr.__invert__(self)
if result is NotImplemented:
@@ -261,31 +323,37 @@ class _ExprWithOp(Expr, Scriptable):
return NotImplemented
def __lt__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__lt__(self, other)
return _tensor_expr_overload.__lt__(self, other)
def __le__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__le__(self, other)
return _tensor_expr_overload.__le__(self, other)
def __eq__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__eq__(self, other)
return Object.__eq__(self, other)
def __ne__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__ne__(self, other)
return Object.__ne__(self, other)
def __gt__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__gt__(self, other)
return _tensor_expr_overload.__gt__(self, other)
def __ge__(self, other):
+ self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__ge__(self, other)
return _tensor_expr_overload.__ge__(self, other)
@@ -300,12 +368,14 @@ class _ExprWithOp(Expr, Scriptable):
return self.__nonzero__()
def equal(self, other, span=None):
+ self, other = _realize_operand(self), _realize_operand(other)
result = _overload_prim_expr.equal(self, other, span)
if result is NotImplemented:
raise TypeError("Primitive expression overload equal is not
registered")
return result
def astype(self, dtype, span=None):
+ self = _realize_operand(self)
if is_prim_expr(self):
result = _overload_prim_expr.astype(self, dtype, span)
if result is NotImplemented:
@@ -317,6 +387,7 @@ class _ExprWithOp(Expr, Scriptable):
return result
def __call__(self, *args, attrs=None):
+ self, args = _realize_operand(self), tuple(_realize_operand(arg) for
arg in args)
if is_prim_expr(self):
raise TypeError("A primitive-valued expression cannot be called")
result = _tensor_expr_overload.__call__(self, *args, attrs=attrs)
@@ -324,13 +395,88 @@ class _ExprWithOp(Expr, Scriptable):
raise TypeError("Tensor expression overload __call__ is not
registered")
return result
+
+class _ExprWithOp(ExprOperand, Expr, Scriptable):
+ """Common type-directed operator behavior for core expressions."""
+
+ __hash__ = Expr.__hash__
+
+ def _operand(self) -> Expr:
+ return self
+
+
+class SubscriptProxy(ExprOperand, ObjectConvertible):
+ """An immutable, lazily-realized subscription of an :class:`Expr`.
+
+ Point subscriptions may be chained to accumulate dimensions. A proxy
+ containing a slice must be realized before applying a region subscript.
+ """
+
+ __slots__ = ("_result", "_slice", "_source")
+ __hash__ = object.__hash__
+
+ def __init__(self, source: Expr, index):
+ if isinstance(source, SubscriptProxy):
+ if any(isinstance(item, slice) for item in source._slice):
+ raise TypeError("Cannot chain a subscription after a slice")
+ self._source = source._source
+ self._slice = source._slice + self._flatten(index)
+ else:
+ _ffi_api.SubscriptExprCheck(source)
+ self._source = source
+ self._slice = self._flatten(index)
+ self._result = None
+
+ @staticmethod
+ def _flatten(index):
+ return tuple(index) if isinstance(index, tuple | list) else (index,)
+
+ @staticmethod
+ def _convert_index(index):
+ def convert(value):
+ value = _realize_operand(value)
+ if value is None or is_prim_expr(value):
+ return value
+ return tvm.tirx.const(value)
+
+ if isinstance(index, slice):
+ return (convert(index.start), convert(index.stop),
convert(index.step))
+ if index is Ellipsis or index is None:
+ raise TypeError("Ellipsis and newaxis are not supported in
expression subscriptions")
+ return convert(index)
+
+ def _operand(self) -> Object:
+ return self.to_expr()
+
def __getitem__(self, index):
- if is_prim_expr(self):
- raise TypeError("A primitive-valued expression cannot be indexed")
- result = _tensor_expr_overload.__getitem__(self, index)
- if result is NotImplemented:
- raise TypeError("Tensor expression overload __getitem__ is not
registered")
- return result
+ return SubscriptProxy(self, index)
+
+ def to_expr(self) -> Object:
+ """Realize and cache the subscribed IR object."""
+ if self._result is None:
+ result = _ffi_api.SubscriptExprRealize(
+ self._source, [self._convert_index(index) for index in
self._slice]
+ )
+ if not isinstance(result, Object):
+ raise TypeError("__subscript_expr_realize__ must return an
Object")
+ self._result = result
+ return self._result
+
+ def asobject(self) -> Object:
+ return self.to_expr()
+
+ def __tvm_ffi_object__(self) -> Object:
+ return self.to_expr()
+
+ @property
+ def ty(self):
+ return self.to_expr().ty
+
+ def same_as(self, other):
+ return self.to_expr().same_as(_realize_operand(other))
+
+ def __repr__(self):
+ return f"SubscriptProxy({self._source!r}, {self._slice!r})"
@tvm_ffi.register_object("ir.Tuple")
diff --git a/python/tvm/relax/expr.py b/python/tvm/relax/expr.py
index 3c983da029..51c63e21d1 100644
--- a/python/tvm/relax/expr.py
+++ b/python/tvm/relax/expr.py
@@ -206,35 +206,6 @@ class ExprWithOp(Expr, Scriptable):
"""
return tvm.ir.Call(self, args, attrs=attrs)
- def __getitem__(self, index: int) -> "ExprWithOp":
- """Get the i-th element of the tuple or Expr with TupleType.
-
- Parameters
- ----------
- index: int
- The index of the element to be retrieved.
-
- Note
- ----
- This function will be overridden by Tuple and ShapeExpr
-
- Returns
- -------
- result: ExprWithOp
- The result expression.
- """
- try:
- return TupleGetItem(self, index)
- except RuntimeError as err:
- # For Python objects with __getitem__, but without
- # __len__, tuple unpacking is done by iterating over
- # sequential indices until IndexError is raised.
- # Therefore, convert from RuntimeError to IndexError for
- # compatibility.
- if "Index out of bounds" in err.args[0]:
- raise IndexError from err
- raise
-
@tvm_ffi.register_object("relax.expr.If")
class If(ExprWithOp):
diff --git a/python/tvm/relax/op/__init__.py b/python/tvm/relax/op/__init__.py
index 24fa24b02a..21a25e16b4 100644
--- a/python/tvm/relax/op/__init__.py
+++ b/python/tvm/relax/op/__init__.py
@@ -185,19 +185,10 @@ def _register_op_make():
def _rhs(_lhs, rhs):
return expr._binary_rhs_helper(rhs)
- def _getitem(value, index):
- try:
- return expr.TupleGetItem(value, index)
- except RuntimeError as err:
- if "Index out of bounds" in err.args[0]:
- raise IndexError from err
- raise
-
_tensor_expr_overload.astype = lambda lhs, dtype, _span=None:
_ffi_api.astype(lhs, dtype)
_tensor_expr_overload.__call__ = lambda func, *args, attrs=None:
expr.tvm.ir.Call(
func, args, attrs=attrs
)
- _tensor_expr_overload.__getitem__ = _getitem
_tensor_expr_overload.__neg__ = lambda lhs: _ffi_api.negative(lhs)
_tensor_expr_overload.__lt__ = lambda lhs, rhs:
expr._binary_op_helper(lhs, rhs, _ffi_api.less)
_tensor_expr_overload.__le__ = lambda lhs, rhs: expr._binary_op_helper(
diff --git a/python/tvm/script/parser/core/evaluator.py
b/python/tvm/script/parser/core/evaluator.py
index 71a1b3bc8f..06f6dc9b09 100644
--- a/python/tvm/script/parser/core/evaluator.py
+++ b/python/tvm/script/parser/core/evaluator.py
@@ -413,6 +413,9 @@ class ExprEvaluator:
The evaluation result.
"""
test = self._eval_expr(self._visit(node.test))
+ from tvm.ir.expr import _realize_operand # pylint:
disable=import-outside-toplevel
+
+ test = _realize_operand(test)
if isinstance(test, bool):
selected = node.body if test else node.orelse
return self._eval_expr(self._visit(selected))
diff --git a/python/tvm/script/parser/core/parser.py
b/python/tvm/script/parser/core/parser.py
index 771be8f3d1..7d0f2887df 100644
--- a/python/tvm/script/parser/core/parser.py
+++ b/python/tvm/script/parser/core/parser.py
@@ -627,7 +627,14 @@ class Parser(doc.NodeVisitor):
for k, v in extra_vars.items():
var_values[k] = v
var_values[ScriptMacro.parser_object_name] = self
- return eval_expr(self, node, var_values)
+ value = eval_expr(self, node, var_values)
+
+ # Subscription proxies are an expression-construction detail. Keep
+ # them lazy while evaluating a compound Python expression, then
+ # normalize once at the parser boundary before statement dispatch.
+ from tvm.ir.expr import _realize_operand # pylint:
disable=import-outside-toplevel
+
+ return _realize_operand(value)
def _duplicate_lhs_check(self, target: doc.expr) -> bool | set[str]:
"""Check whether duplicate lhs exists in assignment.
diff --git a/python/tvm/tirx/_buffer_view.py b/python/tvm/tirx/_buffer_view.py
index 90c4a13a61..3977cf2f3a 100644
--- a/python/tvm/tirx/_buffer_view.py
+++ b/python/tvm/tirx/_buffer_view.py
@@ -572,7 +572,9 @@ class ChunkIndexer:
else:
size = buf.shape[dim] // int(count)
translated.append(slice(pick * size, (pick + 1) * size))
- return buf[tuple(translated)]
+ # ``chunk`` is a view helper whose documented result is a region, not
+ # the lazy subscription proxy used by ordinary expression indexing.
+ return buf[tuple(translated)].to_expr()
class TileIndexer:
diff --git a/python/tvm/tirx/buffer.py b/python/tvm/tirx/buffer.py
index d832aa7602..dc62b35f4c 100644
--- a/python/tvm/tirx/buffer.py
+++ b/python/tvm/tirx/buffer.py
@@ -22,7 +22,7 @@ from numbers import Integral
import tvm_ffi
import tvm
-from tvm.ir import PointerType, PrimType, Range, Type
+from tvm.ir import PointerType, PrimType, Type
from tvm.runtime import convert
from . import _buffer_view, _ffi_api
@@ -458,59 +458,6 @@ class _BufferMethods:
"""
return _buffer_view.chunk(self, spec)
- def __getitem__(self, indices):
- if not is_buffer_var(self):
- return _ORIGINAL_VAR_GETITEM(self, indices)
-
- from ..arith import Analyzer # pylint: disable=import-outside-toplevel
- from .expr import BufferLoad, Ramp # pylint:
disable=import-outside-toplevel
- from .stmt import BufferRegion # pylint:
disable=import-outside-toplevel
-
- if not isinstance(indices, tuple | list):
- indices = [indices]
- has_slice = any(isinstance(i, slice) for i in indices)
- has_step = any(
- isinstance(i, slice) and (i.step is not None and i.step != 1) for
i in indices
- )
- has_implicit_slice = len(indices) < len(self.ty.shape)
- analyzer = Analyzer()
- if (has_slice and not has_step) or has_implicit_slice:
- region = []
- for i, index in enumerate(indices):
- if isinstance(index, slice):
- start = 0 if index.start is None else index.start
- stop = self.ty.shape[i] if index.stop is None else
index.stop
- region.append(Range.from_min_extent(start,
analyzer.simplify(stop - start)))
- else:
- region.append(
- Range.from_min_extent(
- index,
- tvm.tirx.expr.IntImm(index.ty, 1) if
tvm.ir.is_prim_expr(index) else 1,
- )
- )
- if has_implicit_slice:
- for i in range(len(indices), len(self.ty.shape)):
- region.append(Range.from_min_extent(0, self.ty.shape[i]))
- return BufferRegion(self, region)
- else:
- expr_indices = []
- for i, index in enumerate(indices):
- if isinstance(index, slice):
- start = 0 if index.start is None else index.start
- stop = self.ty.shape[i] if index.stop is None else
index.stop
- step = 1 if index.step is None else index.step
- # We should ensure the dtype of start is the same with
that of step.
- if tvm.ir.is_prim_expr(start) and isinstance(step, int):
- step = tvm.tirx.expr.IntImm(start.ty, step)
- lanes = analyzer.simplify((stop - start + step - 1) //
step)
- if lanes == 1:
- expr_indices.append(start)
- else:
- expr_indices.append(Ramp(start, step, int(lanes)))
- else:
- expr_indices.append(index)
- return BufferLoad(self, expr_indices)
-
def decl_buffer(
shape,
@@ -581,9 +528,8 @@ def buffer_data_pointer_type(buffer):
# ``tvm.tirx`` therefore augments ``tvm.ir.Var`` process-wide with the legacy
# buffer operation and metadata surface. Non-buffer Vars reject the metadata
# properties with AttributeError, preserving correct ``hasattr`` behavior.
-_ORIGINAL_VAR_GETITEM = tvm.ir.Var.__getitem__
for _name, _value in _BufferMethods.__dict__.items():
- if _name.startswith("__") and _name != "__getitem__":
+ if _name.startswith("__"):
continue
if callable(_value) or isinstance(_value, property):
setattr(tvm.ir.Var, _name, _value)
diff --git a/python/tvm/tirx/function.py b/python/tvm/tirx/function.py
index 38c701075f..7ddc7af40d 100644
--- a/python/tvm/tirx/function.py
+++ b/python/tvm/tirx/function.py
@@ -324,6 +324,10 @@ class IndexMap(Object):
mapping = mapping_function(*args, **kwargs)
+ from tvm.ir.expr import _realize_operand # pylint:
disable=import-outside-toplevel
+
+ mapping = _realize_operand(mapping)
+
initial_indices = args + list(kwargs.values())
final_indices = []
@@ -339,6 +343,7 @@ class IndexMap(Object):
if is_iterable:
for val in mapping:
+ val = _realize_operand(val)
if tvm.ir.is_prim_expr(val):
final_indices.append(val)
else:
diff --git a/python/tvm/tirx/op.py b/python/tvm/tirx/op.py
index 9d0eb50932..5621e93195 100644
--- a/python/tvm/tirx/op.py
+++ b/python/tvm/tirx/op.py
@@ -26,6 +26,7 @@ import tvm
from tvm import tirx
from tvm.ir import Call, Expr, Op, PointerType, PrimType, TensorLoad
from tvm.ir.base import Span
+from tvm.ir.expr import _realize_operand
from tvm.ir.type import TensorMapType
from tvm.runtime import const
@@ -685,6 +686,7 @@ def address_of(obj: Buffer | TensorLoad | Var, span: Span |
None = None) -> Expr
call : Expr
The call expression.
"""
+ obj = _realize_operand(obj)
if is_buffer_var(obj):
n_dim = len(obj.ty.shape)
buffer_load = BufferLoad(obj, [0] * n_dim)
diff --git a/python/tvm/tirx/script/builder/ir.py
b/python/tvm/tirx/script/builder/ir.py
index a686506d13..2b136e10ae 100644
--- a/python/tvm/tirx/script/builder/ir.py
+++ b/python/tvm/tirx/script/builder/ir.py
@@ -37,6 +37,7 @@ from tvm import tirx as tir
from tvm.ir import Call, TensorLoad, Type, is_prim_expr
from tvm.ir import register_op_attr as _register_op_attr
from tvm.ir.base import deprecated
+from tvm.ir.expr import _realize_operand
from tvm.runtime import convert
from tvm.script.ir_builder.base import IRBuilder
from tvm.script.ir_builder.ir import meta_var
@@ -328,6 +329,7 @@ def buffer(
The declared buffer.
"""
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral)
else shape
+ shape = tuple(_realize_operand(dim) for dim in shape)
if strides is not None:
strides = [Var(s, "int32") if isinstance(s, str) else s for s in
strides]
else:
@@ -527,6 +529,7 @@ def match_buffer(
res : Buffer
The matched buffer.
"""
+ param = _realize_operand(param)
if shape is None:
if isinstance(param, BufferRegion):
dtype = param.buffer.ty.dtype
@@ -1845,6 +1848,7 @@ def decl_buffer(
The declared buffer.
"""
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral)
else shape
+ shape = tuple(_realize_operand(dim) for dim in shape)
if strides is not None:
strides = [Var(s, "int32") if isinstance(s, str) else s for s in
strides]
else:
@@ -2069,7 +2073,7 @@ def alloc_scalar(dtype: str = "float32", scope: str =
"global") -> TensorLoad:
"""Allocate a zero-dimensional buffer (scalar)."""
buf = alloc_buffer(shape=(1,), dtype=dtype, scope=scope,
layout=TileLayout(S[1]))
assert is_buffer_var(buf)
- scalar = buf[0]
+ scalar = _realize_operand(buf[0])
if _current_meta_construction_scope() is not None:
return scalar
return scalar_wrapper(scalar)
@@ -2089,7 +2093,7 @@ def decl_scalar(dtype, data, scope, elem_offset=None,
byte_offset=None) -> Tenso
layout=TileLayout(S[1]),
)
assert is_buffer_var(buf)
- scalar = buf[0]
+ scalar = _realize_operand(buf[0])
if _current_meta_construction_scope() is not None:
return scalar
return scalar_wrapper(scalar)
diff --git a/python/tvm/tirx/script/builder/tirx.py
b/python/tvm/tirx/script/builder/tirx.py
index 8ad43a96d9..4313ae1e20 100644
--- a/python/tvm/tirx/script/builder/tirx.py
+++ b/python/tvm/tirx/script/builder/tirx.py
@@ -22,6 +22,7 @@ from collections.abc import Callable
import tvm
import tvm.tirx.operator as tirx_op
from tvm.ir import Op
+from tvm.ir.expr import _realize_operand
from tvm.tirx import Buffer, BufferRegion, Expr, LambdaExpr, buffer_data,
is_buffer_var
from tvm.tirx.exec_scope import _SCOPE_KIND_TO_NAME, ExecScope
from tvm.tirx.expr import FloatImm, IntImm
@@ -123,12 +124,14 @@ thread = ScopeNamespace("thread", "thread")
def _is_buffer_or_region(x):
+ x = _realize_operand(x)
return is_buffer_var(x) or isinstance(x, BufferRegion)
def _to_region(buffer: BufferRegion | Buffer):
+ buffer = _realize_operand(buffer)
if is_buffer_var(buffer):
- return buffer[[slice(None, None, None) for _ in
range(len(buffer.ty.shape))]]
+ return _realize_operand(buffer[tuple(slice(None) for _ in
buffer.ty.shape)])
assert isinstance(buffer, BufferRegion)
return buffer
diff --git a/python/tvm/tirx/script/parser/operation.py
b/python/tvm/tirx/script/parser/operation.py
index fd67d6f125..f5f578c3a0 100644
--- a/python/tvm/tirx/script/parser/operation.py
+++ b/python/tvm/tirx/script/parser/operation.py
@@ -19,6 +19,7 @@
import tvm
from tvm import tirx
from tvm.ir import PrimType
+from tvm.ir.expr import _realize_operand
from tvm.runtime import DataTypeCode
from tvm.script.parser._core import OpMethod, doc, register_op
from tvm.tirx import IntImm
@@ -37,6 +38,7 @@ def _register_expr_op(ty: type): # pylint:
disable=invalid-name
return ty
def _and(a, b):
+ a, b = _realize_operand(a), _realize_operand(b)
if isinstance(a, bool):
a = IntImm("bool", a)
if isinstance(b, bool):
@@ -47,6 +49,7 @@ def _register_expr_op(ty: type): # pylint:
disable=invalid-name
return tirx.And(a, b)
def _or(a, b):
+ a, b = _realize_operand(a), _realize_operand(b)
if isinstance(a, bool):
a = IntImm("bool", a)
if isinstance(b, bool):
@@ -64,6 +67,7 @@ def _register_expr_op(ty: type): # pylint:
disable=invalid-name
return dtype_str[0:index]
def _auto_broadcast(a, b, op):
+ a, b = _realize_operand(a), _realize_operand(b)
if isinstance(a, int):
if tvm.ir.is_prim_expr(b) or hasattr(b, "expr_ty"):
b_ty = _expr_ty(b)
@@ -165,3 +169,4 @@ def _register_expr_op(ty: type): # pylint:
disable=invalid-name
_register_expr_op(tirx.Expr)
_register_expr_op(tirx.IterVar)
+_register_expr_op(tvm.ir.SubscriptProxy)
diff --git a/python/tvm/tirx/script/parser/parser.py
b/python/tvm/tirx/script/parser/parser.py
index 607988a4d1..7398f0bc0a 100644
--- a/python/tvm/tirx/script/parser/parser.py
+++ b/python/tvm/tirx/script/parser/parser.py
@@ -24,6 +24,7 @@ from typing import Any, TypeVar
import tvm
from tvm.ir import Expr, GlobalVar, PointerType, PrimType, TensorLoad
+from tvm.ir.expr import _realize_operand
from tvm.script.ir_builder import ir as I
from tvm.script.ir_builder.base import IRBuilder
from tvm.script.ir_builder.base import IRBuilderFrame as Frame
@@ -221,6 +222,7 @@ def bind_assign_value(
res : Any
The bound value.
"""
+ value = _realize_operand(value)
if var_name in (prim_var_declarations or set()):
# A quoted Buffer shape may have already created this PrimVar. In that
# case ``n = T.int32()`` is match-like syntax: bind the Python name to
diff --git a/python/tvm/tirx/script/tile.py b/python/tvm/tirx/script/tile.py
index 4aaf79113b..cf5fc9e262 100644
--- a/python/tvm/tirx/script/tile.py
+++ b/python/tvm/tirx/script/tile.py
@@ -18,6 +18,7 @@
import functools
+from tvm.ir.expr import _realize_operand
from tvm.tirx import BufferRegion, is_buffer_var
from .builder import tirx as _builder
@@ -30,6 +31,7 @@ def _get_arg(args, kwargs, index, name):
def _require_buffer_arg(op_name, arg_name, value):
+ value = _realize_operand(value)
if not (is_buffer_var(value) or isinstance(value, BufferRegion)):
raise TypeError(
f"Tx.{op_name} is tile-only and expects `{arg_name}` to be a
Buffer "
diff --git a/src/ir/subscript_proxy.cc b/src/ir/subscript_proxy.cc
new file mode 100644
index 0000000000..a92c961c61
--- /dev/null
+++ b/src/ir/subscript_proxy.cc
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+/*!
+ * \file subscript_proxy.cc
+ * \brief Type-directed realization for the Python frontend's SubscriptProxy.
+ */
+#include <tvm/ffi/function.h>
+#include <tvm/ffi/reflection/registry.h>
+#include <tvm/ir/expr.h>
+#include <tvm/ir/type.h>
+
+namespace tvm {
+
+using SubscriptSlice = ffi::Array<ffi::Variant<
+ ffi::Tuple<ffi::Optional<PrimExpr>, ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>>,
+ PrimExpr>>;
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ refl::EnsureTypeAttrColumn("__subscript_expr_realize__");
+ refl::TypeAttrDef<TupleTypeNode>().def(
+ "__subscript_expr_realize__", [](Expr value, SubscriptSlice slice) ->
ffi::ObjectRef {
+ TVM_FFI_CHECK_EQ(slice.size(), 1, IndexError)
+ << "A tuple expression requires exactly one index";
+ auto index = slice[0].as<PrimExpr>();
+ TVM_FFI_CHECK(index.has_value(), TypeError) << "A tuple expression
requires a point index";
+ const auto* imm = index.value().as<IntImmNode>();
+ TVM_FFI_CHECK(imm != nullptr, TypeError)
+ << "A tuple expression requires a constant integer index";
+ return TupleGetItem(value, static_cast<int>(imm->value));
+ });
+ refl::GlobalDef().def("ir.SubscriptExprCheck", [](Expr value) {
+ TVM_FFI_CHECK(value.defined(), TypeError) << "Cannot subscript an
undefined expression";
+ static refl::TypeAttrColumn realize_column("__subscript_expr_realize__");
+ TVM_FFI_CHECK(realize_column[value->ty->type_index()] != nullptr,
TypeError)
+ << "Type " << value->ty->GetTypeKey() << " does not support subscript";
+ });
+ refl::GlobalDef().def(
+ "ir.SubscriptExprRealize", [](Expr value, SubscriptSlice slice) ->
ffi::ObjectRef {
+ TVM_FFI_CHECK(value.defined(), TypeError) << "Cannot subscript an
undefined expression";
+ static refl::TypeAttrColumn
realize_column("__subscript_expr_realize__");
+ ffi::AnyView packed_realize = realize_column[value->ty->type_index()];
+ TVM_FFI_CHECK(packed_realize != nullptr, TypeError)
+ << "Type " << value->ty->GetTypeKey() << " does not support
subscript";
+ ffi::ObjectRef result =
+ packed_realize.cast<ffi::Function>()(value,
slice).cast<ffi::ObjectRef>();
+ TVM_FFI_CHECK(result.defined(), TypeError)
+ << "__subscript_expr_realize__ for type " <<
value->ty->GetTypeKey()
+ << " returned an undefined object";
+ return result;
+ });
+}
+
+} // namespace tvm
diff --git a/src/relax/ir/dependent_type.cc b/src/relax/ir/dependent_type.cc
index 95bcc3ee04..2ac4898551 100644
--- a/src/relax/ir/dependent_type.cc
+++ b/src/relax/ir/dependent_type.cc
@@ -31,10 +31,27 @@ namespace tvm {
namespace relax {
TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
AnyTypeNode::RegisterReflection();
ShapeTypeNode::RegisterReflection();
TensorTypeNode::RegisterReflection();
FuncTypeNode::RegisterReflection();
+ refl::TypeAttrDef<TensorTypeNode>().def(
+ "__subscript_expr_realize__",
+ [](Expr value,
+ ffi::Array<ffi::Variant<
+ ffi::Tuple<ffi::Optional<PrimExpr>, ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>>,
+ PrimExpr>>
+ slice) -> ffi::ObjectRef {
+ TVM_FFI_CHECK_EQ(slice.size(), 1, IndexError)
+ << "A Relax expression requires exactly one index";
+ auto index = slice[0].as<PrimExpr>();
+ TVM_FFI_CHECK(index.has_value(), TypeError) << "A Relax expression
requires a point index";
+ const auto* imm = index.value().as<IntImmNode>();
+ TVM_FFI_CHECK(imm != nullptr, TypeError)
+ << "A Relax expression requires a constant integer index";
+ return TupleGetItem(value, static_cast<int>(imm->value));
+ });
}
AnyType::AnyType(Span span) : Type(ffi::UnsafeInit{}) {
diff --git a/src/tirx/ir/buffer.cc b/src/tirx/ir/buffer.cc
index 14e0358001..c715aedf0c 100644
--- a/src/tirx/ir/buffer.cc
+++ b/src/tirx/ir/buffer.cc
@@ -29,6 +29,7 @@
#include <tvm/tirx/builtin.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/op.h>
+#include <tvm/tirx/stmt.h>
#include <iterator>
#include <list>
@@ -39,7 +40,73 @@
namespace tvm {
namespace tirx {
-TVM_FFI_STATIC_INIT_BLOCK() { BufferTypeNode::RegisterReflection(); }
+namespace {
+
+ffi::ObjectRef RealizeBufferSubscript(
+ Expr value,
+ ffi::Array<ffi::Variant<
+ ffi::Tuple<ffi::Optional<PrimExpr>, ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>>,
+ PrimExpr>>
+ slice) {
+ BufferVar buffer = value.as_or_throw<BufferVar>();
+ BufferType buffer_ty = buffer.type();
+ TVM_FFI_CHECK_LE(slice.size(), buffer_ty->shape.size(), IndexError)
+ << "Too many indices for a " << buffer_ty->shape.size() << "-dimensional
buffer";
+
+ bool all_points = slice.size() == buffer_ty->shape.size();
+ for (const auto& item : slice) {
+ if (auto descriptor = item.as<ffi::Tuple<ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>,
+ ffi::Optional<PrimExpr>>>()) {
+ all_points = false;
+ ffi::Optional<PrimExpr> step = descriptor.value().get<2>();
+ TVM_FFI_CHECK(!step.has_value() || is_one(step.value()), ValueError)
+ << "Buffer slices with a non-unit step are not supported";
+ }
+ }
+
+ if (all_points) {
+ ffi::Array<PrimExpr> indices;
+ indices.reserve(slice.size());
+ for (const auto& item : slice) {
+ indices.push_back(item.as<PrimExpr>().value());
+ }
+ return BufferLoad(buffer, indices);
+ }
+
+ // Any slice or omitted trailing dimension denotes a region. Rejecting
+ // steps makes the old behavior, where a stride could be silently dropped,
+ // unrepresentable rather than giving it dimension-dependent semantics.
+ arith::Analyzer analyzer;
+ ffi::Array<Range> region;
+ region.reserve(buffer_ty->shape.size());
+ for (size_t i = 0; i < slice.size(); ++i) {
+ if (auto point = slice[i].as<PrimExpr>()) {
+ region.push_back(Range::FromMinExtent(point.value(),
IntImm(point.value().ty(), 1)));
+ } else {
+ auto descriptor = slice[i]
+ .as<ffi::Tuple<ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>,
+ ffi::Optional<PrimExpr>>>()
+ .value();
+ PrimExpr start =
descriptor.get<0>().value_or(IntImm(buffer_ty->shape[i].ty(), 0));
+ PrimExpr stop = descriptor.get<1>().value_or(buffer_ty->shape[i]);
+ // Preserve the sole simplification performed by the former Python path.
+ region.push_back(Range::FromMinExtent(start, analyzer->Simplify(stop -
start)));
+ }
+ }
+ for (size_t i = slice.size(); i < buffer_ty->shape.size(); ++i) {
+ region.push_back(
+ Range::FromMinExtent(IntImm(buffer_ty->shape[i].ty(), 0),
buffer_ty->shape[i]));
+ }
+ return BufferRegion(buffer, region);
+}
+
+} // namespace
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+ namespace refl = tvm::ffi::reflection;
+ BufferTypeNode::RegisterReflection();
+ refl::TypeAttrDef<BufferTypeNode>().def("__subscript_expr_realize__",
RealizeBufferSubscript);
+}
using IndexMod = tirx::FloorModNode;
using IndexDiv = tirx::FloorDivNode;
diff --git a/src/tirx/script/printer/buffer.cc
b/src/tirx/script/printer/buffer.cc
index a3e2c462e4..c0d58a90c9 100644
--- a/src/tirx/script/printer/buffer.cc
+++ b/src/tirx/script/printer/buffer.cc
@@ -381,6 +381,16 @@ ffi::Array<Doc> BufferIndices(const ffi::Array<PrimExpr>&
indices, const AccessP
return indices_doc;
}
+ffi::Array<Doc> BufferLoadIndices(const ffi::Array<PrimExpr>& indices, const
AccessPath& p,
+ const IRDocsifier& d) {
+ ffi::Array<Doc> indices_doc;
+ indices_doc.reserve(indices.size());
+ for (size_t i = 0; i < indices.size(); ++i) {
+ indices_doc.push_back(d->AsDoc<ExprDoc>(indices[i],
p->Attr("indices")->ArrayItem(i)));
+ }
+ return indices_doc;
+}
+
ffi::Array<Doc> BufferSlices(const ffi::Array<Range>& region, const
AccessPath& p,
const IRDocsifier& d) {
int n = region.size();
@@ -445,7 +455,7 @@ TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
return doc.value();
}
- return buffer[BufferIndices(load->indices, p->Attr("indices"), d)];
+ return buffer[BufferLoadIndices(load->indices, p->Attr("indices"),
d)];
});
TVM_STATIC_IR_FUNCTOR(IRDocsifier, vtable)
diff --git a/tests/python/s_tir/analysis/test_s_tir_analysis_identify_memcpy.py
b/tests/python/s_tir/analysis/test_s_tir_analysis_identify_memcpy.py
index acd33f9814..e4cc34f879 100644
--- a/tests/python/s_tir/analysis/test_s_tir_analysis_identify_memcpy.py
+++ b/tests/python/s_tir/analysis/test_s_tir_analysis_identify_memcpy.py
@@ -56,7 +56,7 @@ def test_1d():
B[i] = A[i]
A, B = [param for param in func.params if tvm.tirx.is_buffer_var(param)]
- expected = (A[0:1024], B[0:1024])
+ expected = (A[0:1024].to_expr(), B[0:1024].to_expr())
_check_memcpy_results(func, expected)
@@ -118,7 +118,7 @@ def test_1d_input_2d_output_fused_loop():
B[i // 32, i % 32] = A[i]
A, B = [param for param in func.params if tvm.tirx.is_buffer_var(param)]
- expected = (A[0:1024], B[0:32, 0:32])
+ expected = (A[0:1024].to_expr(), B[0:32, 0:32].to_expr())
_check_memcpy_results(func, expected)
@@ -131,7 +131,7 @@ def test_2d_input_1d_output_fused_loop():
B[i] = A[i // 32, i % 32]
A, B = [param for param in func.params if tvm.tirx.is_buffer_var(param)]
- expected = (A[0:32, 0:32], B[0:1024])
+ expected = (A[0:32, 0:32].to_expr(), B[0:1024].to_expr())
_check_memcpy_results(func, expected)
diff --git a/tests/python/tirx/test_parser_printer.py
b/tests/python/tirx/test_parser_printer.py
index 487594ca44..5023290d70 100644
--- a/tests/python/tirx/test_parser_printer.py
+++ b/tests/python/tirx/test_parser_printer.py
@@ -2194,7 +2194,7 @@ def test_buffer_chunk_ir():
assert isinstance(reg, BufferRegion)
assert len(reg.region) == 3 # rank-preserving: no extra extent-1 chunk dim
assert (int(reg.region[2].min), int(reg.region[2].extent)) == (8, 8)
- assert_structural_equal(reg, A[:, :, 8:16])
+ assert_structural_equal(reg, A[:, :, 8:16].to_expr())
# a None dim passes an int pick straight through (int → extent-1 region),
# while the chunked dim still narrows to its picked chunk.
@@ -2257,20 +2257,39 @@ def test_buffer_slice_region():
from tvm.tirx.stmt import BufferRegion
buf = tvm.tirx.decl_buffer((128, 64), "float16")
- br = buf[32:64, 0:32]
+ br = buf[32:64, 0:32].to_expr()
assert isinstance(br, BufferRegion)
assert br.buffer.same_as(buf)
assert int(br.region[0].extent) == 32
assert int(br.region[1].extent) == 32
+def test_global_call_realizes_buffer_elements():
+ @I.ir_module(s_tir=True)
+ class Module:
+ @T.prim_func(private=True, s_tir=True)
+ def add(a: T.float32, b: T.float32) -> T.float32:
+ return a + b
+
+ @T.prim_func(s_tir=True)
+ def main(
+ A: T.Buffer((16,), "float32"),
+ B: T.Buffer((16,), "float32"),
+ C: T.Buffer((16,), "float32"),
+ ):
+ for i in range(16):
+ C[i] = Module.add(A[i], B[i])
+
+ assert isinstance(Module["main"], tvm.tirx.PrimFunc)
+
+
def test_buffer_region_slice():
"""Verify BufferRegion slicing returns BufferRegion."""
from tvm.tirx.stmt import BufferRegion
buf = tvm.tirx.decl_buffer((128, 64), "float16")
- br1 = buf[32:64, 0:32]
+ br1 = buf[32:64, 0:32].to_expr()
assert isinstance(br1, BufferRegion)
# BufferRegion chained slice
diff --git a/tests/python/tvmscript/test_tvmscript_printer_tir.py
b/tests/python/tvmscript/test_tvmscript_printer_tir.py
index 0ecadaba67..8be6466b2b 100644
--- a/tests/python/tvmscript/test_tvmscript_printer_tir.py
+++ b/tests/python/tvmscript/test_tvmscript_printer_tir.py
@@ -1129,7 +1129,7 @@ def test_vload_with_explicit_scalable_data_type():
@T.prim_func(s_tir=True)
def main(A: T.Buffer((128,), "float32"), B: T.Buffer((128,), "float32")):
- B[0:T.vscale() * 4] = A[0:T.vscale() * 4]
+ B[0:T.vscale() * 4] = A[T.Ramp(0, 1, T.vscale() * 4)]
"""
_assert_print(main, expected_output)
@@ -1149,7 +1149,7 @@ def test_vectorize_llvm_pure_intrin():
@T.prim_func(s_tir=True)
def main(A: T.Buffer((4,), "float32"), B: T.Buffer((4,), "float32")):
- A[0:4] = T.call_llvm_pure_intrin("float32x4", "llvm.sqrt", B[0:4])
+ A[0:4] = T.call_llvm_pure_intrin("float32x4", "llvm.sqrt", B[T.Ramp(0, 1,
4)])
"""
_assert_print(main, expected_output)
diff --git a/tests/python/tvmscript/test_tvmscript_roundtrip.py
b/tests/python/tvmscript/test_tvmscript_roundtrip.py
index ddc2c09603..b55d5279f9 100644
--- a/tests/python/tvmscript/test_tvmscript_roundtrip.py
+++ b/tests/python/tvmscript/test_tvmscript_roundtrip.py
@@ -2425,9 +2425,9 @@ def buffer_ramp_access_as_slice_index():
for i in range(128):
A[i : i + 1 : 1] = i
for i in range(4):
- B[i * 32 : i * 32 + 32] = A[i * 32 : i * 32 + 32 : 1] +
T.broadcast(1.0, 32)
+ B[i * 32 : i * 32 + 32] = A[T.Ramp(i * 32, 1, 32)] +
T.broadcast(1.0, 32)
for i in range(4):
- C[i : i + 128 : 4] = B[i : i + 128 : 4] + T.broadcast(1.0, 32)
+ C[i : i + 128 : 4] = B[T.Ramp(i, 4, 32)] + T.broadcast(1.0, 32)
return buffer_ramp_access