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 9059d9d251 [REFACTOR][Script] Simplify region and call printing
(#20640)
9059d9d251 is described below
commit 9059d9d25149c9ca7a015717b5023d1e2e1185fd
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Oct 10 11:39:30 2026 -0400
[REFACTOR][Script] Simplify region and call printing (#20640)
---
include/tvm/ir/expr.h | 8 ++
include/tvm/script/printer/doc.h | 52 ---------
python/tvm/backend/trn/tensor_instructions.py | 8 +-
python/tvm/ir/op.py | 17 +++
python/tvm/relax/op/base.py | 55 +++++++--
.../tvm/tirx/script/ir_builder/parser_protocol.py | 33 ++++--
python/tvm/tirx/tensor_instruction.py | 14 +--
src/ir/side_effect.cc | 16 +--
src/relax/script/printer/call.cc | 44 +++++++-
src/script/printer/doc.cc | 20 ----
src/script/printer/doc_printer.cc | 71 ------------
src/script/printer/ir/call.cc | 12 +-
src/script/printer/ir/prim_expr.cc | 97 +++++++++++-----
src/script/printer/ir/utils.cc | 45 +-------
src/script/printer/ir/utils.h | 7 +-
src/tirx/ir/buffer.cc | 10 ++
src/tirx/op/region.cc | 10 ++
src/tirx/script/ir_builder/utils.h | 6 +-
src/tirx/script/printer/expr.cc | 15 ++-
src/tirx/script/printer/stmt.cc | 123 +++++++--------------
src/tirx/script/printer/utils.h | 1 -
.../relax/script/test_relax_script_printer.py | 30 +++--
.../relax/test_transform_error_enrichment.py | 7 +-
.../tirx/script/test_tirx_script_basic_usage.py | 8 ++
24 files changed, 320 insertions(+), 389 deletions(-)
diff --git a/include/tvm/ir/expr.h b/include/tvm/ir/expr.h
index 0c0891725f..7eb3755c96 100644
--- a/include/tvm/ir/expr.h
+++ b/include/tvm/ir/expr.h
@@ -1098,6 +1098,14 @@ TVM_DLL CallEffectKind SideEffect(const Expr& expr);
namespace ffi {
+template <>
+inline constexpr bool use_default_type_traits_v<TensorRegion> = false;
+
+template <>
+struct TypeTraits<TensorRegion> : public
ObjectRefWithFallbackTraitsBase<TensorRegion, TensorLoad> {
+ TVM_DLL static TensorRegion ConvertFallbackValue(TensorLoad value);
+};
+
template <>
inline constexpr bool use_default_type_traits_v<PrimVar> = false;
diff --git a/include/tvm/script/printer/doc.h b/include/tvm/script/printer/doc.h
index 4c08bfded7..a7a8ebf186 100644
--- a/include/tvm/script/printer/doc.h
+++ b/include/tvm/script/printer/doc.h
@@ -1352,58 +1352,6 @@ class DocStringDoc : public StmtDoc {
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(DocStringDoc, StmtDoc,
DocStringDocNode);
};
-/*!
- * \brief Doc that represents call to an TIRX operator
- *
- * \sa OpCallDoc
- */
-class OpCallDocNode : public StmtDocNode {
- public:
- /*! \brief The callee of this function call */
- ExprDoc callee{ffi::UnsafeInit()};
- /*! \brief The positional arguments */
- ffi::Array<Doc> args;
- /*! \brief The workspace of this op call */
- ffi::Optional<DictDoc> workspace{std::nullopt};
- /*! \brief The config of this op call */
- ffi::Optional<DictDoc> config{std::nullopt};
- /*! \brief The optional dispatch variant of this op call */
- ffi::Optional<ExprDoc> dispatch{std::nullopt};
-
- static void RegisterReflection() {
- namespace refl = tvm::ffi::reflection;
- refl::ObjectDef<OpCallDocNode>()
- .def_ro("callee", &OpCallDocNode::callee)
- .def_ro("args", &OpCallDocNode::args)
- .def_ro("workspace", &OpCallDocNode::workspace)
- .def_ro("config", &OpCallDocNode::config)
- .def_ro("dispatch", &OpCallDocNode::dispatch);
- }
-
- TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.printer.OpCallDoc", OpCallDocNode,
StmtDocNode);
-};
-
-/*!
- * \brief Reference type of OpCallDocNode.
- *
- * \sa OpCallDocNode
- */
-class OpCallDoc : public StmtDoc {
- public:
- /*!
- * \brief Constructor of OpCallDoc
- * \param callee The callee of this function call.
- * \param args The positional arguments.
- * \param workspace The workspace of this op call.
- * \param config The config of this op call.
- * \param dispatch The optional dispatch variant name of this op call.
- */
- TVM_DLL explicit OpCallDoc(ExprDoc callee, ffi::Array<Doc> args,
ffi::Optional<DictDoc> workspace,
- ffi::Optional<DictDoc> config,
- ffi::Optional<ExprDoc> dispatch = std::nullopt);
- TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(OpCallDoc, StmtDoc,
OpCallDocNode);
-};
-
/*!
* \brief A reference to a canonical script namespace.
*
diff --git a/python/tvm/backend/trn/tensor_instructions.py
b/python/tvm/backend/trn/tensor_instructions.py
index cfc66f5467..1d02167bae 100644
--- a/python/tvm/backend/trn/tensor_instructions.py
+++ b/python/tvm/backend/trn/tensor_instructions.py
@@ -16,7 +16,7 @@
# under the License.
"""Trainium tensor instructions with explicit NKI instruction identity."""
-from tvm.ir import LambdaExpr, PrimType, Tuple, const
+from tvm.ir import Expr, LambdaExpr, PrimType, Tuple, const
from tvm.tirx.tensor_instruction import Instruction, Operand, namespace
@@ -296,10 +296,10 @@ def make_namespace():
original = ns.affine_select
def affine_select(dst, true_value, false_value, pred, **kwargs):
- from tvm.tirx.tensor_instruction import _region
+ from tvm.tirx.tensor_instruction import _operand
- dst = _region(dst)
- if not isinstance(pred, LambdaExpr):
+ dst = _operand(dst, "region")
+ if not isinstance(pred, Expr):
pred = LambdaExpr([PrimType("int32")] * len(dst.region), pred)
return original(dst, true_value, false_value, pred, **kwargs)
diff --git a/python/tvm/ir/op.py b/python/tvm/ir/op.py
index e6a42a153d..4269b0e262 100644
--- a/python/tvm/ir/op.py
+++ b/python/tvm/ir/op.py
@@ -45,6 +45,8 @@ def _make_op_api(op, module_name):
):
# Bind named operands using the registered signature, without inventing
# defaults or interpreting arbitrary Python wrapper signatures.
+ is_region = op.get_attr("FRegionGetBodyParams") is not None
+ body_params = kwargs.pop("body_params", None) if is_region else None
operands = list(args)
for index, info in enumerate(op.args_info):
if index < len(args):
@@ -60,6 +62,19 @@ def _make_op_api(op, module_name):
if attrs is not None:
raise TypeError(f"{op.name}: cannot mix attrs with attribute
keywords")
attrs = make_node(op.attrs_type_key, **kwargs)
+ if is_region:
+ from tvm.script.ir_builder.stmt import region
+
+ if (
+ ty is not None
+ or ty_args is not None
+ or not isinstance(loc, Location)
+ or not loc.same_as(UNKNOWN_LOC)
+ ):
+ raise TypeError(
+ f"{op.name}: region builders do not accept ty, ty_args or
an explicit loc"
+ )
+ return region(op, operands, body_params=body_params, attrs=attrs)
return Call(op, operands, attrs=attrs, ty_args=ty_args, loc=loc, ty=ty)
call.__name__ = op.name.rsplit(".", 1)[-1]
@@ -80,6 +95,8 @@ def _init_op_api(namespace, target_module_name=None):
Generated functions accept registered positional/named operands plus
``attrs``, ``ty_args``, ``loc`` and ``ty``.
+ Region operations construct a RegionFrame and accept ``body_params``
instead
+ of Call result/type arguments.
Omitting the result invokes an available Op inference hook; without one,
Call retains a missing type.
Explicit results and inference errors are preserved; Call.validate checks
diff --git a/python/tvm/relax/op/base.py b/python/tvm/relax/op/base.py
index aa83837075..c3991930bb 100644
--- a/python/tvm/relax/op/base.py
+++ b/python/tvm/relax/op/base.py
@@ -27,7 +27,7 @@ from tvm.ir import Attrs, Call, Op
from tvm.ir.attrs import make_node as _make_attrs
from tvm.ir.location import UNKNOWN_LOC, Location
from tvm.ir.op import _make_op_api
-from tvm.runtime import Object, ObjectConvertible
+from tvm.runtime import DataTypeCode, Object, ObjectConvertible
from ..expr import Expr, ExternFunc, GlobalVar, Var
from ..type import Type
@@ -86,7 +86,7 @@ def _wrap_inline_arg_tuple(args) -> Expr:
"""
if isinstance(args, tuple | list):
- return tvm.relax.Tuple([convert_to_expr(a) for a in args])
+ return tvm.ir.Tuple([convert_to_expr(arg) for arg in args])
elif (
isinstance(args, Expr)
and not isinstance(args, tvm.relax.Tuple)
@@ -97,6 +97,26 @@ def _wrap_inline_arg_tuple(args) -> Expr:
return args
+def _wrap_tir_arg_tuple(func, args) -> Expr:
+ """Bind native scalar literals without changing explicit IR arguments."""
+ if not isinstance(args, tuple | list):
+ return _wrap_inline_arg_tuple(args)
+ signature = getattr(func, "ty", None)
+ arg_types = signature.arg_types if isinstance(signature, tvm.ir.FuncType)
else ()
+ values = []
+ for i, value in enumerate(args):
+ ty = arg_types[i] if i < len(arg_types) else None
+ if isinstance(ty, tvm.ir.PrimType) and ty.dtype.lanes == 1:
+ if type(value) in (int, bool) and ty.matches_code(
+ DataTypeCode.INT, DataTypeCode.UINT, DataTypeCode.BOOL
+ ):
+ value = tvm.tirx.IntImm(ty, value)
+ elif type(value) is float and ty.matches_code(DataTypeCode.FLOAT,
DataTypeCode.BFLOAT):
+ value = tvm.tirx.FloatImm(ty, value)
+ values.append(convert_to_expr(value))
+ return tvm.ir.Tuple(values)
+
+
_call_tir = _make_op_api(Op.get("relax.call_tir"), __name__)
@@ -111,7 +131,13 @@ def call_tir(
and locs are forwarded unchanged.
"""
return _call_tir(
- func, _wrap_inline_arg_tuple(args), ty_args=ty_args, attrs=attrs,
ty=ty, loc=loc, **kwargs
+ func,
+ _wrap_tir_arg_tuple(func, args),
+ ty_args=ty_args,
+ attrs=attrs,
+ ty=ty,
+ loc=loc,
+ **kwargs,
)
@@ -187,7 +213,7 @@ def call_tir_packed(gvar: GlobalVar, args: Expr, *,
ty=None, loc: Location = UNK
relax.call_tir_packed(copy, (source, destination))
"""
- args = _wrap_inline_arg_tuple(args)
+ args = _wrap_tir_arg_tuple(gvar, args)
return Call("relax.call_tir_packed", [gvar, args], ty=ty, loc=loc)
@@ -212,7 +238,13 @@ def call_tir_with_grad(
if attrs is None and kwargs.get("te_grad_kwargs") is None:
kwargs["te_grad_kwargs"] = {}
return _call_tir_with_grad(
- func, _wrap_inline_arg_tuple(args), ty_args=ty_args, attrs=attrs,
ty=ty, loc=loc, **kwargs
+ func,
+ _wrap_tir_arg_tuple(func, args),
+ ty_args=ty_args,
+ attrs=attrs,
+ ty=ty,
+ loc=loc,
+ **kwargs,
)
@@ -242,7 +274,13 @@ def call_tir_inplace(
if isinstance(kwargs.get("inplace_indices"), int):
kwargs["inplace_indices"] = [kwargs["inplace_indices"]]
return _call_tir_inplace(
- func, _wrap_inline_arg_tuple(args), ty_args=ty_args, attrs=attrs,
ty=ty, loc=loc, **kwargs
+ func,
+ _wrap_tir_arg_tuple(func, args),
+ ty_args=ty_args,
+ attrs=attrs,
+ ty=ty,
+ loc=loc,
+ **kwargs,
)
@@ -263,9 +301,8 @@ def call_dps_packed(
"""
if isinstance(func, str):
func = ExternFunc(func)
- return _call_dps_packed(
- func, _wrap_inline_arg_tuple(args), ty_args=ty_args, attrs=attrs,
ty=ty, loc=loc, **kwargs
- )
+ args = _wrap_inline_arg_tuple(args)
+ return _call_dps_packed(func, args, ty_args=ty_args, attrs=attrs, ty=ty,
loc=loc, **kwargs)
def call_py_func(
diff --git a/python/tvm/tirx/script/ir_builder/parser_protocol.py
b/python/tvm/tirx/script/ir_builder/parser_protocol.py
index 3c086a226c..b259e3e7a0 100644
--- a/python/tvm/tirx/script/ir_builder/parser_protocol.py
+++ b/python/tvm/tirx/script/ir_builder/parser_protocol.py
@@ -34,7 +34,6 @@ import tvm
# isort: on
from tvm import ir as _ir
from tvm import tirx as _tir
-from tvm.ir import StringImm as _StringImm
from tvm.ir import is_prim_expr
from tvm.script.ir_builder import base as _base
from tvm.script.ir_builder.base import AlreadyEmitted
@@ -755,18 +754,23 @@ def thread_binding(
)
-def device_entry(*, launch=None, kernel_attrs=None) -> frame.RegionFrame:
+def device_entry(
+ *launch_values, launch=None, kernel_attrs=None, attrs=None,
body_params=None
+) -> frame.RegionFrame:
"""Enter a device kernel with an independent CUDA launch configuration.
CUDA entries use ``LaunchConfig(grid=..., block=...)`` and optional
``KernelAttributes``. Configuration values are ordinary region operands, so
host expressions remain visible to substitution and free-variable analysis.
+ Canonical operands and attributes are also accepted for IR reconstruction.
Other backends may use the argument-free device entry.
"""
if launch is None:
if kernel_attrs is not None:
raise ValueError("device_entry kernel_attrs require a launch
configuration")
- return region("tirx.device_entry", [])
+ return region("tirx.device_entry", launch_values, attrs=attrs,
body_params=body_params)
+ if launch_values or attrs is not None:
+ raise ValueError("device_entry launch cannot be combined with
canonical operands or attrs")
from tvm.backend.cuda.launch._impl import pack_kernel_attrs, pack_launch
names, values = pack_launch(launch)
@@ -777,28 +781,35 @@ def device_entry(*, launch=None, kernel_attrs=None) ->
frame.RegionFrame:
"cuda.launch_fields": names,
"cuda.kernel_attrs": pack_kernel_attrs(kernel_attrs, launch),
},
+ body_params=body_params,
)
-def device_context(device_type: Expr, device_id: Expr) -> frame.RegionFrame:
+def device_context(
+ device_type: Expr, device_id: Expr, *, attrs=None, body_params=None
+) -> frame.RegionFrame:
"""Supply lexical device context for allocation and packed-call lowering.
This region does not change the active runtime device.
"""
- return region("tirx.device_context", [device_type, device_id])
+ return region(
+ "tirx.device_context", [device_type, device_id], attrs=attrs,
body_params=body_params
+ )
-def compute_scope(name: str) -> frame.RegionFrame:
+def compute_scope(name: str, *, attrs=None, body_params=None) ->
frame.RegionFrame:
"""Outline the body as a named CPU compute helper."""
- return region("tirx.compute_scope", [_StringImm(name)])
+ return region("tirx.compute_scope", [name], attrs=attrs,
body_params=body_params)
-def parallel_launch() -> frame.RegionFrame:
+def parallel_launch(*, attrs=None, body_params=None) -> frame.RegionFrame:
"""Launch a CPU worker team around parallel loops and team barriers."""
- return region("tirx.parallel_launch", [])
+ return region("tirx.parallel_launch", [], attrs=attrs,
body_params=body_params)
-def launch_thread(thread_tag: str, extent: Expr) -> frame.RegionFrame:
+def launch_thread(
+ thread_tag: str, extent: Expr, *, attrs=None, body_params=None
+) -> frame.RegionFrame:
"""Launch a hardware or virtual thread with a fresh lexical variable.
The extent determines the variable's scalar integer type. Tags starting
with
@@ -812,7 +823,7 @@ def launch_thread(thread_tag: str, extent: Expr) ->
frame.RegionFrame:
with T.launch_thread("threadIdx.x", 32) as tx:
T.evaluate(tx)
"""
- return region("tirx.launch_thread", [_StringImm(thread_tag), extent])
+ return region("tirx.launch_thread", [thread_tag, extent], attrs=attrs,
body_params=body_params)
# --------------------------------------
diff --git a/python/tvm/tirx/tensor_instruction.py
b/python/tvm/tirx/tensor_instruction.py
index 890e6cb96a..8b732a7a92 100644
--- a/python/tvm/tirx/tensor_instruction.py
+++ b/python/tvm/tirx/tensor_instruction.py
@@ -35,6 +35,7 @@ from tvm.ir import (
PointerType,
PrimType,
StringImm,
+ TensorLoad,
TensorRegion,
Tuple,
const,
@@ -86,11 +87,6 @@ class Instruction:
op.set_signature(args=[arg.name for arg in self.operands])
op.set_attrs_type_key("tirx.tensor." + self.schema)
tvm_ffi.get_global_func("tirx.ConfigureTensorInstruction")(op,
self.validate)
- register_op_attr(
- self.name,
- "__tvm_doc_translate_op_call__",
- tvm_ffi.get_global_func("script.printer.TensorCallDocTranslate")(),
- )
SPECS[self.name] = self
def construct(*args, **qualifiers):
@@ -279,12 +275,16 @@ def _is_absent(value):
def _operand(value, role="expr"):
from tvm.tirx import is_tensor_var
- if value is None:
+ if value is None or _is_absent(value) or (isinstance(value, tuple | list)
and not value):
return Tuple([])
if role == "region":
+ if isinstance(value, TensorLoad):
+ value = tvm_ffi.get_global_func("tirx.AsTensorRegion")(value)
return _region(value)
if role == "selectors":
- return Tuple([Tuple([_expr(cond), _region(candidate)]) for cond,
candidate in value])
+ return Tuple(
+ [Tuple([_expr(cond), _operand(candidate, "region")]) for cond,
candidate in value]
+ )
if role == "tensor":
return value
if is_tensor_var(value):
diff --git a/src/ir/side_effect.cc b/src/ir/side_effect.cc
index 6a3bd54418..7173f29543 100644
--- a/src/ir/side_effect.cc
+++ b/src/ir/side_effect.cc
@@ -25,21 +25,12 @@
#include <tvm/ir/op.h>
#include <algorithm>
-#include <unordered_set>
namespace tvm {
CallEffectKind SideEffect(const Expr& expr) {
static auto effects =
Op::GetAttrMap<TCallEffectKind>(tvm::op_attr::kCallEffectKind);
CallEffectKind kind = CallEffectKind::kPure;
- std::unordered_set<const ExprNode*> visited;
- auto visit_expr =
- [&](const ExprNode* node,
- ffi::StructuralVisitorObj* visitor) ->
ffi::Expected<ffi::Optional<ffi::VisitInterrupt>> {
- // Effects are context-independent; inspect each shared expression only
once.
- if (!visited.insert(node).second) return std::nullopt;
- return visitor->DefaultVisitExpected(node);
- };
ffi::StructuralVisit(
expr,
[&](const CallNode* node,
@@ -48,19 +39,18 @@ CallEffectKind SideEffect(const Expr& expr) {
effects.get(node->op,
static_cast<TCallEffectKind>(CallEffectKind::kOpaque)));
kind = std::max(kind, std::min(effect, CallEffectKind::kUpdateState));
if (kind == CallEffectKind::kUpdateState) return ffi::VisitInterrupt();
- return visit_expr(node, visitor);
+ return visitor->DefaultVisitExpected(node);
},
[&](const TensorLoadNode* node,
ffi::StructuralVisitorObj* visitor) ->
ffi::Expected<ffi::Optional<ffi::VisitInterrupt>> {
kind = std::max(kind, CallEffectKind::kReadState);
- return visit_expr(node, visitor);
+ return visitor->DefaultVisitExpected(node);
},
[](const TypeNode*,
ffi::StructuralVisitorObj*) ->
ffi::Expected<ffi::Optional<ffi::VisitInterrupt>> {
// Effects describe evaluated expressions, not expressions embedded in
types.
return std::nullopt;
- },
- visit_expr);
+ });
return kind;
}
diff --git a/src/relax/script/printer/call.cc b/src/relax/script/printer/call.cc
index eaaae8e204..5c0469eef3 100644
--- a/src/relax/script/printer/call.cc
+++ b/src/relax/script/printer/call.cc
@@ -32,12 +32,50 @@ namespace printer {
namespace details {
namespace {
+// These semantic wrappers convert bare Python scalar tuple fields to 64-bit
+// values. Use raw construction when ordinary tuple syntax would change the IR.
+bool InlineTupleNeedsRawCall(DocTranslatorObj* d, const Expr& expr) {
+ if (const auto* tuple = expr.as<TupleNode>()) {
+ for (const Expr& field : tuple->fields) {
+ if (InlineTupleNeedsRawCall(d, field)) return true;
+ }
+ } else if (const auto* imm = expr.as<IntImmNode>()) {
+ DLDataType dtype = imm->ty.as_or_throw<PrimType>()->dtype;
+ auto implicit =
+ ffi::StringToDLDataType(d->GetExtraConfig<ffi::String>("ir.int_dtype",
"int32"));
+ return dtype == implicit && dtype != ffi::StringToDLDataType("int64");
+ } else if (const auto* imm = expr.as<FloatImmNode>()) {
+ DLDataType dtype = imm->ty.as_or_throw<PrimType>()->dtype;
+ auto implicit =
+
ffi::StringToDLDataType(d->GetExtraConfig<ffi::String>("ir.float_dtype",
"void"));
+ return dtype == implicit && dtype != ffi::StringToDLDataType("float64");
+ }
+ return false;
+}
+
+ffi::Optional<ExprDoc> InlineTupleCallDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
+ const ffi::Object*) {
+ const auto* call =
+ ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
CallNode>(input);
+ if (call->args.size() > 1 && InlineTupleNeedsRawCall(d, call->args[1]))
return RawCall(d, call);
+ if (auto doc = StandardCallDocTranslate(d, call)) return doc;
+ return RawCall(d, call);
+}
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+ for (const char* name : {"relax.call_builtin_with_ctx", "relax.make_closure",
+ "relax.invoke_closure",
"relax.invoke_pure_closure"}) {
+
OpDef(name).set_attr<FDocTranslate>(tvm::script::printer::op_attr::kOpCallDocTranslate,
+
FDocTranslate::FromNative<&InlineTupleCallDocTranslate>());
+ }
+}
+
ffi::Optional<ExprDoc> CallDPSPackedDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
const ffi::Object*) {
const auto* call =
ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
CallNode>(input);
if (call->attrs.defined() || call->args.size() != 2 ||
!call->args[1].as<TupleNode>() ||
- call->ty_args.size() != 1) {
+ call->ty_args.size() != 1 || InlineTupleNeedsRawCall(d, call->args[1])) {
return RawCall(d, call);
}
ExprDoc callee = d->Translate(call->args[0]).value();
@@ -49,11 +87,9 @@ ffi::Optional<ExprDoc>
CallDPSPackedDocTranslate(DocTranslatorObj* d, ffi::AnyVi
} else if (call->args[0].as<StringImmNode>()) {
// A bare Python string now constructs ExternFunc, so retain this explicit
IR value.
callee = AnyValue(d, call->args[0]);
- } else {
- callee = MaterializeCallArgument(d, call->args[0], callee);
}
d->RecordOrigin(callee, call->args[0]);
- ExprDoc args = MaterializeCallArgument(d, call->args[1],
d->Translate(call->args[1]).value());
+ ExprDoc args = d->Translate(call->args[1]).value();
ffi::Array<ffi::String> keys = {"ty_args"};
ffi::Array<ExprDoc> values = {ListDoc({TypeValue(d, call->ty_args[0],
false)})};
bool omit_result = false;
diff --git a/src/script/printer/doc.cc b/src/script/printer/doc.cc
index abe00a5883..df34776bf4 100644
--- a/src/script/printer/doc.cc
+++ b/src/script/printer/doc.cc
@@ -293,17 +293,6 @@ DocStringDoc::DocStringDoc(ffi::String docs) {
this->data_ = std::move(n);
}
-OpCallDoc::OpCallDoc(ExprDoc callee, ffi::Array<Doc> args,
ffi::Optional<DictDoc> workspace,
- ffi::Optional<DictDoc> config, ffi::Optional<ExprDoc>
dispatch) {
- ffi::ObjectPtr<OpCallDocNode> n = ffi::make_object<OpCallDocNode>();
- n->callee = callee;
- n->args = args;
- n->workspace = workspace;
- n->config = config;
- n->dispatch = dispatch;
- this->data_ = std::move(n);
-}
-
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def(
@@ -513,15 +502,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
[](ffi::String docs) { return DocStringDoc(docs); });
}
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def("script.printer.OpCallDoc",
- [](ExprDoc callee, ffi::Array<Doc> args, DictDoc
workspace, DictDoc config,
- ffi::Optional<ExprDoc> dispatch) {
- return OpCallDoc(callee, args, workspace, config,
dispatch);
- });
-}
-
NamespaceDoc::NamespaceDoc(ffi::String canonical_name) {
auto node = ffi::make_object<NamespaceDocNode>();
node->canonical_name = std::move(canonical_name);
diff --git a/src/script/printer/doc_printer.cc
b/src/script/printer/doc_printer.cc
index 502fa7c2d7..dd131c0d41 100644
--- a/src/script/printer/doc_printer.cc
+++ b/src/script/printer/doc_printer.cc
@@ -253,11 +253,6 @@ class DocPrinter {
*/
virtual void PrintTypedDoc(const DocStringDoc& doc) = 0;
- /*!
- * \brief Virtual method to print a OpCallDoc
- */
- virtual void PrintTypedDoc(const OpCallDoc& doc) = 0;
-
/*!
* \brief Increase the indent level of any content to be
* printed after this call
@@ -660,7 +655,6 @@ void DocPrinter::PrintDoc(const Doc& doc) {
TVM_SCRIPT_PRINT_DISPATCH(ClassDoc);
TVM_SCRIPT_PRINT_DISPATCH(CommentDoc);
TVM_SCRIPT_PRINT_DISPATCH(DocStringDoc);
- TVM_SCRIPT_PRINT_DISPATCH(OpCallDoc);
#undef TVM_SCRIPT_PRINT_DISPATCH
table.Finalize();
return table;
@@ -940,7 +934,6 @@ class PythonDocPrinter : public DocPrinter {
void PrintTypedDoc(const ClassDoc& doc) final;
void PrintTypedDoc(const CommentDoc& doc) final;
void PrintTypedDoc(const DocStringDoc& doc) final;
- void PrintTypedDoc(const OpCallDoc& doc) final;
void PrintAssignment(const ExprDoc& lhs, const ffi::Optional<ExprDoc>& rhs,
const ffi::Optional<ExprDoc>& annotation);
@@ -1543,70 +1536,6 @@ void PythonDocPrinter::PrintTypedDoc(const DocStringDoc&
doc) {
}
}
-void PythonDocPrinter::PrintTypedDoc(const OpCallDoc& doc) {
- PrintDoc(doc->callee);
-
- output_ << "(";
-
- // Print positional args
- bool wrote_any = false;
- for (const Doc& arg : doc->args) {
- if (wrote_any) {
- output_ << ", ";
- }
- wrote_any = true;
- PrintDoc(arg);
- }
- // workspace first (if present and non-empty)
- if (doc->workspace.has_value() && !doc->workspace.value()->keys.empty()) {
- if (wrote_any) output_ << ", ";
- wrote_any = true;
- output_ << "workspace=";
- PrintDoc(doc->workspace.value());
- }
- // dispatch next (if present)
- if (doc->dispatch.has_value()) {
- if (wrote_any) output_ << ", ";
- wrote_any = true;
- output_ << "dispatch=";
- PrintDoc(doc->dispatch.value());
- }
- // Flatten config as keyword args: key=value
- if (doc->config.has_value() && !doc->config.value()->keys.empty()) {
- const auto* dict = doc->config.value().as<DictDocNode>();
- // Only flatten if all keys are literal strings; otherwise, fallback to
config={...}
- bool all_str_keys = true;
- for (const ExprDoc& k : dict->keys) {
- if (!k.as<LiteralDocNode>()) {
- all_str_keys = false;
- break;
- }
- const auto* lit = k.as<LiteralDocNode>();
- if (!lit->value.as<ffi::String>()) {
- all_str_keys = false;
- break;
- }
- }
- if (all_str_keys) {
- int n = dict->keys.size();
- for (int i = 0; i < n; ++i) {
- const auto* lit = dict->keys[i].as<LiteralDocNode>();
- std::string key = lit->value.as_or_throw<ffi::String>();
- if (wrote_any) output_ << ", ";
- wrote_any = true;
- output_ << key << "=";
- PrintDoc(dict->values[i]);
- }
- } else {
- if (wrote_any) output_ << ", ";
- wrote_any = true;
- output_ << "config=";
- PrintDoc(doc->config.value());
- }
- }
- output_ << ")";
-}
-
// Associate each recovered path with its nearest statement container.
Expression
// children keep their more precise paths while annotations use the enclosing
// statement's ordinary comment rendering. Transparent statement lists
introduce
diff --git a/src/script/printer/ir/call.cc b/src/script/printer/ir/call.cc
index f2ba0e3b35..fe331c7dbc 100644
--- a/src/script/printer/ir/call.cc
+++ b/src/script/printer/ir/call.cc
@@ -32,7 +32,6 @@ namespace tvm {
namespace script {
namespace printer {
namespace details {
-namespace {
// The name is published only for a callable implementing the shared Op
contract.
// Semantic wrappers register their own Op hooks.
@@ -55,7 +54,7 @@ ffi::Optional<ExprDoc>
StandardCallDocTranslate(DocTranslatorObj* d, const CallN
}
ffi::Array<ExprDoc> args;
for (const Expr& arg : call->args) {
- args.push_back(MaterializeCallArgument(d, arg, d->Translate(arg).value()));
+ args.push_back(d->Translate(arg).value());
}
ffi::Array<ffi::String> keys;
ffi::Array<ExprDoc> values;
@@ -65,6 +64,7 @@ ffi::Optional<ExprDoc>
StandardCallDocTranslate(DocTranslatorObj* d, const CallN
keys.push_back("ty_args");
values.push_back(ListDoc(types));
}
+ bool default_attrs = false;
if (call->attrs.defined()) {
std::vector<std::pair<ffi::String, ffi::Any>> fields;
ffi::reflection::ForEachFieldInfo(
@@ -80,9 +80,8 @@ ffi::Optional<ExprDoc>
StandardCallDocTranslate(DocTranslatorObj* d, const CallN
}
fields.emplace_back(ffi::String(field->name), std::move(value));
});
- // Keep an explicit schema when no attribute fields remain in the spelling,
- // including empty and entirely default-valued attrs.
- if (fields.empty()) return RawCall(d, call);
+ // Named constructors supply the registered attribute schema and defaults.
+ default_attrs = fields.empty();
std::sort(fields.begin(), fields.end(),
[](const auto& a, const auto& b) { return a.first < b.first; });
for (const auto& [key, value] : fields) {
@@ -104,12 +103,15 @@ ffi::Optional<ExprDoc>
StandardCallDocTranslate(DocTranslatorObj* d, const CallN
// Missing information, unavailable inference, or errors require the exact
stored type.
}
if (!omit_result) {
+ if (default_attrs) return RawCall(d, call);
keys.push_back("ty");
values.push_back(TypeValue(d, call->ty));
}
return NamedCallCallee(names[op])->Call(args, keys, values);
}
+namespace {
+
ffi::Optional<ExprDoc> CallDocTranslate(DocTranslatorObj* d, ffi::AnyView
input,
const ffi::Object* destination) {
const auto* call =
diff --git a/src/script/printer/ir/prim_expr.cc
b/src/script/printer/ir/prim_expr.cc
index 2836c7c516..f254ade234 100644
--- a/src/script/printer/ir/prim_expr.cc
+++ b/src/script/printer/ir/prim_expr.cc
@@ -16,11 +16,13 @@
* specific language governing permissions and limitations
* under the License.
*/
+#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/ir/prim/op.h>
#include <tvm/ir/prim/vector_expr.h>
#include <tvm/script/printer/doc_translator.h>
#include <cstring>
+#include <limits>
#include <optional>
#include "utils.h"
@@ -71,40 +73,79 @@ ffi::Array<Doc> TensorIndices(DocTranslatorObj* d, const
ffi::Array<PrimExpr>& i
return docs;
}
-ExprDoc TensorRegionValue(DocTranslatorObj* d, const TensorRegionNode* region,
- bool require_region) {
- ffi::Array<Doc> slices;
- for (const Range& range : region->region) {
- ExprDoc start = d->Translate(range->min).value();
- if (const auto* extent = range->extent.as<IntImmNode>();
- !require_region && extent && extent->value == 1) {
- slices.push_back(start);
- } else {
- ExprDoc end = [&]() -> ExprDoc {
- auto lower = range->min.as<IntImmNode>();
- auto length = range->extent.as<IntImmNode>();
- if (lower && length) {
- auto last = (lower->value + length->value).as<int64_t>();
- if (last) {
- return LiteralDoc::Int(*last, std::nullopt);
- }
- }
- return OperationDoc(OperationDocNode::Kind::kAdd,
- {start, d->Translate(range->extent).value()});
- }();
- slices.push_back(SliceDoc(start, end, std::nullopt));
- }
- }
- return d->Translate(region->source).value()[slices];
-}
-
namespace {
ffi::Optional<ExprDoc> TensorRegionDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
const ffi::Object*) {
const auto* region =
ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
TensorRegionNode>(input);
- return TensorRegionValue(d, region, false);
+ bool indexable = region->ty.as<TensorRegionTypeNode>() &&
!region->region.empty();
+ using Slice =
+ ffi::Tuple<ffi::Optional<PrimExpr>, ffi::Optional<PrimExpr>,
ffi::Optional<PrimExpr>>;
+ ffi::Array<ffi::Variant<Slice, PrimExpr>> indices;
+ ffi::Array<Doc> slices;
+ bool has_slice = false;
+ if (indexable) {
+ for (size_t i = 0; i < region->region.size(); ++i) {
+ const Range& range = region->region[i];
+ if (range->min.ty()->dtype.lanes != 1 || range->extent.ty()->dtype.lanes
!= 1) {
+ indexable = false;
+ break;
+ }
+ ExprDoc start = d->Translate(range->min).value();
+ const auto* extent = range->extent.as<IntImmNode>();
+ bool point =
+ extent && extent->value == 1 && ffi::StructuralEqual()(extent->ty,
range->min.ty());
+ if (point && (has_slice || i + 1 < region->region.size())) {
+ slices.push_back(start);
+ indices.push_back(range->min);
+ } else {
+ try {
+ PrimExpr stop = range->min + range->extent;
+ // Scalar constructors accept signed 64-bit Python literals. A
computed
+ // endpoint outside that range cannot reconstruct through indexing.
+ if (const auto* imm = stop.as<IntImmNode>();
+ imm && (imm->value > std::numeric_limits<int64_t>::max() ||
+ imm->value < std::numeric_limits<int64_t>::min())) {
+ indexable = false;
+ break;
+ }
+ slices.push_back(SliceDoc(start, d->Translate(stop).value(),
std::nullopt));
+ indices.push_back(Slice(range->min, stop, std::nullopt));
+ has_slice = true;
+ } catch (const ffi::Error&) {
+ indexable = false;
+ break;
+ }
+ }
+ }
+ }
+ if (indexable) {
+ try {
+ // Ask the existing type-directed subscription owner whether this syntax
+ // reconstructs the exact region, without depending on a dialect type.
+ static const auto realize =
ffi::Function::GetGlobalRequired("ir.SubscriptExprRealize");
+ auto restored = realize(region->source, indices,
region->loc).cast<TensorRegion>();
+ if (ffi::StructuralEqual()(restored, ffi::GetRef<TensorRegion>(region)))
{
+ return d->Translate(region->source).value()[slices];
+ }
+ } catch (const ffi::Error&) {
+ // Unsupported subscription or a different expression kind needs
explicit construction.
+ }
+ }
+
+ ffi::Array<ExprDoc> ranges;
+ for (const Range& range : region->region) {
+ ranges.push_back(
+ NamespaceDoc("ir")
+ ->Attr("Range")
+ ->Attr("from_min_extent")
+ ->Call({d->Translate(range->min).value(),
d->Translate(range->extent).value()}));
+ }
+ return NamespaceDoc("ir")
+ ->Attr("TensorRegion")
+ ->Call({d->Translate(region->source).value(), ListDoc(ranges)}, {"ty"},
+ {TypeValue(d, region->ty, false)});
}
TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/script/printer/ir/utils.cc b/src/script/printer/ir/utils.cc
index 126b01eaa9..1469dac7cc 100644
--- a/src/script/printer/ir/utils.cc
+++ b/src/script/printer/ir/utils.cc
@@ -160,33 +160,6 @@ ExprDoc CallAttrsValue(DocTranslatorObj* d, const Attrs&
attrs) {
} // namespace
-// Translate operands as IR values when a dialect offers compact expression
sugar.
-ExprDoc MaterializeCallArgument(DocTranslatorObj* d, const Expr& arg, ExprDoc
doc) {
- if (const auto* tuple = arg.as<TupleNode>()) {
- ffi::Array<ExprDoc> fields;
- for (const Expr& field : tuple->fields) {
- fields.push_back(MaterializeCallArgument(d, field,
d->Translate(field).value()));
- }
- doc = NamespaceDoc("ir")->Attr("Tuple")->Call({ListDoc(fields)});
- }
- if (const auto* region = arg.as<TensorRegionNode>()) {
- ffi::Array<ExprDoc> ranges;
- for (const Range& range : region->region) {
- ranges.push_back(
- NamespaceDoc("ir")
- ->Attr("Range")
- ->Attr("from_min_extent")
- ->Call({d->Translate(range->min).value(),
d->Translate(range->extent).value()}));
- }
- doc = NamespaceDoc("ir")
- ->Attr("TensorRegion")
- ->Call({d->Translate(region->source).value(), ListDoc(ranges)},
{"ty"},
- {TypeValue(d, region->ty, false)});
- }
- d->RecordOrigin(doc, arg);
- return doc;
-}
-
// The explicit fallback retains every field, including typed attribute
objects.
ExprDoc RawCall(DocTranslatorObj* d, const CallNode* call,
ffi::Optional<ffi::Array<ExprDoc>> translated_args) {
@@ -197,9 +170,6 @@ ExprDoc RawCall(DocTranslatorObj* d, const CallNode* call,
} else {
for (const Expr& arg : call->args)
args.push_back(d->Translate(arg).value());
}
- for (size_t i = 0; i < args.size(); ++i) {
- args.Set(i, MaterializeCallArgument(d, call->args[i], args[i]));
- }
ExprDoc callee = op.has_value() ?
LiteralDoc::Str(op.value()->name, std::nullopt)
: call->op.as<StringImmNode>() ? AnyValue(d, call->op)
:
d->Translate(call->op).value();
@@ -221,24 +191,19 @@ ExprDoc RawCall(DocTranslatorObj* d, const CallNode* call,
}
// The query aliases the active frame; candidate classification must own a
copy.
-ffi::Dict<Var, IdDoc> CopyImplicitDefs(DocTranslatorObj* d) {
- ffi::Dict<Var, IdDoc> candidates;
+ffi::Map<Var, IdDoc> CopyImplicitDefs(DocTranslatorObj* d) {
+ ffi::Map<Var, IdDoc> candidates;
for (const auto& [var, id] : d->GetImplicitDefs()) candidates.Set(var, id);
return candidates;
}
-void FinalizeFunctionDefinitions(DocTranslatorObj* d, const ffi::Dict<Var,
IdDoc>& signature,
+void FinalizeFunctionDefinitions(DocTranslatorObj* d, const ffi::Map<Var,
IdDoc>& signature,
const FunctionDoc& function) {
auto module = d->GetOrCreateExtraState<ffi::Optional<IRModule>>("ir.module");
auto pending = d->GetImplicitDefs();
- std::vector<Var> parameters;
if (!module.has_value()) {
- for (const auto& [var, id] : signature) {
- if (pending.count(var) && var->ty.as<PrimTypeNode>())
parameters.push_back(var);
- }
- std::sort(parameters.begin(), parameters.end(),
- [&](const Var& a, const Var& b) { return signature[a]->name <
signature[b]->name; });
- for (const Var& var : parameters) {
+ for (const auto& [var, signature_id] : signature) {
+ if (!pending.count(var) || !var->ty.as<PrimTypeNode>()) continue;
IdDoc id = VarDoc(d, var);
if (var->ty.as_or_throw<PrimType>()->dtype == (DLDataType{kDLInt, 64,
1})) {
function->type_params.push_back(id);
diff --git a/src/script/printer/ir/utils.h b/src/script/printer/ir/utils.h
index 24a72e9e9f..16c69b4151 100644
--- a/src/script/printer/ir/utils.h
+++ b/src/script/printer/ir/utils.h
@@ -37,7 +37,6 @@ namespace details {
ffi::Array<StmtDoc> Body(const Stmt& stmt, DocTranslatorObj* d);
ffi::Array<Doc> TensorIndices(DocTranslatorObj* d, const ffi::Array<PrimExpr>&
indices,
bool store = false);
-ExprDoc TensorRegionValue(DocTranslatorObj* d, const TensorRegionNode* region,
bool require_region);
ExprDoc AddMetadata(DocTranslatorObj* d, ffi::Any value);
IdDoc VarDoc(DocTranslatorObj* d, const Var& var, bool explicit_def = true);
@@ -45,12 +44,12 @@ ExprDoc GlobalReference(DocTranslatorObj* d, const
ffi::String& name);
ExprDoc NamedCallCallee(const ffi::String& canonical_name);
ExprDoc TypeValue(DocTranslatorObj* d, const Type& type, bool dtype_literal =
true);
-ExprDoc MaterializeCallArgument(DocTranslatorObj* d, const Expr& arg, ExprDoc
doc);
+ffi::Optional<ExprDoc> StandardCallDocTranslate(DocTranslatorObj* d, const
CallNode* call);
ExprDoc RawCall(DocTranslatorObj* d, const CallNode* call,
ffi::Optional<ffi::Array<ExprDoc>> translated_args =
std::nullopt);
ExprDoc AnyValue(DocTranslatorObj* d, ffi::AnyView value);
-ffi::Dict<Var, IdDoc> CopyImplicitDefs(DocTranslatorObj* d);
-void FinalizeFunctionDefinitions(DocTranslatorObj* d, const ffi::Dict<Var,
IdDoc>& signature,
+ffi::Map<Var, IdDoc> CopyImplicitDefs(DocTranslatorObj* d);
+void FinalizeFunctionDefinitions(DocTranslatorObj* d, const ffi::Map<Var,
IdDoc>& signature,
const FunctionDoc& function);
class VarScope {
diff --git a/src/tirx/ir/buffer.cc b/src/tirx/ir/buffer.cc
index 325bb01dd5..9d3d58936c 100644
--- a/src/tirx/ir/buffer.cc
+++ b/src/tirx/ir/buffer.cc
@@ -39,6 +39,15 @@
#include "../../sym/pattern_match.h"
namespace tvm {
+
+TensorRegion ffi::TypeTraits<TensorRegion>::ConvertFallbackValue(TensorLoad
value) {
+ ffi::Array<Range> ranges;
+ for (const PrimExpr& index : value->indices) {
+ ranges.push_back(Range::FromMinExtent(index, IntImm(index.ty(), 1)));
+ }
+ return tirx::BufferRegion(value->source.as_or_throw<tirx::TensorVar>(),
ranges, value->loc);
+}
+
namespace tirx {
using namespace tvm::prim;
@@ -170,6 +179,7 @@ TensorRegion BufferRegion(TensorVar buffer,
ffi::Array<Range> region, Location l
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
+ refl::GlobalDef().def("tirx.AsTensorRegion", [](TensorRegion region) {
return region; });
refl::GlobalDef().def("tirx.BufferRegion", [](TensorVar buffer,
ffi::Array<Range> region) {
return BufferRegion(buffer, region);
});
diff --git a/src/tirx/op/region.cc b/src/tirx/op/region.cc
index 72bda4a532..cbe8be5bc0 100644
--- a/src/tirx/op/region.cc
+++ b/src/tirx/op/region.cc
@@ -25,6 +25,7 @@
#include <tvm/ffi/function.h>
#include <tvm/ir/prim/op.h>
#include <tvm/ir/stmt.h>
+#include <tvm/script/printer/doc_translator.h>
#include <tvm/tirx/op/region.h>
#include <tvm/tirx/op_attr_types.h>
@@ -55,6 +56,8 @@ const Op& launch_thread_op() {
TVM_FFI_STATIC_INIT_BLOCK() {
OpDef("tirx.launch_thread", "Bind a thread index within a body with a launch
extent.")
+ .set_attr<ffi::String>(tvm::script::printer::op_attr::kScriptPrinterName,
+ "tirx.launch_thread")
.signature(sig::arg<StringImm>("tag"), sig::arg<IntExpr>("extent"))
.set_attr<FRegionGetBodyParams>(tvm::op_attr::kRegionGetBodyParams,
FRegionGetBodyParams::FromNative<&LaunchThreadBodyParams>())
@@ -83,6 +86,7 @@ void ValidateDeviceEntry(const RegionStmtNode* region) {
TVM_FFI_STATIC_INIT_BLOCK() {
OpDef("tirx.device_entry", "Enter a device kernel with independent launch
configuration.")
+
.set_attr<ffi::String>(tvm::script::printer::op_attr::kScriptPrinterName,
"tirx.device_entry")
.signature(sig::var_args("launch_values"),
sig::call_attrs<DictAttrsNode>())
.set_attr<FRegionGetBodyParams>(tvm::op_attr::kRegionGetBodyParams,
FRegionGetBodyParams::FromNative<&RegionNoBodyParams>())
@@ -98,6 +102,8 @@ const Op& device_context_op() {
TVM_FFI_STATIC_INIT_BLOCK() {
OpDef("tirx.device_context", "Supply the device type and ID within a
region.")
+ .set_attr<ffi::String>(tvm::script::printer::op_attr::kScriptPrinterName,
+ "tirx.device_context")
.signature(sig::arg<IntExpr>("device_type"),
sig::arg<IntExpr>("device_id"))
.set_attr<FRegionGetBodyParams>(tvm::op_attr::kRegionGetBodyParams,
FRegionGetBodyParams::FromNative<&RegionNoBodyParams>())
@@ -113,6 +119,8 @@ const Op& compute_scope_op() {
TVM_FFI_STATIC_INIT_BLOCK() {
OpDef("tirx.compute_scope", "Outline a named CPU compute region.")
+ .set_attr<ffi::String>(tvm::script::printer::op_attr::kScriptPrinterName,
+ "tirx.compute_scope")
.signature(sig::arg<StringImm>("name"))
.set_attr<FRegionGetBodyParams>(tvm::op_attr::kRegionGetBodyParams,
FRegionGetBodyParams::FromNative<&RegionNoBodyParams>())
@@ -128,6 +136,8 @@ const Op& parallel_launch_op() {
TVM_FFI_STATIC_INIT_BLOCK() {
OpDef("tirx.parallel_launch", "Launch a CPU worker team around a region.")
+ .set_attr<ffi::String>(tvm::script::printer::op_attr::kScriptPrinterName,
+ "tirx.parallel_launch")
.signature()
.set_attr<FRegionGetBodyParams>(tvm::op_attr::kRegionGetBodyParams,
FRegionGetBodyParams::FromNative<&RegionNoBodyParams>())
diff --git a/src/tirx/script/ir_builder/utils.h
b/src/tirx/script/ir_builder/utils.h
index 00b451360e..06286ccbcf 100644
--- a/src/tirx/script/ir_builder/utils.h
+++ b/src/tirx/script/ir_builder/utils.h
@@ -64,11 +64,7 @@ inline FunctionFrame FindFunctionFrame(const ffi::String&
method) {
* \return The converted TensorRegion.
*/
inline tvm::TensorRegion TensorRegionFromLoad(tvm::TensorLoad buffer_load) {
- ffi::Array<Range> ranges;
- for (const PrimExpr& index : buffer_load->indices) {
- ranges.push_back(Range::FromMinExtent(index, IntImm(index.ty(), 1)));
- }
- return
tvm::tirx::BufferRegion(buffer_load->source.as_or_throw<tvm::tirx::TensorVar>(),
ranges);
+ return ffi::TypeTraits<tvm::TensorRegion>::ConvertFallbackValue(buffer_load);
}
} // namespace tirx
diff --git a/src/tirx/script/printer/expr.cc b/src/tirx/script/printer/expr.cc
index 4da38c5c06..0338e8ed3d 100644
--- a/src/tirx/script/printer/expr.cc
+++ b/src/tirx/script/printer/expr.cc
@@ -94,13 +94,6 @@ TVM_FFI_STATIC_INIT_BLOCK() {
FDocTranslate::FromNative<&VarDocTranslate>());
}
-bool CanTranslateExplicitResultCall(const CallNode* call) {
- return !call->attrs.defined() && call->ty_args.empty() &&
call->ty.as<PrimType>() &&
- std::all_of(call->args.begin(), call->args.end(), [](const Expr& arg)
{
- return !arg->ty.as<MissingType>().has_value() &&
!arg.as<TensorRegionNode>();
- });
-}
-
namespace {
ffi::Optional<ExprDoc> IndexMapDocTranslate(DocTranslatorObj* d, ffi::AnyView
input,
@@ -139,6 +132,12 @@ ffi::Optional<ExprDoc>
LLVMIntrinsicDocTranslate(DocTranslatorObj* d, ffi::AnyVi
!call->ty.as<PrimType>()) {
return RawCall(d, call);
}
+ // The LLVM convenience builders reject direct region operands. Keep their
+ // raw Call representation while ordinary tuple operands recurse normally.
+ if (std::any_of(call->args.begin(), call->args.end(),
+ [](const Expr& arg) { return arg.as<TensorRegionNode>() !=
nullptr; })) {
+ return RawCall(d, call);
+ }
const auto* id = call->args[0].as<IntImmNode>();
// The named constructor uses an int32 intrinsic identifier. Other stored
// representations retain their exact operand type through the full Call.
@@ -159,7 +158,7 @@ ffi::Optional<ExprDoc>
LLVMIntrinsicDocTranslate(DocTranslatorObj* d, ffi::AnyVi
d->RecordOrigin(name_doc, call->args[0]);
ffi::Array<ExprDoc> args = {name_doc};
for (size_t i = 1; i < call->args.size(); ++i) {
- args.push_back(MaterializeCallArgument(d, call->args[i],
d->Translate(call->args[i]).value()));
+ args.push_back(d->Translate(call->args[i]).value());
}
return NamespaceDoc("tirx")
->Attr(call->op.same_as(tirx::call_llvm_intrin_op()) ? "call_llvm_intrin"
diff --git a/src/tirx/script/printer/stmt.cc b/src/tirx/script/printer/stmt.cc
index f7330c1fef..cac5c52da2 100644
--- a/src/tirx/script/printer/stmt.cc
+++ b/src/tirx/script/printer/stmt.cc
@@ -42,52 +42,6 @@ namespace details {
namespace {
-ExprDoc TensorOperandDoc(DocTranslatorObj* d, const Expr& value) {
- if (const auto* region = value.as<TensorRegionNode>()) {
- ExprDoc doc = TensorRegionValue(d, region, true);
- d->RecordOrigin(doc, value);
- return doc;
- }
- if (const auto* tuple = value.as<TupleNode>()) {
- if (tuple->fields.empty()) return LiteralDoc::None(std::nullopt);
- ffi::Array<ExprDoc> fields;
- for (const auto& field : tuple->fields)
fields.push_back(TensorOperandDoc(d, field));
- return TupleDoc(fields);
- }
- return AnyValue(d, value);
-}
-
-ffi::Optional<ExprDoc> TensorCallDocTranslate(DocTranslatorObj* d,
ffi::AnyView input,
- const ffi::Object* destination) {
- const auto* call =
- ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const
CallNode>(input);
- const auto op = call->op.as_or_throw<Op>();
- op.Validate(call);
- ffi::Array<ExprDoc> args;
- for (const auto& arg : call->args) args.push_back(TensorOperandDoc(d, arg));
- ffi::Array<ffi::String> keys;
- ffi::Array<ExprDoc> values;
- ffi::reflection::ForEachFieldInfo(
- TVMFFIGetTypeInfo(call->attrs->type_index()), [&](const TVMFFIFieldInfo*
field) {
- ffi::Any value = ffi::reflection::FieldGetter(field)(call->attrs);
- if ((field->flags & kTVMFFIFieldFlagBitMaskHasDefault) &&
- ffi::StructuralEqual()(
- value,
ffi::AnyView::CopyFromTVMFFIAny(field->default_value_or_factory)))
- return;
- keys.push_back(ffi::String(field->name));
- values.push_back(AnyValue(d, value));
- });
- static const auto& names =
-
Op::GetAttrMap<TScriptPrinterName>(tvm::script::printer::op_attr::kScriptPrinterName);
- return NamedCallCallee(names[op])->Call(args, keys, values);
-}
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- ffi::reflection::GlobalDef().def("script.printer.TensorCallDocTranslate",
[]() {
- return FDocTranslate::FromNative<&TensorCallDocTranslate>();
- });
-}
-
ffi::Optional<ExprDoc> EvaluateDocTranslate(DocTranslatorObj* d, ffi::AnyView
input,
const ffi::Object* destination) {
const auto* stmt =
@@ -172,48 +126,45 @@ ffi::Optional<ExprDoc>
RegionStmtDocTranslate(DocTranslatorObj* d, ffi::AnyView
// Inputs, attributes, and parameter types are evaluated before the body
// parameters enter scope. Explicit Var constructors preserve their exact
types.
- ExprDoc rhs(ffi::UnsafeInit{});
- if (stmt->op.same_as(tirx::device_entry_op()) && stmt->attrs->dict.empty()) {
- rhs = NamespaceDoc("tirx")->Attr("device_entry")->Call({});
- } else if (stmt->op.same_as(tirx::launch_thread_op())) {
- rhs = NamespaceDoc("tirx")
- ->Attr("launch_thread")
-
->Call({LiteralDoc::Str(stmt->args[0].as_or_throw<StringImm>()->value,
std::nullopt),
-
d->Translate(stmt->args[1].as_or_throw<PrimExpr>()).value()});
- } else if (stmt->op.same_as(tirx::device_context_op())) {
- rhs = NamespaceDoc("tirx")
- ->Attr("device_context")
- ->Call({d->Translate(stmt->args[0]).value(),
d->Translate(stmt->args[1]).value()});
- } else if (stmt->op.same_as(tirx::compute_scope_op())) {
- rhs =
- NamespaceDoc("tirx")
- ->Attr("compute_scope")
-
->Call({LiteralDoc::Str(stmt->args[0].as_or_throw<StringImm>()->value,
std::nullopt)});
- } else if (stmt->op.same_as(tirx::parallel_launch_op())) {
- rhs = NamespaceDoc("tirx")->Attr("parallel_launch")->Call({});
- } else {
- ffi::Array<ExprDoc> args;
- for (const Expr& arg : stmt->args)
args.push_back(d->Translate(arg).value());
- for (size_t i = 0; i < args.size(); ++i) {
- args.Set(i, MaterializeCallArgument(d, stmt->args[i], args[i]));
- }
- ffi::Array<ffi::String> keys;
- ffi::Array<ExprDoc> values;
- if (!stmt->body_params.empty()) {
- ffi::Array<ExprDoc> params;
- for (const Var& param : stmt->body_params) {
- ExprDoc value = NamespaceDoc("tirx")->Attr("Var")->Call(
- {LiteralDoc::Str(param->name, std::nullopt), TypeValue(d,
param->ty)});
- d->RecordOrigin(value, param);
- params.push_back(value);
- }
- keys.push_back("body_params");
- values.push_back(ListDoc(params));
+ ffi::Array<ExprDoc> args;
+ for (const Expr& arg : stmt->args) args.push_back(d->Translate(arg).value());
+ ffi::Array<ffi::String> keys;
+ ffi::Array<ExprDoc> values;
+ bool infer_params = false;
+ try {
+ auto inferred = GetRegionBodyParams(stmt->op, stmt->args, stmt->attrs);
+ infer_params = inferred.size() == stmt->body_params.size();
+ for (size_t i = 0; infer_params && i < inferred.size(); ++i) {
+ infer_params = ffi::StructuralEqual()(inferred[i]->ty,
stmt->body_params[i]->ty);
}
- if (!stmt->attrs->dict.empty()) {
- keys.push_back("attrs");
- values.push_back(AnyValue(d, stmt->attrs));
+ } catch (const ffi::Error&) {
+ // Explicit parameters preserve regions whose inference is unavailable.
+ }
+ if (!infer_params) {
+ ffi::Array<ExprDoc> params;
+ for (const Var& param : stmt->body_params) {
+ ExprDoc value = NamespaceDoc("tirx")->Attr("Var")->Call(
+ {LiteralDoc::Str("", std::nullopt), TypeValue(d, param->ty)});
+ d->RecordOrigin(value, param);
+ params.push_back(value);
}
+ keys.push_back("body_params");
+ values.push_back(ListDoc(params));
+ }
+ if (!stmt->attrs->dict.empty()) {
+ keys.push_back("attrs");
+ values.push_back(AnyValue(d, stmt->attrs));
+ }
+ ExprDoc rhs(ffi::UnsafeInit{});
+ ffi::Optional<ffi::String> name;
+ if (Op::HasAttrMap(tvm::script::printer::op_attr::kScriptPrinterName)) {
+ auto names =
+
Op::GetAttrMap<TScriptPrinterName>(tvm::script::printer::op_attr::kScriptPrinterName);
+ if (names.count(stmt->op)) name = names[stmt->op];
+ }
+ if (name.has_value() && !name.value().empty()) {
+ rhs = NamedCallCallee(name.value())->Call(args, keys, values);
+ } else {
rhs = NamespaceDoc("tirx")->Attr("region")->Call(
{LiteralDoc::Str(stmt->op->name, std::nullopt), ListDoc(args)}, keys,
values);
}
diff --git a/src/tirx/script/printer/utils.h b/src/tirx/script/printer/utils.h
index 97c6059923..d6011b497f 100644
--- a/src/tirx/script/printer/utils.h
+++ b/src/tirx/script/printer/utils.h
@@ -34,7 +34,6 @@ namespace details {
void PrintFunction(DocTranslatorObj* d, const tirx::FunctionNode* func,
ExprDoc decorator,
const ffi::String& dialect_attr);
-bool CanTranslateExplicitResultCall(const CallNode* call);
bool IsScalarBuffer(DocTranslatorObj* d, const Expr& source);
ffi::Optional<ExprDoc> VarDocTranslate(DocTranslatorObj* d, ffi::AnyView input,
const ffi::Object* destination);
diff --git a/tests/python/relax/script/test_relax_script_printer.py
b/tests/python/relax/script/test_relax_script_printer.py
index 8cb18f2253..dbf8a9f15f 100644
--- a/tests/python/relax/script/test_relax_script_printer.py
+++ b/tests/python/relax/script/test_relax_script_printer.py
@@ -166,7 +166,7 @@ from __future__ import annotations
@R.function
def foo(x: R.DTensor((128, 128), "float32", R.device_mesh((2, 2), I.Range(0,
4)), "S[0], R")) -> R.DTensor((128, 128), "float32", R.device_mesh((2, 2),
I.Range(0, 4)), "S[0], R"):
- gv0 = R.call_tir(Module.tir_func, I.Tuple([x]), ty_args=[R.DTensor((128,
128), "float32", R.device_mesh((2, 2), I.Range(0, 4)), "S[0], R")])
+ gv0 = R.call_tir(Module.tir_func, (x,), ty_args=[R.DTensor((128, 128),
"float32", R.device_mesh((2, 2), I.Range(0, 4)), "S[0], R")])
return gv0
""",
)
@@ -198,13 +198,13 @@ class Module:
with Ts.sblock(""):
v = Ts.axis.spatial(T.int64(128), i, dtype="int64")
v_1 = Ts.axis.spatial(T.int64(128), j, dtype="int64")
- Ts.reads(x[v, v_1])
- Ts.writes(y[v, v_1])
+ Ts.reads(x[v, v_1:v_1 + T.int64(1)])
+ Ts.writes(y[v, v_1:v_1 + T.int64(1)])
y[v, v_1] = x[v, v_1] + T.float32(1.0)
@R.function
def foo(x: R.DTensor((128, 128), "float32", "mesh[0]", "S[0], R")) ->
R.DTensor((128, 128), "float32", "mesh[0]", "S[0], R"):
- gv0 = R.call_tir(Module.tir_func, I.Tuple([x]),
ty_args=[R.DTensor((128, 128), "float32", "mesh[0]", "S[0], R")])
+ gv0 = R.call_tir(Module.tir_func, (x,), ty_args=[R.DTensor((128, 128),
"float32", "mesh[0]", "S[0], R")])
return gv0
""",
)
@@ -720,7 +720,7 @@ def test_call():
"""
x = I.dynamic("x", dtype="int64")
a: R.Tensor((1, x, 3), dtype="float32")
-I.Call("relax.call_tir", [Module.tir_func, I.Tuple([a, x])],
ty_args=[R.Tensor((1, x, 3), dtype="float32")], ty=R.Tensor((1, x, 3),
dtype="float32"))
+I.Call("relax.call_tir", [Module.tir_func, (a, x)], ty_args=[R.Tensor((1, x,
3), dtype="float32")], ty=R.Tensor((1, x, 3), dtype="float32"))
""",
)
_assert_print_lines(
@@ -728,7 +728,7 @@ I.Call("relax.call_tir", [Module.tir_func, I.Tuple([a,
x])], ty_args=[R.Tensor((
"""
x = I.dynamic("x", dtype="int64")
a: R.Tensor((1, x, 3), dtype="float32")
-R.call_dps_packed("my_dps_func", I.Tuple([a]), ty_args=[R.Tensor((1, x, 3),
dtype="float32")])
+R.call_dps_packed("my_dps_func", (a,), ty_args=[R.Tensor((1, x, 3),
dtype="float32")])
""",
)
@@ -749,7 +749,7 @@ def test_call_tir_with_grad():
"""
v0: R.Tensor((54, 96), dtype="float32")
x = I.dynamic("x", dtype="int64")
-I.Call("relax.call_tir_with_grad", [Module.tir_func, I.Tuple([v0])],
attrs=I.make_node("relax.attrs.CallTIRWithGradAttrs", te_grad_kwargs={"k": 1.0,
"x": x}, te_grad_name="grad_func"), ty_args=[R.Tensor((54, 96),
dtype="float32")], ty=R.Tensor((54, 96), dtype="float32"))
+I.Call("relax.call_tir_with_grad", [Module.tir_func, (v0,)],
attrs=I.make_node("relax.attrs.CallTIRWithGradAttrs", te_grad_kwargs={"k": 1.0,
"x": x}, te_grad_name="grad_func"), ty_args=[R.Tensor((54, 96),
dtype="float32")], ty=R.Tensor((54, 96), dtype="float32"))
""",
)
@@ -776,7 +776,7 @@ def test_call_tir_inplace():
x: R.Tensor((32, 32), dtype="int32")
y: R.Tensor((32, 32), dtype="int32")
t = I.dynamic("t", dtype="int64")
-I.Call("relax.call_tir_inplace", [Module.tir_func, I.Tuple([x, y, t])],
attrs=I.make_node("relax.attrs.CallTIRInplaceAttrs", inplace_indices=[-1, 0]),
ty_args=[R.Tuple(R.Tensor((32, 32), dtype="int32"), R.Tensor((32, 32),
dtype="int32"))], ty=R.Tuple(R.Tensor((32, 32), dtype="int32"), R.Tensor((32,
32), dtype="int32")))
+I.Call("relax.call_tir_inplace", [Module.tir_func, (x, y, t)],
attrs=I.make_node("relax.attrs.CallTIRInplaceAttrs", inplace_indices=[-1, 0]),
ty_args=[R.Tuple(R.Tensor((32, 32), dtype="int32"), R.Tensor((32, 32),
dtype="int32"))], ty=R.Tuple(R.Tensor((32, 32), dtype="int32"), R.Tensor((32,
32), dtype="int32")))
""",
)
@@ -965,7 +965,7 @@ class Module:
@R.function
def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,),
dtype="float32"):
- gv0 = R.call_tir(Module.tir_func, I.Tuple([x]),
ty_args=[R.Tensor((128,), dtype="float32")])
+ gv0 = R.call_tir(Module.tir_func, (x,), ty_args=[R.Tensor((128,),
dtype="float32")])
return gv0
""",
)
@@ -990,7 +990,7 @@ class Module:
@R.function
def foo(x: R.Tensor((128,), dtype="float32")) -> R.Tensor((128,),
dtype="float32"):
- gv0 = R.call_tir(Module.tir_func, I.Tuple([x]),
ty_args=[R.Tensor((128,), dtype="float32")])
+ gv0 = R.call_tir(Module.tir_func, (x,), ty_args=[R.Tensor((128,),
dtype="float32")])
return gv0
""",
)
@@ -1163,14 +1163,13 @@ def test_reused_extern_func():
"""
from __future__ import annotations
-# from tvm.script import ir as I
# from tvm.script import relax as R
@R.function
def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128),
dtype="float32"):
extern_func: R.Callable(derive_func="tvm.relax.type.infer_by_ty_args") =
R.ExternFunc("extern_func")
- y = R.call_dps_packed(extern_func, I.Tuple([x]), ty_args=[R.Tensor((128,
128), dtype="float32")])
- z = R.call_dps_packed(extern_func, I.Tuple([y]), ty_args=[R.Tensor((128,
128), dtype="float32")])
+ y = R.call_dps_packed(extern_func, (x,), ty_args=[R.Tensor((128, 128),
dtype="float32")])
+ z = R.call_dps_packed(extern_func, (y,), ty_args=[R.Tensor((128, 128),
dtype="float32")])
return z
""",
)
@@ -1194,13 +1193,12 @@ def test_inline_extern_func():
"""
from __future__ import annotations
-# from tvm.script import ir as I
# from tvm.script import relax as R
@R.function
def func(x: R.Tensor((128, 128), dtype="float32")) -> R.Tensor((128, 128),
dtype="float32"):
- y = R.call_dps_packed("extern_func", I.Tuple([x]), ty_args=[R.Tensor((128,
128), dtype="float32")])
- z = R.call_dps_packed("extern_func", I.Tuple([y]), ty_args=[R.Tensor((128,
128), dtype="float32")])
+ y = R.call_dps_packed("extern_func", (x,), ty_args=[R.Tensor((128, 128),
dtype="float32")])
+ z = R.call_dps_packed("extern_func", (y,), ty_args=[R.Tensor((128, 128),
dtype="float32")])
return z
""",
)
diff --git a/tests/python/relax/test_transform_error_enrichment.py
b/tests/python/relax/test_transform_error_enrichment.py
index 53e5d39f41..97a954cfcd 100644
--- a/tests/python/relax/test_transform_error_enrichment.py
+++ b/tests/python/relax/test_transform_error_enrichment.py
@@ -59,15 +59,12 @@ def test_pass_error_renders_underlined_tvmscript():
"Location (TVMScript):\n"
"Access path: <root>.body.blocks[0].bindings[0].value\n\n"
"from __future__ import annotations\n\n"
- "# from tvm.script import ir as I\n"
"# from tvm.script import relax as R\n\n"
"@R.function\n"
'def main(x: R.Tensor((3, 4), dtype="float32"), '
'y: R.Tensor((5, 6), dtype="float32")) -> R.Tensor((3, 6),
dtype="float32"):\n'
- ' lv = I.Call("relax.matmul", [x, y],
attrs=I.make_node("relax.attrs.M'
- 'atmulAttrs", out_dtype=None), ty=I.MissingType())\n'
- "
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^"
- "^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n"
+ " lv = R.matmul(x, y)\n"
+ " ^^^^^^^^^^^^^^\n"
" return lv"
)
diff --git a/tests/python/tirx/script/test_tirx_script_basic_usage.py
b/tests/python/tirx/script/test_tirx_script_basic_usage.py
index d0802f9e69..0f9a3694d2 100644
--- a/tests/python/tirx/script/test_tirx_script_basic_usage.py
+++ b/tests/python/tirx/script/test_tirx_script_basic_usage.py
@@ -334,6 +334,14 @@ def func() -> None:
assert_structural_equal(func, from_source(func.script()))
+def test_tensor_region_roundtrip():
+ @T.function
+ def func(A: T.Tensor((4, 4), "float32")):
+ T.cuda.tile.sqrt(A[0, 1:2], A[1, 2:3])
+
+ assert_structural_equal(func, from_source(func.script()))
+
+
def test_roundtrip_unary_inplace():
"""Single-arg unary ops (in-place) should round-trip."""