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 af6ae2afaf [REFACTOR][IR] Make expression subscription eager and
remove SubscriptProxy (#20257)
af6ae2afaf is described below
commit af6ae2afafcd947a5f09324d30932415a9ed87a6
Author: Tianqi Chen <[email protected]>
AuthorDate: Tue Sep 1 13:07:19 2026 -0400
[REFACTOR][IR] Make expression subscription eager and remove SubscriptProxy
(#20257)
Lazy subscription required every expression consumer to remember to
realize a proxy, so missed boundaries failed far from the original
subscript. Realize subscriptions immediately through the existing
type-directed hooks and remove SubscriptProxy plus the consumer-side
realization paths it required.
This deliberately removes chained BufferRegion subscription: use A[i, j]
instead of A[i][j]. Tuple bounds now raise IndexError directly from
TupleGetItem so Python sequence iteration retains its existing behavior.
Validation:
- clean RelWithDebInfo build with LLVM and CUDA enabled
- affected Python suites: 560 passed, 5 skipped
- final parser cleanup suite: 201 passed, 1 skipped
- pre-commit run --all-files
---
python/tvm/backend/cuda/ptx/engine.py | 6 +-
python/tvm/ir/__init__.py | 1 -
python/tvm/ir/expr.py | 178 +++------------------
python/tvm/script/parser/core/evaluator.py | 20 +--
python/tvm/script/parser/core/parser.py | 13 +-
python/tvm/tirx/_buffer_view.py | 4 +-
python/tvm/tirx/function.py | 5 -
python/tvm/tirx/op.py | 2 -
python/tvm/tirx/script/builder/ir.py | 10 +-
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 -
python/tvm/tirx/stmt.py | 37 +----
src/ir/expr.cc | 6 +-
src/ir/{subscript_proxy.cc => subscript_expr.cc} | 10 +-
tests/python/relax/test_blockbuilder_core.py | 3 +
.../test_s_tir_analysis_identify_memcpy.py | 6 +-
tests/python/tirx/test_parser_printer.py | 31 ++--
19 files changed, 60 insertions(+), 286 deletions(-)
diff --git a/python/tvm/backend/cuda/ptx/engine.py
b/python/tvm/backend/cuda/ptx/engine.py
index 5ffdecc785..3897b22991 100644
--- a/python/tvm/backend/cuda/ptx/engine.py
+++ b/python/tvm/backend/cuda/ptx/engine.py
@@ -41,7 +41,6 @@ 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
@@ -444,7 +443,7 @@ def _coerce_pred_operand(entry, slot, values):
like any other, and no syntax line offers a non-predicate alternative at
the same position.
"""
- (value,) = [_realize_operand(value) for value in values]
+ (value,) = values
if slot.rw != "r":
# The 0/1 materialization of a .pred result: a "=r" uint32 the caller
# receives through a reference parameter, so it needs a writable
@@ -457,7 +456,7 @@ def _coerce_pred_operand(entry, slot, values):
)
return values
if isinstance(value, PredArg):
- value = _realize_operand(getattr(value.value, "scalar", value.value))
+ value = getattr(value.value, "scalar", value.value)
if isinstance(value, bool | int):
return [const(int(value), "uint32")]
ty = getattr(value, "ty", None)
@@ -482,7 +481,6 @@ 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 412bd89061..977ec90dcc 100644
--- a/python/tvm/ir/__init__.py
+++ b/python/tvm/ir/__init__.py
@@ -42,7 +42,6 @@ from .expr import (
GlobalVar,
OpaqueExpr,
Range,
- SubscriptProxy,
TensorLoad,
Tuple,
TupleGetItem,
diff --git a/python/tvm/ir/expr.py b/python/tvm/ir/expr.py
index 5005737e26..7bcddca35e 100644
--- a/python/tvm/ir/expr.py
+++ b/python/tvm/ir/expr.py
@@ -22,11 +22,26 @@ import tvm_ffi
import tvm
-from ..runtime import Object, ObjectConvertible, Scriptable
+from ..runtime import Object, Scriptable
from . import _ffi_api, _overload_prim_expr, _tensor_expr_overload
from .base import Node, Span
+def _convert_subscript_index(index):
+ """Convert Python indexing syntax into an FFI subscript descriptor."""
+
+ def convert(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)
+
+
@tvm_ffi.register_object("ir.Expr")
class Expr(Node):
"""Base class of all the expressions."""
@@ -35,29 +50,15 @@ class Expr(Node):
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)], None
- )
- 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)
+
+ indices = tuple(index) if isinstance(index, tuple | list) else (index,)
+ return _ffi_api.SubscriptExprRealize(
+ self, [_convert_subscript_index(item) for item in indices], None
+ )
@tvm_ffi.register_object("ir.OpaqueExpr")
@@ -108,8 +109,6 @@ 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)
@@ -127,140 +126,115 @@ class GlobalVar(Expr):
raise RuntimeError(f"Do not know how to handle GlobalVar.__call__ for
types {arg_types}")
-def _realize_operand(value):
- return value._operand() if isinstance(value, ExprOperand) else value
-
-
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)
result = _tensor_expr_overload.__add__(self, other)
return result
def __radd__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__radd__(self, other)
result = _tensor_expr_overload.__radd__(self, other)
return result
def __sub__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__sub__(self, other)
result = _tensor_expr_overload.__sub__(self, other)
return result
def __rsub__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rsub__(self, other)
result = _tensor_expr_overload.__rsub__(self, other)
return result
def __mul__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__mul__(self, other)
result = _tensor_expr_overload.__mul__(self, other)
return result
def __rmul__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rmul__(self, other)
result = _tensor_expr_overload.__rmul__(self, other)
return result
def __div__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__div__(self, other)
result = _tensor_expr_overload.__div__(self, other)
return result
def __rdiv__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rdiv__(self, other)
result = _tensor_expr_overload.__rdiv__(self, other)
return result
def __truediv__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__truediv__(self, other)
result = _tensor_expr_overload.__truediv__(self, other)
return result
def __rtruediv__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rtruediv__(self, other)
result = _tensor_expr_overload.__rtruediv__(self, other)
return result
def __floordiv__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__floordiv__(self, other)
result = _tensor_expr_overload.__floordiv__(self, other)
return result
def __rfloordiv__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rfloordiv__(self, other)
result = _tensor_expr_overload.__rfloordiv__(self, other)
return result
def __mod__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__mod__(self, other)
result = _tensor_expr_overload.__mod__(self, other)
return result
def __rmod__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__rmod__(self, other)
result = _tensor_expr_overload.__rmod__(self, other)
return result
def __pow__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return NotImplemented
result = _tensor_expr_overload.__pow__(self, other)
return result
def __rpow__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return NotImplemented
result = _tensor_expr_overload.__rpow__(self, other)
return result
def __neg__(self):
- self = _realize_operand(self)
if is_prim_expr(self):
result = _overload_prim_expr.__neg__(self)
if result is NotImplemented:
@@ -272,67 +246,56 @@ class ExprOperand:
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:
@@ -341,40 +304,34 @@ class ExprOperand:
raise TypeError(f"Operator overloading is not supported for expression
type {self.ty}")
def __lt__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__lt__(self, other)
result = _tensor_expr_overload.__lt__(self, other)
return result
def __le__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__le__(self, other)
result = _tensor_expr_overload.__le__(self, other)
return result
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)
result = _tensor_expr_overload.__gt__(self, other)
return result
def __ge__(self, other):
- self, other = _realize_operand(self), _realize_operand(other)
if is_prim_expr(self):
return _overload_prim_expr.__ge__(self, other)
result = _tensor_expr_overload.__ge__(self, other)
@@ -390,7 +347,6 @@ class ExprOperand:
return self.__nonzero__()
def equal(self, other, span=None):
- self, other = _realize_operand(self), _realize_operand(other)
if not is_prim_expr(self):
raise TypeError(f"Operator overloading is not supported for
expression type {self.ty}")
result = _overload_prim_expr.equal(self, other, span)
@@ -399,7 +355,6 @@ class ExprOperand:
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:
@@ -417,7 +372,6 @@ class _ExprCallable:
__slots__ = ()
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)
@@ -431,101 +385,11 @@ class ExprWithOp(ExprOperand, Expr, Scriptable):
__hash__ = Expr.__hash__
- def _operand(self) -> Expr:
- return self
-
class _CallableExprWithOp(_ExprCallable, ExprWithOp):
"""Common operator behavior for expression nodes that support function
calls."""
-class SubscriptProxy(_ExprCallable, 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", "_span")
- __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)
- self._span = source._span
- else:
- _ffi_api.SubscriptExprCheck(source)
- self._source = source
- self._slice = self._flatten(index)
- self._span = None
- self._result = None
-
- def with_span(self, span: Span) -> "SubscriptProxy":
- """Return an unrealized proxy carrying its frontend source span."""
- result = object.__new__(SubscriptProxy)
- result._source = self._source
- result._slice = self._slice
- result._span = span
- result._result = None
- return result
-
- @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):
- 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],
- self._span,
- )
- 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")
class Tuple(_CallableExprWithOp):
"""Tuple expression that groups several fields together.
diff --git a/python/tvm/script/parser/core/evaluator.py
b/python/tvm/script/parser/core/evaluator.py
index e22abf4b9a..71a1b3bc8f 100644
--- a/python/tvm/script/parser/core/evaluator.py
+++ b/python/tvm/script/parser/core/evaluator.py
@@ -131,7 +131,7 @@ class ExprEvaluator:
return result.value
raise TypeError(f"Unexpected result type: {type(result)}")
- def _add_intermediate_result(self, value: Any, node: doc.AST) -> doc.Name:
+ def _add_intermediate_result(self, value: Any) -> doc.Name:
"""Add intermediate result during evaluation into value table.
Parameters
@@ -139,16 +139,13 @@ class ExprEvaluator:
value : Any
The intermediate result.
- node : doc.AST
- The AST node that produced the intermediate result.
-
Returns
-------
name : doc.Name
The doc AST name node with intermediate name for intermediate
result.
"""
if self.parser is not None:
- value = self.parser.annotate_current_source_span(value, node)
+ value = self.parser.annotate_current_source_span(value)
name = f"__tvm_tmp_value_{self.new_value_count}"
self.new_value_count += 1
self.value_table[name] = value
@@ -196,13 +193,13 @@ class ExprEvaluator:
value = self._eval_bool_op(node)
except Exception as err: # pylint: disable=broad-except
self.parser.report_error(node, err)
- return self._add_intermediate_result(value, node)
+ return self._add_intermediate_result(value)
if isinstance(node, doc.IfExp):
try:
value = self._eval_if_exp(node)
except Exception as err: # pylint: disable=broad-except
self.parser.report_error(node, err)
- return self._add_intermediate_result(value, node)
+ return self._add_intermediate_result(value)
args = []
if (
@@ -265,7 +262,7 @@ class ExprEvaluator:
if isinstance(node, doc.ListComp | doc.SetComp | doc.DictComp):
value = self._eval_expr(node)
- return self._add_intermediate_result(value, node)
+ return self._add_intermediate_result(value)
fields = {}
for field in node.__class__._FIELDS: # pylint:
disable=protected-access
@@ -287,7 +284,7 @@ class ExprEvaluator:
value = self._eval_expr(node.__class__(**fields))
except Exception as err: # pylint: disable=broad-except
self.parser.report_error(node, err)
- return self._add_intermediate_result(value, node)
+ return self._add_intermediate_result(value)
def _eval_lambda(self, node: doc.Lambda) -> Any:
"""The doc AST lambda node evaluating method.
@@ -306,7 +303,7 @@ class ExprEvaluator:
value = self._eval_expr(node)
except Exception as err: # pylint: disable=broad-except
self.parser.report_error(node, err)
- return self._add_intermediate_result(value, node)
+ return self._add_intermediate_result(value)
def _eval_bool_op(self, node: doc.BoolOp) -> Any:
"""The doc AST boolean operator node evaluating method.
@@ -416,9 +413,6 @@ 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 94c74f24a1..6319eacb34 100644
--- a/python/tvm/script/parser/core/parser.py
+++ b/python/tvm/script/parser/core/parser.py
@@ -596,12 +596,8 @@ class Parser(doc.NodeVisitor):
with
IRBuilder.current().with_source_span(self.diag.source.to_span(node)):
yield
- def annotate_current_source_span(self, value: Any, node: doc.AST | None =
None) -> Any:
+ def annotate_current_source_span(self, value: Any) -> Any:
"""Attach the active parser span to an expression result, when
applicable."""
- from tvm.ir.expr import SubscriptProxy # pylint:
disable=import-outside-toplevel
-
- if isinstance(value, SubscriptProxy) and node is not None:
- return value.with_span(self.diag.source.to_span(node))
if isinstance(value, Object) and IRBuilder.is_in_scope():
return IRBuilder.current()._set_current_source_span(value) #
pylint: disable=protected-access
return value
@@ -633,12 +629,7 @@ class Parser(doc.NodeVisitor):
var_values[ScriptMacro.parser_object_name] = self
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 self.annotate_current_source_span(_realize_operand(value), node)
+ return self.annotate_current_source_span(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 3977cf2f3a..90c4a13a61 100644
--- a/python/tvm/tirx/_buffer_view.py
+++ b/python/tvm/tirx/_buffer_view.py
@@ -572,9 +572,7 @@ class ChunkIndexer:
else:
size = buf.shape[dim] // int(count)
translated.append(slice(pick * size, (pick + 1) * size))
- # ``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()
+ return buf[tuple(translated)]
class TileIndexer:
diff --git a/python/tvm/tirx/function.py b/python/tvm/tirx/function.py
index 7ddc7af40d..38c701075f 100644
--- a/python/tvm/tirx/function.py
+++ b/python/tvm/tirx/function.py
@@ -324,10 +324,6 @@ 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 = []
@@ -343,7 +339,6 @@ 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 29496bcd35..4d9627292a 100644
--- a/python/tvm/tirx/op.py
+++ b/python/tvm/tirx/op.py
@@ -26,7 +26,6 @@ import tvm
from tvm import tirx
from tvm.ir import Call, Expr, ExprWithOp, 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
@@ -686,7 +685,6 @@ 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 2b136e10ae..c544d35902 100644
--- a/python/tvm/tirx/script/builder/ir.py
+++ b/python/tvm/tirx/script/builder/ir.py
@@ -37,7 +37,6 @@ 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
@@ -329,7 +328,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)
+ shape = tuple(shape)
if strides is not None:
strides = [Var(s, "int32") if isinstance(s, str) else s for s in
strides]
else:
@@ -529,7 +528,6 @@ 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
@@ -1848,7 +1846,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)
+ shape = tuple(shape)
if strides is not None:
strides = [Var(s, "int32") if isinstance(s, str) else s for s in
strides]
else:
@@ -2073,7 +2071,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 = _realize_operand(buf[0])
+ scalar = buf[0]
if _current_meta_construction_scope() is not None:
return scalar
return scalar_wrapper(scalar)
@@ -2093,7 +2091,7 @@ def decl_scalar(dtype, data, scope, elem_offset=None,
byte_offset=None) -> Tenso
layout=TileLayout(S[1]),
)
assert is_buffer_var(buf)
- scalar = _realize_operand(buf[0])
+ scalar = 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 4313ae1e20..76d181621c 100644
--- a/python/tvm/tirx/script/builder/tirx.py
+++ b/python/tvm/tirx/script/builder/tirx.py
@@ -22,7 +22,6 @@ 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
@@ -124,14 +123,12 @@ 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 _realize_operand(buffer[tuple(slice(None) for _ in
buffer.ty.shape)])
+ return 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 f5f578c3a0..fd67d6f125 100644
--- a/python/tvm/tirx/script/parser/operation.py
+++ b/python/tvm/tirx/script/parser/operation.py
@@ -19,7 +19,6 @@
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
@@ -38,7 +37,6 @@ 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):
@@ -49,7 +47,6 @@ 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):
@@ -67,7 +64,6 @@ 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)
@@ -169,4 +165,3 @@ 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 7398f0bc0a..607988a4d1 100644
--- a/python/tvm/tirx/script/parser/parser.py
+++ b/python/tvm/tirx/script/parser/parser.py
@@ -24,7 +24,6 @@ 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
@@ -222,7 +221,6 @@ 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 cf5fc9e262..4aaf79113b 100644
--- a/python/tvm/tirx/script/tile.py
+++ b/python/tvm/tirx/script/tile.py
@@ -18,7 +18,6 @@
import functools
-from tvm.ir.expr import _realize_operand
from tvm.tirx import BufferRegion, is_buffer_var
from .builder import tirx as _builder
@@ -31,7 +30,6 @@ 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/python/tvm/tirx/stmt.py b/python/tvm/tirx/stmt.py
index fdccf5ee14..ff3c27a990 100644
--- a/python/tvm/tirx/stmt.py
+++ b/python/tvm/tirx/stmt.py
@@ -33,9 +33,8 @@ from typing import Any
import tvm_ffi
-from tvm.ir import Expr, Range, Span, is_prim_expr
+from tvm.ir import Expr, Range, Span
from tvm.runtime import Object, Scriptable, const
-from tvm.tirx import IntImm
from . import _ffi_api
from .buffer import Buffer
@@ -626,40 +625,6 @@ class BufferRegion(Object, Scriptable):
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):
diff --git a/src/ir/expr.cc b/src/ir/expr.cc
index d9c34033d3..67f0318e2a 100644
--- a/src/ir/expr.cc
+++ b/src/ir/expr.cc
@@ -73,11 +73,11 @@ Tuple::Tuple(ffi::Array<Expr> fields, Span span) {
}
TupleGetItem::TupleGetItem(Expr tuple, int index, Span span) {
- TVM_FFI_ICHECK_GE(index, 0) << "Index out of bounds: Tuple " << tuple
- << " cannot be accessed with negative index " <<
index;
+ TVM_FFI_CHECK_GE(index, 0, IndexError) << "Index out of bounds: Tuple " <<
tuple
+ << " cannot be accessed with negative
index " << index;
ffi::ObjectPtr<TupleGetItemNode> node = ffi::make_object<TupleGetItemNode>();
if (const auto* tuple_type = tuple->ty.as<TupleTypeNode>()) {
- TVM_FFI_ICHECK_LT(index, tuple_type->fields.size())
+ TVM_FFI_CHECK_LT(index, tuple_type->fields.size(), IndexError)
<< "Index out of bounds: Tuple " << tuple << " is of size " <<
tuple_type->fields.size()
<< ", and cannot be accessed with index " << index;
node->ty = tuple_type->fields[index];
diff --git a/src/ir/subscript_proxy.cc b/src/ir/subscript_expr.cc
similarity index 84%
rename from src/ir/subscript_proxy.cc
rename to src/ir/subscript_expr.cc
index c6e8e75109..9225a7ad62 100644
--- a/src/ir/subscript_proxy.cc
+++ b/src/ir/subscript_expr.cc
@@ -18,8 +18,8 @@
*/
/*!
- * \file subscript_proxy.cc
- * \brief Type-directed realization for the Python frontend's SubscriptProxy.
+ * \file subscript_expr.cc
+ * \brief Type-directed realization for Python expression subscription.
*/
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
@@ -47,12 +47,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
<< "A tuple expression requires a constant integer index";
return TupleGetItem(value, static_cast<int>(imm->value), span);
});
- 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, Span
span) -> ffi::ObjectRef {
TVM_FFI_CHECK(value.defined(), TypeError) << "Cannot subscript an
undefined expression";
diff --git a/tests/python/relax/test_blockbuilder_core.py
b/tests/python/relax/test_blockbuilder_core.py
index b76acc1ee9..212b9d4388 100644
--- a/tests/python/relax/test_blockbuilder_core.py
+++ b/tests/python/relax/test_blockbuilder_core.py
@@ -333,6 +333,9 @@ def test_tuple_indexing():
y = relax_tuple[1]
tvm.ir.assert_structural_equal(y.ty, shape_y)
+ with pytest.raises(IndexError, match="Index out of bounds"):
+ relax_tuple[2]
+
# Tuple unpacking produces TupleGetItem structs
x_unpack, y_unpack = relax_tuple
tvm.ir.assert_structural_equal(x, x_unpack)
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 e4cc34f879..acd33f9814 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].to_expr(), B[0:1024].to_expr())
+ expected = (A[0:1024], B[0:1024])
_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].to_expr(), B[0:32, 0:32].to_expr())
+ expected = (A[0:1024], B[0:32, 0:32])
_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].to_expr(), B[0:1024].to_expr())
+ expected = (A[0:32, 0:32], B[0:1024])
_check_memcpy_results(func, expected)
diff --git a/tests/python/tirx/test_parser_printer.py
b/tests/python/tirx/test_parser_printer.py
index 5023290d70..cf950e63e6 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].to_expr())
+ assert_structural_equal(reg, A[:, :, 8:16])
# a None dim passes an int pick straight through (int → extent-1 region),
# while the chunked dim still narrows to its picked chunk.
@@ -2257,12 +2257,20 @@ 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].to_expr()
+ br = buf[32:64, 0:32]
assert isinstance(br, BufferRegion)
assert br.buffer.same_as(buf)
assert int(br.region[0].extent) == 32
assert int(br.region[1].extent) == 32
+ load = buf[1, 2]
+ assert isinstance(load, tvm.ir.TensorLoad)
+
+ partial = buf[1]
+ assert isinstance(partial, BufferRegion)
+ with pytest.raises(TypeError):
+ _ = partial[2]
+
def test_global_call_realizes_buffer_elements():
@I.ir_module(s_tir=True)
@@ -2283,25 +2291,6 @@ def test_global_call_realizes_buffer_elements():
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].to_expr()
- assert isinstance(br1, BufferRegion)
-
- # BufferRegion chained slice
- br3 = br1[0:16, 0:16]
- assert isinstance(br3, BufferRegion)
- assert br3.buffer.same_as(buf), "chained region slice must reference root
buffer"
- assert int(br3.region[0].min) == 32
- assert int(br3.region[0].extent) == 16
- assert int(br3.region[1].min) == 0
- assert int(br3.region[1].extent) == 16
-
-
def test_roundtrip_serial_unroll_false():
"""T.serial(N, unroll=False) should round-trip."""