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")?;