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 a771de82bd [REFACTOR][TIRx] Promote LambdaExpr to Expr (#20546)
a771de82bd is described below

commit a771de82bdc9517ca4596727dcf0c4c15cacab31
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Oct 4 21:29:12 2026 -0400

    [REFACTOR][TIRx] Promote LambdaExpr to Expr (#20546)
    
    Promote `LambdaExpr` to `Expr` in C++ and Python, retaining the
    inherited `MissingType` and existing construction and application
    behavior.
    
    Preserve lexical parameter scope during structural traversal and
    mutation, and recognize lambda expressions in TIR path traversal.
---
 include/tvm/tirx/tile_primitive.h    |  8 ++--
 python/tvm/tirx/tile_primitive.py    |  2 +-
 src/tirx/ir/lambdaexpr.cc            | 83 ++++++++++++++++++++++++++++++++++--
 src/tirx/ir/tir_visitor_with_path.cc | 11 +++++
 src/tirx/ir/tir_visitor_with_path.h  |  4 ++
 5 files changed, 99 insertions(+), 9 deletions(-)

diff --git a/include/tvm/tirx/tile_primitive.h 
b/include/tvm/tirx/tile_primitive.h
index 879c9b5e72..c41c38c2b4 100644
--- a/include/tvm/tirx/tile_primitive.h
+++ b/include/tvm/tirx/tile_primitive.h
@@ -42,7 +42,7 @@ namespace tirx {
  * At lowering time the dispatch substitutes ``vars`` with the concrete
  * instruction axes via ``Apply``.
  */
-class LambdaExprNode : public ffi::Object {
+class LambdaExprNode : public ExprNode {
  public:
   explicit LambdaExprNode(ffi::UnsafeInit tag) : pred(tag) {}
 
@@ -64,18 +64,18 @@ class LambdaExprNode : public ffi::Object {
   }
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
-  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.LambdaExpr", LambdaExprNode, 
ffi::Object);
+  TVM_FFI_DECLARE_OBJECT_INFO_FINAL("tirx.LambdaExpr", LambdaExprNode, 
ExprNode);
 };
 
 /*!
  * \brief Managed reference to LambdaExprNode.
  * \sa LambdaExprNode
  */
-class LambdaExpr : public ffi::ObjectRef {
+class LambdaExpr : public Expr {
  public:
   explicit LambdaExpr(Array<Var> vars, PrimExpr pred);
 
-  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(LambdaExpr, ffi::ObjectRef, 
LambdaExprNode);
+  TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(LambdaExpr, Expr, 
LambdaExprNode);
 };
 
 /*!
diff --git a/python/tvm/tirx/tile_primitive.py 
b/python/tvm/tirx/tile_primitive.py
index 3118130d01..8adbd3abe5 100644
--- a/python/tvm/tirx/tile_primitive.py
+++ b/python/tvm/tirx/tile_primitive.py
@@ -39,7 +39,7 @@ from .stmt import Stmt
 
 
 @register_object("tirx.LambdaExpr")
-class LambdaExpr(Object):
+class LambdaExpr(Expr):
     """A reified Python lambda: bound variables and a body over them.
 
     Used by tile primitive ops that take a per-element expression over the
diff --git a/src/tirx/ir/lambdaexpr.cc b/src/tirx/ir/lambdaexpr.cc
index 789bf9dc0e..b73e6caab1 100644
--- a/src/tirx/ir/lambdaexpr.cc
+++ b/src/tirx/ir/lambdaexpr.cc
@@ -22,13 +22,88 @@
  * \brief Implementation of LambdaExpr, a reified lambda used by tile 
primitive ops.
  */
 
-#include "tvm/ffi/extra/structural_mutate.h"
-#include "tvm/tirx/tile_primitive.h"
+#include <tvm/ffi/extra/structural_mutate.h>
+#include <tvm/ffi/extra/structural_visit.h>
+#include <tvm/tirx/tile_primitive.h>
+
+#include <utility>
+#include <vector>
 
 namespace tvm {
 namespace tirx {
 
-TVM_FFI_STATIC_INIT_BLOCK() { LambdaExprNode::RegisterReflection(); }
+namespace {
+
+ffi::Expected<ffi::Optional<ffi::VisitInterrupt>> LambdaExprVisit(
+    ffi::StructuralVisitorObj* visitor, ffi::AnyView value) noexcept {
+  const auto* self =
+      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
LambdaExprNode>(value);
+  TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->VisitExpected(self->ty));
+  TVM_FFI_S_VISIT_MAYBE_EARLY_RETURN(visitor->WithDefRegionKind(
+      kTVMFFIDefRegionKindSimple, [&]() { return 
visitor->VisitExpected(self->vars); }));
+  return visitor->VisitExpected(self->pred);
+}
+
+ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
LambdaExprMutate(ffi::StructuralMutatorObj* mutator,
+                                                           ffi::AnyView value) 
noexcept {
+  const auto* self =
+      ffi::details::AnyUnsafe::RawObjectPtrFromAnyViewAfterCheck<const 
LambdaExprNode>(value);
+  // Parameters shadow substitutions from the surrounding expression. Save and 
restore
+  // their remaps so a binder rewrite stays local to this lambda's body.
+  std::vector<ffi::Any> saved;
+  for (const Var& var : self->vars) {
+    auto previous = mutator->VarRemapGetExpected(var);
+    TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(previous);
+    saved.push_back(std::move(previous).value());
+  }
+  auto result = [&]() -> ffi::Expected<ffi::UnchangedOr<ffi::Any>> {
+    for (const Var& var : self->vars) {
+      auto cleared = mutator->VarRemapSetExpected(var, nullptr);
+      TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(cleared);
+    }
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<Type>, mapped_ty,
+                                      mutator->MutateExpected(self->ty));
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<ffi::Array<Var>>, 
mapped_vars,
+                                      
mutator->WithDefRegionKind(kTVMFFIDefRegionKindSimple, [&]() {
+                                        return 
mutator->MutateExpected(self->vars);
+                                      }));
+    auto vars = std::move(mapped_vars).ValueOrUnchanged(self->vars);
+    for (size_t i = 0; i < vars.size(); ++i) {
+      auto remap = mutator->VarRemapSetExpected(self->vars[i], vars[i]);
+      TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(remap);
+    }
+    TVM_FFI_S_MUTATE_ASSIGN_OR_RETURN(ffi::UnchangedOr<PrimExpr>, mapped_pred,
+                                      mutator->MutateExpected(self->pred));
+    if (vars.same_as(self->vars) && mapped_pred.UnchangedOrSameAs(self->pred) 
&&
+        mapped_ty.UnchangedOrSameAs(self->ty)) {
+      return ffi::Unchanged();
+    }
+    auto copy = ffi::make_object<LambdaExprNode>(*self);
+    copy->vars = std::move(vars);
+    copy->pred = std::move(mapped_pred).ValueOrUnchanged(self->pred);
+    copy->ty = std::move(mapped_ty).ValueOrUnchanged(self->ty);
+    return ffi::Any(std::move(copy));
+  }();
+  for (size_t i = 0; i < self->vars.size(); ++i) {
+    auto restored = mutator->VarRemapSetExpected(self->vars[i], saved[i]);
+    TVM_FFI_S_MUTATE_MAYBE_EARLY_RETURN(restored);
+  }
+  return result;
+}
+
+}  // namespace
+
+TVM_FFI_STATIC_INIT_BLOCK() {
+  namespace refl = tvm::ffi::reflection;
+  LambdaExprNode::RegisterReflection();
+  refl::TypeAttrDef<LambdaExprNode>()
+      .attr(refl::type_attr::kStructuralVisit,
+            ffi::FStructuralVisit::FromNative<&LambdaExprVisit>())
+      .attr(refl::type_attr::kStructuralMutate,
+            ffi::FStructuralMutate::FromNative<&LambdaExprMutate>())
+      .attr(refl::type_attr::kStructuralMaybeInplaceMutate,
+            ffi::FStructuralMutate::FromNative<&LambdaExprMutate>());
+}
 
 PrimExpr LambdaExprNode::Apply(const ffi::Array<PrimExpr>& indices) const {
   TVM_FFI_ICHECK_EQ(indices.size(), vars.size());
@@ -46,7 +121,7 @@ PrimExpr LambdaExprNode::Apply(const ffi::Array<PrimExpr>& 
indices) const {
       .as_or_throw<PrimExpr>();
 }
 
-LambdaExpr::LambdaExpr(ffi::Array<Var> vars, PrimExpr pred) {
+LambdaExpr::LambdaExpr(ffi::Array<Var> vars, PrimExpr pred) : 
Expr(ffi::UnsafeInit{}) {
   auto n = ffi::make_object<LambdaExprNode>(std::move(pred));
   n->vars = std::move(vars);
   data_ = std::move(n);
diff --git a/src/tirx/ir/tir_visitor_with_path.cc 
b/src/tirx/ir/tir_visitor_with_path.cc
index a1844b0536..c0402f184f 100644
--- a/src/tirx/ir/tir_visitor_with_path.cc
+++ b/src/tirx/ir/tir_visitor_with_path.cc
@@ -256,6 +256,8 @@ void TIRVisitorWithPath::Dispatch_(const 
tirx::TilePrimitiveCallNode* op, Access
     }
     if (auto buf_region = op->args[i].as<TensorRegion>()) {
       Visit(buf_region.value(), path->Attr("args")->ArrayItem(i));
+    } else if (auto lambda = op->args[i].as<LambdaExpr>()) {
+      Visit(lambda.value(), path->Attr("args")->ArrayItem(i));
     } else if (auto expr = op->args[i].as<PrimExpr>()) {
       Visit(expr.value(), path->Attr("args")->ArrayItem(i));
     } else if (auto stmt = op->args[i].as<Stmt>()) {
@@ -297,6 +299,15 @@ void TIRVisitorWithPath::Dispatch_(const TensorRegionNode* 
op, AccessPath path)
 
 void TIRVisitorWithPath::Dispatch_(const OpaqueExprNode* op, AccessPath path) 
{}
 
+void TIRVisitorWithPath::VisitLambda(const LambdaExprNode* op, AccessPath 
path) {
+  Visit(op->ty, path->Attr("ty"));
+  std::vector<DefContext<Var>> context;
+  for (size_t i = 0; i < op->vars.size(); ++i) {
+    context.push_back(WithDef(op->vars[i], path->Attr("vars")->ArrayItem(i)));
+  }
+  Visit(op->pred, path->Attr("pred"));
+}
+
 void TIRVisitorWithPath::Dispatch_(const TupleNode* op, AccessPath path) {
   Visit(op->fields, path->Attr("fields"));
 }
diff --git a/src/tirx/ir/tir_visitor_with_path.h 
b/src/tirx/ir/tir_visitor_with_path.h
index 1115c4754b..c4da3d05b2 100644
--- a/src/tirx/ir/tir_visitor_with_path.h
+++ b/src/tirx/ir/tir_visitor_with_path.h
@@ -30,6 +30,7 @@
 #include <tvm/runtime/logging.h>
 #include <tvm/tirx/expr_functor.h>
 #include <tvm/tirx/stmt_functor.h>
+#include <tvm/tirx/tile_primitive.h>
 
 #include <exception>
 #include <optional>
@@ -80,6 +81,8 @@ class TIRVisitorWithPath : protected ExprFunctor<void(const 
Expr&, ffi::reflecti
       Dispatch_(tuple_get_item, path);
     } else if (auto* buffer_region = obj.as<TensorRegionNode>()) {
       Dispatch_(buffer_region, path);
+    } else if (auto* lambda = obj.as<LambdaExprNode>()) {
+      VisitLambda(lambda, path);
     } else if (obj.as<OpaqueExprNode>()) {
       Dispatch(obj, path);
     } else {
@@ -99,6 +102,7 @@ class TIRVisitorWithPath : protected ExprFunctor<void(const 
Expr&, ffi::reflecti
   // Visit type metadata through its reflected fields, preserving source 
access paths.
   virtual void Visit(const Type& obj, ffi::reflection::AccessPath path);
   virtual void Visit(ffi::AnyView obj, ffi::reflection::AccessPath path);
+  void VisitLambda(const LambdaExprNode* op, ffi::reflection::AccessPath path);
 
   // Visitors for TIR constructs that are neither PrimExpr nor Stmt
   virtual void Visit(const IRModule& obj, ffi::reflection::AccessPath path);

Reply via email to