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 085fc0b8c [Rust] Reorder items so each file reads top-down (#215)
085fc0b8c is described below

commit 085fc0b8cd22386a6192809e64ddf1cb545b9e6d
Author: Linzhang Li <[email protected]>
AuthorDate: Fri Nov 6 17:00:18 2026 -0500

    [Rust] Reorder items so each file reads top-down (#215)
    
    Reorder items within each file, as apache/tvm-ffi#946 does for tvm-ffi:
    data structures first, each type with its impls, then the public API,
    then helpers. Moves only, plus three section headers for the moved
    helpers; review with `--color-moved`.
    
    - `ir/prim/op.rs`: in each section the public functions come first, then
    the helpers they call, in call order. `ExprResult` is followed by its
    inherent impl, its conversions and the `operators` module, then `binary`
    and `binary_literal`. `make_const_scalar`, `fold_cast` and its helpers,
    the `fold_*` comparisons, `symbolic_infinities`, `fold_conditions`,
    `fold_shift`, the math helpers and `unary_intrinsic` move below the
    operators that use them.
    - `ir/prim/expr.rs`: the operand checks (`Operands`, `binary_type`,
    `bool_type`, `logical_type`) move from the top to the end, under their
    section; `binary_new!` moves to the top, ahead of its uses.
    - `ir/expr.rs`: `impl Expr` follows the generated `Expr`, before
    `Subscript`; `check_float_imm` and `check_int_imm` move to the end,
    under "What the constant constructors check".
    - `ir/stmt.rs`: the statement types come first; `int_imm_value_fits`,
    `region_body_params`, `object_address`, `flatten`, `is_no_op` and
    `into_body` move to a Helpers section at the end.
    - `ir/op_def.rs`: `OpDef`, `AttrKey`, `op_attr`, `CallEffectKind` and
    `sig` come first; `complete`, `doc_text`, the hook types and the ABI
    trampolines move to a Helpers section at the end.
    - `sym/constraint.rs`: as a generated binding, `ConstraintContextObj`
    and its layout assertions, then `ConstraintContext`, then their impls.
    - `lib.rs`: the modules, `pub use tvm_ffi` and `exports` lead; the link
    anchor and the scope guard follow.
    - `ir/prim/const_fold.rs`: the arithmetic and comparison folds come
    before the helpers they share.
    - `ir/support.rs`: `not_a_prim_expr` moves to the end of "Primitive
    types", below `prim_type` and `prim_dtype`, which use it.
    - `tirx/tensor.rs`: `impl TensorType` follows the generated
    `TensorType`, then `TensorVar` and its `sig::ArgType` impl, then
    `tensor_is_scalar`; the private `alloc_alignment` moves to the end.
    - `ir/dtype.rs`, `tirx/op.rs`: the private helpers (`scalar`;
    `is_scalar_int64` and `storage_bytes`) move below the public functions
    that use them.
---
 rust/src/ir/dtype.rs           |  10 +-
 rust/src/ir/expr.rs            | 361 +++++++++--------
 rust/src/ir/op_def.rs          | 179 ++++----
 rust/src/ir/prim/const_fold.rs | 260 ++++++------
 rust/src/ir/prim/expr.rs       | 138 +++----
 rust/src/ir/prim/op.rs         | 898 ++++++++++++++++++++---------------------
 rust/src/ir/stmt.rs            | 185 ++++-----
 rust/src/ir/support.rs         |  18 +-
 rust/src/lib.rs                |  42 +-
 rust/src/sym/constraint.rs     |  98 ++---
 rust/src/tirx/op.rs            |  24 +-
 rust/src/tirx/tensor.rs        |  58 +--
 12 files changed, 1140 insertions(+), 1131 deletions(-)

diff --git a/rust/src/ir/dtype.rs b/rust/src/ir/dtype.rs
index 2d8e28ecc..21b04361e 100644
--- a/rust/src/ir/dtype.rs
+++ b/rust/src/ir/dtype.rs
@@ -42,11 +42,6 @@
 use tvm_ffi::DLDataType;
 use tvm_ffi::DLDataTypeCode;
 
-/// The scalar type of `code` and `bits`.
-const fn scalar(code: DLDataTypeCode, bits: u8) -> DLDataType {
-    DLDataType { code: code as u8, bits, lanes: 1 }
-}
-
 /// Return the signed integer type of `bits` bits, such as `int32` for 32 (C++
 /// `DataType::Int(bits)`).
 pub const fn int(bits: u8) -> DLDataType {
@@ -85,3 +80,8 @@ pub const fn void() -> DLDataType {
 pub const fn with_lanes(dtype: DLDataType, lanes: u16) -> DLDataType {
     DLDataType { code: dtype.code, bits: dtype.bits, lanes }
 }
+
+/// The scalar type of `code` and `bits`.
+const fn scalar(code: DLDataTypeCode, bits: u8) -> DLDataType {
+    DLDataType { code: code as u8, bits, lanes: 1 }
+}
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index 4ea215a3b..4063c7a96 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -202,6 +202,28 @@ unsafe impl UnsafeInit for ExprObj {
 unsafe impl tvm_ffi::object::ImmutableObject for ExprObj {}
 // tvm-ffi-stubgen(end)
 
+impl Expr {
+    /// Return `self[indices]`, as the subscript rule of its type realizes it: 
a [`TupleGetItem`] of
+    /// a tuple, a [`TensorLoad`] or [`TensorRegion`] of a tensor 
(`ir.SubscriptExprRealize`).
+    ///
+    /// # Errors
+    ///
+    /// Returns a `TypeError` if the type of the expression has no subscript 
rule, or the error the
+    /// rule raises.
+    pub fn subscript(
+        &self,
+        indices: impl IntoIterator<Item = Subscript>,
+        loc: impl Into<Location>,
+    ) -> Result<Any> {
+        let indices = indices.into_iter().map(|index| 
Any::from(&index)).collect::<Array<Any>>();
+        tvm_ffi::cached_global_func!("ir.SubscriptExprRealize").call_tuple((
+            self,
+            &indices,
+            &loc.into(),
+        ))
+    }
+}
+
 /// An index of [`Expr::subscript`]: a primitive expression, or a 
`start:stop:step` slice (C++
 /// `SubscriptSlice`).
 #[derive(Clone)]
@@ -226,28 +248,6 @@ impl From<&Subscript> for Any {
     }
 }
 
-impl Expr {
-    /// Return `self[indices]`, as the subscript rule of its type realizes it: 
a [`TupleGetItem`] of
-    /// a tuple, a [`TensorLoad`] or [`TensorRegion`] of a tensor 
(`ir.SubscriptExprRealize`).
-    ///
-    /// # Errors
-    ///
-    /// Returns a `TypeError` if the type of the expression has no subscript 
rule, or the error the
-    /// rule raises.
-    pub fn subscript(
-        &self,
-        indices: impl IntoIterator<Item = Subscript>,
-        loc: impl Into<Location>,
-    ) -> Result<Any> {
-        let indices = indices.into_iter().map(|index| 
Any::from(&index)).collect::<Array<Any>>();
-        tvm_ffi::cached_global_func!("ir.SubscriptExprRealize").call_tuple((
-            self,
-            &indices,
-            &loc.into(),
-        ))
-    }
-}
-
 // 
---------------------------------------------------------------------------------------------
 // Conversions into an expression
 
@@ -1260,91 +1260,6 @@ impl From<f64> for FloatImm {
     }
 }
 
-/// The checks of C++ `FloatImm(ty, value)`: a scalar floating-point type 
whose range holds a
-/// finite `value`.
-fn check_float_imm(dtype: DLDataType, value: f64) -> Result<()> {
-    use support::{check_failed, compare_failed, CppDouble};
-    use tvm_ffi::VALUE_ERROR;
-    const FLOAT4_E2M1FN: u8 = tvm_ffi::DLDataTypeCode::kDLFloat4_e2m1fn as u8;
-    /// `ffi::DLExtDataTypeCode::kDLExtCustomBegin`.
-    const CUSTOM_BEGIN: u8 = 129;
-    if support::is_scalable_vector(dtype) || 
support::is_fixed_length_vector(dtype) {
-        let condition = "!value_ty.IsScalableVector() && 
!value_ty.IsFixedLengthVector()";
-        return Err(check_failed(VALUE_ERROR, condition, "FloatImm can only 
take scalar."));
-    }
-    let (code, bits) = (dtype.code, dtype.bits);
-    let float8 = support::float8_bound(code);
-    let float6 = support::float6_bound(code);
-    let supported = code == support::FLOAT
-        || float8.is_some()
-        || float6.is_some()
-        || (code == support::BFLOAT && bits == 16)
-        || (code == FLOAT4_E2M1FN && bits == 4)
-        || code >= CUSTOM_BEGIN;
-    let name = || support::dtype_name(dtype);
-    if !supported {
-        let condition = "value_ty.MatchesCode(DLDataTypeCode::kDLFloat, \
-                         DLDataTypeCode::kDLFloat8_e3m4, 
DLDataTypeCode::kDLFloat8_e4m3, \
-                         DLDataTypeCode::kDLFloat8_e4m3b11fnuz, 
DLDataTypeCode::kDLFloat8_e4m3fn, \
-                         DLDataTypeCode::kDLFloat8_e4m3fnuz, 
DLDataTypeCode::kDLFloat8_e5m2, \
-                         DLDataTypeCode::kDLFloat8_e5m2fnuz, 
DLDataTypeCode::kDLFloat8_e8m0fnu, \
-                         DLDataTypeCode::kDLFloat6_e2m3fn, 
DLDataTypeCode::kDLFloat6_e3m2fn) || \
-                         
value_ty.MatchesElementType(DLDataTypeCode::kDLBfloat, 16) || \
-                         
value_ty.MatchesElementType(DLDataTypeCode::kDLFloat4_e2m1fn, 4) || \
-                         static_cast<int>(code) >= \
-                         
static_cast<int>(ffi::DLExtDataTypeCode::kDLExtCustomBegin)";
-        let message = format!("FloatImm supports only float, but {} was 
supplied.", name());
-        return Err(check_failed(VALUE_ERROR, condition, &message));
-    }
-    if value.is_infinite() || value.is_nan() {
-        return Ok(());
-    }
-    // The bound checked from below and how its message says so, and the bound 
from above.
-    let (low, high): ((&str, f64, &str), (&str, f64)) = if bits == 32 {
-        (
-            (
-                "value >= std::numeric_limits<float>::lowest()",
-                f32::MIN as f64,
-                "exceeds minimum of",
-            ),
-            ("value <= std::numeric_limits<float>::max()", f32::MAX as f64),
-        )
-    } else if code == support::FLOAT && bits == 16 {
-        (
-            ("value >= -support::kMaxFloat16", -support::MAX_FLOAT16, "exceeds 
minimum of"),
-            ("value <= support::kMaxFloat16", support::MAX_FLOAT16),
-        )
-    } else if code == support::BFLOAT && bits == 16 {
-        (
-            ("value >= -support::kMaxBFloat16", -support::MAX_BFLOAT16, 
"exceeds minimum of"),
-            ("value <= support::kMaxBFloat16", support::MAX_BFLOAT16),
-        )
-    } else if let Some((bound, nonneg)) = float8 {
-        let low = match nonneg {
-            true => ("value >= 0", 0.0, "below zero for unsigned"),
-            false => ("value >= -bound", -bound, "below minimum of"),
-        };
-        (low, ("value <= bound", bound))
-    } else if let Some(bound) = float6 {
-        (("value >= -bound", -bound, "below minimum of"), ("value <= bound", 
bound))
-    } else if code == FLOAT4_E2M1FN {
-        (("value >= -bound", -6.0, "below minimum of"), ("value <= bound", 
6.0))
-    } else {
-        return Ok(());
-    };
-    let failed = |condition: &str, bound: f64, what: &str| {
-        let message = format!("Literal value {} {what} {}", CppDouble(value), 
name());
-        compare_failed(VALUE_ERROR, condition, CppDouble(value), 
CppDouble(bound), &message)
-    };
-    if value < low.1 {
-        return Err(failed(low.0, low.1, low.2));
-    }
-    if value > high.1 {
-        return Err(failed(high.0, high.1, "exceeds maximum of"));
-    }
-    Ok(())
-}
-
 // tvm-ffi-stubgen(begin): object/ir.IntImm
 /// Complete: reflected fields fill [40, 56) exactly.
 #[repr(C)]
@@ -1467,78 +1382,6 @@ impl From<bool> for IntImm {
     }
 }
 
-/// The checks of C++ `IntImm(ty, value)`: a scalar integer or boolean type 
whose range holds
-/// `value`. The messages are built only when a check fails.
-fn check_int_imm(dtype: DLDataType, value: &BigInt) -> Result<()> {
-    use support::{check_failed, compare_failed};
-    use tvm_ffi::VALUE_ERROR;
-    let name = || support::dtype_name(dtype);
-    if support::is_scalable_vector(dtype) || 
support::is_fixed_length_vector(dtype) {
-        let condition = "!value_ty.IsScalableVector() && 
!value_ty.IsFixedLengthVector()";
-        let message = format!("IntImm can only take scalar, but {} was 
supplied.", name());
-        return Err(check_failed(VALUE_ERROR, condition, &message));
-    }
-    if !matches!(dtype.code, support::INT | support::UINT | support::BOOL) {
-        let condition = "value_ty.MatchesCode(DLDataTypeCode::kDLInt, 
DLDataTypeCode::kDLUInt, \
-                         DLDataTypeCode::kDLBool)";
-        let message =
-            format!("IntImm supports only int or uint or bool type, but {} was 
supplied.", name());
-        return Err(check_failed(VALUE_ERROR, condition, &message));
-    }
-    let bits = dtype.bits as u32;
-    if bits == 0 {
-        let message = "IntImm requires a positive integer width";
-        return Err(compare_failed(VALUE_ERROR, "bits > 0", bits, 0, message));
-    }
-    let zero = BigInt::from(0i64);
-    if dtype.code == support::UINT {
-        if *value < zero {
-            let message =
-                format!("Literal value {value} is negative for unsigned 
integer type {}", name());
-            return Err(compare_failed(VALUE_ERROR, "value >= 0", value, 0, 
&message));
-        }
-        let message = || format!("Literal value {value} exceeds maximum of 
{}", name());
-        if bits <= 64 {
-            if !value.to_u64().is_some_and(|small| bits == 64 || small < 1u64 
<< bits) {
-                let condition =
-                    "small.has_value() && (bits == 64 || *small < (uint64_t{1} 
<< bits))";
-                return Err(check_failed(VALUE_ERROR, condition, &message()));
-            }
-        } else {
-            let limit = BigInt::from(1i64).try_shl(bits as i64)?;
-            if *value >= limit {
-                let condition = "value < ffi::BigInt(1) << bits";
-                return Err(compare_failed(VALUE_ERROR, condition, value, 
&limit, &message()));
-            }
-        }
-    } else if bits == 1 || dtype.code == support::BOOL {
-        if *value != zero && *value != BigInt::from(1i64) {
-            let message = format!("{value} exceeds range of {}", name());
-            return Err(check_failed(VALUE_ERROR, "value == 0 || value == 1", 
&message));
-        }
-    } else {
-        let message = || format!("Literal value {value} exceeds range of {}", 
name());
-        if bits <= 64 {
-            let half = 1i128 << (bits - 1);
-            let fits = value.to_i64().is_some_and(|small| {
-                bits == 64 || ((small as i128) >= -half && (small as i128) < 
half)
-            });
-            if !fits {
-                let condition = "small.has_value() && (bits == 64 || (*small 
>= -(int64_t{1} << \
-                                 (bits - 1)) && *small < (int64_t{1} << (bits 
- 1))))";
-                return Err(check_failed(VALUE_ERROR, condition, &message()));
-            }
-        } else {
-            let limit = BigInt::from(1i64).try_shl(bits as i64 - 1)?;
-            if *value < -limit.clone() || *value >= limit {
-                let condition = "value >= -limit && value < limit";
-                return Err(check_failed(VALUE_ERROR, condition, &message()));
-            }
-        }
-    }
-    Ok(())
-}
-
 // tvm-ffi-stubgen(begin): object/ir.GenericConst
 /// Complete: reflected fields fill [40, 56) exactly.
 #[repr(C)]
@@ -2314,3 +2157,163 @@ 
crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterSplitExpr);
 crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterSumExpr);
 crate::def_expr_op_over_loads!(crate::sym::analyzer::SplitExpr);
 crate::def_expr_op_over_loads!(crate::sym::analyzer::SumExpr);
+
+// 
---------------------------------------------------------------------------------------------
+// What the constant constructors check
+
+/// The checks of C++ `FloatImm(ty, value)`: a scalar floating-point type 
whose range holds a
+/// finite `value`.
+fn check_float_imm(dtype: DLDataType, value: f64) -> Result<()> {
+    use support::{check_failed, compare_failed, CppDouble};
+    use tvm_ffi::VALUE_ERROR;
+    const FLOAT4_E2M1FN: u8 = tvm_ffi::DLDataTypeCode::kDLFloat4_e2m1fn as u8;
+    /// `ffi::DLExtDataTypeCode::kDLExtCustomBegin`.
+    const CUSTOM_BEGIN: u8 = 129;
+    if support::is_scalable_vector(dtype) || 
support::is_fixed_length_vector(dtype) {
+        let condition = "!value_ty.IsScalableVector() && 
!value_ty.IsFixedLengthVector()";
+        return Err(check_failed(VALUE_ERROR, condition, "FloatImm can only 
take scalar."));
+    }
+    let (code, bits) = (dtype.code, dtype.bits);
+    let float8 = support::float8_bound(code);
+    let float6 = support::float6_bound(code);
+    let supported = code == support::FLOAT
+        || float8.is_some()
+        || float6.is_some()
+        || (code == support::BFLOAT && bits == 16)
+        || (code == FLOAT4_E2M1FN && bits == 4)
+        || code >= CUSTOM_BEGIN;
+    let name = || support::dtype_name(dtype);
+    if !supported {
+        let condition = "value_ty.MatchesCode(DLDataTypeCode::kDLFloat, \
+                         DLDataTypeCode::kDLFloat8_e3m4, 
DLDataTypeCode::kDLFloat8_e4m3, \
+                         DLDataTypeCode::kDLFloat8_e4m3b11fnuz, 
DLDataTypeCode::kDLFloat8_e4m3fn, \
+                         DLDataTypeCode::kDLFloat8_e4m3fnuz, 
DLDataTypeCode::kDLFloat8_e5m2, \
+                         DLDataTypeCode::kDLFloat8_e5m2fnuz, 
DLDataTypeCode::kDLFloat8_e8m0fnu, \
+                         DLDataTypeCode::kDLFloat6_e2m3fn, 
DLDataTypeCode::kDLFloat6_e3m2fn) || \
+                         
value_ty.MatchesElementType(DLDataTypeCode::kDLBfloat, 16) || \
+                         
value_ty.MatchesElementType(DLDataTypeCode::kDLFloat4_e2m1fn, 4) || \
+                         static_cast<int>(code) >= \
+                         
static_cast<int>(ffi::DLExtDataTypeCode::kDLExtCustomBegin)";
+        let message = format!("FloatImm supports only float, but {} was 
supplied.", name());
+        return Err(check_failed(VALUE_ERROR, condition, &message));
+    }
+    if value.is_infinite() || value.is_nan() {
+        return Ok(());
+    }
+    // The bound checked from below and how its message says so, and the bound 
from above.
+    let (low, high): ((&str, f64, &str), (&str, f64)) = if bits == 32 {
+        (
+            (
+                "value >= std::numeric_limits<float>::lowest()",
+                f32::MIN as f64,
+                "exceeds minimum of",
+            ),
+            ("value <= std::numeric_limits<float>::max()", f32::MAX as f64),
+        )
+    } else if code == support::FLOAT && bits == 16 {
+        (
+            ("value >= -support::kMaxFloat16", -support::MAX_FLOAT16, "exceeds 
minimum of"),
+            ("value <= support::kMaxFloat16", support::MAX_FLOAT16),
+        )
+    } else if code == support::BFLOAT && bits == 16 {
+        (
+            ("value >= -support::kMaxBFloat16", -support::MAX_BFLOAT16, 
"exceeds minimum of"),
+            ("value <= support::kMaxBFloat16", support::MAX_BFLOAT16),
+        )
+    } else if let Some((bound, nonneg)) = float8 {
+        let low = match nonneg {
+            true => ("value >= 0", 0.0, "below zero for unsigned"),
+            false => ("value >= -bound", -bound, "below minimum of"),
+        };
+        (low, ("value <= bound", bound))
+    } else if let Some(bound) = float6 {
+        (("value >= -bound", -bound, "below minimum of"), ("value <= bound", 
bound))
+    } else if code == FLOAT4_E2M1FN {
+        (("value >= -bound", -6.0, "below minimum of"), ("value <= bound", 
6.0))
+    } else {
+        return Ok(());
+    };
+    let failed = |condition: &str, bound: f64, what: &str| {
+        let message = format!("Literal value {} {what} {}", CppDouble(value), 
name());
+        compare_failed(VALUE_ERROR, condition, CppDouble(value), 
CppDouble(bound), &message)
+    };
+    if value < low.1 {
+        return Err(failed(low.0, low.1, low.2));
+    }
+    if value > high.1 {
+        return Err(failed(high.0, high.1, "exceeds maximum of"));
+    }
+    Ok(())
+}
+
+/// The checks of C++ `IntImm(ty, value)`: a scalar integer or boolean type 
whose range holds
+/// `value`. The messages are built only when a check fails.
+fn check_int_imm(dtype: DLDataType, value: &BigInt) -> Result<()> {
+    use support::{check_failed, compare_failed};
+    use tvm_ffi::VALUE_ERROR;
+    let name = || support::dtype_name(dtype);
+    if support::is_scalable_vector(dtype) || 
support::is_fixed_length_vector(dtype) {
+        let condition = "!value_ty.IsScalableVector() && 
!value_ty.IsFixedLengthVector()";
+        let message = format!("IntImm can only take scalar, but {} was 
supplied.", name());
+        return Err(check_failed(VALUE_ERROR, condition, &message));
+    }
+    if !matches!(dtype.code, support::INT | support::UINT | support::BOOL) {
+        let condition = "value_ty.MatchesCode(DLDataTypeCode::kDLInt, 
DLDataTypeCode::kDLUInt, \
+                         DLDataTypeCode::kDLBool)";
+        let message =
+            format!("IntImm supports only int or uint or bool type, but {} was 
supplied.", name());
+        return Err(check_failed(VALUE_ERROR, condition, &message));
+    }
+    let bits = dtype.bits as u32;
+    if bits == 0 {
+        let message = "IntImm requires a positive integer width";
+        return Err(compare_failed(VALUE_ERROR, "bits > 0", bits, 0, message));
+    }
+    let zero = BigInt::from(0i64);
+    if dtype.code == support::UINT {
+        if *value < zero {
+            let message =
+                format!("Literal value {value} is negative for unsigned 
integer type {}", name());
+            return Err(compare_failed(VALUE_ERROR, "value >= 0", value, 0, 
&message));
+        }
+        let message = || format!("Literal value {value} exceeds maximum of 
{}", name());
+        if bits <= 64 {
+            if !value.to_u64().is_some_and(|small| bits == 64 || small < 1u64 
<< bits) {
+                let condition =
+                    "small.has_value() && (bits == 64 || *small < (uint64_t{1} 
<< bits))";
+                return Err(check_failed(VALUE_ERROR, condition, &message()));
+            }
+        } else {
+            let limit = BigInt::from(1i64).try_shl(bits as i64)?;
+            if *value >= limit {
+                let condition = "value < ffi::BigInt(1) << bits";
+                return Err(compare_failed(VALUE_ERROR, condition, value, 
&limit, &message()));
+            }
+        }
+    } else if bits == 1 || dtype.code == support::BOOL {
+        if *value != zero && *value != BigInt::from(1i64) {
+            let message = format!("{value} exceeds range of {}", name());
+            return Err(check_failed(VALUE_ERROR, "value == 0 || value == 1", 
&message));
+        }
+    } else {
+        let message = || format!("Literal value {value} exceeds range of {}", 
name());
+        if bits <= 64 {
+            let half = 1i128 << (bits - 1);
+            let fits = value.to_i64().is_some_and(|small| {
+                bits == 64 || ((small as i128) >= -half && (small as i128) < 
half)
+            });
+            if !fits {
+                let condition = "small.has_value() && (bits == 64 || (*small 
>= -(int64_t{1} << \
+                                 (bits - 1)) && *small < (int64_t{1} << (bits 
- 1))))";
+                return Err(check_failed(VALUE_ERROR, condition, &message()));
+            }
+        } else {
+            let limit = BigInt::from(1i64).try_shl(bits as i64 - 1)?;
+            if *value < -limit.clone() || *value >= limit {
+                let condition = "value >= -limit && value < limit";
+                return Err(check_failed(VALUE_ERROR, condition, &message()));
+            }
+        }
+    }
+    Ok(())
+}
diff --git a/rust/src/ir/op_def.rs b/rust/src/ir/op_def.rs
index 6fb4c8366..355b3a647 100644
--- a/rust/src/ir/op_def.rs
+++ b/rust/src/ir/op_def.rs
@@ -267,17 +267,6 @@ impl Drop for OpDef {
     }
 }
 
-/// Request the APIs of a defined operator in registered destinations through 
`ir.expose_op`,
-/// after its signature and attributes are ready. An absent receiver is a 
no-op; an installed
-/// receiver's error is returned.
-fn complete(state: Result<Op>) -> Result<Op> {
-    let op = state?;
-    if let Ok(expose) = tvm_ffi::Function::get_global("ir.expose_op") {
-        expose.call_tuple((op.name.clone(),))?;
-    }
-    Ok(op)
-}
-
 /// The key of an operator attribute, C++ `ffi::reflection::AttrKey<T>`: its 
name and the type `T`
 /// of its values.
 ///
@@ -356,83 +345,6 @@ impl From<CallEffectKind> for Any {
     }
 }
 
-/// 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 {
-    let indent = doc
-        .lines()
-        .filter(|line| !line.trim().is_empty())
-        .map(|line| line.len() - line.trim_start().len())
-        .min()
-        .unwrap_or(0);
-    let lines: Vec<&str> =
-        doc.lines().map(|line| 
line.get(indent..).unwrap_or("").trim_end()).collect();
-    lines.join("\n").trim().to_string()
-}
-
-/// 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;
-
-/// The C ABI of a native hook on a region statement, 
`NativeFunctionView<R(const
-/// RegionStmtNode*)>`.
-type RegionHook = extern "C" fn(*const RegionStmtObj) -> TVMFFIAny;
-
-extern "C" fn validate_abi<F: Fn(&CallObj) -> Result<()>>(call: *const 
CallObj) -> TVMFFIAny {
-    call_hook(call, zero_sized::<F>())
-}
-
-extern "C" fn infer_type_abi<F: Fn(&CallObj) -> Result<Type>>(call: *const 
CallObj) -> TVMFFIAny {
-    call_hook(call, zero_sized::<F>())
-}
-
-extern "C" fn validate_signature<L: Checks>(call: *const CallObj) -> TVMFFIAny 
{
-    call_hook(call, sig::validate::<L>)
-}
-
-extern "C" fn region_body_params_abi<F: Fn(&CallObj) -> Result<Array<Var>>>(
-    call: *const CallObj,
-) -> TVMFFIAny {
-    call_hook(call, zero_sized::<F>())
-}
-
-extern "C" fn region_validate_abi<F: Fn(&RegionStmtObj) -> Result<()>>(
-    region: *const RegionStmtObj,
-) -> TVMFFIAny {
-    call_hook(region, zero_sized::<F>())
-}
-
-/// The only value of `F`, a function or a closure that captures nothing.
-fn zero_sized<F>() -> F {
-    const {
-        assert!(
-            std::mem::size_of::<F>() == 0,
-            "a native operator hook is a function or a closure that captures 
nothing"
-        )
-    };
-    // SAFETY: a zero-sized type has no bytes to initialize.
-    unsafe { std::mem::zeroed() }
-}
-
-/// Run `hook` on the node C++ passes (the Call or region being typed or 
checked) and return its
-/// result as the hook ABI does. A panic, which must not unwind into C++, 
becomes an error.
-fn call_hook<N, R: Into<Any>>(node: *const N, hook: impl Fn(&N) -> Result<R>) 
-> TVMFFIAny {
-    let result = catch_unwind(AssertUnwindSafe(|| {
-        // SAFETY: C++ passes the node being typed or checked, alive for the 
call.
-        match unsafe { node.as_ref() } {
-            Some(node) => hook(node).map(Into::into),
-            None => Err(Error::new(TYPE_ERROR, "Invalid Op Call", "")),
-        }
-    }));
-    let value = match result {
-        Ok(Ok(value)) => value,
-        Ok(Err(error)) => Any::from(error),
-        Err(payload) => Any::from(tvm_ffi::Error::from_panic(payload)),
-    };
-    // The C++ caller takes ownership of the value.
-    Any::into_raw_ffi_any(value)
-}
-
 /// Signature descriptors for [`OpDef::signature`], C++ `sig::arg<T>` and 
friends.
 ///
 /// [`arg`](sig::arg) and [`ty_arg`](sig::ty_arg) are fixed value and type 
arguments,
@@ -874,3 +786,94 @@ pub mod sig {
         Err(Error::new(TYPE_ERROR, &message, ""))
     }
 }
+
+// 
---------------------------------------------------------------------------------------------
+// Helpers
+
+/// Request the APIs of a defined operator in registered destinations through 
`ir.expose_op`,
+/// after its signature and attributes are ready. An absent receiver is a 
no-op; an installed
+/// receiver's error is returned.
+fn complete(state: Result<Op>) -> Result<Op> {
+    let op = state?;
+    if let Ok(expose) = tvm_ffi::Function::get_global("ir.expose_op") {
+        expose.call_tuple((op.name.clone(),))?;
+    }
+    Ok(op)
+}
+
+/// 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 {
+    let indent = doc
+        .lines()
+        .filter(|line| !line.trim().is_empty())
+        .map(|line| line.len() - line.trim_start().len())
+        .min()
+        .unwrap_or(0);
+    let lines: Vec<&str> =
+        doc.lines().map(|line| 
line.get(indent..).unwrap_or("").trim_end()).collect();
+    lines.join("\n").trim().to_string()
+}
+
+/// 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;
+
+/// The C ABI of a native hook on a region statement, 
`NativeFunctionView<R(const
+/// RegionStmtNode*)>`.
+type RegionHook = extern "C" fn(*const RegionStmtObj) -> TVMFFIAny;
+
+extern "C" fn validate_abi<F: Fn(&CallObj) -> Result<()>>(call: *const 
CallObj) -> TVMFFIAny {
+    call_hook(call, zero_sized::<F>())
+}
+
+extern "C" fn infer_type_abi<F: Fn(&CallObj) -> Result<Type>>(call: *const 
CallObj) -> TVMFFIAny {
+    call_hook(call, zero_sized::<F>())
+}
+
+extern "C" fn validate_signature<L: Checks>(call: *const CallObj) -> TVMFFIAny 
{
+    call_hook(call, sig::validate::<L>)
+}
+
+extern "C" fn region_body_params_abi<F: Fn(&CallObj) -> Result<Array<Var>>>(
+    call: *const CallObj,
+) -> TVMFFIAny {
+    call_hook(call, zero_sized::<F>())
+}
+
+extern "C" fn region_validate_abi<F: Fn(&RegionStmtObj) -> Result<()>>(
+    region: *const RegionStmtObj,
+) -> TVMFFIAny {
+    call_hook(region, zero_sized::<F>())
+}
+
+/// The only value of `F`, a function or a closure that captures nothing.
+fn zero_sized<F>() -> F {
+    const {
+        assert!(
+            std::mem::size_of::<F>() == 0,
+            "a native operator hook is a function or a closure that captures 
nothing"
+        )
+    };
+    // SAFETY: a zero-sized type has no bytes to initialize.
+    unsafe { std::mem::zeroed() }
+}
+
+/// Run `hook` on the node C++ passes (the Call or region being typed or 
checked) and return its
+/// result as the hook ABI does. A panic, which must not unwind into C++, 
becomes an error.
+fn call_hook<N, R: Into<Any>>(node: *const N, hook: impl Fn(&N) -> Result<R>) 
-> TVMFFIAny {
+    let result = catch_unwind(AssertUnwindSafe(|| {
+        // SAFETY: C++ passes the node being typed or checked, alive for the 
call.
+        match unsafe { node.as_ref() } {
+            Some(node) => hook(node).map(Into::into),
+            None => Err(Error::new(TYPE_ERROR, "Invalid Op Call", "")),
+        }
+    }));
+    let value = match result {
+        Ok(Ok(value)) => value,
+        Ok(Err(error)) => Any::from(error),
+        Err(payload) => Any::from(tvm_ffi::Error::from_panic(payload)),
+    };
+    // The C++ caller takes ownership of the value.
+    Any::into_raw_ffi_any(value)
+}
diff --git a/rust/src/ir/prim/const_fold.rs b/rust/src/ir/prim/const_fold.rs
index 44fdb18c1..f2ce2234b 100644
--- a/rust/src/ir/prim/const_fold.rs
+++ b/rust/src/ir/prim/const_fold.rs
@@ -37,136 +37,6 @@ use crate::ir::support;
 /// The folded expression, or `None` when there is nothing to fold.
 pub(super) type Folded = Result<Option<PrimExpr>>;
 
-// 
---------------------------------------------------------------------------------------------
-// Helpers
-
-/// C++ `IsIndexType`: a scalar `int32` or `int64`, which index arithmetic 
folds eagerly.
-pub(super) fn is_index_type(dtype: DLDataType) -> bool {
-    dtype.code == support::INT && (dtype.bits == 32 || dtype.bits == 64) && 
dtype.lanes == 1
-}
-
-/// C++ `IsRepresentable(value, dtype)`: whether the exact integer `value` is 
a literal of the
-/// scalar type `dtype`, an integer in its range or a boolean 0 or 1 (as is an 
`int1`).
-pub(super) fn is_representable(value: &BigInt, dtype: DLDataType) -> bool {
-    if !support::is_scalar(dtype) || dtype.bits == 0 {
-        return false;
-    }
-    let bits = u32::from(dtype.bits);
-    if let Some(small) = value.to_i64() {
-        return match dtype.code {
-            support::UINT => small >= 0 && (bits >= 63 || (small as u64) < 
(1u64 << bits)),
-            support::BOOL => small == 0 || small == 1,
-            support::INT if bits == 1 => small == 0 || small == 1,
-            support::INT => {
-                bits >= 64 || (-(1i64 << (bits - 1))..1i64 << (bits - 
1)).contains(&small)
-            }
-            _ => false,
-        };
-    }
-    match dtype.code {
-        support::UINT => !value.is_negative() && *value < BigInt::from(1i64) 
<< i64::from(bits),
-        support::INT if bits > 1 => {
-            let limit = BigInt::from(1i64) << i64::from(bits - 1);
-            *value >= -limit.clone() && *value < limit
-        }
-        _ => false,
-    }
-}
-
-/// C++ `IsInFloat32Range(value)`: a value that rounds to a finite float32, or 
one that is not
-/// finite.
-pub(super) fn is_in_float32_range(value: f64) -> bool {
-    !value.is_finite() || 
(f64::from(f32::MIN)..=f64::from(f32::MAX)).contains(&value)
-}
-
-/// The value of an integer constant.
-pub(super) fn int(expr: &PrimExpr) -> Option<&BigInt> {
-    expr.downcast_ref::<IntImmObj>().map(|imm| &imm.value)
-}
-
-/// The value of a float constant.
-fn float(expr: &PrimExpr) -> Option<f64> {
-    expr.downcast_ref::<FloatImmObj>().map(|imm| imm.value)
-}
-
-/// C++ `IntImm(dtype, value)`.
-fn int_imm(dtype: DLDataType, value: BigInt) -> Folded {
-    Ok(Some(IntImm::new(PrimType::from_dtype(dtype)?, value, 
UNKNOWN_LOC)?.into()))
-}
-
-/// C++ `IntImm(result_ty, value)` of an exact result, or nothing to fold when 
it is not a value of
-/// the type.
-fn exact(dtype: DLDataType, value: BigInt) -> Folded {
-    match is_representable(&value, dtype) {
-        true => int_imm(dtype, value),
-        false => Ok(None),
-    }
-}
-
-/// C++ `FloatImm(dtype, value)`.
-fn float_imm(dtype: DLDataType, value: f64) -> Folded {
-    Ok(Some(FloatImm::new(PrimType::from_dtype(dtype)?, value, 
UNKNOWN_LOC)?.into()))
-}
-
-/// C++ `IntImm::Bool(value)`.
-fn boolean(value: bool) -> Folded {
-    Ok(Some(IntImm::bool(value).into()))
-}
-
-/// A float32 operation on the operands rounded to float32, as C++ computes 
`float` arithmetic, or
-/// `None` when the result is beyond the float32 range: C++ checks the result 
in double precision
-/// before rounding can turn a finite overflow into infinity, and rounding 
that result to float32
-/// gives the single-precision one (a double holds more than twice the digits 
of a float).
-fn single(x: f64, y: f64, op: fn(f64, f64) -> f64) -> Option<f64> {
-    let result = op(f64::from(x as f32), f64::from(y as f32));
-    is_in_float32_range(result).then(|| f64::from(result as f32))
-}
-
-/// C++ `FloatImm(dtype, value)` of a folded value, or nothing to fold.
-fn float_imm_of(dtype: DLDataType, value: Option<f64>) -> Folded {
-    match value {
-        Some(value) => float_imm(dtype, value),
-        None => Ok(None),
-    }
-}
-
-/// The error of `TVM_FFI_ICHECK_NE(pb->value, 0) << "Divide by zero"`.
-fn check_int_divisor(divisor: &BigInt) -> Result<()> {
-    match divisor.is_zero() {
-        true => Err(support::compare_failed(
-            INTERNAL_ERROR,
-            "pb->value != 0",
-            divisor,
-            0,
-            "Divide by zero",
-        )),
-        false => Ok(()),
-    }
-}
-
-/// The error of `TVM_FFI_ICHECK_NE(fb->value, 0) << "Divide by zero"`.
-fn check_float_divisor(divisor: f64) -> Result<()> {
-    match divisor == 0.0 {
-        true => Err(support::compare_failed(
-            INTERNAL_ERROR,
-            "fb->value != 0",
-            support::CppDouble(divisor),
-            0,
-            "Divide by zero",
-        )),
-        false => Ok(()),
-    }
-}
-
-fn is_one(value: &BigInt) -> bool {
-    value.to_i64() == Some(1)
-}
-
-/// The operand itself.
-fn keep(expr: &PrimExpr) -> Folded {
-    Ok(Some(expr.clone()))
-}
-
 // 
---------------------------------------------------------------------------------------------
 // Arithmetic
 
@@ -484,3 +354,133 @@ pub(super) fn not(a: &PrimExpr) -> Folded {
         None => Ok(None),
     }
 }
+
+// 
---------------------------------------------------------------------------------------------
+// Helpers
+
+/// C++ `IsIndexType`: a scalar `int32` or `int64`, which index arithmetic 
folds eagerly.
+pub(super) fn is_index_type(dtype: DLDataType) -> bool {
+    dtype.code == support::INT && (dtype.bits == 32 || dtype.bits == 64) && 
dtype.lanes == 1
+}
+
+/// C++ `IsRepresentable(value, dtype)`: whether the exact integer `value` is 
a literal of the
+/// scalar type `dtype`, an integer in its range or a boolean 0 or 1 (as is an 
`int1`).
+pub(super) fn is_representable(value: &BigInt, dtype: DLDataType) -> bool {
+    if !support::is_scalar(dtype) || dtype.bits == 0 {
+        return false;
+    }
+    let bits = u32::from(dtype.bits);
+    if let Some(small) = value.to_i64() {
+        return match dtype.code {
+            support::UINT => small >= 0 && (bits >= 63 || (small as u64) < 
(1u64 << bits)),
+            support::BOOL => small == 0 || small == 1,
+            support::INT if bits == 1 => small == 0 || small == 1,
+            support::INT => {
+                bits >= 64 || (-(1i64 << (bits - 1))..1i64 << (bits - 
1)).contains(&small)
+            }
+            _ => false,
+        };
+    }
+    match dtype.code {
+        support::UINT => !value.is_negative() && *value < BigInt::from(1i64) 
<< i64::from(bits),
+        support::INT if bits > 1 => {
+            let limit = BigInt::from(1i64) << i64::from(bits - 1);
+            *value >= -limit.clone() && *value < limit
+        }
+        _ => false,
+    }
+}
+
+/// C++ `IsInFloat32Range(value)`: a value that rounds to a finite float32, or 
one that is not
+/// finite.
+pub(super) fn is_in_float32_range(value: f64) -> bool {
+    !value.is_finite() || 
(f64::from(f32::MIN)..=f64::from(f32::MAX)).contains(&value)
+}
+
+/// The value of an integer constant.
+pub(super) fn int(expr: &PrimExpr) -> Option<&BigInt> {
+    expr.downcast_ref::<IntImmObj>().map(|imm| &imm.value)
+}
+
+/// The value of a float constant.
+fn float(expr: &PrimExpr) -> Option<f64> {
+    expr.downcast_ref::<FloatImmObj>().map(|imm| imm.value)
+}
+
+/// C++ `IntImm(dtype, value)`.
+fn int_imm(dtype: DLDataType, value: BigInt) -> Folded {
+    Ok(Some(IntImm::new(PrimType::from_dtype(dtype)?, value, 
UNKNOWN_LOC)?.into()))
+}
+
+/// C++ `IntImm(result_ty, value)` of an exact result, or nothing to fold when 
it is not a value of
+/// the type.
+fn exact(dtype: DLDataType, value: BigInt) -> Folded {
+    match is_representable(&value, dtype) {
+        true => int_imm(dtype, value),
+        false => Ok(None),
+    }
+}
+
+/// C++ `FloatImm(dtype, value)`.
+fn float_imm(dtype: DLDataType, value: f64) -> Folded {
+    Ok(Some(FloatImm::new(PrimType::from_dtype(dtype)?, value, 
UNKNOWN_LOC)?.into()))
+}
+
+/// C++ `IntImm::Bool(value)`.
+fn boolean(value: bool) -> Folded {
+    Ok(Some(IntImm::bool(value).into()))
+}
+
+/// A float32 operation on the operands rounded to float32, as C++ computes 
`float` arithmetic, or
+/// `None` when the result is beyond the float32 range: C++ checks the result 
in double precision
+/// before rounding can turn a finite overflow into infinity, and rounding 
that result to float32
+/// gives the single-precision one (a double holds more than twice the digits 
of a float).
+fn single(x: f64, y: f64, op: fn(f64, f64) -> f64) -> Option<f64> {
+    let result = op(f64::from(x as f32), f64::from(y as f32));
+    is_in_float32_range(result).then(|| f64::from(result as f32))
+}
+
+/// C++ `FloatImm(dtype, value)` of a folded value, or nothing to fold.
+fn float_imm_of(dtype: DLDataType, value: Option<f64>) -> Folded {
+    match value {
+        Some(value) => float_imm(dtype, value),
+        None => Ok(None),
+    }
+}
+
+/// The error of `TVM_FFI_ICHECK_NE(pb->value, 0) << "Divide by zero"`.
+fn check_int_divisor(divisor: &BigInt) -> Result<()> {
+    match divisor.is_zero() {
+        true => Err(support::compare_failed(
+            INTERNAL_ERROR,
+            "pb->value != 0",
+            divisor,
+            0,
+            "Divide by zero",
+        )),
+        false => Ok(()),
+    }
+}
+
+/// The error of `TVM_FFI_ICHECK_NE(fb->value, 0) << "Divide by zero"`.
+fn check_float_divisor(divisor: f64) -> Result<()> {
+    match divisor == 0.0 {
+        true => Err(support::compare_failed(
+            INTERNAL_ERROR,
+            "fb->value != 0",
+            support::CppDouble(divisor),
+            0,
+            "Divide by zero",
+        )),
+        false => Ok(()),
+    }
+}
+
+fn is_one(value: &BigInt) -> bool {
+    value.to_i64() == Some(1)
+}
+
+/// The operand itself.
+fn keep(expr: &PrimExpr) -> Folded {
+    Ok(Some(expr.clone()))
+}
diff --git a/rust/src/ir/prim/expr.rs b/rust/src/ir/prim/expr.rs
index 87c8dcb70..672a7b811 100644
--- a/rust/src/ir/prim/expr.rs
+++ b/rust/src/ir/prim/expr.rs
@@ -210,75 +210,6 @@ use tvm_ffi::ObjectRefCore;
 use tvm_ffi::Result;
 // tvm-ffi-stubgen(end)
 
-// 
---------------------------------------------------------------------------------------------
-// What the node constructors check
-
-/// What the constructor of a binary node checks of its operands, and the type 
it gives the node
-/// (`TVM_DEFINE_BINOP_CONSTRUCTOR` and its siblings in `src/ir/prim/expr.cc`).
-#[derive(Clone, Copy)]
-enum Operands {
-    /// `Add`, `Min`, ...: operands of one type, which is the node's.
-    Arith,
-    /// `LShift`, `BitwiseAnd`, ...: as `Arith`, of an integer type, or 
boolean if allowed.
-    Bitwise { allow_bool: bool },
-    /// `EQ`, `LT`, ...: operands of one type; the node is boolean.
-    Compare,
-}
-
-/// The type of the binary node `node` of `a` and `b`, after the checks of its 
C++ constructor.
-fn binary_type(node: &str, operands: Operands, a: &PrimExpr, b: &PrimExpr) -> 
Result<PrimType> {
-    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
-    if a_dtype != b_dtype {
-        let message = format!(
-            "mismatched types. {} vs. {}\n",
-            support::dtype_name(a_dtype),
-            support::dtype_name(b_dtype)
-        );
-        let condition = "a_ty->dtype == b_ty->dtype";
-        return Err(support::check_failed(tvm_ffi::TYPE_ERROR, condition, 
&message));
-    }
-    match operands {
-        Operands::Arith => Ok(a.ty()),
-        Operands::Bitwise { allow_bool } => {
-            let code = a_dtype.code;
-            if matches!(code, support::INT | support::UINT) || (allow_bool && 
code == support::BOOL)
-            {
-                return Ok(a.ty());
-            }
-            let condition = format!(
-                "a.ty().MatchesCode(DLDataTypeCode::kDLInt, 
DLDataTypeCode::kDLUInt) || \
-                 ({allow_bool} && a.ty().MatchesCode(DLDataTypeCode::kDLBool))"
-            );
-            let operands = if allow_bool { "integer or boolean" } else { 
"integer" };
-            let message = format!("{node} requires {operands} operands");
-            Err(support::check_failed(tvm_ffi::TYPE_ERROR, &condition, 
&message))
-        }
-        Operands::Compare => bool_type(a_dtype),
-    }
-}
-
-/// The boolean type with the lanes of `dtype` (C++ 
`PrimType(DLDataType{kDLBool, 8, lanes})`).
-fn bool_type(operand: DLDataType) -> Result<PrimType> {
-    use crate::ir::dtype;
-    PrimType::from_dtype(dtype::with_lanes(dtype::bool(), operand.lanes))
-}
-
-/// The type of the logical node of `a` and `b`, after the checks of the C++ 
`And` and `Or`
-/// constructors: boolean operands of one type.
-fn logical_type(a: &PrimExpr, b: &PrimExpr) -> Result<PrimType> {
-    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
-    for (dtype, operand) in [(a_dtype, "a_ty"), (b_dtype, "b_ty")] {
-        if dtype.code != support::BOOL {
-            let condition = 
format!("{operand}.MatchesCode(DLDataTypeCode::kDLBool)");
-            return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
&condition, ""));
-        }
-    }
-    if a_dtype != b_dtype {
-        return Err(support::check_failed(tvm_ffi::TYPE_ERROR, "a_ty == b_ty", 
"mismatched types"));
-    }
-    bool_type(a_dtype)
-}
-
 /// `impl $node { pub fn new(a, b, loc) }` for each binary node, with the 
checks of its C++
 /// constructor.
 macro_rules! binary_new {
@@ -2119,3 +2050,72 @@ impl Let {
         Ok(Self::from_fields(loc.into(), ty, var, value, body))
     }
 }
+
+// 
---------------------------------------------------------------------------------------------
+// What the node constructors check
+
+/// What the constructor of a binary node checks of its operands, and the type 
it gives the node
+/// (`TVM_DEFINE_BINOP_CONSTRUCTOR` and its siblings in `src/ir/prim/expr.cc`).
+#[derive(Clone, Copy)]
+enum Operands {
+    /// `Add`, `Min`, ...: operands of one type, which is the node's.
+    Arith,
+    /// `LShift`, `BitwiseAnd`, ...: as `Arith`, of an integer type, or 
boolean if allowed.
+    Bitwise { allow_bool: bool },
+    /// `EQ`, `LT`, ...: operands of one type; the node is boolean.
+    Compare,
+}
+
+/// The type of the binary node `node` of `a` and `b`, after the checks of its 
C++ constructor.
+fn binary_type(node: &str, operands: Operands, a: &PrimExpr, b: &PrimExpr) -> 
Result<PrimType> {
+    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
+    if a_dtype != b_dtype {
+        let message = format!(
+            "mismatched types. {} vs. {}\n",
+            support::dtype_name(a_dtype),
+            support::dtype_name(b_dtype)
+        );
+        let condition = "a_ty->dtype == b_ty->dtype";
+        return Err(support::check_failed(tvm_ffi::TYPE_ERROR, condition, 
&message));
+    }
+    match operands {
+        Operands::Arith => Ok(a.ty()),
+        Operands::Bitwise { allow_bool } => {
+            let code = a_dtype.code;
+            if matches!(code, support::INT | support::UINT) || (allow_bool && 
code == support::BOOL)
+            {
+                return Ok(a.ty());
+            }
+            let condition = format!(
+                "a.ty().MatchesCode(DLDataTypeCode::kDLInt, 
DLDataTypeCode::kDLUInt) || \
+                 ({allow_bool} && a.ty().MatchesCode(DLDataTypeCode::kDLBool))"
+            );
+            let operands = if allow_bool { "integer or boolean" } else { 
"integer" };
+            let message = format!("{node} requires {operands} operands");
+            Err(support::check_failed(tvm_ffi::TYPE_ERROR, &condition, 
&message))
+        }
+        Operands::Compare => bool_type(a_dtype),
+    }
+}
+
+/// The boolean type with the lanes of `dtype` (C++ 
`PrimType(DLDataType{kDLBool, 8, lanes})`).
+fn bool_type(operand: DLDataType) -> Result<PrimType> {
+    use crate::ir::dtype;
+    PrimType::from_dtype(dtype::with_lanes(dtype::bool(), operand.lanes))
+}
+
+/// The type of the logical node of `a` and `b`, after the checks of the C++ 
`And` and `Or`
+/// constructors: boolean operands of one type.
+fn logical_type(a: &PrimExpr, b: &PrimExpr) -> Result<PrimType> {
+    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
+    for (dtype, operand) in [(a_dtype, "a_ty"), (b_dtype, "b_ty")] {
+        if dtype.code != support::BOOL {
+            let condition = 
format!("{operand}.MatchesCode(DLDataTypeCode::kDLBool)");
+            return Err(support::check_failed(tvm_ffi::INTERNAL_ERROR, 
&condition, ""));
+        }
+    }
+    if a_dtype != b_dtype {
+        return Err(support::check_failed(tvm_ffi::TYPE_ERROR, "a_ty == b_ty", 
"mismatched types"));
+    }
+    bool_type(a_dtype)
+}
diff --git a/rust/src/ir/prim/op.rs b/rust/src/ir/prim/op.rs
index 8929859bd..c435f85f8 100644
--- a/rust/src/ir/prim/op.rs
+++ b/rust/src/ir/prim/op.rs
@@ -209,6 +209,16 @@ use tvm_ffi::VALUE_ERROR;
 #[must_use = "an `ExprResult` holds the result; pass it on, or convert it with 
`PrimExpr::try_from`"]
 pub struct ExprResult(Result<PrimExpr>);
 
+impl ExprResult {
+    /// The dtype a literal next to this expression takes 
(`MakeConst(this.ty(), v)`).
+    fn dtype(&self) -> Result<DLDataType> {
+        match &self.0 {
+            Ok(expr) => Ok(expr.dtype()),
+            Err(error) => Err(error.clone()),
+        }
+    }
+}
+
 impl TryFrom<ExprResult> for Expr {
     type Error = Error;
 
@@ -227,16 +237,6 @@ impl TryFrom<ExprResult> for PrimExpr {
     }
 }
 
-impl ExprResult {
-    /// The dtype a literal next to this expression takes 
(`MakeConst(this.ty(), v)`).
-    fn dtype(&self) -> Result<DLDataType> {
-        match &self.0 {
-            Ok(expr) => Ok(expr.dtype()),
-            Err(error) => Err(error.clone()),
-        }
-    }
-}
-
 impl IntoExpr for ExprResult {
     type Output = Expr;
 
@@ -288,29 +288,6 @@ impl From<&ExprResult> for ExprResult {
     }
 }
 
-/// `lhs <op> rhs`; a literal `rhs` is typed after `lhs`.
-fn binary(
-    lhs: ExprResult,
-    rhs: impl Operand,
-    op: impl FnOnce(PrimExpr, PrimExpr) -> Result<PrimExpr>,
-) -> ExprResult {
-    let rhs = typed_after(rhs.classify(), &lhs);
-    match (lhs.0, rhs.0) {
-        (Ok(a), Ok(b)) => ExprResult(op(a, b)),
-        (Err(error), _) | (_, Err(error)) => ExprResult(Err(error)),
-    }
-}
-
-/// `lhs <op> rhs` for a literal `lhs`, typed after `rhs`.
-fn binary_literal(
-    lhs: impl Operand,
-    rhs: ExprResult,
-    op: impl FnOnce(PrimExpr, PrimExpr) -> Result<PrimExpr>,
-) -> ExprResult {
-    let lhs = typed_after(lhs.classify(), &rhs);
-    binary(lhs, rhs, op)
-}
-
 mod operators {
     //! The operator traits of [`ExprResult`], and of the expressions and 
literals that start one.
 
@@ -409,6 +386,29 @@ mod operators {
     unary_operators!(ExprResult, &ExprResult);
 }
 
+/// `lhs <op> rhs`; a literal `rhs` is typed after `lhs`.
+fn binary(
+    lhs: ExprResult,
+    rhs: impl Operand,
+    op: impl FnOnce(PrimExpr, PrimExpr) -> Result<PrimExpr>,
+) -> ExprResult {
+    let rhs = typed_after(rhs.classify(), &lhs);
+    match (lhs.0, rhs.0) {
+        (Ok(a), Ok(b)) => ExprResult(op(a, b)),
+        (Err(error), _) | (_, Err(error)) => ExprResult(Err(error)),
+    }
+}
+
+/// `lhs <op> rhs` for a literal `lhs`, typed after `rhs`.
+fn binary_literal(
+    lhs: impl Operand,
+    rhs: ExprResult,
+    op: impl FnOnce(PrimExpr, PrimExpr) -> Result<PrimExpr>,
+) -> ExprResult {
+    let lhs = typed_after(lhs.classify(), &rhs);
+    binary(lhs, rhs, op)
+}
+
 // 
---------------------------------------------------------------------------------------------
 // `PrimOps`: the operations Rust has no operator for
 
@@ -810,6 +810,45 @@ impl From<f32> for ConstValue {
     }
 }
 
+unlocated! {
+    /// Create the constant `value` of the type `ty`, broadcast over the lanes 
of a vector type (C++
+    /// `prim::MakeConst(ty, value)`).
+    ///
+    /// # Errors
+    ///
+    /// Returns a `ValueError` if `value` is out of the range of `ty`, or an 
`InternalError` if it
+    /// is negative for an unsigned `ty` or `ty` is not a type of constants, 
such as `void`.
+    make_const(ty: impl Into<PrimType>, value: impl Into<ConstValue>) => 
make_const_at;
+}
+
+/// Build [`make_const`] at the location `loc`.
+pub fn make_const_at(
+    ty: impl Into<PrimType>,
+    value: impl Into<ConstValue>,
+    loc: impl Into<Location>,
+) -> Result<PrimExpr> {
+    let (dtype, value, loc) = (ty.into().dtype, value.into(), loc.into());
+    if !is_vector(dtype) {
+        return make_const_scalar(dtype, value, &loc);
+    }
+    let scalar = make_const_scalar(with_lanes(dtype, 1), value, &loc)?;
+    let lanes = match support::is_fixed_length_vector(dtype) {
+        true => IntImm::int32(support::lanes(dtype)?).into(),
+        false => super::vector_expr::vscale_lanes(vscale_factor(dtype))?,
+    };
+    Ok(Broadcast::new(scalar, lanes, loc)?.into())
+}
+
+/// Convert `value` into a primitive expression: a Rust `bool`, integer or 
float becomes a constant
+/// of the default type, and a primitive expression is returned as is 
(`prim.convert`).
+///
+/// # Errors
+///
+/// Returns a `TypeError` if `value` is not a primitive expression or a 
literal.
+pub fn convert(value: impl Into<Any>) -> Result<PrimExpr> {
+    
tvm_ffi::cached_global_func!("prim.convert").call_tuple((value.into(),))?.try_into()
+}
+
 /// C++ `prim::MakeConstScalar(dtype, value, loc)`: an `IntImm` for an integer 
or boolean type, a
 /// `FloatImm` for a floating-point type.
 pub(crate) fn make_const_scalar(
@@ -855,71 +894,151 @@ pub(crate) fn make_const_scalar(
     Err(Error::new(INTERNAL_ERROR, &message, ""))
 }
 
+// 
---------------------------------------------------------------------------------------------
+// Casts and type matching (C++ `cast` and `BinaryOpMatchTypes`)
+
 unlocated! {
-    /// Create the constant `value` of the type `ty`, broadcast over the lanes 
of a vector type (C++
-    /// `prim::MakeConst(ty, value)`).
+    /// Convert `value` to the type `ty`, folding a constant that keeps its 
value and broadcasting a
+    /// scalar to a vector type (C++ `prim::cast(value, t)`, `prim._cast`).
+    ///
+    /// [`Cast::new`] builds the node without folding.
     ///
     /// # Errors
     ///
-    /// Returns a `ValueError` if `value` is out of the range of `ty`, or an 
`InternalError` if it
-    /// is negative for an unsigned `ty` or `ty` is not a type of constants, 
such as `void`.
-    make_const(ty: impl Into<PrimType>, value: impl Into<ConstValue>) => 
make_const_at;
+    /// Returns a `TypeError` if `value` has the void type, or an 
`InternalError` if `value` and
+    /// `ty` are vectors of different lanes.
+    cast(value: impl IntoExpr<PrimExpr>, ty: impl Into<PrimType>) => cast_at;
 }
 
-/// Build [`make_const`] at the location `loc`.
-pub fn make_const_at(
+/// Build [`cast`] at the location `loc`.
+pub fn cast_at(
+    value: impl IntoExpr<PrimExpr>,
     ty: impl Into<PrimType>,
-    value: impl Into<ConstValue>,
     loc: impl Into<Location>,
 ) -> Result<PrimExpr> {
-    let (dtype, value, loc) = (ty.into().dtype, value.into(), loc.into());
-    if !is_vector(dtype) {
-        return make_const_scalar(dtype, value, &loc);
+    let (value, ty, loc) = (value.into_owned_expr()?, ty.into(), loc.into());
+    let (from, to) = (value.dtype(), ty.dtype);
+    if from == to {
+        return Ok(value);
     }
-    let scalar = make_const_scalar(with_lanes(dtype, 1), value, &loc)?;
-    let lanes = match support::is_fixed_length_vector(dtype) {
-        true => IntImm::int32(support::lanes(dtype)?).into(),
-        false => super::vector_expr::vscale_lanes(vscale_factor(dtype))?,
+    if from == void() {
+        let message = "Cannot cast an expression with the void sentinel type";
+        return Err(support::check_failed(TYPE_ERROR, "!value.ty().IsVoid()", 
message));
+    }
+    if support::is_scalar(to) {
+        return cast_scalar(value, ty, &loc);
+    }
+    let element = PrimType::from_dtype(with_lanes(to, 1))?;
+    if !is_vector(from) {
+        let value = cast_at(value, element, loc.clone())?;
+        return Ok(Broadcast::new(value, lanes_of(to)?, loc)?.into());
+    }
+    if support::is_scalable_vector(from) != support::is_scalable_vector(to) {
+        let condition = "value.ty().IsScalableVector() == 
dtype.IsScalableVector()";
+        return Err(support::check_failed(INTERNAL_ERROR, condition, ""));
+    }
+    let lanes_match = match support::is_scalable_vector(from) {
+        true => vscale_factor(from) == vscale_factor(to),
+        false => support::lanes(from)? == support::lanes(to)?,
     };
-    Ok(Broadcast::new(scalar, lanes, loc)?.into())
+    if !lanes_match {
+        return Err(support::check_failed(INTERNAL_ERROR, "lanes_match", ""));
+    }
+    if let Some(broadcast) = value.downcast_ref::<BroadcastObj>() {
+        let value = cast_at(broadcast.value.clone(), element, loc.clone())?;
+        return Ok(Broadcast::new(value, broadcast.lanes.clone(), loc)?.into());
+    }
+    if let Some(ramp) = value.downcast_ref::<RampObj>() {
+        // Casting the base and the stride is valid only when every lane is 
the same integer in
+        // both types: widening must not remove an overflow of the source, and 
a floating-point
+        // cast need not be affine.
+        let integer = matches!(to.code, support::INT | support::UINT) && 
matches!(to.bits, 32 | 64);
+        if let (Some(base), Some(stride)) =
+            (const_fold::int(&ramp.base_), const_fold::int(&ramp.stride))
+        {
+            if support::is_fixed_length_vector(to) && integer {
+                let last = base + stride * i64::from(support::lanes(to)? - 1);
+                let source = support::prim_dtype(&ramp.base_)?;
+                let fits = |value: &BigInt| 
const_fold::is_representable(value, element.dtype);
+                if fits(base)
+                    && fits(stride)
+                    && fits(&last)
+                    && const_fold::is_representable(&last, source)
+                {
+                    let base = cast_at(ramp.base_.clone(), element.clone(), 
loc.clone())?;
+                    let stride = cast_at(ramp.stride.clone(), element, 
loc.clone())?;
+                    return Ok(Ramp::new(base, stride, ramp.lanes.clone(), 
loc)?.into());
+                }
+            }
+        }
+    }
+    Ok(Cast::new(ty, value, loc)?.into())
 }
 
-/// Convert `value` into a primitive expression: a Rust `bool`, integer or 
float becomes a constant
-/// of the default type, and a primitive expression is returned as is 
(`prim.convert`).
+/// Bring `a` and `b` to one type, as every binary operator does first (C++
+/// `BinaryOpMatchTypes(lhs, rhs)`).
+///
+/// A scalar next to a vector is broadcast, and both are cast to the promoted 
type: the wider float,
+/// a float over an integer, the wider integer, unsigned over signed.
 ///
 /// # Errors
 ///
-/// Returns a `TypeError` if `value` is not a primitive expression or a 
literal.
-pub fn convert(value: impl Into<Any>) -> Result<PrimExpr> {
-    
tvm_ffi::cached_global_func!("prim.convert").call_tuple((value.into(),))?.try_into()
+/// Returns an `InternalError` if the lanes of the two differ or their types 
cannot be promoted to
+/// one.
+pub fn match_types(
+    a: impl IntoExpr<PrimExpr>,
+    b: impl IntoExpr<PrimExpr>,
+) -> Result<(PrimExpr, PrimExpr)> {
+    match_types_at(a, b, UNKNOWN_LOC)
 }
 
-// 
---------------------------------------------------------------------------------------------
-// Casts and type matching (C++ `cast` and `BinaryOpMatchTypes`)
-
-/// The exponent C++ `frexp` gives a finite `value`, `e` with `value = m * 
2^e` and
-/// `0.5 <= |m| < 1`, and 0 for zero.
-fn frexp_exponent(value: f64) -> i32 {
-    match ((value.to_bits() >> 52) & 0x7ff) as i32 {
-        0 if value == 0.0 => 0,
-        // A subnormal, scaled into the normal range.
-        0 => frexp_exponent(value * pow2(64)) - 64,
-        biased => biased - 1022,
+/// Run [`match_types`] with the casts at the location `loc`.
+pub fn match_types_at(
+    a: impl IntoExpr<PrimExpr>,
+    b: impl IntoExpr<PrimExpr>,
+    loc: impl Into<Location>,
+) -> Result<(PrimExpr, PrimExpr)> {
+    let (a, b, loc) = (a.into_owned_expr()?, b.into_owned_expr()?, loc.into());
+    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
+    if a_dtype == b_dtype {
+        return Ok((a, b));
+    }
+    let a = broadcast_to_match_lanes(a, b_dtype)?;
+    let b = broadcast_to_match_lanes(b, a.dtype())?;
+    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
+    if support::is_scalable_vector(a_dtype) != 
support::is_scalable_vector(b_dtype) {
+        let condition = "lhs_ty.IsScalableVector() == 
rhs_ty.IsScalableVector()";
+        let message = "Can't match scalable and fixed length vectors";
+        return Err(support::check_failed(INTERNAL_ERROR, condition, message));
+    }
+    let lanes_match = match support::is_scalable_vector(a_dtype) {
+        true => vscale_factor(a_dtype) == vscale_factor(b_dtype),
+        false => support::lanes(a_dtype)? == support::lanes(b_dtype)?,
+    };
+    if !lanes_match {
+        let (lhs, rhs) = (support::dtype_name(a_dtype), 
support::dtype_name(b_dtype));
+        let message = format!("Cannot match type {lhs} vs {rhs}");
+        return Err(support::check_failed(INTERNAL_ERROR, "lanes_match", 
&message));
     }
+    let promoted = promote_binary_op_type(a_dtype, b_dtype)?;
+    let a = match a_dtype == promoted {
+        true => a,
+        false => cast_at(a, PrimType::from_dtype(promoted)?, loc.clone())?,
+    };
+    let b = match b_dtype == promoted {
+        true => b,
+        false => cast_at(b, PrimType::from_dtype(promoted)?, loc)?,
+    };
+    Ok((a, b))
 }
 
-/// `2^exponent` for an exponent of a normal double.
-fn pow2(exponent: i32) -> f64 {
-    f64::from_bits(((exponent + 1023) as u64) << 52)
-}
-
-/// Whether the finite `value` of a float16 or bfloat16 `FloatImm` is a value 
of its type: a
-/// `FloatImm` holds an unrounded double, which stands for the value the type 
rounds it to.
-fn is_rounded_to_16_bits(value: f64, bfloat: bool) -> bool {
-    let (digits, min_step) = if bfloat { (8, -133) } else { (11, -24) };
-    let step = (frexp_exponent(value) - digits).max(min_step);
-    let scaled = value * pow2(-step);
-    scaled.trunc() == scaled
+/// C++ `cast` to a scalar type: a constant folded to `ty` keeping its 
location (see
+/// [`fold_cast`]); anything else a [`Cast`].
+fn cast_scalar(value: PrimExpr, ty: PrimType, loc: &Location) -> 
Result<PrimExpr> {
+    match fold_cast(&value, ty.dtype)? {
+        Some(folded) => Ok(folded),
+        None => Ok(Cast::new(ty, value, loc.clone())?.into()),
+    }
 }
 
 /// A constant cast to the scalar type `to` as C++ `cast` folds it: only when 
the result is the
@@ -1005,91 +1124,29 @@ fn fold_cast(value: &PrimExpr, to: DLDataType) -> 
Result<Option<PrimExpr>> {
     Ok(None)
 }
 
-/// C++ `cast` to a scalar type: a constant folded to `ty` keeping its 
location (see
-/// [`fold_cast`]); anything else a [`Cast`].
-fn cast_scalar(value: PrimExpr, ty: PrimType, loc: &Location) -> 
Result<PrimExpr> {
-    match fold_cast(&value, ty.dtype)? {
-        Some(folded) => Ok(folded),
-        None => Ok(Cast::new(ty, value, loc.clone())?.into()),
-    }
+/// Whether the finite `value` of a float16 or bfloat16 `FloatImm` is a value 
of its type: a
+/// `FloatImm` holds an unrounded double, which stands for the value the type 
rounds it to.
+fn is_rounded_to_16_bits(value: f64, bfloat: bool) -> bool {
+    let (digits, min_step) = if bfloat { (8, -133) } else { (11, -24) };
+    let step = (frexp_exponent(value) - digits).max(min_step);
+    let scaled = value * pow2(-step);
+    scaled.trunc() == scaled
 }
 
-unlocated! {
-    /// Convert `value` to the type `ty`, folding a constant that keeps its 
value and broadcasting a
-    /// scalar to a vector type (C++ `prim::cast(value, t)`, `prim._cast`).
-    ///
-    /// [`Cast::new`] builds the node without folding.
-    ///
-    /// # Errors
-    ///
-    /// Returns a `TypeError` if `value` has the void type, or an 
`InternalError` if `value` and
-    /// `ty` are vectors of different lanes.
-    cast(value: impl IntoExpr<PrimExpr>, ty: impl Into<PrimType>) => cast_at;
+/// The exponent C++ `frexp` gives a finite `value`, `e` with `value = m * 
2^e` and
+/// `0.5 <= |m| < 1`, and 0 for zero.
+fn frexp_exponent(value: f64) -> i32 {
+    match ((value.to_bits() >> 52) & 0x7ff) as i32 {
+        0 if value == 0.0 => 0,
+        // A subnormal, scaled into the normal range.
+        0 => frexp_exponent(value * pow2(64)) - 64,
+        biased => biased - 1022,
+    }
 }
 
-/// Build [`cast`] at the location `loc`.
-pub fn cast_at(
-    value: impl IntoExpr<PrimExpr>,
-    ty: impl Into<PrimType>,
-    loc: impl Into<Location>,
-) -> Result<PrimExpr> {
-    let (value, ty, loc) = (value.into_owned_expr()?, ty.into(), loc.into());
-    let (from, to) = (value.dtype(), ty.dtype);
-    if from == to {
-        return Ok(value);
-    }
-    if from == void() {
-        let message = "Cannot cast an expression with the void sentinel type";
-        return Err(support::check_failed(TYPE_ERROR, "!value.ty().IsVoid()", 
message));
-    }
-    if support::is_scalar(to) {
-        return cast_scalar(value, ty, &loc);
-    }
-    let element = PrimType::from_dtype(with_lanes(to, 1))?;
-    if !is_vector(from) {
-        let value = cast_at(value, element, loc.clone())?;
-        return Ok(Broadcast::new(value, lanes_of(to)?, loc)?.into());
-    }
-    if support::is_scalable_vector(from) != support::is_scalable_vector(to) {
-        let condition = "value.ty().IsScalableVector() == 
dtype.IsScalableVector()";
-        return Err(support::check_failed(INTERNAL_ERROR, condition, ""));
-    }
-    let lanes_match = match support::is_scalable_vector(from) {
-        true => vscale_factor(from) == vscale_factor(to),
-        false => support::lanes(from)? == support::lanes(to)?,
-    };
-    if !lanes_match {
-        return Err(support::check_failed(INTERNAL_ERROR, "lanes_match", ""));
-    }
-    if let Some(broadcast) = value.downcast_ref::<BroadcastObj>() {
-        let value = cast_at(broadcast.value.clone(), element, loc.clone())?;
-        return Ok(Broadcast::new(value, broadcast.lanes.clone(), loc)?.into());
-    }
-    if let Some(ramp) = value.downcast_ref::<RampObj>() {
-        // Casting the base and the stride is valid only when every lane is 
the same integer in
-        // both types: widening must not remove an overflow of the source, and 
a floating-point
-        // cast need not be affine.
-        let integer = matches!(to.code, support::INT | support::UINT) && 
matches!(to.bits, 32 | 64);
-        if let (Some(base), Some(stride)) =
-            (const_fold::int(&ramp.base_), const_fold::int(&ramp.stride))
-        {
-            if support::is_fixed_length_vector(to) && integer {
-                let last = base + stride * i64::from(support::lanes(to)? - 1);
-                let source = support::prim_dtype(&ramp.base_)?;
-                let fits = |value: &BigInt| 
const_fold::is_representable(value, element.dtype);
-                if fits(base)
-                    && fits(stride)
-                    && fits(&last)
-                    && const_fold::is_representable(&last, source)
-                {
-                    let base = cast_at(ramp.base_.clone(), element.clone(), 
loc.clone())?;
-                    let stride = cast_at(ramp.stride.clone(), element, 
loc.clone())?;
-                    return Ok(Ramp::new(base, stride, ramp.lanes.clone(), 
loc)?.into());
-                }
-            }
-        }
-    }
-    Ok(Cast::new(ty, value, loc)?.into())
+/// `2^exponent` for an exponent of a normal double.
+fn pow2(exponent: i32) -> f64 {
+    f64::from_bits(((exponent + 1023) as u64) << 52)
 }
 
 /// C++ `BroadcastToMatchLanes`: `value`, broadcast to the lanes of `other` 
when it is a scalar
@@ -1145,63 +1202,6 @@ fn promote_binary_op_type(lhs: DLDataType, rhs: 
DLDataType) -> Result<DLDataType
     }
 }
 
-/// Bring `a` and `b` to one type, as every binary operator does first (C++
-/// `BinaryOpMatchTypes(lhs, rhs)`).
-///
-/// A scalar next to a vector is broadcast, and both are cast to the promoted 
type: the wider float,
-/// a float over an integer, the wider integer, unsigned over signed.
-///
-/// # Errors
-///
-/// Returns an `InternalError` if the lanes of the two differ or their types 
cannot be promoted to
-/// one.
-pub fn match_types(
-    a: impl IntoExpr<PrimExpr>,
-    b: impl IntoExpr<PrimExpr>,
-) -> Result<(PrimExpr, PrimExpr)> {
-    match_types_at(a, b, UNKNOWN_LOC)
-}
-
-/// Run [`match_types`] with the casts at the location `loc`.
-pub fn match_types_at(
-    a: impl IntoExpr<PrimExpr>,
-    b: impl IntoExpr<PrimExpr>,
-    loc: impl Into<Location>,
-) -> Result<(PrimExpr, PrimExpr)> {
-    let (a, b, loc) = (a.into_owned_expr()?, b.into_owned_expr()?, loc.into());
-    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
-    if a_dtype == b_dtype {
-        return Ok((a, b));
-    }
-    let a = broadcast_to_match_lanes(a, b_dtype)?;
-    let b = broadcast_to_match_lanes(b, a.dtype())?;
-    let (a_dtype, b_dtype) = (a.dtype(), b.dtype());
-    if support::is_scalable_vector(a_dtype) != 
support::is_scalable_vector(b_dtype) {
-        let condition = "lhs_ty.IsScalableVector() == 
rhs_ty.IsScalableVector()";
-        let message = "Can't match scalable and fixed length vectors";
-        return Err(support::check_failed(INTERNAL_ERROR, condition, message));
-    }
-    let lanes_match = match support::is_scalable_vector(a_dtype) {
-        true => vscale_factor(a_dtype) == vscale_factor(b_dtype),
-        false => support::lanes(a_dtype)? == support::lanes(b_dtype)?,
-    };
-    if !lanes_match {
-        let (lhs, rhs) = (support::dtype_name(a_dtype), 
support::dtype_name(b_dtype));
-        let message = format!("Cannot match type {lhs} vs {rhs}");
-        return Err(support::check_failed(INTERNAL_ERROR, "lanes_match", 
&message));
-    }
-    let promoted = promote_binary_op_type(a_dtype, b_dtype)?;
-    let a = match a_dtype == promoted {
-        true => a,
-        false => cast_at(a, PrimType::from_dtype(promoted)?, loc.clone())?,
-    };
-    let b = match b_dtype == promoted {
-        true => b,
-        false => cast_at(b, PrimType::from_dtype(promoted)?, loc)?,
-    };
-    Ok((a, b))
-}
-
 // 
---------------------------------------------------------------------------------------------
 // Arithmetic (`src/ir/prim/op.cc`)
 
@@ -1264,26 +1264,6 @@ folded_binary!(
     ne / ne_at: fold_ne => NE,
 );
 
-fn fold_gt(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
-    const_fold::compare(a, b, |x, y| x > y, |x, y| x > y)
-}
-
-fn fold_ge(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
-    const_fold::compare(a, b, |x, y| x >= y, |x, y| x >= y)
-}
-
-fn fold_lt(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
-    const_fold::compare(a, b, |x, y| x < y, |x, y| x < y)
-}
-
-fn fold_le(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
-    const_fold::compare(a, b, |x, y| x <= y, |x, y| x <= y)
-}
-
-fn fold_ne(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
-    const_fold::compare(a, b, |x, y| x != y, |x, y| x != y)
-}
-
 unlocated! {
     /// Build the boolean `a == b` (C++ `equal`, `prim._OpEQ`).
     eq(a: impl Operand, b: impl Operand) => eq_at;
@@ -1411,16 +1391,6 @@ pub fn ceildiv_at(a: impl Operand, b: impl Operand, loc: 
impl Into<Location>) ->
     }
 }
 
-/// `SymbolicLimits::pos_inf_` and `neg_inf_`, the opaque infinities of 
integer analysis, which
-/// [`min`] and [`max`] recognize by identity; fetched once per thread.
-fn symbolic_infinities() -> (PrimExpr, PrimExpr) {
-    use std::cell::OnceCell;
-    thread_local! {
-        static INFINITIES: OnceCell<(PrimExpr, PrimExpr)> = const { 
OnceCell::new() };
-    }
-    support::shared(&INFINITIES, || Ok((sym::pos_inf()?, sym::neg_inf()?)))
-}
-
 /// Build [`min`] at the location `loc`.
 pub fn min_at(a: impl Operand, b: impl Operand, loc: impl Into<Location>) -> 
Result<PrimExpr> {
     let ((a, b), loc) = (operands(a, b)?, loc.into());
@@ -1467,6 +1437,36 @@ pub fn max_at(a: impl Operand, b: impl Operand, loc: 
impl Into<Location>) -> Res
     }
 }
 
+fn fold_gt(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
+    const_fold::compare(a, b, |x, y| x > y, |x, y| x > y)
+}
+
+fn fold_ge(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
+    const_fold::compare(a, b, |x, y| x >= y, |x, y| x >= y)
+}
+
+fn fold_lt(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
+    const_fold::compare(a, b, |x, y| x < y, |x, y| x < y)
+}
+
+fn fold_le(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
+    const_fold::compare(a, b, |x, y| x <= y, |x, y| x <= y)
+}
+
+fn fold_ne(a: &PrimExpr, b: &PrimExpr) -> const_fold::Folded {
+    const_fold::compare(a, b, |x, y| x != y, |x, y| x != y)
+}
+
+/// `SymbolicLimits::pos_inf_` and `neg_inf_`, the opaque infinities of 
integer analysis, which
+/// [`min`] and [`max`] recognize by identity; fetched once per thread.
+fn symbolic_infinities() -> (PrimExpr, PrimExpr) {
+    use std::cell::OnceCell;
+    thread_local! {
+        static INFINITIES: OnceCell<(PrimExpr, PrimExpr)> = const { 
OnceCell::new() };
+    }
+    support::shared(&INFINITIES, || Ok((sym::pos_inf()?, sym::neg_inf()?)))
+}
+
 // 
---------------------------------------------------------------------------------------------
 // Logic
 
@@ -1574,20 +1574,6 @@ pub fn any_at(
     fold_conditions(conds, loc.into(), "any", logical_or_at)
 }
 
-fn fold_conditions(
-    conds: impl IntoIterator<Item = impl IntoExpr<PrimExpr>>,
-    loc: Location,
-    name: &str,
-    op: fn(PrimExpr, PrimExpr, Location) -> Result<PrimExpr>,
-) -> Result<PrimExpr> {
-    let mut conds = conds.into_iter().map(IntoExpr::into_owned_expr);
-    let Some(first) = conds.next() else {
-        let message = format!("{name} must take at least 1 argument");
-        return Err(Error::new(VALUE_ERROR, &message, ""));
-    };
-    conds.try_fold(first?, |acc, cond| op(acc, cond?, loc.clone()))
-}
-
 /// Build [`if_then_else`] at the location `loc`.
 pub fn if_then_else_at(
     cond: impl IntoExpr<PrimExpr>,
@@ -1609,46 +1595,23 @@ pub fn if_then_else_at(
     call(Some(&ty), "prim.if_then_else", vec![cond, true_value, false_value], 
loc)
 }
 
-// 
---------------------------------------------------------------------------------------------
-// Bitwise operators
-
-/// The folding of a shift of index types: a constant amount is checked 
against the width, two
-/// constants fold to the value `shift` gives them, if any, and a shift by 
zero is the operand
-/// itself (`TVM_PRIM_INDEX_CONST_PROPAGATION`).
-fn fold_shift(
-    a: &PrimExpr,
-    b: &PrimExpr,
-    shift: impl FnOnce(&BigInt, &BigInt) -> Result<Option<BigInt>>,
-    loc: &Location,
-) -> const_fold::Folded {
-    let dtype = a.dtype();
-    if !const_fold::is_index_type(dtype) || 
!const_fold::is_index_type(b.dtype()) {
-        return Ok(None);
-    }
-    let (pa, pb) = (const_fold::int(a), const_fold::int(b));
-    if let Some(y) = pb {
-        if y.is_negative() || *y >= BigInt::from(i64::from(dtype.bits)) {
-            let ty = a.ty();
-            let condition = "pb->value >= 0 && pb->value < result_ty.bits()";
-            let message = format!(
-                "Shift amount must be non-negative and less than {} for type 
{}",
-                dtype.bits,
-                support::repr(&ty)
-            );
-            return Err(support::check_failed(INTERNAL_ERROR, condition, 
&message));
-        }
-    }
-    if let (Some(x), Some(y)) = (pa, pb) {
-        if let Some(value) = shift(x, y)? {
-            return Ok(Some(IntImm::new(PrimType::from_dtype(dtype)?, value, 
loc.clone())?.into()));
-        }
-    }
-    if pb.is_some_and(BigInt::is_zero) {
-        return Ok(Some(a.clone()));
-    }
-    Ok(None)
+fn fold_conditions(
+    conds: impl IntoIterator<Item = impl IntoExpr<PrimExpr>>,
+    loc: Location,
+    name: &str,
+    op: fn(PrimExpr, PrimExpr, Location) -> Result<PrimExpr>,
+) -> Result<PrimExpr> {
+    let mut conds = conds.into_iter().map(IntoExpr::into_owned_expr);
+    let Some(first) = conds.next() else {
+        let message = format!("{name} must take at least 1 argument");
+        return Err(Error::new(VALUE_ERROR, &message, ""));
+    };
+    conds.try_fold(first?, |acc, cond| op(acc, cond?, loc.clone()))
 }
 
+// 
---------------------------------------------------------------------------------------------
+// Bitwise operators
+
 unlocated! {
     /// Build `a << b` of two integers (C++ `left_shift`, `prim.left_shift`).
     ///
@@ -1776,30 +1739,46 @@ pub fn bitwise_not_at(a: impl IntoExpr<PrimExpr>, loc: 
impl Into<Location>) -> R
     Ok(BitwiseNot::new(a, loc)?.into())
 }
 
-// 
---------------------------------------------------------------------------------------------
-// Math functions (`src/ir/prim/math.cc`)
-
-/// `floor`, `ceil`, `round`, `nearbyint` and `trunc`: an integer or a boolean 
is itself, a float
-/// constant is folded with `fold`, keeping its location, and anything else is 
a call.
-fn rounding(
-    x: PrimExpr,
-    op: &'static str,
-    fold: fn(f64) -> f64,
-    loc: Location,
-) -> Result<PrimExpr> {
-    let dtype = x.dtype();
-    if matches!(dtype.code, support::INT | support::UINT | support::BOOL) {
-        return Ok(x);
+/// The folding of a shift of index types: a constant amount is checked 
against the width, two
+/// constants fold to the value `shift` gives them, if any, and a shift by 
zero is the operand
+/// itself (`TVM_PRIM_INDEX_CONST_PROPAGATION`).
+fn fold_shift(
+    a: &PrimExpr,
+    b: &PrimExpr,
+    shift: impl FnOnce(&BigInt, &BigInt) -> Result<Option<BigInt>>,
+    loc: &Location,
+) -> const_fold::Folded {
+    let dtype = a.dtype();
+    if !const_fold::is_index_type(dtype) || 
!const_fold::is_index_type(b.dtype()) {
+        return Ok(None);
     }
-    if let Some(imm) = x.downcast_ref::<FloatImmObj>() {
-        return Ok(
-            FloatImm::new(PrimType::from_dtype(dtype)?, fold(imm.value), 
imm.loc.clone())?.into()
-        );
+    let (pa, pb) = (const_fold::int(a), const_fold::int(b));
+    if let Some(y) = pb {
+        if y.is_negative() || *y >= BigInt::from(i64::from(dtype.bits)) {
+            let ty = a.ty();
+            let condition = "pb->value >= 0 && pb->value < result_ty.bits()";
+            let message = format!(
+                "Shift amount must be non-negative and less than {} for type 
{}",
+                dtype.bits,
+                support::repr(&ty)
+            );
+            return Err(support::check_failed(INTERNAL_ERROR, condition, 
&message));
+        }
     }
-    let ty = x.ty.clone();
-    call(Some(&ty), op, vec![x], loc)
+    if let (Some(x), Some(y)) = (pa, pb) {
+        if let Some(value) = shift(x, y)? {
+            return Ok(Some(IntImm::new(PrimType::from_dtype(dtype)?, value, 
loc.clone())?.into()));
+        }
+    }
+    if pb.is_some_and(BigInt::is_zero) {
+        return Ok(Some(a.clone()));
+    }
+    Ok(None)
 }
 
+// 
---------------------------------------------------------------------------------------------
+// Math functions (`src/ir/prim/math.cc`)
+
 /// The unary math functions, each in a pair with its `_at` variant, 
implemented by a function
 /// of the expression and the location.
 macro_rules! unary {
@@ -1865,94 +1844,6 @@ unary! {
     };
 }
 
-fn absolute(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
-    let dtype = x.dtype();
-    match dtype.code {
-        support::INT => integer_abs(x, loc),
-        support::FLOAT | support::BFLOAT => match 
x.downcast_ref::<FloatImmObj>() {
-            Some(imm) => {
-                Ok(FloatImm::new(PrimType::from_dtype(dtype)?, 
imm.value.abs(), imm.loc.clone())?
-                    .into())
-            }
-            None => {
-                let ty = x.ty.clone();
-                call(Some(&ty), "prim.fabs", vec![x], loc)
-            }
-        },
-        support::UINT => Ok(x),
-        _ => {
-            let message = format!(
-                "Data type {} not supported for absolute op. Skipping absolute 
op...",
-                support::repr(&x.ty)
-            );
-            Err(Error::new(INTERNAL_ERROR, &message, ""))
-        }
-    }
-}
-
-/// C++ `IntegerAbs(x, loc)` of a signed integer: a constant folds when its 
absolute value is a
-/// value of its type; anything else is `select(x >= 0, x, -x)`.
-fn integer_abs(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
-    let dtype = x.dtype();
-    if let Some(imm) = x.downcast_ref::<IntImmObj>() {
-        let value = match imm.value.is_negative() {
-            true => -imm.value.clone(),
-            false => imm.value.clone(),
-        };
-        if const_fold::is_representable(&value, dtype) {
-            return Ok(IntImm::new(PrimType::from_dtype(dtype)?, value, 
imm.loc.clone())?.into());
-        }
-    }
-    let zero = make_const(PrimType::from_dtype(dtype)?, 0)?;
-    let nonnegative = ge(x.clone(), zero)?;
-    let negated = neg(x.clone())?;
-    Ok(Select::new(nonnegative, x, negated, loc)?.into())
-}
-
-fn infinite(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
-    let dtype = x.dtype();
-    let result = PrimType::bool().with_lanes(support::lanes(dtype)? as u16)?;
-    match dtype.code {
-        support::INT | support::UINT => make_const_at(result, false, loc),
-        support::FLOAT => {
-            let infinity = infinity_at(x.ty(), loc.clone())?;
-            let at_infinity = eq(abs_at(x.clone(), loc.clone())?, infinity)?;
-            let not_nan = logical_not(isnan_at(x, loc)?)?;
-            logical_and(at_infinity, not_nan)
-        }
-        _ => {
-            let message = format!(
-                "Data type {} not supported for finiteness ops. Skipping 
it...",
-                support::repr(&x.ty)
-            );
-            Err(Error::new(INTERNAL_ERROR, &message, ""))
-        }
-    }
-}
-
-fn finite(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
-    let not_infinite = logical_not(isinf_at(x.clone(), loc.clone())?)?;
-    let not_nan = logical_not(isnan_at(x, loc)?)?;
-    logical_and(not_infinite, not_nan)
-}
-
-fn likely_hint(cond: PrimExpr, loc: Location) -> Result<PrimExpr> {
-    if cond.downcast_ref::<IntImmObj>().is_some() {
-        return Ok(cond);
-    }
-    let ty = cond.ty.clone();
-    call(Some(&ty), "prim.likely", vec![cond], loc)
-}
-
-/// The check `TVM_FFI_ICHECK_EQ(dtype.lanes(), 1)` of the limits.
-fn check_scalar(dtype: DLDataType) -> Result<()> {
-    let lanes = support::lanes(dtype)?;
-    match lanes == 1 {
-        true => Ok(()),
-        false => Err(support::compare_failed(INTERNAL_ERROR, "dtype.lanes() == 
1", lanes, 1, "")),
-    }
-}
-
 unlocated! {
     /// Create the largest value of the scalar type `ty` (C++ `max_value`, 
`prim.max_value`).
     ///
@@ -2063,24 +1954,6 @@ pub fn vscale_at(loc: impl Into<Location>) -> 
Result<PrimExpr> {
     call(Some(&ty), "prim.vscale", vec![], loc.into())
 }
 
-/// A function of two matched floats, called with the type of the first (`pow` 
and `fmod`).
-fn float_binary(
-    x: PrimExpr,
-    y: PrimExpr,
-    op: &'static str,
-    what: &str,
-    loc: Location,
-) -> Result<PrimExpr> {
-    let (x, y) = match_types_at(x, y, loc.clone())?;
-    if x.dtype().code != support::FLOAT {
-        let condition = "x.ty().MatchesCode(DLDataTypeCode::kDLFloat)";
-        let message = format!("{what} only applies to float");
-        return Err(support::check_failed(INTERNAL_ERROR, condition, &message));
-    }
-    let ty = x.ty.clone();
-    call(Some(&ty), op, vec![x, y], loc)
-}
-
 unlocated! {
     /// Build `x` to the power `y`, of two matched floats (C++ `pow`, 
`prim._OpPow`).
     ///
@@ -2132,40 +2005,136 @@ pub fn fma_at(
     call(Some(&ty), "prim.fma", vec![x, y.into_owned_expr()?, 
z.into_owned_expr()?], loc.into())
 }
 
-// 
---------------------------------------------------------------------------------------------
-// Intrinsics (`TVM_DECLARE_INTRIN_UNARY` and friends in 
`include/tvm/ir/prim/op.h`)
-
-/// A unary intrinsic `op`, a call typed as its argument. A bfloat16 argument 
is computed in
-/// float32 and cast back; with `float_only`, any argument but a float or a 
bfloat16 is a
-/// `TypeError` (C++ `CheckMathUnaryOpInputDType`).
-fn unary_intrinsic(
-    op: &'static str,
+/// `floor`, `ceil`, `round`, `nearbyint` and `trunc`: an integer or a boolean 
is itself, a float
+/// constant is folded with `fold`, keeping its location, and anything else is 
a call.
+fn rounding(
     x: PrimExpr,
-    float_only: bool,
+    op: &'static str,
+    fold: fn(f64) -> f64,
     loc: Location,
 ) -> Result<PrimExpr> {
     let dtype = x.dtype();
-    if float_only && dtype.code != support::FLOAT && !is_bfloat16(dtype) {
-        let condition = "dtype.code() == DLDataTypeCode::kDLFloat || \
-                         dtype.MatchesElementType(DLDataTypeCode::kDLBfloat, 
16)";
-        let message =
-            format!("{op} only supports floating-point inputs, but got {}", 
support::repr(&x.ty));
-        return Err(support::check_failed(TYPE_ERROR, condition, &message));
+    if matches!(dtype.code, support::INT | support::UINT | support::BOOL) {
+        return Ok(x);
     }
-    if !is_bfloat16(dtype) {
-        let ty = x.ty.clone();
-        return call(Some(&ty), op, vec![x], loc);
+    if let Some(imm) = x.downcast_ref::<FloatImmObj>() {
+        return Ok(
+            FloatImm::new(PrimType::from_dtype(dtype)?, fold(imm.value), 
imm.loc.clone())?.into()
+        );
     }
-    let float32 = match support::is_scalable_vector(dtype) {
-        true => support::scalable_vector(PrimType::float(32).dtype, 
vscale_factor(dtype))?,
-        false => with_lanes(PrimType::float(32).dtype, dtype.lanes),
-    };
-    let float32 = PrimType::from_dtype(float32)?;
-    let x_float32: PrimExpr = Cast::new(float32.clone(), x, 
loc.clone())?.into();
-    let result = call(Some(&float32.into()), op, vec![x_float32], 
loc.clone())?;
-    Ok(Cast::new(PrimType::from_dtype(dtype)?, result, loc)?.into())
+    let ty = x.ty.clone();
+    call(Some(&ty), op, vec![x], loc)
+}
+
+fn absolute(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
+    let dtype = x.dtype();
+    match dtype.code {
+        support::INT => integer_abs(x, loc),
+        support::FLOAT | support::BFLOAT => match 
x.downcast_ref::<FloatImmObj>() {
+            Some(imm) => {
+                Ok(FloatImm::new(PrimType::from_dtype(dtype)?, 
imm.value.abs(), imm.loc.clone())?
+                    .into())
+            }
+            None => {
+                let ty = x.ty.clone();
+                call(Some(&ty), "prim.fabs", vec![x], loc)
+            }
+        },
+        support::UINT => Ok(x),
+        _ => {
+            let message = format!(
+                "Data type {} not supported for absolute op. Skipping absolute 
op...",
+                support::repr(&x.ty)
+            );
+            Err(Error::new(INTERNAL_ERROR, &message, ""))
+        }
+    }
+}
+
+/// C++ `IntegerAbs(x, loc)` of a signed integer: a constant folds when its 
absolute value is a
+/// value of its type; anything else is `select(x >= 0, x, -x)`.
+fn integer_abs(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
+    let dtype = x.dtype();
+    if let Some(imm) = x.downcast_ref::<IntImmObj>() {
+        let value = match imm.value.is_negative() {
+            true => -imm.value.clone(),
+            false => imm.value.clone(),
+        };
+        if const_fold::is_representable(&value, dtype) {
+            return Ok(IntImm::new(PrimType::from_dtype(dtype)?, value, 
imm.loc.clone())?.into());
+        }
+    }
+    let zero = make_const(PrimType::from_dtype(dtype)?, 0)?;
+    let nonnegative = ge(x.clone(), zero)?;
+    let negated = neg(x.clone())?;
+    Ok(Select::new(nonnegative, x, negated, loc)?.into())
 }
 
+fn infinite(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
+    let dtype = x.dtype();
+    let result = PrimType::bool().with_lanes(support::lanes(dtype)? as u16)?;
+    match dtype.code {
+        support::INT | support::UINT => make_const_at(result, false, loc),
+        support::FLOAT => {
+            let infinity = infinity_at(x.ty(), loc.clone())?;
+            let at_infinity = eq(abs_at(x.clone(), loc.clone())?, infinity)?;
+            let not_nan = logical_not(isnan_at(x, loc)?)?;
+            logical_and(at_infinity, not_nan)
+        }
+        _ => {
+            let message = format!(
+                "Data type {} not supported for finiteness ops. Skipping 
it...",
+                support::repr(&x.ty)
+            );
+            Err(Error::new(INTERNAL_ERROR, &message, ""))
+        }
+    }
+}
+
+fn finite(x: PrimExpr, loc: Location) -> Result<PrimExpr> {
+    let not_infinite = logical_not(isinf_at(x.clone(), loc.clone())?)?;
+    let not_nan = logical_not(isnan_at(x, loc)?)?;
+    logical_and(not_infinite, not_nan)
+}
+
+fn likely_hint(cond: PrimExpr, loc: Location) -> Result<PrimExpr> {
+    if cond.downcast_ref::<IntImmObj>().is_some() {
+        return Ok(cond);
+    }
+    let ty = cond.ty.clone();
+    call(Some(&ty), "prim.likely", vec![cond], loc)
+}
+
+/// The check `TVM_FFI_ICHECK_EQ(dtype.lanes(), 1)` of the limits.
+fn check_scalar(dtype: DLDataType) -> Result<()> {
+    let lanes = support::lanes(dtype)?;
+    match lanes == 1 {
+        true => Ok(()),
+        false => Err(support::compare_failed(INTERNAL_ERROR, "dtype.lanes() == 
1", lanes, 1, "")),
+    }
+}
+
+/// A function of two matched floats, called with the type of the first (`pow` 
and `fmod`).
+fn float_binary(
+    x: PrimExpr,
+    y: PrimExpr,
+    op: &'static str,
+    what: &str,
+    loc: Location,
+) -> Result<PrimExpr> {
+    let (x, y) = match_types_at(x, y, loc.clone())?;
+    if x.dtype().code != support::FLOAT {
+        let condition = "x.ty().MatchesCode(DLDataTypeCode::kDLFloat)";
+        let message = format!("{what} only applies to float");
+        return Err(support::check_failed(INTERNAL_ERROR, condition, &message));
+    }
+    let ty = x.ty.clone();
+    call(Some(&ty), op, vec![x, y], loc)
+}
+
+// 
---------------------------------------------------------------------------------------------
+// Intrinsics (`TVM_DECLARE_INTRIN_UNARY` and friends in 
`include/tvm/ir/prim/op.h`)
+
 /// The unary intrinsics; `float_only` marks `TVM_DECLARE_FLOAT_INTRIN_UNARY`.
 macro_rules! unary_intrinsics {
     ($($(#[$doc:meta])* $name:ident / $at:ident => $op:literal, 
$float_only:literal;)+) => {
@@ -2270,6 +2239,37 @@ binary_intrinsics! {
     ldexp / ldexp_at => "prim.ldexp";
 }
 
+/// A unary intrinsic `op`, a call typed as its argument. A bfloat16 argument 
is computed in
+/// float32 and cast back; with `float_only`, any argument but a float or a 
bfloat16 is a
+/// `TypeError` (C++ `CheckMathUnaryOpInputDType`).
+fn unary_intrinsic(
+    op: &'static str,
+    x: PrimExpr,
+    float_only: bool,
+    loc: Location,
+) -> Result<PrimExpr> {
+    let dtype = x.dtype();
+    if float_only && dtype.code != support::FLOAT && !is_bfloat16(dtype) {
+        let condition = "dtype.code() == DLDataTypeCode::kDLFloat || \
+                         dtype.MatchesElementType(DLDataTypeCode::kDLBfloat, 
16)";
+        let message =
+            format!("{op} only supports floating-point inputs, but got {}", 
support::repr(&x.ty));
+        return Err(support::check_failed(TYPE_ERROR, condition, &message));
+    }
+    if !is_bfloat16(dtype) {
+        let ty = x.ty.clone();
+        return call(Some(&ty), op, vec![x], loc);
+    }
+    let float32 = match support::is_scalable_vector(dtype) {
+        true => support::scalable_vector(PrimType::float(32).dtype, 
vscale_factor(dtype))?,
+        false => with_lanes(PrimType::float(32).dtype, dtype.lanes),
+    };
+    let float32 = PrimType::from_dtype(float32)?;
+    let x_float32: PrimExpr = Cast::new(float32.clone(), x, 
loc.clone())?.into();
+    let result = call(Some(&float32.into()), op, vec![x_float32], 
loc.clone())?;
+    Ok(Cast::new(PrimType::from_dtype(dtype)?, result, loc)?.into())
+}
+
 // 
---------------------------------------------------------------------------------------------
 // Data type queries and operand checks the operators share
 
diff --git a/rust/src/ir/stmt.rs b/rust/src/ir/stmt.rs
index d2cc580b1..4ddcb5c05 100644
--- a/rust/src/ir/stmt.rs
+++ b/rust/src/ir/stmt.rs
@@ -572,17 +572,6 @@ impl For {
     }
 }
 
-/// Whether `value` is in the range of the integer type `dtype` (C++ 
`IntImmValueFits`).
-fn int_imm_value_fits(value: &BigInt, dtype: DLDataType) -> Result<bool> {
-    let bits = dtype.bits as u32;
-    if dtype.code == support::UINT {
-        let limit = BigInt::from(1i64).try_shl(bits as i64)?;
-        return Ok(!value.is_negative() && *value < limit);
-    }
-    let limit = BigInt::from(1i64).try_shl(bits as i64 - 1)?;
-    Ok(*value >= -limit.clone() && *value < limit)
-}
-
 // tvm-ffi-stubgen(begin): object/ir.While
 /// Complete: reflected fields fill [32, 48) exactly.
 #[repr(C)]
@@ -900,50 +889,6 @@ impl RegionStmt {
     }
 }
 
-/// The parameters the body of a region of `op` on `args` binds, which the 
operator's
-/// `FRegionGetBodyParams` gives once its validator accepts the arguments
-/// (C++ `GetRegionBodyParams`).
-fn region_body_params(op: &Op, args: &Array<Expr>, attrs: &DictAttrs) -> 
Result<Array<Var>> {
-    let name = op.name.as_str();
-    let Some(get_body_params) = support::NativeHook::of_op(op, 
"FRegionGetBodyParams")? else {
-        let message = format!(
-            "{name} does not support region construction: FRegionGetBodyParams 
is required"
-        );
-        return Err(support::check_failed(tvm_ffi::VALUE_ERROR, 
"IsRegionOp(op)", &message));
-    };
-    let signature = Call::from_fields(
-        UNKNOWN_LOC,
-        MissingType::new(),
-        op.clone(),
-        args.clone(),
-        Some(attrs.clone().into()),
-        Array::from_vec(vec![]),
-    );
-    signature.validate()?;
-    let params: Array<Var> = get_body_params.call_as(&signature)?;
-    let mut definitions = std::collections::HashSet::new();
-    for param in params.iter() {
-        if param.ty.downcast_ref::<MissingTypeObj>().is_some() {
-            let condition = "!param->ty.as<MissingType>().has_value()";
-            let message = format!("FRegionGetBodyParams for {name} must return 
typed variables");
-            return Err(support::check_failed(tvm_ffi::VALUE_ERROR, condition, 
&message));
-        }
-        if !definitions.insert(object_address(param)) {
-            let condition = "definitions.insert(param.get()).second";
-            let message =
-                format!("FRegionGetBodyParams for {name} must return distinct 
definitions");
-            return Err(support::check_failed(tvm_ffi::VALUE_ERROR, condition, 
&message));
-        }
-    }
-    Ok(params)
-}
-
-/// The address of the object `value` refers to, which identifies a definition.
-fn object_address<T: ObjectRefCore>(value: &T) -> usize {
-    // The pointer is only compared, never dereferenced.
-    ObjectArc::as_ptr(ObjectRefCore::data(value)) as usize
-}
-
 // tvm-ffi-stubgen(begin): object/ir.SeqStmt
 /// Complete: reflected fields fill [32, 40) exactly.
 #[repr(C)]
@@ -1002,42 +947,6 @@ impl SeqStmt {
     }
 }
 
-/// `seq` with each nested sequence replaced by its statements and the no-ops 
removed.
-fn flatten(seq: Array<Stmt>) -> Array<Stmt> {
-    let nested_or_no_op =
-        |stmt: &Stmt| stmt.downcast_ref::<SeqStmtObj>().is_some() || 
is_no_op(stmt);
-    if !seq.iter().any(nested_or_no_op) {
-        return seq;
-    }
-    fn append(stmt: Stmt, flattened: &mut Vec<Stmt>) {
-        match stmt.downcast_ref::<SeqStmtObj>() {
-            Some(nested) => nested.seq.iter().for_each(|child| 
append(child.clone(), flattened)),
-            None if is_no_op(&stmt) => {}
-            None => flattened.push(stmt),
-        }
-    }
-    let mut flattened = Vec::new();
-    seq.iter().for_each(|stmt| append(stmt.clone(), &mut flattened));
-    Array::from_vec(flattened)
-}
-
-/// Whether `stmt` is `Evaluate(0)`, the no-op a sequence drops (C++ 
`IsSeqStmtNoOp`).
-fn is_no_op(stmt: &Stmt) -> bool {
-    stmt.downcast_ref::<EvaluateObj>()
-        .and_then(|evaluate| evaluate.value.downcast_ref::<IntImmObj>())
-        .is_some_and(|imm| imm.value == BigInt::from(0i64))
-}
-
-/// A statement as a body, as C++ converts a `Stmt` to a `SeqStmt`: a sequence 
as it is, any other
-/// statement as the sequence of it at its location (C++ `SeqStmt(stmt)`).
-fn into_body(stmt: Stmt) -> Result<SeqStmt> {
-    if stmt.downcast_ref::<SeqStmtObj>().is_some() {
-        return Ok(stmt.downcast().expect("a SeqStmt"));
-    }
-    let loc = stmt.loc.clone();
-    Ok(SeqStmt::from_fields(loc, flatten(Array::from_vec(vec![stmt]))))
-}
-
 // tvm-ffi-stubgen(begin): object/ir.If
 /// Complete: reflected fields fill [32, 56) exactly.
 #[repr(C)]
@@ -1326,3 +1235,97 @@ tvm_ffi::unsafe_impl_object_ref_upcast!(Continue => 
Stmt);
 // tvm-ffi-stubgen(end)
 
 // `ir.Continue` stores its location as given: the generated `Continue::new` 
is the same.
+
+// 
---------------------------------------------------------------------------------------------
+// Helpers
+
+/// Whether `value` is in the range of the integer type `dtype` (C++ 
`IntImmValueFits`).
+fn int_imm_value_fits(value: &BigInt, dtype: DLDataType) -> Result<bool> {
+    let bits = dtype.bits as u32;
+    if dtype.code == support::UINT {
+        let limit = BigInt::from(1i64).try_shl(bits as i64)?;
+        return Ok(!value.is_negative() && *value < limit);
+    }
+    let limit = BigInt::from(1i64).try_shl(bits as i64 - 1)?;
+    Ok(*value >= -limit.clone() && *value < limit)
+}
+
+/// The parameters the body of a region of `op` on `args` binds, which the 
operator's
+/// `FRegionGetBodyParams` gives once its validator accepts the arguments
+/// (C++ `GetRegionBodyParams`).
+fn region_body_params(op: &Op, args: &Array<Expr>, attrs: &DictAttrs) -> 
Result<Array<Var>> {
+    let name = op.name.as_str();
+    let Some(get_body_params) = support::NativeHook::of_op(op, 
"FRegionGetBodyParams")? else {
+        let message = format!(
+            "{name} does not support region construction: FRegionGetBodyParams 
is required"
+        );
+        return Err(support::check_failed(tvm_ffi::VALUE_ERROR, 
"IsRegionOp(op)", &message));
+    };
+    let signature = Call::from_fields(
+        UNKNOWN_LOC,
+        MissingType::new(),
+        op.clone(),
+        args.clone(),
+        Some(attrs.clone().into()),
+        Array::from_vec(vec![]),
+    );
+    signature.validate()?;
+    let params: Array<Var> = get_body_params.call_as(&signature)?;
+    let mut definitions = std::collections::HashSet::new();
+    for param in params.iter() {
+        if param.ty.downcast_ref::<MissingTypeObj>().is_some() {
+            let condition = "!param->ty.as<MissingType>().has_value()";
+            let message = format!("FRegionGetBodyParams for {name} must return 
typed variables");
+            return Err(support::check_failed(tvm_ffi::VALUE_ERROR, condition, 
&message));
+        }
+        if !definitions.insert(object_address(param)) {
+            let condition = "definitions.insert(param.get()).second";
+            let message =
+                format!("FRegionGetBodyParams for {name} must return distinct 
definitions");
+            return Err(support::check_failed(tvm_ffi::VALUE_ERROR, condition, 
&message));
+        }
+    }
+    Ok(params)
+}
+
+/// The address of the object `value` refers to, which identifies a definition.
+fn object_address<T: ObjectRefCore>(value: &T) -> usize {
+    // The pointer is only compared, never dereferenced.
+    ObjectArc::as_ptr(ObjectRefCore::data(value)) as usize
+}
+
+/// `seq` with each nested sequence replaced by its statements and the no-ops 
removed.
+fn flatten(seq: Array<Stmt>) -> Array<Stmt> {
+    let nested_or_no_op =
+        |stmt: &Stmt| stmt.downcast_ref::<SeqStmtObj>().is_some() || 
is_no_op(stmt);
+    if !seq.iter().any(nested_or_no_op) {
+        return seq;
+    }
+    fn append(stmt: Stmt, flattened: &mut Vec<Stmt>) {
+        match stmt.downcast_ref::<SeqStmtObj>() {
+            Some(nested) => nested.seq.iter().for_each(|child| 
append(child.clone(), flattened)),
+            None if is_no_op(&stmt) => {}
+            None => flattened.push(stmt),
+        }
+    }
+    let mut flattened = Vec::new();
+    seq.iter().for_each(|stmt| append(stmt.clone(), &mut flattened));
+    Array::from_vec(flattened)
+}
+
+/// Whether `stmt` is `Evaluate(0)`, the no-op a sequence drops (C++ 
`IsSeqStmtNoOp`).
+fn is_no_op(stmt: &Stmt) -> bool {
+    stmt.downcast_ref::<EvaluateObj>()
+        .and_then(|evaluate| evaluate.value.downcast_ref::<IntImmObj>())
+        .is_some_and(|imm| imm.value == BigInt::from(0i64))
+}
+
+/// A statement as a body, as C++ converts a `Stmt` to a `SeqStmt`: a sequence 
as it is, any other
+/// statement as the sequence of it at its location (C++ `SeqStmt(stmt)`).
+fn into_body(stmt: Stmt) -> Result<SeqStmt> {
+    if stmt.downcast_ref::<SeqStmtObj>().is_some() {
+        return Ok(stmt.downcast().expect("a SeqStmt"));
+    }
+    let loc = stmt.loc.clone();
+    Ok(SeqStmt::from_fields(loc, flatten(Array::from_vec(vec![stmt]))))
+}
diff --git a/rust/src/ir/support.rs b/rust/src/ir/support.rs
index 1e2e0e928..38d418d34 100644
--- a/rust/src/ir/support.rs
+++ b/rust/src/ir/support.rs
@@ -230,15 +230,6 @@ pub(crate) fn prim_dtype(expr: &(impl AsRef<Expr> + 
?Sized)) -> Result<DLDataTyp
     }
 }
 
-fn not_a_prim_expr(expr: &Expr) -> Error {
-    let message = format!(
-        "Expected `ir.PrimExpr` but got `{}[ty={}]`",
-        type_key(AnyView::from(expr).type_index()),
-        type_key(AnyView::from(&expr.ty).type_index()),
-    );
-    Error::new(TYPE_ERROR, &message, "")
-}
-
 /// The lanes of a dtype as C++ decodes them: negative for a scalable vector.
 pub(crate) fn encoded_lanes(dtype: DLDataType) -> i16 {
     dtype.lanes as i16
@@ -281,6 +272,15 @@ pub(crate) fn scalable_vector(dtype: DLDataType, factor: 
i32) -> Result<DLDataTy
     Ok(DLDataType { code: dtype.code, bits: dtype.bits, lanes: (-factor) as 
u16 })
 }
 
+fn not_a_prim_expr(expr: &Expr) -> Error {
+    let message = format!(
+        "Expected `ir.PrimExpr` but got `{}[ty={}]`",
+        type_key(AnyView::from(expr).type_index()),
+        type_key(AnyView::from(&expr.ty).type_index()),
+    );
+    Error::new(TYPE_ERROR, &message, "")
+}
+
 // 
---------------------------------------------------------------------------------------------
 // Sequences
 
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 356e3ac5e..a371a2502 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -160,6 +160,27 @@
 //! uv run --no-sync cargo test --manifest-path rust/Cargo.toml
 //! ```
 
+pub mod backend;
+pub mod codegen;
+pub mod ir;
+mod r#macro;
+pub mod runtime;
+pub mod script;
+pub mod sym;
+pub mod target;
+pub mod tirx;
+
+pub use tvm_ffi;
+
+// 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. The module keeps
+// the symbol, which `#[no_mangle]` exports from wherever it is, out of the 
crate's documentation.
+#[doc(hidden)]
+pub mod exports {
+    tvm_ffi::tvm_ffi_dll_export_typed_func!(tirx_register_ir, 
tvm_ffi::run_static_init_blocks);
+}
+
 // 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`
@@ -201,24 +222,3 @@ impl Drop for ExitOnDrop {
         let _ = self.0.call_packed(&[]);
     }
 }
-
-pub mod backend;
-pub mod codegen;
-pub mod ir;
-mod r#macro;
-pub mod runtime;
-pub mod script;
-pub mod sym;
-pub mod target;
-pub mod tirx;
-
-pub use tvm_ffi;
-
-// 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. The module keeps
-// the symbol, which `#[no_mangle]` exports from wherever it is, out of the 
crate's documentation.
-#[doc(hidden)]
-pub mod exports {
-    tvm_ffi::tvm_ffi_dll_export_typed_func!(tirx_register_ir, 
tvm_ffi::run_static_init_blocks);
-}
diff --git a/rust/src/sym/constraint.rs b/rust/src/sym/constraint.rs
index 687c55121..2b659af70 100644
--- a/rust/src/sym/constraint.rs
+++ b/rust/src/sym/constraint.rs
@@ -286,6 +286,16 @@ pub struct ConstraintContextObj {
     constraints: UnsafeCell<Array<Constraint>>,
 }
 
+// These verify the Rust mirror against the matching x86_64 header layout, not
+// an arbitrary loaded binary's hidden fields. Stub generation cannot certify 
them.
+const _: () = {
+    assert!(size_of::<ConstraintContextObj>() == 48);
+    assert!(align_of::<ConstraintContextObj>() == 8);
+    assert!(offset_of!(ConstraintContextObj, parent) == 24);
+    assert!(offset_of!(ConstraintContextObj, parent_prefix_size) == 32);
+    assert!(offset_of!(ConstraintContextObj, constraints) == 40);
+};
+
 /// A context of constraints: the premises in effect at a point of a 
traversal, ancestors' first.
 ///
 /// Clones share the context's identity and later appends; use
@@ -296,46 +306,6 @@ pub struct ConstraintContext {
     base: ObjectArc<ConstraintContextObj>,
 }
 
-impl ConstraintContext {
-    /// Create an empty root context, with the complete native-compatible 
layout (C++
-    /// `ConstraintContext()`).
-    pub fn new() -> Self {
-        Self::allocate(None, 0)
-    }
-
-    /// Create an empty child that captures the first `parent_prefix_size` 
local entries of
-    /// `parent`.
-    ///
-    /// Earlier ancestor prefixes are inherited unchanged; later appends to 
the parent are excluded.
-    ///
-    /// # Errors
-    ///
-    /// Returns an error if `parent_prefix_size` is negative or larger than 
the parent's local size.
-    pub fn with_parent_prefix_size(parent: Self, parent_prefix_size: i64) -> 
Result<Self> {
-        if parent_prefix_size < 0 || parent_prefix_size > parent.local_size() {
-            return Err(Error::new(
-                VALUE_ERROR,
-                "Constraint context parent local prefix is out of bounds",
-                "",
-            ));
-        }
-        Ok(Self::allocate(Some(parent.base), parent_prefix_size))
-    }
-
-    fn allocate(parent: Option<ObjectArc<ConstraintContextObj>>, prefix: i64) 
-> Self {
-        Self {
-            base: ObjectArc::new(ConstraintContextObj {
-                base: Object::new(),
-                parent,
-                parent_prefix_size: prefix,
-                // Start with an empty Rust array. Native Append owns all 
growth;
-                // never install a nonempty Rust allocation in this mutable 
field.
-                constraints: UnsafeCell::new(Array::default()),
-            }),
-        }
-    }
-}
-
 impl ConstraintContextObj {
     /// Append `constraint` to the local entries 
(`sym.ConstraintContextAppend`).
     ///
@@ -397,12 +367,42 @@ impl ConstraintContextObj {
     }
 }
 
-// These verify the Rust mirror against the matching x86_64 header layout, not
-// an arbitrary loaded binary's hidden fields. Stub generation cannot certify 
them.
-const _: () = {
-    assert!(size_of::<ConstraintContextObj>() == 48);
-    assert!(align_of::<ConstraintContextObj>() == 8);
-    assert!(offset_of!(ConstraintContextObj, parent) == 24);
-    assert!(offset_of!(ConstraintContextObj, parent_prefix_size) == 32);
-    assert!(offset_of!(ConstraintContextObj, constraints) == 40);
-};
+impl ConstraintContext {
+    /// Create an empty root context, with the complete native-compatible 
layout (C++
+    /// `ConstraintContext()`).
+    pub fn new() -> Self {
+        Self::allocate(None, 0)
+    }
+
+    /// Create an empty child that captures the first `parent_prefix_size` 
local entries of
+    /// `parent`.
+    ///
+    /// Earlier ancestor prefixes are inherited unchanged; later appends to 
the parent are excluded.
+    ///
+    /// # Errors
+    ///
+    /// Returns an error if `parent_prefix_size` is negative or larger than 
the parent's local size.
+    pub fn with_parent_prefix_size(parent: Self, parent_prefix_size: i64) -> 
Result<Self> {
+        if parent_prefix_size < 0 || parent_prefix_size > parent.local_size() {
+            return Err(Error::new(
+                VALUE_ERROR,
+                "Constraint context parent local prefix is out of bounds",
+                "",
+            ));
+        }
+        Ok(Self::allocate(Some(parent.base), parent_prefix_size))
+    }
+
+    fn allocate(parent: Option<ObjectArc<ConstraintContextObj>>, prefix: i64) 
-> Self {
+        Self {
+            base: ObjectArc::new(ConstraintContextObj {
+                base: Object::new(),
+                parent,
+                parent_prefix_size: prefix,
+                // Start with an empty Rust array. Native Append owns all 
growth;
+                // never install a nonempty Rust allocation in this mutable 
field.
+                constraints: UnsafeCell::new(Array::default()),
+            }),
+        }
+    }
+}
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index 24a890618..6a236ebb2 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -198,18 +198,6 @@ pub fn logaddexp_at(
     log(add(exp(a)?, exp(b)?)?)
 }
 
-/// Whether `dtype` is a scalar 64-bit integer, which a pointer reinterprets 
as.
-fn is_scalar_int64(dtype: DLDataType) -> bool {
-    support::is_scalar(dtype)
-        && dtype.bits == 64
-        && matches!(dtype.code, support::INT | support::UINT)
-}
-
-/// C++ `PrimType::StorageBytes()`.
-fn storage_bytes(dtype: DLDataType) -> Result<u64> {
-    Ok((u64::from(dtype.bits) * support::lanes(dtype)? as u64).div_ceil(8))
-}
-
 /// Reinterpret `value` bit for bit as the primitive or pointer type `ty`
 /// (C++ `tirx::reinterpret(target_ty, value, loc)`, `tirx.reinterpret`).
 ///
@@ -375,3 +363,15 @@ pub fn gpu_thread_return_at(loc: impl Into<Location>) -> 
Result<PrimExpr> {
     let loc: Location = loc.into();
     
tvm_ffi::cached_global_func!("tirx.gpu_thread_return").call_tuple((&loc,))?.try_into()
 }
+
+/// Whether `dtype` is a scalar 64-bit integer, which a pointer reinterprets 
as.
+fn is_scalar_int64(dtype: DLDataType) -> bool {
+    support::is_scalar(dtype)
+        && dtype.bits == 64
+        && matches!(dtype.code, support::INT | support::UINT)
+}
+
+/// C++ `PrimType::StorageBytes()`.
+fn storage_bytes(dtype: DLDataType) -> Result<u64> {
+    Ok((u64::from(dtype.bits) * support::lanes(dtype)? as u64).div_ceil(8))
+}
diff --git a/rust/src/tirx/tensor.rs b/rust/src/tirx/tensor.rs
index 994682266..e076249ef 100644
--- a/rust/src/tirx/tensor.rs
+++ b/rust/src/tirx/tensor.rs
@@ -163,24 +163,6 @@ unsafe impl tvm_ffi::object::ImmutableObject for 
TensorTypeObj {}
 tvm_ffi::unsafe_impl_object_ref_upcast!(TensorType => Type);
 // tvm-ffi-stubgen(end)
 
-/// The runtime's allocation alignment (C++ `runtime::kAllocAlignment`, which 
a build may set),
-/// read from the default the reflection registers for 
`TensorType::data_alignment`.
-fn alloc_alignment() -> Result<i32> {
-    static ALLOC_ALIGNMENT: std::sync::OnceLock<i32> = 
std::sync::OnceLock::new();
-    if let Some(alignment) = ALLOC_ALIGNMENT.get() {
-        return Ok(*alignment);
-    }
-    let Some(default) = support::field_default(TensorTypeObj::type_index(), 
"data_alignment")
-    else {
-        let message = "tirx.TensorType registers no default data_alignment";
-        return Err(tvm_ffi::Error::new(tvm_ffi::INTERNAL_ERROR, message, ""));
-    };
-    let alignment = i32::try_from(i64::try_from(default)?).map_err(|_| {
-        tvm_ffi::Error::new(tvm_ffi::INTERNAL_ERROR, "the default 
data_alignment exceeds i32", "")
-    })?;
-    Ok(*ALLOC_ALIGNMENT.get_or_init(|| alignment))
-}
-
 impl TensorType {
     /// Create the type of a tensor of `shape` and `dtype` in `scope`, aligned 
to `data_alignment`
     /// bytes (the runtime's allocation alignment when 0 or less), with 
`layout` if given (C++
@@ -220,6 +202,20 @@ impl TensorType {
     }
 }
 
+/// A signature descriptor ([`sig::ArgType`]) of a `Var`, not a subclass, 
whose type is a
+/// [`TensorType`] (C++ `tirx::TensorVar`).
+pub struct TensorVar;
+
+impl sig::ArgType for TensorVar {
+    fn check(value: &TVMFFIAny) -> bool {
+        sig::var_of_type::<TensorTypeObj>(value)
+    }
+
+    fn type_str() -> std::string::String {
+        crate::ir::expr::VarObj::TYPE_KEY.into()
+    }
+}
+
 /// Return whether the tensor variable `tensor` is a scalar: one element, the 
default alignment and
 /// a trivial layout; `alloc_or_decl` is true for `alloc_scalar`, false for 
`decl_scalar`
 /// (`tirx.TensorIsScalar`).
@@ -233,16 +229,20 @@ pub fn tensor_is_scalar(tensor: impl IntoObjectRef<Var>, 
alloc_or_decl: bool) ->
         .try_into()
 }
 
-/// A signature descriptor ([`sig::ArgType`]) of a `Var`, not a subclass, 
whose type is a
-/// [`TensorType`] (C++ `tirx::TensorVar`).
-pub struct TensorVar;
-
-impl sig::ArgType for TensorVar {
-    fn check(value: &TVMFFIAny) -> bool {
-        sig::var_of_type::<TensorTypeObj>(value)
-    }
-
-    fn type_str() -> std::string::String {
-        crate::ir::expr::VarObj::TYPE_KEY.into()
+/// The runtime's allocation alignment (C++ `runtime::kAllocAlignment`, which 
a build may set),
+/// read from the default the reflection registers for 
`TensorType::data_alignment`.
+fn alloc_alignment() -> Result<i32> {
+    static ALLOC_ALIGNMENT: std::sync::OnceLock<i32> = 
std::sync::OnceLock::new();
+    if let Some(alignment) = ALLOC_ALIGNMENT.get() {
+        return Ok(*alignment);
     }
+    let Some(default) = support::field_default(TensorTypeObj::type_index(), 
"data_alignment")
+    else {
+        let message = "tirx.TensorType registers no default data_alignment";
+        return Err(tvm_ffi::Error::new(tvm_ffi::INTERNAL_ERROR, message, ""));
+    };
+    let alignment = i32::try_from(i64::try_from(default)?).map_err(|_| {
+        tvm_ffi::Error::new(tvm_ffi::INTERNAL_ERROR, "the default 
data_alignment exceeds i32", "")
+    })?;
+    Ok(*ALLOC_ALIGNMENT.get_or_init(|| alignment))
 }

Reply via email to