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 a6bb154d7 [Rust] Bump tvm-ffi to f0520693; cache generated field 
getters (#227)
a6bb154d7 is described below

commit a6bb154d723e9f72184faa563fce9d17b1b02e3b
Author: Tianqi Chen <[email protected]>
AuthorDate: Sat Nov 7 19:28:48 2026 -0500

    [Rust] Bump tvm-ffi to f0520693; cache generated field getters (#227)
    
    Bump 3rdparty/tvm-ffi from c244bc5f to apache/tvm-ffi f0520693
    (apache/tvm-ffi#961), whose Rust stub generator caches each field
    accessor's `FieldGetter` in a `ColdInitLazyLock` instead of resolving it
    by name on every call.
    
    - Regenerated bindings: the 20 remaining generated field accessors use
    the cached getter.
    - Target: drop the mutable `str_repr_` cache; `str()` serializes the
    config on each call.
    - Register empty reflection for the field-less `tirx.Layout`,
    `sym.IterMapExpr`, and `sym.IntSet` bases so their layout is known.
    - `SplitHostDevice` and `SpecializeDeviceHelpers` compare targets
    structurally instead of by their JSON string.
    - Hide the canonical simplifier's internal `sym.CanonicalExpr`,
    `sym.SplitExpr`, and `sym.SumExpr` from the Rust bindings.
    - Regenerated bindings: `Target`, `TileLayout`, `ComposeLayout`,
    `IterSplitExpr`, `IterSumExpr`, `IntervalSet`, and their bases are now
    plain `repr(C)` field mirrors.
    
    - Drop the field-wise allocators of these constructed types
    (`from_fields` on the six complete types, `new` on the `Layout`,
    `IntSet`, and `IterMapExpr` bases and on their `*Obj` types): their C++
    constructors parse configs, derive masks, and check invariants, which a
    field-wise allocator would bypass. The stub generator has no directive
    to omit an allocator, so these nine bindings are hand-written (`skip` +
    `ty-map`) with the generated layout, field reads, and upcasts.
---
 3rdparty/tvm-ffi                                  |   2 +-
 include/tvm/target/target.h                       |   6 --
 rust/src/ir/expr.rs                               |   6 +-
 rust/src/ir/transform/core.rs                     |  17 +++-
 rust/src/lib.rs                                   |   4 +-
 rust/src/sym.rs                                   |  10 ++
 rust/src/sym/analyzer.rs                          | 114 +---------------------
 rust/src/sym/int_set.rs                           |  45 ++++-----
 rust/src/sym/iter_affine_map.rs                   |  85 +++++++---------
 rust/src/target.rs                                |  58 +++++------
 rust/src/tirx.rs                                  |   4 +
 rust/src/tirx/layout.rs                           |  97 ++++++++----------
 rust/src/tirx/structural.rs                       |  11 ++-
 rust/src/tirx/tile/dispatch.rs                    |  48 +++++++--
 rust/src/tirx/transform/function_pass.rs          |   5 +-
 rust/tests/sym/analyzer.rs                        |  10 +-
 rust/tests/sym/int_set.rs                         |   6 +-
 rust/tests/sym/iter_affine_map.rs                 |  18 ++--
 rust/tests/target/main.rs                         |  31 +++---
 rust/tests/tirx/layout.rs                         |   2 +-
 src/backend/cuda/transform/bind_backend_config.cc |  11 ++-
 src/sym/int_set.cc                                |   5 +-
 src/sym/iter_affine_map.cc                        |   1 +
 src/target/target.cc                              |   8 +-
 src/tirx/ir/layout/tile_core.cc                   |   5 +-
 src/tirx/transform/split_host_device.cc           |   3 +-
 26 files changed, 260 insertions(+), 352 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index c244bc5fc..f05206931 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit c244bc5fcbaac54bbc6f0563fccf302cb570388a
+Subproject commit f052069318165cb60f3fc7dd0d3252db9eddffee
diff --git a/include/tvm/target/target.h b/include/tvm/target/target.h
index b6d4cc343..4bf23a7f5 100644
--- a/include/tvm/target/target.h
+++ b/include/tvm/target/target.h
@@ -147,12 +147,6 @@ class TargetObj : public ffi::Object {
   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. */
-  mutable ffi::String str_repr_;
-
-  friend class TargetInternal;
 };
 
 /*!
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index d1eb86355..d2878f6ac 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -2315,13 +2315,11 @@ crate::def_expr_op_over_loads!(crate::ir::prim::Ramp);
 crate::def_expr_op_over_loads!(crate::ir::prim::Broadcast);
 crate::def_expr_op_over_loads!(crate::ir::prim::Shuffle);
 
-// These symbolic reference classes also inherit C++ PrimExpr. The opaque Rust 
mirrors retain
-// their existing allocation restrictions; attaching operators only forwards 
their handles.
+// These symbolic reference classes also inherit C++ PrimExpr; attaching 
operators only forwards
+// their handles.
 crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterMapExpr);
 crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterSplitExpr);
 crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterSumExpr);
-crate::def_expr_op_over_loads!(crate::sym::analyzer::SplitExpr);
-crate::def_expr_op_over_loads!(crate::sym::analyzer::SumExpr);
 
 // 
---------------------------------------------------------------------------------------------
 // What the constant constructors check
diff --git a/rust/src/ir/transform/core.rs b/rust/src/ir/transform/core.rs
index 4d7b63788..0a8fa6322 100644
--- a/rust/src/ir/transform/core.rs
+++ b/rust/src/ir/transform/core.rs
@@ -48,6 +48,7 @@ use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
+use tvm_ffi::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
 
@@ -198,7 +199,9 @@ impl Deref for ModulePassObj {
 #[allow(missing_docs)]
 impl ModulePassObj {
     pub fn pass_info(&self) -> Result<PassInfo> {
-        FieldGetter::new(Self::type_index(), "pass_info")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(ModulePassObj::type_index(), "pass_info"));
+        GETTER.clone()?.get(self)
     }
 }
 
@@ -260,11 +263,15 @@ impl Deref for SequentialObj {
 #[allow(missing_docs)]
 impl SequentialObj {
     pub fn pass_info(&self) -> Result<PassInfo> {
-        FieldGetter::new(Self::type_index(), "pass_info")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(SequentialObj::type_index(), "pass_info"));
+        GETTER.clone()?.get(self)
     }
 
     pub fn passes(&self) -> Result<Array<Pass>> {
-        FieldGetter::new(Self::type_index(), "passes")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(SequentialObj::type_index(), "passes"));
+        GETTER.clone()?.get(self)
     }
 }
 
@@ -502,7 +509,9 @@ pub struct PassInstrumentObj {
 #[allow(missing_docs)]
 impl PassInstrumentObj {
     pub fn name(&self) -> Result<String> {
-        FieldGetter::new(Self::type_index(), "name")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(PassInstrumentObj::type_index(), "name"));
+        GETTER.clone()?.get(self)
     }
 }
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index ed14c7cd9..bcece86e9 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -48,10 +48,10 @@
 //! use tvm_tirx::tvm_ffi::{Function, String};
 //!
 //! let target = Target::new("llvm")?;
-//! assert_eq!(target.kind()?.name()?, "llvm");
+//! assert_eq!(target.kind.name()?, "llvm");
 //!
 //! let same = 
Function::get_global("target.Target")?.call_tuple((String::from("llvm"),))?;
-//! assert_eq!(Target::try_from(same)?.kind()?.name()?, "llvm");
+//! assert_eq!(Target::try_from(same)?.kind.name()?, "llvm");
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 //! ```
 //!
diff --git a/rust/src/sym.rs b/rust/src/sym.rs
index 0931f604f..348f00915 100644
--- a/rust/src/sym.rs
+++ b/rust/src/sym.rs
@@ -26,6 +26,16 @@
 // to the file of the Python module that defines it.
 // tvm-ffi-stubgen(prefix): sym
 // tvm-ffi-stubgen(skip): sym.ConstraintContext
+// The canonical simplifier's intermediate forms are internal to it.
+// tvm-ffi-stubgen(skip): sym.CanonicalExpr
+// tvm-ffi-stubgen(skip): sym.SplitExpr
+// tvm-ffi-stubgen(skip): sym.SumExpr
+// Integer sets and iterator sums are hand-written in their modules: C++ 
constructs them.
+// tvm-ffi-stubgen(skip): sym.IntSet
+// tvm-ffi-stubgen(skip): sym.IntervalSet
+// tvm-ffi-stubgen(skip): sym.IterMapExpr
+// tvm-ffi-stubgen(skip): sym.IterSplitExpr
+// tvm-ffi-stubgen(skip): sym.IterSumExpr
 
 pub mod analyzer;
 pub mod constraint;
diff --git a/rust/src/sym/analyzer.rs b/rust/src/sym/analyzer.rs
index 916cdb543..3bf325735 100644
--- a/rust/src/sym/analyzer.rs
+++ b/rust/src/sym/analyzer.rs
@@ -18,13 +18,11 @@
  */
 
 //! The analyzer. An [`Analyzer`] proves, bounds and simplifies integer 
expressions under the facts
-//! of a [`ConstraintContext`]; its bounds are [`ConstIntBound`]s and 
[`ModularSet`]s, and
-//! [`SplitExpr`] and [`SumExpr`] are the forms its canonical simplifier works 
in.
+//! of a [`ConstraintContext`]; its bounds are [`ConstIntBound`]s and 
[`ModularSet`]s.
 
 // tvm-ffi-stubgen(missing-docs): allow
-// Python has no class for `CanonicalExpr`, `SumExpr` and `SplitExpr`, the 
forms of the canonical
-// simplifier, or for the `RewriteSimplifierStats` that 
`Analyzer.rewrite_simplify_stats()`
-// returns; they are here, with the analyzer they belong to.
+// Python has no class for the `RewriteSimplifierStats` that 
`Analyzer.rewrite_simplify_stats()`
+// returns; it is here, with the analyzer it belongs to.
 
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
@@ -40,17 +38,11 @@
 // tvm-ffi-stubgen(import-object): crate::ir::support
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 // tvm-ffi-stubgen(import-object): crate::ir::expr::PrimExpr
-// The canonical forms are primitive expressions, as C++ `SplitExpr` and 
`SumExpr` derive from
-// `PrimExpr`.
-// tvm-ffi-stubgen(upcast): sym.SplitExpr -> PrimExpr
-// tvm-ffi-stubgen(upcast): sym.SumExpr -> PrimExpr
 // tvm-ffi-stubgen(import-object): std::borrow::Borrow
 // tvm-ffi-stubgen(import-object): tvm_ffi::IntoObjectRef
 
 // tvm-ffi-stubgen(begin): import-section
 use crate::ExitOnDrop;
-use crate::ir::expr::Expr;
-use crate::ir::expr::ExprObj;
 use crate::ir::expr::IntoExpr;
 use crate::ir::expr::PrimExpr;
 use crate::ir::expr::Var;
@@ -58,7 +50,6 @@ use crate::ir::support;
 use crate::sym::constraint::ConstraintContext;
 use crate::sym::int_set::IntSet;
 use std::borrow::Borrow;
-use std::ops::Deref;
 use tvm_ffi::Error;
 use tvm_ffi::IntoObjectRef;
 use tvm_ffi::Map;
@@ -583,102 +574,3 @@ impl RewriteSimplifierStats {
     }
 }
 // tvm-ffi-stubgen(end)
-
-// 
---------------------------------------------------------------------------------------------
-// The forms of the canonical simplifier
-
-/// The base of the intermediate forms the canonical simplifier introduces, 
[`SplitExpr`] and
-/// [`SumExpr`].
-// tvm-ffi-stubgen(begin): object/sym.CanonicalExpr
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::ObjectRef, Clone)]
-pub struct CanonicalExpr {
-    data_: ObjectArc<CanonicalExprObj>,
-}
-
-/// The object behind [`CanonicalExpr`].
-///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::Object)]
-#[type_key = "sym.CanonicalExpr"]
-#[type_immutable]
-pub struct CanonicalExprObj {
-    base_: ExprObj,
-}
-
-impl Deref for CanonicalExprObj {
-    type Target = ExprObj;
-    fn deref(&self) -> &ExprObj {
-        &self.base_
-    }
-}
-
-tvm_ffi::impl_object_ref_upcast!(CanonicalExpr => Expr);
-// tvm-ffi-stubgen(end)
-
-/// The canonical form of a split index: `((index % upper_factor) / 
lower_factor) * scale`.
-// tvm-ffi-stubgen(begin): object/sym.SplitExpr
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::ObjectRef, Clone)]
-pub struct SplitExpr {
-    data_: ObjectArc<SplitExprObj>,
-}
-
-/// The object behind [`SplitExpr`].
-///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::Object)]
-#[type_key = "sym.SplitExpr"]
-#[type_immutable]
-pub struct SplitExprObj {
-    base_: CanonicalExprObj,
-}
-
-impl Deref for SplitExprObj {
-    type Target = CanonicalExprObj;
-    fn deref(&self) -> &CanonicalExprObj {
-        &self.base_
-    }
-}
-
-tvm_ffi::impl_object_ref_upcast!(SplitExpr => Expr, SplitExpr => 
CanonicalExpr);
-tvm_ffi::impl_object_ref_upcast!(unsafe SplitExpr => PrimExpr);
-// tvm-ffi-stubgen(end)
-
-/// The canonical form of a sum: a sum of [`SplitExpr`]s plus a constant base.
-// tvm-ffi-stubgen(begin): object/sym.SumExpr
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::ObjectRef, Clone)]
-pub struct SumExpr {
-    data_: ObjectArc<SumExprObj>,
-}
-
-/// The object behind [`SumExpr`].
-///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
-#[allow(missing_docs)]
-#[repr(C)]
-#[derive(tvm_ffi::derive::Object)]
-#[type_key = "sym.SumExpr"]
-#[type_immutable]
-pub struct SumExprObj {
-    base_: CanonicalExprObj,
-}
-
-impl Deref for SumExprObj {
-    type Target = CanonicalExprObj;
-    fn deref(&self) -> &CanonicalExprObj {
-        &self.base_
-    }
-}
-
-tvm_ffi::impl_object_ref_upcast!(SumExpr => Expr, SumExpr => CanonicalExpr);
-tvm_ffi::impl_object_ref_upcast!(unsafe SumExpr => PrimExpr);
-// tvm-ffi-stubgen(end)
diff --git a/rust/src/sym/int_set.rs b/rust/src/sym/int_set.rs
index c9cc3ac8a..5182c7676 100644
--- a/rust/src/sym/int_set.rs
+++ b/rust/src/sym/int_set.rs
@@ -22,10 +22,17 @@
 //! `estimate_region_*` functions bound the region a set of accesses covers.
 
 // tvm-ffi-stubgen(missing-docs): allow
+// The sets are hand-written: C++ constructs them, so they have no field-wise 
allocator.
+// tvm-ffi-stubgen(ty-map): sym.IntSet -> crate::sym::int_set::IntSet
+// tvm-ffi-stubgen(ty-map): sym.IntervalSet -> crate::sym::int_set::IntervalSet
+// tvm-ffi-stubgen(import-object): std::ops::Deref
+// tvm-ffi-stubgen(import-object): tvm_ffi::Object
+// tvm-ffi-stubgen(import-object): tvm_ffi::ObjectArc
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
 // tvm-ffi-stubgen(import-object): tvm_ffi::Array
 // tvm-ffi-stubgen(import-object): tvm_ffi::Map
+// tvm-ffi-stubgen(import-object): tvm_ffi::Result
 // tvm-ffi-stubgen(import-object): crate::ir::expr::Range
 // tvm-ffi-stubgen(import-object): crate::ir::expr::Var
 // tvm-ffi-stubgen(import-object): crate::sym::analyzer::Analyzer
@@ -34,9 +41,6 @@
 // tvm-ffi-stubgen(import-object): crate::ir::expr::PrimExpr
 // tvm-ffi-stubgen(import-object): std::borrow::Borrow
 // tvm-ffi-stubgen(import-object): tvm_ffi::IntoObjectRef
-// An interval is bounded by `PrimExpr`s, as C++ `IntervalSetObj` holds them.
-// tvm-ffi-stubgen(field): sym.IntervalSet.min_value -> PrimExpr
-// tvm-ffi-stubgen(field): sym.IntervalSet.max_value -> PrimExpr
 // tvm-ffi-stubgen(begin): import-section
 use crate::ir::expr::IntoExpr;
 use crate::ir::expr::PrimExpr;
@@ -51,9 +55,7 @@ use tvm_ffi::IntoObjectRef;
 use tvm_ffi::Map;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
-use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
-use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
 
 /// The base of every set of integers, one dimension each, that bounds the 
values an expression
@@ -78,7 +80,6 @@ use tvm_ffi::reflection::FieldGetter;
 /// assert_eq!(script(&bound)?, "sym.IntervalSet(min_value=neg_inf, 
max_value=15)");
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
-// tvm-ffi-stubgen(begin): object/sym.IntSet
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -88,15 +89,21 @@ pub struct IntSet {
 
 /// The object behind [`IntSet`].
 ///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [24, 24) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
 #[type_key = "sym.IntSet"]
+#[type_immutable]
+#[type_rs_parent]
 pub struct IntSetObj {
     base_: Object,
 }
-// tvm-ffi-stubgen(end)
+
+const _: () = {
+    assert!(::core::mem::size_of::<IntSetObj>() == 24);
+    assert!(::core::mem::align_of::<IntSetObj>() == 8);
+};
 
 impl IntSet {
     /// Return whether the set is empty (`sym.IntSetIsNothing`).
@@ -127,7 +134,6 @@ impl IntSet {
 
 /// The interval `[min_value, max_value]` of integers, whose bounds are 
symbolic expressions and
 /// may be [`neg_inf`] or [`pos_inf`].
-// tvm-ffi-stubgen(begin): object/sym.IntervalSet
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -137,7 +143,7 @@ pub struct IntervalSet {
 
 /// The object behind [`IntervalSet`].
 ///
-/// Opaque: parent 'sym.IntSet' is opaque (layout-unknown). Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [24, 40) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -146,8 +152,15 @@ pub struct IntervalSet {
 #[type_immutable]
 pub struct IntervalSetObj {
     base_: IntSetObj,
+    pub min_value: PrimExpr,
+    pub max_value: PrimExpr,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<IntervalSetObj>() == 40);
+    assert!(::core::mem::align_of::<IntervalSetObj>() == 8);
+};
+
 impl Deref for IntervalSetObj {
     type Target = IntSetObj;
     fn deref(&self) -> &IntSetObj {
@@ -155,19 +168,7 @@ impl Deref for IntervalSetObj {
     }
 }
 
-#[allow(missing_docs)]
-impl IntervalSetObj {
-    pub fn min_value(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "min_value")?.get(self)
-    }
-
-    pub fn max_value(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "max_value")?.get(self)
-    }
-}
-
 tvm_ffi::impl_object_ref_upcast!(IntervalSet => IntSet);
-// tvm-ffi-stubgen(end)
 
 impl IntervalSet {
     /// Create the interval `[min_value, max_value]`, whose bounds may be 
[`neg_inf`] and
diff --git a/rust/src/sym/iter_affine_map.rs b/rust/src/sym/iter_affine_map.rs
index ba0e00c6c..ab88dd731 100644
--- a/rust/src/sym/iter_affine_map.rs
+++ b/rust/src/sym/iter_affine_map.rs
@@ -22,10 +22,19 @@
 //! functions simplify, divide and invert such maps.
 
 // tvm-ffi-stubgen(missing-docs): allow
+// The iterator forms are hand-written: C++ constructs them, so they have no 
field-wise
+// allocator.
+// tvm-ffi-stubgen(ty-map): sym.IterMapExpr -> 
crate::sym::iter_affine_map::IterMapExpr
+// tvm-ffi-stubgen(ty-map): sym.IterSplitExpr -> 
crate::sym::iter_affine_map::IterSplitExpr
+// tvm-ffi-stubgen(ty-map): sym.IterSumExpr -> 
crate::sym::iter_affine_map::IterSumExpr
+// tvm-ffi-stubgen(import-object): crate::ir::expr::Expr
+// tvm-ffi-stubgen(import-object): crate::ir::expr::ExprObj
+// tvm-ffi-stubgen(import-object): std::ops::Deref
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
 // tvm-ffi-stubgen(import-object): tvm_ffi::AnyView
 // tvm-ffi-stubgen(import-object): tvm_ffi::Map
+// tvm-ffi-stubgen(import-object): tvm_ffi::Result
 // tvm-ffi-stubgen(import-object): crate::ir::expr::Range
 // tvm-ffi-stubgen(import-object): crate::ir::expr::Var
 // tvm-ffi-stubgen(import-object): crate::sym::analyzer::Analyzer
@@ -33,21 +42,13 @@
 // 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
-// The iterator forms are primitive expressions, as C++ `IterMapExpr` derives 
from `PrimExpr`, and
-// their marks and padding predicate are `PrimExpr`s.
-// tvm-ffi-stubgen(upcast): sym.IterMapExpr -> PrimExpr
-// tvm-ffi-stubgen(upcast): sym.IterSplitExpr -> PrimExpr
-// tvm-ffi-stubgen(upcast): sym.IterSumExpr -> PrimExpr
+// The marks and the padding predicate are `PrimExpr`s, as in C++.
 // tvm-ffi-stubgen(field): sym.IterMark.source -> PrimExpr
 // tvm-ffi-stubgen(field): sym.IterMark.extent -> PrimExpr
 // tvm-ffi-stubgen(field): sym.IterMapResult.padding_predicate -> PrimExpr
 // tvm-ffi-stubgen(nullable): sym.IterMapResult.padding_predicate
 // tvm-ffi-stubgen(import-object): std::borrow::Borrow
 // tvm-ffi-stubgen(import-object): tvm_ffi::IntoObjectRef
-// tvm-ffi-stubgen(field): sym.IterSplitExpr.lower_factor -> PrimExpr
-// tvm-ffi-stubgen(field): sym.IterSplitExpr.extent -> PrimExpr
-// tvm-ffi-stubgen(field): sym.IterSplitExpr.scale -> PrimExpr
-// tvm-ffi-stubgen(field): sym.IterSumExpr.base -> PrimExpr
 // tvm-ffi-stubgen(begin): import-section
 use crate::ir::expr::Expr;
 use crate::ir::expr::ExprObj;
@@ -66,15 +67,12 @@ use tvm_ffi::IntoObjectRef;
 use tvm_ffi::Map;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
-use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
-use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
 
 /// The base of the iterator map forms, [`IterSplitExpr`] and [`IterSumExpr`]. 
They hold the result
 /// of detection and do not appear in a well-formed function.
-// tvm-ffi-stubgen(begin): object/sym.IterMapExpr
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -84,16 +82,22 @@ pub struct IterMapExpr {
 
 /// The object behind [`IterMapExpr`].
 ///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [40, 40) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
 #[type_key = "sym.IterMapExpr"]
 #[type_immutable]
+#[type_rs_parent]
 pub struct IterMapExprObj {
     base_: ExprObj,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<IterMapExprObj>() == 40);
+    assert!(::core::mem::align_of::<IterMapExprObj>() == 8);
+};
+
 impl Deref for IterMapExprObj {
     type Target = ExprObj;
     fn deref(&self) -> &ExprObj {
@@ -103,7 +107,6 @@ impl Deref for IterMapExprObj {
 
 tvm_ffi::impl_object_ref_upcast!(IterMapExpr => Expr);
 tvm_ffi::impl_object_ref_upcast!(unsafe IterMapExpr => PrimExpr);
-// tvm-ffi-stubgen(end)
 
 /// The result of [`detect_iter_map`]: the detected `indices`, or empty 
`indices` and the `errors`
 /// that say why detection failed.
@@ -237,7 +240,6 @@ impl IterMark {
 // tvm-ffi-stubgen(end)
 
 /// The split `(source // lower_factor) % extent * scale` of the marked 
iterator `source`.
-// tvm-ffi-stubgen(begin): object/sym.IterSplitExpr
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -247,7 +249,7 @@ pub struct IterSplitExpr {
 
 /// The object behind [`IterSplitExpr`].
 ///
-/// Opaque: parent 'sym.IterMapExpr' is opaque (layout-unknown). Fields are 
read through the C ABI getters.
+/// Complete: reflected fields fill [40, 72) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -256,8 +258,17 @@ pub struct IterSplitExpr {
 #[type_immutable]
 pub struct IterSplitExprObj {
     base_: IterMapExprObj,
+    pub source: IterMark,
+    pub lower_factor: PrimExpr,
+    pub extent: PrimExpr,
+    pub scale: PrimExpr,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<IterSplitExprObj>() == 72);
+    assert!(::core::mem::align_of::<IterSplitExprObj>() == 8);
+};
+
 impl Deref for IterSplitExprObj {
     type Target = IterMapExprObj;
     fn deref(&self) -> &IterMapExprObj {
@@ -265,31 +276,10 @@ impl Deref for IterSplitExprObj {
     }
 }
 
-#[allow(missing_docs)]
-impl IterSplitExprObj {
-    pub fn source(&self) -> Result<IterMark> {
-        FieldGetter::new(Self::type_index(), "source")?.get(self)
-    }
-
-    pub fn lower_factor(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "lower_factor")?.get(self)
-    }
-
-    pub fn extent(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "extent")?.get(self)
-    }
-
-    pub fn scale(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "scale")?.get(self)
-    }
-}
-
 tvm_ffi::impl_object_ref_upcast!(IterSplitExpr => Expr, IterSplitExpr => 
IterMapExpr);
 tvm_ffi::impl_object_ref_upcast!(unsafe IterSplitExpr => PrimExpr);
-// tvm-ffi-stubgen(end)
 
 /// The sum of the splits `args` plus `base`.
-// tvm-ffi-stubgen(begin): object/sym.IterSumExpr
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -299,7 +289,7 @@ pub struct IterSumExpr {
 
 /// The object behind [`IterSumExpr`].
 ///
-/// Opaque: parent 'sym.IterMapExpr' is opaque (layout-unknown). Fields are 
read through the C ABI getters.
+/// Complete: reflected fields fill [40, 56) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -308,8 +298,15 @@ pub struct IterSumExpr {
 #[type_immutable]
 pub struct IterSumExprObj {
     base_: IterMapExprObj,
+    pub args: Array<IterSplitExpr>,
+    pub base: PrimExpr,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<IterSumExprObj>() == 56);
+    assert!(::core::mem::align_of::<IterSumExprObj>() == 8);
+};
+
 impl Deref for IterSumExprObj {
     type Target = IterMapExprObj;
     fn deref(&self) -> &IterMapExprObj {
@@ -317,20 +314,8 @@ impl Deref for IterSumExprObj {
     }
 }
 
-#[allow(missing_docs)]
-impl IterSumExprObj {
-    pub fn args(&self) -> Result<Array<IterSplitExpr>> {
-        FieldGetter::new(Self::type_index(), "args")?.get(self)
-    }
-
-    pub fn base(&self) -> Result<PrimExpr> {
-        FieldGetter::new(Self::type_index(), "base")?.get(self)
-    }
-}
-
 tvm_ffi::impl_object_ref_upcast!(IterSumExpr => Expr, IterSumExpr => 
IterMapExpr);
 tvm_ffi::impl_object_ref_upcast!(unsafe 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/target.rs b/rust/src/target.rs
index 6085fa3f4..a62fc2e11 100644
--- a/rust/src/target.rs
+++ b/rust/src/target.rs
@@ -24,12 +24,19 @@
 // tvm-ffi-stubgen(missing-docs): allow
 // Every object registered under `target` has its block in this file.
 // tvm-ffi-stubgen(prefix): target
+// `target.Target` is hand-written below: a target is built by parsing its 
config, never from
+// raw fields.
+// tvm-ffi-stubgen(skip): target.Target
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
 // tvm-ffi-stubgen(import-object): tvm_ffi::Module
 // tvm-ffi-stubgen(import-object): crate::ir::module::IRModule
 // tvm-ffi-stubgen(import-object): tvm_ffi::IntoObjectRef
 // tvm-ffi-stubgen(import-object): std::borrow::Borrow
+// tvm-ffi-stubgen(ty-map): target.Target -> crate::target::Target
+// tvm-ffi-stubgen(import-object): tvm_ffi::Any
+// tvm-ffi-stubgen(import-object): tvm_ffi::Map
+// tvm-ffi-stubgen(import-object): tvm_ffi::object::ObjectRef
 
 // tvm-ffi-stubgen(begin): import-section
 use crate::ir::module::IRModule;
@@ -44,6 +51,7 @@ use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
+use tvm_ffi::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use tvm_ffi::object::ObjectRef;
 use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
@@ -67,7 +75,6 @@ use tvm_ffi::reflection::FieldGetter;
 /// assert!(Target::current(true)?.is_none());
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
-// tvm-ffi-stubgen(begin): object/target.Target
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -77,7 +84,7 @@ pub struct Target {
 
 /// The object behind [`Target`].
 ///
-/// Opaque: bytes [72, 88) of [24, 88) are not accounted for by reflected 
fields. Fields are read through the C ABI getters.
+/// Complete: reflected fields fill [24, 72) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -85,31 +92,17 @@ pub struct Target {
 #[type_final]
 pub struct TargetObj {
     base_: Object,
+    pub kind: TargetKind,
+    pub host: Option<ObjectRef>,
+    pub tag: String,
+    pub keys: Array<String>,
+    pub attrs: Map<String, Any>,
 }
 
-#[allow(missing_docs)]
-impl TargetObj {
-    pub fn kind(&self) -> Result<TargetKind> {
-        FieldGetter::new(Self::type_index(), "kind")?.get(self)
-    }
-
-    pub fn tag(&self) -> Result<String> {
-        FieldGetter::new(Self::type_index(), "tag")?.get(self)
-    }
-
-    pub fn keys(&self) -> Result<Array<String>> {
-        FieldGetter::new(Self::type_index(), "keys")?.get(self)
-    }
-
-    pub fn attrs(&self) -> Result<Map<String, Any>> {
-        FieldGetter::new(Self::type_index(), "attrs")?.get(self)
-    }
-
-    pub fn host(&self) -> Result<Option<ObjectRef>> {
-        FieldGetter::new(Self::type_index(), "host")?.get(self)
-    }
-}
-// tvm-ffi-stubgen(end)
+const _: () = {
+    assert!(::core::mem::size_of::<TargetObj>() == 72);
+    assert!(::core::mem::align_of::<TargetObj>() == 8);
+};
 
 impl Target {
     /// Create the target that a tag (`"nvidia/nvidia-a100"`), a kind name 
(`"llvm"`) or a JSON
@@ -232,15 +225,22 @@ pub struct TargetKindObj {
 #[allow(missing_docs)]
 impl TargetKindObj {
     pub fn name(&self) -> Result<String> {
-        FieldGetter::new(Self::type_index(), "name")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(TargetKindObj::type_index(), "name"));
+        GETTER.clone()?.get(self)
     }
 
     pub fn default_device_type(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "default_device_type")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(TargetKindObj::type_index(), 
"default_device_type")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn default_keys(&self) -> Result<Array<String>> {
-        FieldGetter::new(Self::type_index(), "default_keys")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(TargetKindObj::type_index(), "default_keys"));
+        GETTER.clone()?.get(self)
     }
 }
 // tvm-ffi-stubgen(end)
@@ -285,7 +285,7 @@ pub fn list_tags() -> Result<Map<String, Target>> {
 /// let config: Map<String, Any> =
 ///     [("kind".into(), 
Any::from(String::from("llvm")))].into_iter().collect();
 /// target::register_tag("my_ext/cpu", config, false)?;
-/// assert_eq!(Target::new("my_ext/cpu")?.kind()?.name()?, "llvm");
+/// assert_eq!(Target::new("my_ext/cpu")?.kind.name()?, "llvm");
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
 pub fn register_tag(
diff --git a/rust/src/tirx.rs b/rust/src/tirx.rs
index d0607d711..1b8918897 100644
--- a/rust/src/tirx.rs
+++ b/rust/src/tirx.rs
@@ -25,6 +25,10 @@
 // An object registered under `tirx` that no file has a block for yet is 
generated here, to be moved
 // to the file of the Python module that defines it.
 // tvm-ffi-stubgen(prefix): tirx
+// Layouts are hand-written in `layout`: C++ constructs them.
+// tvm-ffi-stubgen(skip): tirx.Layout
+// tvm-ffi-stubgen(skip): tirx.TileLayout
+// tvm-ffi-stubgen(skip): tirx.ComposeLayout
 
 pub mod analysis;
 pub mod function;
diff --git a/rust/src/tirx/layout.rs b/rust/src/tirx/layout.rs
index 4b9f61a64..f70ae26e7 100644
--- a/rust/src/tirx/layout.rs
+++ b/rust/src/tirx/layout.rs
@@ -22,12 +22,19 @@
 //! strided [`Iter`] factors, and a [`ComposeLayout`] swizzles the memory 
coordinate of one.
 
 // tvm-ffi-stubgen(missing-docs): allow
+// The layouts are hand-written: C++ constructs them, so they have no 
field-wise allocator.
+// tvm-ffi-stubgen(ty-map): tirx.Layout -> crate::tirx::layout::Layout
+// tvm-ffi-stubgen(ty-map): tirx.TileLayout -> crate::tirx::layout::TileLayout
+// tvm-ffi-stubgen(ty-map): tirx.ComposeLayout -> 
crate::tirx::layout::ComposeLayout
+// tvm-ffi-stubgen(import-object): std::ops::Deref
+// tvm-ffi-stubgen(import-object): tvm_ffi::Array
 // Names the hand-written wrappers use: the import section keeps them even 
when no generated block
 // does.
 // tvm-ffi-stubgen(import-object): crate::sym::analyzer::Analyzer
 // tvm-ffi-stubgen(import-object): crate::tirx::thread_scope::ThreadScope
 // tvm-ffi-stubgen(import-object): tvm_ffi::Any
 // tvm-ffi-stubgen(import-object): tvm_ffi::String
+// tvm-ffi-stubgen(import-object): tvm_ffi::Result
 // tvm-ffi-stubgen(import-object): crate::ir::support
 // tvm-ffi-stubgen(import-object): crate::ir::expr::IntoExpr
 // tvm-ffi-stubgen(import-object): std::borrow::Borrow
@@ -35,10 +42,9 @@
 // An iterator's extent and stride are `PrimExpr`s, as C++ `IterObj` holds 
them.
 // tvm-ffi-stubgen(field): tirx.Iter.extent -> PrimExpr
 // tvm-ffi-stubgen(field): tirx.Iter.stride -> PrimExpr
-// A tile layout's offsets are `PrimExpr`s, as C++ `TileLayoutObj` holds them.
-// tvm-ffi-stubgen(field): tirx.TileLayout.offset -> Map<Axis, PrimExpr>
 // tvm-ffi-stubgen(import-object): tvm_ffi::Map
 // tvm-ffi-stubgen(import-object): tvm_ffi::IntoObjectRef
+
 // tvm-ffi-stubgen(begin): import-section
 use crate::ir::expr::IntoExpr;
 use crate::ir::expr::PrimExpr;
@@ -53,11 +59,9 @@ use tvm_ffi::IntoObjectRef;
 use tvm_ffi::Map;
 use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
-use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
 use tvm_ffi::enum_ref::EnumObj;
-use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
 
 /// The base of every layout: a map from the logical indices of a tensor to a 
coordinate on each
@@ -87,7 +91,6 @@ use tvm_ffi::reflection::FieldGetter;
 /// assert_eq!(script(&offset)?, "i * 8 + j");
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
-// tvm-ffi-stubgen(begin): object/tirx.Layout
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -97,15 +100,21 @@ pub struct Layout {
 
 /// The object behind [`Layout`].
 ///
-/// Opaque: no metadata of its own: total_size is unknown. Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [24, 24) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
 #[type_key = "tirx.Layout"]
+#[type_immutable]
+#[type_rs_parent]
 pub struct LayoutObj {
     base_: Object,
 }
-// tvm-ffi-stubgen(end)
+
+const _: () = {
+    assert!(::core::mem::size_of::<LayoutObj>() == 24);
+    assert!(::core::mem::align_of::<LayoutObj>() == 8);
+};
 
 impl Layout {
     /// Check the invariants of the layout (`tirx.LayoutCheckWellFormed`).
@@ -222,7 +231,6 @@ impl Layout {
 /// The layout of strided factors: each [`Iter`] of `shard` spans distinct 
elements and each of
 /// `replica` their copies, adding its stride per position to the coordinate 
on its axis, from the
 /// origin `offset` gives on each axis.
-// tvm-ffi-stubgen(begin): object/tirx.TileLayout
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -232,7 +240,7 @@ pub struct TileLayout {
 
 /// The object behind [`TileLayout`].
 ///
-/// Opaque: parent 'tirx.Layout' is opaque (layout-unknown). Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [24, 48) exactly.
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -241,8 +249,16 @@ pub struct TileLayout {
 #[type_immutable]
 pub struct TileLayoutObj {
     base_: LayoutObj,
+    pub shard: Array<Iter>,
+    pub replica: Array<Iter>,
+    pub offset: Map<Axis, PrimExpr>,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<TileLayoutObj>() == 48);
+    assert!(::core::mem::align_of::<TileLayoutObj>() == 8);
+};
+
 impl Deref for TileLayoutObj {
     type Target = LayoutObj;
     fn deref(&self) -> &LayoutObj {
@@ -250,23 +266,7 @@ impl Deref for TileLayoutObj {
     }
 }
 
-#[allow(missing_docs)]
-impl TileLayoutObj {
-    pub fn shard(&self) -> Result<Array<Iter>> {
-        FieldGetter::new(Self::type_index(), "shard")?.get(self)
-    }
-
-    pub fn replica(&self) -> Result<Array<Iter>> {
-        FieldGetter::new(Self::type_index(), "replica")?.get(self)
-    }
-
-    pub fn offset(&self) -> Result<Map<Axis, PrimExpr>> {
-        FieldGetter::new(Self::type_index(), "offset")?.get(self)
-    }
-}
-
 tvm_ffi::impl_object_ref_upcast!(TileLayout => Layout);
-// tvm-ffi-stubgen(end)
 
 impl TileLayout {
     /// Create the tile layout of the `shard` and `replica` iterators, shifted 
by `offset`
@@ -467,7 +467,6 @@ impl Axis {
 /// The layout `tile_layout` with its memory coordinate XOR-swizzled: above 
the low `per_element`
 /// bits, the group of `swizzle_len` bits `atom_len` bits up is XORed into the 
group below if
 /// `swizzle_inner`, and the lower group into the upper one otherwise.
-// tvm-ffi-stubgen(begin): object/tirx.ComposeLayout
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::ObjectRef, Clone)]
@@ -477,7 +476,7 @@ pub struct ComposeLayout {
 
 /// The object behind [`ComposeLayout`].
 ///
-/// Opaque: parent 'tirx.Layout' is opaque (layout-unknown). Fields are read 
through the C ABI getters.
+/// Complete: reflected fields fill [24, 56) exactly (alignment padding [37, 
40)).
 #[allow(missing_docs)]
 #[repr(C)]
 #[derive(tvm_ffi::derive::Object)]
@@ -486,8 +485,20 @@ pub struct ComposeLayout {
 #[type_immutable]
 pub struct ComposeLayoutObj {
     base_: LayoutObj,
+    pub per_element: i32,
+    pub swizzle_len: i32,
+    pub atom_len: i32,
+    pub swizzle_inner: bool,
+    pub tile_layout: TileLayout,
+    pub inner_mask: i32,
+    pub outer_mask: i32,
 }
 
+const _: () = {
+    assert!(::core::mem::size_of::<ComposeLayoutObj>() == 56);
+    assert!(::core::mem::align_of::<ComposeLayoutObj>() == 8);
+};
+
 impl Deref for ComposeLayoutObj {
     type Target = LayoutObj;
     fn deref(&self) -> &LayoutObj {
@@ -495,39 +506,7 @@ impl Deref for ComposeLayoutObj {
     }
 }
 
-#[allow(missing_docs)]
-impl ComposeLayoutObj {
-    pub fn per_element(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "per_element")?.get(self)
-    }
-
-    pub fn swizzle_len(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "swizzle_len")?.get(self)
-    }
-
-    pub fn atom_len(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "atom_len")?.get(self)
-    }
-
-    pub fn swizzle_inner(&self) -> Result<bool> {
-        FieldGetter::new(Self::type_index(), "swizzle_inner")?.get(self)
-    }
-
-    pub fn inner_mask(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "inner_mask")?.get(self)
-    }
-
-    pub fn outer_mask(&self) -> Result<i64> {
-        FieldGetter::new(Self::type_index(), "outer_mask")?.get(self)
-    }
-
-    pub fn tile_layout(&self) -> Result<TileLayout> {
-        FieldGetter::new(Self::type_index(), "tile_layout")?.get(self)
-    }
-}
-
 tvm_ffi::impl_object_ref_upcast!(ComposeLayout => Layout);
-// tvm-ffi-stubgen(end)
 
 impl ComposeLayout {
     /// Create `tile_layout` with its memory coordinate swizzled: above the 
low `per_element` bits,
diff --git a/rust/src/tirx/structural.rs b/rust/src/tirx/structural.rs
index 4aeeb8a57..136e5ff0c 100644
--- a/rust/src/tirx/structural.rs
+++ b/rust/src/tirx/structural.rs
@@ -29,6 +29,7 @@ use tvm_ffi::Object;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
+use tvm_ffi::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use tvm_ffi::reflection::FieldGetter;
 
 /// Header-only stand-in for the builtin `ffi.StructuralMutator`; it only 
carries the ancestor depth.
@@ -77,7 +78,10 @@ pub struct StructuralMutatorWithConstraintContextObj {
 #[allow(missing_docs)]
 impl StructuralMutatorWithConstraintContextObj {
     pub fn constraints(&self) -> Result<ConstraintContext> {
-        FieldGetter::new(Self::type_index(), "constraints")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            
FieldGetter::new(StructuralMutatorWithConstraintContextObj::type_index(), 
"constraints")
+        });
+        GETTER.clone()?.get(self)
     }
 }
 // tvm-ffi-stubgen(end)
@@ -107,7 +111,10 @@ pub struct StructuralVisitorWithConstraintContextObj {
 #[allow(missing_docs)]
 impl StructuralVisitorWithConstraintContextObj {
     pub fn constraints(&self) -> Result<ConstraintContext> {
-        FieldGetter::new(Self::type_index(), "constraints")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            
FieldGetter::new(StructuralVisitorWithConstraintContextObj::type_index(), 
"constraints")
+        });
+        GETTER.clone()?.get(self)
     }
 }
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/tirx/tile/dispatch.rs b/rust/src/tirx/tile/dispatch.rs
index af079ff18..d8deb5b64 100644
--- a/rust/src/tirx/tile/dispatch.rs
+++ b/rust/src/tirx/tile/dispatch.rs
@@ -58,6 +58,7 @@ use tvm_ffi::ObjectCore;
 use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
 use tvm_ffi::String;
+use tvm_ffi::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use tvm_ffi::object::ObjectRef;
 use tvm_ffi::reflection::FieldGetter;
 // tvm-ffi-stubgen(end)
@@ -118,43 +119,70 @@ pub struct DispatchContextObj {
 #[allow(missing_docs)]
 impl DispatchContextObj {
     pub fn target(&self) -> Result<Target> {
-        FieldGetter::new(Self::type_index(), "target")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(DispatchContextObj::type_index(), "target"));
+        GETTER.clone()?.get(self)
     }
 
     pub fn thread_scope(&self) -> Result<ThreadScope> {
-        FieldGetter::new(Self::type_index(), "thread_scope")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "thread_scope")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn launch_params(&self) -> Result<Any> {
-        FieldGetter::new(Self::type_index(), "launch_params")?.get_any(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "launch_params")
+        });
+        GETTER.clone()?.get_any(self)
     }
 
     pub fn var_range_map(&self) -> Result<Map<Var, Range>> {
-        FieldGetter::new(Self::type_index(), "var_range_map")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "var_range_map")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn alloc_only(&self) -> Result<bool> {
-        FieldGetter::new(Self::type_index(), "alloc_only")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "alloc_only")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn callbacks(&self) -> Result<Map<String, ObjectRef>> {
-        FieldGetter::new(Self::type_index(), "callbacks")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "callbacks")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn shared_state(&self) -> Result<Map<String, ObjectRef>> {
-        FieldGetter::new(Self::type_index(), "shared_state")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "shared_state")
+        });
+        GETTER.clone()?.get(self)
     }
 
     pub fn inter(&self) -> Result<Map<String, Array<PrimExpr>>> {
-        FieldGetter::new(Self::type_index(), "inter")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(DispatchContextObj::type_index(), "inter"));
+        GETTER.clone()?.get(self)
     }
 
     pub fn intra(&self) -> Result<Map<String, Array<PrimExpr>>> {
-        FieldGetter::new(Self::type_index(), "intra")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(DispatchContextObj::type_index(), "intra"));
+        GETTER.clone()?.get(self)
     }
 
     pub fn scope_kind(&self) -> Result<String> {
-        FieldGetter::new(Self::type_index(), "scope_kind")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> = 
ColdInitLazyLock::new(|| {
+            FieldGetter::new(DispatchContextObj::type_index(), "scope_kind")
+        });
+        GETTER.clone()?.get(self)
     }
 }
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/tirx/transform/function_pass.rs 
b/rust/src/tirx/transform/function_pass.rs
index bef18cdec..3d0101c15 100644
--- a/rust/src/tirx/transform/function_pass.rs
+++ b/rust/src/tirx/transform/function_pass.rs
@@ -45,6 +45,7 @@ use tvm_ffi::IntoObjectRef;
 use tvm_ffi::ObjectArc;
 use tvm_ffi::ObjectCore;
 use tvm_ffi::Result;
+use tvm_ffi::extra::cold_init_lazy_lock::ColdInitLazyLock;
 use tvm_ffi::reflection::FieldGetter;
 use tvm_ffi::rvalue_ref::RValueRef;
 // tvm-ffi-stubgen(end)
@@ -107,7 +108,9 @@ impl Deref for FunctionPassObj {
 #[allow(missing_docs)]
 impl FunctionPassObj {
     pub fn pass_info(&self) -> Result<PassInfo> {
-        FieldGetter::new(Self::type_index(), "pass_info")?.get(self)
+        static GETTER: ColdInitLazyLock<Result<FieldGetter>> =
+            ColdInitLazyLock::new(|| 
FieldGetter::new(FunctionPassObj::type_index(), "pass_info"));
+        GETTER.clone()?.get(self)
     }
 }
 
diff --git a/rust/tests/sym/analyzer.rs b/rust/tests/sym/analyzer.rs
index 0e68fb34b..ad15c6b41 100644
--- a/rust/tests/sym/analyzer.rs
+++ b/rust/tests/sym/analyzer.rs
@@ -19,7 +19,7 @@
 
 //! Tests of `sym::analyzer`: the analyzer and its results.
 
-use tvm_tirx::ir::{Expr, ExprObj};
+use tvm_tirx::ir::Expr;
 use tvm_tirx::sym::*;
 use tvm_tirx::tvm_ffi::Object;
 
@@ -31,12 +31,8 @@ fn generated_types_match_the_registry() {
     assert_type_contracts!(true;
         ConstIntBoundObj: Object, ModularSetObj: Object, 
RewriteSimplifierStatsObj: Object,
     );
-    // The analyzer holds its sub-analyzers unreflected; the expression 
hierarchy registers no
-    // layout of its own, so the canonical expressions are opaque too.
-    assert_type_contracts!(false;
-        AnalyzerObj: Object, CanonicalExprObj: ExprObj, SplitExprObj: 
CanonicalExprObj,
-        SumExprObj: CanonicalExprObj,
-    );
+    // The analyzer holds its sub-analyzers unreflected.
+    assert_type_contracts!(false; AnalyzerObj: Object);
 }
 
 #[test]
diff --git a/rust/tests/sym/int_set.rs b/rust/tests/sym/int_set.rs
index 1796d4b47..b2e6065b3 100644
--- a/rust/tests/sym/int_set.rs
+++ b/rust/tests/sym/int_set.rs
@@ -49,15 +49,15 @@ fn interval_sets_are_read_through_accessors() {
         .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);
+    assert_eq!(as_int(interval.min_value.clone()), 2);
+    assert_eq!(as_int(interval.max_value.clone()), 20);
 }
 
 #[test]
 fn int_sets() {
     let analyzer = Analyzer::new();
     let interval = IntervalSet::new(int32(0), int32(10));
-    assert!(analyzer.can_prove_equal(interval.min_value().unwrap(), 
int32(0)).unwrap());
+    assert!(analyzer.can_prove_equal(interval.min_value.clone(), 
int32(0)).unwrap());
     let set: IntSet = interval.into();
     assert!(!set.is_nothing().unwrap());
     assert!(!set.is_everything().unwrap());
diff --git a/rust/tests/sym/iter_affine_map.rs 
b/rust/tests/sym/iter_affine_map.rs
index a89781950..7a48a4c62 100644
--- a/rust/tests/sym/iter_affine_map.rs
+++ b/rust/tests/sym/iter_affine_map.rs
@@ -67,22 +67,22 @@ fn iter_maps_decompose_into_marks_and_splits() {
     // 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 {
-        let splits = sum.args().unwrap();
+        let splits = sum.args.clone();
         assert_eq!(splits.len(), 1);
         splits.get(0).unwrap()
     };
     let mut lower_factors = Vec::new();
     for sum in result.indices.iter() {
-        assert_eq!(as_int(sum.base().unwrap()), 0);
+        assert_eq!(as_int(sum.base.clone()), 0);
         let outer = only_split(sum);
-        let mark: IterMark = outer.source().unwrap();
+        let mark: IterMark = outer.source.clone();
         assert_eq!(as_int(mark.extent.clone()), 4);
         let inner_sum: IterSumExpr = mark.source.clone().downcast().unwrap();
         let inner = only_split(&inner_sum);
-        let x_mark: IterMark = inner.source().unwrap();
+        let x_mark: IterMark = inner.source.clone();
         assert!(mark_is(&x_mark, &x, 16));
-        assert_eq!(as_int(inner.extent().unwrap()), 4);
-        lower_factors.push(as_int(inner.lower_factor().unwrap()));
+        assert_eq!(as_int(inner.extent.clone()), 4);
+        lower_factors.push(as_int(inner.lower_factor.clone()));
     }
     assert_eq!(lower_factors, vec![4, 1]);
 }
@@ -105,7 +105,7 @@ fn iter_maps() {
     assert!(analyzer.can_prove_equal(normalized, index.clone()).unwrap());
 
     let sum = normalize_to_iter_sum(index.clone(), &dom, 
Some(&analyzer)).unwrap();
-    assert_eq!(sum.args().unwrap().len(), 2);
+    assert_eq!(sum.args.len(), 2);
 
     let simplified =
         iter_map_simplify([index.clone()], &dom, &predicate, 
IterMapLevel::default(), None, true)
@@ -130,7 +130,7 @@ fn iter_maps() {
 
     let mark = IterMark::new(x.clone(), int32(4));
     let split = IterSplitExpr::new(mark, int32(1), int32(4), 
int32(1)).unwrap();
-    assert!(analyzer.can_prove_equal(split.scale().unwrap(), 
int32(1)).unwrap());
+    assert!(analyzer.can_prove_equal(split.scale.clone(), int32(1)).unwrap());
     let sum = IterSumExpr::new([split], int32(0));
-    assert_eq!(sum.args().unwrap().len(), 1);
+    assert_eq!(sum.args.len(), 1);
 }
diff --git a/rust/tests/target/main.rs b/rust/tests/target/main.rs
index c89b466b5..56bed5479 100644
--- a/rust/tests/target/main.rs
+++ b/rust/tests/target/main.rs
@@ -46,23 +46,22 @@ fn generated_types_match_the_registry() {
 #[test]
 fn native_targets_are_read_through_accessors() {
     let llvm = native_target("llvm");
-    let kind: TargetKind = llvm.kind().unwrap();
+    let kind: TargetKind = llvm.kind.clone();
     assert_eq!(kind.name().unwrap().as_str(), "llvm");
     assert!(kind.default_keys().unwrap().iter().any(|key| key.as_str() == 
"cpu"));
-    assert!(llvm.keys().unwrap().iter().any(|key| key.as_str() == "cpu"));
-    assert!(llvm.host().unwrap().is_none());
+    assert!(llvm.keys.iter().any(|key| key.as_str() == "cpu"));
+    assert!(llvm.host.is_none());
 
     // The kind's reflected device type is what the native query reports.
     let cuda = native_target("cuda");
-    assert_eq!(cuda.kind().unwrap().name().unwrap().as_str(), "cuda");
+    assert_eq!(cuda.kind.name().unwrap().as_str(), "cuda");
     let device_type: i64 = global("target.TargetGetDeviceType")
         .call_tuple((cuda.clone(),))
         .unwrap()
         .try_into()
         .unwrap();
-    assert_eq!(cuda.kind().unwrap().default_device_type().unwrap(), 
device_type);
+    assert_eq!(cuda.kind.default_device_type().unwrap(), device_type);
     assert_ne!(device_type, kind.default_device_type().unwrap());
-    cuda.attrs().unwrap();
 
     // `host` is declared as an `ObjectRef`; it holds a `Target`.
     let with_host: Target = global("target.WithHost")
@@ -70,28 +69,28 @@ fn native_targets_are_read_through_accessors() {
         .unwrap()
         .try_into()
         .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().downcast::<TargetKind>().is_err());
+    let host: Target = with_host.host.clone().unwrap().downcast().unwrap();
+    assert_eq!(host.kind.name().unwrap().as_str(), "llvm");
+    assert!(with_host.host.clone().unwrap().downcast::<TargetKind>().is_err());
 }
 
 #[test]
 fn construction_and_queries() {
     let llvm = Target::new("llvm").unwrap();
-    assert_eq!(llvm.kind().unwrap().name().unwrap().as_str(), "llvm");
+    assert_eq!(llvm.kind.name().unwrap().as_str(), "llvm");
     assert_eq!(llvm.get_target_device_type().unwrap(), 1);
     
assert!(llvm.export().unwrap().contains_key(&FfiString::from("kind")).unwrap());
     assert!(llvm.as_json().unwrap().as_str().contains("llvm"));
     assert!(llvm.feature("no_such_feature").unwrap().is_none());
 
     let c = Target::from_config(config("c")).unwrap();
-    assert_eq!(c.kind().unwrap().name().unwrap().as_str(), "c");
-    assert!(llvm.with_host(Some(&c)).unwrap().host().unwrap().is_some());
-    assert!(llvm.with_host(None).unwrap().host().unwrap().is_none());
+    assert_eq!(c.kind.name().unwrap().as_str(), "c");
+    assert!(llvm.with_host(Some(&c)).unwrap().host.is_some());
+    assert!(llvm.with_host(None).unwrap().host.is_none());
 
     let kinds = Target::list_kinds().unwrap();
     assert!(kinds.iter().any(|kind| kind.as_str() == "llvm"));
-    let options = llvm.kind().unwrap().options().unwrap();
+    let options = llvm.kind.options().unwrap();
     assert!(options.contains_key(&FfiString::from("mtriple")).unwrap());
     assert_eq!(TargetKind::options_from_name("llvm").unwrap().len(), 
options.len());
 }
@@ -103,7 +102,7 @@ fn scope_ends_on_drop() {
     {
         let _scope = Target::new("c").unwrap().scope().unwrap();
         let current = Target::current(false).unwrap().unwrap();
-        assert_eq!(current.kind().unwrap().name().unwrap().as_str(), "c");
+        assert_eq!(current.kind.name().unwrap().as_str(), "c");
     }
     assert!(Target::current(true).unwrap().is_none());
 }
@@ -111,7 +110,7 @@ fn scope_ends_on_drop() {
 #[test]
 fn tags() {
     let tagged = target::register_tag("rust-test/llvm", config("llvm"), 
true).unwrap();
-    assert_eq!(tagged.kind().unwrap().name().unwrap().as_str(), "llvm");
+    assert_eq!(tagged.kind.name().unwrap().as_str(), "llvm");
     let tags = target::list_tags().unwrap();
     assert!(tags.contains_key(&FfiString::from("rust-test/llvm")).unwrap());
     assert!(target::register_tag("rust-test/llvm", config("llvm"), 
false).is_err());
diff --git a/rust/tests/tirx/layout.rs b/rust/tests/tirx/layout.rs
index a55ac3b80..20cd8b431 100644
--- a/rust/tests/tirx/layout.rs
+++ b/rust/tests/tirx/layout.rs
@@ -65,7 +65,7 @@ fn layouts() {
 
     // Memory offset 10 becomes 10 ^ ((10 >> 3) & 3) = 11.
     let swizzled = ComposeLayout::new(0, 2, 3, &tile, true).unwrap();
-    assert_eq!(swizzled.atom_len().unwrap(), 3);
+    assert_eq!(swizzled.atom_len, 3);
     assert_eq!(at(Layout::from(&swizzled).apply([int32(1), int32(2)], 
&analyzer).unwrap()), 11);
 }
 
diff --git a/src/backend/cuda/transform/bind_backend_config.cc 
b/src/backend/cuda/transform/bind_backend_config.cc
index 13223a4ce..bdab846c9 100644
--- a/src/backend/cuda/transform/bind_backend_config.cc
+++ b/src/backend/cuda/transform/bind_backend_config.cc
@@ -20,13 +20,15 @@
 /*! \file bind_backend_config.cc
  *  \brief Resolve CUDA configuration before any target-sensitive lowering.
  */
+#include <tvm/ffi/extra/structural_equal.h>
+#include <tvm/ffi/extra/structural_key.h>
 #include <tvm/ffi/reflection/registry.h>
 #include <tvm/ir/unique_name_supply.h>
 #include <tvm/target/target.h>
 #include <tvm/tirx/stmt_functor.h>
 #include <tvm/tirx/transform.h>
 
-#include <map>
+#include <unordered_map>
 #include <unordered_set>
 #include <vector>
 
@@ -156,7 +158,7 @@ class DeviceHelperSpecializer : public StmtExprMutator {
     if (auto existing = replacements_.Get(callee.value())) {
       replacement = existing.value();
     } else {
-      bool same_group = original_target.value()->str() == target_->str() &&
+      bool same_group = ffi::StructuralEqual()(original_target.value(), 
target_) &&
                         
original->GetAttr<ffi::String>("backend_config").value_or("") == config_;
       replacement = same_group
                         ? callee.value()
@@ -239,7 +241,7 @@ Pass SpecializeDeviceHelpers() {
     IRModule result({}, mod->attrs, mod->global_infos);
     UniqueNameSupply names(mod->functions.begin(), mod->functions.end(),
                            [](const auto& kv) { return kv.first->name_hint; });
-    std::map<std::string, ffi::ObjectPtr<DeviceHelperSpecializer>> groups;
+    std::unordered_map<ffi::StructuralKey, 
ffi::ObjectPtr<DeviceHelperSpecializer>> groups;
     for (auto [gv, base] : mod->functions) {
       auto func = base.as<Function>();
       if (!func.has_value()) {
@@ -257,8 +259,7 @@ Pass SpecializeDeviceHelpers() {
         continue;
       }
       auto config = 
func.value()->GetAttr<ffi::String>("backend_config").value_or("");
-      std::string key = std::string(target.value()->str()) + "\n" + 
std::string(config);
-      auto& group = groups[key];
+      auto& group = 
groups[ffi::StructuralKey(ffi::Array<ffi::Any>{target.value(), config})];
       if (!group)
         group =
             ffi::make_object<DeviceHelperSpecializer>(mod, result, names, 
target.value(), config);
diff --git a/src/sym/int_set.cc b/src/sym/int_set.cc
index 864416232..4e51eacae 100644
--- a/src/sym/int_set.cc
+++ b/src/sym/int_set.cc
@@ -51,7 +51,10 @@ using prim::IsOne;
 using prim::IsZero;
 using prim::MakeConst;
 
-TVM_FFI_STATIC_INIT_BLOCK() { IntervalSetObj::RegisterReflection(); }
+TVM_FFI_STATIC_INIT_BLOCK() {
+  ffi::reflection::ObjectDef<IntSetObj>();
+  IntervalSetObj::RegisterReflection();
+}
 
 IntervalSet::IntervalSet(PrimExpr min_value, PrimExpr max_value) : 
IntSet(ffi::UnsafeInit{}) {
   auto node = ffi::make_object<IntervalSetObj>(std::move(min_value), 
std::move(max_value));
diff --git a/src/sym/iter_affine_map.cc b/src/sym/iter_affine_map.cc
index 012c1794c..903c653e4 100644
--- a/src/sym/iter_affine_map.cc
+++ b/src/sym/iter_affine_map.cc
@@ -42,6 +42,7 @@ namespace sym {
 using namespace tvm::prim;
 
 TVM_FFI_STATIC_INIT_BLOCK() {
+  ffi::reflection::ObjectDef<IterMapExprObj>();
   IterMarkObj::RegisterReflection();
   IterSplitExprObj::RegisterReflection();
   IterSumExprObj::RegisterReflection();
diff --git a/src/target/target.cc b/src/target/target.cc
index faa73ff46..cae0f99e0 100644
--- a/src/target/target.cc
+++ b/src/target/target.cc
@@ -54,7 +54,6 @@ class TargetInternal {
     ffi::ObjectPtr<TargetObj> n = ffi::make_object<TargetObj>(*target.get());
     n->host =
         target_host.has_value() ? 
ffi::Optional<ffi::ObjectRef>(target_host.value()) : std::nullopt;
-    n->str_repr_ = ffi::String();
     return Target(ffi::UnsafeInit{}, std::move(n));
   }
 
@@ -94,12 +93,7 @@ static TargetKind GetTargetKind(const ffi::String& name) {
   return kind.value();
 }
 
-ffi::String TargetObj::str() const {
-  if (str_repr_.empty()) {
-    str_repr_ = ffi::json::Stringify(ToConfig());
-  }
-  return str_repr_;
-}
+ffi::String TargetObj::str() const { return ffi::json::Stringify(ToConfig()); }
 
 /**********  Small member methods  **********/
 
diff --git a/src/tirx/ir/layout/tile_core.cc b/src/tirx/ir/layout/tile_core.cc
index 035dc0780..9c4b057dc 100644
--- a/src/tirx/ir/layout/tile_core.cc
+++ b/src/tirx/ir/layout/tile_core.cc
@@ -147,7 +147,10 @@ TVM_FFI_INLINE ffi::Expected<ffi::UnchangedOr<ffi::Any>> 
TileLayoutMaybeInplaceM
 
 }  // namespace
 
-TVM_FFI_STATIC_INIT_BLOCK() { ComposeLayoutObj::RegisterReflection(); }
+TVM_FFI_STATIC_INIT_BLOCK() {
+  ffi::reflection::ObjectDef<LayoutObj>();
+  ComposeLayoutObj::RegisterReflection();
+}
 
 /**************** Iter ****************/
 TVM_FFI_STATIC_INIT_BLOCK() {
diff --git a/src/tirx/transform/split_host_device.cc 
b/src/tirx/transform/split_host_device.cc
index 121fafe96..ec673a021 100644
--- a/src/tirx/transform/split_host_device.cc
+++ b/src/tirx/transform/split_host_device.cc
@@ -23,6 +23,7 @@
  */
 #include <tvm/backend/cuda/attr.h>
 #include <tvm/ffi/cast.h>
+#include <tvm/ffi/extra/structural_equal.h>
 #include <tvm/ffi/extra/structural_mutate.h>
 #include <tvm/ffi/function.h>
 #include <tvm/ffi/reflection/registry.h>
@@ -761,7 +762,7 @@ class DeviceKernelMutator : public StmtExprMutator {
     }
 
     if (!force_kernel_launch) {
-      bool same_target = caller_target->str() == callee_target->str();
+      bool same_target = ffi::StructuralEqual()(caller_target, callee_target);
       if (same_target) {
         // Calls within the same target may be handled at codegen time
         // as internal subroutine calls.

Reply via email to