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 a6e9f0ced [Rust] Bump tvm-ffi to the locked-down Rust API 
(apache/tvm-ffi#937, #938) (#208)
a6e9f0ced is described below

commit a6e9f0ced84f9da1896afb3151926160a7617647
Author: Tianqi Chen <[email protected]>
AuthorDate: Thu Nov 5 23:21:44 2026 -0500

    [Rust] Bump tvm-ffi to the locked-down Rust API (apache/tvm-ffi#937, #938) 
(#208)
    
    Bumps tvm-ffi to the Rust API that is being locked down:
    apache/tvm-ffi#937 (casts and lookups aligned with std and C++) and
    apache/tvm-ffi#938 (thread safety only where it holds; `TryFrom` between
    references). Migrates TIRx to it.
    
    ## Thread safety
    
    - **Mutable classes are declared so:** `IRModule`, `PassContext`,
    `IterMapResult`, `TargetKind` and `Target` set `_type_mutable = true`,
    as their handles already hand out mutable pointers.
    - **Every other class is `ImmutableObject`:** the regenerated bindings
    mark each class C++ does not declare mutable. Expressions, statements,
    types and functions are now `Send + Sync`; mutable classes and erased
    `ObjectRef`/`Any` handles stay single-threaded.
    - **`Op` is `ImmutableObject` by hand**, as its binding is hand-written:
    registration counts as initialization. Its validator slot is written
    only then, so `Op` crosses threads too.
    
    ## API migration
    
    - **Bindings regenerated:** upcasts use `unsafe_impl_object_upcast!`.
    - **Casts:**
    - `ObjectRefCast` imports are gone, since `same_as`, `downcast_ref` and
    `downcast` are on `ObjectRefCore`.
      - Downcasts with `?` use `try_into()`.
    - The typed views' hand-written `TryFrom` impls are replaced by the
    generated ones.
    - `try_as` becomes `TryFrom`, or `downcast_ref` where a value is only
    inspected.
    - **Typed views implement `Debug`**, as derived references do.
    - **Renames:** `ObjectArc::as_ptr`, `VisitInterrupt::new`,
    `reflection::get_method`, `TypeAttrColumn` for type attributes,
    `Error::from_panic`, and `implements_function_with_imports`.
    - **`Array::iter()` borrows** each element.
    - **Callbacks are `Send + Sync`:** one test now captures a typed value
    instead of an `Any`.
    - The IR guide's cast section teaches `try_into` and `downcast`.
---
 3rdparty/tvm-ffi                                   |   2 +-
 docs/ir_guides/working_with_ir_in_rust.rst         |  36 ++---
 examples/extension_guides/rust/src/lib.rs          |  12 +-
 examples/ir_guides/rust/src/lib.rs                 |  17 ++-
 include/tvm/ir/module.h                            |   2 +
 include/tvm/ir/transform.h                         |   2 +
 include/tvm/sym/iter_affine_map.h                  |   2 +
 include/tvm/target/target.h                        |   2 +
 include/tvm/target/target_kind.h                   |   2 +
 rust/examples/define_ir.rs                         |   4 +-
 rust/src/backend/cuda/op.rs                        |  18 ++-
 rust/src/codegen.rs                                |   2 +-
 rust/src/ir/attrs.rs                               |  12 +-
 rust/src/ir/base.rs                                |   6 +-
 rust/src/ir/expr.rs                                | 140 ++++++++++++------
 rust/src/ir/function.rs                            |   6 +-
 rust/src/ir/global_info.rs                         |   4 +
 rust/src/ir/location.rs                            |  26 +++-
 rust/src/ir/op.rs                                  |  33 ++++-
 rust/src/ir/op_def.rs                              |   8 +-
 rust/src/ir/prim/expr.rs                           | 162 +++++++++++++++++----
 rust/src/ir/prim/vector_expr.rs                    |  18 ++-
 rust/src/ir/stmt.rs                                |  90 +++++++++---
 rust/src/ir/support.rs                             |  12 +-
 rust/src/ir/transform/core.rs                      |  20 ++-
 rust/src/ir/transform/instrument.rs                |   2 +-
 rust/src/ir/type.rs                                |  64 ++++++--
 rust/src/macro/def_expr_op_overloads.rs            |   2 +-
 rust/src/macro/typed_expr_view.rs                  |  28 ++--
 rust/src/runtime.rs                                |   4 +
 rust/src/sym/analyzer.rs                           |  18 ++-
 rust/src/sym/constraint.rs                         |  28 +++-
 rust/src/sym/int_set.rs                            |   6 +-
 rust/src/sym/iter_affine_map.rs                    |  18 ++-
 rust/src/tirx/function.rs                          |   6 +-
 rust/src/tirx/index_map.rs                         |   6 +-
 rust/src/tirx/layout.rs                            |  20 ++-
 rust/src/tirx/op.rs                                |   6 +-
 .../structural_walk_with_constraint_context.rs     |   4 +-
 rust/src/tirx/tensor.rs                            |   6 +-
 rust/src/tirx/thread_scope.rs                      |   4 +
 rust/src/tirx/tile/dispatch.rs                     |  14 +-
 rust/src/tirx/tile/instruction.rs                  |  12 +-
 rust/src/tirx/transform/function_pass.rs           |   6 +-
 rust/src/tirx/transform/transform.rs               |  12 +-
 rust/src/tirx/type.rs                              |   6 +-
 rust/tests/ir/expr.rs                              |  35 +++--
 rust/tests/ir/op_def.rs                            |  14 +-
 rust/tests/ir/prim.rs                              |   6 +-
 rust/tests/ir/prim/op.rs                           |  35 +++--
 rust/tests/sym/int_set.rs                          |   6 +-
 rust/tests/sym/iter_affine_map.rs                  |  10 +-
 rust/tests/target/main.rs                          |   6 +-
 rust/tests/tirx/main.rs                            |   8 +-
 rust/tests/tirx/op.rs                              |   6 +-
 .../tirx/structural_map_with_constraint_context.rs |   2 +-
 .../structural_mutate_with_constraint_context.rs   |   2 +-
 .../structural_visit_with_constraint_context.rs    |  12 +-
 .../structural_walk_with_constraint_context.rs     |   2 +-
 rust/tests/tirx/tile/dispatch.rs                   |   4 +-
 60 files changed, 770 insertions(+), 288 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index 34fafd9ee..5fdba0f4c 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit 34fafd9ee80003a725851dd9adc8cd186fb0daf3
+Subproject commit 5fdba0f4c562465435ab4a860da602ab80f6820d
diff --git a/docs/ir_guides/working_with_ir_in_rust.rst 
b/docs/ir_guides/working_with_ir_in_rust.rst
index 334d56146..5f7e1fd47 100644
--- a/docs/ir_guides/working_with_ir_in_rust.rst
+++ b/docs/ir_guides/working_with_ir_in_rust.rst
@@ -111,7 +111,7 @@ key constructs are:
        directly, clone cheaply
    * - ``.into()``
      - Convert to a parent type
-   * - |try_cast|_
+   * - |downcast|_
      - Convert to a child type, with a checked ``Result``
    * - |match_any!|_
      - Branch on the node type
@@ -146,7 +146,7 @@ its type.
 As in C++, the operators take a ``PrimExpr``, an expression whose type is a
 ``PrimType``. ``x`` is a ``PrimVar``, a variable of a primitive type, because a
 plain ``Var`` may have any type: it takes part in arithmetic only after
-``downcast_ref::<PrimVar>()`` or ``try_cast::<PrimVar>()`` checks it, and 
``&var + 1``
+``downcast_ref::<PrimVar>()`` or ``downcast::<PrimVar>()`` checks it, and 
``&var + 1``
 does not compile.
 
 An operator can still fail, for example on mismatched types, so
@@ -162,7 +162,7 @@ Objects and Managed References
 **Every node type comes as a pair: an underlying object and a named
 managed reference.** The top of
 ``x * 2 + 1`` is an addition, so let's convert the expression to ``Add``
-with ``try_cast``, which the next subsection explains, and read its two
+with ``try_into``, which the next subsection explains, and read its two
 operands:
 
 .. literalinclude:: ../../examples/ir_guides/rust/src/lib.rs
@@ -229,9 +229,10 @@ Casts
 **Casts move a managed reference up and down the type hierarchy.** A
 function that takes any expression wants an ``Expr``, while reading the
 operands needs an ``Add``. ``.into()`` converts to a parent type through
-``From``/``Into`` and cannot fail. ``.try_cast()`` converts to a child
-type: like ``TryFrom``, it checks the node's actual type and returns a
-``Result``.
+``From``/``Into`` and cannot fail. ``.try_into()``, or ``T::try_from``,
+converts to a child type: it checks the node's actual type and fails with a
+``TypeError`` naming both types, ready for ``?``. ``try_from(&value)``
+borrows and clones only on success.
 
 .. literalinclude:: ../../examples/ir_guides/rust/src/lib.rs
    :language: rust
@@ -248,17 +249,18 @@ The output is:
 ``value`` is an ``Expr`` again, but the node behind it is still an
 addition, so the cast back to ``Add`` succeeds.
 
-What if we guess wrong? The cast to ``Mul`` returns an ``Err``, and its
-message names both type keys: the actual type ``prim.Add`` and the
-requested ``prim.Mul``.
+What if we guess wrong? The cast to ``Mul`` fails with a ``TypeError`` that
+names both type keys: the actual type ``prim.Add`` and the requested
+``prim.Mul``.
 
 .. note::
 
-   ``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::<Add>()``
-   returns ``Option<&Add>`` without consuming or cloning the reference.
-   ``Any`` and ``AnyView`` provide the same borrowed downcast.
+   To try several types in turn, ``value.downcast::<Add>()`` takes the
+   reference and hands it back unchanged in ``Err``, as
+   ``Box<dyn Any>::downcast`` does in the standard library. 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
 many; that is what matching is for.
@@ -775,7 +777,7 @@ Quick Reference
      - What it does
    * - |match_any!|_
      - Branches on the node type; the first matching arm runs
-   * - |try_cast|_, |downcast_ref|_
+   * - |downcast|_, |downcast_ref|_
      - Downcasts to one type, as a managed reference in a ``Result`` or a
        borrowed object in an ``Option``
    * - ``.into()``
@@ -813,8 +815,8 @@ Quick Reference
 .. _Add: ../reference/rust/generated/tvm_tirx/ir/prim/expr/struct.Add.html
 .. |script| replace:: ``script``
 .. _script: ../reference/rust/generated/tvm_tirx/script/fn.script.html
-.. |try_cast| replace:: ``.try_cast()``
-.. _try_cast: 
../reference/rust/generated/tvm_ffi/object/trait.ObjectRefCast.html
+.. |downcast| replace:: ``.downcast()``
+.. _downcast: 
../reference/rust/generated/tvm_ffi/object/trait.ObjectRefCore.html#method.downcast
 .. |structural_walk| replace:: ``structural_walk``
 .. _structural_walk: 
../reference/rust/generated/tvm_ffi/extra/structural_walk/fn.structural_walk.html
 .. |structural_map| replace:: ``structural_map``
diff --git a/examples/extension_guides/rust/src/lib.rs 
b/examples/extension_guides/rust/src/lib.rs
index 533addb7b..c99b7844a 100644
--- a/examples/extension_guides/rust/src/lib.rs
+++ b/examples/extension_guides/rust/src/lib.rs
@@ -30,8 +30,8 @@ use tvm_tirx::ir::{
 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, UnchangedOr, WalkOrder,
+    self, def_object_ref, dispatch, rs_object, Any, AnyView, Error, 
ObjectRefCore, Result,
+    Unchanged, UnchangedOr, WalkOrder,
 };
 
 // begin type
@@ -70,7 +70,7 @@ fn check_call(call: &CallObj, count: usize) -> Result<()> {
 /// `bfloat16`.
 fn float_part(ty: &Type) -> Result<PrimType> {
     let elem: PrimType =
-        ty.clone().try_cast().map_err(|_| type_error("expected a scalar 
float"))?;
+        ty.clone().downcast().map_err(|_| type_error("expected a scalar 
float"))?;
     let bits = elem.dtype.bits;
     let float = elem.dtype == dtype::float(bits) || elem.dtype == 
dtype::bfloat(bits);
     require(float, "expected a scalar float")?;
@@ -89,13 +89,13 @@ fn infer_complex(call: &CallObj) -> Result<Type> {
 // end inference
 
 fn complex_arg(call: &CallObj, index: usize) -> Result<ComplexType> {
-    let op: Op = call.op.clone().try_cast()?;
+    let op = Op::try_from(&call.op)?;
     let ty: ComplexType = call
         .args
         .get(index)?
         .ty
         .clone()
-        .try_cast()
+        .downcast()
         .map_err(|_| type_error(&format!("{} expects a complex value", 
op.name.as_str())))?;
     float_part(&ty.dtype.clone().into())?;
     Ok(ty)
@@ -115,7 +115,7 @@ fn infer_part(call: &CallObj) -> Result<Type> {
 
 // Explicit result types bypass inference, so validation checks the inputs and 
result again.
 fn validate_call(call: &CallObj) -> Result<()> {
-    let op: Op = call.op.clone().try_cast()?;
+    let op = Op::try_from(&call.op)?;
     let expected = match op.name.as_str() {
         "my_ext.complex" => infer_complex(call)?,
         "my_ext.cmul" => infer_cmul(call)?,
diff --git a/examples/ir_guides/rust/src/lib.rs 
b/examples/ir_guides/rust/src/lib.rs
index 412fd962d..21c4e7418 100644
--- a/examples/ir_guides/rust/src/lib.rs
+++ b/examples/ir_guides/rust/src/lib.rs
@@ -35,8 +35,8 @@ use 
tvm_tirx::tirx::structural_walk_with_constraint_context::structural_walk_wit
 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,
-    UnchangedOr, VisitInterrupt, WalkOrder, WalkResult,
+    ObjectRefCore, Result, StructuralMutatorView, StructuralVisitorView, 
Unchanged, UnchangedOr,
+    VisitInterrupt, WalkOrder, WalkResult,
 };
 
 /// A Python list of strings.
@@ -63,7 +63,7 @@ fn construct() -> Result<tvm_ffi::String> {
 // [objects.begin]
 fn objects() -> Result<Strings> {
     let (_, expr) = x_times_2_plus_1()?;
-    let add: Add = expr.try_cast()?;
+    let add: Add = expr.try_into()?;
     let alias = add.clone();
     Ok(lines([
         format!("a = {}", script(&add.a)?.as_str()),
@@ -76,11 +76,12 @@ fn objects() -> Result<Strings> {
 // [casts.begin]
 fn casts() -> Result<Strings> {
     let (_, expr) = x_times_2_plus_1()?;
-    let add: Add = expr.try_cast()?;
+    let add: Add = expr.try_into()?;
     let value: Expr = add.into();
-    let again: Add = value.clone().try_cast()?;
+    let again = Add::try_from(&value)?;
     let mut out = vec![format!("back to Add: {}", script(&again)?.as_str())];
-    if let Err(err) = value.try_cast::<Mul>() {
+    // A wrong guess is a `TypeError` naming both types.
+    if let Err(err) = Mul::try_from(&value) {
         out.push(format!("as Mul: {}", err.message()));
     }
     Ok(lines(out))
@@ -151,7 +152,7 @@ fn print() -> Result<tvm_ffi::String> {
 fn written_tensors(func: Function) -> Result<Strings> {
     let mut writes = Vec::new();
     let on_store = |store: TensorStore| -> Result<WalkResult> {
-        let dest: Var = store.dest.clone().try_cast()?;
+        let dest = Var::try_from(&store.dest)?;
         writes.push(dest.name.clone());
         Ok(WalkResult::Advance)
     };
@@ -175,7 +176,7 @@ fn first_write(func: Function) -> 
Result<Option<TensorStore>> {
 fn specialize(func: Function, n: Var, value: i64) -> Result<Function> {
     let specialize_var = |var: Var| -> Result<Expr> {
         if var.same_as(&n) {
-            let ty: PrimType = var.ty.clone().try_cast()?;
+            let ty = PrimType::try_from(&var.ty)?;
             return Ok(make_const(ty, value)?.into());
         }
         Ok(var.into())
diff --git a/include/tvm/ir/module.h b/include/tvm/ir/module.h
index dcb417482..ca3cb2738 100644
--- a/include/tvm/ir/module.h
+++ b/include/tvm/ir/module.h
@@ -278,6 +278,8 @@ class IRModuleObj : public ffi::Object {
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
 
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.IRModule", IRModuleObj, ffi::Object);
+  // Shared instances are mutated in place, so the class is mutable.
+  static constexpr bool _type_mutable = true;
 
  private:
   friend class IRModule;
diff --git a/include/tvm/ir/transform.h b/include/tvm/ir/transform.h
index 27fe8f922..2ce1d5915 100644
--- a/include/tvm/ir/transform.h
+++ b/include/tvm/ir/transform.h
@@ -663,6 +663,8 @@ class PassContextObj : public ffi::Object {
         .def_ro("config", &PassContextObj::config);
   }
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.PassContext", PassContextObj, 
ffi::Object);
+  // Shared instances are mutated in place, so the class is mutable.
+  static constexpr bool _type_mutable = true;
 };
 
 inline PassContext::PassContext(ffi::UnsafeInit tag, 
ffi::ObjectPtr<PassContextObj> n)
diff --git a/include/tvm/sym/iter_affine_map.h 
b/include/tvm/sym/iter_affine_map.h
index 4ad3c4374..182253f61 100644
--- a/include/tvm/sym/iter_affine_map.h
+++ b/include/tvm/sym/iter_affine_map.h
@@ -300,6 +300,8 @@ class IterMapResultObj : public ffi::Object {
         .def_ro("padding_predicate", &IterMapResultObj::padding_predicate);
   }
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("sym.IterMapResult", IterMapResultObj, 
ffi::Object);
+  // Shared instances are mutated in place, so the class is mutable.
+  static constexpr bool _type_mutable = true;
 };
 
 /*!
diff --git a/include/tvm/target/target.h b/include/tvm/target/target.h
index 7843098fb..b6d4cc343 100644
--- a/include/tvm/target/target.h
+++ b/include/tvm/target/target.h
@@ -145,6 +145,8 @@ class TargetObj : public ffi::Object {
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindTreeNode;
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("target.Target", TargetObj, ffi::Object);
+  // Shared instances are mutated in place, so the class is mutable.
+  static constexpr bool _type_mutable = true;
 
  private:
   /*! \brief Internal string repr. */
diff --git a/include/tvm/target/target_kind.h b/include/tvm/target/target_kind.h
index e5c9a104a..91929229a 100644
--- a/include/tvm/target/target_kind.h
+++ b/include/tvm/target/target_kind.h
@@ -70,6 +70,8 @@ class TargetKindObj : public ffi::Object {
 
   static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = 
kTVMFFISEqHashKindUniqueInstance;
   TVM_FFI_DECLARE_OBJECT_INFO_FINAL("target.TargetKind", TargetKindObj, 
ffi::Object);
+  // Shared instances are mutated in place, so the class is mutable.
+  static constexpr bool _type_mutable = true;
 
  private:
   /*! \brief ConfigSchema for validating and resolving target attributes */
diff --git a/rust/examples/define_ir.rs b/rust/examples/define_ir.rs
index f3a7a1398..3e0a308ed 100644
--- a/rust/examples/define_ir.rs
+++ b/rust/examples/define_ir.rs
@@ -34,7 +34,7 @@ use tvm_tirx::ir::{
     register_type, CallEffectKind, CallObj, DictAttrs, Expr, OpDef, PrimType, 
Type, TypeObj, Var,
     UNKNOWN_LOC,
 };
-use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_object, Array, ObjectRefCast, 
Result};
+use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_object, Array, Result};
 
 fn first_arg_type(call: &CallObj) -> Result<Type> {
     Ok(call.args.get(0)?.ty.clone())
@@ -42,7 +42,7 @@ fn first_arg_type(call: &CallObj) -> Result<Type> {
 
 /// The variable a loop binds in its body: an index of the extent's type.
 fn loop_index(call: &CallObj) -> Result<Array<Var>> {
-    let ty: PrimType = call.args.get(0)?.ty.clone().try_cast()?;
+    let ty: PrimType = PrimType::try_from(&call.args.get(0)?.ty)?;
     Ok(Array::from_vec(vec![Var::new("i", ty, UNKNOWN_LOC)]))
 }
 
diff --git a/rust/src/backend/cuda/op.rs b/rust/src/backend/cuda/op.rs
index 8477e040a..2717fd4d9 100644
--- a/rust/src/backend/cuda/op.rs
+++ b/rust/src/backend/cuda/op.rs
@@ -156,7 +156,11 @@ impl TCGen05InstrDescriptorAttrs {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TCGen05InstrDescriptorAttrs => Attrs);
+// SAFETY: `tirx.cuda.TCGen05InstrDescriptorAttrs` is not `_type_mutable`: a 
shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for 
TCGen05InstrDescriptorAttrsObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TCGen05InstrDescriptorAttrs => Attrs);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): 
object/tirx.cuda.TCGen05InstrDescriptorBlockScaledAttrs
@@ -287,7 +291,11 @@ impl TCGen05InstrDescriptorBlockScaledAttrs {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TCGen05InstrDescriptorBlockScaledAttrs => Attrs);
+// SAFETY: `tirx.cuda.TCGen05InstrDescriptorBlockScaledAttrs` is not 
`_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for 
TCGen05InstrDescriptorBlockScaledAttrsObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TCGen05InstrDescriptorBlockScaledAttrs => 
Attrs);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/tirx.cuda.TensorMapEncodeTiledAttr
@@ -380,5 +388,9 @@ impl TensorMapEncodeTiledAttr {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorMapEncodeTiledAttr => Attrs);
+// SAFETY: `tirx.cuda.TensorMapEncodeTiledAttr` is not `_type_mutable`: a 
shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorMapEncodeTiledAttrObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorMapEncodeTiledAttr => Attrs);
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/codegen.rs b/rust/src/codegen.rs
index 106f96d43..b4004ba9c 100644
--- a/rust/src/codegen.rs
+++ b/rust/src/codegen.rs
@@ -68,7 +68,7 @@ use tvm_ffi::String;
 /// let module = IRModule::from_functions(&functions, None, &Map::new())?;
 ///
 /// let compiled = codegen::build_module(&module, Target::new("c")?)?;
-/// assert!(compiled.implements_function("add_one", true)?);
+/// assert!(compiled.implements_function_with_imports("add_one")?);
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
 pub fn build_module(module: impl Into<IRModule>, target: impl Into<Target>) -> 
Result<Module> {
diff --git a/rust/src/ir/attrs.rs b/rust/src/ir/attrs.rs
index bb4b6fcee..28a17eb32 100644
--- a/rust/src/ir/attrs.rs
+++ b/rust/src/ir/attrs.rs
@@ -34,7 +34,7 @@
 //! let module = IRModule::from_functions(&Map::new(), 
Some(&DictAttrs::new(dict)), &Map::new())?;
 //!
 //! let prefix = module.attrs.dict.get(&"system_lib_prefix".into())?.unwrap();
-//! assert_eq!(prefix.try_as::<String>().unwrap(), "my_lib_");
+//! assert_eq!(String::try_from(prefix)?, "my_lib_");
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 //! ```
 
@@ -105,6 +105,10 @@ unsafe impl UnsafeInit for AttrsObj {
         }
     }
 }
+
+// SAFETY: `ir.Attrs` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AttrsObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.DictAttrs
@@ -158,5 +162,9 @@ impl DictAttrs {
     }
 }
 
-tvm_ffi::impl_object_upcast!(DictAttrs => Attrs);
+// SAFETY: `ir.DictAttrs` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for DictAttrsObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(DictAttrs => Attrs);
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/ir/base.rs b/rust/src/ir/base.rs
index 02f0d1124..b375d3941 100644
--- a/rust/src/ir/base.rs
+++ b/rust/src/ir/base.rs
@@ -33,7 +33,7 @@
 //! # Function::register_global("my_ext.double", double)?;
 //! let double = EnvFunc::new("my_ext.double", 
Function::get_global("my_ext.double")?);
 //!
-//! assert_eq!(double.func.call_tuple((21_i64,))?.try_as::<i64>(), Some(42));
+//! assert_eq!(i64::try_from(double.func.call_tuple((21_i64,))?)?, 42);
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 //! ```
 
@@ -89,4 +89,8 @@ impl EnvFunc {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `ir.EnvFunc` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for EnvFuncObj {}
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index 4d027b2a5..27021fb70 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -74,12 +74,11 @@
 // tvm-ffi-stubgen(import-object): crate::ir::prim::op::ExprResult
 // tvm-ffi-stubgen(import-object): crate::ir::r#type::PrimTypeObj
 // tvm-ffi-stubgen(import-object): tvm_ffi::Error
-// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectRefCast
+// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectRefCore
 // tvm-ffi-stubgen(import-object): tvm_ffi::object::is_instance_of
 // tvm-ffi-stubgen(import-object): tvm_ffi::tvm_ffi_sys::TVMFFIAny
 // tvm-ffi-stubgen(import-object): tvm_ffi::type_traits::AnyCompatible
 // tvm-ffi-stubgen(import-object): tvm_ffi::AnyView
-// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectRefCore
 // tvm-ffi-stubgen(import-object): tvm_ffi::Result
 // tvm-ffi-stubgen(import-object): tvm_ffi::tvm_ffi_sys::TVMFFITypeIndex
 // tvm-ffi-stubgen(import-object): crate::ir::location::UNKNOWN_LOC
@@ -130,7 +129,6 @@ use tvm_ffi::DLDataType;
 use tvm_ffi::Error;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
-use tvm_ffi::ObjectRefCast;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
 use tvm_ffi::ShallowCopy;
@@ -207,6 +205,10 @@ unsafe impl UnsafeInit for ExprObj {
         }
     }
 }
+
+// SAFETY: `ir.Expr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ExprObj {}
 // tvm-ffi-stubgen(end)
 
 /// An index of [`Expr::subscript`]: an expression, or a `start:stop:step` 
slice.
@@ -265,7 +267,7 @@ impl Expr {
 /// `IntoObjectRef<T>` is an `IntoExpr<T>`, so a new expression type converts 
into each type it
 /// upcasts to without a list to maintain. The conversion never checks a type: 
an [`Expr`] or a
 /// [`Var`] is not a `PrimExpr` until it is checked, as in C++, by 
`downcast_ref`, which borrows it
-/// in place (C++ `as`), or `try_cast`, which takes it (C++ `as_or_throw`). 
Its one error is the
+/// in place (C++ `as`), or `downcast`, which takes it (C++ `as_or_throw`). 
Its one error is the
 /// first error an [`ExprResult`] operand raised.
 ///
 /// # Examples
@@ -450,30 +452,14 @@ crate::r#macro::typed_expr_view! {
     mismatch = typed_expr_mismatch, fallback = int_literal
 }
 
-tvm_ffi::impl_object_upcast!(PrimExpr => Expr, IntExpr => Expr, IntExpr => 
PrimExpr);
-
-impl TryFrom<PrimExpr> for IntExpr {
-    type Error = Error;
-
-    fn try_from(value: PrimExpr) -> Result<Self> {
-        value.try_cast()
-    }
-}
-
-impl TryFrom<&PrimExpr> for IntExpr {
-    type Error = Error;
-
-    fn try_from(value: &PrimExpr) -> Result<Self> {
-        value.clone().try_cast()
-    }
-}
+tvm_ffi::unsafe_impl_object_upcast!(PrimExpr => Expr, IntExpr => Expr, IntExpr 
=> PrimExpr);
 
 /// The built expression, checked to be an integer, or the first error raised 
while building it.
 impl TryFrom<ExprResult> for IntExpr {
     type Error = Error;
 
     fn try_from(value: ExprResult) -> Result<Self> {
-        PrimExpr::try_from(value)?.try_cast()
+        PrimExpr::try_from(value)?.try_into()
     }
 }
 
@@ -728,7 +714,11 @@ unsafe impl UnsafeInit for StagingExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(StagingExpr => Expr);
+// SAFETY: `ir.StagingExpr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for StagingExprObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(StagingExpr => Expr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.OpaqueExpr
@@ -794,7 +784,11 @@ unsafe impl UnsafeInit for OpaqueExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(OpaqueExpr => Expr);
+// SAFETY: `ir.OpaqueExpr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for OpaqueExprObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(OpaqueExpr => Expr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.GlobalVar
@@ -852,7 +846,11 @@ impl GlobalVar {
     }
 }
 
-tvm_ffi::impl_object_upcast!(GlobalVar => Expr);
+// SAFETY: `ir.GlobalVar` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for GlobalVarObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(GlobalVar => Expr);
 // tvm-ffi-stubgen(end)
 
 impl GlobalVar {
@@ -918,7 +916,11 @@ impl Tuple {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Tuple => Expr);
+// SAFETY: `ir.Tuple` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TupleObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Tuple => Expr);
 // tvm-ffi-stubgen(end)
 
 impl Tuple {
@@ -998,7 +1000,11 @@ impl TupleGetItem {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TupleGetItem => Expr);
+// SAFETY: `ir.TupleGetItem` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TupleGetItemObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TupleGetItem => Expr);
 // tvm-ffi-stubgen(end)
 
 impl TupleGetItem {
@@ -1108,7 +1114,11 @@ impl TensorLoad {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorLoad => Expr, TensorLoad => PrimExpr);
+// SAFETY: `ir.TensorLoad` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorLoadObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorLoad => Expr, TensorLoad => 
PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl TensorLoad {
@@ -1210,7 +1220,11 @@ unsafe impl UnsafeInit for ConstantObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Constant => Expr);
+// SAFETY: `ir.Constant` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ConstantObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Constant => Expr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.FloatImm
@@ -1264,7 +1278,11 @@ impl FloatImm {
     }
 }
 
-tvm_ffi::impl_object_upcast!(FloatImm => Expr, FloatImm => Constant, FloatImm 
=> PrimExpr);
+// SAFETY: `ir.FloatImm` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FloatImmObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(FloatImm => Expr, FloatImm => Constant, 
FloatImm => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl FloatImm {
@@ -1462,7 +1480,11 @@ impl IntImm {
     }
 }
 
-tvm_ffi::impl_object_upcast!(IntImm => Expr, IntImm => Constant, IntImm => 
PrimExpr);
+// SAFETY: `ir.IntImm` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IntImmObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(IntImm => Expr, IntImm => Constant, IntImm 
=> PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl IntImm {
@@ -1661,7 +1683,11 @@ impl GenericConst {
     }
 }
 
-tvm_ffi::impl_object_upcast!(GenericConst => Expr, GenericConst => Constant);
+// SAFETY: `ir.GenericConst` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for GenericConstObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(GenericConst => Expr, GenericConst => 
Constant);
 // tvm-ffi-stubgen(end)
 
 impl GenericConst {
@@ -1754,7 +1780,11 @@ impl DataTypeImm {
     }
 }
 
-tvm_ffi::impl_object_upcast!(DataTypeImm => Expr, DataTypeImm => Constant);
+// SAFETY: `ir.DataTypeImm` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for DataTypeImmObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(DataTypeImm => Expr, DataTypeImm => 
Constant);
 // tvm-ffi-stubgen(end)
 
 impl DataTypeImm {
@@ -1821,7 +1851,11 @@ impl StringImm {
     }
 }
 
-tvm_ffi::impl_object_upcast!(StringImm => Expr, StringImm => Constant);
+// SAFETY: `ir.StringImm` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for StringImmObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(StringImm => Expr, StringImm => Constant);
 // tvm-ffi-stubgen(end)
 
 impl StringImm {
@@ -1907,7 +1941,11 @@ impl Call {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Call => Expr);
+// SAFETY: `ir.Call` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for CallObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Call => Expr);
 // tvm-ffi-stubgen(end)
 
 impl Call {
@@ -2042,7 +2080,11 @@ impl TensorRegion {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorRegion => Expr);
+// SAFETY: `ir.TensorRegion` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorRegionObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorRegion => Expr);
 // tvm-ffi-stubgen(end)
 
 impl TensorRegion {
@@ -2127,7 +2169,11 @@ unsafe impl UnsafeInit for VarObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Var => Expr);
+// SAFETY: `ir.Var` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for VarObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Var => Expr);
 // tvm-ffi-stubgen(end)
 
 impl Var {
@@ -2144,7 +2190,7 @@ crate::r#macro::typed_expr_view! {
     ///
     /// A `PrimVar` is a view of a `Var`, as [`PrimExpr`] is of an `Expr`: 
[`PrimVar::new`] types it
     /// by a `PrimType`, and a `Var` becomes one only through the check of C++ 
`PrimVar`, by
-    /// `downcast_ref`, which borrows it in place, or `try_cast`, which takes 
it. It converts into a
+    /// `downcast_ref`, which borrows it in place, or `downcast`, which takes 
it. It converts into a
     /// `Var`, a `PrimExpr` and an `Expr` for free, and reads the variable 
through `Deref`.
     ///
     /// # Examples
@@ -2152,7 +2198,7 @@ crate::r#macro::typed_expr_view! {
     /// ```
     /// use tvm_tirx::ir::{MissingType, PrimExpr, PrimType, PrimVar, Var, 
UNKNOWN_LOC};
     /// use tvm_tirx::script::script;
-    /// use tvm_tirx::tvm_ffi::{ObjectRefCast, ObjectRefCore};
+    /// use tvm_tirx::tvm_ffi::{ObjectRefCore};
     ///
     /// let i = PrimVar::new("i", PrimType::int(32), UNKNOWN_LOC);
     /// assert_eq!(i.name.as_str(), "i");
@@ -2161,7 +2207,7 @@ crate::r#macro::typed_expr_view! {
     /// let var: Var = i.into();
     /// assert!(var.downcast_ref::<PrimVar>().is_some());
     /// let untyped = Var::new("u", MissingType::new(), UNKNOWN_LOC);
-    /// assert!(untyped.try_cast::<PrimVar>().is_err());
+    /// assert!(untyped.downcast::<PrimVar>().is_err());
     /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
     /// ```
     ///
@@ -2176,7 +2222,7 @@ crate::r#macro::typed_expr_view! {
     fallback = no_literal
 }
 
-tvm_ffi::impl_object_upcast!(PrimVar => Expr, PrimVar => Var, PrimVar => 
PrimExpr);
+tvm_ffi::unsafe_impl_object_upcast!(PrimVar => Expr, PrimVar => Var, PrimVar 
=> PrimExpr);
 
 impl PrimVar {
     /// Create the variable `name` of the primitive type `ty` (C++ 
`PrimVar(name, ty, loc)`).
@@ -2244,7 +2290,11 @@ impl LambdaExpr {
     }
 }
 
-tvm_ffi::impl_object_upcast!(LambdaExpr => Expr, LambdaExpr => StagingExpr);
+// SAFETY: `ir.LambdaExpr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LambdaExprObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(LambdaExpr => Expr, LambdaExpr => 
StagingExpr);
 // tvm-ffi-stubgen(end)
 
 impl LambdaExpr {
@@ -2326,6 +2376,10 @@ impl Range {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `ir.Range` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RangeObj {}
 // tvm-ffi-stubgen(end)
 
 impl Range {
@@ -2377,7 +2431,7 @@ impl Range {
 
 // The operators apply to the expressions typed by a `PrimType`, the subtypes 
of `PrimExpr`, as the
 // C++ operators take `PrimExpr`; the typed views get theirs from 
`typed_expr_view!`. An `Expr`, a
-// `Var` or a `Call` is checked first, with `downcast_ref` or `try_cast`.
+// `Var` or a `Call` is checked first, with `downcast_ref` or `downcast`.
 crate::def_expr_op_over_loads!(IntImm);
 crate::def_expr_op_over_loads!(FloatImm);
 crate::def_expr_op_over_loads!(TensorLoad);
diff --git a/rust/src/ir/function.rs b/rust/src/ir/function.rs
index c0c31ae9f..447176f57 100644
--- a/rust/src/ir/function.rs
+++ b/rust/src/ir/function.rs
@@ -134,7 +134,11 @@ unsafe impl UnsafeInit for BaseFuncObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BaseFunc => Expr);
+// SAFETY: `ir.BaseFunc` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BaseFuncObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BaseFunc => Expr);
 // tvm-ffi-stubgen(end)
 
 impl BaseFunc {
diff --git a/rust/src/ir/global_info.rs b/rust/src/ir/global_info.rs
index c4815bf20..d632355f6 100644
--- a/rust/src/ir/global_info.rs
+++ b/rust/src/ir/global_info.rs
@@ -87,4 +87,8 @@ unsafe impl UnsafeInit for GlobalInfoObj {
         }
     }
 }
+
+// SAFETY: `ir.GlobalInfo` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for GlobalInfoObj {}
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/ir/location.rs b/rust/src/ir/location.rs
index 5d278e2a6..9d24336cd 100644
--- a/rust/src/ir/location.rs
+++ b/rust/src/ir/location.rs
@@ -111,6 +111,10 @@ unsafe impl UnsafeInit for LocationObj {
         }
     }
 }
+
+// SAFETY: `ir.Location` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LocationObj {}
 // tvm-ffi-stubgen(end)
 
 impl Location {
@@ -171,7 +175,11 @@ impl UnknownLoc {
     }
 }
 
-tvm_ffi::impl_object_upcast!(UnknownLoc => Location);
+// SAFETY: `ir.UnknownLoc` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for UnknownLocObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(UnknownLoc => Location);
 // tvm-ffi-stubgen(end)
 
 impl UnknownLoc {
@@ -271,7 +279,11 @@ impl SourceLoc {
     }
 }
 
-tvm_ffi::impl_object_upcast!(SourceLoc => Location);
+// SAFETY: `ir.SourceLoc` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SourceLocObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(SourceLoc => Location);
 // tvm-ffi-stubgen(end)
 
 // `ir.SourceLoc` stores its arguments as given: the generated 
`SourceLoc::new` is the same.
@@ -328,7 +340,11 @@ impl CallSiteLoc {
     }
 }
 
-tvm_ffi::impl_object_upcast!(CallSiteLoc => Location);
+// SAFETY: `ir.CallSiteLoc` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for CallSiteLocObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(CallSiteLoc => Location);
 // tvm-ffi-stubgen(end)
 
 // `ir.CallSiteLoc` stores its arguments as given: the generated 
`CallSiteLoc::new` is the same.
@@ -376,6 +392,10 @@ impl SourceName {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `ir.SourceName` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SourceNameObj {}
 // tvm-ffi-stubgen(end)
 
 impl SourceName {
diff --git a/rust/src/ir/op.rs b/rust/src/ir/op.rs
index 9e3914171..a6a452e06 100644
--- a/rust/src/ir/op.rs
+++ b/rust/src/ir/op.rs
@@ -99,7 +99,7 @@ pub struct OpObj {
     /// The type key of the operator's `Call.attrs`, or empty when it declares 
none.
     pub attrs_type_key: String,
     attrs_type_index: u32,
-    validator: Any,
+    validator: ValidatorCell,
     validator_is_custom: bool,
 }
 
@@ -119,6 +119,20 @@ const _: () = {
     assert!(::core::mem::offset_of!(OpObj, validator_is_custom) == 152);
 };
 
+// SAFETY: `ir.Op` is not `_type_mutable`: registration finishes initializing 
an entry, and a
+// shared one is never mutated after.
+unsafe impl tvm_ffi::object::ImmutableObject for OpObj {}
+
+/// The validator slot of an operator: an opaque pointer to a C ABI adapter, 
written while the
+/// operator is registered and only read after, so it may be shared as the 
rest of `OpObj` is.
+#[repr(transparent)]
+struct ValidatorCell(Any);
+
+// SAFETY: the cell is written only during registration, which counts as 
initialization (as for
+// the rest of `OpObj`), and holds a native function pointer that is safe to 
call from any thread.
+unsafe impl Send for ValidatorCell {}
+unsafe impl Sync for ValidatorCell {}
+
 /// A registered operator, the callee of a [`Call`](crate::ir::Call) to it.
 ///
 /// The registry keeps one object per name, so `call.op.same_as(&op)` tests 
whether a call is to
@@ -153,7 +167,7 @@ impl OpObj {
     /// Return the validator that 
[`Call::validate`](crate::ir::Call::validate) runs: a native
     /// function pointer, a packed function, or `None`.
     pub fn validator(&self) -> &Any {
-        &self.validator
+        &self.validator.0
     }
 
     /// Return whether the validator was set with
@@ -241,15 +255,18 @@ impl Op {
         // SAFETY: C++ `OpDef` writes these members of the registry's node in 
place too. No
         // reference into the node is live, and registration is not 
synchronized with readers.
         unsafe {
-            let node = ObjectArc::as_raw(ObjectRefCore::data(self)) as *mut 
OpObj;
-            let previous = 
std::ptr::replace(std::ptr::addr_of_mut!((*node).validator), validator);
+            let node = ObjectArc::as_ptr(ObjectRefCore::data(self)) as *mut 
OpObj;
+            let previous = std::ptr::replace(
+                std::ptr::addr_of_mut!((*node).validator),
+                ValidatorCell(validator),
+            );
             std::ptr::addr_of_mut!((*node).validator_is_custom).write(custom);
             drop(previous);
         }
     }
 }
 
-tvm_ffi::impl_object_upcast!(Op => Expr);
+tvm_ffi::unsafe_impl_object_upcast!(Op => Expr);
 
 // tvm-ffi-stubgen(begin): object/ir.ArgumentInfo
 /// Complete: reflected fields fill [24, 56) exactly.
@@ -295,6 +312,10 @@ impl ArgumentInfo {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `ir.ArgumentInfo` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ArgumentInfoObj {}
 // tvm-ffi-stubgen(end)
 
 // The crate has no `ffi.List` binding, so the column is read through the 
`TVMFFISeqCell` that
@@ -317,7 +338,7 @@ impl<T> OpAttrMap<T> {
         // The cell is read on every lookup because growth may move the 
element buffer.
         let cell = unsafe {
             let data = tvm_ffi::object::ObjectRefCore::data(&self.column);
-            &*(ObjectArc::as_raw(data) as *const support::SeqCell)
+            &*(ObjectArc::as_ptr(data) as *const support::SeqCell)
         };
         let index = op.index as usize;
         if index >= cell.size as usize {
diff --git a/rust/src/ir/op_def.rs b/rust/src/ir/op_def.rs
index 6f70a46bf..6fb4c8366 100644
--- a/rust/src/ir/op_def.rs
+++ b/rust/src/ir/op_def.rs
@@ -240,7 +240,7 @@ impl OpDef {
         self.then(|op| {
             let packed: Vec<AnyView> =
                 
std::iter::once(AnyView::from(op)).chain(args.iter().copied()).collect();
-            tvm_ffi::Function::from_type_key_method("ir.Op", 
method)?.call_packed(&packed)?;
+            tvm_ffi::reflection::get_method("ir.Op", 
method)?.call_packed(&packed)?;
             Ok(())
         })
     }
@@ -427,10 +427,10 @@ fn call_hook<N, R: Into<Any>>(node: *const N, hook: impl 
Fn(&N) -> Result<R>) ->
     let value = match result {
         Ok(Ok(value)) => value,
         Ok(Err(error)) => Any::from(error),
-        Err(payload) => 
Any::from(tvm_ffi::function_internal::panic_to_error(payload)),
+        Err(payload) => Any::from(tvm_ffi::Error::from_panic(payload)),
     };
-    // SAFETY: the C++ caller takes ownership of the value.
-    unsafe { Any::into_raw_ffi_any(value) }
+    // The C++ caller takes ownership of the value.
+    Any::into_raw_ffi_any(value)
 }
 
 /// Signature descriptors for [`OpDef::signature`], C++ `sig::arg<T>` and 
friends.
diff --git a/rust/src/ir/prim/expr.rs b/rust/src/ir/prim/expr.rs
index 28bc94ad2..3b176d98a 100644
--- a/rust/src/ir/prim/expr.rs
+++ b/rust/src/ir/prim/expr.rs
@@ -363,7 +363,11 @@ impl Cast {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Cast => Expr, Cast => PrimExpr);
+// SAFETY: `prim.Cast` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for CastObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Cast => Expr, Cast => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Cast {
@@ -449,7 +453,11 @@ impl LShift {
     }
 }
 
-tvm_ffi::impl_object_upcast!(LShift => Expr, LShift => PrimExpr);
+// SAFETY: `prim.LShift` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LShiftObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(LShift => Expr, LShift => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -515,7 +523,11 @@ impl RShift {
     }
 }
 
-tvm_ffi::impl_object_upcast!(RShift => Expr, RShift => PrimExpr);
+// SAFETY: `prim.RShift` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RShiftObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(RShift => Expr, RShift => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -581,7 +593,11 @@ impl BitwiseAnd {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BitwiseAnd => Expr, BitwiseAnd => PrimExpr);
+// SAFETY: `prim.BitwiseAnd` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BitwiseAndObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BitwiseAnd => Expr, BitwiseAnd => 
PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -647,7 +663,11 @@ impl BitwiseOr {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BitwiseOr => Expr, BitwiseOr => PrimExpr);
+// SAFETY: `prim.BitwiseOr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BitwiseOrObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BitwiseOr => Expr, BitwiseOr => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -713,7 +733,11 @@ impl BitwiseXor {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BitwiseXor => Expr, BitwiseXor => PrimExpr);
+// SAFETY: `prim.BitwiseXor` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BitwiseXorObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BitwiseXor => Expr, BitwiseXor => 
PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -777,7 +801,11 @@ impl BitwiseNot {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BitwiseNot => Expr, BitwiseNot => PrimExpr);
+// SAFETY: `prim.BitwiseNot` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BitwiseNotObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BitwiseNot => Expr, BitwiseNot => 
PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl BitwiseNot {
@@ -858,7 +886,11 @@ impl Add {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Add => Expr, Add => PrimExpr);
+// SAFETY: `prim.Add` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AddObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Add => Expr, Add => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -923,7 +955,11 @@ impl Sub {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Sub => Expr, Sub => PrimExpr);
+// SAFETY: `prim.Sub` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SubObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Sub => Expr, Sub => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -988,7 +1024,11 @@ impl Mul {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Mul => Expr, Mul => PrimExpr);
+// SAFETY: `prim.Mul` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for MulObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Mul => Expr, Mul => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1053,7 +1093,11 @@ impl Div {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Div => Expr, Div => PrimExpr);
+// SAFETY: `prim.Div` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for DivObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Div => Expr, Div => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1118,7 +1162,11 @@ impl Mod {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Mod => Expr, Mod => PrimExpr);
+// SAFETY: `prim.Mod` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ModObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Mod => Expr, Mod => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1183,7 +1231,11 @@ impl FloorDiv {
     }
 }
 
-tvm_ffi::impl_object_upcast!(FloorDiv => Expr, FloorDiv => PrimExpr);
+// SAFETY: `prim.FloorDiv` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FloorDivObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(FloorDiv => Expr, FloorDiv => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1249,7 +1301,11 @@ impl FloorMod {
     }
 }
 
-tvm_ffi::impl_object_upcast!(FloorMod => Expr, FloorMod => PrimExpr);
+// SAFETY: `prim.FloorMod` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FloorModObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(FloorMod => Expr, FloorMod => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1315,7 +1371,11 @@ impl Min {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Min => Expr, Min => PrimExpr);
+// SAFETY: `prim.Min` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for MinObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Min => Expr, Min => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1380,7 +1440,11 @@ impl Max {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Max => Expr, Max => PrimExpr);
+// SAFETY: `prim.Max` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for MaxObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Max => Expr, Max => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1448,7 +1512,11 @@ impl EQ {
     }
 }
 
-tvm_ffi::impl_object_upcast!(EQ => Expr, EQ => PrimExpr);
+// SAFETY: `prim.EQ` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for EQObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(EQ => Expr, EQ => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1514,7 +1582,11 @@ impl NE {
     }
 }
 
-tvm_ffi::impl_object_upcast!(NE => Expr, NE => PrimExpr);
+// SAFETY: `prim.NE` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for NEObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(NE => Expr, NE => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1580,7 +1652,11 @@ impl LT {
     }
 }
 
-tvm_ffi::impl_object_upcast!(LT => Expr, LT => PrimExpr);
+// SAFETY: `prim.LT` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LTObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(LT => Expr, LT => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1646,7 +1722,11 @@ impl LE {
     }
 }
 
-tvm_ffi::impl_object_upcast!(LE => Expr, LE => PrimExpr);
+// SAFETY: `prim.LE` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LEObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(LE => Expr, LE => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1712,7 +1792,11 @@ impl GT {
     }
 }
 
-tvm_ffi::impl_object_upcast!(GT => Expr, GT => PrimExpr);
+// SAFETY: `prim.GT` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for GTObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(GT => Expr, GT => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1778,7 +1862,11 @@ impl GE {
     }
 }
 
-tvm_ffi::impl_object_upcast!(GE => Expr, GE => PrimExpr);
+// SAFETY: `prim.GE` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for GEObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(GE => Expr, GE => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 binary_new! {
@@ -1847,7 +1935,11 @@ impl And {
     }
 }
 
-tvm_ffi::impl_object_upcast!(And => Expr, And => PrimExpr);
+// SAFETY: `prim.And` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AndObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(And => Expr, And => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl And {
@@ -1925,7 +2017,11 @@ impl Or {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Or => Expr, Or => PrimExpr);
+// SAFETY: `prim.Or` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for OrObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Or => Expr, Or => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Or {
@@ -2001,7 +2097,11 @@ impl Not {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Not => Expr, Not => PrimExpr);
+// SAFETY: `prim.Not` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for NotObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Not => Expr, Not => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Not {
@@ -2095,7 +2195,11 @@ impl Select {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Select => Expr, Select => PrimExpr);
+// SAFETY: `prim.Select` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SelectObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Select => Expr, Select => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Select {
@@ -2220,7 +2324,11 @@ impl Let {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Let => Expr, Let => PrimExpr);
+// SAFETY: `prim.Let` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for LetObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Let => Expr, Let => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Let {
diff --git a/rust/src/ir/prim/vector_expr.rs b/rust/src/ir/prim/vector_expr.rs
index cf894924e..10c6c4088 100644
--- a/rust/src/ir/prim/vector_expr.rs
+++ b/rust/src/ir/prim/vector_expr.rs
@@ -175,7 +175,11 @@ impl Ramp {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Ramp => Expr, Ramp => PrimExpr);
+// SAFETY: `prim.Ramp` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RampObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Ramp => Expr, Ramp => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Ramp {
@@ -285,7 +289,11 @@ impl Broadcast {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Broadcast => Expr, Broadcast => PrimExpr);
+// SAFETY: `prim.Broadcast` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BroadcastObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Broadcast => Expr, Broadcast => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Broadcast {
@@ -373,7 +381,11 @@ impl Shuffle {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Shuffle => Expr, Shuffle => PrimExpr);
+// SAFETY: `prim.Shuffle` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ShuffleObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Shuffle => Expr, Shuffle => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 impl Shuffle {
diff --git a/rust/src/ir/stmt.rs b/rust/src/ir/stmt.rs
index 7eeade055..34101612c 100644
--- a/rust/src/ir/stmt.rs
+++ b/rust/src/ir/stmt.rs
@@ -188,6 +188,10 @@ unsafe impl UnsafeInit for StmtObj {
         }
     }
 }
+
+// SAFETY: `ir.Stmt` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for StmtObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.Bind
@@ -246,7 +250,11 @@ impl Bind {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Bind => Stmt);
+// SAFETY: `ir.Bind` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BindObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Bind => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl Bind {
@@ -339,7 +347,11 @@ impl AssertStmt {
     }
 }
 
-tvm_ffi::impl_object_upcast!(AssertStmt => Stmt);
+// SAFETY: `ir.AssertStmt` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AssertStmtObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(AssertStmt => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl AssertStmt {
@@ -455,7 +467,11 @@ impl For {
     }
 }
 
-tvm_ffi::impl_object_upcast!(For => Stmt);
+// SAFETY: `ir.For` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ForObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(For => Stmt);
 // tvm-ffi-stubgen(end)
 
 /// How a [`For`] loop runs its iterations, as C++ `ir::ForKind`.
@@ -633,7 +649,11 @@ impl While {
     }
 }
 
-tvm_ffi::impl_object_upcast!(While => Stmt);
+// SAFETY: `ir.While` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for WhileObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(While => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl While {
@@ -722,7 +742,11 @@ impl TensorStore {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorStore => Stmt);
+// SAFETY: `ir.TensorStore` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorStoreObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorStore => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl TensorStore {
@@ -845,7 +869,11 @@ impl RegionStmt {
     }
 }
 
-tvm_ffi::impl_object_upcast!(RegionStmt => Stmt);
+// SAFETY: `ir.RegionStmt` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RegionStmtObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(RegionStmt => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl RegionStmt {
@@ -895,7 +923,7 @@ impl RegionStmt {
         }
         let mut definitions = std::collections::HashSet::new();
         for var in body_params.iter().chain(result_vars.iter()) {
-            if !definitions.insert(object_address(&var)) {
+            if !definitions.insert(object_address(var)) {
                 let condition = "definitions.insert(var.get()).second";
                 let message = "RegionStmt parameters and results must be 
distinct definitions";
                 return Err(support::check_failed(tvm_ffi::VALUE_ERROR, 
condition, message));
@@ -937,7 +965,7 @@ fn region_body_params(op: &Op, args: &Array<Expr>, attrs: 
&DictAttrs) -> Result<
             let message = format!("FRegionGetBodyParams for {name} must return 
typed variables");
             return Err(support::check_failed(tvm_ffi::VALUE_ERROR, condition, 
&message));
         }
-        if !definitions.insert(object_address(&param)) {
+        if !definitions.insert(object_address(param)) {
             let condition = "definitions.insert(param.get()).second";
             let message =
                 format!("FRegionGetBodyParams for {name} must return distinct 
definitions");
@@ -949,8 +977,8 @@ fn region_body_params(op: &Op, args: &Array<Expr>, attrs: 
&DictAttrs) -> Result<
 
 /// The address of the object `value` refers to, which identifies a definition.
 fn object_address<T: ObjectRefCore>(value: &T) -> usize {
-    // SAFETY: the pointer is only compared, never dereferenced.
-    unsafe { ObjectArc::as_raw(ObjectRefCore::data(value)) as usize }
+    // The pointer is only compared, never dereferenced.
+    ObjectArc::as_ptr(ObjectRefCore::data(value)) as usize
 }
 
 // tvm-ffi-stubgen(begin): object/ir.SeqStmt
@@ -1004,7 +1032,11 @@ impl SeqStmt {
     }
 }
 
-tvm_ffi::impl_object_upcast!(SeqStmt => Stmt);
+// SAFETY: `ir.SeqStmt` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SeqStmtObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(SeqStmt => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl SeqStmt {
@@ -1025,13 +1057,13 @@ fn flatten(seq: Array<Stmt>) -> Array<Stmt> {
     }
     fn append(stmt: Stmt, flattened: &mut Vec<Stmt>) {
         match stmt.downcast_ref::<SeqStmtObj>() {
-            Some(nested) => nested.seq.iter().for_each(|child| append(child, 
flattened)),
+            Some(nested) => nested.seq.iter().for_each(|child| 
append(child.clone(), flattened)),
             None if is_no_op(&stmt) => {}
             None => flattened.push(stmt),
         }
     }
     let mut flattened = Vec::new();
-    seq.iter().for_each(|stmt| append(stmt, &mut flattened));
+    seq.iter().for_each(|stmt| append(stmt.clone(), &mut flattened));
     Array::from_vec(flattened)
 }
 
@@ -1046,7 +1078,7 @@ fn is_no_op(stmt: &Stmt) -> bool {
 /// statement as the sequence of it at its location (C++ `SeqStmt(stmt)`).
 fn into_body(stmt: Stmt) -> Result<SeqStmt> {
     if stmt.downcast_ref::<SeqStmtObj>().is_some() {
-        return Ok(tvm_ffi::ObjectRefCast::try_cast(stmt).expect("a SeqStmt"));
+        return Ok(stmt.downcast().expect("a SeqStmt"));
     }
     let loc = stmt.loc.clone();
     Ok(SeqStmt::from_fields(loc, flatten(Array::from_vec(vec![stmt]))))
@@ -1115,7 +1147,11 @@ impl If {
     }
 }
 
-tvm_ffi::impl_object_upcast!(If => Stmt);
+// SAFETY: `ir.If` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IfObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(If => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl If {
@@ -1185,7 +1221,11 @@ impl Evaluate {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Evaluate => Stmt);
+// SAFETY: `ir.Evaluate` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for EvaluateObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Evaluate => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl Evaluate {
@@ -1245,7 +1285,11 @@ impl Break {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Break => Stmt);
+// SAFETY: `ir.Break` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BreakObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Break => Stmt);
 // tvm-ffi-stubgen(end)
 
 // `ir.Break` stores its location as given: the generated `Break::new` is the 
same.
@@ -1301,7 +1345,11 @@ impl Return {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Return => Stmt);
+// SAFETY: `ir.Return` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ReturnObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Return => Stmt);
 // tvm-ffi-stubgen(end)
 
 impl Return {
@@ -1361,7 +1409,11 @@ impl Continue {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Continue => Stmt);
+// SAFETY: `ir.Continue` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ContinueObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Continue => Stmt);
 // tvm-ffi-stubgen(end)
 
 // `ir.Continue` stores its location as given: the generated `Continue::new` 
is the same.
diff --git a/rust/src/ir/support.rs b/rust/src/ir/support.rs
index 0b92e484e..1e2e0e928 100644
--- a/rust/src/ir/support.rs
+++ b/rust/src/ir/support.rs
@@ -121,7 +121,7 @@ where
 
 /// A dtype as C++ prints it, such as `int32`.
 pub(crate) fn dtype_name(dtype: DLDataType) -> std::string::String {
-    DLDataTypeExt::to_string(&dtype).as_str().to_string()
+    dtype.to_string()
 }
 
 /// The registered key of a type index (C++ `TypeIndexToTypeKey`).
@@ -216,7 +216,7 @@ pub(crate) fn shared<T: Clone + 'static>(
 pub(crate) fn prim_type(expr: &(impl AsRef<Expr> + ?Sized)) -> 
Result<PrimType> {
     let expr = expr.as_ref();
     match expr.ty.downcast_ref::<PrimTypeObj>() {
-        Some(_) => 
Ok(tvm_ffi::ObjectRefCast::try_cast(expr.ty.clone()).expect("a PrimType")),
+        Some(_) => Ok(expr.ty.downcast_ref::<PrimType>().cloned().expect("a 
PrimType")),
         None => Err(not_a_prim_expr(expr)),
     }
 }
@@ -302,7 +302,7 @@ pub(crate) fn array_cells<T: 
tvm_ffi::type_traits::ContainerElement + Clone>(
     // SAFETY: an `ffi.Array` is the object header followed by 
`TVMFFISeqCell`, and `array` keeps
     // its `size` elements alive.
     unsafe {
-        let cell = &*(ObjectArc::as_raw(ObjectRefCore::data(array)) as *const 
SeqCell);
+        let cell = &*(ObjectArc::as_ptr(ObjectRefCore::data(array)) as *const 
SeqCell);
         if cell.size == 0 {
             return &[];
         }
@@ -348,7 +348,7 @@ impl NativeHook {
             // SAFETY: `node` keeps the object alive for the call, and the 
adapter returns an
             // owned value.
             Self::Adapter(adapter) => unsafe {
-                let node = ObjectArc::as_raw(ObjectRefCore::data(node)) as 
*const c_void;
+                let node = ObjectArc::as_ptr(ObjectRefCore::data(node)) as 
*const c_void;
                 Any::from_raw_ffi_any(adapter(node))
             },
             Self::Packed(function) => 
function.call_packed(&[AnyView::from(node)])?,
@@ -402,8 +402,8 @@ where
         *const *const TVMFFIObject,
     ) -> TVMFFIAny;
     fn object<T: ObjectRefCore>(value: &T) -> *const TVMFFIObject {
-        // SAFETY: the pointer is only read while `value` keeps the object 
alive.
-        unsafe { ObjectArc::as_raw(ObjectRefCore::data(value)) as *const 
TVMFFIObject }
+        // The pointer is only read while `value` keeps the object alive.
+        ObjectArc::as_ptr(ObjectRefCore::data(value)) as *const TVMFFIObject
     }
     let address: *mut c_void = hook.try_into()?;
     let (a, b, c) = (object(a), object(b), object(c));
diff --git a/rust/src/ir/transform/core.rs b/rust/src/ir/transform/core.rs
index ca4955126..dce5543af 100644
--- a/rust/src/ir/transform/core.rs
+++ b/rust/src/ir/transform/core.rs
@@ -150,6 +150,10 @@ impl PassInfo {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `transform.PassInfo` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for PassInfoObj {}
 // tvm-ffi-stubgen(end)
 
 // `PassInfo::new` above has the semantics of the native `transform.PassInfo`, 
which only stores
@@ -191,7 +195,11 @@ impl ModulePassObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(ModulePass => Pass);
+// SAFETY: `transform.ModulePass` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ModulePassObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(ModulePass => Pass);
 // tvm-ffi-stubgen(end)
 
 impl ModulePass {
@@ -256,7 +264,11 @@ impl SequentialObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Sequential => Pass);
+// SAFETY: `transform.Sequential` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for SequentialObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Sequential => Pass);
 // tvm-ffi-stubgen(end)
 
 impl Sequential {
@@ -482,6 +494,10 @@ impl PassInstrumentObj {
         FieldGetter::new(Self::type_index(), "name")?.get(self)
     }
 }
+
+// SAFETY: `transform.PassInstrument` is not `_type_mutable`: a shared 
instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for PassInstrumentObj {}
 // tvm-ffi-stubgen(end)
 
 /// Create a pass that logs `header` and the module, which it returns unchanged
diff --git a/rust/src/ir/transform/instrument.rs 
b/rust/src/ir/transform/instrument.rs
index 74e399432..355aea5fa 100644
--- a/rust/src/ir/transform/instrument.rs
+++ b/rust/src/ir/transform/instrument.rs
@@ -102,7 +102,7 @@ impl Deref for BasePassInstrumentObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BasePassInstrument => PassInstrument);
+tvm_ffi::unsafe_impl_object_upcast!(BasePassInstrument => PassInstrument);
 // tvm-ffi-stubgen(end)
 
 /// A hook of a [`BasePassInstrument`], called when a pass context is entered 
or exited.
diff --git a/rust/src/ir/type.rs b/rust/src/ir/type.rs
index 45ddf4cf5..7818dfcd5 100644
--- a/rust/src/ir/type.rs
+++ b/rust/src/ir/type.rs
@@ -137,6 +137,10 @@ unsafe impl UnsafeInit for TypeObj {
         }
     }
 }
+
+// SAFETY: `ir.Type` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TypeObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/ir.MissingType
@@ -189,7 +193,11 @@ impl MissingType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(MissingType => Type);
+// SAFETY: `ir.MissingType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for MissingTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(MissingType => Type);
 // tvm-ffi-stubgen(end)
 
 impl MissingType {
@@ -255,7 +263,11 @@ impl NoneType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(NoneType => Type);
+// SAFETY: `ir.NoneType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for NoneTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(NoneType => Type);
 // tvm-ffi-stubgen(end)
 
 impl NoneType {
@@ -320,7 +332,11 @@ impl AnyType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(AnyType => Type);
+// SAFETY: `ir.AnyType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AnyTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(AnyType => Type);
 // tvm-ffi-stubgen(end)
 
 // `ir.AnyType` stores its location as given: the generated `AnyType::new` is 
the same.
@@ -375,7 +391,11 @@ impl TensorRegionType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorRegionType => Type);
+// SAFETY: `ir.TensorRegionType` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorRegionTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorRegionType => Type);
 // tvm-ffi-stubgen(end)
 
 impl TensorRegionType {
@@ -441,7 +461,11 @@ impl OpaqueType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(OpaqueType => Type);
+// SAFETY: `ir.OpaqueType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for OpaqueTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(OpaqueType => Type);
 // tvm-ffi-stubgen(end)
 
 // `ir.OpaqueType` allocates a fresh type at the unknown location: the 
generated
@@ -498,7 +522,11 @@ impl PrimType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(PrimType => Type);
+// SAFETY: `ir.PrimType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for PrimTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(PrimType => Type);
 // tvm-ffi-stubgen(end)
 
 impl PrimType {
@@ -638,7 +666,11 @@ impl StringType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(StringType => Type);
+// SAFETY: `ir.StringType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for StringTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(StringType => Type);
 // tvm-ffi-stubgen(end)
 
 // `ir.StringType` allocates a fresh type at the unknown location: the 
generated
@@ -700,7 +732,11 @@ impl PtrType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(PtrType => Type);
+// SAFETY: `ir.PtrType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for PtrTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(PtrType => Type);
 // tvm-ffi-stubgen(end)
 
 impl PtrType {
@@ -772,7 +808,11 @@ impl TupleType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TupleType => Type);
+// SAFETY: `ir.TupleType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TupleTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TupleType => Type);
 // tvm-ffi-stubgen(end)
 
 impl TupleType {
@@ -843,7 +883,11 @@ impl FuncType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(FuncType => Type);
+// SAFETY: `ir.FuncType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FuncTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(FuncType => Type);
 // tvm-ffi-stubgen(end)
 
 impl FuncType {
diff --git a/rust/src/macro/def_expr_op_overloads.rs 
b/rust/src/macro/def_expr_op_overloads.rs
index 5c3d377b7..2013f3c9c 100644
--- a/rust/src/macro/def_expr_op_overloads.rs
+++ b/rust/src/macro/def_expr_op_overloads.rs
@@ -50,7 +50,7 @@
 /// }
 ///
 /// // Every thread index is an `int32`, as `new` makes it.
-/// tvm_ffi::impl_object_upcast!(ThreadIndex => PrimExpr);
+/// tvm_ffi::unsafe_impl_object_upcast!(ThreadIndex => PrimExpr);
 /// tvm_tirx::def_expr_op_over_loads!(ThreadIndex);
 ///
 /// # fn main() -> tvm_tirx::tvm_ffi::Result<()> {
diff --git a/rust/src/macro/typed_expr_view.rs 
b/rust/src/macro/typed_expr_view.rs
index 6f1ba93bb..52d8c70f4 100644
--- a/rust/src/macro/typed_expr_view.rs
+++ b/rust/src/macro/typed_expr_view.rs
@@ -170,6 +170,14 @@ macro_rules! typed_expr_view {
         tvm_ffi::impl_try_from_any!($name);
         tvm_ffi::impl_arg_into_ref!($name);
         tvm_ffi::impl_into_arg_holder_default!($name);
+
+        // The type key and address, as `#[derive(ObjectRef)]` prints a 
reference, so a
+        // `downcast` result can be unwrapped.
+        impl ::core::fmt::Debug for $name {
+            fn fmt(&self, f: &mut ::core::fmt::Formatter<'_>) -> 
::core::fmt::Result {
+                tvm_ffi::object::fmt_object_ref(self, f)
+            }
+        }
         crate::def_expr_op_over_loads!($name);
 
         impl From<&$name> for $name {
@@ -199,7 +207,7 @@ macro_rules! typed_expr_view {
             /// Return the type of this expression, the [`PrimType`] the view 
guarantees (C++
             /// `PrimExpr::ty()`).
             pub fn ty(&self) -> PrimType {
-                self.base.ty.clone().try_cast().expect("a view is typed by a 
`PrimType`")
+                self.base.ty.downcast_ref::<PrimType>().cloned().expect("a 
view is typed by a `PrimType`")
             }
 
             /// Return the dtype of this expression's type.
@@ -208,24 +216,6 @@ macro_rules! typed_expr_view {
                 ty.expect("a view is typed by a `PrimType`").dtype
             }
         }
-
-        /// Convert an expression into the view; a `TypeError` names a type 
the view does not
-        /// accept.
-        impl TryFrom<Expr> for $name {
-            type Error = Error;
-
-            fn try_from(value: Expr) -> Result<Self> {
-                value.try_cast()
-            }
-        }
-
-        impl TryFrom<&Expr> for $name {
-            type Error = Error;
-
-            fn try_from(value: &Expr) -> Result<Self> {
-                value.clone().try_cast()
-            }
-        }
     };
 }
 
diff --git a/rust/src/runtime.rs b/rust/src/runtime.rs
index 9a2902064..b5aada2ac 100644
--- a/rust/src/runtime.rs
+++ b/rust/src/runtime.rs
@@ -94,6 +94,10 @@ impl Deref for MemoryScope {
         &self.base
     }
 }
+
+// SAFETY: `runtime.MemoryScope` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for MemoryScopeObj {}
 // tvm-ffi-stubgen(end)
 
 impl MemoryScope {
diff --git a/rust/src/sym/analyzer.rs b/rust/src/sym/analyzer.rs
index c2a24c8fe..9eaa63f82 100644
--- a/rust/src/sym/analyzer.rs
+++ b/rust/src/sym/analyzer.rs
@@ -440,6 +440,10 @@ impl ConstIntBound {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `sym.ConstIntBound` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ConstIntBoundObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.ModularSet
@@ -486,6 +490,10 @@ impl ModularSet {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `sym.ModularSet` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ModularSetObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.RewriteSimplifierStats
@@ -565,6 +573,10 @@ impl RewriteSimplifierStats {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `sym.RewriteSimplifierStats` is not `_type_mutable`: a shared 
instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RewriteSimplifierStatsObj {}
 // tvm-ffi-stubgen(end)
 
 // 
---------------------------------------------------------------------------------------------
@@ -599,7 +611,7 @@ impl Deref for CanonicalExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(CanonicalExpr => Expr);
+tvm_ffi::unsafe_impl_object_upcast!(CanonicalExpr => Expr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.SplitExpr
@@ -631,7 +643,7 @@ impl Deref for SplitExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(SplitExpr => Expr, SplitExpr => CanonicalExpr, 
SplitExpr => PrimExpr);
+tvm_ffi::unsafe_impl_object_upcast!(SplitExpr => Expr, SplitExpr => 
CanonicalExpr, SplitExpr => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.SumExpr
@@ -663,5 +675,5 @@ impl Deref for SumExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(SumExpr => Expr, SumExpr => CanonicalExpr, 
SumExpr => PrimExpr);
+tvm_ffi::unsafe_impl_object_upcast!(SumExpr => Expr, SumExpr => CanonicalExpr, 
SumExpr => PrimExpr);
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/sym/constraint.rs b/rust/src/sym/constraint.rs
index 5822dd286..dbc482c53 100644
--- a/rust/src/sym/constraint.rs
+++ b/rust/src/sym/constraint.rs
@@ -121,6 +121,10 @@ unsafe impl UnsafeInit for ConstraintObj {
         }
     }
 }
+
+// SAFETY: `sym.Constraint` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ConstraintObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.BindingConstraint
@@ -175,7 +179,11 @@ impl BindingConstraint {
     }
 }
 
-tvm_ffi::impl_object_upcast!(BindingConstraint => Constraint);
+// SAFETY: `sym.BindingConstraint` is not `_type_mutable`: a shared instance 
is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for BindingConstraintObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(BindingConstraint => Constraint);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.PredicateConstraint
@@ -230,7 +238,11 @@ impl PredicateConstraint {
     }
 }
 
-tvm_ffi::impl_object_upcast!(PredicateConstraint => Constraint);
+// SAFETY: `sym.PredicateConstraint` is not `_type_mutable`: a shared instance 
is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for PredicateConstraintObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(PredicateConstraint => Constraint);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.RangeConstraint
@@ -285,7 +297,11 @@ impl RangeConstraint {
     }
 }
 
-tvm_ffi::impl_object_upcast!(RangeConstraint => Constraint);
+// SAFETY: `sym.RangeConstraint` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RangeConstraintObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(RangeConstraint => Constraint);
 // tvm-ffi-stubgen(end)
 
 /// The object of a [`ConstraintContext`], with the layout of the native one 
and a stable identity.
@@ -378,8 +394,7 @@ impl ConstraintContextObj {
 
     /// Capture the visible prefix in an independently extensible child.
     pub fn snapshot(&self) -> Result<ConstraintContext> {
-        let parent = AnyView::from(self)
-            .try_as::<ConstraintContext>()
+        let parent = ConstraintContext::try_from(AnyView::from(self))
             .expect("context has its registered native type");
         ConstraintContext::with_parent_prefix_size(parent, self.local_size())
     }
@@ -394,8 +409,7 @@ impl ConstraintContextObj {
     /// The callback may append without entering this iteration; each borrowed 
constraint is valid
     /// only during its callback.
     pub fn for_each(&self, mut callback: impl FnMut(&Constraint)) {
-        let retained = AnyView::from(self)
-            .try_as::<ConstraintContext>()
+        let retained = ConstraintContext::try_from(AnyView::from(self))
             .expect("context has its registered native type");
         retained.for_each_prefix(retained.local_size(), &mut callback);
     }
diff --git a/rust/src/sym/int_set.rs b/rust/src/sym/int_set.rs
index c45ffd50f..c0ed0f2ba 100644
--- a/rust/src/sym/int_set.rs
+++ b/rust/src/sym/int_set.rs
@@ -163,7 +163,11 @@ impl IntervalSetObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(IntervalSet => IntSet);
+// SAFETY: `sym.IntervalSet` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IntervalSetObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(IntervalSet => IntSet);
 // tvm-ffi-stubgen(end)
 
 impl IntervalSet {
diff --git a/rust/src/sym/iter_affine_map.rs b/rust/src/sym/iter_affine_map.rs
index bd1de01ba..b9ef5ca39 100644
--- a/rust/src/sym/iter_affine_map.rs
+++ b/rust/src/sym/iter_affine_map.rs
@@ -122,7 +122,7 @@ impl Deref for IterMapExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(IterMapExpr => Expr, IterMapExpr => PrimExpr);
+tvm_ffi::unsafe_impl_object_upcast!(IterMapExpr => Expr, IterMapExpr => 
PrimExpr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.IterMapResult
@@ -224,6 +224,10 @@ impl IterMark {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `sym.IterMark` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IterMarkObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.IterSplitExpr
@@ -274,7 +278,11 @@ impl IterSplitExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(IterSplitExpr => Expr, IterSplitExpr => 
IterMapExpr, IterSplitExpr => PrimExpr);
+// SAFETY: `sym.IterSplitExpr` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IterSplitExprObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(IterSplitExpr => Expr, IterSplitExpr => 
IterMapExpr, IterSplitExpr => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/sym.IterSumExpr
@@ -317,7 +325,11 @@ impl IterSumExprObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(IterSumExpr => Expr, IterSumExpr => IterMapExpr, 
IterSumExpr => PrimExpr);
+// SAFETY: `sym.IterSumExpr` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IterSumExprObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(IterSumExpr => Expr, IterSumExpr => 
IterMapExpr, IterSumExpr => PrimExpr);
 // tvm-ffi-stubgen(end)
 
 // `IterMark::new` is generated: the native `sym.IterMark` constructor stores 
the fields as given.
diff --git a/rust/src/tirx/function.rs b/rust/src/tirx/function.rs
index 683f5e939..b57671869 100644
--- a/rust/src/tirx/function.rs
+++ b/rust/src/tirx/function.rs
@@ -148,7 +148,11 @@ impl Function {
     }
 }
 
-tvm_ffi::impl_object_upcast!(Function => Expr, Function => BaseFunc);
+// SAFETY: `tirx.Function` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FunctionObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(Function => Expr, Function => BaseFunc);
 // tvm-ffi-stubgen(end)
 
 impl Function {
diff --git a/rust/src/tirx/index_map.rs b/rust/src/tirx/index_map.rs
index ad76da310..6fe599159 100644
--- a/rust/src/tirx/index_map.rs
+++ b/rust/src/tirx/index_map.rs
@@ -38,7 +38,7 @@
 //! assert_eq!(script(&transpose)?, "T.index_map(lambda i, j: (j, i))");
 //!
 //! let text =
-//!     |exprs: Array<PrimExpr>| exprs.iter().map(|e| 
script(&e)).collect::<Result<Vec<_>>>();
+//!     |exprs: Array<PrimExpr>| 
exprs.iter().map(script).collect::<Result<Vec<_>>>();
 //! assert_eq!(text(transpose.map_indices([1, 2], None)?)?, ["2", "1"]);
 //!
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
@@ -128,6 +128,10 @@ impl IndexMap {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `tirx.IndexMap` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IndexMapObj {}
 // tvm-ffi-stubgen(end)
 
 // The native `tirx.IndexMap` stores its fields as given, as the generated 
`IndexMap::new` does.
diff --git a/rust/src/tirx/layout.rs b/rust/src/tirx/layout.rs
index 5789e0336..4147dfbfb 100644
--- a/rust/src/tirx/layout.rs
+++ b/rust/src/tirx/layout.rs
@@ -261,7 +261,11 @@ impl TileLayoutObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TileLayout => Layout);
+// SAFETY: `tirx.TileLayout` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TileLayoutObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TileLayout => Layout);
 // tvm-ffi-stubgen(end)
 
 impl TileLayout {
@@ -376,6 +380,10 @@ impl Iter {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `tirx.Iter` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for IterObj {}
 // tvm-ffi-stubgen(end)
 
 // The native `tirx.Iter` stores its fields as given, as the generated 
`Iter::new` does.
@@ -402,6 +410,10 @@ impl Deref for Axis {
         &self.base
     }
 }
+
+// SAFETY: `tirx.Axis` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for AxisObj {}
 // tvm-ffi-stubgen(end)
 
 impl Axis {
@@ -498,7 +510,11 @@ impl ComposeLayoutObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(ComposeLayout => Layout);
+// SAFETY: `tirx.ComposeLayout` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ComposeLayoutObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(ComposeLayout => Layout);
 // tvm-ffi-stubgen(end)
 
 impl ComposeLayout {
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index 8c437ef8e..ac7bb8c6b 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -169,7 +169,11 @@ impl CallFFIKernelAttr {
     }
 }
 
-tvm_ffi::impl_object_upcast!(CallFFIKernelAttr => Attrs);
+// SAFETY: `tirx.CallFFIKernelAttr` is not `_type_mutable`: a shared instance 
is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for CallFFIKernelAttrObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(CallFFIKernelAttr => Attrs);
 // tvm-ffi-stubgen(end)
 
 // The native `tirx.CallFFIKernelAttr` stores its fields as given, as the 
generated
diff --git a/rust/src/tirx/structural_walk_with_constraint_context.rs 
b/rust/src/tirx/structural_walk_with_constraint_context.rs
index 5352d20aa..cc650f209 100644
--- a/rust/src/tirx/structural_walk_with_constraint_context.rs
+++ b/rust/src/tirx/structural_walk_with_constraint_context.rs
@@ -102,11 +102,11 @@ where
             }
             WalkResult::Interrupt => {
                 core::hint::cold_path();
-                Ok(Some(VisitInterrupt::with(Any::new())))
+                Ok(Some(VisitInterrupt::new(Any::new())))
             }
             WalkResult::InterruptWith(payload) => {
                 core::hint::cold_path();
-                Ok(Some(VisitInterrupt::with(payload)))
+                Ok(Some(VisitInterrupt::new(payload)))
             }
         }
     }
diff --git a/rust/src/tirx/tensor.rs b/rust/src/tirx/tensor.rs
index d5de159c1..278bd697b 100644
--- a/rust/src/tirx/tensor.rs
+++ b/rust/src/tirx/tensor.rs
@@ -165,7 +165,11 @@ impl TensorType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorType => Type);
+// SAFETY: `tirx.TensorType` is not `_type_mutable`: a shared instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorType => Type);
 // tvm-ffi-stubgen(end)
 
 /// The runtime's allocation alignment (C++ `runtime::kAllocAlignment`, which 
a build may set),
diff --git a/rust/src/tirx/thread_scope.rs b/rust/src/tirx/thread_scope.rs
index 7c71de7bb..761b55dbb 100644
--- a/rust/src/tirx/thread_scope.rs
+++ b/rust/src/tirx/thread_scope.rs
@@ -83,6 +83,10 @@ impl Deref for ThreadScope {
         &self.base
     }
 }
+
+// SAFETY: `tirx.ThreadScope` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ThreadScopeObj {}
 // tvm-ffi-stubgen(end)
 
 impl ThreadScope {
diff --git a/rust/src/tirx/tile/dispatch.rs b/rust/src/tirx/tile/dispatch.rs
index fc195b5ca..25eb54ed4 100644
--- a/rust/src/tirx/tile/dispatch.rs
+++ b/rust/src/tirx/tile/dispatch.rs
@@ -51,7 +51,7 @@
 // does.
 // tvm-ffi-stubgen(import-object): crate::ir::stmt::Stmt
 // tvm-ffi-stubgen(import-object): tvm_ffi::AnyView
-// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectRefCast
+// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectRefCore
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 // tvm-ffi-stubgen(import-object): crate::ir::expr::PrimExpr
 // tvm-ffi-stubgen(import-object): crate::ir::expr::PrimVar
@@ -78,7 +78,7 @@ use tvm_ffi::Map;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectCore;
-use tvm_ffi::ObjectRefCast;
+use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
 use tvm_ffi::object::ObjectRef;
@@ -148,6 +148,10 @@ impl DispatchContextObj {
         FieldGetter::new(Self::type_index(), "scope_kind")?.get(self)
     }
 }
+
+// SAFETY: `tirx.DispatchContext` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for DispatchContextObj {}
 // tvm-ffi-stubgen(end)
 
 impl DispatchContext {
@@ -255,7 +259,11 @@ impl DispatchContext {
     }
 
     /// Cache `value` under `key` across dispatches 
(`tirx.DispatchContextSharedStateSet`).
-    pub fn cache_set(&self, key: &str, value: &impl ObjectRefCast) -> 
Result<()> {
+    pub fn cache_set(
+        &self,
+        key: &str,
+        value: &(impl ObjectRefCore + tvm_ffi::type_traits::AnyCompatible),
+    ) -> Result<()> {
         let key = String::from(key);
         
tvm_ffi::cached_global_func!("tirx.DispatchContextSharedStateSet").call_packed(&[
             AnyView::from(self),
diff --git a/rust/src/tirx/tile/instruction.rs 
b/rust/src/tirx/tile/instruction.rs
index 1cb906312..aece34084 100644
--- a/rust/src/tirx/tile/instruction.rs
+++ b/rust/src/tirx/tile/instruction.rs
@@ -105,7 +105,11 @@ impl ScopeAttrs {
     }
 }
 
-tvm_ffi::impl_object_upcast!(ScopeAttrs => Attrs);
+// SAFETY: `tirx.tensor.ScopeAttrs` is not `_type_mutable`: a shared instance 
is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for ScopeAttrsObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(ScopeAttrs => Attrs);
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/tirx.tensor.TrnAttrs
@@ -197,7 +201,11 @@ impl TrnAttrs {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TrnAttrs => Attrs);
+// SAFETY: `tirx.tensor.TrnAttrs` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TrnAttrsObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TrnAttrs => Attrs);
 // tvm-ffi-stubgen(end)
 
 /// Return the tensor region `value` covers: a `TensorLoad` converted, a 
[`TensorRegion`] as is
diff --git a/rust/src/tirx/transform/function_pass.rs 
b/rust/src/tirx/transform/function_pass.rs
index 33465d7c8..8fd6701b8 100644
--- a/rust/src/tirx/transform/function_pass.rs
+++ b/rust/src/tirx/transform/function_pass.rs
@@ -106,7 +106,11 @@ impl FunctionPassObj {
     }
 }
 
-tvm_ffi::impl_object_upcast!(FunctionPass => Pass);
+// SAFETY: `tirx.FunctionPass` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for FunctionPassObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(FunctionPass => Pass);
 // tvm-ffi-stubgen(end)
 
 impl FunctionPass {
diff --git a/rust/src/tirx/transform/transform.rs 
b/rust/src/tirx/transform/transform.rs
index c836614e8..f8e74876a 100644
--- a/rust/src/tirx/transform/transform.rs
+++ b/rust/src/tirx/transform/transform.rs
@@ -27,7 +27,7 @@
 //!
 //! ```
 //! use tvm_tirx::tirx::{transform, Function};
-//! use tvm_tirx::tvm_ffi::ObjectRefCast;
+//! use tvm_tirx::tvm_ffi::{Error, ObjectRefCore};
 //! # use tvm_tirx::ir::{BaseFunc, DictAttrs, Evaluate, GlobalVar, IRModule, 
MissingType, SeqStmt};
 //! # use tvm_tirx::ir::{Var, UNKNOWN_LOC};
 //! # use tvm_tirx::tvm_ffi::Map;
@@ -38,7 +38,7 @@
 //! # let module = IRModule::from_functions(&functions.into_iter().collect(), 
None, &Map::new())?;
 //!
 //! let module = transform::remove_no_op()?.run(module)?;
-//! let main: Function = module.lookup_name("main")?.try_cast()?;
+//! let main: Function = module.lookup_name("main")?.try_into()?;
 //! assert!(main.body.seq.is_empty());
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 //! ```
@@ -106,6 +106,10 @@ impl RemoveNoOpConfig {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `tirx.transform.RemoveNoOpConfig` is not `_type_mutable`: a shared 
instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for RemoveNoOpConfigObj {}
 // tvm-ffi-stubgen(end)
 
 // tvm-ffi-stubgen(begin): object/tirx.transform.UnrollLoopConfig
@@ -180,6 +184,10 @@ impl UnrollLoopConfig {
         Self { base: ObjectArc::new(obj) }
     }
 }
+
+// SAFETY: `tirx.transform.UnrollLoopConfig` is not `_type_mutable`: a shared 
instance is never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for UnrollLoopConfigObj {}
 // tvm-ffi-stubgen(end)
 
 /// Infer and mark an unambiguous TIRx entry function 
(`tirx.transform.AnnotateEntryFunc`).
diff --git a/rust/src/tirx/type.rs b/rust/src/tirx/type.rs
index ba655ff0a..7c0740b79 100644
--- a/rust/src/tirx/type.rs
+++ b/rust/src/tirx/type.rs
@@ -93,7 +93,11 @@ impl TensorMapType {
     }
 }
 
-tvm_ffi::impl_object_upcast!(TensorMapType => Type);
+// SAFETY: `tirx.TensorMapType` is not `_type_mutable`: a shared instance is 
never
+// mutated, as C++ changes it only through copy-on-write.
+unsafe impl tvm_ffi::object::ImmutableObject for TensorMapTypeObj {}
+
+tvm_ffi::unsafe_impl_object_upcast!(TensorMapType => Type);
 // tvm-ffi-stubgen(end)
 
 // The native `tirx.TensorMapType` stores the location as given, as the 
generated
diff --git a/rust/tests/ir/expr.rs b/rust/tests/ir/expr.rs
index d1c3a6acc..eaefbf6b1 100644
--- a/rust/tests/ir/expr.rs
+++ b/rust/tests/ir/expr.rs
@@ -22,8 +22,8 @@
 use tvm_tirx::ir::prim::Broadcast;
 use tvm_tirx::ir::*;
 use tvm_tirx::tvm_ffi::{
-    Any, AnyView, Array, BigInt, DLDataType, Function, ObjectRefCast, 
ObjectRefCore, Result,
-    String as FfiString, TYPE_ERROR,
+    Any, AnyView, Array, BigInt, DLDataType, Function, ObjectRefCore, Result, 
String as FfiString,
+    TYPE_ERROR,
 };
 
 use super::{int32, int32_var, op, parse_dtype, tensor_var};
@@ -215,7 +215,7 @@ fn builtin_constants() {
     agree("Expr::from(bool)", Ok(Expr::from(false)), int(dtype::bool(), 0));
     agree("Expr::from(f32)", Ok(Expr::from(0.5f32)), float(dtype::float(32), 
0.5));
     agree("Expr::from(f64)", Ok(Expr::from(0.5f64)), float(dtype::float(64), 
0.5));
-    let folded: IntImm = op("prim._OpAdd", 1i32, 2i32).try_cast().unwrap();
+    let folded: IntImm = op("prim._OpAdd", 1i32, 2i32).downcast().unwrap();
     assert_eq!(folded.value, BigInt::from(3));
 }
 
@@ -348,7 +348,7 @@ fn test_op(name: &str, attrs: &[(&str, Any)]) -> Op {
     }
     Op::register(name, "").unwrap();
     let op = Op::get(name).unwrap();
-    let set_attr = Function::from_type_key_method("ir.Op", 
"_set_attr").unwrap();
+    let set_attr = tvm_ffi::reflection::get_method("ir.Op", 
"_set_attr").unwrap();
     for (key, value) in attrs {
         let key = FfiString::from(*key);
         set_attr
@@ -365,15 +365,14 @@ fn calls_agree_with_cpp() {
     let f32_ty: Type = PrimType::float(32).into();
     let one: Expr = FloatImm::float32(1.0).into();
     let i16_ty: Type = PrimType::int(16).into();
-    let returns = |value: Any| {
-        Function::from_packed(move |_args: &[AnyView]| -> Result<Any> { 
Ok(value.clone()) })
-    };
-    let packed_infer = test_op(
-        "rust_test.packed_infer",
-        &[("FInferType", returns(Any::from(i16_ty.clone())).into())],
-    );
+    // A callback captures thread-safe state: a typed value, converted on each 
call.
+    fn returns<T: Clone + Into<Any> + Send + Sync + 'static>(value: T) -> 
Function {
+        Function::from_packed(move |_args: &[AnyView]| -> Result<Any> { 
Ok(value.clone().into()) })
+    }
+    let packed_infer =
+        test_op("rust_test.packed_infer", &[("FInferType", 
returns(i16_ty.clone()).into())]);
     let packed_mistyped =
-        test_op("rust_test.packed_mistyped", &[("FInferType", 
returns(Any::from(5i64)).into())]);
+        test_op("rust_test.packed_mistyped", &[("FInferType", 
returns(5i64).into())]);
     let fixed =
         test_op("rust_test.fixed_return", &[("TFixedReturnType", 
Any::from(f32_ty.clone()))]);
     let plain = test_op("rust_test.no_inference", &[]);
@@ -547,8 +546,8 @@ fn operators_take_views() {
     let error = PrimExpr::try_from(Expr::from(tensor_var("A"))).err().unwrap();
     assert_eq!(error.kind(), TYPE_ERROR);
     assert!(error.message().contains("ir.Var[ty=tirx.TensorType]"), "{error}");
-    // `ObjectRefCast` downcasts between views as it does between node types.
-    let back: IntExpr = sum.try_cast().unwrap();
+    // `ObjectRefCore` downcasts between views as it does between node types.
+    let back: IntExpr = sum.downcast().unwrap();
     assert_eq!(back.dtype(), dtype::int(32));
 }
 
@@ -571,17 +570,17 @@ fn prim_vars_are_views() {
     assert!(i.as_expr().same_as(&i));
 
     // A `Var` narrows only through the check of C++ `PrimVar`: `downcast_ref` 
borrows it in place
-    // and `try_cast` takes it.
+    // and `downcast` takes it.
     assert!(var.downcast_ref::<PrimVar>().unwrap().same_as(&i));
     let vector = Var::new("v", PrimType::float(32).with_lanes(4).unwrap(), 
UNKNOWN_LOC);
-    assert_eq!(vector.try_cast::<PrimVar>().unwrap().dtype().lanes, 4);
+    assert_eq!(vector.downcast::<PrimVar>().unwrap().dtype().lanes, 4);
     let untyped = Var::new("u", MissingType::new(), UNKNOWN_LOC);
     assert!(untyped.downcast_ref::<PrimVar>().is_none());
-    let error = untyped.try_cast::<PrimVar>().err().unwrap();
+    let error = PrimVar::try_from(&untyped).unwrap_err();
     assert_eq!(error.kind(), TYPE_ERROR);
     // C++ `TypeTraits<PrimVar>` names the variable class on both sides.
     assert_eq!(error.message(), "Cannot convert from type `ir.Var` to 
`ir.Var`");
-    assert!(tensor_var("A").try_cast::<PrimVar>().is_err());
+    assert!(tensor_var("A").downcast::<PrimVar>().is_err());
 
     // It takes part in arithmetic as a `PrimExpr`.
     let sum = PrimExpr::try_from(&i * 4 + 1).unwrap();
diff --git a/rust/tests/ir/op_def.rs b/rust/tests/ir/op_def.rs
index 4d50c2ef9..994e3f158 100644
--- a/rust/tests/ir/op_def.rs
+++ b/rust/tests/ir/op_def.rs
@@ -39,8 +39,8 @@ use tvm_tirx::tvm_ffi::static_init::{StaticInitBlock, 
STATIC_INIT_BLOCKS};
 use tvm_tirx::tvm_ffi::tvm_ffi_sys::TVMFFITypeIndex;
 use tvm_tirx::tvm_ffi::{
     self, def_object_ref, rs_object, run_static_init_blocks, Any, AnyView, 
Array, Error, ErrorKind,
-    Function, Map, ObjectCore, ObjectRefCast, ObjectRefCore, Result, String as 
FfiString,
-    ATTRIBUTE_ERROR, INTERNAL_ERROR, TYPE_ERROR, VALUE_ERROR,
+    Function, Map, ObjectCore, ObjectRefCore, Result, String as FfiString, 
ATTRIBUTE_ERROR,
+    INTERNAL_ERROR, TYPE_ERROR, VALUE_ERROR,
 };
 
 use super::{int32, parse_dtype, tensor_var};
@@ -487,8 +487,8 @@ fn is_block_registered(block: &StaticInitBlock) -> bool {
 }
 
 fn launch_body_params(call: &CallObj) -> Result<Array<Var>> {
-    let tag = call.args.get(0)?.try_cast::<StringImm>()?;
-    let extent_ty: PrimType = call.args.get(1)?.ty.clone().try_cast()?;
+    let tag = StringImm::try_from(call.args.get(0)?)?;
+    let extent_ty: PrimType = PrimType::try_from(&call.args.get(1)?.ty)?;
     let dtype = extent_ty.dtype;
     if tag.value.as_str().is_empty() {
         let message = "Check failed: (!tag->value.empty()) is false: 
launch_thread expects a \
@@ -616,7 +616,11 @@ fn static_init_blocks_define_operators_and_types() {
 
     // The type `register_type` registers with them: tvm-ffi constructs it 
from its fields,
     // positionally or by keyword, and reads it back from a JSON graph.
-    let init = Function::from_type_attr(BlockTypeObj::type_index(), 
"__ffi_init__").unwrap();
+    let init: Function = 
tvm_ffi::reflection::TypeAttrColumn::new("__ffi_init__")
+        .and_then(|column| column.get(BlockTypeObj::type_index()))
+        .unwrap()
+        .try_into()
+        .unwrap();
     let kwargs = global("ffi.GetKwargsObject").call_tuple(()).unwrap();
     let (elem, count, four) = (Type::from(PrimType::int(32)), 
FfiString::from("count"), 4i64);
     let built = init
diff --git a/rust/tests/ir/prim.rs b/rust/tests/ir/prim.rs
index 753d72f80..58bbc3ae6 100644
--- a/rust/tests/ir/prim.rs
+++ b/rust/tests/ir/prim.rs
@@ -26,7 +26,7 @@ mod vector_expr;
 
 use tvm_tirx::ir::prim;
 use tvm_tirx::ir::{UNKNOWN_LOC, Expr, IntImm, PrimType, PrimVar};
-use tvm_tirx::tvm_ffi::{DLDataTypeExt, ObjectRefCast};
+use tvm_tirx::tvm_ffi::{DLDataTypeExt, ObjectRefCore};
 
 use crate::common::serial;
 
@@ -37,12 +37,12 @@ fn var(name: &str, ty: PrimType) -> PrimVar {
 
 /// The dtype of a primitive expression, by name.
 fn dtype(expr: &impl AsRef<Expr>) -> String {
-    let ty: PrimType = expr.as_ref().ty.clone().try_cast().unwrap();
+    let ty: PrimType = expr.as_ref().ty.clone().downcast().unwrap();
     ty.dtype.to_string().as_str().to_owned()
 }
 
 fn int_value(expr: impl Into<Expr>) -> i64 {
-    let imm: IntImm = expr.into().try_cast().unwrap();
+    let imm: IntImm = expr.into().downcast().unwrap();
     imm.value.to_i64().unwrap()
 }
 
diff --git a/rust/tests/ir/prim/op.rs b/rust/tests/ir/prim/op.rs
index 6b546de34..6f2316e00 100644
--- a/rust/tests/ir/prim/op.rs
+++ b/rust/tests/ir/prim/op.rs
@@ -31,8 +31,7 @@ use tvm_tirx::ir::{
 };
 use tvm_tirx::sym;
 use tvm_tirx::tvm_ffi::{
-    Any, AnyCompatible, AnyView, BigInt, ObjectRefCast, ObjectRefCore, Result, 
String as FfiString,
-    TYPE_ERROR,
+    Any, AnyCompatible, AnyView, BigInt, ObjectRefCore, Result, String as 
FfiString, TYPE_ERROR,
 };
 
 use super::{dtype, int_value, var};
@@ -431,20 +430,20 @@ fn located_variants() {
     let _serial = serial();
     let x = var("x", PrimType::int(32));
     let here: Location = SourceLoc::new(SourceName::get("ops.rs").unwrap(), 1, 
2, 3, 4).into();
-    let located: prim::FloorDiv = op::floordiv_at(&x, 4, 
here.clone()).unwrap().try_cast().unwrap();
+    let located: prim::FloorDiv = op::floordiv_at(&x, 4, 
here.clone()).unwrap().downcast().unwrap();
     assert!(located.loc.same_as(&here));
-    let unlocated: prim::FloorDiv = op::floordiv(&x, 
4).unwrap().try_cast().unwrap();
+    let unlocated: prim::FloorDiv = op::floordiv(&x, 
4).unwrap().downcast().unwrap();
     assert!(unlocated.loc.downcast_ref::<UnknownLocObj>().is_some());
     // A folded constant is at the unknown location, as C++ `TryConstFold` 
builds it.
-    let folded: IntImm = op::add_at(1, 2, 
here.clone()).unwrap().try_cast().unwrap();
+    let folded: IntImm = op::add_at(1, 2, 
here.clone()).unwrap().downcast().unwrap();
     assert!(folded.loc.downcast_ref::<UnknownLocObj>().is_some());
-    let cast: Cast = op::cast_at(&x, PrimType::int(64), 
here.clone()).unwrap().try_cast().unwrap();
+    let cast: Cast = op::cast_at(&x, PrimType::int(64), 
here.clone()).unwrap().downcast().unwrap();
     assert!(cast.loc.same_as(&here));
     let call: Call =
-        op::exp_at(var("f", PrimType::float(32)), 
here.clone()).unwrap().try_cast().unwrap();
+        op::exp_at(var("f", PrimType::float(32)), 
here.clone()).unwrap().downcast().unwrap();
     assert!(call.loc.same_as(&here));
     let limit: IntImm =
-        op::max_value_at(PrimType::int(8), 
here.clone()).unwrap().try_cast().unwrap();
+        op::max_value_at(PrimType::int(8), 
here.clone()).unwrap().downcast().unwrap();
     assert!(limit.loc.same_as(&here));
 }
 
@@ -859,11 +858,11 @@ fn arithmetic_operators() {
     assert_eq!(folded!(op::shift_left, 1, 3), 8);
     assert_eq!(folded!(op::shift_right, 8, 3), 1);
     let not = op::bitwise_not(0).unwrap();
-    assert!(not.try_cast::<prim::BitwiseNot>().is_ok(), "the node, not 
folded");
+    assert!(not.downcast::<prim::BitwiseNot>().is_ok(), "the node, not 
folded");
 
     // On a variable they build the nodes.
     let floordiv = op::floordiv(&x, 2).unwrap();
-    assert!(floordiv.try_cast::<prim::FloorDiv>().is_ok());
+    assert!(floordiv.downcast::<prim::FloorDiv>().is_ok());
     assert_eq!(dtype(&op::pow(2.0f32, 3.0f32).unwrap()), "float32");
 }
 
@@ -875,11 +874,11 @@ fn comparison_and_logic_operators() {
     for cmp in [op::eq, op::ne, op::lt, op::le, op::gt, op::ge] {
         assert_eq!(dtype(&cmp(PrimExpr::from(&x), one.clone()).unwrap()), 
"bool");
     }
-    assert!(op::lt(&x, &one).unwrap().try_cast::<prim::LT>().is_ok());
+    assert!(op::lt(&x, &one).unwrap().downcast::<prim::LT>().is_ok());
 
     let conds = [op::lt(&x, &one).unwrap(), op::gt(&x, -5).unwrap()];
-    assert!(op::all(conds.clone()).unwrap().try_cast::<prim::And>().is_ok());
-    assert!(op::any(conds.clone()).unwrap().try_cast::<prim::Or>().is_ok());
+    assert!(op::all(conds.clone()).unwrap().downcast::<prim::And>().is_ok());
+    assert!(op::any(conds.clone()).unwrap().downcast::<prim::Or>().is_ok());
     assert!(structural_equal(&op::all([&conds[0]]).unwrap(), &conds[0]));
     assert!(op::any(Vec::<PrimExpr>::new()).is_err());
     assert!(structural_equal(
@@ -916,7 +915,7 @@ fn math_operators() {
     assert_eq!(int_value(op::abs(-3).unwrap()), 3);
     assert_eq!(int_value(op::max_value(PrimType::int(8)).unwrap()), 127);
     assert_eq!(int_value(op::min_value(PrimType::int(8)).unwrap()), -128);
-    let inf: FloatImm = 
op::infinity(PrimType::float(32)).unwrap().try_cast().unwrap();
+    let inf: FloatImm = 
op::infinity(PrimType::float(32)).unwrap().downcast().unwrap();
     assert!(inf.value.is_infinite());
 }
 
@@ -932,26 +931,26 @@ fn make_const() {
     assert!(op::make_const(PrimType::uint(8), -1).is_err());
     assert!(op::make_const(PrimType::int(8), 128).is_err());
     for ty in [PrimType::float(16), PrimType::bfloat(16), PrimType::float(64)] 
{
-        let two: FloatImm = op::make_const(ty.clone(), 
2).unwrap().try_cast().unwrap();
+        let two: FloatImm = op::make_const(ty.clone(), 
2).unwrap().downcast().unwrap();
         assert_eq!(two.value, 2.0);
         assert!(structural_equal(&two.ty, &ty));
     }
     // A float value too, as C++ `MakeConst(ty, double)`.
-    let half: FloatImm = op::make_const(PrimType::float(32), 
0.5).unwrap().try_cast().unwrap();
+    let half: FloatImm = op::make_const(PrimType::float(32), 
0.5).unwrap().downcast().unwrap();
     assert_eq!(half.value, 0.5);
     assert_eq!(int_value(op::make_const(PrimType::int(32), 2.0).unwrap()), 2);
     assert!(op::make_const(PrimType::void(), 0).is_err());
 
     let vector = op::make_const(PrimType::int(32).with_lanes(4).unwrap(), 
1).unwrap();
     assert_eq!(dtype(&vector), "int32x4");
-    let broadcast: prim::Broadcast = vector.try_cast().unwrap();
+    let broadcast: prim::Broadcast = vector.downcast().unwrap();
     assert_eq!(int_value(broadcast.value.clone()), 1);
     assert_eq!(int_value(broadcast.lanes.clone()), 4);
 
     let scalable = PrimType::int(32).with_scalable_lanes(4).unwrap();
     let vector = op::make_const(scalable.clone(), 1).unwrap();
     assert!(structural_equal(&vector.ty, &scalable));
-    let broadcast: prim::Broadcast = vector.try_cast().unwrap();
+    let broadcast: prim::Broadcast = vector.downcast().unwrap();
     let lanes = broadcast.lanes.downcast_ref::<prim::MulObj>().expect("vscale 
* 4");
     assert_eq!(int_value(lanes.b.clone()), 4);
 }
diff --git a/rust/tests/sym/int_set.rs b/rust/tests/sym/int_set.rs
index 44a0d37a0..7263f5254 100644
--- a/rust/tests/sym/int_set.rs
+++ b/rust/tests/sym/int_set.rs
@@ -21,7 +21,7 @@
 
 use tvm_tirx::ir::{IntImm, Location, PrimExpr, Range, Var};
 use tvm_tirx::sym::*;
-use tvm_tirx::tvm_ffi::{Map, Object, ObjectRefCast};
+use tvm_tirx::tvm_ffi::{Map, Object, ObjectRefCore};
 
 use super::{fixture, int32, op, range, var, Fixture};
 use crate::common::{assert_type_contracts, global};
@@ -47,8 +47,8 @@ fn interval_sets_are_read_through_accessors() {
         .unwrap()
         .try_into()
         .unwrap();
-    let interval: IntervalSet = set.try_cast().unwrap();
-    let as_int = |expr: PrimExpr| 
expr.try_cast::<IntImm>().unwrap().value.to_i64().unwrap();
+    let interval: IntervalSet = set.downcast().unwrap();
+    let as_int = |expr: PrimExpr| 
expr.downcast::<IntImm>().unwrap().value.to_i64().unwrap();
     assert_eq!(as_int(interval.min_value().unwrap()), 2);
     assert_eq!(as_int(interval.max_value().unwrap()), 20);
 }
diff --git a/rust/tests/sym/iter_affine_map.rs 
b/rust/tests/sym/iter_affine_map.rs
index efb85f5f9..8b8ca0b26 100644
--- a/rust/tests/sym/iter_affine_map.rs
+++ b/rust/tests/sym/iter_affine_map.rs
@@ -21,7 +21,7 @@
 
 use tvm_tirx::ir::{ExprObj, IntImm, PrimExpr, PrimVar, Range, Var};
 use tvm_tirx::sym::*;
-use tvm_tirx::tvm_ffi::{Array, Map, Object, ObjectIdentity, ObjectRefCast};
+use tvm_tirx::tvm_ffi::{Array, Map, Object, ObjectIdentity, ObjectRefCore};
 
 use super::{fixture, int32, op, range, var, Fixture};
 use crate::common::{assert_type_contracts, global};
@@ -37,8 +37,8 @@ fn generated_types_match_the_registry() {
 }
 
 fn mark_is(mark: &IterMark, var: &PrimVar, extent: i64) -> bool {
-    let source: PrimVar = mark.source.clone().try_cast().unwrap();
-    let extent_imm: IntImm = mark.extent.clone().try_cast().unwrap();
+    let source: PrimVar = mark.source.clone().downcast().unwrap();
+    let extent_imm: IntImm = mark.extent.clone().downcast().unwrap();
     ObjectIdentity::of(&source) == ObjectIdentity::of(var)
         && extent_imm.value.to_i64() == Some(extent)
 }
@@ -62,7 +62,7 @@ fn iter_maps_decompose_into_marks_and_splits() {
         result.errors.iter().map(|e| e.to_string()).collect::<Vec<_>>()
     );
     assert_eq!(result.indices.len(), 2);
-    let as_int = |expr: PrimExpr| 
expr.try_cast::<IntImm>().unwrap().value.to_i64().unwrap();
+    let as_int = |expr: PrimExpr| 
expr.downcast::<IntImm>().unwrap().value.to_i64().unwrap();
     // Each index is one split of a mark of extent 4, whose source is itself a 
sum over the
     // mark of x on [0, 16): x // 4 takes lower factor 4 of it, x % 4 lower 
factor 1.
     let only_split = |sum: &IterSumExpr| -> IterSplitExpr {
@@ -76,7 +76,7 @@ fn iter_maps_decompose_into_marks_and_splits() {
         let outer = only_split(&sum);
         let mark: IterMark = outer.source().unwrap();
         assert_eq!(as_int(mark.extent.clone()), 4);
-        let inner_sum: IterSumExpr = mark.source.clone().try_cast().unwrap();
+        let inner_sum: IterSumExpr = mark.source.clone().downcast().unwrap();
         let inner = only_split(&inner_sum);
         let x_mark: IterMark = inner.source().unwrap();
         assert!(mark_is(&x_mark, &x, 16));
diff --git a/rust/tests/target/main.rs b/rust/tests/target/main.rs
index 9740e2e0e..c05d203da 100644
--- a/rust/tests/target/main.rs
+++ b/rust/tests/target/main.rs
@@ -24,7 +24,7 @@
 mod common;
 
 use tvm_tirx::target::{self, Target, TargetKind, TargetKindObj, TargetObj};
-use tvm_tirx::tvm_ffi::{Any, Map, Object, ObjectRefCast, String as FfiString, 
TypeIndex};
+use tvm_tirx::tvm_ffi::{Any, Map, Object, ObjectRefCore, String as FfiString, 
TypeIndex};
 
 use common::{assert_type_contracts, empty_module, global, load_llvm_backend};
 
@@ -70,9 +70,9 @@ fn native_targets_are_read_through_accessors() {
         .unwrap()
         .try_into()
         .unwrap();
-    let host: Target = with_host.host().unwrap().unwrap().try_cast().unwrap();
+    let host: Target = with_host.host().unwrap().unwrap().downcast().unwrap();
     assert_eq!(host.kind().unwrap().name().unwrap().as_str(), "llvm");
-    
assert!(with_host.host().unwrap().unwrap().try_cast::<TargetKind>().is_err());
+    
assert!(with_host.host().unwrap().unwrap().downcast::<TargetKind>().is_err());
 }
 
 #[test]
diff --git a/rust/tests/tirx/main.rs b/rust/tests/tirx/main.rs
index 8a58df347..a827743aa 100644
--- a/rust/tests/tirx/main.rs
+++ b/rust/tests/tirx/main.rs
@@ -45,7 +45,7 @@ use tvm_tirx::ir::{
 };
 use tvm_tirx::runtime::MemoryScope;
 use tvm_tirx::tirx::{Axis, Function, Iter, TileLayout};
-use tvm_tirx::tvm_ffi::{Array, Map, ObjectRefCast, String as FfiString};
+use tvm_tirx::tvm_ffi::{Array, Map, ObjectRefCore, String as FfiString};
 
 use common::global;
 
@@ -62,7 +62,7 @@ fn var(name: &str, ty: impl Into<Type>) -> Var {
 }
 
 fn prim_var(name: &str, ty: impl Into<PrimType>) -> PrimVar {
-    var(name, ty.into()).try_cast().unwrap()
+    var(name, ty.into()).downcast().unwrap()
 }
 
 fn op(name: &str, a: impl Into<Expr>, b: impl Into<Expr>) -> PrimExpr {
@@ -77,8 +77,8 @@ fn range(extent: i64) -> Range {
         .unwrap()
 }
 
-fn as_i64(expr: &(impl ObjectRefCast + Clone)) -> i64 {
-    expr.clone().try_cast::<IntImm>().unwrap().value.to_i64().unwrap()
+fn as_i64(expr: &impl ObjectRefCore) -> i64 {
+    expr.downcast_ref::<IntImm>().unwrap().value.to_i64().unwrap()
 }
 
 /// A new scope of the test's own, since the builtin ones cannot be registered 
again.
diff --git a/rust/tests/tirx/op.rs b/rust/tests/tirx/op.rs
index 8629889be..7a3cb1c58 100644
--- a/rust/tests/tirx/op.rs
+++ b/rust/tests/tirx/op.rs
@@ -25,9 +25,7 @@ use tvm_tirx::ir::{
     StringImm, Type, UNKNOWN_LOC,
 };
 use tvm_tirx::tirx::{fast_erf_float_expr, gpu_thread_return, logaddexp, 
reinterpret};
-use tvm_tirx::tvm_ffi::{
-    AnyView, DLDataTypeExt, Function, ObjectRefCast, ObjectRefCore, String as 
FfiString,
-};
+use tvm_tirx::tvm_ffi::{AnyView, DLDataTypeExt, Function, ObjectRefCore, 
String as FfiString};
 
 use super::{memory_scope, var};
 use crate::common::{agree, global, serial, structural_equal};
@@ -154,5 +152,5 @@ fn fast_erf_follows_the_native_lowering() {
     // The operator chain checks its operands as C++ does: an integer argument 
is matched to the
     // float constants; a non-primitive one now fails the `PrimVar` check 
before the call.
     assert!(fast_erf_float_expr(prim_var("int32"), 32).is_ok());
-    assert!(var("s", ptr("int8")).try_cast::<PrimVar>().is_err());
+    assert!(var("s", ptr("int8")).downcast::<PrimVar>().is_err());
 }
diff --git a/rust/tests/tirx/structural_map_with_constraint_context.rs 
b/rust/tests/tirx/structural_map_with_constraint_context.rs
index a97ac30ea..c50b48835 100644
--- a/rust/tests/tirx/structural_map_with_constraint_context.rs
+++ b/rust/tests/tirx/structural_map_with_constraint_context.rs
@@ -92,7 +92,7 @@ fn 
ordered_mapping_collects_result_facts_and_restores_scopes() {
                                     .value
                                     .to_i64()
                                     .unwrap(),
-                                context.to_array().iter().collect::<Vec<_>>(),
+                                
context.to_array().iter().cloned().collect::<Vec<_>>(),
                             ));
                             Unchanged
                         },
diff --git a/rust/tests/tirx/structural_mutate_with_constraint_context.rs 
b/rust/tests/tirx/structural_mutate_with_constraint_context.rs
index 4309d7d16..86ded3449 100644
--- a/rust/tests/tirx/structural_mutate_with_constraint_context.rs
+++ b/rust/tests/tirx/structural_mutate_with_constraint_context.rs
@@ -70,7 +70,7 @@ impl Rewrite {
         view: &mut View<'_, Self>,
     ) -> 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()));
+        self.facts.push((marker, 
view.base().constraints().to_array().iter().cloned().collect()));
         view.default_mutate(self, node)
     }
     fn mutate_for(
diff --git a/rust/tests/tirx/structural_visit_with_constraint_context.rs 
b/rust/tests/tirx/structural_visit_with_constraint_context.rs
index a26641601..1d7276712 100644
--- a/rust/tests/tirx/structural_visit_with_constraint_context.rs
+++ b/rust/tests/tirx/structural_visit_with_constraint_context.rs
@@ -55,7 +55,10 @@ impl Observe {
                 self.array = Some(context.to_array());
                 self.live = Some(context.clone());
             }
-            self.facts.push((value.value.to_i64().unwrap(), 
context.to_array().iter().collect()));
+            self.facts.push((
+                value.value.to_i64().unwrap(),
+                context.to_array().iter().cloned().collect(),
+            ));
         }
         visitor.default_visit(self, node)
     }
@@ -172,7 +175,7 @@ fn interrupts_restore_native_scope_and_errors_terminate() {
             if self.fail {
                 Err(Error::new(RUNTIME_ERROR, "constraint stop", ""))
             } else {
-                Ok(Some(VisitInterrupt::with(17_i64)))
+                Ok(Some(VisitInterrupt::new(17_i64)))
             }
         }
     }
@@ -205,7 +208,10 @@ fn 
lazy_call_arms_gain_predicates_but_eager_select_arms_do_not() {
         ) -> Result<Option<VisitInterrupt>> {
             let value = node.value.to_i64().unwrap();
             if [21, 22, 31, 32].contains(&value) {
-                self.0.push((value, 
visitor.base().constraints().to_array().iter().collect()));
+                self.0.push((
+                    value,
+                    
visitor.base().constraints().to_array().iter().cloned().collect(),
+                ));
             }
             Ok(None)
         }
diff --git a/rust/tests/tirx/structural_walk_with_constraint_context.rs 
b/rust/tests/tirx/structural_walk_with_constraint_context.rs
index 8ce561aeb..2cb1c34f8 100644
--- a/rust/tests/tirx/structural_walk_with_constraint_context.rs
+++ b/rust/tests/tirx/structural_walk_with_constraint_context.rs
@@ -86,7 +86,7 @@ fn 
typed_walk_callbacks_collect_facts_and_restore_analyzer_scopes() {
                         let marker = 
&node.value.downcast_ref::<IntImmObj>().unwrap().value;
                         facts.push((
                             marker.to_i64().unwrap(),
-                            context.to_array().iter().collect::<Vec<_>>(),
+                            
context.to_array().iter().cloned().collect::<Vec<_>>(),
                         ));
                         analyzer.sync_constraint_context(context)?;
                         loop_bounds.push(if 
analyzer.const_int_bound_is_bound(&i)? {
diff --git a/rust/tests/tirx/tile/dispatch.rs b/rust/tests/tirx/tile/dispatch.rs
index d40ab38cc..f30f16130 100644
--- a/rust/tests/tirx/tile/dispatch.rs
+++ b/rust/tests/tirx/tile/dispatch.rs
@@ -23,7 +23,7 @@ use tvm_tirx::ir::{Location, UNKNOWN_LOC, PrimExpr, PrimType, 
Stmt, Var};
 use tvm_tirx::target::Target;
 use tvm_tirx::tirx::tile::DispatchContext;
 use tvm_tirx::tirx::{TensorType, ThreadScope};
-use tvm_tirx::tvm_ffi::{Any, Map, ObjectRefCast, String as FfiString};
+use tvm_tirx::tvm_ffi::{Any, Map, ObjectRefCore, String as FfiString};
 
 use crate::common::{global, serial, structural_equal};
 use crate::{int32, memory_scope, prim_var, var};
@@ -53,7 +53,7 @@ fn dispatch_contexts() {
     assert!(ctx.cache_get("key").unwrap().is_none());
     ctx.cache_set("key", &tx_var).unwrap();
     let cached = ctx.cache_get("key").unwrap().unwrap();
-    assert!(structural_equal(&cached.try_cast::<Var>().unwrap(), &tx_var));
+    assert!(structural_equal(&cached.downcast::<Var>().unwrap(), &tx_var));
 
     let stmt: Stmt = global("ir.Evaluate")
         .call_tuple((int32(0), Location::unknown()))

Reply via email to