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

Seven-Streams 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 c9c3124e2 [FIX][RUST] Match the C++ min/max folding and messages, and 
regenerate stale bindings (#44)
c9c3124e2 is described below

commit c9c3124e256b81f9ff0f9318e4491a23693de961
Author: Linzhang Li <[email protected]>
AuthorDate: Sat Oct 24 13:35:39 2026 -0400

    [FIX][RUST] Match the C++ min/max folding and messages, and regenerate 
stale bindings (#44)
    
    ## Summary
    
    On main, two Rust tests fail and the stubgen check reports stale
    bindings. Each comes from a C++
    change that merged next to #36 and that the Rust side did not follow.
    
    - `prim::op::binary_operators_agree_with_cpp` fails with
    `prim._OpMin: 106 of 8281 cases disagree`. #31 made the C++ float
    min/max folding keep a NaN and
      take the second operand of two equal ones:
    
      ```cpp
    if (fa && fb) return std::isnan(fa->value) || fa->value < fb->value ? a
    : b;
      ```
    
    The Rust folding still followed `std::min`, which keeps the first of two
    unordered values, so
    `min(0, nan)` folded to `0` and `min(0.0, -0.0)` to `0.0`, where C++
    gives `nan` and `-0.0`.
    `min` and `max` now use the same condition and return the operand, as
    C++ does.
    - `op::reinterpret_agrees_with_cpp`: #35 renamed `PtrTypeNode` to
    `PtrTypeObj`. The errors of
    `tirx.reinterpret` quote the C++ check (`target_ty.as<PtrTypeObj>()`),
    and the four Rust
      messages that follow them still said `PtrTypeNode`.
    - `tvm-ffi-stubgen rust/src --target rust --dlls
    build/lib/libtvm_tirx_compiler.so --check`
    reports `rust/src/sym.rs` and `rust/src/tirx.rs` stale: the crate has no
    bindings for the types
    #38 registers. They are regenerated with the stubgen of the pinned
    tvm-ffi (830f2eb), not edited
      by hand:
    - `sym::Constraint` and its subclasses `BindingConstraint`,
    `PredicateConstraint` and
    `RangeConstraint`, whose fields are complete, so Rust can construct
    them;
      - `sym::ConstraintContext`, opaque;
    - `tirx::StructuralMutatorWithConstraintContext` and
    `StructuralVisitorWithConstraintContext`,
        opaque, whose `constraints` field is read through the C ABI getter.
---
 rust/src/ir/prim/const_fold.rs |  11 +-
 rust/src/sym.rs                | 245 +++++++++++++++++++++++++++++++++++++++++
 rust/src/tirx.rs               |  83 ++++++++++++++
 rust/src/tirx/op.rs            |   8 +-
 4 files changed, 338 insertions(+), 9 deletions(-)

diff --git a/rust/src/ir/prim/const_fold.rs b/rust/src/ir/prim/const_fold.rs
index c0f3004b8..57b15a73a 100644
--- a/rust/src/ir/prim/const_fold.rs
+++ b/rust/src/ir/prim/const_fold.rs
@@ -403,15 +403,16 @@ pub(super) fn floormod(a: &Expr, b: &Expr) -> Folded {
     Ok(None)
 }
 
-/// `TryConstFold<Min>`: two constants, or an operand with itself. Floats 
compare as `std::min`
-/// does, keeping the first of two unordered values.
+/// `TryConstFold<Min>`: two constants, or an operand with itself. Of two 
floats, the first is kept
+/// when it is NaN or less than the second, and the second otherwise: a NaN in 
either operand is
+/// the result, and of two equal values (`0.0` and `-0.0`) the second is.
 pub(super) fn min(a: &Expr, b: &Expr) -> Folded {
     let dtype = support::prim_dtype(a)?;
     if let (Some(x), Some(y)) = (int(a), int(b)) {
         return int_imm(dtype, x.min(y).clone());
     }
     if let (Some(x), Some(y)) = (float(a), float(b)) {
-        return float_imm(dtype, if y < x { y } else { x });
+        return keep(if x.is_nan() || x < y { a } else { b });
     }
     if a.same_as(b) {
         return keep(a);
@@ -419,14 +420,14 @@ pub(super) fn min(a: &Expr, b: &Expr) -> Folded {
     Ok(None)
 }
 
-/// `TryConstFold<Max>`, as [`min`] (`std::max` keeps the first of two 
unordered values).
+/// `TryConstFold<Max>`, as [`min`]: the first float is kept when it is NaN or 
greater.
 pub(super) fn max(a: &Expr, b: &Expr) -> Folded {
     let dtype = support::prim_dtype(a)?;
     if let (Some(x), Some(y)) = (int(a), int(b)) {
         return int_imm(dtype, x.max(y).clone());
     }
     if let (Some(x), Some(y)) = (float(a), float(b)) {
-        return float_imm(dtype, if x < y { y } else { x });
+        return keep(if x.is_nan() || x > y { a } else { b });
     }
     if a.same_as(b) {
         return keep(a);
diff --git a/rust/src/sym.rs b/rust/src/sym.rs
index bad9310af..076b2ebeb 100644
--- a/rust/src/sym.rs
+++ b/rust/src/sym.rs
@@ -37,4 +37,249 @@ pub use int_set::*;
 pub use iter_affine_map::*;
 
 // tvm-ffi-stubgen(begin): import-section
+use crate::ir::expr::Expr;
+use crate::ir::expr::Range;
+use crate::ir::expr::Var;
+use std::ops::Deref;
+use tvm_ffi::Object;
+use tvm_ffi::ObjectArc;
+use tvm_ffi::ShallowCopy;
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/sym.Constraint
+/// Complete: reflected fields fill [24, 24) exactly.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "sym.Constraint"]
+pub struct ConstraintObj {
+    base: Object,
+}
+
+const _: () = {
+    assert!(::core::mem::size_of::<ConstraintObj>() == 24);
+    assert!(::core::mem::align_of::<ConstraintObj>() == 8);
+};
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct Constraint {
+    base: ObjectArc<ConstraintObj>,
+}
+
+impl Deref for Constraint {
+    type Target = ConstraintObj;
+    fn deref(&self) -> &ConstraintObj {
+        &self.base
+    }
+}
+
+impl ConstraintObj {
+    pub fn new() -> Self {
+        let base = Object::new();
+        Self { base }
+    }
+}
+
+impl Constraint {
+    /// Lossless complete-field allocation.
+    pub fn new() -> Self {
+        let obj = ConstraintObj::new();
+        Self { base: ObjectArc::new(obj) }
+    }
+}
+
+impl ShallowCopy for ConstraintObj {
+    fn shallow_copy(&self) -> Self {
+        Self { base: ShallowCopy::shallow_copy(&self.base) }
+    }
+}
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/sym.BindingConstraint
+/// Complete: reflected fields fill [24, 40) exactly.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "sym.BindingConstraint"]
+#[type_final]
+pub struct BindingConstraintObj {
+    base: ConstraintObj,
+    pub var: Var,
+    pub value: Expr,
+}
+
+const _: () = {
+    assert!(::core::mem::size_of::<BindingConstraintObj>() == 40);
+    assert!(::core::mem::align_of::<BindingConstraintObj>() == 8);
+};
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct BindingConstraint {
+    base: ObjectArc<BindingConstraintObj>,
+}
+
+impl Deref for BindingConstraint {
+    type Target = BindingConstraintObj;
+    fn deref(&self) -> &BindingConstraintObj {
+        &self.base
+    }
+}
+
+impl Deref for BindingConstraintObj {
+    type Target = ConstraintObj;
+    fn deref(&self) -> &ConstraintObj {
+        &self.base
+    }
+}
+
+impl BindingConstraintObj {
+    pub(crate) fn new(var: Var, value: Expr) -> Self {
+        let base = ConstraintObj::new();
+        Self { base, var, value }
+    }
+}
+
+impl BindingConstraint {
+    /// Lossless complete-field allocation.
+    pub fn new(var: impl Into<Var>, value: impl Into<Expr>) -> Self {
+        let obj = BindingConstraintObj::new(var.into(), value.into());
+        Self { base: ObjectArc::new(obj) }
+    }
+}
+
+tvm_ffi::impl_object_upcast!(BindingConstraint => Constraint);
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/sym.ConstraintContext
+/// Opaque: bytes [24, 48) of [24, 48) are not accounted for by reflected 
fields. Fields are read through the C ABI getters.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "sym.ConstraintContext"]
+#[type_final]
+pub struct ConstraintContextObj {
+    base: Object,
+}
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct ConstraintContext {
+    base: ObjectArc<ConstraintContextObj>,
+}
+
+impl Deref for ConstraintContext {
+    type Target = ConstraintContextObj;
+    fn deref(&self) -> &ConstraintContextObj {
+        &self.base
+    }
+}
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/sym.PredicateConstraint
+/// Complete: reflected fields fill [24, 40) exactly (alignment padding [33, 
40)).
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "sym.PredicateConstraint"]
+#[type_final]
+pub struct PredicateConstraintObj {
+    base: ConstraintObj,
+    pub condition: Expr,
+    pub assume_always: bool,
+}
+
+const _: () = {
+    assert!(::core::mem::size_of::<PredicateConstraintObj>() == 40);
+    assert!(::core::mem::align_of::<PredicateConstraintObj>() == 8);
+};
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct PredicateConstraint {
+    base: ObjectArc<PredicateConstraintObj>,
+}
+
+impl Deref for PredicateConstraint {
+    type Target = PredicateConstraintObj;
+    fn deref(&self) -> &PredicateConstraintObj {
+        &self.base
+    }
+}
+
+impl Deref for PredicateConstraintObj {
+    type Target = ConstraintObj;
+    fn deref(&self) -> &ConstraintObj {
+        &self.base
+    }
+}
+
+impl PredicateConstraintObj {
+    pub(crate) fn new(condition: Expr, assume_always: bool) -> Self {
+        let base = ConstraintObj::new();
+        Self { base, condition, assume_always }
+    }
+}
+
+impl PredicateConstraint {
+    /// Lossless complete-field allocation.
+    pub fn new(condition: impl Into<Expr>, assume_always: bool) -> Self {
+        let obj = PredicateConstraintObj::new(condition.into(), assume_always);
+        Self { base: ObjectArc::new(obj) }
+    }
+}
+
+tvm_ffi::impl_object_upcast!(PredicateConstraint => Constraint);
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/sym.RangeConstraint
+/// Complete: reflected fields fill [24, 40) exactly.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "sym.RangeConstraint"]
+#[type_final]
+pub struct RangeConstraintObj {
+    base: ConstraintObj,
+    pub var: Var,
+    pub range: Range,
+}
+
+const _: () = {
+    assert!(::core::mem::size_of::<RangeConstraintObj>() == 40);
+    assert!(::core::mem::align_of::<RangeConstraintObj>() == 8);
+};
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct RangeConstraint {
+    base: ObjectArc<RangeConstraintObj>,
+}
+
+impl Deref for RangeConstraint {
+    type Target = RangeConstraintObj;
+    fn deref(&self) -> &RangeConstraintObj {
+        &self.base
+    }
+}
+
+impl Deref for RangeConstraintObj {
+    type Target = ConstraintObj;
+    fn deref(&self) -> &ConstraintObj {
+        &self.base
+    }
+}
+
+impl RangeConstraintObj {
+    pub(crate) fn new(var: Var, range: Range) -> Self {
+        let base = ConstraintObj::new();
+        Self { base, var, range }
+    }
+}
+
+impl RangeConstraint {
+    /// Lossless complete-field allocation.
+    pub fn new(var: impl Into<Var>, range: impl Into<Range>) -> Self {
+        let obj = RangeConstraintObj::new(var.into(), range.into());
+        Self { base: ObjectArc::new(obj) }
+    }
+}
+
+tvm_ffi::impl_object_upcast!(RangeConstraint => Constraint);
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/tirx.rs b/rust/src/tirx.rs
index 0b7173f73..a60b7c25b 100644
--- a/rust/src/tirx.rs
+++ b/rust/src/tirx.rs
@@ -50,4 +50,87 @@ pub use thread_scope::*;
 pub use r#type::*;
 
 // tvm-ffi-stubgen(begin): import-section
+use crate::sym::ConstraintContext;
+use std::ops::Deref;
+use tvm_ffi::FieldGetter;
+use tvm_ffi::Object;
+use tvm_ffi::ObjectArc;
+use tvm_ffi::ObjectCore;
+use tvm_ffi::Result;
+
+/// Header-only stand-in for the builtin `ffi.StructuralMutator`; it only 
carries the ancestor depth.
+#[allow(dead_code)]
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "ffi.StructuralMutator"]
+struct FfiStructuralMutatorObj {
+    base: Object,
+}
+
+/// Header-only stand-in for the builtin `ffi.StructuralVisitor`; it only 
carries the ancestor depth.
+#[allow(dead_code)]
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "ffi.StructuralVisitor"]
+struct FfiStructuralVisitorObj {
+    base: Object,
+}
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/tirx.StructuralMutatorWithConstraintContext
+/// Opaque: parent 'ffi.StructuralMutator' is opaque (no-mirror). Fields are 
read through the C ABI getters.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "tirx.StructuralMutatorWithConstraintContext"]
+pub struct StructuralMutatorWithConstraintContextObj {
+    base: FfiStructuralMutatorObj,
+}
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct StructuralMutatorWithConstraintContext {
+    base: ObjectArc<StructuralMutatorWithConstraintContextObj>,
+}
+
+impl Deref for StructuralMutatorWithConstraintContext {
+    type Target = StructuralMutatorWithConstraintContextObj;
+    fn deref(&self) -> &StructuralMutatorWithConstraintContextObj {
+        &self.base
+    }
+}
+
+impl StructuralMutatorWithConstraintContextObj {
+    pub fn constraints(&self) -> Result<ConstraintContext> {
+        FieldGetter::new(Self::type_index(), "constraints")?.get(self)
+    }
+}
+// tvm-ffi-stubgen(end)
+
+// tvm-ffi-stubgen(begin): object/tirx.StructuralVisitorWithConstraintContext
+/// Opaque: parent 'ffi.StructuralVisitor' is opaque (no-mirror). Fields are 
read through the C ABI getters.
+#[repr(C)]
+#[derive(tvm_ffi::derive::Object)]
+#[type_key = "tirx.StructuralVisitorWithConstraintContext"]
+pub struct StructuralVisitorWithConstraintContextObj {
+    base: FfiStructuralVisitorObj,
+}
+
+#[repr(C)]
+#[derive(tvm_ffi::derive::ObjectRef, Clone)]
+pub struct StructuralVisitorWithConstraintContext {
+    base: ObjectArc<StructuralVisitorWithConstraintContextObj>,
+}
+
+impl Deref for StructuralVisitorWithConstraintContext {
+    type Target = StructuralVisitorWithConstraintContextObj;
+    fn deref(&self) -> &StructuralVisitorWithConstraintContextObj {
+        &self.base
+    }
+}
+
+impl StructuralVisitorWithConstraintContextObj {
+    pub fn constraints(&self) -> Result<ConstraintContext> {
+        FieldGetter::new(Self::type_index(), "constraints")?.get(self)
+    }
+}
 // tvm-ffi-stubgen(end)
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index d82bd0d69..3ce71436a 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -221,7 +221,7 @@ pub fn reinterpret_at(
                 "String reinterpret requires a pointer target, but got {}",
                 support::repr(&ty)
             );
-            return type_error("target_ty.as<PtrTypeNode>()", message);
+            return type_error("target_ty.as<PtrTypeObj>()", message);
         }
     } else if let Some(target) = ty.as_node::<PrimTypeObj>() {
         let target_dtype = target.dtype;
@@ -261,7 +261,7 @@ pub fn reinterpret_at(
                     "Reinterpret source must be PrimType or PtrType, but got 
{}",
                     support::repr(&value.ty)
                 );
-                return type_error("value->ty.as<PtrTypeNode>()", message);
+                return type_error("value->ty.as<PtrTypeObj>()", message);
             }
             if !is_scalar_int64(target_dtype) {
                 let condition = "target_dtype.value().IsScalar() && 
target_dtype.value().bits() == 64 && \
@@ -279,7 +279,7 @@ pub fn reinterpret_at(
                 "Reinterpret target must be PrimType or PtrType, but got {}",
                 support::repr(&ty)
             );
-            return type_error("target_ty.as<PtrTypeNode>()", message);
+            return type_error("target_ty.as<PtrTypeObj>()", message);
         }
         if let Some(source) = value.ty.as_node::<PrimTypeObj>() {
             if !is_scalar_int64(source.dtype) {
@@ -296,7 +296,7 @@ pub fn reinterpret_at(
                 "Reinterpret source must be PrimType or PtrType, but got {}",
                 support::repr(&value.ty)
             );
-            return type_error("value->ty.as<PtrTypeNode>()", message);
+            return type_error("value->ty.as<PtrTypeObj>()", message);
         }
     }
     let op = support::cached_op("tirx.reinterpret")?;

Reply via email to