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 de477ccd [REFACTOR][RUST] Link libtvm_tirx_compiler instead of loading 
it at run time (#17)
de477ccd is described below

commit de477ccd574e4e97244434e3d23ae7588d7e0177
Author: Linzhang Li <[email protected]>
AuthorDate: Wed Oct 21 17:04:11 2026 -0400

    [REFACTOR][RUST] Link libtvm_tirx_compiler instead of loading it at run 
time (#17)
    
    # [REFACTOR][RUST] Link libtvm_tirx_compiler instead of loading it at
    run time
    
    ## Summary
    
    The Rust crate linked only libtvm_ffi and loaded `libtvm_tirx_compiler`
    at run time through
    `Module::load_from_file`. Every test started with
    `common::load_tirx_compiler()`, and a user of
    the crate had to load the library and keep the module alive before
    touching any binding.
    
    The crate now links `libtvm_tirx_compiler` from its build script, the
    way `tvm-ffi-sys` links
    libtvm_ffi. The library is loaded when the program starts, its type keys
    and global functions are
    registered before any Rust code runs, and the run-time loading step is
    gone. Before:
    
    ```rust
    let _compiler = 
Module::load_from_file("../build/lib/libtvm_tirx_compiler.so")?;
    let target = Target::new("llvm")?;
    ```
    
    After:
    
    ```rust
    let target = Target::new("llvm")?;
    ```
---
 AGENTS.md                                          | 11 +--
 rust/build.rs                                      | 81 ++++++++++++++++++----
 rust/src/lib.rs                                    | 53 +++++++++++---
 rust/tests/codegen/llvm.rs                         |  3 -
 rust/tests/codegen/main.rs                         |  3 +-
 rust/tests/common/mod.rs                           | 23 +-----
 rust/tests/ir/expr.rs                              |  4 +-
 rust/tests/ir/function.rs                          |  3 +-
 rust/tests/ir/location.rs                          |  3 +-
 rust/tests/ir/module.rs                            |  3 +-
 rust/tests/ir/op.rs                                |  3 +-
 rust/tests/ir/prim.rs                              |  3 +-
 rust/tests/ir/prim/expr.rs                         |  3 +-
 rust/tests/ir/prim/op.rs                           |  6 +-
 rust/tests/ir/prim/vector_expr.rs                  |  3 +-
 rust/tests/ir/stmt.rs                              |  3 +-
 rust/tests/ir/transform/core.rs                    |  4 +-
 rust/tests/ir/transform/instrument.rs              |  4 +-
 rust/tests/ir/type.rs                              |  3 +-
 rust/tests/ir/unique_name_supply.rs                |  3 +-
 rust/tests/runtime/main.rs                         |  7 --
 rust/tests/script/main.rs                          |  3 +-
 rust/tests/sym/analyzer.rs                         |  5 +-
 rust/tests/sym/int_set.rs                          |  5 +-
 rust/tests/sym/iter_affine_map.rs                  |  4 +-
 rust/tests/sym/main.rs                             |  3 +-
 rust/tests/target/main.rs                          |  8 +--
 rust/tests/tirx/analysis/analysis.rs               |  4 +-
 rust/tests/tirx/function.rs                        |  3 +-
 rust/tests/tirx/index_map.rs                       |  3 +-
 rust/tests/tirx/layout.rs                          |  5 +-
 rust/tests/tirx/op.rs                              |  3 +-
 rust/tests/tirx/tensor.rs                          |  3 +-
 rust/tests/tirx/thread_scope.rs                    |  3 +-
 rust/tests/tirx/tile/dispatch.rs                   |  3 +-
 rust/tests/tirx/tile/instruction.rs                |  4 +-
 rust/tests/tirx/transform/function_pass.rs         |  3 +-
 rust/tests/tirx/transform/transform.rs             |  4 +-
 .../tests/codegen/main.rs => src/ir/link_anchor.cc | 32 ++++-----
 .../codegen/main.rs => src/runtime/link_anchor.cc  | 32 ++++-----
 40 files changed, 175 insertions(+), 184 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index f79d0c70..62db434d 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -123,12 +123,15 @@ describe missing coverage; they do not demonstrate that a 
backend works.
 
 `rust/` is a standalone crate (`tvm-tirx`) over `3rdparty/tvm-ffi/rust`. Its
 IR bindings are generated by `tvm-ffi-stubgen` between `tvm-ffi-stubgen(begin)`
-and `(end)` markers. Build TIRx first; the tests load
-`build/lib/libtvm_tirx_compiler.so` and need `tvm-ffi-config` on `PATH`:
+and `(end)` markers. The crate links `libtvm_tirx_compiler` from the directory
+`tvm-tirx-config --libdir` prints, so build TIRx first. Its build script needs
+`tvm-ffi-config` and `tvm-tirx-config` on `PATH`, which `uv run` provides after
+the editable install:
 
 ```bash
-tvm-ffi-stubgen rust/src --target rust --dlls 
build/lib/libtvm_tirx_compiler.so --check
-(cd rust && cargo fmt --check && cargo test)
+uv run --no-sync tvm-ffi-stubgen rust/src --target rust --dlls 
build/lib/libtvm_tirx_compiler.so --check
+uv run --no-sync cargo fmt --check --manifest-path rust/Cargo.toml
+uv run --no-sync cargo test --manifest-path rust/Cargo.toml
 ```
 
 ## Linting
diff --git a/rust/build.rs b/rust/build.rs
index 2caa6072..c062967e 100644
--- a/rust/build.rs
+++ b/rust/build.rs
@@ -16,30 +16,81 @@
  * specific language governing permissions and limitations
  * under the License.
  */
-//! `tvm-ffi-sys`'s build script links libtvm_ffi, but the loader-path
-//! environment it emits only applies to its own package; re-emit it here so a
-//! plain `cargo test` finds libtvm_ffi at startup.
+//! Links `libtvm_tirx_compiler` from the directory `tvm-tirx-config --libdir` 
prints, as
+//! `tvm-ffi-sys` links libtvm_ffi from the one `tvm-ffi-config --libdir` 
prints. The loader-path
+//! environment `tvm-ffi-sys` emits only applies to its own package, so this 
script emits it again
+//! for both directories, so that a plain `cargo test` finds the libraries at 
startup.
 
 use std::env;
+use std::path::Path;
 use std::process::Command;
 
+/// The library directory a configuration helper prints for `--libdir`.
+fn lib_dir(config: &str) -> String {
+    let output = 
Command::new(config).arg("--libdir").output().unwrap_or_else(|err| {
+        panic!("could not run `{config}`: {err}; put the directory of 
`{config}` on PATH")
+    });
+    assert!(
+        output.status.success(),
+        "`{config} --libdir` failed ({}): {}",
+        output.status,
+        String::from_utf8_lossy(&output.stderr).trim()
+    );
+    let lib_dir = String::from_utf8(output.stdout).unwrap().trim().to_string();
+    assert!(!lib_dir.is_empty(), "`{config} --libdir` printed no library 
directory");
+    lib_dir
+}
+
+/// Checks that `lib_dir` holds the file the linker looks for under 
`-ltvm_tirx_compiler`.
+///
+/// `tvm-tirx-config --libdir` falls back to `<prefix>/lib` when no directory 
it knows holds the
+/// libraries, as before the first build. Failing here rather than in the link 
step makes cargo
+/// run the script again on the next build, instead of keeping a search path 
that has no library.
+fn check_compiler_library(lib_dir: &str, target_os: &str) {
+    let names: &[&str] = match target_os {
+        "windows" => &["tvm_tirx_compiler.lib", "libtvm_tirx_compiler.dll.a"],
+        "macos" | "ios" => &["libtvm_tirx_compiler.dylib"],
+        _ => &["libtvm_tirx_compiler.so"],
+    };
+    let dir = Path::new(lib_dir);
+    assert!(
+        names.iter().any(|name| dir.join(name).is_file()),
+        "`tvm-tirx-config --libdir` printed {lib_dir}, which holds no {}: 
build TIRx first \
+         (`uv pip install -e .`), or set TVM_TIRX_LIBRARY_PATH to the 
directory of the library",
+        names.join(" or ")
+    );
+}
+
 fn main() {
     println!("cargo:rerun-if-env-changed=PATH");
+    // `tvm-tirx-config` puts this directory first when it is set.
+    println!("cargo:rerun-if-env-changed=TVM_TIRX_LIBRARY_PATH");
     println!("cargo:rerun-if-changed=build.rs");
-    let output = Command::new("tvm-ffi-config")
-        .arg("--libdir")
-        .output()
-        .expect("failed to run tvm-ffi-config; put the tvm_ffi package's bin 
directory on PATH");
-    assert!(output.status.success(), "tvm-ffi-config --libdir failed");
-    let lib_dir = String::from_utf8(output.stdout).unwrap().trim().to_string();
+    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
+    let ffi_lib_dir = lib_dir("tvm-ffi-config");
+    let tirx_lib_dir = lib_dir("tvm-tirx-config");
+    check_compiler_library(&tirx_lib_dir, &target_os);
+
+    // The crate references the library only through its link anchor (see 
`src/lib.rs`).
+    println!("cargo:rustc-link-search=native={tirx_lib_dir}");
+    println!("cargo:rustc-link-lib=dylib=tvm_tirx_compiler");
 
-    let loader_var = match env::var("CARGO_CFG_TARGET_OS").as_deref() {
-        Ok("windows") => "PATH",
-        Ok("macos") => "DYLD_LIBRARY_PATH",
+    let loader_var = match target_os.as_str() {
+        "windows" => "PATH",
+        "macos" => "DYLD_LIBRARY_PATH",
         _ => "LD_LIBRARY_PATH",
     };
+    // The value emitted below starts with the variable's value in this 
script's environment, so
+    // the script must run again when that changes; on Windows the variable is 
PATH, declared above.
+    if loader_var != "PATH" {
+        println!("cargo:rerun-if-env-changed={loader_var}");
+    }
     let sep = if loader_var == "PATH" { ";" } else { ":" };
-    let prev = env::var(loader_var).unwrap_or_default();
-    let val = if prev.is_empty() { lib_dir } else { 
format!("{prev}{sep}{lib_dir}") };
-    println!("cargo:rustc-env={loader_var}={val}");
+    let mut dirs = vec![ffi_lib_dir, tirx_lib_dir];
+    if let Ok(prev) = env::var(loader_var) {
+        if !prev.is_empty() {
+            dirs.insert(0, prev);
+        }
+    }
+    println!("cargo:rustc-env={loader_var}={}", dirs.join(sep));
 }
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index cf2e636b..96a6e245 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -36,17 +36,25 @@
 //!
 //! # Using the bindings
 //!
-//! Load `libtvm_tirx_compiler` before using these types, so that their type 
keys are registered,
-//! and keep the module alive while its objects are in use. The native global 
functions have Rust
-//! wrappers, as methods of the handle types and as functions of the modules, 
such as
-//! [`target::Target::new`] for `target.Target`. Native functions that have no 
Rust wrapper are
-//! called through [`tvm_ffi::Function::get_global`]. From `rust/` on Linux:
+//! The crate links `libtvm_tirx_compiler`, so its type keys and global 
functions are registered
+//! when a program starts. The build script finds the library through 
`tvm-tirx-config --libdir`,
+//! as `tvm-ffi-sys` finds libtvm_ffi through `tvm-ffi-config --libdir`: both 
helpers come with
+//! their Python packages (`apache-tvm-tirx` and `apache-tvm-ffi`) and must be 
on `PATH` when the
+//! crate is built. The native global functions have Rust wrappers, as methods 
of the handle types
+//! and as functions of the modules, such as [`target::Target::new`] for 
`target.Target`. Native
+//! functions that have no Rust wrapper are called through 
[`tvm_ffi::Function::get_global`].
+//!
+//! The crate uses the library only through the TVM FFI registry, so a linker 
that drops
+//! unreferenced libraries (`--as-needed`, which rustc passes on Linux) keeps 
it only because
+//! the crate holds a reference to a symbol of it, the link anchor, that is 
linked along with the
+//! crate. A program that calls at least one wrapper therefore has the library 
as a dependency,
+//! at no run-time cost. A program that reaches the registry only through 
`tvm_ffi` itself,
+//! without calling a wrapper, must keep the library by other means, such as 
the linker
+//! arguments `tvm-tirx-config --libs compiler` prints.
 //!
 //! ```no_run
 //! use tvm_tirx::target::Target;
-//! use tvm_tirx::tvm_ffi::Module;
 //!
-//! let _compiler = 
Module::load_from_file("../build/lib/libtvm_tirx_compiler.so")?;
 //! let target = Target::new("llvm")?;
 //! assert_eq!(target.kind()?.name()?.as_str(), "llvm");
 //! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
@@ -98,15 +106,16 @@
 //!
 //! # Tests
 //!
-//! Build `libtvm_tirx_compiler` first. The build scripts need 
`tvm-ffi-config` on `PATH`, which
-//! `uv run` provides from the `.venv`:
+//! Build `libtvm_tirx_compiler` first. The build scripts need 
`tvm-ffi-config` and
+//! `tvm-tirx-config` on `PATH`, which `uv run` provides from the `.venv` 
after the editable
+//! install, where `tvm-tirx-config --libdir` is `build/lib`:
 //!
 //! ```bash
 //! uv run --no-sync cargo test --manifest-path rust/Cargo.toml
 //! ```
 //!
-//! The tests load `build/lib/libtvm_tirx_compiler.so`; set 
`TVM_TIRX_COMPILER_LIBRARY` to load
-//! another library.
+//! Set `TVM_TIRX_LIBRARY_PATH` to the directory of another 
`libtvm_tirx_compiler` to build and
+//! test against that one; `tvm-tirx-config` puts it first.
 //!
 //! The tests are laid out like the modules. Each top-level module is one test 
binary,
 //! `tests/<module>/main.rs`, and the tests of `src/<path>.rs` are in 
`tests/<path>.rs`:
@@ -114,6 +123,28 @@
 //! tests of one module with `--test <module>`, such as `--test tirx`. 
`tests/common/` holds the
 //! helpers the binaries share.
 
+// The crate reaches `libtvm_tirx_compiler` through the FFI registry, so a 
linker that drops
+// unreferenced libraries (`--as-needed`) would drop the library the build 
script links, and its
+// registrations would never run. Each TIRx shared library exports a 
`<library>_link_anchor`
+// function that does nothing, defined in the `link_anchor.cc` of its sources 
(for the compiler,
+// `src/ir/link_anchor.cc`) and declared by whoever references it. The static 
below holds the
+// anchor's address: a relocation against the symbol in the object of this 
module, which costs
+// nothing at run time and is what makes the linker keep the library.
+//
+// A program pulls in only the objects of the crate it references, so the 
static keeps the
+// library only when the object of this module is pulled. It is, because the 
objects of the crate
+// reference one another densely and `sym::analyzer` references 
`ExitOnDrop::new` below. The test
+// binaries rely on the same mechanism and fail at their first registry lookup 
if that changes. A
+// program that uses the registry only through `tvm_ffi` itself, without 
calling a wrapper, must
+// keep the library by other means, such as the linker arguments 
`tvm-tirx-config --libs compiler`
+// prints.
+extern "C" {
+    fn tvm_tirx_compiler_link_anchor() -> i32;
+}
+
+#[used]
+static TVM_TIRX_COMPILER_LINK_ANCHOR: unsafe extern "C" fn() -> i32 = 
tvm_tirx_compiler_link_anchor;
+
 /// Makes a scope guard neither `Send` nor `Sync`: the native scopes keep 
their state per thread,
 /// or in an object that is not synchronized, so a scope must end on the 
thread that opened it.
 pub(crate) type ThreadBound = std::marker::PhantomData<*const ()>;
diff --git a/rust/tests/codegen/llvm.rs b/rust/tests/codegen/llvm.rs
index dec2c74d..e74843e6 100644
--- a/rust/tests/codegen/llvm.rs
+++ b/rust/tests/codegen/llvm.rs
@@ -22,11 +22,8 @@
 use tvm_tirx::codegen::{self, llvm};
 use tvm_tirx::target::Target;
 
-use crate::common::load_tirx_compiler;
-
 #[test]
 fn llvm_queries() {
-    load_tirx_compiler();
     assert!(llvm::version_major().unwrap() >= 10);
     let sqrt = llvm::lookup_intrinsic_id("llvm.sqrt").unwrap();
     assert!(sqrt > 0);
diff --git a/rust/tests/codegen/main.rs b/rust/tests/codegen/main.rs
index aebe9c0d..86856c24 100644
--- a/rust/tests/codegen/main.rs
+++ b/rust/tests/codegen/main.rs
@@ -28,11 +28,10 @@ mod llvm;
 use tvm_tirx::codegen;
 use tvm_tirx::target::Target;
 
-use common::{empty_module, load_tirx_compiler};
+use common::empty_module;
 
 #[test]
 fn build_module() {
-    load_tirx_compiler();
     let module = empty_module();
     codegen::build_module(&module, Target::new("llvm").unwrap()).unwrap();
     codegen::build_module(&module, Target::new("c").unwrap()).unwrap();
diff --git a/rust/tests/common/mod.rs b/rust/tests/common/mod.rs
index cd7bab2b..aa2f2cf8 100644
--- a/rust/tests/common/mod.rs
+++ b/rust/tests/common/mod.rs
@@ -22,30 +22,11 @@
 #![allow(dead_code)]
 
 use std::mem::size_of;
-use std::path::PathBuf;
-use std::sync::{Mutex, MutexGuard, OnceLock};
+use std::sync::{Mutex, MutexGuard};
 
 use tvm_tirx::ir::{BaseFunc, GlobalInfo, GlobalVar, IRModule};
 use tvm_tirx::tvm_ffi::tvm_ffi_sys::{TVMFFIGetTypeInfo, TVMFFITypeInfo};
-use tvm_tirx::tvm_ffi::{
-    AnyCompatible, AnyView, Array, Function, Map, Module, ObjectCore, String 
as FfiString,
-};
-
-static TIRX_COMPILER: OnceLock<Module> = OnceLock::new();
-
-/// Load `libtvm_tirx_compiler` so its type keys are in the FFI type registry.
-pub fn load_tirx_compiler() {
-    TIRX_COMPILER.get_or_init(|| {
-        let default = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
-            .join("..")
-            .join("build")
-            .join("lib")
-            .join("libtvm_tirx_compiler.so");
-        let library =
-            
std::env::var_os("TVM_TIRX_COMPILER_LIBRARY").map(PathBuf::from).unwrap_or(default);
-        Module::load_from_file(library.to_string_lossy()).unwrap()
-    });
-}
+use tvm_tirx::tvm_ffi::{AnyCompatible, AnyView, Array, Function, Map, 
ObjectCore, String as FfiString};
 
 /// Run the tests of a binary one at a time: registering an operator, a memory 
scope or the layout
 /// axes of a backend grows a native registry, which other threads must not 
read meanwhile, and
diff --git a/rust/tests/ir/expr.rs b/rust/tests/ir/expr.rs
index 463d7c24..6d63613d 100644
--- a/rust/tests/ir/expr.rs
+++ b/rust/tests/ir/expr.rs
@@ -23,11 +23,10 @@ use tvm_tirx::ir::*;
 use tvm_tirx::tvm_ffi::Any;
 
 use super::{dtype, int32, int32_var, loc, op, tensor_var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn expressions() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = int32_var("x");
     assert_eq!(x.name.as_str(), "x");
@@ -70,7 +69,6 @@ fn expressions() {
 
 #[test]
 fn ranges() {
-    load_tirx_compiler();
     let _serial = serial();
     let range = Range::new(int32(8), None, loc()).unwrap();
     assert!(structural_equal(&range.extent, &int32(8)));
diff --git a/rust/tests/ir/function.rs b/rust/tests/ir/function.rs
index 484031d1..9a9be894 100644
--- a/rust/tests/ir/function.rs
+++ b/rust/tests/ir/function.rs
@@ -22,11 +22,10 @@
 use tvm_tirx::tvm_ffi::{Any, Map, String as FfiString};
 
 use super::{function, int32};
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 
 #[test]
 fn function_attrs() {
-    load_tirx_compiler();
     let _serial = serial();
     let func = function(0);
     let copy = func.copy().unwrap();
diff --git a/rust/tests/ir/location.rs b/rust/tests/ir/location.rs
index 5337839b..8dc49db9 100644
--- a/rust/tests/ir/location.rs
+++ b/rust/tests/ir/location.rs
@@ -21,11 +21,10 @@
 
 use tvm_tirx::ir::SourceName;
 
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn source_names() {
-    load_tirx_compiler();
     let _serial = serial();
     let name = SourceName::get("main.py").unwrap();
     assert_eq!(name.name.as_str(), "main.py");
diff --git a/rust/tests/ir/module.rs b/rust/tests/ir/module.rs
index 23bf1002..8c00b325 100644
--- a/rust/tests/ir/module.rs
+++ b/rust/tests/ir/module.rs
@@ -23,11 +23,10 @@ use tvm_tirx::ir::{BaseFunc, Expr, GlobalInfo, GlobalVar, 
IRModule};
 use tvm_tirx::tvm_ffi::{Any, Map, String as FfiString};
 
 use super::{function, int32, loc};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn modules() {
-    load_tirx_compiler();
     let _serial = serial();
     let func = function(0);
     let main = GlobalVar::new("main", loc()).unwrap();
diff --git a/rust/tests/ir/op.rs b/rust/tests/ir/op.rs
index c0cb139e..32d5de4c 100644
--- a/rust/tests/ir/op.rs
+++ b/rust/tests/ir/op.rs
@@ -21,11 +21,10 @@
 
 use tvm_tirx::ir::Op;
 
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 
 #[test]
 fn ops() {
-    load_tirx_compiler();
     let _serial = serial();
     let op_names = Op::list_op_names().unwrap();
     assert!(op_names.iter().any(|name| name.as_str() == "prim.exp"));
diff --git a/rust/tests/ir/prim.rs b/rust/tests/ir/prim.rs
index e57ec544..d9d5bed9 100644
--- a/rust/tests/ir/prim.rs
+++ b/rust/tests/ir/prim.rs
@@ -29,7 +29,7 @@ use tvm_tirx::ir::{Expr, FloatImm, IntImm, PrimType, Var};
 use tvm_tirx::tvm_ffi::{BigInt, DLDataType, DLDataTypeExt, ObjectRefCast, 
String as FfiString};
 
 use super::loc;
-use crate::common::{global, load_tirx_compiler, serial};
+use crate::common::{global, serial};
 
 fn ty(dtype: &str) -> PrimType {
     PrimType::from_dtype(DLDataType::try_from_str(dtype).unwrap()).unwrap()
@@ -73,7 +73,6 @@ fn int_value(expr: Expr) -> i64 {
 
 #[test]
 fn expr_deep_equal() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", "int32");
     let a = prim::op::add(&x, int("int32", 1), loc()).unwrap();
diff --git a/rust/tests/ir/prim/expr.rs b/rust/tests/ir/prim/expr.rs
index 0a300126..9b0f91ae 100644
--- a/rust/tests/ir/prim/expr.rs
+++ b/rust/tests/ir/prim/expr.rs
@@ -23,12 +23,11 @@ use tvm_tirx::ir::prim;
 use tvm_tirx::ir::Expr;
 
 use super::{dtype, float, int, ty, var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 use crate::loc;
 
 #[test]
 fn node_constructors() {
-    load_tirx_compiler();
     let _serial = serial();
     let (x, y) = (var("x", "int32"), var("y", "int32"));
     let (p, q) = (var("p", "bool"), var("q", "bool"));
diff --git a/rust/tests/ir/prim/op.rs b/rust/tests/ir/prim/op.rs
index 3c3b0aa7..c43d14f3 100644
--- a/rust/tests/ir/prim/op.rs
+++ b/rust/tests/ir/prim/op.rs
@@ -24,12 +24,11 @@ use tvm_tirx::ir::{Expr, FloatImm};
 use tvm_tirx::tvm_ffi::ObjectRefCast;
 
 use super::{dtype, float, int, int_value, ty, var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 use crate::loc;
 
 #[test]
 fn arithmetic_operators() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", "int32");
 
@@ -73,7 +72,6 @@ fn arithmetic_operators() {
 
 #[test]
 fn comparison_and_logic_operators() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", "int32");
     let one = int("int32", 1);
@@ -96,7 +94,6 @@ fn comparison_and_logic_operators() {
 
 #[test]
 fn casts_and_conversions() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", "int32");
     assert_eq!(dtype(&op::cast(&x, ty("float16"), loc()).unwrap()), "float16");
@@ -107,7 +104,6 @@ fn casts_and_conversions() {
 
 #[test]
 fn math_operators() {
-    load_tirx_compiler();
     let _serial = serial();
     let f = var("f", "float32");
     for unary in [op::ceil, op::floor, op::round, op::nearbyint, op::trunc] {
diff --git a/rust/tests/ir/prim/vector_expr.rs 
b/rust/tests/ir/prim/vector_expr.rs
index 514098dc..663e0c67 100644
--- a/rust/tests/ir/prim/vector_expr.rs
+++ b/rust/tests/ir/prim/vector_expr.rs
@@ -23,12 +23,11 @@ use tvm_tirx::ir::prim;
 use tvm_tirx::ir::Expr;
 
 use super::{dtype, int, var};
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 use crate::loc;
 
 #[test]
 fn vector_constructors() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", "int32");
     let ramp = prim::Ramp::new(&x, int("int32", 1), int("int32", 4), 
loc()).unwrap();
diff --git a/rust/tests/ir/stmt.rs b/rust/tests/ir/stmt.rs
index 40419334..d4463fe3 100644
--- a/rust/tests/ir/stmt.rs
+++ b/rust/tests/ir/stmt.rs
@@ -23,11 +23,10 @@ use tvm_tirx::ir::*;
 use tvm_tirx::tvm_ffi::{Any, Map, String as FfiString};
 
 use super::{dtype, int32, int32_var, loc, op, seq, tensor_var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn statements() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = int32_var("x");
     let y = int32_var("y");
diff --git a/rust/tests/ir/transform/core.rs b/rust/tests/ir/transform/core.rs
index a7f726fb..d4aa982a 100644
--- a/rust/tests/ir/transform/core.rs
+++ b/rust/tests/ir/transform/core.rs
@@ -23,11 +23,10 @@ use tvm_tirx::ir::transform::*;
 use tvm_tirx::tvm_ffi::{Any, Map, String as FfiString};
 
 use super::{logging_pass, no_instruments, Log};
-use crate::common::{empty_module, load_tirx_compiler, serial};
+use crate::common::{empty_module, serial};
 
 #[test]
 fn pass_context_scope() {
-    load_tirx_compiler();
     let _serial = serial();
     let default_level = PassContext::current().unwrap().opt_level;
     let ctx = PassContext::create(3, ["a"], ["b"], no_instruments(), 
None).unwrap();
@@ -53,7 +52,6 @@ fn pass_context_scope() {
 
 #[test]
 fn module_and_sequential_passes() {
-    load_tirx_compiler();
     let _serial = serial();
     let log = Log::default();
     let first = logging_pass("first", &log);
diff --git a/rust/tests/ir/transform/instrument.rs 
b/rust/tests/ir/transform/instrument.rs
index e613e3ad..986810e9 100644
--- a/rust/tests/ir/transform/instrument.rs
+++ b/rust/tests/ir/transform/instrument.rs
@@ -23,11 +23,10 @@ use tvm_tirx::ir::transform::*;
 use tvm_tirx::ir::IRModule;
 
 use super::{logging_pass, Log};
-use crate::common::{empty_module, load_tirx_compiler, serial};
+use crate::common::{empty_module, serial};
 
 #[test]
 fn instruments() {
-    load_tirx_compiler();
     let _serial = serial();
     let log = Log::default();
     let hook = |log: &Log, entry: &'static str| {
@@ -76,7 +75,6 @@ fn instruments() {
 
 #[test]
 fn pass_timing() {
-    load_tirx_compiler();
     let _serial = serial();
     let log = Log::default();
     let timing = pass_timing_instrument().unwrap();
diff --git a/rust/tests/ir/type.rs b/rust/tests/ir/type.rs
index bb9a10c1..f67da64e 100644
--- a/rust/tests/ir/type.rs
+++ b/rust/tests/ir/type.rs
@@ -22,11 +22,10 @@
 use tvm_tirx::ir::{FuncType, MissingType, NoneType, PrimType, PtrType, 
TensorRegionType, TupleType};
 
 use super::{dtype, loc, scope};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn types() {
-    load_tirx_compiler();
     let _serial = serial();
     MissingType::new().unwrap();
     NoneType::instance().unwrap();
diff --git a/rust/tests/ir/unique_name_supply.rs 
b/rust/tests/ir/unique_name_supply.rs
index 39d53df2..8c23cf0f 100644
--- a/rust/tests/ir/unique_name_supply.rs
+++ b/rust/tests/ir/unique_name_supply.rs
@@ -21,11 +21,10 @@
 
 use tvm_tirx::ir::UniqueNameSupply;
 
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 
 #[test]
 fn unique_names() {
-    load_tirx_compiler();
     let _serial = serial();
     let names = UniqueNameSupply::new("p").unwrap();
     let fresh = names.fresh_name("x", true, true).unwrap();
diff --git a/rust/tests/runtime/main.rs b/rust/tests/runtime/main.rs
index 2c5751a6..b7496e4f 100644
--- a/rust/tests/runtime/main.rs
+++ b/rust/tests/runtime/main.rs
@@ -26,11 +26,8 @@ mod common;
 use tvm_tirx::runtime::{self, MemoryScope};
 use tvm_tirx::tvm_ffi::{DLDataType, DLDataTypeExt, DLDevice, DLDeviceType, 
Module, TypeIndex};
 
-use common::load_tirx_compiler;
-
 #[test]
 fn tensors() {
-    load_tirx_compiler();
     let cpu = DLDevice::new(DLDeviceType::kDLCPU, 0);
     let float32 = DLDataType::try_from_str("float32").unwrap();
     let tensor = runtime::empty(&[2, 8], float32, cpu, None).unwrap();
@@ -44,7 +41,6 @@ fn tensors() {
 
 #[test]
 fn memory_scopes() {
-    load_tirx_compiler();
     let scope = MemoryScope::register("rust_test.scratch").unwrap();
     let again = MemoryScope::register("rust_test.scratch");
     assert!(again.is_err());
@@ -56,14 +52,12 @@ fn memory_scopes() {
 
 #[test]
 fn queries() {
-    load_tirx_compiler();
     assert!(runtime::enabled("cpu").unwrap());
     assert!(runtime::num_threads().unwrap() >= 1);
 }
 
 #[test]
 fn device_attrs() {
-    load_tirx_compiler();
     let cpu = DLDevice::new(DLDeviceType::kDLCPU, 0);
     let exist = runtime::get_device_attr(cpu, "exist").unwrap();
     assert!(bool::try_from(exist).unwrap());
@@ -73,7 +67,6 @@ fn device_attrs() {
 
 #[test]
 fn cuda_queries() {
-    load_tirx_compiler();
     // The CUDA queries are in the CUDA runtime library, which the build may 
have.
     let cuda_runtime = std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
         .join("../build/lib/libtvm_runtime_cuda.so");
diff --git a/rust/tests/script/main.rs b/rust/tests/script/main.rs
index 4ff85d89..44b63f88 100644
--- a/rust/tests/script/main.rs
+++ b/rust/tests/script/main.rs
@@ -25,11 +25,10 @@ mod common;
 
 use tvm_tirx::script;
 
-use common::{empty_module, load_tirx_compiler};
+use common::empty_module;
 
 #[test]
 fn print_module() {
-    load_tirx_compiler();
     let text = script::script(&empty_module()).unwrap();
     assert!(text.as_str().contains("class Module"), "{}", text.as_str());
 }
diff --git a/rust/tests/sym/analyzer.rs b/rust/tests/sym/analyzer.rs
index 03abb2ea..b9974a82 100644
--- a/rust/tests/sym/analyzer.rs
+++ b/rust/tests/sym/analyzer.rs
@@ -24,11 +24,10 @@ use tvm_tirx::sym::*;
 use tvm_tirx::tvm_ffi::Object;
 
 use super::{fixture, int32, op, range, var, Fixture};
-use crate::common::{assert_type_contracts, global, load_tirx_compiler};
+use crate::common::{assert_type_contracts, global};
 
 #[test]
 fn generated_types_match_the_registry() {
-    load_tirx_compiler();
     assert_type_contracts!(true;
         ConstIntBoundObj: Object, ModularSetObj: Object, 
RewriteSimplifierStatsObj: Object,
     );
@@ -86,7 +85,6 @@ fn rust_allocated_bounds_reach_the_native_analyzer() {
 
 #[test]
 fn bounds_and_simplification() {
-    load_tirx_compiler();
     let analyzer = Analyzer::new().unwrap();
     let x = var("x");
     analyzer.bind_range(&x, &range(0, 16), false).unwrap();
@@ -113,7 +111,6 @@ fn bounds_and_simplification() {
 
 #[test]
 fn scopes_end_on_drop() {
-    load_tirx_compiler();
     let analyzer = Analyzer::new().unwrap();
     let x = var("x");
     let x_mod_3 = op("prim._OpFloorMod", &x, int32(3));
diff --git a/rust/tests/sym/int_set.rs b/rust/tests/sym/int_set.rs
index 5c85449e..a8ee5efc 100644
--- a/rust/tests/sym/int_set.rs
+++ b/rust/tests/sym/int_set.rs
@@ -24,11 +24,10 @@ use tvm_tirx::sym::*;
 use tvm_tirx::tvm_ffi::{Map, Object, ObjectRefCast};
 
 use super::{fixture, imm, int32, loc, op, range, var, Fixture};
-use crate::common::{assert_type_contracts, global, load_tirx_compiler};
+use crate::common::{assert_type_contracts, global};
 
 #[test]
 fn generated_types_match_the_registry() {
-    load_tirx_compiler();
     // `sym.IntSet` registers no layout of its own, so its subclasses are 
opaque too.
     assert_type_contracts!(false; IntSetObj: Object, IntervalSetObj: 
IntSetObj);
 }
@@ -56,7 +55,6 @@ fn interval_sets_are_read_through_accessors() {
 
 #[test]
 fn int_sets() {
-    load_tirx_compiler();
     let analyzer = Analyzer::new().unwrap();
     let interval = IntervalSet::new(int32(0), int32(10)).unwrap();
     assert!(analyzer.can_prove_equal(interval.min_value().unwrap(), 
int32(0)).unwrap());
@@ -94,7 +92,6 @@ fn int_sets() {
 
 #[test]
 fn region_bounds() {
-    load_tirx_compiler();
     let x = var("x");
     let var_dom = Map::from_iter([(x.clone(), range(0, 16))]);
     let region = [Range::from_complete_fields(x.clone().into(), int32(1), 
loc())];
diff --git a/rust/tests/sym/iter_affine_map.rs 
b/rust/tests/sym/iter_affine_map.rs
index b92c0bc0..9128bc66 100644
--- a/rust/tests/sym/iter_affine_map.rs
+++ b/rust/tests/sym/iter_affine_map.rs
@@ -24,11 +24,10 @@ use tvm_tirx::sym::*;
 use tvm_tirx::tvm_ffi::{Array, Map, Object, ObjectIdentity, ObjectRefCast};
 
 use super::{fixture, imm, int32, op, range, var, Fixture};
-use crate::common::{assert_type_contracts, global, load_tirx_compiler};
+use crate::common::{assert_type_contracts, global};
 
 #[test]
 fn generated_types_match_the_registry() {
-    load_tirx_compiler();
     assert_type_contracts!(true; IterMapResultObj: Object, IterMarkObj: 
Object);
     // The expression hierarchy registers no layout of its own, so the 
iterator expressions are
     // opaque.
@@ -89,7 +88,6 @@ fn iter_maps_decompose_into_marks_and_splits() {
 
 #[test]
 fn iter_maps() {
-    load_tirx_compiler();
     let analyzer = Analyzer::new().unwrap();
     let (x, y) = (var("x"), var("y"));
     let dom = Map::from_iter([(x.clone(), range(0, 4)), (y.clone(), range(0, 
4))]);
diff --git a/rust/tests/sym/main.rs b/rust/tests/sym/main.rs
index 9cf793a2..a20141d0 100644
--- a/rust/tests/sym/main.rs
+++ b/rust/tests/sym/main.rs
@@ -31,7 +31,7 @@ use tvm_tirx::ir::{Expr, IntImm, Location, PrimType, Range, 
Var};
 use tvm_tirx::sym::Analyzer;
 use tvm_tirx::tvm_ffi::{BigInt, DLDataType, DLDataTypeExt, String as 
FfiString};
 
-use common::{global, load_tirx_compiler};
+use common::global;
 
 fn loc() -> Location {
     global("ir.UnknownLoc").call_packed(&[]).unwrap().try_into().unwrap()
@@ -79,7 +79,6 @@ struct Fixture {
 
 /// An analyzer with `x` bound to `[0, 16)`, through the native functions only.
 fn fixture() -> Fixture {
-    load_tirx_compiler();
     let x = var("x");
     // A typed `Analyzer` handle goes through `call_tuple` like any other 
object.
     let analyzer: Analyzer = 
global("sym.Analyzer").call_tuple(()).unwrap().try_into().unwrap();
diff --git a/rust/tests/target/main.rs b/rust/tests/target/main.rs
index 01f390e2..d1ca39ec 100644
--- a/rust/tests/target/main.rs
+++ b/rust/tests/target/main.rs
@@ -26,7 +26,7 @@ mod common;
 use tvm_tirx::target::{self, Target, TargetKind, TargetKindObj, TargetObj};
 use tvm_tirx::tvm_ffi::{Any, Map, Object, ObjectRefCast, String as FfiString, 
TypeIndex};
 
-use common::{assert_type_contracts, empty_module, global, load_tirx_compiler};
+use common::{assert_type_contracts, empty_module, global};
 
 fn config(kind: &str) -> Map<FfiString, Any> {
     Map::from_iter([(FfiString::from("kind"), 
Any::from(FfiString::from(kind)))])
@@ -39,14 +39,12 @@ fn native_target(name: &str) -> Target {
 
 #[test]
 fn generated_types_match_the_registry() {
-    load_tirx_compiler();
     // Both hold members reflection does not describe, so both bindings are 
opaque.
     assert_type_contracts!(false; TargetObj: Object, TargetKindObj: Object);
 }
 
 #[test]
 fn native_targets_are_read_through_accessors() {
-    load_tirx_compiler();
     let llvm = native_target("llvm");
     let kind: TargetKind = llvm.kind().unwrap();
     assert_eq!(kind.name().unwrap().as_str(), "llvm");
@@ -79,7 +77,6 @@ fn native_targets_are_read_through_accessors() {
 
 #[test]
 fn construction_and_queries() {
-    load_tirx_compiler();
     let llvm = Target::new("llvm").unwrap();
     assert_eq!(llvm.kind().unwrap().name().unwrap().as_str(), "llvm");
     assert_eq!(llvm.get_target_device_type().unwrap(), 1);
@@ -104,7 +101,6 @@ fn construction_and_queries() {
 
 #[test]
 fn scope_ends_on_drop() {
-    load_tirx_compiler();
     assert!(Target::current(true).unwrap().is_none());
     assert!(Target::current(false).is_err());
     {
@@ -117,7 +113,6 @@ fn scope_ends_on_drop() {
 
 #[test]
 fn tags() {
-    load_tirx_compiler();
     let tagged = target::register_tag("rust-test/llvm", &config("llvm"), 
true).unwrap();
     assert_eq!(tagged.kind().unwrap().name().unwrap().as_str(), "llvm");
     let tags = target::list_tags().unwrap();
@@ -127,7 +122,6 @@ fn tags() {
 
 #[test]
 fn build_by_kind() {
-    load_tirx_compiler();
     let c = Target::new("c").unwrap();
     target::build("c", empty_module(), &c).unwrap();
     target::build("llvm", empty_module(), 
Target::new("llvm").unwrap()).unwrap();
diff --git a/rust/tests/tirx/analysis/analysis.rs 
b/rust/tests/tirx/analysis/analysis.rs
index b3c06a27..30ab5dae 100644
--- a/rust/tests/tirx/analysis/analysis.rs
+++ b/rust/tests/tirx/analysis/analysis.rs
@@ -22,12 +22,11 @@
 
 use tvm_tirx::tirx::analysis;
 
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 use crate::{as_i64, int32, module_of, op, prim_type, var, void_function};
 
 #[test]
 fn well_formedness() {
-    load_tirx_compiler();
     let _serial = serial();
     let func = void_function(vec![var("x", prim_type("int32"))]);
     assert!(analysis::verify_memory(&func).unwrap());
@@ -37,7 +36,6 @@ fn well_formedness() {
 
 #[test]
 fn expression_analyses() {
-    load_tirx_compiler();
     let _serial = serial();
     let (x, y) = (var("x", prim_type("int32")), var("y", prim_type("int32")));
     let undefined = analysis::undefined_vars(op("prim._OpAdd", &x, &y), 
[x.clone()]).unwrap();
diff --git a/rust/tests/tirx/function.rs b/rust/tests/tirx/function.rs
index fd753e65..babe1672 100644
--- a/rust/tests/tirx/function.rs
+++ b/rust/tests/tirx/function.rs
@@ -22,11 +22,10 @@
 use tvm_tirx::tirx::renew_def;
 
 use super::{prim_type, var, void_function};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn functions() {
-    load_tirx_compiler();
     let _serial = serial();
     let x = var("x", prim_type("int32"));
     let func = void_function(vec![x.clone()]);
diff --git a/rust/tests/tirx/index_map.rs b/rust/tests/tirx/index_map.rs
index 2b2f8060..7b559da1 100644
--- a/rust/tests/tirx/index_map.rs
+++ b/rust/tests/tirx/index_map.rs
@@ -24,11 +24,10 @@ use tvm_tirx::tirx::IndexMap;
 use tvm_tirx::tvm_ffi::{Array, DLDataType, DLDataTypeExt, DLDevice, 
DLDeviceType};
 
 use super::{as_i64, int32, op, prim_type, range, var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn index_maps() {
-    load_tirx_compiler();
     let _serial = serial();
     let (i, j) = (var("i", prim_type("int32")), var("j", prim_type("int32")));
     let transpose = IndexMap::new(
diff --git a/rust/tests/tirx/layout.rs b/rust/tests/tirx/layout.rs
index bcde3d76..0eb50fcd 100644
--- a/rust/tests/tirx/layout.rs
+++ b/rust/tests/tirx/layout.rs
@@ -24,11 +24,10 @@ use tvm_tirx::tirx::{Axis, ComposeLayout, Iter, Layout, 
TileLayout};
 use tvm_tirx::tvm_ffi::{Map, String as FfiString};
 
 use super::{as_i64, int32, tile_8x8};
-use crate::common::{global, load_tirx_compiler, serial};
+use crate::common::{global, serial};
 
 #[test]
 fn memory_axes() {
-    load_tirx_compiler();
     let _serial = serial();
     let m = Axis::get("m").unwrap();
     assert!(m.is_memory().unwrap() && !m.is_thread().unwrap());
@@ -38,7 +37,6 @@ fn memory_axes() {
 
 #[test]
 fn layouts() {
-    load_tirx_compiler();
     let _serial = serial();
     let tile = tile_8x8();
     let layout = Layout::from(&tile);
@@ -71,7 +69,6 @@ fn layouts() {
 
 #[test]
 fn thread_layout_scope() {
-    load_tirx_compiler();
     let _serial = serial();
     global("tirx.cuda.RegisterLayoutAxes").call_tuple(()).unwrap();
     let laneid = Axis::get("laneid").unwrap();
diff --git a/rust/tests/tirx/op.rs b/rust/tests/tirx/op.rs
index 53d6df5a..7320af47 100644
--- a/rust/tests/tirx/op.rs
+++ b/rust/tests/tirx/op.rs
@@ -22,11 +22,10 @@
 use tvm_tirx::tirx::{gpu_thread_return, logaddexp, reinterpret};
 
 use super::{loc, prim_type, var};
-use crate::common::{load_tirx_compiler, serial, structural_equal};
+use crate::common::{serial, structural_equal};
 
 #[test]
 fn ops() {
-    load_tirx_compiler();
     let _serial = serial();
     let (a, b) = (var("a", prim_type("float32")), var("b", 
prim_type("float32")));
     let sum = logaddexp(&a, &b, loc()).unwrap();
diff --git a/rust/tests/tirx/tensor.rs b/rust/tests/tirx/tensor.rs
index 5830cfd5..80fe481b 100644
--- a/rust/tests/tirx/tensor.rs
+++ b/rust/tests/tirx/tensor.rs
@@ -22,11 +22,10 @@
 use tvm_tirx::tirx::{tensor_is_scalar, Layout, TensorType};
 
 use super::{int32, loc, memory_scope, prim_type, tile_8x8, var};
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 
 #[test]
 fn tensor_types() {
-    load_tirx_compiler();
     let _serial = serial();
     let scope = memory_scope();
     let ty = TensorType::new([int32(1)], prim_type("float32"), &scope, 0, 
None, loc()).unwrap();
diff --git a/rust/tests/tirx/thread_scope.rs b/rust/tests/tirx/thread_scope.rs
index 7121642d..d5746739 100644
--- a/rust/tests/tirx/thread_scope.rs
+++ b/rust/tests/tirx/thread_scope.rs
@@ -21,11 +21,10 @@
 
 use tvm_tirx::tirx::ThreadScope;
 
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 
 #[test]
 fn thread_scopes() {
-    load_tirx_compiler();
     let _serial = serial();
     let warp = ThreadScope::get("cuda.warp").unwrap();
     let cta = ThreadScope::get("cuda.cta").unwrap();
diff --git a/rust/tests/tirx/tile/dispatch.rs b/rust/tests/tirx/tile/dispatch.rs
index 10f9f3a1..63bada3a 100644
--- a/rust/tests/tirx/tile/dispatch.rs
+++ b/rust/tests/tirx/tile/dispatch.rs
@@ -25,12 +25,11 @@ use tvm_tirx::tirx::tile::DispatchContext;
 use tvm_tirx::tirx::{TensorType, ThreadScope};
 use tvm_tirx::tvm_ffi::{Any, Map, ObjectRefCast, String as FfiString};
 
-use crate::common::{global, load_tirx_compiler, serial, structural_equal};
+use crate::common::{global, serial, structural_equal};
 use crate::{int32, loc, memory_scope, prim_type, var};
 
 #[test]
 fn dispatch_contexts() {
-    load_tirx_compiler();
     let _serial = serial();
     let target = Target::new("cuda").unwrap();
     let warp = ThreadScope::get("cuda.warp").unwrap();
diff --git a/rust/tests/tirx/tile/instruction.rs 
b/rust/tests/tirx/tile/instruction.rs
index 1cab4cf8..12ece73c 100644
--- a/rust/tests/tirx/tile/instruction.rs
+++ b/rust/tests/tirx/tile/instruction.rs
@@ -24,12 +24,11 @@ use tvm_tirx::tirx::tile::{as_tensor_region, 
configure_tensor_instruction};
 use tvm_tirx::tirx::TensorType;
 use tvm_tirx::tvm_ffi::Array;
 
-use crate::common::{global, load_tirx_compiler, serial, structural_equal};
+use crate::common::{global, serial, structural_equal};
 use crate::{int32, loc, memory_scope, prim_type, var};
 
 #[test]
 fn tensor_regions() {
-    load_tirx_compiler();
     let _serial = serial();
     let scope = memory_scope();
     let ty = TensorType::new([int32(4)], prim_type("float32"), &scope, 0, 
None, loc()).unwrap();
@@ -45,7 +44,6 @@ fn tensor_regions() {
 
 #[test]
 fn tensor_instructions() {
-    load_tirx_compiler();
     let _serial = serial();
     Op::register("rust.tests.tirx.instruction", "").unwrap();
     let instruction = Op::get("rust.tests.tirx.instruction").unwrap();
diff --git a/rust/tests/tirx/transform/function_pass.rs 
b/rust/tests/tirx/transform/function_pass.rs
index a07b0c86..b3fde2c1 100644
--- a/rust/tests/tirx/transform/function_pass.rs
+++ b/rust/tests/tirx/transform/function_pass.rs
@@ -25,12 +25,11 @@ use std::sync::Arc;
 use tvm_tirx::ir::transform::{Pass, PassInfo};
 use tvm_tirx::tirx::transform::FunctionPass;
 
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 use crate::{module_of, void_function};
 
 #[test]
 fn function_passes() {
-    load_tirx_compiler();
     let _serial = serial();
     let func = void_function(vec![]);
     let runs = Arc::new(AtomicUsize::new(0));
diff --git a/rust/tests/tirx/transform/transform.rs 
b/rust/tests/tirx/transform/transform.rs
index 923bd811..e266a07c 100644
--- a/rust/tests/tirx/transform/transform.rs
+++ b/rust/tests/tirx/transform/transform.rs
@@ -23,12 +23,11 @@ use tvm_tirx::ir::transform::Pass;
 use tvm_tirx::target::Target;
 use tvm_tirx::tirx::transform as tx;
 
-use crate::common::{load_tirx_compiler, serial};
+use crate::common::serial;
 use crate::{module_of, void_function};
 
 #[test]
 fn passes() {
-    load_tirx_compiler();
     let _serial = serial();
     let target = Target::new("llvm").unwrap();
     let passes: Vec<Pass> = vec![
@@ -67,7 +66,6 @@ fn passes() {
 
 #[test]
 fn filter() {
-    load_tirx_compiler();
     let _serial = serial();
     let func = void_function(vec![]);
     let kept = tx::filter(|_func| Ok(true)).unwrap().run(module_of("main", 
&func)).unwrap();
diff --git a/rust/tests/codegen/main.rs b/src/ir/link_anchor.cc
similarity index 52%
copy from rust/tests/codegen/main.rs
copy to src/ir/link_anchor.cc
index aebe9c0d..4ebc7eed 100644
--- a/rust/tests/codegen/main.rs
+++ b/src/ir/link_anchor.cc
@@ -17,23 +17,17 @@
  * under the License.
  */
 
-//! Tests of `tvm_tirx::codegen`, laid out like `src/codegen/`: the tests of 
`src/codegen.rs` are
-//! here, and those of `src/codegen/llvm.rs` in `tests/codegen/llvm.rs`.
-
-#[path = "../common/mod.rs"]
-mod common;
-
-mod llvm;
-
-use tvm_tirx::codegen;
-use tvm_tirx::target::Target;
-
-use common::{empty_module, load_tirx_compiler};
+/*!
+ * \file src/ir/link_anchor.cc
+ * \brief The link anchor of libtvm_tirx_compiler.
+ *
+ * Each TIRx shared library exports a `<library>_link_anchor` function that 
does nothing, defined
+ * in the `link_anchor.cc` of its sources. A language binding that uses the 
library only through
+ * the TVM FFI registry references no other symbol of it, so a linker that 
drops unreferenced
+ * libraries (`--as-needed`) would drop the dependency and the library's 
registrations would
+ * never run; the binding references the anchor (its address is enough) to 
keep the library
+ * linked. There is no header: the binding declares the function itself.
+ */
+#include <tvm/runtime/base.h>
 
-#[test]
-fn build_module() {
-    load_tirx_compiler();
-    let module = empty_module();
-    codegen::build_module(&module, Target::new("llvm").unwrap()).unwrap();
-    codegen::build_module(&module, Target::new("c").unwrap()).unwrap();
-}
+extern "C" TVM_TIRX_DLL int tvm_tirx_compiler_link_anchor() { return 0; }
diff --git a/rust/tests/codegen/main.rs b/src/runtime/link_anchor.cc
similarity index 52%
copy from rust/tests/codegen/main.rs
copy to src/runtime/link_anchor.cc
index aebe9c0d..bd3270bb 100644
--- a/rust/tests/codegen/main.rs
+++ b/src/runtime/link_anchor.cc
@@ -17,23 +17,17 @@
  * under the License.
  */
 
-//! Tests of `tvm_tirx::codegen`, laid out like `src/codegen/`: the tests of 
`src/codegen.rs` are
-//! here, and those of `src/codegen/llvm.rs` in `tests/codegen/llvm.rs`.
-
-#[path = "../common/mod.rs"]
-mod common;
-
-mod llvm;
-
-use tvm_tirx::codegen;
-use tvm_tirx::target::Target;
-
-use common::{empty_module, load_tirx_compiler};
+/*!
+ * \file src/runtime/link_anchor.cc
+ * \brief The link anchor of libtvm_runtime.
+ *
+ * Each TIRx shared library exports a `<library>_link_anchor` function that 
does nothing, defined
+ * in the `link_anchor.cc` of its sources. A language binding that uses the 
library only through
+ * the TVM FFI registry references no other symbol of it, so a linker that 
drops unreferenced
+ * libraries (`--as-needed`) would drop the dependency and the library's 
registrations would
+ * never run; the binding references the anchor (its address is enough) to 
keep the library
+ * linked. There is no header: the binding declares the function itself.
+ */
+#include <tvm/runtime/base.h>
 
-#[test]
-fn build_module() {
-    load_tirx_compiler();
-    let module = empty_module();
-    codegen::build_module(&module, Target::new("llvm").unwrap()).unwrap();
-    codegen::build_module(&module, Target::new("c").unwrap()).unwrap();
-}
+extern "C" TVM_RUNTIME_DLL int tvm_runtime_link_anchor() { return 0; }

Reply via email to