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 a7b48136a3 [REFACTOR][S-TIR] Localize index datatype normalization
(#20688)
a7b48136a3 is described below
commit a7b48136a32e745fb7dd4af074eb10a7cab6010a
Author: Tianqi Chen <[email protected]>
AuthorDate: Mon Oct 12 19:18:41 2026 -0400
[REFACTOR][S-TIR] Localize index datatype normalization (#20688)
Give S-TIR its own private datatype legalization and index-context
helpers so TE function creation and tensorization no longer inherit the
private TIRx datatype rewriter. Preserve the existing S-TIR
normalization interface and behavior.
---
src/s_tir/ir/data_type_rewriter.cc | 532 +++++++++++++++++++++++++++++++++++++
src/s_tir/ir/data_type_rewriter.h | 116 +++++++-
2 files changed, 644 insertions(+), 4 deletions(-)
diff --git a/src/s_tir/ir/data_type_rewriter.cc
b/src/s_tir/ir/data_type_rewriter.cc
index 25bb577f0e..9e770981c7 100644
--- a/src/s_tir/ir/data_type_rewriter.cc
+++ b/src/s_tir/ir/data_type_rewriter.cc
@@ -19,15 +19,521 @@
#include "data_type_rewriter.h"
+#include <tvm/ffi/cast.h>
+#include <tvm/ir/op.h>
#include <tvm/ir/prim/op.h>
+#include <tvm/tirx/op/region.h>
+#include <algorithm>
#include <functional>
+#include <utility>
namespace tvm {
namespace s_tir {
using namespace tvm::tirx;
using namespace tvm::prim;
+UnchangedOr<Stmt> DataTypeLegalizer::Mutate_(const ForNode* op, InplaceMode
inplace_mode) {
+ auto result = StmtExprMutator::Mutate_(op, inplace_mode);
+ if (!result.IsUnchanged()) {
+ op = ffi::AnyView(result).as<ForNode>();
+ TVM_FFI_ICHECK(op != nullptr) << "Expected type to be ForNode, but get "
+ << ffi::AnyView(result).GetTypeKey();
+ if (!op->unique()) inplace_mode = InplaceMode::kDisallow;
+ }
+ PrimExpr e = Mutate(op->loop_var,
inplace_mode).ValueOrUnchanged(op->loop_var);
+ Var var = e.as_or_throw<Var>();
+ PrimType var_ty = var->ty.as_or_throw<PrimType>();
+ if (inplace_mode == InplaceMode::kAllow) {
+ auto* n = const_cast<ForNode*>(op);
+ n->min = prim::cast(var_ty, op->min);
+ n->extent = prim::cast(var_ty, op->extent);
+ if (op->step.has_value()) {
+ n->step = prim::cast(var_ty, *op->step);
+ }
+ return result;
+ }
+ auto n = ffi::make_object<ForNode>(*op);
+ n->min = prim::cast(var_ty, op->min);
+ n->extent = prim::cast(var_ty, op->extent);
+ if (op->step.has_value()) {
+ n->step = prim::cast(var_ty, *op->step);
+ }
+ return For(n);
+}
+
+UnchangedOr<Stmt> DataTypeLegalizer::Mutate_(const RegionStmtNode* op,
InplaceMode inplace_mode) {
+ Stmt result = StmtExprMutator::Mutate_(op,
inplace_mode).ValueOrUnchanged(ffi::GetRef<Stmt>(op));
+ const auto* region = result.as<RegionStmtNode>();
+ if (!region || !region->op.same_as(tirx::launch_thread_op())) return result;
+ PrimVar var = region->body_params[0].as_or_throw<PrimVar>();
+ PrimExpr extent = region->args[1].as_or_throw<PrimExpr>();
+ if (var.ty() == extent.ty()) return result;
+ RegionStmt updated = ffi::GetRef<RegionStmt>(region);
+ updated.CopyOnWrite()->args.Set(1, prim::cast(var.ty(), extent));
+ return updated;
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::LetNode* op,
+ InplaceMode inplace_mode) {
+ auto value_result = this->Mutate(op->value, inplace_mode);
+ bool value_unchanged = value_result.UnchangedOrSameAs(op->value);
+ PrimExpr value = std::move(value_result).ValueOrUnchanged(op->value);
+ Var var = Mutate(op->var).ValueOrUnchanged(op->var).as_or_throw<Var>();
+ if (value.ty() != var->ty.as_or_throw<PrimType>()) {
+ if (var.same_as(op->var)) {
+ var = op->var.CopyWithDType(value.ty());
+ VarRemapSet(op->var, var);
+ } else {
+ value = prim::cast(var->ty.as_or_throw<PrimType>(), value);
+ value_unchanged = false;
+ }
+ }
+ auto new_body_result = this->Mutate(op->body, inplace_mode);
+ bool new_body_unchanged = new_body_result.UnchangedOrSameAs(op->body);
+ PrimExpr new_body = std::move(new_body_result).ValueOrUnchanged(op->body);
+
+ if (value_unchanged && new_body_unchanged && var.same_as(op->var)) {
+ return ffi::Unchanged();
+ } else {
+ return prim::Let(var, value, new_body, op->loc);
+ }
+}
+
+UnchangedOr<Stmt> DataTypeLegalizer::Mutate_(const BindNode* op, InplaceMode
inplace_mode) {
+ if (!op->value.as<PrimExpr>()) {
+ return StmtExprMutator::Mutate_(op, inplace_mode);
+ }
+ auto value_result = this->Mutate(op->value, inplace_mode);
+ bool value_unchanged = value_result.UnchangedOrSameAs(op->value);
+ Expr value = std::move(value_result).ValueOrUnchanged(op->value);
+ Var var = op->var;
+
+ if (auto prim_value = value.as<PrimExpr>()) {
+ if (prim_value.value().ty() != op->var->ty.as_or_throw<PrimType>()) {
+ var = op->var.CopyWithDType(prim_value.value().ty());
+ VarRemapSet(op->var, var);
+ }
+ }
+
+ if (value_unchanged && var.same_as(op->var)) {
+ return ffi::Unchanged();
+ } else {
+ return Bind(var, value, op->loc);
+ }
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::SelectNode* op,
+ InplaceMode inplace_mode) {
+ auto condition_result = this->Mutate(op->condition, inplace_mode);
+ bool condition_unchanged = condition_result.UnchangedOrSameAs(op->condition);
+ PrimExpr condition =
std::move(condition_result).ValueOrUnchanged(op->condition);
+ auto true_value_result = this->Mutate(op->true_value, inplace_mode);
+ bool true_value_unchanged =
true_value_result.UnchangedOrSameAs(op->true_value);
+ PrimExpr true_value =
std::move(true_value_result).ValueOrUnchanged(op->true_value);
+ auto false_value_result = this->Mutate(op->false_value, inplace_mode);
+ bool false_value_unchanged =
false_value_result.UnchangedOrSameAs(op->false_value);
+ PrimExpr false_value =
std::move(false_value_result).ValueOrUnchanged(op->false_value);
+ if (condition_unchanged && true_value_unchanged && false_value_unchanged &&
+ true_value.ty() == false_value.ty()) {
+ return ffi::Unchanged();
+ } else {
+ PrimType true_dtype = true_value.ty();
+ PrimType false_dtype = false_value.ty();
+ int bits = std::max(true_dtype.bits(), false_dtype.bits());
+ PrimType dtype = true_dtype.WithBits(bits);
+ if (true_dtype != dtype) true_value = prim::cast(dtype, true_value);
+ if (false_dtype != dtype) false_value = prim::cast(dtype, false_value);
+ return prim::Select(condition, true_value, false_value);
+ }
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::RampNode* op,
+ InplaceMode inplace_mode) {
+ auto base_result = Mutate(op->base, inplace_mode);
+ bool base_unchanged = base_result.UnchangedOrSameAs(op->base);
+ PrimExpr base = std::move(base_result).ValueOrUnchanged(op->base);
+ auto stride_result = Mutate(op->stride, inplace_mode);
+ bool stride_unchanged = stride_result.UnchangedOrSameAs(op->stride);
+ PrimExpr stride = std::move(stride_result).ValueOrUnchanged(op->stride);
+ if (base_unchanged && stride_unchanged && base.ty() == stride.ty()) {
+ return ffi::Unchanged();
+ } else {
+ PrimType base_dtype = base.ty();
+ PrimType stride_dtype = stride.ty();
+ TVM_FFI_ICHECK(base_dtype.MatchesCode(DLDataTypeCode::kDLInt) &&
+ stride_dtype.MatchesCode(DLDataTypeCode::kDLInt));
+ int bits = std::max(base_dtype.bits(), stride_dtype.bits());
+ PrimType dtype = base_dtype.WithBits(bits);
+ if (base_dtype->dtype != dtype->dtype) base = prim::cast(dtype, base);
+ if (stride_dtype->dtype != dtype->dtype) stride = prim::cast(dtype,
stride);
+ return prim::Ramp(base, stride, op->lanes);
+ }
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::BroadcastNode* op,
+ InplaceMode inplace_mode) {
+ auto value = Mutate(op->value, inplace_mode);
+ auto lanes = Mutate(op->lanes, inplace_mode);
+ if (value.UnchangedOrSameAs(op->value) &&
lanes.UnchangedOrSameAs(op->lanes)) {
+ return ffi::Unchanged();
+ }
+ // Construction re-infers the dtype; the shared structural hook retains the
old dtype.
+ return prim::Broadcast(std::move(value).ValueOrUnchanged(op->value),
+ std::move(lanes).ValueOrUnchanged(op->lanes));
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::ShuffleNode* op,
+ InplaceMode inplace_mode) {
+ auto vectors = Mutate(op->vectors,
inplace_mode).as_or_throw<UnchangedOr<ffi::Array<PrimExpr>>>();
+ auto indices = Mutate(op->indices,
inplace_mode).as_or_throw<UnchangedOr<ffi::Array<PrimExpr>>>();
+ bool unchanged = vectors.UnchangedOrSameAs(op->vectors) &&
indices.UnchangedOrSameAs(op->indices);
+ if (unchanged && inplace_mode == InplaceMode::kDisallow) return
ffi::Unchanged();
+ // Arrays can change in place; reconstruct to infer the dtype from the final
vectors.
+ PrimExpr updated =
prim::Shuffle(std::move(vectors).ValueOrUnchanged(op->vectors),
+
std::move(indices).ValueOrUnchanged(op->indices));
+ if (unchanged && updated.ty() == op->ty.as_or_throw<PrimType>()) return
ffi::Unchanged();
+ return updated;
+}
+
+#define TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(OP, FUNC)
\
+ UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const OP* op, InplaceMode
inplace_mode) { \
+ auto a_result = this->Mutate(op->a, inplace_mode);
\
+ bool a_unchanged = a_result.UnchangedOrSameAs(op->a);
\
+ PrimExpr a = std::move(a_result).ValueOrUnchanged(op->a);
\
+ auto b_result = this->Mutate(op->b, inplace_mode);
\
+ bool b_unchanged = b_result.UnchangedOrSameAs(op->b);
\
+ PrimExpr b = std::move(b_result).ValueOrUnchanged(op->b);
\
+ if (a_unchanged && b_unchanged && a.ty() == b.ty()) {
\
+ return ffi::Unchanged();
\
+ } else {
\
+ return FUNC(a, b);
\
+ }
\
+ }
+
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::AddNode, operator+);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::SubNode, operator-);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::MulNode, operator*);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::DivNode, div);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::ModNode, truncmod);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::FloorDivNode, floordiv);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::FloorModNode, floormod);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::MinNode, min);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::MaxNode, max);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::EQNode, operator==);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::NENode, operator!=);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::LENode, operator<=);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::LTNode, operator<); //
NOLINT(*)
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::GTNode, operator>); //
NOLINT(*)
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::GENode, operator>=);
+
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::BitwiseAndNode, bitwise_and);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::BitwiseOrNode, bitwise_or);
+TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::BitwiseXorNode, bitwise_xor);
+
+#undef TVM_DEFINE_BIOP_EXPR_MUTATE_WITH_TYPE_MATCH
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::LShiftNode* op,
+ InplaceMode inplace_mode) {
+ PrimType before_dtype = op->a.ty();
+ // Preserve the original operands while computing the narrowed shift.
+ PrimExpr lhs = Mutate(op->a, InplaceMode::kDisallow).ValueOrUnchanged(op->a);
+ PrimExpr rhs = Mutate(op->b, InplaceMode::kDisallow).ValueOrUnchanged(op->b);
+ PrimType after_dtype = lhs.ty();
+ if (ShouldClampShiftAmounts() && before_dtype.code() ==
DLDataTypeCode::kDLInt &&
+ after_dtype.code() == DLDataTypeCode::kDLInt && before_dtype.bits() >
after_dtype.bits()) {
+ // Values fit in the narrowed dtype. Clamp lane-wise to keep dynamic and
+ // vector shift amounts below its width, preserving representable results.
+ rhs = min(rhs, MakeConst(rhs.ty(), after_dtype.bits() - 1, op->loc),
op->loc);
+ }
+ if (lhs.same_as(op->a) && rhs.same_as(op->b) && lhs.ty() == rhs.ty()) {
+ return ffi::Unchanged();
+ }
+ return left_shift(lhs, rhs, op->loc);
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::RShiftNode* op,
+ InplaceMode inplace_mode) {
+ PrimType before_dtype = op->a.ty();
+ // Preserve the original operands while computing the narrowed shift.
+ PrimExpr lhs = Mutate(op->a, InplaceMode::kDisallow).ValueOrUnchanged(op->a);
+ PrimExpr rhs = Mutate(op->b, InplaceMode::kDisallow).ValueOrUnchanged(op->b);
+ PrimType after_dtype = lhs.ty();
+ if (ShouldClampShiftAmounts() && before_dtype.code() ==
DLDataTypeCode::kDLInt &&
+ after_dtype.code() == DLDataTypeCode::kDLInt && before_dtype.bits() >
after_dtype.bits()) {
+ // Values fit in the narrowed dtype. Clamp lane-wise to keep dynamic and
+ // vector shift amounts below its width, preserving representable results.
+ rhs = min(rhs, MakeConst(rhs.ty(), after_dtype.bits() - 1, op->loc),
op->loc);
+ }
+ if (lhs.same_as(op->a) && rhs.same_as(op->b) && lhs.ty() == rhs.ty()) {
+ return ffi::Unchanged();
+ }
+ return right_shift(lhs, rhs, op->loc);
+}
+
+UnchangedOr<PrimExpr> DataTypeLegalizer::Mutate_(const prim::BitwiseNotNode*
op,
+ InplaceMode inplace_mode) {
+ auto a = Mutate(op->a, inplace_mode);
+ if (a.UnchangedOrSameAs(op->a)) return ffi::Unchanged();
+ return prim::BitwiseNot(std::move(a).ValueOrUnchanged(op->a), op->loc);
+}
+
+UnchangedOr<Expr> DataTypeLegalizer::Mutate_(const CallNode* op, InplaceMode
inplace_mode) {
+ Call before = ffi::GetRef<Call>(op);
+ // Keep the original argument dtype available for clz correction below.
+ Expr e =
+ StmtExprMutator::Mutate_(op,
InplaceMode::kDisallow).ValueOrUnchanged(ffi::GetRef<Expr>(op));
+ op = e.as<CallNode>();
+ TVM_FFI_ICHECK(op != nullptr) << "Expected type to be CallNode"
+ << ", but get " << e->GetTypeKey();
+ if (!op->ty.as<PrimTypeNode>()) {
+ return e;
+ }
+ PrimExpr prim_e = e.as_or_throw<PrimExpr>();
+ static const Op pow_op = Op::Get("prim.pow");
+ static const Op& clz_op = prim::clz_op();
+ if (op->op.same_as(pow_op)) {
+ return pow(op->args[0].as_or_throw<PrimExpr>(),
op->args[1].as_or_throw<PrimExpr>());
+ } else if (op->op.same_as(prim::if_then_else_op())) {
+ return Call(op->ty.as_or_throw<PrimType>(), op->op,
+ {op->args[0].as_or_throw<PrimExpr>(),
op->args[1].as_or_throw<PrimExpr>(),
+ op->args[2].as_or_throw<PrimExpr>()},
+ op->attrs, {}, op->loc)
+ .as_or_throw<PrimExpr>();
+ } else if (op->op.same_as(clz_op)) {
+ PrimType before_dtype = before->args[0].as_or_throw<PrimExpr>().ty();
+ PrimType after_dtype = op->args[0].as_or_throw<PrimExpr>().ty();
+ TVM_FFI_ICHECK((before_dtype.code() == DLDataTypeCode::kDLInt ||
+ before_dtype.code() == DLDataTypeCode::kDLUInt) &&
+ (before_dtype.bits() == 32 || before_dtype.bits() == 64))
+ << "clz only supports 32 or 64 bit integer types, but get type before
legalizing: "
+ << before_dtype;
+ TVM_FFI_ICHECK((after_dtype.code() == DLDataTypeCode::kDLInt ||
+ after_dtype.code() == DLDataTypeCode::kDLUInt) &&
+ (after_dtype.bits() == 32 || after_dtype.bits() == 64))
+ << "clz only supports 32 or 64 bit integer types, but get type after
legalizing: "
+ << after_dtype;
+ return prim_e - after_dtype.bits() + before_dtype.bits();
+ }
+ return prim_e;
+}
+
+UnchangedOr<Stmt> IndexDataTypeRewriter::Mutate_(const RegionStmtNode* op,
+ InplaceMode inplace_mode) {
+ bool was_enabled = is_enabled_;
+ if (op->op.same_as(tirx::launch_thread_op())) is_enabled_ = true;
+ auto result = DataTypeLegalizer::Mutate_(op, inplace_mode);
+ is_enabled_ = was_enabled;
+ return result;
+}
+
+UnchangedOr<ffi::Any> IndexDataTypeRewriter::Mutate(ffi::AnyView value,
InplaceMode inplace_mode) {
+ bool is_enabled = is_enabled_;
+ if (value.as<TensorTypeNode>()) is_enabled_ = true;
+ auto result = DataTypeLegalizer::Mutate(value, inplace_mode);
+ is_enabled_ = is_enabled;
+ return result;
+}
+
+UnchangedOr<Stmt> IndexDataTypeRewriter::Mutate_(const TensorStoreNode* op,
+ InplaceMode inplace_mode) {
+ TensorStore store = ffi::GetRef<TensorStore>(op);
+
+ TensorVar new_tensor = Mutate(op->dest.as_or_throw<TensorVar>(),
inplace_mode)
+ .as_or_throw<UnchangedOr<TensorVar>>()
+
.ValueOrUnchanged(op->dest.as_or_throw<TensorVar>());
+ auto value_result = this->Mutate(op->value, inplace_mode);
+ bool value_unchanged = value_result.UnchangedOrSameAs(op->value);
+ auto value = std::move(value_result).ValueOrUnchanged(op->value);
+ PrimType value_dtype = value.ty();
+ if (new_tensor->dtype != value_dtype && value_dtype.IsScalar()) {
+ value = prim::cast(new_tensor->dtype, value);
+ value_unchanged = false;
+ }
+ auto indices = VisitIndices(op->indices, inplace_mode);
+
+ if (!new_tensor.same_as(op->dest.as_or_throw<TensorVar>()) ||
!value_unchanged ||
+ !indices.same_as(op->indices)) {
+ auto writer = store.CopyOnWrite();
+ writer->dest = new_tensor;
+ writer->value = value;
+ writer->indices = indices;
+ }
+
+ return store;
+}
+
+UnchangedOr<PrimExpr> IndexDataTypeRewriter::Mutate_(const TensorLoadNode* op,
+ InplaceMode inplace_mode)
{
+ TensorLoad load = ffi::GetRef<TensorLoad>(op);
+
+ TensorVar new_tensor =
+ Mutate(op->source,
inplace_mode).ValueOrUnchanged(op->source).as_or_throw<TensorVar>();
+ auto indices = VisitIndices(op->indices, inplace_mode);
+
+ if (!new_tensor.same_as(op->source.as_or_throw<tvm::tirx::TensorVar>()) ||
+ !indices.same_as(op->indices)) {
+ return MakeTensorLoad(new_tensor, indices, op->loc);
+ }
+
+ return load;
+}
+
+ffi::Array<PrimExpr> IndexDataTypeRewriter::VisitIndices(const
ffi::Array<PrimExpr>& indices,
+ InplaceMode
inplace_mode) {
+ bool is_enabled = is_enabled_;
+ is_enabled_ = true;
+ auto result = Mutate(indices, inplace_mode)
+ .as_or_throw<UnchangedOr<ffi::Array<PrimExpr>>>()
+ .ValueOrUnchanged(indices);
+ is_enabled_ = is_enabled;
+ return result;
+}
+
+UnchangedOr<Stmt> IndexDataTypeRewriter::Mutate_(const IfNode* op, InplaceMode
inplace_mode) {
+ bool is_condition = is_condition_;
+ is_condition_ = true;
+ auto cond_result = Mutate(op->condition, inplace_mode);
+ bool cond_unchanged = cond_result.UnchangedOrSameAs(op->condition);
+ PrimExpr cond = std::move(cond_result).ValueOrUnchanged(op->condition);
+ is_condition_ = is_condition;
+ auto then_case_result = Mutate(op->then_case, inplace_mode);
+ bool then_case_unchanged = then_case_result.UnchangedOrSameAs(op->then_case);
+ SeqStmt then_case =
std::move(then_case_result).ValueOrUnchanged(op->then_case);
+ ffi::Optional<SeqStmt> else_case =
+ op->else_case.has_value()
+ ? ffi::Optional<SeqStmt>{Mutate(op->else_case.value(), inplace_mode)
+
.ValueOrUnchanged(op->else_case.value())}
+ : std::nullopt;
+ if (!cond_unchanged || !then_case_unchanged ||
!else_case.same_as(op->else_case)) {
+ If new_stmt = ffi::GetRef<If>(op);
+ auto* n = new_stmt.CopyOnWrite();
+ n->condition = std::move(cond);
+ n->then_case = std::move(then_case);
+ n->else_case = std::move(else_case);
+ return new_stmt;
+ }
+ return ffi::Unchanged();
+}
+
+UnchangedOr<Stmt> IndexDataTypeRewriter::Mutate_(const ForNode* op,
InplaceMode inplace_mode) {
+ bool is_enabled = is_enabled_;
+ is_enabled_ = true;
+ PrimVar new_loop_var =
+ Mutate(op->loop_var,
inplace_mode).ValueOrUnchanged(op->loop_var).as_or_throw<PrimVar>();
+ auto min_result = Mutate(op->min, inplace_mode);
+ bool min_unchanged = min_result.UnchangedOrSameAs(op->min);
+ PrimExpr min = std::move(min_result).ValueOrUnchanged(op->min);
+ auto extent_result = Mutate(op->extent, inplace_mode);
+ bool extent_unchanged = extent_result.UnchangedOrSameAs(op->extent);
+ PrimExpr extent = std::move(extent_result).ValueOrUnchanged(op->extent);
+ is_enabled_ = is_enabled;
+ auto new_body_result = Mutate(op->body, inplace_mode);
+ bool new_body_unchanged = new_body_result.UnchangedOrSameAs(op->body);
+ Stmt new_body = std::move(new_body_result).ValueOrUnchanged(op->body);
+
+ if (!new_loop_var.same_as(op->loop_var) || !min_unchanged ||
!extent_unchanged ||
+ !new_body_unchanged) {
+ For new_for = ffi::GetRef<For>(op);
+ auto* n = new_for.CopyOnWrite();
+ n->loop_var = new_loop_var;
+ n->min = prim::cast(new_loop_var.ty(), min);
+ n->extent = prim::cast(new_loop_var.ty(), extent);
+ n->body = new_body;
+ return new_for;
+
+ } else {
+ return ffi::Unchanged();
+ }
+}
+
+UnchangedOr<Stmt> IndexDataTypeRewriter::Mutate_(const BindNode* op,
InplaceMode inplace_mode) {
+ if (!op->value.as<PrimExpr>()) {
+ return StmtExprMutator::Mutate_(op, inplace_mode);
+ }
+ auto mapped = VarRemapGet(op->var);
+ if (mapped == nullptr || mapped.type_index() ==
ffi::TypeIndex::kTVMFFIUnchanged) {
+ return DataTypeLegalizer::Mutate_(op, inplace_mode);
+ }
+ Var var = mapped.as_or_throw<Var>();
+ bool is_enabled = is_enabled_;
+ is_enabled_ = true;
+ PrimExpr value =
+ Mutate(op->value,
inplace_mode).ValueOrUnchanged(op->value).as_or_throw<PrimExpr>();
+ is_enabled_ = is_enabled;
+ // The collected index requirement need not apply to every variable in the
RHS.
+ return Bind(var, prim::cast(var->ty.as_or_throw<PrimType>(), value),
op->loc);
+}
+
+#define TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(OP, FUNC)
\
+ UnchangedOr<PrimExpr> IndexDataTypeRewriter::Mutate_(const OP* op,
InplaceMode inplace_mode) { \
+ bool is_enabled = is_enabled_;
\
+ is_enabled_ = is_condition_ &&
op->a.ty().MatchesCode(DLDataTypeCode::kDLInt) && \
+ op->b.ty().MatchesCode(DLDataTypeCode::kDLInt);
\
+ auto result = Parent::Mutate_(op, inplace_mode);
\
+ is_enabled_ = is_enabled;
\
+ return result;
\
+ }
+
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::EQNode, operator==);
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::NENode, operator!=);
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::LENode, operator<=);
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::LTNode, operator<); //
NOLINT(*)
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::GTNode, operator>); //
NOLINT(*)
+TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH(prim::GENode, operator>=);
+
+UnchangedOr<Expr> IndexDataTypeRewriter::Mutate_(const CallNode* op,
InplaceMode inplace_mode) {
+ // handle if_then_else condition
+ if (op->op.same_as(prim::if_then_else_op())) {
+ bool is_condition = is_condition_;
+ is_condition_ = true;
+ PrimExpr cond =
Mutate(op->args[0]).ValueOrUnchanged(op->args[0]).as_or_throw<PrimExpr>();
+ is_condition_ = is_condition;
+ PrimExpr true_value =
Mutate(op->args[1]).ValueOrUnchanged(op->args[1]).as_or_throw<PrimExpr>();
+ PrimExpr false_value =
+
Mutate(op->args[2]).ValueOrUnchanged(op->args[2]).as_or_throw<PrimExpr>();
+ PrimType true_dtype = true_value.ty();
+ PrimType false_dtype = false_value.ty();
+ PrimType dtype = true_dtype.WithBits(std::max(true_dtype.bits(),
false_dtype.bits()));
+ if (true_dtype != dtype) true_value = prim::cast(dtype, true_value);
+ if (false_dtype != dtype) false_value = prim::cast(dtype, false_value);
+ return Call(dtype, op->op, {cond, true_value, false_value}, op->attrs, {},
op->loc)
+ .as_or_throw<PrimExpr>();
+ }
+ return Parent::Mutate_(op, inplace_mode);
+}
+
+UnchangedOr<PrimExpr> IndexDataTypeRewriter::Mutate_(const prim::SelectNode*
op,
+ InplaceMode inplace_mode)
{
+ bool is_condition = true;
+ std::swap(is_condition_, is_condition);
+ auto condition_result = this->Mutate(op->condition, inplace_mode);
+ bool condition_unchanged = condition_result.UnchangedOrSameAs(op->condition);
+ PrimExpr condition =
std::move(condition_result).ValueOrUnchanged(op->condition);
+ std::swap(is_condition_, is_condition);
+ auto true_value_result = this->Mutate(op->true_value, inplace_mode);
+ bool true_value_unchanged =
true_value_result.UnchangedOrSameAs(op->true_value);
+ PrimExpr true_value =
std::move(true_value_result).ValueOrUnchanged(op->true_value);
+ auto false_value_result = this->Mutate(op->false_value, inplace_mode);
+ bool false_value_unchanged =
false_value_result.UnchangedOrSameAs(op->false_value);
+ PrimExpr false_value =
std::move(false_value_result).ValueOrUnchanged(op->false_value);
+
+ if (condition_unchanged && true_value_unchanged && false_value_unchanged &&
+ true_value.ty() == false_value.ty()) {
+ return ffi::Unchanged();
+ } else {
+ PrimType true_dtype = true_value.ty();
+ PrimType false_dtype = false_value.ty();
+ int bits = std::max(true_dtype.bits(), false_dtype.bits());
+ PrimType dtype = true_dtype.WithBits(bits);
+ if (true_dtype->dtype != dtype->dtype) true_value = prim::cast(dtype,
true_value);
+ if (false_dtype->dtype != dtype->dtype) false_value = prim::cast(dtype,
false_value);
+ return prim::Select(condition, true_value, false_value);
+ }
+}
+
+#undef TVM_DEFINE_CMPOP_EXPR_MUTATE_WITH_TYPE_MATCH
+
Function IndexDataTypeNormalizer::Rewrite(Function func) {
// Keep this short setup local so its collector uses S-TIR block semantics
// without adding a dialect-specific collector hook to the TIRX normalizer.
@@ -238,5 +744,31 @@ TensorRegion
IndexDataTypeNormalizer::VisitTensorRegion(const TensorRegion& tens
}
}
+bool IndexDataTypeNormalizer::CanRewriteDType(PrimType dtype) const {
+ return dtype.code() == DLDataTypeCode::kDLInt && dtype.bits() >= 32;
+}
+
+UnchangedOr<PrimExpr> IndexDataTypeNormalizer::Mutate_(const IntImmNode* op,
+ InplaceMode
inplace_mode) {
+ if (is_enabled_ && CanRewriteDType(op->ty.as_or_throw<PrimType>())) {
+ TVM_FFI_ICHECK_LE(op->value,
max_value(target_data_type_).as_or_throw<IntImm>()->value);
+ return prim::cast(target_data_type_, ffi::GetRef<IntImm>(op));
+ }
+ return ffi::Unchanged();
+}
+
+UnchangedOr<PrimExpr> IndexDataTypeNormalizer::Mutate_(const prim::CastNode*
op,
+ InplaceMode
inplace_mode) {
+ // Unwrap the cast only when the dtype of this cast is integer dtype.
+ // When the dtype of this cast is not integer dtype, it means that this cast
+ // has some other purpose, and we should not unwrap the cast.
+ PrimType dtype = op->ty.as_or_throw<PrimType>();
+ if (is_enabled_ && CanRewriteDType(dtype)) {
+ PrimExpr value = this->Mutate(op->value,
inplace_mode).ValueOrUnchanged(op->value);
+ return value.ty() == target_data_type_ ? value :
prim::Cast(target_data_type_, value);
+ }
+ return IndexDataTypeRewriter::Mutate_(op, inplace_mode);
+}
+
} // namespace s_tir
} // namespace tvm
diff --git a/src/s_tir/ir/data_type_rewriter.h
b/src/s_tir/ir/data_type_rewriter.h
index 755e298924..2fc3cd7d6f 100644
--- a/src/s_tir/ir/data_type_rewriter.h
+++ b/src/s_tir/ir/data_type_rewriter.h
@@ -21,27 +21,135 @@
#define TVM_S_TIR_IR_DATA_TYPE_REWRITER_H_
#include <tvm/s_tir/stmt.h>
+#include <tvm/tirx/stmt_functor.h>
-#include "../../tirx/ir/data_type_rewriter.h"
+#include <utility>
namespace tvm {
namespace s_tir {
+/*!
+ * \brief Legalize the data types of expressions to make sure they are
consistent with other
+ * parts of the program.
+ *
+ * It enforces the following rules:
+ * - The data type of the index variable in a loop must be consistent with the
data type of the loop
+ * bounds.
+ * - The data type of the binary and ternary expressions must be consistent
with the data types of
+ * each of their operands.
+ *
+ * Usually we enforce the consistency of data types when constructing the IR
nodes. However, such
+ * inconsistency may happen as a result of IR mutation in some passes. This
class can be used as
+ * base class of such passes to ensure the consistency of data types.
+ */
+class DataTypeLegalizer : public tirx::StmtExprMutator {
+ public:
+ TVM_DEFINE_OBJECT_FUNCTOR_DEFAULT_CONSTRUCTOR(DataTypeLegalizer,
tirx::StmtExprMutator)
+ using tirx::StmtExprMutator::Mutate;
+ using tirx::StmtExprMutator::Mutate_;
+
+ protected:
+ explicit DataTypeLegalizer(const VTable* vtable) :
tirx::StmtExprMutator(vtable) {}
+ UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<Stmt> Mutate_(const RegionStmtNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<Stmt> Mutate_(const BindNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<Expr> Mutate_(const CallNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::SelectNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::RampNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::BroadcastNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::ShuffleNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LShiftNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::RShiftNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::BitwiseAndNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::BitwiseOrNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::BitwiseXorNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::BitwiseNotNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::AddNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::SubNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::MulNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::DivNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::ModNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::FloorDivNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::FloorModNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::MinNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::MaxNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::EQNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::NENode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LTNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LENode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::GTNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::GENode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LetNode* op, InplaceMode
inplace_mode) override;
+
+ /*! \brief Whether to clamp shift amounts after narrowing signed integers. */
+ virtual bool ShouldClampShiftAmounts() const { return false; }
+};
+
+/*!
+ * \brief Data type rewriter for tensor indices.
+ *
+ * Detect the components of tensor indices that should be considered for data
type rewriting.
+ * This class doesn't perform actual rewriting of data types. During recursive
visiting, the
+ * internal flags `is_enabled_` and `is_conditional_` are used to indicate
whether the current
+ * expression is a tensor index or a conditional expression, which can be used
in the sub-classes to
+ * implement different rewriting rules.
+ */
+class IndexDataTypeRewriter : public DataTypeLegalizer {
+ public:
+ TVM_DEFINE_OBJECT_FUNCTOR_DEFAULT_CONSTRUCTOR(IndexDataTypeRewriter,
DataTypeLegalizer)
+ using DataTypeLegalizer::Mutate;
+ using DataTypeLegalizer::Mutate_;
+
+ protected:
+ explicit IndexDataTypeRewriter(const VTable* vtable) :
DataTypeLegalizer(vtable) {}
+ using Parent = DataTypeLegalizer;
+ UnchangedOr<ffi::Any> Mutate(ffi::AnyView value, InplaceMode inplace_mode)
override;
+ UnchangedOr<Stmt> Mutate_(const TensorStoreNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<Stmt> Mutate_(const RegionStmtNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const TensorLoadNode* op, InplaceMode
inplace_mode) override;
+ ffi::Array<PrimExpr> VisitIndices(const ffi::Array<PrimExpr>& indices,
InplaceMode inplace_mode);
+ UnchangedOr<Stmt> Mutate_(const IfNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<Stmt> Mutate_(const BindNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<Expr> Mutate_(const CallNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<Stmt> Mutate_(const ForNode* op, InplaceMode inplace_mode)
override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::EQNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::NENode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LTNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::LENode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::GTNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::GENode* op, InplaceMode
inplace_mode) override;
+
+ UnchangedOr<PrimExpr> Mutate_(const prim::SelectNode* op, InplaceMode
inplace_mode) override;
+
+ // indicator of index expr to rewrite
+ bool is_enabled_{false};
+ // indicator of condition
+ bool is_condition_{false};
+};
+
// Explicit normalization for schedulable TE functions and tensor intrinsic
bodies.
// Ordinary TIRX dtype passes run after block lowering.
-class IndexDataTypeNormalizer : public tirx::IndexDataTypeNormalizer {
+class IndexDataTypeNormalizer : public IndexDataTypeRewriter {
public:
- using Parent = tirx::IndexDataTypeNormalizer;
+ using Parent = IndexDataTypeRewriter;
using Parent::Mutate;
using Parent::Mutate_;
explicit IndexDataTypeNormalizer(PrimType target_data_type)
- : Parent(std::move(target_data_type), GlobalVTable()) {}
+ : Parent(GlobalVTable()), target_data_type_(std::move(target_data_type))
{}
tirx::Function Rewrite(tirx::Function func);
virtual UnchangedOr<Stmt> Mutate_(const SBlockNode* op, InplaceMode
inplace_mode);
virtual UnchangedOr<Stmt> Mutate_(const SBlockRealizeNode* op, InplaceMode
inplace_mode);
protected:
+ UnchangedOr<PrimExpr> Mutate_(const IntImmNode* op, InplaceMode
inplace_mode) override;
+ UnchangedOr<PrimExpr> Mutate_(const prim::CastNode* op, InplaceMode
inplace_mode) override;
+
+ /*! \brief Specifies which data type we can rewrite */
+ virtual bool CanRewriteDType(PrimType dtype) const;
+
+ PrimType target_data_type_ = PrimType::Int(64);
+
static void InitVTable(VTable* table) {
Parent::InitVTable(table);
SetDispatch<IndexDataTypeNormalizer, SBlockNode>(table);