This is an automated email from the ASF dual-hosted git repository.

spectrometerHBH 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 c8d0ae636 [DOCS][RUST] Polish the extension tutorial and example (#176)
c8d0ae636 is described below

commit c8d0ae636c265bb5e03a427b6f2792f41629c172
Author: Linzhang Li <[email protected]>
AuthorDate: Tue Nov 3 13:09:06 2026 -0500

    [DOCS][RUST] Polish the extension tutorial and example (#176)
---
 AGENTS.md                                |  2 +-
 docs/advanced/defining_ir_extensions.rst | 85 +++++++++++++++-----------------
 docs/advanced/extending_ir_in_rust.rst   | 31 ++++++------
 docs/advanced/using_ir_extensions.rst    |  2 +-
 docs/reference/rust/index.rst            |  8 +--
 examples/extension/Cargo.toml            |  3 +-
 examples/extension/src/lib.rs            | 65 +++++++++---------------
 rust/Cargo.toml                          |  2 +-
 rust/src/lib.rs                          |  5 +-
 tests/python/ir/test_op_rust.py          |  2 +-
 10 files changed, 94 insertions(+), 111 deletions(-)

diff --git a/AGENTS.md b/AGENTS.md
index 7e2523ebe..2d0a31d25 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -34,7 +34,7 @@ harnesses on top. The Python package is `tvm_tirx`; the 
distribution is
 include/tvm/        C++ public headers (ir, sym, tirx, target, runtime, 
backends)
 src/                C++ compiler and runtime implementations
 python/tvm_tirx/    Python package and reflected C++ bindings
-rust/               Rust bindings (stubgen)
+rust/               Rust bindings (stubgen) and extension tools
 tests/cpp/          GoogleTest C++ tests
 tests/python/       pytest tests organized by owning namespace and backend
 tests/lint/         ASF header and file-type checks
diff --git a/docs/advanced/defining_ir_extensions.rst 
b/docs/advanced/defining_ir_extensions.rst
index b9f2a4709..ba65df853 100644
--- a/docs/advanced/defining_ir_extensions.rst
+++ b/docs/advanced/defining_ir_extensions.rst
@@ -27,8 +27,9 @@ the validation and traversal helpers they use.
 Defining a Type
 ---------------
 
-``ComplexType`` has one field, ``elem``, the scalar type shared by the real and
-imaginary parts:
+To work with complex numbers directly in the IR, we first introduce a type for
+them. ``ComplexType`` has one field, ``dtype``, the scalar type shared by the
+real and imaginary parts, as in ``TensorType``:
 
 .. literalinclude:: ../../examples/extension/src/lib.rs
    :language: rust
@@ -50,18 +51,14 @@ reference. Its arguments are:
   as the result of type inference.
 - ``ComplexTypeObj``: the object it refers to. A ``ComplexTypeObj`` converts
   into a ``ComplexType`` with ``into()``, and a ``ComplexType`` dereferences to
-  it, so ``ty.elem`` reads the field.
-
-``infer_complex`` in the next section uses both conversions: it builds a
-``ComplexTypeObj``, turns it into a ``ComplexType``, and returns it as a
-``Type``.
+  it, so ``ty.dtype`` reads the field.
 
 The ``static_init_block!`` runs ``register_type`` when the library loads, as a
 ``TVM_FFI_STATIC_INIT_BLOCK`` does for C++, and Python then sees the type as
 ``my_ext.Complex``.
 
 A ``ComplexType`` records the scalar type of the two parts, and a
-``complex(re, im)`` call supplies their values as its operands. ``elem`` can be
+``complex(re, im)`` call supplies their values as its operands. ``dtype`` can 
be
 any scalar float type, such as ``float32``, ``float64`` or ``bfloat16``; this
 tutorial uses ``float32``.
 
@@ -71,7 +68,7 @@ Defining Operators
 To support operations on the new type, we need to define new operators. In our
 case, complex arithmetic needs four: one builds a complex value from two
 scalars, one multiplies complex values, and two read the parts back as scalars
-so that results can be stored in ordinary ``float32`` buffers. Like the type,
+so that results can be stored in ordinary ``float32`` tensors. Like the type,
 they are registered under the ``my_ext`` namespace:
 
 .. list-table::
@@ -94,7 +91,7 @@ they are registered under the ``my_ext`` namespace:
      - Extract the imaginary part
      - ``T.float32``
 
-The constructor is registered as an ``OpDef`` chain in a
+The ``complex`` operator is registered as an ``OpDef`` chain in a
 ``static_init_block!``. Each step updates the operator registry, and the chain
 completes when its statement ends:
 
@@ -113,14 +110,18 @@ checks the arguments and computes the result type:
    :end-before: // end inference
 
 ``set_validator`` repeats these checks for a call whose result type is given
-explicitly rather than inferred. ``Pure`` marks the operator as computing a
-value without writing memory.
-
-``infer_complex`` requires both parts to be scalar floats of the same type, and
-converts neither. The other three operators follow the same pattern. ``cmul``
-requires two complex arguments of the same type and returns it; ``real`` and
-``imag`` require one and return its ``elem``. Passing an ordinary scalar to 
``cmul`` therefore fails
-with ``TypeError: my_ext.cmul expects a complex value``.
+explicitly rather than inferred. ``Pure`` marks the operator as a pure
+function, which does not interact with any external state.
+
+``infer_complex`` computes the type of a ``complex(re, im)`` call: a
+``ComplexType`` whose ``dtype`` is the type of the two parts. It requires both
+parts to be scalar floats of the same type, and converts neither. It then uses
+both conversions of the reference: it builds a ``ComplexTypeObj``, turns it 
into
+a ``ComplexType``, and returns it as a ``Type``. The other three operators
+follow the same pattern. ``cmul`` requires two complex arguments of the same
+type and returns it; ``real`` and ``imag`` require one and return its
+``dtype``. Passing an ordinary scalar to ``cmul`` therefore fails with
+``TypeError: my_ext.cmul expects a complex value``.
 
 Writing a Pass
 --------------
@@ -153,26 +154,18 @@ that pair. This lets the rules apply to the step-by-step 
function from
    R[i] = product_re
    Im[i] = product_im
 
-Only scalar arithmetic remains. The pass looks up the four operators by name,
-as C++ does with ``Op::Get``:
-
-.. literalinclude:: ../../examples/extension/src/lib.rs
-   :language: rust
-   :start-after: // begin operators
-   :end-before: // end operators
-
-The operators are registered when the library loads, and ``Op::get`` returns
-the registry's single object for a name. ``call.op.same_as(&ops.cmul)``
-therefore tests whether a call's ``op`` is ``my_ext.cmul`` by comparing
-pointers. A lookup goes through the registry, so the pass fetches the operators
-once per run and keeps them. ``Lowering`` implements the rewrites with them:
+Only scalar arithmetic remains. The pass looks up the operators by name, as C++
+does with ``Op::Get``. The operators are registered when the library loads, and
+``Op::get`` returns the registry's single object for a name.
+``call.op.same_as(&Op::get("my_ext.cmul")?)`` therefore tests whether a call's
+``op`` is ``my_ext.cmul`` by comparing pointers. ``Lowering`` implements the
+rewrites:
 
 .. literalinclude:: ../../examples/extension/src/lib.rs
    :language: rust
    :start-after: // begin lowering
    :end-before: // end lowering
    :prepend: struct Lowering {
-          ops: ComplexOps,
           bindings: Vec<(Var, Var, Var)>,
       }
       impl Lowering {
@@ -183,15 +176,15 @@ once per run and keeps them. ``Lowering`` implements the 
rewrites with them:
           fn map_sequence(&mut self, seq: SeqStmt) -> Result<Any> { ... }
    :append: }
 
-``ops`` holds the four operators, ``bindings`` records each complex variable
-with its two scalar replacements, and ``parts`` reads the real and imaginary
-parts of an operand, either a ``complex(re, im)`` call or a variable already
-split. ``#[dispatch(map)]`` sends each node to the ``map_`` method of its type,
-and leaves other nodes unchanged: ``map_call`` applies the three rules,
-``map_bind`` splits a complex binding, and ``map_sequence`` flattens the
-sequence that a split binding leaves inside another. The value-only callback
-passes each node to ``lowering.dispatch`` and returns ``Unchanged`` when no
-handler matches; these rewrites do not need a definition-region argument.
+``bindings`` records each complex variable with its two scalar replacements,
+and ``parts`` reads the real and imaginary parts of an operand, either a
+``complex(re, im)`` call or a variable already split. ``#[dispatch(map)]``
+sends each node to the ``map_`` method of its type, and leaves other nodes
+unchanged: ``map_call`` applies the three rules, ``map_bind`` splits a complex
+binding, and ``map_sequence`` flattens the sequence that a split binding leaves
+inside another. The callback passes each node to ``lowering.dispatch`` and
+returns ``Unchanged`` when no handler matches; these rewrites do not need the
+definition region kind.
 
 The pass is built from an ordinary named function:
 
@@ -205,8 +198,7 @@ rewritten function; ``Some`` replaces the original. The 
first ``structural_map``
 walks the function with ``Lowering``. ``PostOrder`` visits children before 
their
 parent, so the operands of ``cmul`` are lowered before ``cmul`` itself. The
 second ``structural_map`` only checks the result. It takes a tuple of three
-functions, and calls each on the nodes of its parameter type; the closure
-passes the operators to ``check_call_lowered``:
+functions, and calls each on the nodes of its parameter type:
 
 .. code-block:: rust
 
@@ -217,14 +209,15 @@ passes the operators to ``check_call_lowered``:
    fn check_expr(expr: Expr) -> Result<Expr> { ... }
 
    /// Reject a call to one of the four complex operators that the rewrite did 
not consume.
-   fn check_call_lowered(ops: &ComplexOps, call: Call) -> Result<Call> { ... }
+   fn check_call_lowered(call: Call) -> Result<Call> { ... }
 
 Each returns its node unchanged and raises a ``ValueError`` on an unsupported
 use, such as a complex function parameter, so the pass reports such a use
 before code generation.
 
-Finally, ``FunctionPass::new`` wraps the function as a pass, and the factory is
-exported so that Python can call it as ``lib.lower_complex()``:
+Finally, ``FunctionPass::new`` wraps the function as a pass, and
+``tvm_ffi_dll_export_typed_func!`` exports ``lower_complex``, which builds it,
+so that the Python package can call it from the loaded library:
 
 .. literalinclude:: ../../examples/extension/src/lib.rs
    :language: rust
diff --git a/docs/advanced/extending_ir_in_rust.rst 
b/docs/advanced/extending_ir_in_rust.rst
index e5d286c48..fe718306a 100644
--- a/docs/advanced/extending_ir_in_rust.rst
+++ b/docs/advanced/extending_ir_in_rust.rst
@@ -48,13 +48,15 @@ directly.
 With agents, compiler development iterates quickly. Along the way, a project 
may
 need new types and operators to describe its own concepts, such as a new number
 format or a hardware feature, and passes that lower them to what the backend
-supports. To support this, TIRx provides Rust bindings, the ``tvm-tirx`` crate,
-which let you define new types, operators, and passes on the same IR objects as
-C++ and Python. An extension is a separate Rust library that builds and loads 
on
-its own, without changing the C++ library, so the C++ core stays stable while
-the extension iterates quickly. Once loaded, its types and operators can be
-written in TVMScript and its passes can run in Python compilation pipelines,
-just like built-in ones.
+supports. To support this, TIRx provides the ``tvm-tirx`` crate, with which you
+define new types, operators, and passes in Rust on the same IR objects as C++
+and Python.
+
+With the ``tvm-tirx`` crate, users can extend TIRx in separate Rust libraries.
+Each extension builds and loads on its own, without changing the C++ library, 
so
+the C++ core stays stable while the extension iterates quickly. Once loaded, 
its
+types and operators can be written in TVMScript and its passes can run in 
Python
+compilation pipelines, just like built-in ones.
 
 Setting Up the Project
 ----------------------
@@ -73,9 +75,10 @@ it is installed and imported like any other package. The 
example package,
    ├── python/my_ext/    # loads the library and registers a compilation 
pipeline
    └── run_example.py    # compiles and runs a function that uses the extension
 
-The Rust library is an ordinary crate that builds a shared library. Its only
-dependency is the `tvm-tirx <https://crates.io/crates/tvm-tirx>`_ crate, which
-re-exports TVM FFI as ``tvm_tirx::tvm_ffi``:
+The Rust library is an ordinary crate that builds a shared library. It depends
+on the `tvm-tirx <https://crates.io/crates/tvm-tirx>`_ crate, which re-exports
+TVM FFI as ``tvm_tirx::tvm_ffi``, and also lists ``tvm-ffi`` itself, because
+the code that ``#[dispatch]`` generates refers to the crate by that name:
 
 .. literalinclude:: ../../examples/extension/Cargo.toml
    :language: toml
@@ -106,7 +109,7 @@ multiplication into real arithmetic:
 
    (a + bi)(c + di) = (ac - bd) + (ad + bc)i.
 
-The following function multiplies two arrays of complex numbers element by
+The following function multiplies two tensors of complex numbers element by
 element. ``A`` and ``B`` hold the real and imaginary parts of the first 
operand,
 ``C`` and ``D`` those of the second:
 
@@ -116,9 +119,9 @@ element. ``A`` and ``B`` hold the real and imaginary parts 
of the first operand,
    :end-before: # end kernel
 
 ``my_ext`` is the extension's Python package, which holds the builders of its
-type and operators; :doc:`using_ir_extensions` shows how it provides them. 
``z``, ``w``, and ``product`` are complex
-values that exist only in the IR.
-The function's inputs and outputs are ordinary ``float32`` arrays, so no
+type and operators; :doc:`using_ir_extensions` shows how it provides them.
+``z``, ``w``, and ``product`` are complex values that exist only in the IR.
+The function's inputs and outputs are ordinary ``float32`` tensors, so no
 runtime storage format for complex numbers is needed.
 
 Next, :doc:`defining_ir_extensions` defines the type, operators, and pass this
diff --git a/docs/advanced/using_ir_extensions.rst 
b/docs/advanced/using_ir_extensions.rst
index 29d3ba284..04e72f180 100644
--- a/docs/advanced/using_ir_extensions.rst
+++ b/docs/advanced/using_ir_extensions.rst
@@ -107,6 +107,6 @@ Next Steps
 Complex addition or conjugation needs one more operator and one more rewrite
 rule in ``LowerComplex``, registered and written the same way. The example
 keeps complex values inside a function: parameters and outputs are ordinary
-arrays, and complex numbers have no runtime storage format or calling
+tensors, and complex numbers have no runtime storage format or calling
 convention. See the :doc:`Rust API reference <../reference/rust/index>` for
 the full Rust API.
diff --git a/docs/reference/rust/index.rst b/docs/reference/rust/index.rst
index 4905526cc..07cfed0f1 100644
--- a/docs/reference/rust/index.rst
+++ b/docs/reference/rust/index.rst
@@ -18,8 +18,9 @@
 Rust API
 ========
 
-The `tvm-tirx <https://crates.io/crates/tvm-tirx>`__ crate binds the TIRx 
compiler
-objects over TVM FFI. Its modules follow the Python package: ``tvm_tirx::ir`` 
is
+The `tvm-tirx <https://crates.io/crates/tvm-tirx>`__ crate provides TIRx in 
Rust:
+the compiler objects over TVM FFI, and the definition of new types, operators, 
and
+passes. Its modules follow the Python package: ``tvm_tirx::ir`` is
 :doc:`tvm_tirx.ir <../python/ir>`, ``tvm_tirx::tirx`` is :doc:`tvm_tirx.tirx
 <../python/tirx>`, and so on. Operators and types can also be defined from 
Rust in
 ``tvm_ffi::static_init_block!`` blocks, which run when their library loads; the
@@ -45,7 +46,8 @@ Crates
      - Description
    * - 📦 `tvm-tirx <generated/tvm_tirx/index.html>`_
      - The IR, TIRx, symbolic analysis, target, backend, runtime, codegen and 
script
-       bindings, and the Rust IR constructors and operators.
+       bindings, the Rust IR constructors and operators, and the definition of 
new
+       types, operators, and passes.
    * - 📦 `tvm-ffi <generated/tvm_ffi/index.html>`_
      - The TVM FFI objects, functions, containers and ``static_init_block!`` 
the
        bindings are built on, re-exported as ``tvm_tirx::tvm_ffi``.
diff --git a/examples/extension/Cargo.toml b/examples/extension/Cargo.toml
index 12ef76e38..a22b57344 100644
--- a/examples/extension/Cargo.toml
+++ b/examples/extension/Cargo.toml
@@ -26,7 +26,8 @@ publish = false
 crate-type = ["cdylib"]
 
 [dependencies]
-# Use this checkout so the Rust API and native libraries share the same FFI 
revision.
+# This example builds against this checkout. A standalone extension depends on
+# the published releases instead, e.g. tvm-tirx = "<version>".
 tvm-tirx = { path = "../../rust" }
 # Procedural macros resolve the FFI crate directly; use the same pinned 
sources.
 tvm-ffi = { path = "../../3rdparty/tvm-ffi/rust/tvm-ffi" }
diff --git a/examples/extension/src/lib.rs b/examples/extension/src/lib.rs
index f630aae1f..778150c0d 100644
--- a/examples/extension/src/lib.rs
+++ b/examples/extension/src/lib.rs
@@ -39,7 +39,7 @@ use tvm_tirx::tvm_ffi::{
 #[rs_class("my_ext.ComplexType", final)]
 pub struct ComplexTypeObj {
     base: TypeObj,
-    pub elem: PrimType,
+    pub dtype: PrimType,
 }
 def_object_ref!(pub ComplexType, Type, ComplexTypeObj);
 
@@ -97,20 +97,20 @@ fn complex_arg(call: &CallObj, index: usize) -> 
Result<ComplexType> {
         .clone()
         .try_cast()
         .map_err(|_| type_error(&format!("{} expects a complex value", 
op.name.as_str())))?;
-    float_part(&ty.elem.clone().into())?;
+    float_part(&ty.dtype.clone().into())?;
     Ok(ty)
 }
 
 fn infer_cmul(call: &CallObj) -> Result<Type> {
     check_call(call, 2)?;
     let (a, b) = (complex_arg(call, 0)?, complex_arg(call, 1)?);
-    require(a.elem.dtype == b.elem.dtype, "my_ext.cmul expects complex values 
of the same type")?;
+    require(a.dtype.dtype == b.dtype.dtype, "my_ext.cmul expects complex 
values of the same type")?;
     Ok(a.into())
 }
 
 fn infer_part(call: &CallObj) -> Result<Type> {
     check_call(call, 1)?;
-    Ok(complex_arg(call, 0)?.elem.clone().into())
+    Ok(complex_arg(call, 0)?.dtype.clone().into())
 }
 
 // Explicit result types bypass inference, so validation checks the inputs and 
result again.
@@ -161,33 +161,17 @@ tvm_ffi::static_init_block! {
         .set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure);
 }
 
-// begin operators
-struct ComplexOps {
-    complex: Op,
-    cmul: Op,
-    real: Op,
-    imag: Op,
-}
-
-impl ComplexOps {
-    fn get() -> Result<Self> {
-        Ok(Self {
-            complex: Op::get("my_ext.complex")?,
-            cmul: Op::get("my_ext.cmul")?,
-            real: Op::get("my_ext.real")?,
-            imag: Op::get("my_ext.imag")?,
-        })
-    }
-
-    /// Whether `op`, such as a call's `op`, is one of the four operators.
-    fn contains(&self, op: &Expr) -> bool {
-        [&self.complex, &self.cmul, &self.real, 
&self.imag].into_iter().any(|own| op.same_as(own))
+/// Whether `op`, such as a call's `op`, is one of the four complex operators.
+fn is_complex_op(op: &Expr) -> Result<bool> {
+    for name in ["my_ext.complex", "my_ext.cmul", "my_ext.real", 
"my_ext.imag"] {
+        if op.same_as(&Op::get(name)?) {
+            return Ok(true);
+        }
     }
+    Ok(false)
 }
-// end operators
 
 struct Lowering {
-    ops: ComplexOps,
     // Each complex local variable is replaced by two scalar local variables.
     bindings: Vec<(Var, Var, Var)>,
 }
@@ -195,7 +179,7 @@ struct Lowering {
 impl Lowering {
     fn parts(&self, expr: &Expr) -> Result<(Expr, Expr)> {
         if let Some(call) = expr.downcast_ref::<CallObj>() {
-            if call.op.same_as(&self.ops.complex) {
+            if call.op.same_as(&Op::get("my_ext.complex")?) {
                 return Ok((call.args.get(0)?, call.args.get(1)?));
             }
         }
@@ -216,11 +200,10 @@ impl Lowering {
 
     // begin lowering
     fn map_call(&mut self, call: Call) -> Result<Any> {
-        let ops = &self.ops;
-        if ops.contains(&call.op) {
+        if is_complex_op(&call.op)? {
             validate_call(&call)?;
         }
-        if call.op.same_as(&ops.cmul) {
+        if call.op.same_as(&Op::get("my_ext.cmul")?) {
             let (a, b) = self.parts(&call.args.get(0)?)?;
             let (c, d) = self.parts(&call.args.get(1)?)?;
             // (a + bi)(c + di) = (ac - bd) + (ad + bc)i
@@ -228,7 +211,7 @@ impl Lowering {
             let im = &a * &d + &b * &c;
             return Ok(Call::new(
                 None,
-                ops.complex.clone(),
+                Op::get("my_ext.complex")?,
                 [re, im],
                 None,
                 Vec::<Type>::new(),
@@ -236,9 +219,10 @@ impl Lowering {
             )?
             .into());
         }
-        if call.op.same_as(&ops.real) || call.op.same_as(&ops.imag) {
+        let real = call.op.same_as(&Op::get("my_ext.real")?);
+        if real || call.op.same_as(&Op::get("my_ext.imag")?) {
             let (re, im) = self.parts(&call.args.get(0)?)?;
-            return Ok(if call.op.same_as(&ops.real) { re.into() } else { 
im.into() });
+            return Ok(if real { re.into() } else { im.into() });
         }
         Ok(call.into())
     }
@@ -247,8 +231,8 @@ impl Lowering {
         if let Some(ty) = bind.var.ty.downcast_ref::<ComplexTypeObj>() {
             let (re, im) = self.parts(&bind.value)?;
             let name = bind.var.name.as_str();
-            let re_var = Var::new(&format!("{name}_re"), ty.elem.clone(), 
bind.var.loc.clone());
-            let im_var = Var::new(&format!("{name}_im"), ty.elem.clone(), 
bind.var.loc.clone());
+            let re_var = Var::new(&format!("{name}_re"), ty.dtype.clone(), 
bind.var.loc.clone());
+            let im_var = Var::new(&format!("{name}_im"), ty.dtype.clone(), 
bind.var.loc.clone());
             self.bindings.push((bind.var.clone(), re_var.clone(), 
im_var.clone()));
             let binds = [
                 Bind::new(re_var, re, bind.loc.clone())?,
@@ -280,8 +264,8 @@ fn check_expr(expr: Expr) -> Result<Expr> {
 }
 
 /// Reject a call to one of the four complex operators that the rewrite did 
not consume.
-fn check_call_lowered(ops: &ComplexOps, call: Call) -> Result<Call> {
-    if ops.contains(&call.op) {
+fn check_call_lowered(call: Call) -> Result<Call> {
+    if is_complex_op(&call.op)? {
         return Err(Error::new(
             tvm_ffi::VALUE_ERROR,
             "LowerComplex left an unsupported complex call",
@@ -297,16 +281,15 @@ fn lower_function(
     _module: IRModule,
     _ctx: PassContext,
 ) -> Result<Option<Function>> {
-    let mut lowering = Lowering { ops: ComplexOps::get()?, bindings: 
Vec::new() };
+    let mut lowering = Lowering { bindings: Vec::new() };
     let rewritten = tvm_ffi::structural_map(
         func,
         |value: AnyView<'_>| lowering.dispatch(value).unwrap_or_else(|| 
Ok(Unchanged.into())),
         WalkOrder::PostOrder,
     )?;
-    let ops = &lowering.ops;
     let checked = tvm_ffi::structural_map(
         rewritten,
-        (check_type, |call| check_call_lowered(ops, call), check_expr),
+        (check_type, check_call_lowered, check_expr),
         WalkOrder::PostOrder,
     )?;
     let function: Function = checked.try_into()?;
diff --git a/rust/Cargo.toml b/rust/Cargo.toml
index 42702a814..c2722e2fa 100644
--- a/rust/Cargo.toml
+++ b/rust/Cargo.toml
@@ -21,7 +21,7 @@ version = "0.1.0-alpha.5"
 edition = "2021"
 # The oldest toolchain that builds the crate and tvm-ffi; CMake checks cargo 
against it.
 rust-version = "1.84"
-description = "Rust bindings for the TVM TIRx compiler"
+description = "Rust API for the TVM TIRx compiler: IR bindings and IR 
extensions"
 license = "Apache-2.0"
 homepage = "https://tvm.apache.org/tirx/";
 documentation = "https://tvm.apache.org/tirx/reference/rust/";
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index ecd457a24..c31d497cf 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -17,9 +17,10 @@
  * under the License.
  */
 
-//! Rust bindings for the TIRx compiler objects, over TVM FFI.
+//! The TIRx compiler in Rust: its IR objects over TVM FFI, and the definition 
of new types,
+//! operators and passes that extend it.
 //!
-//! The bindings are generated by `tvm-ffi-stubgen --target rust` from the 
reflection metadata of
+//! The IR bindings are generated by `tvm-ffi-stubgen --target rust` from the 
reflection metadata of
 //! `libtvm_tirx_compiler`. The modules follow the Python package `tvm_tirx`:
 //!
 //! - [`ir`]: the shared IR (expressions, statements, types, functions, 
modules and operators),
diff --git a/tests/python/ir/test_op_rust.py b/tests/python/ir/test_op_rust.py
index 00c692fa1..a8a54f970 100644
--- a/tests/python/ir/test_op_rust.py
+++ b/tests/python/ir/test_op_rust.py
@@ -177,7 +177,7 @@ EXTENSION_CHECKS = """
     a_var, b_var = ir.Var("a", "float32"), ir.Var("b", "float32")
     z = my_ext.complex(a_var, b_var)
     assert isinstance(z.ty, my_ext.Complex)
-    assert tvm_ffi.structural_equal(z.ty.elem, ir.PrimType("float32"))
+    assert tvm_ffi.structural_equal(z.ty.dtype, ir.PrimType("float32"))
     assert tvm_ffi.structural_equal(ir.load_json(ir.save_json(z.ty)), z.ty)
     text = complex_product.script()
     parsed = tvm_tirx.script.parse(text, extra_vars={"T": T, "my_ext": my_ext})

Reply via email to