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 8afa23043 [REFACTOR][RUST] Define operators and types in 
tvm_ffi::static_init_block! (#105)
8afa23043 is described below

commit 8afa23043008b576f526bd2187a7b5e031585ad4
Author: Linzhang Li <[email protected]>
AuthorDate: Wed Oct 28 08:35:04 2026 -0400

    [REFACTOR][RUST] Define operators and types in tvm_ffi::static_init_block! 
(#105)
    
    ## Summary
    
    This PR defines Rust operators and types the way C++ does. The
    definitions are ordinary items, and
    a `tvm_ffi::static_init_block!` registers them when the library loads,
    the counterpart of
    `TVM_FFI_STATIC_INIT_BLOCK`. The crate's own `register_ops!` and
    `register_types!` macros, and the
    registry behind them, are removed.
    
    ```rust
    /// The type of a pair of values, printed as `my_ext.Pair(first, second)`.
    #[rs_class("my_ext.PairType", final)]
    pub struct PairTypeObj {
        base: TypeObj,
        pub first: Type,
        pub second: Type,
    }
    def_object_ref!(pub PairType, Type, PairTypeObj);
    
    impl Placeholder for PairTypeObj {
        fn placeholder() -> Self {
            Self::new(TypeObj::placeholder(), Type::placeholder(), 
Type::placeholder())
        }
    }
    
    tvm_ffi::static_init_block! {
        OpDef::new("my_ext.clamp", "Clamp an index to the range `[0, bound)`.")
            .signature((sig::arg::<IntExpr>("index", ""), 
sig::arg::<IntExpr>("bound", "")))
            .set_infer_type(index_type)
            .op()?;
        register_type::<PairType>()?;
    }
    ```
    
    Signed-off-by: Yuchuan <[email protected]>
---
 3rdparty/tvm-ffi                         |   2 +-
 AGENTS.md                                |   4 +-
 python/tvm_tirx/backend/rust/__init__.py |   5 +-
 rust/Cargo.toml                          |   1 -
 rust/examples/define_ir.rs               |  51 +++--
 rust/src/ir/op_def.rs                    | 331 ++-----------------------------
 rust/src/ir/type_def.rs                  | 165 ++++++---------
 rust/src/lib.rs                          |  61 +++---
 rust/tests/ir/op_def.rs                  | 154 ++++++--------
 tests/python/ir/test_op_rust.py          |   4 +-
 10 files changed, 212 insertions(+), 566 deletions(-)

diff --git a/3rdparty/tvm-ffi b/3rdparty/tvm-ffi
index dbdcdacee..1a2e530fa 160000
--- a/3rdparty/tvm-ffi
+++ b/3rdparty/tvm-ffi
@@ -1 +1 @@
-Subproject commit dbdcdaceeef5b0b03251e8dea87725a1c1e2cde6
+Subproject commit 1a2e530fa68f5a251e3dea8b24d2a7ba19614a14
diff --git a/AGENTS.md b/AGENTS.md
index 2b28a8075..4c2f3188a 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -139,8 +139,8 @@ uv run --no-sync cargo test --manifest-path rust/Cargo.toml
 
 The editable install also builds the crate as `libtvm_tirx_rust` when it finds 
a
 cargo as new as the crate's `rust-version` (`TVM_TIRX_USE_RUST=AUTO`; rustup's
-cargo is preferred). `import tvm_tirx` loads it and registers the operators of 
its
-`register_ops!` blocks, so re-run the editable install after changing them.
+cargo is preferred). `import tvm_tirx` loads it and registers the operators 
and types
+of its static init blocks, so re-run the editable install after changing them.
 
 ## Linting
 
diff --git a/python/tvm_tirx/backend/rust/__init__.py 
b/python/tvm_tirx/backend/rust/__init__.py
index 38e1e2d6c..2c0640a60 100644
--- a/python/tvm_tirx/backend/rust/__init__.py
+++ b/python/tvm_tirx/backend/rust/__init__.py
@@ -37,8 +37,9 @@ def register_backend():
     except (OSError, FileNotFoundError, RuntimeError):
         pass
     else:
-        # Loading the library registers its operators and types, and keeps it, 
and their hooks,
-        # loaded for the process; ``tirx_register_ir`` raises the error of a 
block that failed.
+        # Loading the library runs its static init blocks, which register its 
operators and
+        # types, and keeps it, and their hooks, loaded for the process; 
``tirx_register_ir``
+        # raises the error of a block that failed.
         
tvm_ffi.load_module(_LOADED_LIBS["tvm_tirx_rust"]._name).tirx_register_ir()
     return None
 
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 80f96d402..24cdff321 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -30,7 +30,6 @@ name = "tvm_tirx"
 crate-type = ["rlib", "cdylib"]
 
 [dependencies]
-linkme = "0.3"
 tvm-ffi = { path = "../3rdparty/tvm-ffi/rust/tvm-ffi" }
 
 # A library of operators and a type defined in Rust, which 
`tests/python/ir/test_op_rust.py` loads.
diff --git a/rust/examples/define_ir.rs b/rust/examples/define_ir.rs
index 0d1677fd2..76e267258 100644
--- a/rust/examples/define_ir.rs
+++ b/rust/examples/define_ir.rs
@@ -31,9 +31,10 @@
 use tvm_tirx::ir::op_attr::{CALL_EFFECT_KIND, FIXED_RETURN_TYPE};
 use tvm_tirx::ir::sig::{self, IntExpr};
 use tvm_tirx::ir::{
-    CallEffectKind, CallObj, DictAttrs, Expr, OpDef, PrimType, Type, TypeObj, 
Var, UNKNOWN_LOC,
+    register_type, CallEffectKind, CallObj, DictAttrs, Expr, OpDef, 
Placeholder, PrimType, Type,
+    TypeObj, Var, UNKNOWN_LOC,
 };
-use tvm_tirx::tvm_ffi::{self, rs_class, Array, ObjectRefCast, Result};
+use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_class, Array, ObjectRefCast, 
Result};
 
 fn first_arg_type(call: &CallObj) -> Result<Type> {
     Ok(call.args.get(0)?.ty.clone())
@@ -45,45 +46,54 @@ fn loop_index(call: &CallObj) -> Result<Array<Var>> {
     Ok(Array::new(vec![Var::new("i", ty, UNKNOWN_LOC)]))
 }
 
-tvm_tirx::register_ops! {
+// A type, which Python constructs from its fields, `T.ExamplePair(T.int32, 
T.float32)`, as it
+// constructs a C++ type. Its object makes the placeholder `__ffi_new__` fills.
+/// The type of a pair of values.
+#[rs_class("tirx.ExamplePairType", final)]
+pub struct ExamplePairTypeObj {
+    base: TypeObj,
+    pub first: Type,
+    pub second: Type,
+}
+def_object_ref!(pub ExamplePairType, Type, ExamplePairTypeObj);
+
+impl Placeholder for ExamplePairTypeObj {
+    fn placeholder() -> Self {
+        Self::new(TypeObj::placeholder(), Type::placeholder(), 
Type::placeholder())
+    }
+}
+
+tvm_ffi::static_init_block! {
     OpDef::new("tirx.example_clamp", "Clamp an index to the range `[0, 
bound)`.")
         .signature((
             sig::arg::<IntExpr>("index", "The index."),
             sig::arg::<IntExpr>("bound", "The exclusive upper bound."),
         ))
         .set_infer_type(first_arg_type)
-        .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure);
+        .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure)
+        .op()?;
 
     // `Call.attrs` holds a `DictAttrs`; outside `tirx.`, the calls print as 
`my_ext.hint(..)`.
     OpDef::new("my_ext.hint", "Return the value, with hints for the code 
generator.")
         .signature((sig::arg::<Expr>("value", "The value."), 
sig::call_attrs::<DictAttrs>()))
         .set_infer_type(first_arg_type)
-        .set_attr(CALL_EFFECT_KIND, CallEffectKind::ExprAnnotation);
+        .set_attr(CALL_EFFECT_KIND, CallEffectKind::ExprAnnotation)
+        .op()?;
 
     // A region operation, which binds an index in its body.
     OpDef::new("tirx.example_loop", "Run the body for each index in `[0, 
extent)`.")
         .signature(sig::arg::<IntExpr>("extent", "The number of iterations."))
-        .set_region_body_params(loop_index);
-}
+        .set_region_body_params(loop_index)
+        .op()?;
 
-// A type, which Python constructs from its fields, `T.ExamplePair(T.int32, 
T.float32)`, as it
-// constructs a C++ type.
-tvm_tirx::register_types! {
-    /// The type of a pair of values.
-    #[rs_class("tirx.ExamplePairType", final)]
-    pub struct ExamplePairTypeObj {
-        base: TypeObj,
-        pub first: Type,
-        pub second: Type,
-    }
-    def_object_ref!(pub ExamplePairType, Type, ExamplePairTypeObj);
+    register_type::<ExamplePairType>()?;
 }
 
 // A block can be anywhere in the library.
 mod effects {
     use super::*;
 
-    tvm_tirx::register_ops! {
+    tvm_ffi::static_init_block! {
         OpDef::new("tirx.example_mark", "
             Mark a point in the program.
 
@@ -91,6 +101,7 @@ mod effects {
         ")
             .signature(())
             .set_attr(FIXED_RETURN_TYPE, PrimType::void())
-            .set_attr(CALL_EFFECT_KIND, CallEffectKind::Opaque);
+            .set_attr(CALL_EFFECT_KIND, CallEffectKind::Opaque)
+            .op()?;
     }
 }
diff --git a/rust/src/ir/op_def.rs b/rust/src/ir/op_def.rs
index 7bc2aa3b5..5122bb02e 100644
--- a/rust/src/ir/op_def.rs
+++ b/rust/src/ir/op_def.rs
@@ -18,7 +18,7 @@
  */
 
 //! Registering operators from Rust: C++ `OpDef`, with its signature 
descriptors ([`sig`]) and
-//! attribute keys ([`op_attr`]); the [`register_ops!`](crate::register_ops) 
blocks that define
+//! attribute keys ([`op_attr`]), whose chains a 
[`tvm_ffi::static_init_block!`] runs to define
 //! operators, as C++ defines them in a `TVM_FFI_STATIC_INIT_BLOCK`; and the 
handles that keep
 //! operators in `static`s.
 //!
@@ -27,11 +27,11 @@
 //! packing arguments. A signature generates its validator from its argument 
types, as C++
 //! `OpDef::signature` does from `sig::arg<T>`; each signature is its own 
compiled function.
 //!
-//! The [`register_ops!`](crate::register_ops) and 
[`register_types!`](crate::register_types)
-//! blocks are gathered at link time, wherever in the program or library they 
are, and run when it
-//! loads, as a C++ static init block does: once Python loads a library built 
on this crate, by any
-//! means, it sees its operators and types as it sees those of a C++ library. 
A block that several
-//! libraries of a process have, as each links its own copy of a crate they 
share, runs once.
+//! A [`tvm_ffi::static_init_block!`] is gathered at link time, wherever in 
the program or library
+//! it is, and runs when it loads, as a C++ static init block does, so that 
once Python loads a
+//! library built on this crate, by any means, it sees the operators and types 
the block defines
+//! as it sees those of a C++ library. A block that several libraries of a 
process have, as each
+//! links its own copy of a crate they share, runs once.
 
 use std::any::TypeId;
 use std::cell::RefCell;
@@ -39,12 +39,11 @@ use std::ffi::c_void;
 use std::marker::PhantomData;
 use std::panic::{catch_unwind, AssertUnwindSafe};
 use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
-use std::sync::{Once, OnceLock};
+use std::sync::OnceLock;
 
 use tvm_ffi::tvm_ffi_sys::{TVMFFIAny, TVMFFIObject, TVMFFITypeIndex};
 use tvm_ffi::{
-    Any, AnyView, Array, Error, Function, ObjectArc, ObjectRefCore, Result, 
String, INTERNAL_ERROR,
-    TYPE_ERROR, VALUE_ERROR,
+    Any, AnyView, Array, Error, ObjectArc, ObjectRefCore, Result, String, 
TYPE_ERROR, VALUE_ERROR,
 };
 
 use super::attrs::Attrs;
@@ -60,8 +59,8 @@ use sig::{Checks, Specs};
 /// Each step updates the registry at once, as in C++. After a failed step the 
rest are skipped,
 /// and [`OpDef::op`] returns the error. The registry does not synchronize 
registration with
 /// readers, so register an operator before building IR with it, as a
-/// [`register_ops!`](crate::register_ops) block does. An operator and its 
hooks live for the rest
-/// of the process.
+/// [`tvm_ffi::static_init_block!`] does. An operator and its hooks live for 
the rest of the
+/// process.
 ///
 /// ```no_run
 /// use tvm_tirx::ir::op_attr::CALL_EFFECT_KIND;
@@ -80,6 +79,7 @@ use sig::{Checks, Specs};
 ///     .op()?;
 /// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
 /// ```
+#[must_use = "end the chain with `.op()?`, which returns its error and exposes 
the operator"]
 pub struct OpDef(Result<Op>);
 
 impl OpDef {
@@ -322,23 +322,24 @@ impl From<CallEffectKind> for Any {
 
 /// A handle to an operator, kept in a `static` as C++ keeps one in `const Op& 
xxx_op()`. The
 /// operator is looked up by name once per thread; [`OpHandle::get`] returns 
it and
-/// [`OpHandle::is`] compares with it. The 
[`register_ops!`](crate::register_ops) blocks run when
-/// their program or library loads; where the loader does not run them, the 
first lookup of an
-/// operator that is not registered runs them.
+/// [`OpHandle::is`] compares with it. The [`tvm_ffi::static_init_block!`]s 
that define operators
+/// run when their program or library loads; where the loader does not run 
them, the first lookup
+/// of an operator that is not registered runs them, with 
[`tvm_ffi::run_static_init_blocks`].
 ///
 /// ```no_run
 /// use tvm_tirx::ir::sig::{self, IntExpr};
 /// use tvm_tirx::ir::{Call, CallObj, Expr, OpDef, OpHandle, PrimType, Type, 
Var, UNKNOWN_LOC};
-/// use tvm_tirx::tvm_ffi::Result;
+/// use tvm_tirx::tvm_ffi::{self, Result};
 ///
 /// fn first_arg_type(call: &CallObj) -> Result<Type> {
 ///     Ok(call.args.get(0)?.ty.clone())
 /// }
 ///
-/// tvm_tirx::register_ops! {
+/// tvm_ffi::static_init_block! {
 ///     OpDef::new("my_ext.clamp", "Clamp an index to a bound.")
 ///         .signature((sig::arg::<IntExpr>("index", ""), 
sig::arg::<IntExpr>("bound", "")))
-///         .set_infer_type(first_arg_type);
+///         .set_infer_type(first_arg_type)
+///         .op()?;
 /// }
 ///
 /// static CLAMP: OpHandle = OpHandle::new("my_ext.clamp");
@@ -369,8 +370,7 @@ thread_local! {
 }
 
 impl OpHandle {
-    /// The operator `name`, registered in C++, in Python or by a
-    /// [`register_ops!`](crate::register_ops) block.
+    /// The operator `name`, registered in C++, in Python or by a Rust 
[`OpDef`].
     pub const fn new(name: &'static str) -> Self {
         Self { name, slot: OnceLock::new(), object: 
AtomicPtr::new(std::ptr::null_mut()) }
     }
@@ -413,7 +413,7 @@ impl OpHandle {
             Some(op) => op,
             None => {
                 let op = Op::get(self.name).or_else(|missing| {
-                    let registered = register_defined_ir();
+                    let registered = tvm_ffi::run_static_init_blocks();
                     Op::get(self.name).map_err(|_| match registered {
                         Ok(()) => missing,
                         Err(failed) => {
@@ -436,209 +436,6 @@ impl OpHandle {
     }
 }
 
-/// A [`register_ops!`](crate::register_ops) or 
[`register_types!`](crate::register_types) block,
-/// gathered at link time.
-#[doc(hidden)]
-pub struct Registration {
-    /// The macro of the block, and its module and line.
-    kind: &'static str,
-    module: &'static str,
-    line: u32,
-    /// The version of the block's crate and a hash of the block's text, which 
with the module and
-    /// line identify the block in every library that links the crate.
-    version: &'static str,
-    text_hash: u64,
-    register: fn() -> Result<()>,
-    /// The outcome of the block, which runs once: the error's text if it 
failed.
-    outcome: OnceLock<std::result::Result<(), std::string::String>>,
-}
-
-/// The blocks of [`register_ops!`](crate::register_ops) and
-/// [`register_types!`](crate::register_types), gathered at link time from the 
whole program or
-/// library.
-#[doc(hidden)]
-#[linkme::distributed_slice]
-pub static REGISTRATIONS: [Registration];
-
-impl Registration {
-    #[doc(hidden)]
-    pub const fn __new(
-        kind: &'static str,
-        module: &'static str,
-        line: u32,
-        version: &'static str,
-        text: &str,
-        register: fn() -> Result<()>,
-    ) -> Self {
-        let text_hash = text_hash(text);
-        Self { kind, module, line, version, text_hash, register, outcome: 
OnceLock::new() }
-    }
-
-    /// The identity of the block in the process, `module:line@version#hash`: 
the same in every
-    /// library that links the block's crate, and another one if the crate 
version or the block's
-    /// text differs.
-    #[doc(hidden)]
-    pub fn identity(&self) -> std::string::String {
-        format!("{}:{}@{}#{:016x}", self.module, self.line, self.version, 
self.text_hash)
-    }
-
-    /// The name of the global function that marks the block registered in the 
process,
-    /// `ir.registered_block.<identity>`. The libraries of a process share the 
global function
-    /// table of TVM FFI, and their statics are their own.
-    #[doc(hidden)]
-    pub fn marker(&self) -> std::string::String {
-        format!("ir.registered_block.{}", self.identity())
-    }
-
-    /// Run the block if it has not run. The caller that runs a failing block 
gets its error;
-    /// later ones get an error that names the block.
-    #[doc(hidden)]
-    pub fn register(&self) -> Result<()> {
-        let mut failure = None;
-        let outcome = self.outcome.get_or_init(|| {
-            self.run().map_err(|error| {
-                let text = format!("{}: {}", error.kind(), error.message());
-                failure = Some(error);
-                text
-            })
-        });
-        match (failure, outcome) {
-            (Some(error), _) => Err(error),
-            (None, Ok(())) => Ok(()),
-            (None, Err(text)) => {
-                let message = format!("The {} block at {} failed: {text}", 
self.kind, self.place());
-                Err(Error::new(INTERNAL_ERROR, &message, ""))
-            }
-        }
-    }
-
-    /// Run the block unless another library of the process has run it. Every 
library links its
-    /// own copy of the crates it is built from, so the same block can be in 
several, and what it
-    /// defines is registered once, with the hooks of the first library that 
runs it. The block
-    /// is marked once it succeeds, so that one that fails fails in every 
library.
-    fn run(&self) -> Result<()> {
-        let marker = self.marker();
-        if Function::get_global(&marker).is_err() {
-            (self.register)()?;
-            Function::register_global(&marker, Function::from_packed(|_| 
Ok(Any::default())))?;
-        }
-        Ok(())
-    }
-
-    /// Where the block is, for an error.
-    fn place(&self) -> std::string::String {
-        format!("{}:{}", self.module, self.line)
-    }
-}
-
-/// The FNV-1a hash of `text` without its whitespace, which does not depend on 
how the compiler
-/// spaces the tokens of a block it stringifies.
-const fn text_hash(text: &str) -> u64 {
-    let bytes = text.as_bytes();
-    let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
-    let mut i = 0;
-    while i < bytes.len() {
-        if !bytes[i].is_ascii_whitespace() {
-            hash = (hash ^ bytes[i] as u64).wrapping_mul(0x0100_0000_01b3);
-        }
-        i += 1;
-    }
-    hash
-}
-
-/// Run every [`register_ops!`](crate::register_ops) and 
[`register_types!`](crate::register_types)
-/// block of this program or library that has not run, in the order of their 
modules and lines;
-/// running again does nothing. Every block is tried, and the error lists 
those that failed. A
-/// block that another library of the process has registered, from the same 
version of its crate
-/// and the same text, is skipped: libraries that link the same crate register 
its operators and
-/// types once.
-///
-/// The blocks run when their program or library loads, so this reports the 
errors of those that
-/// failed. Every library built on this crate exports it as the TVM FFI 
function
-/// `tirx_register_ir`, which `import tvm_tirx` calls on `libtvm_tirx_rust` to 
raise them. Where
-/// the loader does not run the blocks, the first lookup through an 
[`OpHandle`] of an operator
-/// that is not registered calls it.
-pub fn register_defined_ir() -> Result<()> {
-    let mut blocks: Vec<&Registration> = REGISTRATIONS.iter().collect();
-    blocks.sort_by_key(|block| (block.module, block.line));
-    let mut failures: Vec<(&Registration, Error)> =
-        blocks.into_iter().filter_map(|block| block.register().err().map(|e| 
(block, e))).collect();
-    if failures.len() <= 1 {
-        return failures.pop().map_or(Ok(()), |(_, error)| Err(error));
-    }
-    let mut kinds: Vec<&str> = failures.iter().map(|(block, _)| 
block.kind).collect();
-    kinds.sort();
-    kinds.dedup();
-    let mut message = format!("{} {} blocks failed:", failures.len(), 
kinds.join(" and "));
-    for (block, error) in &failures {
-        message.push_str(&format!("\n  {}: {}: {}", block.place(), 
error.kind(), error.message()));
-    }
-    Err(Error::new(failures[0].1.kind(), &message, ""))
-}
-
-/// The constructor of every [`register_ops!`](crate::register_ops) and
-/// [`register_types!`](crate::register_types) block, which the loader runs 
when the program or
-/// library loads: the first one keeps the library loaded and runs every block 
of it. An error
-/// cannot reach a caller here, so it is printed; [`register_defined_ir`] 
reports it again.
-#[doc(hidden)]
-pub extern "C" fn __register_at_load() {
-    static LOADED: Once = Once::new();
-    LOADED.call_once(|| {
-        let library = keep_loaded().unwrap_or_else(|| 
"register_ops!".to_string());
-        match catch_unwind(register_defined_ir) {
-            Ok(Ok(())) => {}
-            Ok(Err(error)) => eprintln!(
-                "{library}: registering operators and types failed: {}: {}",
-                error.kind(),
-                error.message()
-            ),
-            Err(_) => eprintln!("{library}: registering operators and types 
panicked"),
-        }
-    });
-}
-
-/// Keep the library that holds this code loaded for the rest of the process, 
as the operators and
-/// types it registers keep its hooks: unloading it, as `tvm_ffi.load_module` 
does when Python drops the
-/// module, would leave them dangling. Returns the path of the library.
-#[cfg(any(target_os = "linux", target_os = "macos"))]
-fn keep_loaded() -> Option<std::string::String> {
-    use std::ffi::{c_char, c_int, CStr};
-
-    #[repr(C)]
-    struct DlInfo {
-        fname: *const c_char,
-        fbase: *mut c_void,
-        sname: *const c_char,
-        saddr: *mut c_void,
-    }
-    extern "C" {
-        fn dladdr(addr: *const c_void, info: *mut DlInfo) -> c_int;
-        fn dlopen(filename: *const c_char, flags: c_int) -> *mut c_void;
-    }
-    // RTLD_LAZY | RTLD_NOLOAD | RTLD_NODELETE.
-    #[cfg(target_os = "linux")]
-    const FLAGS: c_int = 0x1 | 0x4 | 0x1000;
-    #[cfg(target_os = "macos")]
-    const FLAGS: c_int = 0x1 | 0x10 | 0x80;
-
-    let null = std::ptr::null_mut();
-    let mut info = DlInfo { fname: null as _, fbase: null, sname: null as _, 
saddr: null };
-    // SAFETY: `dladdr` fills `info` for an address of this library, whose 
path stays valid while
-    // the library is loaded; `dlopen` of a loaded library only marks it, and 
its handle is kept.
-    unsafe {
-        if dladdr(keep_loaded as *const c_void, &mut info) == 0 || 
info.fname.is_null() {
-            return None;
-        }
-        dlopen(info.fname, FLAGS);
-        Some(CStr::from_ptr(info.fname).to_string_lossy().into_owned())
-    }
-}
-
-#[cfg(not(any(target_os = "linux", target_os = "macos")))]
-fn keep_loaded() -> Option<std::string::String> {
-    None
-}
-
 /// The text of `doc` without the indentation its lines share and without the 
blank lines around
 /// it. Text that is not indented is unchanged.
 fn doc_text(doc: &str) -> std::string::String {
@@ -653,94 +450,6 @@ fn doc_text(doc: &str) -> std::string::String {
     lines.join("\n").trim().to_string()
 }
 
-/// Define operators as C++ does in a `TVM_FFI_STATIC_INIT_BLOCK`: each item 
is an [`OpDef`]
-/// chain, which the block runs in order, stopping at the first that fails.
-///
-/// ```no_run
-/// use tvm_tirx::ir::op_attr::{CALL_EFFECT_KIND, FIXED_RETURN_TYPE};
-/// use tvm_tirx::ir::sig::{self, IntExpr};
-/// use tvm_tirx::ir::{CallEffectKind, CallObj, Op, OpDef, PrimType, Type};
-/// use tvm_tirx::tvm_ffi::Result;
-///
-/// fn first_arg_type(call: &CallObj) -> Result<Type> {
-///     Ok(call.args.get(0)?.ty.clone())
-/// }
-///
-/// tvm_tirx::register_ops! {
-///     OpDef::new("my_ext.clamp", "Clamp an index to a bound.")
-///         .signature((sig::arg::<IntExpr>("index", ""), 
sig::arg::<IntExpr>("bound", "")))
-///         .set_infer_type(first_arg_type)
-///         .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure);
-///
-///     OpDef::new("my_ext.mark", "
-///         Mark a point in the program.
-///
-///         The documentation is dedented.
-///     ")
-///         .set_attr(FIXED_RETURN_TYPE, PrimType::void())
-///         .set_attr(CALL_EFFECT_KIND, CallEffectKind::Opaque);
-/// }
-///
-/// // Registered when the program loaded.
-/// assert_eq!(Op::get("my_ext.clamp")?.doc.as_str(), "Clamp an index to a 
bound.");
-/// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
-/// ```
-///
-/// As a C++ static init block, a block runs when its program or library 
loads: it adds a
-/// constructor, which the loader runs, and the first constructor runs every 
block of the program
-/// or library with [`register_defined_ir`]. Python sees the operators of a 
library once it loads
-/// it, by any means, and the library stays loaded for the process, as the 
operators keep its
-/// hooks. An error cannot reach a caller when the library loads: it is 
printed, and
-/// [`register_defined_ir`] (`tirx_register_ir`) and the handles report it 
again. Where the
-/// loader does not run the constructor, an [`OpHandle`] runs the blocks on 
the first lookup of an
-/// operator that is not registered.
-///
-/// A block runs once per process. Every library built on the crate links its 
own copy of the
-/// crates it uses, so a block of a crate that several libraries link is in 
each of them; the first
-/// library that runs it registers its operators, and the others skip it. A 
block of another
-/// version of the crate, or whose text differs, is another block, and 
registering its operators
-/// again is an error. A change to a hook's body alone does not make another 
block: rebuild every
-/// library that links a crate after changing it.
-#[macro_export]
-macro_rules! register_ops {
-    ($($def:expr);* $(;)?) => {
-        $crate::__register_block!("register_ops!", stringify!($($def);*), || {
-            $($crate::ir::OpDef::op($def)?;)*
-            Ok(())
-        });
-    };
-}
-
-/// The registration of a [`register_ops!`](crate::register_ops) or
-/// [`register_types!`](crate::register_types) block: gathered at link time, 
and run by a
-/// constructor when its program or library loads.
-#[doc(hidden)]
-#[macro_export]
-macro_rules! __register_block {
-    ($kind:literal, $text:expr, $register:expr) => {
-        const _: () = {
-            
#[$crate::__private::linkme::distributed_slice($crate::ir::REGISTRATIONS)]
-            #[linkme(crate = $crate::__private::linkme)]
-            static REGISTRATION: $crate::ir::Registration = 
$crate::ir::Registration::__new(
-                $kind,
-                module_path!(),
-                line!(),
-                match option_env!("CARGO_PKG_VERSION") {
-                    Some(version) => version,
-                    None => "",
-                },
-                $text,
-                $register,
-            );
-            #[used]
-            #[cfg_attr(target_os = "linux", link_section = ".init_array")]
-            #[cfg_attr(target_os = "macos", link_section = 
"__DATA,__mod_init_func")]
-            #[cfg_attr(windows, link_section = ".CRT$XCU")]
-            static REGISTER_AT_LOAD: extern "C" fn() = 
$crate::ir::__register_at_load;
-        };
-    };
-}
-
 /// The C ABI of a native operator hook, `NativeFunctionView<R(const 
CallNode*)>`: it returns
 /// the result, or the error, in an owned `TVMFFIAny`.
 type Hook = extern "C" fn(*const CallObj) -> TVMFFIAny;
diff --git a/rust/src/ir/type_def.rs b/rust/src/ir/type_def.rs
index ef9852d84..d68dd4231 100644
--- a/rust/src/ir/type_def.rs
+++ b/rust/src/ir/type_def.rs
@@ -17,16 +17,16 @@
  * under the License.
  */
 
-//! Registering IR types from Rust: the 
[`register_types!`](crate::register_types) blocks that
-//! define `rs_class` types, as C++ defines types with `refl::ObjectDef` in a
-//! `TVM_FFI_STATIC_INIT_BLOCK`, and the [`Placeholder`] values of their 
fields.
+//! Registering IR types from Rust: [`register_type`], which a 
[`tvm_ffi::static_init_block!`]
+//! calls for an `rs_class` type, as C++ registers a type with 
`refl::ObjectDef` in a
+//! `TVM_FFI_STATIC_INIT_BLOCK`, and the [`Placeholder`] values of its fields.
 //!
 //! `rs_class` registers the fields of a type, but not a way to make one. C++ 
`refl::ObjectDef`
 //! also registers a creator of an empty object, which tvm-ffi fills through 
the reflection of the
 //! fields to construct the type from Python (the `__ffi_init__` it generates) 
and to read it from
-//! a JSON graph. A Rust object cannot be empty, as its references are not 
null, so a block
-//! registers `__ffi_new__`, which makes an object whose fields hold 
[`Placeholder`]s, and tvm-ffi
-//! then sets each field as it sets those of a C++ object.
+//! a JSON graph. A Rust object cannot be empty, as its references are not 
null, so
+//! [`register_type`] registers `__ffi_new__`, which makes an object whose 
fields hold
+//! [`Placeholder`]s, and tvm-ffi then sets each field as it sets those of a 
C++ object.
 
 use tvm_ffi::tvm_ffi_sys::{TVMFFIByteArray, TVMFFITypeRegisterAttr};
 use tvm_ffi::{Any, AnyCompatible, Array, Function, ObjectCore, ObjectRefCore, 
Result, String};
@@ -36,11 +36,12 @@ use super::location::Location;
 use super::r#type::{MissingType, PrimType, Type, TypeObj};
 use super::support;
 
-/// A valid value of a field type, which an object of a 
[`register_types!`](crate::register_types)
-/// block holds until reflection sets the field: `__ffi_new__` makes an object 
with one in every
-/// field, and a field `__ffi_init__` does not take (`#[reflect(init_off)]`) 
keeps it. A block
-/// implements it for the objects and references it defines, and this crate 
for the common field
-/// types; implement it for a field type of your own crate.
+/// A valid value of a field type, which an object of a type [`register_type`] 
registers holds
+/// until reflection sets the field: `__ffi_new__` makes an object with one in 
every field, and a
+/// field `__ffi_init__` does not take (`#[reflect(init_off)]`) keeps it. The 
object of a type you
+/// register implements it, from the placeholders of its parent's object and 
its fields; this
+/// crate implements it for the common field types, and a field type of your 
own crate implements
+/// it too.
 pub trait Placeholder {
     /// The value.
     fn placeholder() -> Self;
@@ -106,18 +107,59 @@ impl Placeholder for Expr {
     }
 }
 
-/// Register the type of the reference `R` of a 
[`register_types!`](crate::register_types) block:
-/// `__ffi_new__`, then the `__ffi_init__` tvm-ffi generates from the 
reflected fields, then, once
-/// the Python receiver is available, request its constructor in registered 
destinations
-/// (`ir.expose_type`).
-/// An absent receiver is a no-op; an installed receiver's error is returned.
-#[doc(hidden)]
-pub fn __register_type<R>() -> Result<()>
+/// Register the IR type of the reference `R`, an `rs_class` type, as C++ 
registers one with
+/// `refl::ObjectDef`: `__ffi_new__`, from the [`Placeholder`] of the object, 
then the
+/// `__ffi_init__` tvm-ffi generates from the reflected fields, then, when the 
receiver is
+/// installed, a request for its constructor in registered destinations 
(`ir.expose_type`); an
+/// absent receiver is a no-op, and an installed receiver's error is returned. 
With an explicitly
+/// declared `my_ext` type API destination, Python then constructs the type 
from its fields,
+/// `my_ext.Pair(T.int32, second=T.float32)`, as it constructs a C++ type, and 
a JSON graph holds
+/// it. Import `tvm_tirx` before loading the library so exposure requests 
reach Python. Import the
+/// extension module in Python scope, or supply it to text parsing through 
imports, `extra_vars` or
+/// explicit parser namespace registration; type exposure does not add parser 
defaults.
+///
+/// Call it in a [`tvm_ffi::static_init_block!`], with the 
[`OpDef`](crate::ir::OpDef) chains of
+/// the operators, so that the type is registered when its program or library 
loads, as a C++ one
+/// is; the type's struct, reference and `impl`s are written outside the 
block, as any other:
+///
+/// ```no_run
+/// use tvm_tirx::ir::{register_type, Location, Placeholder, Type, TypeObj};
+/// use tvm_tirx::tvm_ffi::{self, def_object_ref, rs_class, ObjectRefCore};
+///
+/// /// The type of a pair of values, printed as `my_ext.Pair(first, second)`.
+/// #[rs_class("my_ext.PairType", final)]
+/// pub struct PairTypeObj {
+///     base: TypeObj,
+///     pub first: Type,
+///     #[reflect(kw_only)]
+///     pub second: Type,
+/// }
+/// def_object_ref!(pub PairType, Type, PairTypeObj);
+///
+/// impl Placeholder for PairTypeObj {
+///     fn placeholder() -> Self {
+///         Self::new(TypeObj::placeholder(), Type::placeholder(), 
Type::placeholder())
+///     }
+/// }
+///
+/// impl PairType {
+///     pub fn new(first: impl Into<Type>, second: impl Into<Type>) -> Self {
+///         PairTypeObj::new(TypeObj::new(Location::unknown()), first, 
second).into()
+///     }
+/// }
+///
+/// tvm_ffi::static_init_block! {
+///     register_type::<PairType>()?;
+/// }
+/// ```
+pub fn register_type<R>() -> Result<()>
 where
-    R: ObjectRefCore + AnyCompatible + Placeholder + 'static,
+    R: ObjectRefCore + AnyCompatible + From<R::ContainerType> + 'static,
+    R::ContainerType: Placeholder,
 {
     let type_index = R::ContainerType::type_index();
-    let new = Function::from_typed(|| -> Result<R> { Ok(R::placeholder()) });
+    let new =
+        Function::from_typed(|| -> Result<R> { 
Ok(R::from(R::ContainerType::placeholder())) });
     register_type_attr(type_index, "__ffi_new__", Any::from(new))?;
     
tvm_ffi::cached_global_func!("ffi._RegisterFFIInit").call_tuple((type_index,))?;
     if let Ok(expose) = Function::get_global("ir.expose_type") {
@@ -134,86 +176,3 @@ fn register_type_attr(type_index: i32, name: &str, mut 
value: Any) -> Result<()>
         TVMFFITypeRegisterAttr(type_index, &name, Any::as_data_ptr(&mut value))
     })
 }
-
-/// Define IR types as C++ does with `refl::ObjectDef` in a 
`TVM_FFI_STATIC_INIT_BLOCK`: each item
-/// is an `rs_class` object struct and the `def_object_ref!` of its reference, 
written as outside a
-/// block.
-///
-/// ```no_run
-/// use tvm_tirx::ir::{Type, TypeObj};
-/// use tvm_tirx::tvm_ffi::{self, rs_class};
-///
-/// tvm_tirx::register_types! {
-///     /// The type of a pair of values, printed as `my_ext.Pair(first, 
second)`.
-///     #[rs_class("my_ext.PairType", final)]
-///     pub struct PairTypeObj {
-///         base: TypeObj,
-///         pub first: Type,
-///         #[reflect(kw_only)]
-///         pub second: Type,
-///     }
-///     def_object_ref!(pub PairType, Type, PairTypeObj);
-/// }
-///
-/// ```
-///
-/// The block registers its types when its program or library loads, as a
-/// [`register_ops!`](crate::register_ops) block registers its operators, and 
once per process.
-/// With an explicitly declared `my_ext` type API destination, Python 
constructs a type from its
-/// fields as `my_ext.Pair(T.int32, second=T.float32)`, like a C++ type, and a 
JSON graph holds it.
-/// Import `tvm_tirx` before loading the library so exposure requests reach 
Python. Import the
-/// extension module in Python scope, or supply it to text parsing through 
imports, `extra_vars`
-/// or explicit parser namespace registration; type exposure does not add 
parser defaults.
-/// The type of each field and the parent's object implement [`Placeholder`].
-#[macro_export]
-macro_rules! register_types {
-    ($(
-        $(#[$attr:meta])*
-        $vis:vis struct $obj:ident {
-            $base:ident: $base_ty:ty
-            $(, $(#[$field_attr:meta])* $field_vis:vis $field:ident: 
$field_ty:ty)* $(,)?
-        }
-        def_object_ref!($ref_vis:vis $ref:ident, $parent:ty, $ref_obj:ident 
$(,)?);
-    )*) => {
-        $(
-            $(#[$attr])*
-            $vis struct $obj {
-                $base: $base_ty,
-                $($(#[$field_attr])* $field_vis $field: $field_ty,)*
-            }
-
-            $crate::tvm_ffi::def_object_ref!($ref_vis $ref, $parent, $ref_obj);
-
-            impl $crate::ir::Placeholder for $obj {
-                fn placeholder() -> Self {
-                    $obj::new(
-                        <$base_ty as $crate::ir::Placeholder>::placeholder(),
-                        $(<$field_ty as 
$crate::ir::Placeholder>::placeholder(),)*
-                    )
-                }
-            }
-
-            impl $crate::ir::Placeholder for $ref {
-                fn placeholder() -> Self {
-                    <$obj as $crate::ir::Placeholder>::placeholder().into()
-                }
-            }
-        )*
-
-        $crate::__register_block!(
-            "register_types!",
-            stringify!($(
-                $(#[$attr])*
-                $vis struct $obj {
-                    $base: $base_ty
-                    $(, $(#[$field_attr])* $field_vis $field: $field_ty)*
-                }
-                def_object_ref!($ref_vis $ref, $parent, $ref_obj);
-            )*),
-            || {
-                $($crate::ir::__register_type::<$ref>()?;)*
-                Ok(())
-            }
-        );
-    };
-}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index ed1c902f3..d82481dc5 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -99,30 +99,44 @@
 //! `floordiv(&x + 1, 4)?` is the C++ `floordiv(x + 1, 4)`. An `Ex` passes 
into any function or
 //! constructor that takes an [`ir::IntoExpr`], as any expression does.
 //!
-//! Operators are defined from Rust in [`register_ops!`] blocks, as C++ 
defines them in a
-//! `TVM_FFI_STATIC_INIT_BLOCK`: each item is an [`ir::OpDef`] chain, the C++ 
`OpDef`, with the
-//! signature ([`ir::sig`], whose descriptors generate the validator), the 
attributes, by the keys
-//! of [`ir::op_attr`] and [`tirx::op_attr`], and the type inference, 
validator and region hooks as
-//! Rust functions, which C++ calls natively:
+//! Operators and types are defined from Rust in a 
[`tvm_ffi::static_init_block!`], as C++ defines
+//! them in a `TVM_FFI_STATIC_INIT_BLOCK`. An operator is an [`ir::OpDef`] 
chain, the C++ `OpDef`,
+//! with the signature ([`ir::sig`], whose descriptors generate the 
validator), the attributes, by
+//! the keys of [`ir::op_attr`] and [`tirx::op_attr`], and the type inference, 
validator and
+//! region hooks as Rust functions, which C++ calls natively; a type, written 
outside the block as
+//! any other ([Defining a type in Rust](#defining-a-type-in-rust)), is 
registered by
+//! [`ir::register_type`]:
 //!
 //! ```no_run
 //! use tvm_tirx::ir::op_attr::CALL_EFFECT_KIND;
 //! use tvm_tirx::ir::sig::{self, IntExpr};
-//! use tvm_tirx::ir::{CallEffectKind, CallObj, OpDef, OpHandle, Type};
-//! use tvm_tirx::tvm_ffi::Result;
+//! use tvm_tirx::ir::{register_type, CallEffectKind, CallObj, OpDef, 
OpHandle, Type};
+//! use tvm_tirx::tvm_ffi::{self, Result};
+//! # use tvm_tirx::ir::{Placeholder, TypeObj};
+//! # use tvm_tirx::tvm_ffi::{def_object_ref, rs_class, ObjectRefCore};
+//! # #[rs_class("my_ext.PairType", final)]
+//! # pub struct PairTypeObj { base: TypeObj, pub first: Type, pub second: 
Type }
+//! # def_object_ref!(pub PairType, Type, PairTypeObj);
+//! # impl Placeholder for PairTypeObj {
+//! #     fn placeholder() -> Self {
+//! #         Self::new(TypeObj::placeholder(), Type::placeholder(), 
Type::placeholder())
+//! #     }
+//! # }
 //!
 //! fn index_type(call: &CallObj) -> Result<Type> {
 //!     Ok(call.args.get(0)?.ty.clone())
 //! }
 //!
-//! tvm_tirx::register_ops! {
+//! tvm_ffi::static_init_block! {
 //!     OpDef::new("my_ext.clamp", "Clamp an index to the range `[0, bound)`.")
 //!         .signature((
 //!             sig::arg::<IntExpr>("index", "The index."),
 //!             sig::arg::<IntExpr>("bound", "The exclusive upper bound."),
 //!         ))
 //!         .set_infer_type(index_type)
-//!         .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure);
+//!         .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure)
+//!         .op()?;
+//!     register_type::<PairType>()?;
 //! }
 //!
 //! static CLAMP: OpHandle = OpHandle::new("my_ext.clamp");
@@ -130,8 +144,8 @@
 //!
 //! An [`ir::OpHandle`] in a `static` keeps an operator as C++ keeps one in 
`const Op& xxx_op()`.
 //! A block runs when its program or library loads, as a C++ one does, 
wherever in it the block
-//! is. Every library built on the crate exports [`ir::register_defined_ir`], 
which reports the
-//! errors of the blocks that failed, as the TVM FFI function 
`tirx_register_ir`.
+//! is. Every library built on the crate exports 
[`tvm_ffi::run_static_init_blocks`], which
+//! reports the errors of the blocks that failed, as the TVM FFI function 
`tirx_register_ir`.
 //!
 //! # The library Python loads
 //!
@@ -142,8 +156,8 @@
 //! crate defines by the time the import returns, as the C++ ones are, and 
calls `tirx_register_ir`
 //! to raise the error of a block that failed. Python uses the operators as it 
uses the C++ ones:
 //! `Op.get` finds them, a `Call` built in Python runs their hooks, and the 
TVMScript printer prints
-//! their calls. Completing an operator or a type of a 
[`register_types!`](crate::register_types)
-//! block requests API exposure through the `ir.expose_op` or `ir.expose_type` 
FFI receiver.
+//! their calls. Completing an operator with [`ir::OpDef::op`] or a type with 
[`ir::register_type`]
+//! requests API exposure through the `ir.expose_op` or `ir.expose_type` FFI 
receiver.
 //! Python installs these receivers before loading native libraries and 
publishes the selected
 //! definitions to registered destinations, including destinations initialized 
later. Op API
 //! destinations construct raw calls; script destinations add builder 
behavior. Type destinations
@@ -202,9 +216,10 @@
 //! The generated code names `tvm_ffi`, which `use tvm_tirx::tvm_ffi` brings 
into scope in a crate
 //! that depends only on `tvm-tirx`. The parent's object must have a public 
`new` and implement
 //! `ShallowCopy`; the bindings generate both for a complete type that is not 
final, as `ir.Type`.
-//! Written with its `def_object_ref!` in a 
[`register_types!`](crate::register_types) block, the
-//! type is also registered with the operators, so that Python constructs it 
from its fields and
-//! TVMScript parses it, as a C++ type.
+//! Registered by [`ir::register_type`] in a [`tvm_ffi::static_init_block!`], 
with the operators,
+//! the type is also constructed by Python from its fields and parsed by 
TVMScript, as a C++ type;
+//! its object must implement [`ir::Placeholder`], written from the 
placeholders of the parent's
+//! object and the fields.
 //!
 //! # Generated and hand-written code
 //!
@@ -305,13 +320,7 @@ pub mod tirx;
 
 pub use tvm_ffi;
 
-// The errors of the blocks of every library built on the crate, which `import 
tvm_tirx` raises for
-// `libtvm_tirx_rust`: `__tvm_ffi_tirx_register_ir`, the TVM FFI function 
`tirx_register_ir` of
-// the library as `tvm_ffi.load_module` loads it.
-tvm_ffi::tvm_ffi_dll_export_typed_func!(tirx_register_ir, 
ir::register_defined_ir);
-
-/// What the exported macros expand to.
-#[doc(hidden)]
-pub mod __private {
-    pub use linkme;
-}
+// The errors of the static init blocks of every library built on the crate, 
which
+// `import tvm_tirx` raises for `libtvm_tirx_rust`: 
`__tvm_ffi_tirx_register_ir`, the TVM FFI
+// function `tirx_register_ir` of the library as `tvm_ffi.load_module` loads 
it.
+tvm_ffi::tvm_ffi_dll_export_typed_func!(tirx_register_ir, 
tvm_ffi::run_static_init_blocks);
diff --git a/rust/tests/ir/op_def.rs b/rust/tests/ir/op_def.rs
index 0601595bb..f0a66987f 100644
--- a/rust/tests/ir/op_def.rs
+++ b/rust/tests/ir/op_def.rs
@@ -17,10 +17,10 @@
  * under the License.
  */
 
-//! Tests of `ir::op_def`: operators registered from Rust, by an `OpDef` or in 
`register_ops!`
-//! blocks, which `register_defined_ir` or the first lookup through an 
`OpHandle` runs, are used by
+//! Tests of `ir::op_def`: operators registered from Rust, by an `OpDef` or in 
static init blocks,
+//! which run when the test binary loads or at the first lookup through an 
`OpHandle`, are used by
 //! the native registry, `Call` constructor and validation, and their type 
inference, validators
-//! and region contracts are native hooks.
+//! and region contracts are native hooks; and types registered with them.
 //!
 //! The registry does not synchronize registration, so the tests take 
`serial()`; the tests
 //! register operators of different names.
@@ -31,19 +31,20 @@ use std::sync::atomic::{AtomicUsize, Ordering};
 use tvm_tirx::ir::op_attr::{CALL_EFFECT_KIND, FIXED_RETURN_TYPE};
 use tvm_tirx::ir::sig::{self, IntExpr, PrimExpr, PrimVar};
 use tvm_tirx::ir::{
-    register_defined_ir, AttrKey, Attrs, Call, CallEffectKind, CallObj, 
DictAttrs, Evaluate, Expr,
-    FuncType, IntImm, Op, OpDef, OpHandle, PrimType, RegionStmt, 
RegionStmtObj, Registration,
-    StringImm, Type, Var, VarObj, REGISTRATIONS, UNKNOWN_LOC,
+    register_type, AttrKey, Attrs, Call, CallEffectKind, CallObj, DictAttrs, 
Evaluate, Expr,
+    FuncType, IntImm, Op, OpDef, OpHandle, Placeholder, PrimType, RegionStmt, 
RegionStmtObj,
+    StringImm, Type, TypeObj, Var, VarObj, UNKNOWN_LOC,
 };
 use tvm_tirx::tirx::{CallFFIKernelAttr, TensorVar};
+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::{
-    Any, AnyView, Array, Error, ErrorKind, Function, Map, ObjectCore, 
ObjectRefCast, ObjectRefCore,
-    Result, String as FfiString, ATTRIBUTE_ERROR, INTERNAL_ERROR, TYPE_ERROR, 
VALUE_ERROR,
+    self, def_object_ref, rs_class, 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,
 };
 
 use super::{int32, parse_dtype, tensor_var};
-use types::{BlockType, BlockTypeObj};
 use crate::common::{global, serial, structural_equal};
 
 fn var(name: &str, dtype: &str) -> Var {
@@ -413,7 +414,23 @@ fn accept(_call: &CallObj) -> Result<()> {
     Ok(())
 }
 
-tvm_tirx::register_ops! {
+/// A type and a count, registered with the operators when the test binary 
loads.
+#[rs_class("rust_test.BlockType", final)]
+pub struct BlockTypeObj {
+    base: TypeObj,
+    pub elem: Type,
+    #[reflect(kw_only)]
+    pub count: i64,
+}
+def_object_ref!(pub BlockType, Type, BlockTypeObj);
+
+impl Placeholder for BlockTypeObj {
+    fn placeholder() -> Self {
+        Self::new(TypeObj::placeholder(), Type::placeholder(), 
i64::placeholder())
+    }
+}
+
+tvm_ffi::static_init_block! {
     OpDef::new("rust_test.defined_identity", "
         Return the value.
 
@@ -421,73 +438,61 @@ tvm_tirx::register_ops! {
     ")
         .signature(sig::arg::<Expr>("value", "The value."))
         .set_infer_type(identity_type)
-        .set_attr(CALL_EFFECT_KIND, CallEffectKind::ExprAnnotation);
+        .set_attr(CALL_EFFECT_KIND, CallEffectKind::ExprAnnotation)
+        .op()?;
 
     // `tirx.launch_thread`'s region contract (`src/tirx/op/region.cc`), 
written in Rust: one
     // thread index of the extent's type.
     OpDef::new("rust_test.launch", "Bind a thread index below an extent.")
         .signature((sig::arg::<StringImm>("tag", ""), 
sig::arg::<IntExpr>("extent", "")))
         .set_region_body_params(launch_body_params)
-        .set_region_validate(no_results_or_attributes);
+        .set_region_validate(no_results_or_attributes)
+        .op()?;
 
     OpDef::new("rust_test.panicking_region", "")
-        .set_region_body_params(|_: &CallObj| -> Result<Array<Var>> { 
panic!("hook bug") });
+        .set_region_body_params(|_: &CallObj| -> Result<Array<Var>> { 
panic!("hook bug") })
+        .op()?;
+
+    register_type::<BlockType>()?;
 }
 
 mod elsewhere {
     use super::*;
 
-    tvm_tirx::register_ops! {
+    tvm_ffi::static_init_block! {
         OpDef::new("rust_test.defined_elsewhere", "An operator in another 
module.")
-            .set_attr(FIXED_RETURN_TYPE, PrimType::void());
-    }
-}
-
-// A type of a `register_types!` block, registered with the operators when the 
test binary loads.
-mod types {
-    use tvm_tirx::ir::{Type, TypeObj};
-    use tvm_tirx::tvm_ffi::{self, rs_class};
-
-    tvm_tirx::register_types! {
-        /// A type and a count.
-        #[rs_class("rust_test.BlockType", final)]
-        pub struct BlockTypeObj {
-            base: TypeObj,
-            pub elem: Type,
-            #[reflect(kw_only)]
-            pub count: i64,
-        }
-        def_object_ref!(pub BlockType, Type, BlockTypeObj);
+            .set_attr(FIXED_RETURN_TYPE, PrimType::void())
+            .op()?;
     }
 }
 
-// Two blocks that fail, which `register_defined_ir` reports together while it 
runs the others: a
+// Two blocks that fail, which `run_static_init_blocks` reports together while 
it runs the others: a
 // name C++ registers, and a second custom validator, after which the block 
stops.
 mod failing {
     use super::*;
 
-    tvm_tirx::register_ops! {
-        OpDef::new("tirx.launch_thread", "");
+    tvm_ffi::static_init_block! {
+        OpDef::new("tirx.launch_thread", "").op()?;
     }
 
-    tvm_tirx::register_ops! {
-        OpDef::new("rust_test.defined_broken", 
"").set_validator(accept).set_validator(accept);
-        OpDef::new("rust_test.after_broken", "");
+    tvm_ffi::static_init_block! {
+        OpDef::new("rust_test.defined_broken", 
"").set_validator(accept).set_validator(accept).op()?;
+        OpDef::new("rust_test.after_broken", "").op()?;
     }
 }
 
-/// The blocks of `module` in this test binary.
-fn blocks_of(module: &str) -> Vec<&'static Registration> {
+/// The static init blocks of `module` in this test binary.
+fn blocks_of(module: &str) -> Vec<&'static StaticInitBlock> {
     let prefix = format!("ir::op_def{module}:");
     let of_module = |identity: &str| {
         identity
             .strip_prefix(&prefix)
             .is_some_and(|rest| rest.starts_with(|c: char| c.is_ascii_digit()))
     };
-    REGISTRATIONS.iter().filter(|block| of_module(&block.identity())).collect()
+    STATIC_INIT_BLOCKS.iter().filter(|block| 
of_module(&block.identity())).collect()
 }
 
-fn is_block_registered(block: &Registration) -> bool {
+fn is_block_registered(block: &StaticInitBlock) -> bool {
     Function::get_global(&block.marker()).is_ok()
 }
 
@@ -559,7 +564,7 @@ fn region(
 }
 
 #[test]
-fn register_ops_blocks_define_operators() {
+fn static_init_blocks_define_operators_and_types() {
     let _serial = serial();
 
     // The blocks ran when the test binary loaded; a block that fails does not 
keep the others from
@@ -575,11 +580,11 @@ fn register_ops_blocks_define_operators() {
     let fixed = Op::attr_map::<Type>("TFixedReturnType").unwrap().unwrap();
     assert!(structural_equal(&fixed.get(&elsewhere).unwrap().unwrap(), 
&PrimType::void()));
 
-    // `register_defined_ir` reports the blocks that failed together, each 
with its first error;
+    // `run_static_init_blocks` reports the blocks that failed together, each 
with its first error;
     // a block stops at its first failure, and a failed block does not run 
again.
-    let error = register_defined_ir().unwrap_err();
+    let error = run_static_init_blocks().unwrap_err();
     let message = error.message();
-    assert!(message.starts_with("2 register_ops! blocks failed:"), 
"{message}");
+    assert!(message.starts_with("2 static init blocks failed:"), "{message}");
     assert!(message.contains("op_def::failing:"), "{message}");
     assert!(
         message.contains("ValueError: Validator of rust_test.defined_broken is 
already registered"),
@@ -588,10 +593,10 @@ fn register_ops_blocks_define_operators() {
     assert!(message.contains("AttributeError: "), "{message}");
     assert!(message.contains("is registered before"), "{message}");
     assert!(Op::get("rust_test.after_broken").is_err(), "the block stopped");
-    assert_eq!(register_defined_ir().unwrap_err().message(), message);
+    assert_eq!(run_static_init_blocks().unwrap_err().message(), message);
 
-    // The type of a `register_types!` block is registered with them: tvm-ffi 
constructs it from its
-    // fields, positionally or by keyword, and reads it back from a JSON graph.
+    // 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 kwargs = global("ffi.GetKwargsObject").call_tuple(()).unwrap();
     let (elem, count, four) = (Type::from(PrimType::int(32)), 
FfiString::from("count"), 4i64);
@@ -637,7 +642,7 @@ fn register_ops_blocks_define_operators() {
     assert_error(
         UNREGISTERED.get(),
         ATTRIBUTE_ERROR,
-        "Operator rust_test.unregistered is not registered\n2 register_ops! 
blocks failed:",
+        "Operator rust_test.unregistered is not registered\n2 static init 
blocks failed:",
     );
     assert!(!UNREGISTERED.is(&call.op));
 
@@ -694,50 +699,3 @@ fn register_ops_blocks_define_operators() {
     );
     assert!(built.is_err(), "a panicking hook fails the constructor");
 }
-
-#[test]
-fn a_block_another_library_registered_is_skipped() {
-    let _serial = serial();
-    fn register() -> Result<()> {
-        OpDef::new("rust_test.registered_once", "").op()?;
-        Ok(())
-    }
-    // One block in two libraries, as one of a crate both link.
-    let text = r#"OpDef::new("rust_test.registered_once", "")"#;
-    let block = || Registration::__new("register_ops!", "ext::shared", 3, 
"0.1.0", text, register);
-    let (first, second) = (block(), block());
-    assert_eq!(first.marker(), second.marker());
-
-    // The first library registers the operator and marks the block; the 
second skips it, where
-    // registering the name again would fail.
-    first.register().unwrap();
-    assert!(is_block_registered(&first));
-    let op = Op::get("rust_test.registered_once").unwrap();
-    second.register().unwrap();
-    assert!(Op::get("rust_test.registered_once").unwrap().same_as(&op));
-
-    // Another version of the crate is another block, whose name is already 
registered.
-    let other = Registration::__new("register_ops!", "ext::shared", 3, 
"0.2.0", text, register);
-    assert!(other.register().unwrap_err().message().contains("is registered 
before"));
-}
-
-#[test]
-fn register_ops_block_identity_is_its_crate_version_and_text() {
-    fn register() -> Result<()> {
-        Ok(())
-    }
-    let identity = |version, text| {
-        Registration::__new("register_ops!", "ext::ops", 7, version, text, 
register)
-    };
-    let text = r#"OpDef::new("ext.clamp", 
"Clamp.").set_infer_type(first_arg_type)"#;
-    let block = identity("0.1.0", text).identity();
-    assert!(block.starts_with("ext::ops:[email protected]#"), "{block}");
-
-    // The same block in another library, whose compiler may space the tokens 
otherwise.
-    let spaced = "OpDef :: new(\"ext.clamp\", \"Clamp.\")\n    . 
set_infer_type(first_arg_type)";
-    assert_eq!(identity("0.1.0", spaced).identity(), block);
-    // A block of another version of the crate, or whose text differs, is 
another block.
-    assert_ne!(identity("0.1.1", text).identity(), block);
-    let renamed = text.replace("ext.clamp", "ext.clip");
-    assert_ne!(identity("0.1.0", &renamed).identity(), block);
-}
diff --git a/tests/python/ir/test_op_rust.py b/tests/python/ir/test_op_rust.py
index a0b61cdfd..a72dd18b1 100644
--- a/tests/python/ir/test_op_rust.py
+++ b/tests/python/ir/test_op_rust.py
@@ -268,7 +268,7 @@ def 
test_a_library_registers_its_operators_when_python_loads_it(define_ir_librar
         ROUND_TRIP,
         libraries=[define_ir_library, copy],
     )
-    assert "registering operators and types failed" not in stderr, stderr
+    assert "the static init block at" not in stderr, stderr
 
 
 def 
test_a_block_that_fails_when_its_library_loads_is_reported(define_ir_library):
@@ -295,5 +295,5 @@ def 
test_a_block_that_fails_when_its_library_loads_is_reported(define_ir_library
         """,
         libraries=[define_ir_library],
     )
-    assert "registering operators and types failed" in stderr, stderr
+    assert "the static init block at" in stderr, stderr
     assert "Operator tirx.example_mark is registered before" in stderr, stderr

Reply via email to