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 3edd4f1d9 [Rust] Borrow references in IntoExpr; bump tvm-ffi to 
a21f5fa7 (#203)
3edd4f1d9 is described below

commit 3edd4f1d905481984a278ab56f24f10d80fe3878
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Nov 5 10:16:58 2026 -0500

    [Rust] Borrow references in IntoExpr; bump tvm-ffi to a21f5fa7 (#203)
    
    Bumps `3rdparty/tvm-ffi` to a21f5fa7, which brings in apache/tvm-ffi#930
    (borrowed upcasts through `AsRef`) and #931 (`downcast_ref`,
    `#[dispatch]` and `match_any!` borrowing reference types in place), and
    uses them in TIRx.
    
    - **`IntoExpr` borrows references.** The trait has an associated
    `Output: Borrow<Expr> + Into<Expr>`. An owned value or a literal
    converts into an owned `Expr`, as before. A reference to an expression
    type (`&Expr`, `&Var`, `&PrimExpr`, ...) borrows as `&Expr` with no
    reference-count change, so `analyzer.can_prove(&cond, ..)` no longer
    clones. Read-only functions borrow with `.borrow()`. Constructors take
    ownership with `into_owned_expr()`, which clones only a borrowed
    expression. Owned values keep a blanket impl. References are listed per
    expression type, because blanket impls over `T` and `&T` overlap under
    Rust's coherence rules.
    - **Typed views.** `typed_expr_view!` gives `PrimExpr` and `IntExpr` the
    in-place borrow impls that `#[derive(ObjectRef)]` generates
    (`downcast_ref`, `&PrimExpr` handlers and `PrimExpr(ref x)` arms). The
    hand-written `AsRef<PrimExpr> for IntExpr` is removed in favor of the
    one the upcast macro now generates.
    - **Examples.** The IR guide's `match_any!`, `#[dispatch]`, walk and
    visit examples borrow reference types (`Add(ref add)`, `&Add`, `&For`,
    `&If`) in place of object types, and the guard examples call
    `can_prove(&branch.condition, ..)`.
---
 3rdparty/tvm-ffi                           |   2 +-
 docs/ir_guides/working_with_ir_in_rust.rst |  14 +--
 examples/ir_guides/rust/src/lib.rs         |  35 ++++----
 rust/src/ir/expr.rs                        | 133 ++++++++++++++++++++++++-----
 rust/src/ir/module.rs                      |   3 +-
 rust/src/ir/prim.rs                        |   3 +-
 rust/src/ir/prim/expr.rs                   |  21 +++--
 rust/src/ir/prim/op.rs                     |  47 +++++-----
 rust/src/ir/prim/vector_expr.rs            |   9 +-
 rust/src/ir/stmt.rs                        |  17 ++--
 rust/src/macro/typed_expr_view.rs          |  34 ++++++++
 rust/src/sym/analyzer.rs                   |  19 +++--
 rust/src/sym/int_set.rs                    |  13 +--
 rust/src/sym/iter_affine_map.rs            |  28 +++---
 rust/src/tirx/analysis/analysis.rs         |   5 +-
 rust/src/tirx/index_map.rs                 |   5 +-
 rust/src/tirx/layout.rs                    |  27 ++++--
 rust/src/tirx/op.rs                        |   6 +-
 rust/src/tirx/tensor.rs                    |   3 +-
 rust/src/tirx/tile/dispatch.rs             |   6 +-
 rust/src/tirx/tile/instruction.rs          |   3 +-
 21 files changed, 295 insertions(+), 138 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index 55b4fad05..a21f5fa79 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit 55b4fad05c39996f506c170985da2381134ef362
+Subproject commit a21f5fa79329cc63b46c0cf087ebcf7ac4516686
diff --git a/docs/ir_guides/working_with_ir_in_rust.rst 
b/docs/ir_guides/working_with_ir_in_rust.rst
index 9c6741245..575560be5 100644
--- a/docs/ir_guides/working_with_ir_in_rust.rst
+++ b/docs/ir_guides/working_with_ir_in_rust.rst
@@ -249,8 +249,8 @@ requested ``prim.Mul``.
 
    ``try_cast`` consumes the managed reference it converts, so we clone
    ``value`` before the first cast; the clone costs one reference-count
-   increment. For read-only inspection, ``value.downcast_ref::<AddObj>()``
-   returns ``Option<&AddObj>`` without consuming or cloning the reference.
+   increment. For read-only inspection, ``value.downcast_ref::<Add>()``
+   returns ``Option<&Add>`` without consuming or cloning the reference.
    ``Any`` and ``AnyView`` provide the same borrowed downcast.
 
 Casting to one type at a time gets tedious when a node can be one of
@@ -355,9 +355,9 @@ generates a ``match_any!`` and a ``Dispatcher`` 
implementation:
        pub fn dispatch(&self, value: AnyView<'_>, nested: bool) -> 
Option<String> {
            match_any! {
                value {
-                   AddObj(ref node) => Some(self.print_add(node, 
nested).into()),
-                   VarObj(ref node) => Some(self.print_var(node).into()),
-                   IntImmObj(ref node) => Some(self.print_imm(node).into()),
+                   Add(ref node) => Some(self.print_add(node, nested).into()),
+                   Var(ref node) => Some(self.print_var(node).into()),
+                   IntImm(ref node) => Some(self.print_imm(node).into()),
                    _ => None,
                }
            }
@@ -453,8 +453,8 @@ The parameter list of a callback decides what it receives:
      - Receives
    * - ``|store: TensorStore|``
      - Each write, as a managed reference.
-   * - ``|store: &TensorStoreObj|``
-     - Each write, as a borrowed object, without changing its reference
+   * - ``|store: &TensorStore|``
+     - Each write, borrowed in place, without changing its reference
        count.
    * - ``|value: AnyView<'_>|``
      - Every value the walk reaches.
diff --git a/examples/ir_guides/rust/src/lib.rs 
b/examples/ir_guides/rust/src/lib.rs
index b380c1fe7..c285e9e60 100644
--- a/examples/ir_guides/rust/src/lib.rs
+++ b/examples/ir_guides/rust/src/lib.rs
@@ -24,11 +24,8 @@
 
 use std::collections::HashMap;
 
-use tvm_tirx::ir::prim::{make_const, Add, AddObj, Mul, MulObj};
-use tvm_tirx::ir::{
-    Expr, For, ForObj, If, IfObj, IntImmObj, PrimType, TensorStore, 
TensorStoreObj, Var, VarObj,
-    UNKNOWN_LOC,
-};
+use tvm_tirx::ir::prim::{make_const, Add, Mul};
+use tvm_tirx::ir::{Expr, For, If, IntImm, PrimType, TensorStore, Var, 
UNKNOWN_LOC};
 use tvm_tirx::script::script;
 use tvm_tirx::sym::{Analyzer, ConstraintContext, ProofStrength};
 use 
tvm_tirx::tirx::structural_map_with_constraint_context::structural_map_with_constraint_context;
@@ -92,10 +89,10 @@ fn casts() -> Result<Strings> {
 fn describe(expr: &Expr) -> Result<String> {
     let text = match_any! {
         AnyView::from(expr) {
-            AddObj(ref add) => format!("({} + {})", describe(&add.a)?, 
describe(&add.b)?),
-            MulObj(ref mul) => format!("({} * {})", describe(&mul.a)?, 
describe(&mul.b)?),
-            VarObj(ref var) => var.name.as_str().to_owned(),
-            IntImmObj(ref imm) => imm.value.to_string(),
+            Add(ref add) => format!("({} + {})", describe(&add.a)?, 
describe(&add.b)?),
+            Mul(ref mul) => format!("({} * {})", describe(&mul.a)?, 
describe(&mul.b)?),
+            Var(ref var) => var.name.as_str().to_owned(),
+            IntImm(ref imm) => imm.value.to_string(),
             _ => "<other>".to_owned(),
         }
     };
@@ -116,7 +113,7 @@ struct Printer;
 
 #[dispatch(print, (AnyView<'_>, bool), String)]
 impl Printer {
-    fn print_add(&self, add: &AddObj, nested: bool) -> String {
+    fn print_add(&self, add: &Add, nested: bool) -> String {
         let text = format!("{} + {}", self.show(&add.a, true), 
self.show(&add.b, true));
         if nested {
             format!("({text})")
@@ -125,11 +122,11 @@ impl Printer {
         }
     }
 
-    fn print_var(&self, var: &VarObj) -> String {
+    fn print_var(&self, var: &Var) -> String {
         var.name.as_str().to_owned()
     }
 
-    fn print_imm(&self, imm: &IntImmObj) -> String {
+    fn print_imm(&self, imm: &IntImm) -> String {
         imm.value.to_string()
     }
 
@@ -220,12 +217,12 @@ struct Counter {
 
 #[dispatch(walk, (AnyView<'_>, DefRegionKind), Result<WalkResult>)]
 impl Counter {
-    fn walk_var(&mut self, _var: &VarObj, _kind: DefRegionKind) -> 
Result<WalkResult> {
+    fn walk_var(&mut self, _var: &Var, _kind: DefRegionKind) -> 
Result<WalkResult> {
         self.vars += 1;
         Ok(WalkResult::Advance)
     }
 
-    fn walk_add(&mut self, _add: &AddObj) -> Result<WalkResult> {
+    fn walk_add(&mut self, _add: &Add) -> Result<WalkResult> {
         self.adds += 1;
         Ok(WalkResult::Advance)
     }
@@ -252,7 +249,7 @@ struct LoopNest {
 impl LoopNest {
     fn visit_for(
         &mut self,
-        loop_: &ForObj,
+        loop_: &For,
         visitor: &mut StructuralVisitorView<'_, Self>,
     ) -> Result<Option<VisitInterrupt>> {
         self.loops.push(loop_.var.name.as_str().to_owned());
@@ -261,7 +258,7 @@ impl LoopNest {
         result
     }
 
-    fn visit_store(&mut self, store: &TensorStoreObj) -> 
Result<Option<VisitInterrupt>> {
+    fn visit_store(&mut self, store: &TensorStore) -> 
Result<Option<VisitInterrupt>> {
         let loops = self.loops.join(", ");
         let write = format!("{} inside [{}]", script(&store.dest)?.as_str(), 
loops);
         self.writes.push(write);
@@ -319,9 +316,9 @@ fn proven_guards(func: Function) -> Result<Array<bool>> {
     let mut proven = Vec::new();
     structural_walk_with_constraint_context(
         &func,
-        |branch: &IfObj, constraints: &ConstraintContext| -> 
Result<WalkResult> {
+        |branch: &If, constraints: &ConstraintContext| -> Result<WalkResult> {
             analyzer.sync_constraint_context(constraints)?;
-            proven.push(analyzer.can_prove(branch.condition.clone(), 
ProofStrength::Default)?);
+            proven.push(analyzer.can_prove(&branch.condition, 
ProofStrength::Default)?);
             Ok(WalkResult::Advance)
         },
         WalkOrder::PreOrder,
@@ -337,7 +334,7 @@ fn remove_redundant_guards(func: Function) -> 
Result<Function> {
         func,
         |branch: If, constraints: &ConstraintContext| -> 
Result<UnchangedOr<Any>> {
             analyzer.sync_constraint_context(constraints)?;
-            Ok(if analyzer.can_prove(branch.condition.clone(), 
ProofStrength::Default)? {
+            Ok(if analyzer.can_prove(&branch.condition, 
ProofStrength::Default)? {
                 branch.then_case.clone().into()
             } else {
                 Unchanged.into()
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index ec3f01d45..7b215a8c7 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -109,6 +109,7 @@ use crate::ir::r#type::Type;
 use crate::ir::support;
 use std::ops::Deref;
 use std::ptr::addr_of_mut;
+use std::borrow::Borrow;
 use tvm_ffi::Any;
 use tvm_ffi::AnyView;
 use tvm_ffi::Array;
@@ -265,16 +266,112 @@ impl Expr {
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
 pub trait IntoExpr {
+    /// The expression: an owned [`Expr`] for an owned value or a literal, or 
a borrowed
+    /// `&Expr` for a reference, which converts without a reference-count 
change.
+    ///
+    /// A function that only reads the expression borrows it with `.borrow()`; 
one that keeps
+    /// it takes ownership with `.into()`, which clones only a borrowed 
expression.
+    type Output: Borrow<Expr> + Into<Expr>;
+
     /// Convert into an expression, or return the first error an 
[`ExprResult`] raised.
-    fn into_expr(self) -> Result<Expr>;
+    fn into_expr(self) -> Result<Self::Output>;
+
+    /// Convert into an owned expression, as 
[`Cow::into_owned`](std::borrow::Cow::into_owned)
+    /// does: an owned value moves, and only a borrowed expression is cloned.
+    fn into_owned_expr(self) -> Result<Expr>
+    where
+        Self: Sized,
+    {
+        self.into_expr().map(Into::into)
+    }
 }
 
-impl<T: Into<Expr>> IntoExpr for T {
+// An owned expression, typed view or literal converts into an owned `Expr`.
+impl<T: AnyCompatible + Into<Expr>> IntoExpr for T {
+    type Output = Expr;
+
     fn into_expr(self) -> Result<Expr> {
         Ok(self.into())
     }
 }
 
+// A reference to an expression, a typed view or a node borrows as `&Expr` 
through the upcast's
+// `AsRef`, without a reference-count change.
+macro_rules! impl_into_expr_ref {
+    ($($t:ty),*) => {$(
+        impl<'a> IntoExpr for &'a $t {
+            type Output = &'a Expr;
+
+            fn into_expr(self) -> Result<&'a Expr> {
+                Ok(self.as_ref())
+            }
+        }
+    )*};
+}
+// Every expression type with an upcast to `Expr`. A reference borrows as 
`&Expr`; the owned
+// blanket above cannot also cover references, because two blanket impls over 
`T` and `&T`
+// overlap for a downstream type. Add a new expression type here.
+impl_into_expr_ref!(
+    Expr,
+    crate::ir::expr::Call,
+    crate::ir::expr::Constant,
+    crate::ir::expr::DataTypeImm,
+    crate::ir::expr::FloatImm,
+    crate::ir::expr::GenericConst,
+    crate::ir::expr::GlobalVar,
+    crate::ir::expr::IntExpr,
+    crate::ir::expr::IntImm,
+    crate::ir::expr::LambdaExpr,
+    crate::ir::expr::OpaqueExpr,
+    crate::ir::expr::PrimExpr,
+    crate::ir::expr::StagingExpr,
+    crate::ir::expr::StringImm,
+    crate::ir::expr::TensorLoad,
+    crate::ir::expr::TensorRegion,
+    crate::ir::expr::Tuple,
+    crate::ir::expr::TupleGetItem,
+    crate::ir::expr::Var,
+    crate::ir::function::BaseFunc,
+    crate::ir::op::Op,
+    crate::ir::prim::expr::Add,
+    crate::ir::prim::expr::And,
+    crate::ir::prim::expr::BitwiseAnd,
+    crate::ir::prim::expr::BitwiseNot,
+    crate::ir::prim::expr::BitwiseOr,
+    crate::ir::prim::expr::BitwiseXor,
+    crate::ir::prim::expr::Cast,
+    crate::ir::prim::expr::Div,
+    crate::ir::prim::expr::EQ,
+    crate::ir::prim::expr::FloorDiv,
+    crate::ir::prim::expr::FloorMod,
+    crate::ir::prim::expr::GE,
+    crate::ir::prim::expr::GT,
+    crate::ir::prim::expr::LE,
+    crate::ir::prim::expr::LShift,
+    crate::ir::prim::expr::LT,
+    crate::ir::prim::expr::Let,
+    crate::ir::prim::expr::Max,
+    crate::ir::prim::expr::Min,
+    crate::ir::prim::expr::Mod,
+    crate::ir::prim::expr::Mul,
+    crate::ir::prim::expr::NE,
+    crate::ir::prim::expr::Not,
+    crate::ir::prim::expr::Or,
+    crate::ir::prim::expr::RShift,
+    crate::ir::prim::expr::Select,
+    crate::ir::prim::expr::Sub,
+    crate::ir::prim::vector_expr::Broadcast,
+    crate::ir::prim::vector_expr::Ramp,
+    crate::ir::prim::vector_expr::Shuffle,
+    crate::sym::analyzer::CanonicalExpr,
+    crate::sym::analyzer::SplitExpr,
+    crate::sym::analyzer::SumExpr,
+    crate::sym::iter_affine_map::IterMapExpr,
+    crate::sym::iter_affine_map::IterSplitExpr,
+    crate::sym::iter_affine_map::IterSumExpr,
+    crate::tirx::function::Function
+);
+
 // Rust literals, as C++ converts them into an expression: an `i32` and an 
`f32` through the
 // constructors `PrimExpr(int32_t)` and `PrimExpr(float)`, the others as 
constants of their type.
 
@@ -379,15 +476,6 @@ crate::r#macro::typed_expr_view! {
 
 tvm_ffi::impl_object_upcast!(PrimExpr => Expr, IntExpr => Expr, IntExpr => 
PrimExpr);
 
-/// An `IntExpr` borrowed as the `PrimExpr` it is, as C++ `IntExpr` derives 
from `PrimExpr`.
-impl AsRef<PrimExpr> for IntExpr {
-    fn as_ref(&self) -> &PrimExpr {
-        // SAFETY: both views are `repr(transparent)` over an 
`ObjectArc<ExprObj>`, and every
-        // scalar integer type is a `PrimType`.
-        unsafe { &*(self as *const IntExpr as *const PrimExpr) }
-    }
-}
-
 impl TryFrom<PrimExpr> for IntExpr {
     type Error = Error;
 
@@ -913,7 +1001,7 @@ impl TupleGetItem {
         index: i32,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let tuple_value = tuple_value.into_expr()?;
+        let tuple_value = tuple_value.into_owned_expr()?;
         if index < 0 {
             let message = format!(
                 "Index out of bounds: Tuple {} cannot be accessed with 
negative index {index}",
@@ -1029,10 +1117,10 @@ impl TensorLoad {
     ) -> Result<Self> {
         static INFER_TYPE: support::TypeAttr =
             support::TypeAttr::new("__tvm_tensor_load_infer_type__");
-        let (source, loc) = (source.into_expr()?, loc.into());
+        let (source, loc) = (source.into_owned_expr()?, loc.into());
         let indices = indices
             .into_iter()
-            .map(|index| PrimExpr::try_from(index.into_expr()?))
+            .map(|index| PrimExpr::try_from(index.into_owned_expr()?))
             .collect::<Result<Array<PrimExpr>>>()?;
         let ty_index = AnyView::from(&source.ty).type_index();
         let Some(infer) = INFER_TYPE.get(ty_index) else {
@@ -1811,8 +1899,9 @@ impl Call {
         ty_args: impl IntoIterator<Item = impl Into<Type>>,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (op, attrs, loc) = (op.into_expr()?, attrs.cloned(), loc.into());
-        let args = 
args.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let (op, attrs, loc) = (op.into_owned_expr()?, attrs.cloned(), 
loc.into());
+        let args =
+            
args.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         let ty_args = 
ty_args.into_iter().map(Into::into).collect::<Array<Type>>();
         if let Some(ty) = ty {
             return Ok(Self::from_fields(loc, ty.clone(), op, args, attrs, 
ty_args));
@@ -2089,8 +2178,10 @@ impl LambdaExpr {
     ///
     /// Returns a `ValueError` if the number of arguments is not the number of 
variables.
     pub fn apply(&self, arguments: impl IntoIterator<Item = impl IntoExpr>) -> 
Result<Expr> {
-        let arguments =
-            
arguments.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let arguments = arguments
+            .into_iter()
+            .map(IntoExpr::into_owned_expr)
+            .collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("ir.LambdaExprApply")
             .call_tuple((self, &arguments))?
             .try_into()
@@ -2152,7 +2243,7 @@ impl Range {
     /// Create the range `[begin, end)` (C++ `Range(begin, end, loc)`);
     /// [`from_min_extent`](Self::from_min_extent) takes the extent instead of 
the end.
     pub fn new(begin: impl IntoExpr, end: impl IntoExpr, loc: impl 
Into<Location>) -> Result<Self> {
-        let (begin, end) = (begin.into_expr()?, end.into_expr()?);
+        let (begin, end) = (begin.into_owned_expr()?, end.into_owned_expr()?);
         let extent = Self::get_extent_from_begin_end(begin.clone(), end)?;
         Ok(Self::from_fields(begin, extent, loc.into()))
     }
@@ -2163,7 +2254,7 @@ impl Range {
         extent: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (min_value, extent) = (min_value.into_expr()?, 
extent.into_expr()?);
+        let (min_value, extent) = (min_value.into_owned_expr()?, 
extent.into_owned_expr()?);
         support::prim_dtype(&min_value)?;
         support::prim_dtype(&extent)?;
         Ok(Self::from_fields(min_value, extent, loc.into()))
@@ -2172,7 +2263,7 @@ impl Range {
     /// Return the extent `end - begin` of the range `[begin, end)`, folded
     /// (C++ `Range::GetExtentFromBeginEnd`).
     pub fn get_extent_from_begin_end(begin: impl IntoExpr, end: impl IntoExpr) 
-> Result<Expr> {
-        let (begin, end) = (begin.into_expr()?, end.into_expr()?);
+        let (begin, end) = (begin.into_owned_expr()?, end.into_owned_expr()?);
         support::prim_dtype(&begin)?;
         support::prim_dtype(&end)?;
         if let Some(add) = end.downcast_ref::<AddObj>() {
diff --git a/rust/src/ir/module.rs b/rust/src/ir/module.rs
index 7a60a16a0..ba0d91c68 100644
--- a/rust/src/ir/module.rs
+++ b/rust/src/ir/module.rs
@@ -53,6 +53,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::attrs::DictAttrs;
 use crate::ir::expr::Expr;
 use crate::ir::expr::GlobalVar;
@@ -173,7 +174,7 @@ impl IRModule {
     /// Returns a `TypeError` if `expr` is not a function.
     pub fn from_expr(expr: impl IntoExpr, functions: &Map<GlobalVar, 
BaseFunc>) -> Result<Self> {
         tvm_ffi::cached_global_func!("ir.IRModuleFromExpr")
-            .call_tuple((&expr.into_expr()?, functions))?
+            .call_tuple((expr.into_expr()?.borrow(), functions))?
             .try_into()
     }
 
diff --git a/rust/src/ir/prim.rs b/rust/src/ir/prim.rs
index 81c69fb24..b8fa95599 100644
--- a/rust/src/ir/prim.rs
+++ b/rust/src/ir/prim.rs
@@ -42,6 +42,7 @@ pub use op::*;
 pub use vector_expr::*;
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::expr::Expr;
 use crate::ir::expr::IntoExpr;
 use tvm_ffi::Result;
@@ -51,6 +52,6 @@ use tvm_ffi::Result;
 /// structural equality for that (`prim.expr_deep_equal`).
 pub fn expr_deep_equal(lhs: impl IntoExpr, rhs: impl IntoExpr) -> Result<bool> 
{
     tvm_ffi::cached_global_func!("prim.expr_deep_equal")
-        .call_tuple((&lhs.into_expr()?, &rhs.into_expr()?))?
+        .call_tuple((lhs.into_expr()?.borrow(), rhs.into_expr()?.borrow()))?
         .try_into()
 }
diff --git a/rust/src/ir/prim/expr.rs b/rust/src/ir/prim/expr.rs
index 104971a08..ee8878a1c 100644
--- a/rust/src/ir/prim/expr.rs
+++ b/rust/src/ir/prim/expr.rs
@@ -185,7 +185,7 @@ macro_rules! binary_new {
                     b: impl IntoExpr,
                     loc: impl Into<Location>,
                 ) -> Result<Self> {
-                    let (a, b) = (a.into_expr()?, b.into_expr()?);
+                    let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
                     let ty = binary_type(stringify!($node), $operands, &a, 
&b)?;
                     Ok(Self::from_fields(loc.into(), ty, a, b))
                 }
@@ -267,7 +267,7 @@ impl Cast {
         value: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (ty, value) = (ty.into(), value.into_expr()?);
+        let (ty, value) = (ty.into(), value.into_owned_expr()?);
         let value_dtype = support::prim_dtype(&value)?;
         if ty.dtype.lanes != value_dtype.lanes {
             let condition = "ty->dtype.lanes == value_expr_ty->dtype.lanes";
@@ -672,7 +672,7 @@ impl BitwiseNot {
     ///
     /// Returns a `TypeError` if `a` is not of an integer or boolean type.
     pub fn new(a: impl IntoExpr, loc: impl Into<Location>) -> Result<Self> {
-        let a = a.into_expr()?;
+        let a = a.into_owned_expr()?;
         if !matches!(support::prim_dtype(&a)?.code, support::INT | 
support::UINT | support::BOOL) {
             let condition = "a_ty.MatchesCode(DLDataTypeCode::kDLInt, 
DLDataTypeCode::kDLUInt) || \
                              a_ty.MatchesCode(DLDataTypeCode::kDLBool)";
@@ -1743,7 +1743,7 @@ impl And {
     /// Returns an `InternalError` if an operand is not boolean, or a 
`TypeError` if the operand
     /// types differ.
     pub fn new(a: impl IntoExpr, b: impl IntoExpr, loc: impl Into<Location>) 
-> Result<Self> {
-        let (a, b) = (a.into_expr()?, b.into_expr()?);
+        let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
         let ty = logical_type(&a, &b)?;
         Ok(Self::from_fields(loc.into(), ty, a, b))
     }
@@ -1817,7 +1817,7 @@ impl Or {
     /// Returns an `InternalError` if an operand is not boolean, or a 
`TypeError` if the operand
     /// types differ.
     pub fn new(a: impl IntoExpr, b: impl IntoExpr, loc: impl Into<Location>) 
-> Result<Self> {
-        let (a, b) = (a.into_expr()?, b.into_expr()?);
+        let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
         let ty = logical_type(&a, &b)?;
         Ok(Self::from_fields(loc.into(), ty, a, b))
     }
@@ -1884,7 +1884,7 @@ impl Not {
     ///
     /// Returns an `InternalError` if `a` is not boolean.
     pub fn new(a: impl IntoExpr, loc: impl Into<Location>) -> Result<Self> {
-        let a = a.into_expr()?;
+        let a = a.into_owned_expr()?;
         let a_dtype = support::prim_dtype(&a)?;
         if a_dtype.code != support::BOOL {
             let condition = "a_ty.MatchesCode(DLDataTypeCode::kDLBool)";
@@ -1985,8 +1985,11 @@ impl Select {
         false_value: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (condition, true_value, false_value) =
-            (condition.into_expr()?, true_value.into_expr()?, 
false_value.into_expr()?);
+        let (condition, true_value, false_value) = (
+            condition.into_owned_expr()?,
+            true_value.into_owned_expr()?,
+            false_value.into_owned_expr()?,
+        );
         let condition_dtype = support::prim_dtype(&condition)?;
         let true_dtype = support::prim_dtype(&true_value)?;
         let false_dtype = support::prim_dtype(&false_value)?;
@@ -2101,7 +2104,7 @@ impl Let {
         body: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (var, value, body) = (var.into(), value.into_expr()?, 
body.into_expr()?);
+        let (var, value, body) = (var.into(), value.into_owned_expr()?, 
body.into_owned_expr()?);
         let value_dtype = support::prim_dtype(&value)?;
         support::prim_dtype(&body)?;
         let Some(var_ty) = var.ty.downcast_ref::<PrimTypeObj>() else {
diff --git a/rust/src/ir/prim/op.rs b/rust/src/ir/prim/op.rs
index 3cc62ee4a..c146e76d6 100644
--- a/rust/src/ir/prim/op.rs
+++ b/rust/src/ir/prim/op.rs
@@ -239,14 +239,21 @@ impl ExprResult {
 }
 
 impl IntoExpr for ExprResult {
+    type Output = Expr;
+
     fn into_expr(self) -> Result<Expr> {
         self.0.map(Into::into)
     }
 }
 
-impl IntoExpr for &ExprResult {
-    fn into_expr(self) -> Result<Expr> {
-        self.0.clone().map(Into::into)
+impl<'a> IntoExpr for &'a ExprResult {
+    type Output = &'a Expr;
+
+    fn into_expr(self) -> Result<&'a Expr> {
+        match &self.0 {
+            Ok(expr) => Ok(expr.as_ref()),
+            Err(error) => Err(error.clone()),
+        }
     }
 }
 
@@ -737,7 +744,7 @@ fn operands(a: impl Operand, b: impl Operand) -> 
Result<(Expr, Expr)> {
         }
         (a, b) => (on_its_own(a), on_its_own(b)),
     };
-    Ok((a.into_expr()?, b.into_expr()?))
+    Ok((a.into_owned_expr()?, b.into_owned_expr()?))
 }
 
 /// The functions without a location: each builds at the unknown location 
through its `_at`
@@ -1013,7 +1020,7 @@ pub fn cast_at(
     ty: impl Into<PrimType>,
     loc: impl Into<Location>,
 ) -> Result<Expr> {
-    let (value, ty, loc) = (value.into_expr()?, ty.into(), loc.into());
+    let (value, ty, loc) = (value.into_owned_expr()?, ty.into(), loc.into());
     let (from, to) = (support::prim_dtype(&value)?, ty.dtype);
     if from == to {
         return Ok(value);
@@ -1145,7 +1152,7 @@ pub fn match_types_at(
     b: impl IntoExpr,
     loc: impl Into<Location>,
 ) -> Result<(Expr, Expr)> {
-    let (a, b, loc) = (a.into_expr()?, b.into_expr()?, loc.into());
+    let (a, b, loc) = (a.into_owned_expr()?, b.into_owned_expr()?, loc.into());
     let (a_dtype, b_dtype) = (support::prim_dtype(&a)?, 
support::prim_dtype(&b)?);
     if a_dtype == b_dtype {
         return Ok((a, b));
@@ -1317,7 +1324,7 @@ pub fn eq_at(a: impl Operand, b: impl Operand, loc: impl 
Into<Location>) -> Resu
 
 /// Build [`neg`] at the location `loc`.
 pub fn neg_at(a: impl IntoExpr, loc: impl Into<Location>) -> Result<Expr> {
-    let (a, loc) = (a.into_expr()?, loc.into());
+    let (a, loc) = (a.into_owned_expr()?, loc.into());
     let dtype = support::prim_dtype(&a)?;
     if let Some(imm) = a.downcast_ref::<IntImmObj>() {
         let value = -imm.value.clone();
@@ -1489,7 +1496,7 @@ pub fn logical_and_at(
     b: impl IntoExpr,
     loc: impl Into<Location>,
 ) -> Result<Expr> {
-    let (a, b) = (a.into_expr()?, b.into_expr()?);
+    let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
     check_operands(&a, &b, "&&", "logical AND", "boolean", (&[support::BOOL], 
BOOL_CODES))?;
     match const_fold::and(&a, &b)? {
         Some(folded) => Ok(folded),
@@ -1499,7 +1506,7 @@ pub fn logical_and_at(
 
 /// Build [`logical_or`] at the location `loc`.
 pub fn logical_or_at(a: impl IntoExpr, b: impl IntoExpr, loc: impl 
Into<Location>) -> Result<Expr> {
-    let (a, b) = (a.into_expr()?, b.into_expr()?);
+    let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
     check_operands(&a, &b, "||", "logical OR", "boolean", (&[support::BOOL], 
BOOL_CODES))?;
     match const_fold::or(&a, &b)? {
         Some(folded) => Ok(folded),
@@ -1509,7 +1516,7 @@ pub fn logical_or_at(a: impl IntoExpr, b: impl IntoExpr, 
loc: impl Into<Location
 
 /// Build [`logical_not`] at the location `loc`.
 pub fn logical_not_at(a: impl IntoExpr, loc: impl Into<Location>) -> 
Result<Expr> {
-    let a = a.into_expr()?;
+    let a = a.into_owned_expr()?;
     check_code(&a, "a", &[support::BOOL], BOOL_CODES, || {
         format!(
             "Expected boolean argument for ! operator (logical NOT), but 
received {} of type {}",
@@ -1545,7 +1552,7 @@ fn fold_conditions(
     name: &str,
     op: fn(Expr, Expr, Location) -> Result<Expr>,
 ) -> Result<Expr> {
-    let mut conds = conds.into_iter().map(IntoExpr::into_expr);
+    let mut conds = conds.into_iter().map(IntoExpr::into_owned_expr);
     let Some(first) = conds.next() else {
         let message = format!("{name} must take at least 1 argument");
         return Err(Error::new(VALUE_ERROR, &message, ""));
@@ -1560,7 +1567,7 @@ pub fn if_then_else_at(
     false_value: impl IntoExpr,
     loc: impl Into<Location>,
 ) -> Result<Expr> {
-    let (cond, loc) = (cond.into_expr()?, loc.into());
+    let (cond, loc) = (cond.into_owned_expr()?, loc.into());
     if support::prim_dtype(&cond)?.code != support::BOOL {
         let condition = "cond.ty().MatchesCode(DLDataTypeCode::kDLBool)";
         let message = "if_then_else only accept the condition to be boolean 
type.";
@@ -1717,7 +1724,7 @@ bitwise!(
 
 /// Build [`bitwise_not`] at the location `loc`.
 pub fn bitwise_not_at(a: impl IntoExpr, loc: impl Into<Location>) -> 
Result<Expr> {
-    let a = a.into_expr()?;
+    let a = a.into_owned_expr()?;
     check_code(&a, "a", &INTEGER_OR_BOOL, INTEGER_OR_BOOL_CODES, || {
         format!(
             "Expected integer or boolean argument for ~ operator (bitwise 
NOT), but received {} \
@@ -1758,7 +1765,7 @@ macro_rules! unary {
 
             #[doc = concat!("Build [`", stringify!($name), "`] at the location 
`loc`.")]
             pub fn $at(x: impl IntoExpr, loc: impl Into<Location>) -> 
Result<Expr> {
-                $imp(x.into_expr()?, loc.into())
+                $imp(x.into_owned_expr()?, loc.into())
             }
         )+
     };
@@ -2031,12 +2038,12 @@ unlocated! {
 
 /// Build [`pow`] at the location `loc`.
 pub fn pow_at(x: impl IntoExpr, y: impl IntoExpr, loc: impl Into<Location>) -> 
Result<Expr> {
-    float_binary(x.into_expr()?, y.into_expr()?, "prim.pow", "power", 
loc.into())
+    float_binary(x.into_owned_expr()?, y.into_owned_expr()?, "prim.pow", 
"power", loc.into())
 }
 
 /// Build [`fmod`] at the location `loc`.
 pub fn fmod_at(x: impl IntoExpr, y: impl IntoExpr, loc: impl Into<Location>) 
-> Result<Expr> {
-    float_binary(x.into_expr()?, y.into_expr()?, "prim.fmod", "fmod", 
loc.into())
+    float_binary(x.into_owned_expr()?, y.into_owned_expr()?, "prim.fmod", 
"fmod", loc.into())
 }
 
 /// Build [`fma`] at the location `loc`.
@@ -2046,9 +2053,9 @@ pub fn fma_at(
     z: impl IntoExpr,
     loc: impl Into<Location>,
 ) -> Result<Expr> {
-    let x = x.into_expr()?;
+    let x = x.into_owned_expr()?;
     let ty = x.ty.clone();
-    call(Some(&ty), "prim.fma", vec![x, y.into_expr()?, z.into_expr()?], 
loc.into())
+    call(Some(&ty), "prim.fma", vec![x, y.into_owned_expr()?, 
z.into_owned_expr()?], loc.into())
 }
 
 // 
---------------------------------------------------------------------------------------------
@@ -2159,9 +2166,9 @@ macro_rules! binary_intrinsics {
                 y: impl IntoExpr,
                 loc: impl Into<Location>,
             ) -> Result<Expr> {
-                let x = x.into_expr()?;
+                let x = x.into_owned_expr()?;
                 let ty = x.ty.clone();
-                call(Some(&ty), $op, vec![x, y.into_expr()?], loc.into())
+                call(Some(&ty), $op, vec![x, y.into_owned_expr()?], loc.into())
             }
         )+
     };
diff --git a/rust/src/ir/prim/vector_expr.rs b/rust/src/ir/prim/vector_expr.rs
index b0b163556..816f5aeea 100644
--- a/rust/src/ir/prim/vector_expr.rs
+++ b/rust/src/ir/prim/vector_expr.rs
@@ -169,7 +169,8 @@ impl Ramp {
         lanes: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (base, stride, lanes) = (base.into_expr()?, stride.into_expr()?, 
lanes.into_expr()?);
+        let (base, stride, lanes) =
+            (base.into_owned_expr()?, stride.into_owned_expr()?, 
lanes.into_owned_expr()?);
         let base_ty = support::prim_type(&base)?;
         let stride_ty = support::prim_type(&stride)?;
         support::prim_dtype(&lanes)?;
@@ -276,7 +277,7 @@ impl Broadcast {
         lanes: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (value, lanes) = (value.into_expr()?, lanes.into_expr()?);
+        let (value, lanes) = (value.into_owned_expr()?, 
lanes.into_owned_expr()?);
         let value_dtype = support::prim_dtype(&value)?;
         support::prim_dtype(&lanes)?;
         if !support::is_scalar(value_dtype) {
@@ -362,9 +363,9 @@ impl Shuffle {
         loc: impl Into<Location>,
     ) -> Result<Self> {
         let vectors =
-            
vectors.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+            
vectors.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         let indices =
-            
indices.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+            
indices.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         let dtypes = vectors
             .iter()
             .map(|vector| support::prim_dtype(&vector))
diff --git a/rust/src/ir/stmt.rs b/rust/src/ir/stmt.rs
index 2847ec2a4..5ead61de4 100644
--- a/rust/src/ir/stmt.rs
+++ b/rust/src/ir/stmt.rs
@@ -244,7 +244,7 @@ impl Bind {
         value: impl IntoExpr,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (var, value) = (var.into(), value.into_expr()?);
+        let (var, value) = (var.into(), value.into_owned_expr()?);
         if !support::structural_equal(&value.ty, &var.ty)? {
             let condition = "ffi::StructuralEqual()(value->ty, var->ty)";
             return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
condition, ""));
@@ -337,7 +337,7 @@ impl AssertStmt {
         message_parts: impl IntoIterator<Item = impl Into<StringImm>>,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let (condition, error_kind) = (condition.into_expr()?, 
error_kind.into());
+        let (condition, error_kind) = (condition.into_owned_expr()?, 
error_kind.into());
         let message_parts = 
message_parts.into_iter().map(Into::into).collect::<Array<StringImm>>();
         let condition_ty = support::prim_type(&condition)?;
         if condition_ty.dtype.code != support::BOOL {
@@ -477,7 +477,7 @@ impl For {
         loc: impl Into<Location>,
     ) -> Result<Self> {
         let (var, begin, end, step) =
-            (var.into(), begin.into_expr()?, end.into_expr()?, 
step.into_expr()?);
+            (var.into(), begin.into_owned_expr()?, end.into_owned_expr()?, 
step.into_owned_expr()?);
         let var_expr: Expr = var.clone().into();
         let var_ty = support::prim_type(&var_expr)?;
         for expr in [&begin, &end, &step] {
@@ -635,7 +635,7 @@ impl While {
         body: impl Into<Stmt>,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let condition = condition.into_expr()?;
+        let condition = condition.into_owned_expr()?;
         let condition_dtype = support::prim_dtype(&condition)?;
         let body = into_body(body.into())?;
         if !support::is_scalar(condition_dtype) {
@@ -723,9 +723,9 @@ impl TensorStore {
     ) -> Result<Self> {
         static VALIDATE: support::TypeAttr =
             support::TypeAttr::new("__tvm_tensor_store_validate__");
-        let (dest, value) = (dest.into_expr()?, value.into_expr()?);
+        let (dest, value) = (dest.into_owned_expr()?, 
value.into_owned_expr()?);
         let indices =
-            
indices.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+            
indices.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         for index in indices.iter() {
             support::prim_dtype(&index)?;
         }
@@ -850,7 +850,8 @@ impl RegionStmt {
         loc: impl Into<Location>,
     ) -> Result<Self> {
         let (op, attrs, body) = (op.into(), attrs.into(), 
into_body(body.into())?);
-        let args = 
args.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let args =
+            
args.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         let body_params = 
body_params.into_iter().map(Into::into).collect::<Array<Var>>();
         let result_vars = 
result_vars.into_iter().map(Into::into).collect::<Array<Var>>();
         let name = op.name.as_str().to_string();
@@ -1110,7 +1111,7 @@ impl If {
         else_case: Option<&Stmt>,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let condition = condition.into_expr()?;
+        let condition = condition.into_owned_expr()?;
         support::prim_dtype(&condition)?;
         let then_case = into_body(then_case.into())?;
         let else_case = else_case.map(|stmt| 
into_body(stmt.clone())).transpose()?;
diff --git a/rust/src/macro/typed_expr_view.rs 
b/rust/src/macro/typed_expr_view.rs
index 3c0b4d7cd..691bfd4df 100644
--- a/rust/src/macro/typed_expr_view.rs
+++ b/rust/src/macro/typed_expr_view.rs
@@ -110,6 +110,40 @@ macro_rules! typed_expr_view {
             }
         }
 
+        // As `#[derive(ObjectRef)]` generates: the view is exactly one object 
pointer, so a
+        // cell or an object pointer that holds a `$name` borrows as `&$name` 
in place.
+        // SAFETY: the view is `repr(transparent)` over its `ObjectArc`, and 
the checked
+        // forms run `check_any_strict` first.
+        unsafe impl tvm_ffi::type_traits::RefFromAnyUnchecked for $name {
+            #[inline(always)]
+            unsafe fn ref_from_any_unchecked(cell: &TVMFFIAny) -> &Self {
+                unsafe { &*(::core::ptr::addr_of!(cell.data_union) as *const 
Self) }
+            }
+        }
+
+        unsafe impl tvm_ffi::type_traits::DowncastRefFromAny for $name {
+            #[inline(always)]
+            unsafe fn downcast_ref_from_any(cell: &TVMFFIAny) -> Option<&Self> 
{
+                unsafe { 
tvm_ffi::type_traits::downcast_ref_from_any_in_place::<Self>(cell) }
+            }
+        }
+
+        unsafe impl tvm_ffi::type_traits::DowncastRefFromObject for $name {
+            #[inline(always)]
+            unsafe fn downcast_ref_from_object(
+                place: &*mut tvm_ffi::tvm_ffi_sys::TVMFFIObject,
+            ) -> Option<&Self> {
+                unsafe { 
tvm_ffi::type_traits::downcast_ref_from_object_in_place::<Self>(place) }
+            }
+        }
+
+        impl AsRef<$name> for $name {
+            #[inline(always)]
+            fn as_ref(&self) -> &Self {
+                self
+            }
+        }
+
         tvm_ffi::impl_try_from_any!($name);
         tvm_ffi::impl_arg_into_ref!($name);
         tvm_ffi::impl_into_arg_holder_default!($name);
diff --git a/rust/src/sym/analyzer.rs b/rust/src/sym/analyzer.rs
index cc69d7938..01e0044b6 100644
--- a/rust/src/sym/analyzer.rs
+++ b/rust/src/sym/analyzer.rs
@@ -64,6 +64,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ExitOnDrop;
 use crate::ir::expr::Expr;
 use crate::ir::expr::ExprObj;
@@ -165,7 +166,7 @@ impl Analyzer {
     /// Return the constant bounds of `expr` under the known facts 
(`sym.AnalyzerConstIntBound`).
     pub fn const_int_bound(&self, expr: impl IntoExpr) -> 
Result<ConstIntBound> {
         tvm_ffi::cached_global_func!("sym.AnalyzerConstIntBound")
-            .call_tuple((self, &expr.into_expr()?))?
+            .call_tuple((self, expr.into_expr()?.borrow()))?
             .try_into()
     }
 
@@ -179,7 +180,7 @@ impl Analyzer {
     /// Return the modular set `coeff * x + base` of `expr` 
(`sym.AnalyzerModularSet`).
     pub fn modular_set(&self, expr: impl IntoExpr) -> Result<ModularSet> {
         tvm_ffi::cached_global_func!("sym.AnalyzerModularSet")
-            .call_tuple((self, &expr.into_expr()?))?
+            .call_tuple((self, expr.into_expr()?.borrow()))?
             .try_into()
     }
 
@@ -191,7 +192,7 @@ impl Analyzer {
         dom_map: Option<&Map<Var, IntSet>>,
     ) -> Result<IntSet> {
         tvm_ffi::cached_global_func!("sym.AnalyzerIntSet")
-            .call_tuple((self, &expr.into_expr()?, dom_map.cloned()))?
+            .call_tuple((self, expr.into_expr()?.borrow(), dom_map.cloned()))?
             .try_into()
     }
 
@@ -199,21 +200,21 @@ impl Analyzer {
     /// (`sym.AnalyzerSimplify`).
     pub fn simplify(&self, expr: impl IntoExpr, steps: i32) -> Result<Expr> {
         tvm_ffi::cached_global_func!("sym.AnalyzerSimplify")
-            .call_tuple((self, &expr.into_expr()?, steps))?
+            .call_tuple((self, expr.into_expr()?.borrow(), steps))?
             .try_into()
     }
 
     /// Simplify `expr` by rewrite rules (`sym.AnalyzerRewriteSimplify`).
     pub fn rewrite_simplify(&self, expr: impl IntoExpr) -> Result<Expr> {
         tvm_ffi::cached_global_func!("sym.AnalyzerRewriteSimplify")
-            .call_tuple((self, &expr.into_expr()?))?
+            .call_tuple((self, expr.into_expr()?.borrow()))?
             .try_into()
     }
 
     /// Simplify `expr` into canonical form (`sym.AnalyzerCanonicalSimplify`).
     pub fn canonical_simplify(&self, expr: impl IntoExpr) -> Result<Expr> {
         tvm_ffi::cached_global_func!("sym.AnalyzerCanonicalSimplify")
-            .call_tuple((self, &expr.into_expr()?))?
+            .call_tuple((self, expr.into_expr()?.borrow()))?
             .try_into()
     }
 
@@ -242,14 +243,14 @@ impl Analyzer {
     /// Return whether `expr` is provably true (`sym.AnalyzerCanProve`).
     pub fn can_prove(&self, expr: impl IntoExpr, strength: ProofStrength) -> 
Result<bool> {
         tvm_ffi::cached_global_func!("sym.AnalyzerCanProve")
-            .call_tuple((self, &expr.into_expr()?, strength as i32))?
+            .call_tuple((self, expr.into_expr()?.borrow(), strength as i32))?
             .try_into()
     }
 
     /// Return whether `lhs == rhs` is provably true 
(`sym.AnalyzerCanProveEqual`).
     pub fn can_prove_equal(&self, lhs: impl IntoExpr, rhs: impl IntoExpr) -> 
Result<bool> {
         tvm_ffi::cached_global_func!("sym.AnalyzerCanProveEqual")
-            .call_tuple((self, &lhs.into_expr()?, &rhs.into_expr()?))?
+            .call_tuple((self, lhs.into_expr()?.borrow(), 
rhs.into_expr()?.borrow()))?
             .try_into()
     }
 
@@ -257,7 +258,7 @@ impl Analyzer {
     /// (`sym.AnalyzerTryCompare`).
     pub fn try_compare(&self, lhs: impl IntoExpr, rhs: impl IntoExpr) -> 
Result<CompareResult> {
         let result: i64 = 
tvm_ffi::cached_global_func!("sym.AnalyzerTryCompare")
-            .call_tuple((self, &lhs.into_expr()?, &rhs.into_expr()?))?
+            .call_tuple((self, lhs.into_expr()?.borrow(), 
rhs.into_expr()?.borrow()))?
             .try_into()?;
         result.try_into()
     }
diff --git a/rust/src/sym/int_set.rs b/rust/src/sym/int_set.rs
index 6d97f1f4a..68beb1b5c 100644
--- a/rust/src/sym/int_set.rs
+++ b/rust/src/sym/int_set.rs
@@ -52,6 +52,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::expr::Expr;
 use crate::ir::expr::IntoExpr;
 use crate::ir::expr::Range;
@@ -106,14 +107,14 @@ impl IntSet {
     /// (`sym.IntSetVector`).
     pub fn vector(vec: impl IntoExpr) -> Result<Self> {
         tvm_ffi::cached_global_func!("sym.IntSetVector")
-            .call_tuple((&vec.into_expr()?,))?
+            .call_tuple((vec.into_expr()?.borrow(),))?
             .try_into()
     }
 
     /// Return the set that holds only `point` (`sym.IntSetSinglePoint`).
     pub fn single_point(point: impl IntoExpr) -> Result<Self> {
         tvm_ffi::cached_global_func!("sym.IntSetSinglePoint")
-            .call_tuple((&point.into_expr()?,))?
+            .call_tuple((point.into_expr()?.borrow(),))?
             .try_into()
     }
 }
@@ -185,7 +186,7 @@ pub fn estimate_region_lower_bound(
 ) -> Result<Option<Array<IntSet>>> {
     let region = region.into_iter().map(Into::into).collect::<Array<Range>>();
     tvm_ffi::cached_global_func!("sym.EstimateRegionLowerBound")
-        .call_tuple((&region, var_dom, &predicate.into_expr()?, 
analyzer.cloned()))?
+        .call_tuple((&region, var_dom, predicate.into_expr()?.borrow(), 
analyzer.cloned()))?
         .try_into()
 }
 
@@ -199,7 +200,7 @@ pub fn estimate_region_strict_bound(
 ) -> Result<Option<Array<IntSet>>> {
     let region = region.into_iter().map(Into::into).collect::<Array<Range>>();
     tvm_ffi::cached_global_func!("sym.EstimateRegionStrictBound")
-        .call_tuple((&region, var_dom, &predicate.into_expr()?, 
analyzer.cloned()))?
+        .call_tuple((&region, var_dom, predicate.into_expr()?.borrow(), 
analyzer.cloned()))?
         .try_into()
 }
 
@@ -213,7 +214,7 @@ pub fn estimate_region_upper_bound(
 ) -> Result<Array<IntSet>> {
     let region = region.into_iter().map(Into::into).collect::<Array<Range>>();
     tvm_ffi::cached_global_func!("sym.EstimateRegionUpperBound")
-        .call_tuple((&region, var_dom, &predicate.into_expr()?, 
analyzer.cloned()))?
+        .call_tuple((&region, var_dom, predicate.into_expr()?.borrow(), 
analyzer.cloned()))?
         .try_into()
 }
 
@@ -244,6 +245,6 @@ pub fn deduce_bound(
     relax_map: &Map<Var, IntSet>,
 ) -> Result<IntSet> {
     tvm_ffi::cached_global_func!("sym.DeduceBound")
-        .call_tuple((&var.into_expr()?, &cond.into_expr()?, hint_map, 
relax_map))?
+        .call_tuple((var.into_expr()?.borrow(), cond.into_expr()?.borrow(), 
hint_map, relax_map))?
         .try_into()
 }
diff --git a/rust/src/sym/iter_affine_map.rs b/rust/src/sym/iter_affine_map.rs
index 84ce1ea6b..dfabbe9ba 100644
--- a/rust/src/sym/iter_affine_map.rs
+++ b/rust/src/sym/iter_affine_map.rs
@@ -58,6 +58,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::expr::Expr;
 use crate::ir::expr::ExprObj;
 use crate::ir::expr::IntoExpr;
@@ -317,9 +318,9 @@ impl IterSplitExpr {
         tvm_ffi::cached_global_func!("sym.IterSplitExpr")
             .call_tuple((
                 &source.into(),
-                &lower_factor.into_expr()?,
-                &extent.into_expr()?,
-                &scale.into_expr()?,
+                lower_factor.into_expr()?.borrow(),
+                extent.into_expr()?.borrow(),
+                scale.into_expr()?.borrow(),
             ))?
             .try_into()
     }
@@ -365,8 +366,9 @@ pub fn detect_iter_map(
     analyzer: Option<&Analyzer>,
     simplify_trivial_iterators: bool,
 ) -> Result<IterMapResult> {
-    let indices = 
indices.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
-    let (predicate, analyzer) = (predicate.into_expr()?, analyzer.cloned());
+    let indices =
+        
indices.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
+    let (predicate, analyzer) = (predicate.into_owned_expr()?, 
analyzer.cloned());
     tvm_ffi::cached_global_func!("sym.DetectIterMap")
         .call_packed(&[
             AnyView::from(&indices),
@@ -387,7 +389,7 @@ pub fn normalize_to_iter_sum(
     analyzer: Option<&Analyzer>,
 ) -> Result<IterSumExpr> {
     tvm_ffi::cached_global_func!("sym.NormalizeToIterSum")
-        .call_tuple((&index.into_expr()?, input_iters, analyzer.cloned()))?
+        .call_tuple((index.into_expr()?.borrow(), input_iters, 
analyzer.cloned()))?
         .try_into()
 }
 
@@ -401,8 +403,9 @@ pub fn iter_map_simplify(
     analyzer: Option<&Analyzer>,
     simplify_trivial_iterators: bool,
 ) -> Result<Array<Expr>> {
-    let indices = 
indices.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
-    let (predicate, analyzer) = (predicate.into_expr()?, analyzer.cloned());
+    let indices =
+        
indices.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
+    let (predicate, analyzer) = (predicate.into_owned_expr()?, 
analyzer.cloned());
     tvm_ffi::cached_global_func!("sym.IterMapSimplify")
         .call_packed(&[
             AnyView::from(&indices),
@@ -418,7 +421,7 @@ pub fn iter_map_simplify(
 /// Return the plain expression of the iterator map `expr` 
(`sym.NormalizeIterMapToExpr`).
 pub fn normalize_iter_map_to_expr(expr: impl IntoExpr) -> Result<Expr> {
     tvm_ffi::cached_global_func!("sym.NormalizeIterMapToExpr")
-        .call_tuple((&expr.into_expr()?,))?
+        .call_tuple((expr.into_expr()?.borrow(),))?
         .try_into()
 }
 
@@ -437,9 +440,9 @@ pub fn subspace_divide(
     simplify_trivial_iterators: bool,
 ) -> Result<Array<Array<IterMark>>> {
     let bindings =
-        
bindings.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        
bindings.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
     let sub_iters = 
sub_iters.into_iter().map(Into::into).collect::<Array<Var>>();
-    let (predicate, analyzer) = (predicate.into_expr()?, analyzer.cloned());
+    let (predicate, analyzer) = (predicate.into_owned_expr()?, 
analyzer.cloned());
     tvm_ffi::cached_global_func!("sym.SubspaceDivide")
         .call_packed(&[
             AnyView::from(&bindings),
@@ -460,7 +463,8 @@ pub fn inverse_affine_iter_map(
     outputs: impl IntoIterator<Item = impl IntoExpr>,
 ) -> Result<Map<Var, Expr>> {
     let iter_map = 
iter_map.into_iter().map(Into::into).collect::<Array<IterSumExpr>>();
-    let outputs = 
outputs.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+    let outputs =
+        
outputs.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
     tvm_ffi::cached_global_func!("sym.InverseAffineIterMap")
         .call_tuple((&iter_map, &outputs))?
         .try_into()
diff --git a/rust/src/tirx/analysis/analysis.rs 
b/rust/src/tirx/analysis/analysis.rs
index b7f8f88d2..f91d282e4 100644
--- a/rust/src/tirx/analysis/analysis.rs
+++ b/rust/src/tirx/analysis/analysis.rs
@@ -48,6 +48,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::expr::Expr;
 use crate::ir::expr::IntoExpr;
 use crate::ir::expr::Var;
@@ -105,7 +106,7 @@ pub fn detect_linear_equation(
 ) -> Result<Array<Expr>> {
     let vars = vars.into_iter().map(Into::into).collect::<Array<Var>>();
     tvm_ffi::cached_global_func!("tirx.analysis.DetectLinearEquation")
-        .call_tuple((&expr.into_expr()?, &vars))?
+        .call_tuple((expr.into_expr()?.borrow(), &vars))?
         .try_into()
 }
 
@@ -118,6 +119,6 @@ pub fn detect_clip_bound(
 ) -> Result<Array<Option<Expr>>> {
     let vars = vars.into_iter().map(Into::into).collect::<Array<Var>>();
     tvm_ffi::cached_global_func!("tirx.analysis.DetectClipBound")
-        .call_tuple((&expr.into_expr()?, &vars))?
+        .call_tuple((expr.into_expr()?.borrow(), &vars))?
         .try_into()
 }
diff --git a/rust/src/tirx/index_map.rs b/rust/src/tirx/index_map.rs
index 10fd495ba..fee79a12e 100644
--- a/rust/src/tirx/index_map.rs
+++ b/rust/src/tirx/index_map.rs
@@ -140,7 +140,7 @@ impl IndexMap {
         analyzer: Option<&Analyzer>,
     ) -> Result<Array<Expr>> {
         let indices =
-            
indices.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+            
indices.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("tirx.IndexMapMapIndices")
             .call_tuple((self, &indices, analyzer.cloned()))?
             .try_into()
@@ -156,7 +156,8 @@ impl IndexMap {
         shape: impl IntoIterator<Item = impl IntoExpr>,
         analyzer: Option<&Analyzer>,
     ) -> Result<Array<Expr>> {
-        let shape = 
shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let shape =
+            
shape.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("tirx.IndexMapMapShape")
             .call_tuple((self, &shape, analyzer.cloned()))?
             .try_into()
diff --git a/rust/src/tirx/layout.rs b/rust/src/tirx/layout.rs
index e2eaf2e27..6b4d3e409 100644
--- a/rust/src/tirx/layout.rs
+++ b/rust/src/tirx/layout.rs
@@ -55,6 +55,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::expr::Expr;
 use crate::ir::expr::IntoExpr;
 use crate::ir::support;
@@ -142,7 +143,8 @@ impl Layout {
         coord: impl IntoIterator<Item = impl IntoExpr>,
         analyzer: &Analyzer,
     ) -> Result<Map<String, Expr>> {
-        let coord = 
coord.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let coord =
+            
coord.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("tirx.LayoutApply")
             .call_tuple((self, &coord, analyzer))?
             .try_into()
@@ -156,7 +158,7 @@ impl Layout {
         analyzer: &Analyzer,
     ) -> Result<Map<String, Expr>> {
         tvm_ffi::cached_global_func!("tirx.LayoutApplyLinear")
-            .call_tuple((self, &coord.into_expr()?, analyzer))?
+            .call_tuple((self, coord.into_expr()?.borrow(), analyzer))?
             .try_into()
     }
 
@@ -168,8 +170,10 @@ impl Layout {
         shape: impl IntoIterator<Item = impl IntoExpr>,
         analyzer: &Analyzer,
     ) -> Result<Map<String, Expr>> {
-        let coord = 
coord.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
-        let shape = 
shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let coord =
+            
coord.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
+        let shape =
+            
shape.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("tirx.LayoutApplyWithShape")
             .call_tuple((self, &coord, &shape, analyzer))?
             .try_into()
@@ -194,10 +198,14 @@ impl Layout {
         inner_shape: impl IntoIterator<Item = impl IntoExpr>,
     ) -> Result<Layout> {
         let outer = outer.into();
-        let outer_shape =
-            
outer_shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
-        let inner_shape =
-            
inner_shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let outer_shape = outer_shape
+            .into_iter()
+            .map(IntoExpr::into_owned_expr)
+            .collect::<Result<Array<Expr>>>()?;
+        let inner_shape = inner_shape
+            .into_iter()
+            .map(IntoExpr::into_owned_expr)
+            .collect::<Result<Array<Expr>>>()?;
         tvm_ffi::cached_global_func!("tirx.LayoutTile")
             .call_tuple((self, &outer, &outer_shape, &inner_shape))?
             .try_into()
@@ -291,7 +299,8 @@ impl TileLayout {
         &self,
         shape: impl IntoIterator<Item = impl IntoExpr>,
     ) -> Result<(TileLayout, Array<i64>)> {
-        let shape = 
shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let shape =
+            
shape.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         let pair: Array<Any> = 
tvm_ffi::cached_global_func!("tirx.TileLayoutGroup")
             .call_tuple((self, &shape))?
             .try_into()?;
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index 23c7fc874..b4c175e91 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -185,7 +185,7 @@ pub fn logaddexp(a: impl IntoExpr, b: impl IntoExpr) -> 
Result<Expr> {
 
 /// Build [`logaddexp`] with the casts at `loc`.
 pub fn logaddexp_at(a: impl IntoExpr, b: impl IntoExpr, loc: impl 
Into<Location>) -> Result<Expr> {
-    let (a, b) = (a.into_expr()?, b.into_expr()?);
+    let (a, b) = (a.into_owned_expr()?, b.into_owned_expr()?);
     for (operand, side) in [(&a, "a"), (&b, "b")] {
         if support::prim_dtype(operand)?.code != support::FLOAT {
             let condition = 
format!("{side}.ty().MatchesCode(DLDataTypeCode::kDLFloat)");
@@ -228,7 +228,7 @@ pub fn reinterpret_at(
     value: impl IntoExpr,
     loc: impl Into<Location>,
 ) -> Result<Expr> {
-    let (ty, value, loc) = (ty.into(), value.into_expr()?, loc.into());
+    let (ty, value, loc) = (ty.into(), value.into_owned_expr()?, loc.into());
     let target_is_pointer = ty.downcast_ref::<PtrTypeObj>().is_some();
     let source_is_pointer = value.ty.downcast_ref::<PtrTypeObj>().is_some();
     let type_error = |condition: &str, message: std::string::String| {
@@ -350,7 +350,7 @@ pub fn fast_erf_float_expr(arg: impl IntoExpr, bits: u8) -> 
Result<Expr> {
         -2.13374055278905e-04,
         -1.45660718464996e-05,
     ];
-    let x = arg.into_expr()?.min(constant(4.0)).max(constant(-4.0));
+    let x = arg.into_owned_expr()?.min(constant(4.0)).max(constant(-4.0));
     let x2 = &x * &x;
     // Horner's rule from the highest coefficient down, as C++ writes it out.
     let mut p = &x2 * constant(alpha[6]) + constant(alpha[5]);
diff --git a/rust/src/tirx/tensor.rs b/rust/src/tirx/tensor.rs
index edc26732b..1b3a5231f 100644
--- a/rust/src/tirx/tensor.rs
+++ b/rust/src/tirx/tensor.rs
@@ -191,7 +191,8 @@ impl TensorType {
         layout: Option<&Layout>,
         loc: impl Into<Location>,
     ) -> Result<Self> {
-        let shape = 
shape.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let shape =
+            
shape.into_iter().map(IntoExpr::into_owned_expr).collect::<Result<Array<Expr>>>()?;
         for extent in shape.iter() {
             support::prim_dtype(&extent)?;
         }
diff --git a/rust/src/tirx/tile/dispatch.rs b/rust/src/tirx/tile/dispatch.rs
index 8dda2c949..495fb332d 100644
--- a/rust/src/tirx/tile/dispatch.rs
+++ b/rust/src/tirx/tile/dispatch.rs
@@ -200,8 +200,10 @@ impl DispatchContext {
         annotations: &Map<String, Any>,
     ) -> Result<()> {
         let tensor = tensor.into();
-        let allocated_addr =
-            
allocated_addr.into_iter().map(IntoExpr::into_expr).collect::<Result<Array<Expr>>>()?;
+        let allocated_addr = allocated_addr
+            .into_iter()
+            .map(IntoExpr::into_owned_expr)
+            .collect::<Result<Array<Expr>>>()?;
         
tvm_ffi::cached_global_func!("tirx.DispatchContextAddAllocTensor").call_tuple((
             self,
             &tensor,
diff --git a/rust/src/tirx/tile/instruction.rs 
b/rust/src/tirx/tile/instruction.rs
index a79ae6df2..55d8d08ec 100644
--- a/rust/src/tirx/tile/instruction.rs
+++ b/rust/src/tirx/tile/instruction.rs
@@ -36,6 +36,7 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 
 // tvm-ffi-stubgen(begin): import-section
+use std::borrow::Borrow;
 use crate::ir::attrs::Attrs;
 use crate::ir::attrs::AttrsObj;
 use crate::ir::expr::Call;
@@ -206,7 +207,7 @@ tvm_ffi::impl_object_upcast!(TrnAttrs => Attrs);
 /// Returns an error if `value` is neither.
 pub fn as_tensor_region(value: impl IntoExpr) -> Result<TensorRegion> {
     tvm_ffi::cached_global_func!("tirx.AsTensorRegion")
-        .call_tuple((&value.into_expr()?,))?
+        .call_tuple((value.into_expr()?.borrow(),))?
         .try_into()
 }
 

Reply via email to