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-tirx.git


The following commit(s) were added to refs/heads/main by this push:
     new 24173d5ce [Rust] Update TVM FFI and use Result<UnchangedOr<Any>> map 
and mutate results (#194)
24173d5ce is described below

commit 24173d5ce4662fb12a2b78686704b39af4c27828
Author: Tianqi Chen <[email protected]>
AuthorDate: Wed Nov 4 16:22:34 2026 -0500

    [Rust] Update TVM FFI and use Result<UnchangedOr<Any>> map and mutate 
results (#194)
    
    This PR updates TVM FFI to include apache/tvm-ffi#925, which makes
    `Result<UnchangedOr<Any>>` the result of Rust structural map and mutate
    dispatchers, and migrates TIRx to it.
    
    - The constraint-aware map and mutate adapters require dispatchers
    producing `Option<Result<UnchangedOr<Any>>>`. The map adapter returns
    recursion results directly and checks for the unchanged marker with
    `is_unchanged()`.
    - The constraint-aware mutation test, the IR guide and extension guide
    examples, and their docs use `Result<UnchangedOr<Any>>` handlers.
    Handlers return `default_mutate` directly and keep a value with
    `Unchanged.into()`.
    - The TVM FFI update also brings in apache/tvm-ffi#920, #922, and #924.
---
 3rdparty/tvm-ffi                                   |  2 +-
 docs/advanced/defining_ir_extensions.rst           |  6 +--
 docs/ir_guides/working_with_ir_in_rust.rst         |  6 ++-
 examples/extension_guides/rust/src/lib.rs          | 10 ++--
 examples/ir_guides/rust/src/lib.rs                 | 14 +++---
 .../tirx/structural_map_with_constraint_context.rs | 53 +++++++++++-----------
 .../structural_mutate_with_constraint_context.rs   |  6 +--
 .../structural_mutate_with_constraint_context.rs   | 28 ++++++------
 8 files changed, 64 insertions(+), 61 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index 3e906bc68..2e7dcacb6 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit 3e906bc685ea45624bc80e8d7b7d86497d492af3
+Subproject commit 2e7dcacb6f21dcc3cc9603493f06f3760d9fa7e8
diff --git a/docs/advanced/defining_ir_extensions.rst 
b/docs/advanced/defining_ir_extensions.rst
index 7af329cac..d728efd77 100644
--- a/docs/advanced/defining_ir_extensions.rst
+++ b/docs/advanced/defining_ir_extensions.rst
@@ -292,9 +292,9 @@ The rewrites are:
             impl Lowering {
                 fn parts(&self, expr: &Expr) -> Result<(Expr, Expr)> { ... }
             }
-            #[dispatch(map, (AnyView<'_>,), Result<Any>)]
+            #[dispatch(map, (AnyView<'_>,), Result<UnchangedOr<Any>>)]
             impl Lowering {
-                fn map_sequence(&mut self, seq: SeqStmt) -> Result<Any> { ... }
+                fn map_sequence(&mut self, seq: SeqStmt) -> 
Result<UnchangedOr<Any>> { ... }
          :append: }
 
    .. group-tab:: Python
@@ -309,7 +309,7 @@ and ``parts`` reads the real and imaginary parts of an 
operand, either a
 validates a call to one of the operators, then applies the three rules;
 ``map_bind`` splits a complex binding, and ``map_sequence`` flattens the
 sequence that a split binding leaves inside another. In Rust,
-``#[dispatch(map, (AnyView<'_>,), Result<Any>)]`` sends each node to the
+``#[dispatch(map, (AnyView<'_>,), Result<UnchangedOr<Any>>)]`` sends each node 
to the
 ``map_`` method of its type. In Python, the next excerpt pairs each method with
 the type of node it takes.
 
diff --git a/docs/ir_guides/working_with_ir_in_rust.rst 
b/docs/ir_guides/working_with_ir_in_rust.rst
index 98bc11fdb..cd8db1c46 100644
--- a/docs/ir_guides/working_with_ir_in_rust.rst
+++ b/docs/ir_guides/working_with_ir_in_rust.rst
@@ -645,8 +645,10 @@ The output is:
 ``mutate_for`` records the replacement for its loop variable, then
 ``mutator.default_mutate(self, value)`` rewrites the loop's children
 through the same struct, so ``mutate_var`` sees the replacement before any
-use of ``i``. ``default_mutate`` returns ``UnchangedOr<Any>``, and
-``.map(Into::into)`` turns it into the handler's ``Any`` result.
+use of ``i``. Mutate handlers return ``Result<UnchangedOr<Any>>``, the
+same type that ``default_mutate`` returns, so ``mutate_for`` returns its
+result directly. ``mutate_var`` returns the new index with ``.into()``, or
+``Unchanged`` to keep the variable.
 
 A mutate handler receives its node as a ``MutationView``, here
 ``MutationView<'_, For>``. We read the loop's fields through it, as with
diff --git a/examples/extension_guides/rust/src/lib.rs 
b/examples/extension_guides/rust/src/lib.rs
index 94c5e8f3d..295ce1b2a 100644
--- a/examples/extension_guides/rust/src/lib.rs
+++ b/examples/extension_guides/rust/src/lib.rs
@@ -31,7 +31,7 @@ use tvm_tirx::tirx::transform::FunctionPass;
 use tvm_tirx::tirx::Function;
 use tvm_tirx::tvm_ffi::{
     self, def_object_ref, dispatch, rs_object, Any, AnyView, Error, 
ObjectRefCast, ObjectRefCore,
-    Result, Unchanged, WalkOrder,
+    Result, Unchanged, UnchangedOr, WalkOrder,
 };
 
 // begin type
@@ -192,14 +192,14 @@ impl Lowering {
     }
 }
 
-#[dispatch(map, (AnyView<'_>,), Result<Any>)]
+#[dispatch(map, (AnyView<'_>,), Result<UnchangedOr<Any>>)]
 impl Lowering {
-    fn map_sequence(&mut self, seq: SeqStmt) -> Result<Any> {
+    fn map_sequence(&mut self, seq: SeqStmt) -> Result<UnchangedOr<Any>> {
         Ok(SeqStmt::new(seq.seq.iter(), seq.loc.clone()).into())
     }
 
     // begin lowering
-    fn map_call(&mut self, call: Call) -> Result<Any> {
+    fn map_call(&mut self, call: Call) -> Result<UnchangedOr<Any>> {
         if is_complex_op(&call.op)? {
             validate_call(&call)?;
         }
@@ -227,7 +227,7 @@ impl Lowering {
         Ok(call.into())
     }
 
-    fn map_bind(&mut self, bind: Bind) -> Result<Any> {
+    fn map_bind(&mut self, bind: Bind) -> Result<UnchangedOr<Any>> {
         if let Some(ty) = bind.var.ty.downcast_ref::<ComplexTypeObj>() {
             let (re, im) = self.parts(&bind.value)?;
             let name = bind.var.name.as_str();
diff --git a/examples/ir_guides/rust/src/lib.rs 
b/examples/ir_guides/rust/src/lib.rs
index a7f6df938..fed3f2868 100644
--- a/examples/ir_guides/rust/src/lib.rs
+++ b/examples/ir_guides/rust/src/lib.rs
@@ -37,7 +37,7 @@ use tvm_tirx::tirx::Function;
 use tvm_tirx::tvm_ffi::{
     self, dispatch, match_any, Any, AnyView, Array, DefRegionKind, 
MutationView, ObjectIdentity,
     ObjectRefCast, ObjectRefCore, Result, StructuralMutatorView, 
StructuralVisitorView, Unchanged,
-    VisitInterrupt, WalkOrder, WalkResult,
+    UnchangedOr, VisitInterrupt, WalkOrder, WalkResult,
 };
 
 /// A Python list of strings.
@@ -283,27 +283,27 @@ struct ReverseLoops {
     reversed_index: HashMap<ObjectIdentity, Expr>,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl ReverseLoops {
     fn mutate_for(
         &mut self,
         value: MutationView<'_, For>,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let index = (&value.end - 1 - &value.var).try_into()?;
         self.reversed_index.insert(ObjectIdentity::of(&value.var), index);
-        mutator.default_mutate(self, value).map(Into::into)
+        mutator.default_mutate(self, value)
     }
 
     fn mutate_var(
         &mut self,
         var: Var,
         mutator: &mut StructuralMutatorView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let is_use = mutator.def_region_kind() == DefRegionKind::None;
         Ok(match self.reversed_index.get(&ObjectIdentity::of(&var)) {
             Some(index) if is_use => index.clone().into(),
-            _ => var.into(),
+            _ => Unchanged.into(),
         })
     }
 }
@@ -335,7 +335,7 @@ fn remove_redundant_guards(func: Function) -> 
Result<Function> {
     let analyzer = Analyzer::new();
     structural_map_with_constraint_context(
         func,
-        |branch: If, constraints: &ConstraintContext| -> Result<Any> {
+        |branch: If, constraints: &ConstraintContext| -> 
Result<UnchangedOr<Any>> {
             analyzer.sync_constraint_context(constraints)?;
             Ok(if analyzer.can_prove(branch.condition.clone(), 
ProofStrength::Default)? {
                 branch.then_case.clone().into()
diff --git a/rust/src/tirx/structural_map_with_constraint_context.rs 
b/rust/src/tirx/structural_map_with_constraint_context.rs
index 2d5e721ec..4f24d6640 100644
--- a/rust/src/tirx/structural_map_with_constraint_context.rs
+++ b/rust/src/tirx/structural_map_with_constraint_context.rs
@@ -26,11 +26,9 @@ use std::borrow::BorrowMut;
 
 use tvm_ffi::{
     Any, AnyView, DefRegionKind, Dispatcher, IntoDispatcher, MutationView, 
Object, Result,
-    WalkOrder,
+    UnchangedOr, WalkOrder,
 };
 
-use tvm_ffi::tvm_ffi_sys::TVMFFITypeIndex;
-
 use super::structural_mutate_with_constraint_context::{
     structural_mutate_with_constraint_context, 
StructuralMutatorWithConstraintContextView,
 };
@@ -46,11 +44,13 @@ impl<'v, 'c, 'r, 'a, D, const PRE: bool>
             MutationView<'v>,
             &'c mut StructuralMutatorWithConstraintContextView<'r, 
MapMutator<'a, D, PRE>>,
         ),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
     > for &mut MapMutator<'a, D, PRE>
 where
-    for<'s, 'b, 'ctx> &'s mut D:
-        Dispatcher<(AnyView<'b>, &'ctx ConstraintContext, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'b, 'ctx> &'s mut D: Dispatcher<
+        (AnyView<'b>, &'ctx ConstraintContext, DefRegionKind),
+        Option<Result<UnchangedOr<Any>>>,
+    >,
 {
     #[inline(always)]
     fn dispatch_tuple(
@@ -59,22 +59,24 @@ where
             MutationView<'v>,
             &'c mut StructuralMutatorWithConstraintContextView<'r, 
MapMutator<'a, D, PRE>>,
         ),
-    ) -> Option<Result<Any>> {
+    ) -> Option<Result<UnchangedOr<Any>>> {
         Some(self.map(value, mutator))
     }
 }
 
 impl<D, const PRE: bool> MapMutator<'_, D, PRE>
 where
-    for<'s, 'v, 'c> &'s mut D:
-        Dispatcher<(AnyView<'v>, &'c ConstraintContext, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'v, 'c> &'s mut D: Dispatcher<
+        (AnyView<'v>, &'c ConstraintContext, DefRegionKind),
+        Option<Result<UnchangedOr<Any>>>,
+    >,
 {
     #[inline(always)]
     fn map(
         &mut self,
         value: MutationView<'_>,
         mutator: &mut StructuralMutatorWithConstraintContextView<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let kind = mutator.def_region_kind();
         if PRE {
             let mode = value.inplace_mode();
@@ -88,12 +90,13 @@ where
                 ))
             };
             let Some(result) = result else {
-                return mutator.default_mutate(self, value).map(Into::into);
+                return mutator.default_mutate(self, value);
             };
-            let mut mapped = result?;
-            if mapped.type_index() == TVMFFITypeIndex::kTVMFFIUnchanged as i32 
{
-                return mutator.default_mutate(self, value).map(Into::into);
+            let mapped = result?;
+            if mapped.is_unchanged() {
+                return mutator.default_mutate(self, value);
             }
+            let mut mapped: Any = mapped.into();
             let same_object = value
                 .downcast_ref::<Object>()
                 .zip(mapped.downcast_ref::<Object>())
@@ -101,13 +104,13 @@ where
             if same_object {
                 // An identity callback must not make a unique root appear 
shared.
                 drop(mapped);
-                return mutator.default_mutate(self, value).map(Into::into);
+                return mutator.default_mutate(self, value);
             }
             // An owned replacement can lend a capability, restricted by the
             // original path's permission. The shared engine checks live 
aliases.
             let replacement = MutationView::from(&mut 
mapped).with_inplace_mode(mode);
             let result = mutator.default_mutate(self, replacement)?;
-            Ok(result.value_or(mapped))
+            Ok(UnchangedOr::changed(result.value_or(mapped)))
         } else {
             // SAFETY: this private adapter runs only inside the shared engine
             // callback. The engine owns the root; recursive callers keep each
@@ -118,9 +121,7 @@ where
             // let the reborrow escape this callback.
             let original: AnyView<'_> = unsafe { 
std::mem::transmute(value.as_any_view()) };
             let mapped = mutator.default_mutate(self, value)?;
-            let unchanged = mapped.is_unchanged();
-            let mapped: Any = mapped.into();
-            let current = if unchanged { original } else { 
AnyView::from(&mapped) };
+            let current = if mapped.is_unchanged() { original } else { 
AnyView::from(&mapped) };
             // Scoped native hooks have restored the incoming context here.
             let result = unsafe {
                 self.mapper.dispatch_tuple((
@@ -132,11 +133,7 @@ where
             match result {
                 Some(result) => {
                     let result = result?;
-                    Ok(if result.type_index() == 
TVMFFITypeIndex::kTVMFFIUnchanged as i32 {
-                        mapped
-                    } else {
-                        result
-                    })
+                    Ok(if result.is_unchanged() { mapped } else { result })
                 }
                 None => Ok(mapped),
             }
@@ -169,13 +166,15 @@ where
     O: BorrowMut<D>,
     for<'v, 'c> H: IntoDispatcher<
         (AnyView<'v>, &'c ConstraintContext, DefRegionKind),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
         M,
         Target = D,
         Output = O,
     >,
-    for<'s, 'v, 'c> &'s mut D:
-        Dispatcher<(AnyView<'v>, &'c ConstraintContext, DefRegionKind), 
Option<Result<Any>>>,
+    for<'s, 'v, 'c> &'s mut D: Dispatcher<
+        (AnyView<'v>, &'c ConstraintContext, DefRegionKind),
+        Option<Result<UnchangedOr<Any>>>,
+    >,
 {
     let mut storage = mapper.into_dispatcher();
     let mapper = storage.borrow_mut();
diff --git a/rust/src/tirx/structural_mutate_with_constraint_context.rs 
b/rust/src/tirx/structural_mutate_with_constraint_context.rs
index 88ddb50e1..a921d43cc 100644
--- a/rust/src/tirx/structural_mutate_with_constraint_context.rs
+++ b/rust/src/tirx/structural_mutate_with_constraint_context.rs
@@ -33,7 +33,7 @@ use tvm_ffi::object::ObjectRef;
 use tvm_ffi::tvm_ffi_sys::{TVMFFIAny, TVMFFITypeIndex};
 use tvm_ffi::{
     Any, Dispatcher, InplaceMode, IntoDispatcher, MutationView, ObjectArc, 
ObjectCore,
-    ObjectRefCore, Result, StructuralMutatorObj, StructuralMutatorView,
+    ObjectRefCore, Result, StructuralMutatorObj, StructuralMutatorView, 
UnchangedOr,
 };
 
 use super::StructuralMutatorWithConstraintContextObj;
@@ -152,14 +152,14 @@ where
     O: BorrowMut<D>,
     for<'v, 'c, 'r> H: IntoDispatcher<
         (MutationView<'v>, &'c mut 
StructuralMutatorWithConstraintContextView<'r, D>),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
         M,
         Target = D,
         Output = O,
     >,
     for<'s, 'v, 'c, 'r> &'s mut D: Dispatcher<
         (MutationView<'v>, &'c mut 
StructuralMutatorWithConstraintContextView<'r, D>),
-        Option<Result<Any>>,
+        Option<Result<UnchangedOr<Any>>>,
     >,
 {
     
StructuralMutateEngine::<StructuralMutatorWithConstraintContextBase>::mutate_root(
diff --git a/rust/tests/tirx/structural_mutate_with_constraint_context.rs 
b/rust/tests/tirx/structural_mutate_with_constraint_context.rs
index 88f674c18..38158e6f7 100644
--- a/rust/tests/tirx/structural_mutate_with_constraint_context.rs
+++ b/rust/tests/tirx/structural_mutate_with_constraint_context.rs
@@ -25,7 +25,9 @@ use 
tvm_tirx::tirx::structural_mutate_with_constraint_context::{
     structural_mutate_with_constraint_context as mutate,
     StructuralMutatorWithConstraintContextView as View,
 };
-use tvm_tirx::tvm_ffi::{dispatch, Any, DefRegionKind, MutationView, 
ObjectRefCore, Result, Unchanged};
+use tvm_tirx::tvm_ffi::{
+    dispatch, Any, DefRegionKind, MutationView, ObjectRefCore, Result, 
Unchanged, UnchangedOr,
+};
 
 use super::{int32, op};
 use crate::common::serial;
@@ -41,13 +43,13 @@ struct Rewrite {
     restored: bool,
 }
 
-#[dispatch(mutate, (MutationView<'_>, &mut View<'_, Self>), Result<Any>)]
+#[dispatch(mutate, (MutationView<'_>, &mut View<'_, Self>), 
Result<UnchangedOr<Any>>)]
 impl Rewrite {
     fn mutate_integer(
         &mut self,
         node: MutationView<'_, IntImm>,
         _: &mut View<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         if node.value.to_i64().unwrap() == 4 {
             Ok(int32(8).into())
         } else {
@@ -58,28 +60,28 @@ impl Rewrite {
         &mut self,
         node: MutationView<'_, Var>,
         view: &mut View<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         self.definitions.push(view.def_region_kind());
-        view.default_mutate(self, node).map(Into::into)
+        view.default_mutate(self, node)
     }
     fn mutate_evaluate(
         &mut self,
         node: MutationView<'_, Evaluate>,
         view: &mut View<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let marker = 
node.value.downcast_ref::<IntImmObj>().unwrap().value.to_i64().unwrap();
         self.facts.push((marker, 
view.base().constraints().to_array().iter().collect()));
-        view.default_mutate(self, node).map(Into::into)
+        view.default_mutate(self, node)
     }
     fn mutate_for(
         &mut self,
         node: MutationView<'_, For>,
         view: &mut View<'_, Self>,
-    ) -> Result<Any> {
+    ) -> Result<UnchangedOr<Any>> {
         let before = view.base().constraints().to_array().len();
         let result = view.default_mutate(self, node);
         self.restored = view.base().constraints().to_array().len() == before;
-        result.map(Into::into)
+        result
     }
 }
 
@@ -144,21 +146,21 @@ fn 
alias_acquired_in_callback_forces_copying_default_descent() {
     struct Alias {
         saved: Option<Any>,
     }
-    #[dispatch(mutate, (MutationView<'_>, &mut View<'_, Self>), Result<Any>)]
+    #[dispatch(mutate, (MutationView<'_>, &mut View<'_, Self>), 
Result<UnchangedOr<Any>>)]
     impl Alias {
         fn mutate_for(
             &mut self,
             node: MutationView<'_, For>,
             view: &mut View<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             self.saved = Some(node.as_any_view().into());
-            view.default_mutate(self, node).map(Into::into)
+            view.default_mutate(self, node)
         }
         fn mutate_integer(
             &mut self,
             node: MutationView<'_, IntImm>,
             _: &mut View<'_, Self>,
-        ) -> Result<Any> {
+        ) -> Result<UnchangedOr<Any>> {
             Ok(if node.value.to_i64() == Some(4) { int32(8).into() } else { 
Unchanged.into() })
         }
     }

Reply via email to