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 501635deb [FEAT] Add Rust primitive expression operator overloads
(#157)
501635deb is described below
commit 501635debfd322189116b5ffe0113aa55cb349f2
Author: Tianqi Chen <[email protected]>
AuthorDate: Sun Nov 1 22:12:10 2026 -0500
[FEAT] Add Rust primitive expression operator overloads (#157)
Add `def_expr_op_over_loads!(Type)` for existing Rust reference
wrappers, with owned and borrowed operators, contextual scalar operands,
and named operations. Register native primitive-expression counterparts
plus `Expr`, `Var`, and `Call` explicitly in `rust/src/ir/expr.rs`,
preserving object layouts, allocation, and ancestor conversions.
Rename the fallible operator result to `ExprResult`, remove `ex()` and
`.expr()`, and support standard `TryFrom<ExprResult> for Expr`
extraction. Update examples and the Rust guide to use expression
operators directly while retaining ordinary Rust borrowing.
---
docs/ir_guides/working_with_ir_in_rust.rst | 22 ++-
examples/extension/src/lib.rs | 5 +-
examples/ir_guides/rust/src/lib.rs | 8 +-
rust/src/ir/expr.rs | 54 +++++-
rust/src/ir/prim/op.rs | 258 +++++++++++++----------------
rust/src/lib.rs | 5 +-
rust/src/macro.rs | 21 +++
rust/src/macro/def_expr_op_overloads.rs | 148 +++++++++++++++++
rust/src/tirx/op.rs | 15 +-
rust/tests/ir/prim/op.rs | 78 +++++----
10 files changed, 404 insertions(+), 210 deletions(-)
diff --git a/docs/ir_guides/working_with_ir_in_rust.rst
b/docs/ir_guides/working_with_ir_in_rust.rst
index cb0e658e5..fbba2c262 100644
--- a/docs/ir_guides/working_with_ir_in_rust.rst
+++ b/docs/ir_guides/working_with_ir_in_rust.rst
@@ -103,7 +103,7 @@ key constructs are:
* - Construct
- Purpose
- * - Constructors, ``ex()``
+ * - Constructors, expression operators
- Build expressions with ordinary Rust operators
* - ``FooObj`` / ``Foo``
- Underlying object and its named managed reference: read fields
@@ -138,13 +138,13 @@ The output is:
x * 2 + 1
|Var::new|_ takes a name, a type, and a source location; ``UNKNOWN_LOC``
-means there is none. |ex|_ wraps ``x`` so that ``*`` and ``+`` build IR,
-and the literals ``2`` and ``1`` become constants of the type of ``x``.
+means there is none. The operators ``*`` and ``+`` build IR directly
+from ``x``, and the literals ``2`` and ``1`` become constants of its type.
-An operator can fail, for example on mismatched types, so ``ex`` carries
-the first error along, much like a chain of ``Result``\ s. ``.expr()``
-takes the result out and ``?`` passes an error on. |script|_ prints the
-node as TVMScript.
+An operator can fail, for example on mismatched types, so ``ExprResult``
+carries the first error along, much like a chain of ``Result``\ s. The
+standard ``.try_into()`` conversion extracts an ``Expr``, and ``?`` passes
+an error on. |script|_ prints the node as TVMScript.
We now have an expression; next, let's see what kind of Rust value it is.
@@ -546,8 +546,8 @@ both visits and mutations. A ``#[dispatch(mutate)]`` method
that takes a
trailing ``&mut Mutator`` gets the mutation counterpart of
``VisitContext``, with ``default_mutate`` for the children and
``def_region_kind()``. Let's reverse the loop
-by replacing each use of ``i`` with ``n - 1 - i``, built with ``ex`` as
-before:
+by replacing each use of ``i`` with ``n - 1 - i``. As before,
+``.try_into()?`` extracts the built expression or returns its error:
.. literalinclude:: ../../examples/ir_guides/rust/src/lib.rs
:language: rust
@@ -605,7 +605,7 @@ Quick Reference
borrowed object in an ``Option``
* - ``.into()``
- Upcasts a managed reference to a parent type
- * - |Var::new|_, |ex|_\ ``(...)``, ``.expr()``
+ * - |Var::new|_, expression operators, ``.try_into()?``
- Creates a node; builds an expression with operators
* - |structural_walk|_\ ``(&func, callbacks, WalkOrder::PreOrder)``
- Walks with typed callbacks that return ``WalkResult``
@@ -654,8 +654,6 @@ Quick Reference
.. _VisitCallbacks:
../reference/rust/generated/tvm_ffi/extra/structural_visit/struct.VisitCallbacks.html
.. |MutateCallbacks| replace:: ``MutateCallbacks``
.. _MutateCallbacks:
../reference/rust/generated/tvm_ffi/extra/structural_mutate/struct.MutateCallbacks.html
-.. |ex| replace:: ``ex``
-.. _ex: ../reference/rust/generated/tvm_tirx/ir/prim/op/fn.ex.html
.. |ObjectIdentity| replace:: ``ObjectIdentity``
.. _ObjectIdentity:
../reference/rust/generated/tvm_ffi/object/struct.ObjectIdentity.html
.. |StructuralVisitorWithConstraintContext| replace::
``StructuralVisitorWithConstraintContext``
diff --git a/examples/extension/src/lib.rs b/examples/extension/src/lib.rs
index e50168731..0f6fdeda1 100644
--- a/examples/extension/src/lib.rs
+++ b/examples/extension/src/lib.rs
@@ -21,7 +21,6 @@
//! See docs/advanced/extending_ir_in_rust.rst; the my_ext Python package
loads this library.
use tvm_tirx::ir::op_attr::CALL_EFFECT_KIND;
-use tvm_tirx::ir::prim::ex;
use tvm_tirx::ir::sig::{self, PrimExpr};
use tvm_tirx::ir::transform::{Pass, PassContext, PassInfo};
use tvm_tirx::ir::{
@@ -207,8 +206,8 @@ impl Lowering {
let (a, b) = self.parts(&call.args.get(0)?)?;
let (c, d) = self.parts(&call.args.get(1)?)?;
// (a + bi)(c + di) = (ac - bd) + (ad + bc)i
- let re = ex(&a) * ex(&c) - ex(&b) * ex(&d);
- let im = ex(&a) * ex(&d) + ex(&b) * ex(&c);
+ let re = &a * &c - &b * &d;
+ let im = &a * &d + &b * &c;
return Ok(Call::new(
None,
COMPLEX.get()?,
diff --git a/examples/ir_guides/rust/src/lib.rs
b/examples/ir_guides/rust/src/lib.rs
index 1f100d28b..8c9a74ce4 100644
--- a/examples/ir_guides/rust/src/lib.rs
+++ b/examples/ir_guides/rust/src/lib.rs
@@ -24,7 +24,7 @@
use std::collections::HashMap;
-use tvm_tirx::ir::prim::{ex, make_const, Add, AddObj, Mul};
+use tvm_tirx::ir::prim::{make_const, Add, AddObj, Mul};
use tvm_tirx::ir::{
Expr, For, ForObj, IntImm, PrimType, TensorStore, TensorStoreObj, Var,
VarObj, UNKNOWN_LOC,
};
@@ -46,7 +46,7 @@ fn lines(items: impl IntoIterator<Item = String>) -> Strings {
// [construct.begin]
fn x_times_2_plus_1() -> Result<(Var, Expr)> {
let x = Var::new("x", PrimType::int(32), UNKNOWN_LOC);
- let expr = (ex(&x) * 2 + 1).expr()?;
+ let expr = (&x * 2 + 1).try_into()?;
Ok((x, expr))
}
@@ -101,7 +101,7 @@ fn describe(expr: &Expr) -> Result<String> {
// [matching.begin]
fn matching() -> Result<Strings> {
let (x, expr) = x_times_2_plus_1()?;
- let difference = (ex(&x) - 1).expr()?;
+ let difference = (&x - 1).try_into()?;
Ok(lines([describe(&expr)?, describe(&difference)?]))
}
// [matching.end]
@@ -241,7 +241,7 @@ struct ReverseLoops {
#[dispatch(mutate)]
impl ReverseLoops {
fn mutate_for(&mut self, loop_: For, mutator: &mut Mutator) -> Result<Any>
{
- let index = (ex(&loop_.end) - 1 - &loop_.var).expr()?;
+ let index = (&loop_.end - 1 - &loop_.var).try_into()?;
self.reversed_index.insert(ObjectIdentity::of(&loop_.var), index);
mutator.default_mutate(self, &loop_)
}
diff --git a/rust/src/ir/expr.rs b/rust/src/ir/expr.rs
index d42798b85..2dbd74e67 100644
--- a/rust/src/ir/expr.rs
+++ b/rust/src/ir/expr.rs
@@ -1045,10 +1045,10 @@ impl From<bool> for IntImm {
}
/// What converts to an expression, possibly failing: anything that converts
into an [`Expr`] (a
-/// node, a `Var`, a Rust `i32`, `f32` and the like), and an
[`Ex`](crate::ir::prim::Ex), an
+/// node, a `Var`, a Rust `i32`, `f32` and the like), and an
[`ExprResult`](crate::ir::prim::ExprResult), an
/// expression under construction, which gives the first error any of its
steps raised. The
/// constructors and functions that can fail take their expression operands as
`impl IntoExpr`, so
-/// an `Ex` passes straight in and `?` on their result covers it.
+/// an `ExprResult` passes straight in and `?` on their result covers it.
pub trait IntoExpr {
fn into_expr(self) -> Result<Expr>;
}
@@ -1825,3 +1825,53 @@ impl Range {
}
}
}
+
+// Operator attachment is separate from generated layouts and upcasts. Keep
the native
+// primitive-expression counterparts here, plus Expr, Var and Call for general
handles whose
+// primitive type is checked when an operator is applied.
+crate::def_expr_op_over_loads!(Expr);
+crate::def_expr_op_over_loads!(Var);
+crate::def_expr_op_over_loads!(Call);
+crate::def_expr_op_over_loads!(IntImm);
+crate::def_expr_op_over_loads!(FloatImm);
+crate::def_expr_op_over_loads!(TensorLoad);
+
+// Primitive operator and vector reference classes inherit C++ PrimExpr.
+crate::def_expr_op_over_loads!(crate::ir::prim::Cast);
+crate::def_expr_op_over_loads!(crate::ir::prim::LShift);
+crate::def_expr_op_over_loads!(crate::ir::prim::RShift);
+crate::def_expr_op_over_loads!(crate::ir::prim::BitwiseAnd);
+crate::def_expr_op_over_loads!(crate::ir::prim::BitwiseOr);
+crate::def_expr_op_over_loads!(crate::ir::prim::BitwiseXor);
+crate::def_expr_op_over_loads!(crate::ir::prim::BitwiseNot);
+crate::def_expr_op_over_loads!(crate::ir::prim::Add);
+crate::def_expr_op_over_loads!(crate::ir::prim::Sub);
+crate::def_expr_op_over_loads!(crate::ir::prim::Mul);
+crate::def_expr_op_over_loads!(crate::ir::prim::Div);
+crate::def_expr_op_over_loads!(crate::ir::prim::Mod);
+crate::def_expr_op_over_loads!(crate::ir::prim::FloorDiv);
+crate::def_expr_op_over_loads!(crate::ir::prim::FloorMod);
+crate::def_expr_op_over_loads!(crate::ir::prim::Min);
+crate::def_expr_op_over_loads!(crate::ir::prim::Max);
+crate::def_expr_op_over_loads!(crate::ir::prim::EQ);
+crate::def_expr_op_over_loads!(crate::ir::prim::NE);
+crate::def_expr_op_over_loads!(crate::ir::prim::LT);
+crate::def_expr_op_over_loads!(crate::ir::prim::LE);
+crate::def_expr_op_over_loads!(crate::ir::prim::GT);
+crate::def_expr_op_over_loads!(crate::ir::prim::GE);
+crate::def_expr_op_over_loads!(crate::ir::prim::And);
+crate::def_expr_op_over_loads!(crate::ir::prim::Or);
+crate::def_expr_op_over_loads!(crate::ir::prim::Not);
+crate::def_expr_op_over_loads!(crate::ir::prim::Select);
+crate::def_expr_op_over_loads!(crate::ir::prim::Let);
+crate::def_expr_op_over_loads!(crate::ir::prim::Ramp);
+crate::def_expr_op_over_loads!(crate::ir::prim::Broadcast);
+crate::def_expr_op_over_loads!(crate::ir::prim::Shuffle);
+
+// These symbolic reference classes also inherit C++ PrimExpr. The opaque Rust
mirrors retain
+// their existing allocation restrictions; attaching operators only forwards
their handles.
+crate::def_expr_op_over_loads!(crate::sym::iter_affine_map::IterMapExpr);
+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);
diff --git a/rust/src/ir/prim/op.rs b/rust/src/ir/prim/op.rs
index ec62cf30e..f1b092f72 100644
--- a/rust/src/ir/prim/op.rs
+++ b/rust/src/ir/prim/op.rs
@@ -30,7 +30,7 @@
//!
//! Each function builds at the unknown location, as the C++ default argument
does; the `_at`
//! variant takes a location. An operand is an expression, an expression under
construction (an
-//! [`Ex`], see below) or a literal, typed as C++ types it: an integer next to
an expression is
+//! [`ExprResult`], see below) or a literal, typed as C++ types it: an integer
next to an expression is
//! `MakeConst(other.ty(), v)`, an `f32` a float32 constant, an `f64` a
float64 one, and a literal
//! on its own the constant [`From`] makes of it (an `i32` is an `int32`):
//!
@@ -44,10 +44,10 @@
//! # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
//! ```
//!
-//! `&x + 1` is the operator syntax of [`Ex`]: the C++ operators on an
expression, a `Var` or an
-//! `Ex`, with literals typed the same way. An `Ex` holds the expression under
construction or the
+//! `&x + 1` builds an [`ExprResult`]: the C++ operators on expression
handles, with literals
+//! typed the same way. An `ExprResult` holds the expression under
construction or the
//! first error any step raised; it passes into any function or constructor
that takes
-//! `impl IntoExpr`, whose `?` then reports that error, and [`Ex::expr`] takes
the expression out.
+//! `impl IntoExpr`, whose `?` then reports that error, and `Expr::try_from`
takes the expression out.
//! The operations Rust has no operator for are the [`PrimOps`] methods
(`x.floordiv(4)`,
//! `x.less(y)`, `x.cast(ty)`).
@@ -302,7 +302,7 @@ fn check_operands(
//
---------------------------------------------------------------------------------------------
// Operands
-/// An operand of an operator: an expression, an expression under construction
([`Ex`]) or a
+/// An operand of an operator: an expression, an expression under construction
([`ExprResult`]) or a
/// literal, which is typed after the expression it meets (C++
/// `TVM_DEFINE_BINOP_CONST_VAL_OVERLOAD` and friends): an `i32` or `i64` is
/// `MakeConst(other.ty(), v)`, an `f32` a float32 constant, an `f64` a
float64 one. A literal
@@ -315,14 +315,14 @@ pub trait Operand {
/// What an [`Operand`] is.
#[doc(hidden)]
pub enum OperandKind {
- Expr(Ex),
+ Expr(ExprResult),
Int32(i32),
Int64(i64),
Float32(f32),
Float64(f64),
}
-impl<T: Into<Ex>> Operand for T {
+impl<T: Into<ExprResult>> Operand for T {
fn classify(self) -> OperandKind {
OperandKind::Expr(self.into())
}
@@ -356,27 +356,27 @@ impl Operand for f64 {
}
/// An operand next to `peer`: a literal is typed after it.
-fn typed_after(operand: OperandKind, peer: &Ex) -> Ex {
+fn typed_after(operand: OperandKind, peer: &ExprResult) -> ExprResult {
let constant = |value: i64| {
- Ex(peer.dtype().and_then(|dtype|
make_const(PrimType::from_dtype(dtype)?, value)))
+ ExprResult(peer.dtype().and_then(|dtype|
make_const(PrimType::from_dtype(dtype)?, value)))
};
match operand {
- OperandKind::Expr(ex) => ex,
+ OperandKind::Expr(expr) => expr,
OperandKind::Int32(value) => constant(i64::from(value)),
OperandKind::Int64(value) => constant(value),
- OperandKind::Float32(value) => Ex(Ok(FloatImm::float32(value).into())),
- OperandKind::Float64(value) => Ex(Ok(FloatImm::float64(value).into())),
+ OperandKind::Float32(value) =>
ExprResult(Ok(FloatImm::float32(value).into())),
+ OperandKind::Float64(value) =>
ExprResult(Ok(FloatImm::float64(value).into())),
}
}
/// An operand on its own: a literal is the constant [`From`] makes of it.
-fn on_its_own(operand: OperandKind) -> Ex {
+fn on_its_own(operand: OperandKind) -> ExprResult {
match operand {
- OperandKind::Expr(ex) => ex,
- OperandKind::Int32(value) => Ex(Ok(IntImm::int32(value).into())),
- OperandKind::Int64(value) => Ex(Ok(IntImm::int64(value).into())),
- OperandKind::Float32(value) => Ex(Ok(FloatImm::float32(value).into())),
- OperandKind::Float64(value) => Ex(Ok(FloatImm::float64(value).into())),
+ OperandKind::Expr(expr) => expr,
+ OperandKind::Int32(value) =>
ExprResult(Ok(IntImm::int32(value).into())),
+ OperandKind::Int64(value) =>
ExprResult(Ok(IntImm::int64(value).into())),
+ OperandKind::Float32(value) =>
ExprResult(Ok(FloatImm::float32(value).into())),
+ OperandKind::Float64(value) =>
ExprResult(Ok(FloatImm::float64(value).into())),
}
}
@@ -394,7 +394,7 @@ fn operands(a: impl Operand, b: impl Operand) ->
Result<(Expr, Expr)> {
}
(a, b) => (on_its_own(a), on_its_own(b)),
};
- Ok((a.expr()?, b.expr()?))
+ Ok((a.into_expr()?, b.into_expr()?))
}
/// The functions without a location: each builds at the unknown location
through its `_at`
@@ -1725,14 +1725,14 @@ binary_intrinsics! {
}
//
---------------------------------------------------------------------------------------------
-// `Ex`: expressions under construction
+// `ExprResult`: expressions under construction
/// An expression being built by operators: either the expression, or the
first error any step
-/// raised. Later steps pass an error through untouched. An `Ex` is an
[`IntoExpr`], so it passes
+/// raised. Later steps pass an error through untouched. An `ExprResult` is an
[`IntoExpr`], so it passes
/// into any function or constructor that takes one, whose `?` then reports
the error;
-/// [`Ex::expr`] takes the expression out.
+/// `Expr::try_from` takes the expression out.
///
-/// Any expression node converts into an `Ex`, as it converts into a C++
`PrimExpr`. The operators
+/// Registered expression wrappers convert into an `ExprResult`. The operators
/// `+`, `-`, `*`, `&`, `|`, `^`, `<<`, `>>`, unary `-` and `!` are the C++
ones, typed after C++:
/// an integer literal on either side is `MakeConst(other.ty(), v)`, an `f32`
a float32 constant,
/// an `f64` a float64 one. `/` and `%` are not defined: name the division
@@ -1741,15 +1741,19 @@ binary_intrinsics! {
/// Comparisons, `&&` and `||` are [`PrimOps`] methods too, because Rust's
comparison traits
/// return `bool`.
#[derive(Clone)]
-#[must_use = "an `Ex` holds the result; pass it on, or take it with `.expr()`"]
-pub struct Ex(Result<Expr>);
+#[must_use = "an `ExprResult` holds the result; pass it on, or convert it with
`Expr::try_from`"]
+pub struct ExprResult(Result<Expr>);
-impl Ex {
- /// The built expression, or the first error.
- pub fn expr(self) -> Result<Expr> {
- self.0
+impl TryFrom<ExprResult> for Expr {
+ type Error = Error;
+
+ /// The built expression, or the first error raised while constructing it.
+ fn try_from(value: ExprResult) -> Result<Self> {
+ value.0
}
+}
+impl ExprResult {
/// The dtype a literal next to this expression takes
(`MakeConst(this.ty(), v)`).
fn dtype(&self) -> Result<DLDataType> {
match &self.0 {
@@ -1759,100 +1763,80 @@ impl Ex {
}
}
-impl IntoExpr for Ex {
+impl IntoExpr for ExprResult {
fn into_expr(self) -> Result<Expr> {
self.0
}
}
-impl IntoExpr for &Ex {
+impl IntoExpr for &ExprResult {
fn into_expr(self) -> Result<Expr> {
self.0.clone()
}
}
-/// `value` as an [`Ex`], to start an expression from something that is not
one yet.
-pub fn ex(value: impl Into<Ex>) -> Ex {
- value.into()
-}
-
-impl<T: Into<Expr>> From<Result<T>> for Ex {
+impl<T: Into<Expr>> From<Result<T>> for ExprResult {
fn from(value: Result<T>) -> Self {
- Ex(value.map(Into::into))
+ ExprResult(value.map(Into::into))
}
}
-impl From<&Ex> for Ex {
- fn from(value: &Ex) -> Self {
+impl From<&ExprResult> for ExprResult {
+ fn from(value: &ExprResult) -> Self {
value.clone()
}
}
-macro_rules! ex_from_nodes {
- ($($ty:ty),+ $(,)?) => {
- $(
- impl From<$ty> for Ex {
- fn from(value: $ty) -> Self {
- Ex(Ok(value.into()))
- }
- }
- impl From<&$ty> for Ex {
- fn from(value: &$ty) -> Self {
- Ex(Ok(value.clone().into()))
- }
- }
- )+
- };
-}
-
-ex_from_nodes!(
- Expr, Var, IntImm, FloatImm, Call, TensorLoad, Add, And, BitwiseAnd,
BitwiseNot, BitwiseOr,
- BitwiseXor, Broadcast, Cast, Div, EQ, FloorDiv, FloorMod, GE, GT, LE,
LShift, LT, Let, Max,
- Min, Mod, Mul, NE, Not, Or, RShift, Ramp, Select, Shuffle, Sub,
-);
-
/// `lhs <op> rhs`; a literal `rhs` is typed after `lhs`.
-fn binary(lhs: Ex, rhs: impl Operand, op: impl FnOnce(Expr, Expr) ->
Result<Expr>) -> Ex {
+fn binary(
+ lhs: ExprResult,
+ rhs: impl Operand,
+ op: impl FnOnce(Expr, Expr) -> Result<Expr>,
+) -> ExprResult {
let rhs = typed_after(rhs.classify(), &lhs);
match (lhs.0, rhs.0) {
- (Ok(a), Ok(b)) => Ex(op(a, b)),
- (Err(error), _) | (_, Err(error)) => Ex(Err(error)),
+ (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: Ex, op: impl FnOnce(Expr, Expr) ->
Result<Expr>) -> Ex {
+fn binary_literal(
+ lhs: impl Operand,
+ rhs: ExprResult,
+ op: impl FnOnce(Expr, Expr) -> Result<Expr>,
+) -> ExprResult {
let lhs = typed_after(lhs.classify(), &rhs);
binary(lhs, rhs, op)
}
mod operators {
- //! The operator traits of [`Ex`], and of the expressions and literals
that start one.
+ //! The operator traits of [`ExprResult`], and of the expressions and
literals that start one.
use std::ops::{
Add, AddAssign, BitAnd, BitOr, BitXor, Mul, MulAssign, Neg, Not, Shl,
Shr, Sub, SubAssign,
};
- use super::{binary, binary_literal, Ex, Expr, Operand, Var};
+ use super::{binary, binary_literal, ExprResult, Operand};
macro_rules! binary_operators {
($($Trait:ident::$method:ident => $op:ident),+ $(,)?) => {
$(
- impl<R: Operand> $Trait<R> for Ex {
- type Output = Ex;
- fn $method(self, rhs: R) -> Ex {
+ impl<R: Operand> $Trait<R> for ExprResult {
+ type Output = ExprResult;
+ fn $method(self, rhs: R) -> ExprResult {
binary(self, rhs, super::$op)
}
}
- binary_operators!(@lhs $Trait::$method => $op; &Ex, Expr,
&Expr, Var, &Var);
+ binary_operators!(@lhs $Trait::$method => $op; &ExprResult);
binary_operators!(@literal $Trait::$method => $op; i32, i64,
f32, f64);
)+
};
(@lhs $Trait:ident::$method:ident => $op:ident; $($lhs:ty),+) => {
$(
impl<R: Operand> $Trait<R> for $lhs {
- type Output = Ex;
- fn $method(self, rhs: R) -> Ex {
+ type Output = ExprResult;
+ fn $method(self, rhs: R) -> ExprResult {
binary(self.into(), rhs, super::$op)
}
}
@@ -1860,14 +1844,14 @@ mod operators {
};
(@literal $Trait:ident::$method:ident => $op:ident; $($lit:ty),+) => {
$(
- binary_operators!(@literal_rhs $Trait::$method => $op; $lit;
Ex, &Ex, Expr, &Expr, Var, &Var);
+ binary_operators!(@literal_rhs $Trait::$method => $op; $lit;
ExprResult, &ExprResult);
)+
};
(@literal_rhs $Trait:ident::$method:ident => $op:ident; $lit:ty;
$($rhs:ty),+) => {
$(
impl $Trait<$rhs> for $lit {
- type Output = Ex;
- fn $method(self, rhs: $rhs) -> Ex {
+ type Output = ExprResult;
+ fn $method(self, rhs: $rhs) -> ExprResult {
binary_literal(self, rhs.into(), super::$op)
}
}
@@ -1889,7 +1873,7 @@ mod operators {
macro_rules! assign_operators {
($($Trait:ident::$method:ident => $op:ident),+ $(,)?) => {
$(
- impl<R: Operand> $Trait<R> for Ex {
+ impl<R: Operand> $Trait<R> for ExprResult {
fn $method(&mut self, rhs: R) {
*self = binary(self.clone(), rhs, super::$op);
}
@@ -1905,23 +1889,23 @@ mod operators {
$(
/// C++ unary `-` (`neg`).
impl Neg for $ty {
- type Output = Ex;
- fn neg(self) -> Ex {
- Ex(super::neg(Ex::from(self)))
+ type Output = ExprResult;
+ fn neg(self) -> ExprResult {
+ ExprResult(super::neg(ExprResult::from(self)))
}
}
/// C++ `!` (`logical_not`).
impl Not for $ty {
- type Output = Ex;
- fn not(self) -> Ex {
- Ex(super::logical_not(Ex::from(self)))
+ type Output = ExprResult;
+ fn not(self) -> ExprResult {
+ ExprResult(super::logical_not(ExprResult::from(self)))
}
}
)+
};
}
- unary_operators!(Ex, &Ex, Expr, &Expr, Var, &Var);
+ unary_operators!(ExprResult, &ExprResult);
}
/// [`PrimOps`] methods that are a function of one expression.
@@ -1929,8 +1913,8 @@ macro_rules! unary_methods {
($($(#[$doc:meta])* $name:ident;)+) => {
$(
$(#[$doc])*
- fn $name(&self) -> Ex {
- Ex($name(self.to_ex()))
+ fn $name(&self) -> ExprResult {
+ ExprResult($name(self.to_expr_result()))
}
)+
};
@@ -1942,52 +1926,52 @@ macro_rules! binary_methods {
($($(#[$doc:meta])* $name:ident;)+) => {
$(
$(#[$doc])*
- fn $name(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, $name)
+ fn $name(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, $name)
}
)+
};
}
/// The C++ `PrimExpr` operations that are functions rather than Rust
operators, as methods of
-/// an [`Ex`], an [`Expr`] or a [`Var`]. Each takes an expression or a literal
on the right, typed
+/// an [`ExprResult`] or an expression handle. Each takes an expression or a
literal on the right, typed
/// as for the operators, and leaves `self` usable; the comparisons are named
after the C++
/// functions (`less`, `equal`), since Rust's `eq` and `lt` return a `bool`.
pub trait PrimOps {
- /// This expression as an [`Ex`] (a reference-count increment).
- fn to_ex(&self) -> Ex;
+ /// This expression as an [`ExprResult`] (a reference-count increment).
+ fn to_expr_result(&self) -> ExprResult;
/// C++ `a < b` ([`lt`]).
- fn less(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, lt)
+ fn less(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, lt)
}
/// C++ `a <= b` ([`le`]).
- fn less_equal(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, le)
+ fn less_equal(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, le)
}
/// C++ `a > b` ([`gt`]).
- fn greater(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, gt)
+ fn greater(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, gt)
}
/// C++ `a >= b` ([`ge`]).
- fn greater_equal(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, ge)
+ fn greater_equal(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, ge)
}
/// C++ `a == b` ([`eq`]).
- fn equal(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, eq)
+ fn equal(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, eq)
}
/// C++ `a != b` ([`ne`]).
- fn not_equal(&self, rhs: impl Operand) -> Ex {
- binary(self.to_ex(), rhs, ne)
+ fn not_equal(&self, rhs: impl Operand) -> ExprResult {
+ binary(self.to_expr_result(), rhs, ne)
}
/// C++ `a && b` ([`logical_and`]).
- fn logical_and(&self, rhs: impl IntoExpr) -> Ex {
- Ex(logical_and(self.to_ex(), rhs))
+ fn logical_and(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(logical_and(self.to_expr_result(), rhs))
}
/// C++ `a || b` ([`logical_or`]).
- fn logical_or(&self, rhs: impl IntoExpr) -> Ex {
- Ex(logical_or(self.to_ex(), rhs))
+ fn logical_or(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(logical_or(self.to_expr_result(), rhs))
}
binary_methods! {
@@ -2014,32 +1998,32 @@ pub trait PrimOps {
}
/// C++ `pow(a, b)` ([`pow`]).
- fn pow(&self, rhs: impl IntoExpr) -> Ex {
- Ex(pow(self.to_ex(), rhs))
+ fn pow(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(pow(self.to_expr_result(), rhs))
}
/// C++ `fmod(a, b)` ([`fmod`]).
- fn fmod(&self, rhs: impl IntoExpr) -> Ex {
- Ex(fmod(self.to_ex(), rhs))
+ fn fmod(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(fmod(self.to_expr_result(), rhs))
}
/// C++ `atan2(a, b)` ([`atan2`]).
- fn atan2(&self, rhs: impl IntoExpr) -> Ex {
- Ex(atan2(self.to_ex(), rhs))
+ fn atan2(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(atan2(self.to_expr_result(), rhs))
}
/// C++ `nextafter(a, b)` ([`nextafter`]).
- fn nextafter(&self, rhs: impl IntoExpr) -> Ex {
- Ex(nextafter(self.to_ex(), rhs))
+ fn nextafter(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(nextafter(self.to_expr_result(), rhs))
}
/// C++ `copysign(a, b)` ([`copysign`]).
- fn copysign(&self, rhs: impl IntoExpr) -> Ex {
- Ex(copysign(self.to_ex(), rhs))
+ fn copysign(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(copysign(self.to_expr_result(), rhs))
}
/// C++ `hypot(a, b)` ([`hypot`]).
- fn hypot(&self, rhs: impl IntoExpr) -> Ex {
- Ex(hypot(self.to_ex(), rhs))
+ fn hypot(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(hypot(self.to_expr_result(), rhs))
}
/// C++ `ldexp(a, b)` ([`ldexp`]).
- fn ldexp(&self, rhs: impl IntoExpr) -> Ex {
- Ex(ldexp(self.to_ex(), rhs))
+ fn ldexp(&self, rhs: impl IntoExpr) -> ExprResult {
+ ExprResult(ldexp(self.to_expr_result(), rhs))
}
unary_methods! {
@@ -2120,35 +2104,23 @@ pub trait PrimOps {
}
/// C++ `cast(a, t)` ([`cast`]).
- fn cast(&self, ty: impl Into<PrimType>) -> Ex {
- Ex(cast(self.to_ex(), ty))
+ fn cast(&self, ty: impl Into<PrimType>) -> ExprResult {
+ ExprResult(cast(self.to_expr_result(), ty))
}
/// C++ `if_then_else(self, t, f)` ([`if_then_else`]), with `self` the
condition.
- fn if_then_else(&self, t: impl IntoExpr, f: impl IntoExpr) -> Ex {
- Ex(if_then_else(self.to_ex(), t, f))
+ fn if_then_else(&self, t: impl IntoExpr, f: impl IntoExpr) -> ExprResult {
+ ExprResult(if_then_else(self.to_expr_result(), t, f))
}
/// The raw `Select(self, t, f)` ([`Select::new`]), with `self` the
condition.
- fn select(&self, t: impl IntoExpr, f: impl IntoExpr) -> Ex {
- Ex(Select::new(self.to_ex(), t, f, UNKNOWN_LOC).map(Into::into))
+ fn select(&self, t: impl IntoExpr, f: impl IntoExpr) -> ExprResult {
+ ExprResult(Select::new(self.to_expr_result(), t, f,
UNKNOWN_LOC).map(Into::into))
}
}
-impl PrimOps for Ex {
- fn to_ex(&self) -> Ex {
+impl PrimOps for ExprResult {
+ fn to_expr_result(&self) -> ExprResult {
self.clone()
}
}
-
-impl PrimOps for Expr {
- fn to_ex(&self) -> Ex {
- self.into()
- }
-}
-
-impl PrimOps for Var {
- fn to_ex(&self) -> Ex {
- self.into()
- }
-}
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index 344b1ab51..f92a0945e 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -95,8 +95,8 @@
//! its siblings match their operand types and fold constants as C++ `add`
does, and the math
//! functions ([`ir::prim::op::floor`], [`ir::prim::op::exp`], ...) build the
same calls. They
//! take expressions or literals typed as C++ types them and build at the
unknown location (the
-//! `_at` variants take one), and an [`ir::prim::op::Ex`] gives them operator
syntax:
-//! `floordiv(&x + 1, 4)?` is the C++ `floordiv(x + 1, 4)`. An `Ex` passes
into any function or
+//! `_at` variants take one), and an [`ir::prim::op::ExprResult`] gives them
operator syntax:
+//! `floordiv(&x + 1, 4)?` is the C++ `floordiv(x + 1, 4)`. An `ExprResult`
passes into any function or
//! constructor that takes an [`ir::IntoExpr`], as any expression does.
//!
//! Operators and types are defined from Rust in a
[`tvm_ffi::static_init_block!`], as C++ defines
@@ -305,6 +305,7 @@ impl Drop for ExitOnDrop {
pub mod backend;
pub mod codegen;
pub mod ir;
+mod r#macro;
pub mod runtime;
pub mod script;
pub mod sym;
diff --git a/rust/src/macro.rs b/rust/src/macro.rs
new file mode 100644
index 000000000..d4ac44359
--- /dev/null
+++ b/rust/src/macro.rs
@@ -0,0 +1,21 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+//! Macros for defining Rust IR bindings.
+
+mod def_expr_op_overloads;
diff --git a/rust/src/macro/def_expr_op_overloads.rs
b/rust/src/macro/def_expr_op_overloads.rs
new file mode 100644
index 000000000..4b38b1185
--- /dev/null
+++ b/rust/src/macro/def_expr_op_overloads.rs
@@ -0,0 +1,148 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+//! Expression operators for existing reference wrappers.
+
+/// Add primitive-expression operators to an existing reference wrapper.
+///
+/// Pass a module-scope path to a type implementing `Clone` and `Into<Expr>`.
Define its object
+/// layout, reference wrapper and ancestor upcasts separately, using
`tvm_ffi::def_object_ref!` or
+/// generated bindings. For a deeper parent, also provide the upcast to `Expr`.
+///
+/// Owned operands move their handle; borrowed operands clone the handle.
Operators return
+/// [`ExprResult`](crate::ir::prim::ExprResult), preserving contextual literal
typing and the
+/// first construction error. Import [`PrimOps`](crate::ir::prim::PrimOps) for
named operations
+/// such as `floordiv` and `less`. Assignment operators apply only to
`ExprResult`.
+///
+/// ```text
+/// tvm_ffi::def_object_ref!(pub MyExpr, Expr, MyExprObj);
+/// tvm_tirx::def_expr_op_over_loads!(MyExpr);
+/// ```
+#[macro_export]
+macro_rules! def_expr_op_over_loads {
+ (crate::$($ty:ident)::+ $(,)?) => {
+ $crate::def_expr_op_over_loads!(@impl crate::$($ty)::+);
+ };
+ (self::$($ty:ident)::+ $(,)?) => {
+ $crate::def_expr_op_over_loads!(@impl self::$($ty)::+);
+ };
+ (super::$($ty:ident)::+ $(,)?) => {
+ $crate::def_expr_op_over_loads!(@impl super::$($ty)::+);
+ };
+ (::$($ty:ident)::+ $(,)?) => {
+ $crate::def_expr_op_over_loads!(@impl ::$($ty)::+);
+ };
+ ($($ty:ident)::+ $(,)?) => {
+ // Anchor caller names before introducing generic operator parameters.
+ $crate::def_expr_op_over_loads!(@impl self::$($ty)::+);
+ };
+ (@impl $ty:ty) => {
+ impl ::core::convert::From<$ty> for $crate::ir::prim::ExprResult {
+ fn from(value: $ty) -> Self {
+ Self::from(::core::result::Result::<$crate::ir::Expr,
$crate::tvm_ffi::Error>::Ok(
+ ::core::convert::Into::<$crate::ir::Expr>::into(value),
+ ))
+ }
+ }
+
+ impl ::core::convert::From<&$ty> for $crate::ir::prim::ExprResult {
+ fn from(value: &$ty) -> Self {
+ Self::from(<$ty as ::core::clone::Clone>::clone(value))
+ }
+ }
+
+ impl $crate::ir::prim::PrimOps for $ty {
+ fn to_expr_result(&self) -> $crate::ir::prim::ExprResult {
+ ::core::convert::From::from(self)
+ }
+ }
+
+ $crate::def_expr_op_over_loads!(@binary $ty;
+ Add::add, Sub::sub, Mul::mul, BitAnd::bitand, BitOr::bitor,
+ BitXor::bitxor, Shl::shl, Shr::shr
+ );
+ $crate::def_expr_op_over_loads!(@unary $ty; Neg::neg, Not::not);
+ };
+ (@binary $ty:ty; $($trait:ident::$method:ident),+ $(,)?) => {
+ $(
+ impl<R: $crate::ir::prim::Operand> ::core::ops::$trait<R> for $ty {
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self, rhs: R) -> Self::Output {
+ <Self::Output as ::core::ops::$trait<R>>::$method(
+ ::core::convert::From::from(self), rhs,
+ )
+ }
+ }
+
+ impl<R: $crate::ir::prim::Operand> ::core::ops::$trait<R> for &$ty
{
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self, rhs: R) -> Self::Output {
+ <Self::Output as ::core::ops::$trait<R>>::$method(
+ ::core::convert::From::from(self), rhs,
+ )
+ }
+ }
+
+ $crate::def_expr_op_over_loads!(@literal $ty, $trait::$method;
+ ::core::primitive::i32, ::core::primitive::i64,
+ ::core::primitive::f32, ::core::primitive::f64);
+ )+
+ };
+ (@literal $ty:ty, $trait:ident::$method:ident; $($literal:ty),+ $(,)?) => {
+ $(
+ impl ::core::ops::$trait<$ty> for $literal {
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self, rhs: $ty) -> Self::Output {
+ <Self as ::core::ops::$trait<Self::Output>>::$method(
+ self, ::core::convert::From::from(rhs),
+ )
+ }
+ }
+
+ impl ::core::ops::$trait<&$ty> for $literal {
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self, rhs: &$ty) -> Self::Output {
+ <Self as ::core::ops::$trait<Self::Output>>::$method(
+ self, ::core::convert::From::from(rhs),
+ )
+ }
+ }
+ )+
+ };
+ (@unary $ty:ty; $($trait:ident::$method:ident),+ $(,)?) => {
+ $(
+ impl ::core::ops::$trait for $ty {
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self) -> Self::Output {
+ <Self::Output as ::core::ops::$trait>::$method(
+ ::core::convert::From::from(self),
+ )
+ }
+ }
+
+ impl ::core::ops::$trait for &$ty {
+ type Output = $crate::ir::prim::ExprResult;
+ fn $method(self) -> Self::Output {
+ <Self::Output as ::core::ops::$trait>::$method(
+ ::core::convert::From::from(self),
+ )
+ }
+ }
+ )+
+ };
+}
diff --git a/rust/src/tirx/op.rs b/rust/src/tirx/op.rs
index 3ce71436a..7ea2f6bd0 100644
--- a/rust/src/tirx/op.rs
+++ b/rust/src/tirx/op.rs
@@ -35,10 +35,9 @@
// tvm-ffi-stubgen(import-object): crate::ir::expr::FloatImm
// tvm-ffi-stubgen(import-object): crate::ir::expr::StringImmObj
// tvm-ffi-stubgen(import-object): crate::ir::location::UNKNOWN_LOC
-// tvm-ffi-stubgen(import-object): crate::ir::prim::op::Ex
+// tvm-ffi-stubgen(import-object): crate::ir::prim::op::ExprResult
// tvm-ffi-stubgen(import-object): crate::ir::prim::op::PrimOps
// tvm-ffi-stubgen(import-object): crate::ir::prim::op::add
-// tvm-ffi-stubgen(import-object): crate::ir::prim::op::ex
// tvm-ffi-stubgen(import-object): crate::ir::prim::op::exp
// tvm-ffi-stubgen(import-object): crate::ir::prim::op::log
// tvm-ffi-stubgen(import-object): crate::ir::prim::op::match_types_at
@@ -63,10 +62,9 @@ use crate::ir::expr::IntoExpr;
use crate::ir::expr::StringImmObj;
use crate::ir::location::Location;
use crate::ir::location::UNKNOWN_LOC;
-use crate::ir::prim::op::Ex;
+use crate::ir::prim::op::ExprResult;
use crate::ir::prim::op::PrimOps;
use crate::ir::prim::op::add;
-use crate::ir::prim::op::ex;
use crate::ir::prim::op::exp;
use crate::ir::prim::op::log;
use crate::ir::prim::op::match_types_at;
@@ -308,8 +306,9 @@ pub fn reinterpret_at(
pub fn fast_erf_float_expr(arg: impl IntoExpr, bits: u8) -> Result<Expr> {
let dtype = PrimType::float(bits).dtype;
// The C++ coefficients are `float` literals.
- let constant =
- |value: f32| -> Ex { FloatImm::from_dtype(dtype, f64::from(value),
UNKNOWN_LOC).into() };
+ let constant = |value: f32| -> ExprResult {
+ FloatImm::from_dtype(dtype, f64::from(value), UNKNOWN_LOC).into()
+ };
// alpha_1, alpha_3, ..., alpha_13: the odd coefficients of the numerator.
#[allow(clippy::excessive_precision)]
let alpha: [f32; 7] = [
@@ -330,7 +329,7 @@ pub fn fast_erf_float_expr(arg: impl IntoExpr, bits: u8) ->
Result<Expr> {
-2.13374055278905e-04,
-1.45660718464996e-05,
];
- let x = ex(arg.into_expr()?).min(constant(4.0)).max(constant(-4.0));
+ let x = arg.into_expr()?.min(constant(4.0)).max(constant(-4.0));
let x2 = &x * &x;
// Horner's rule from the highest coefficient down, as C++ writes it out.
let mut p = &x2 * constant(alpha[6]) + constant(alpha[5]);
@@ -342,7 +341,7 @@ pub fn fast_erf_float_expr(arg: impl IntoExpr, bits: u8) ->
Result<Expr> {
for coefficient in beta[..3].iter().rev() {
q = &x2 * q + constant(*coefficient);
}
- p.div(q).expr()
+ p.div(q).try_into()
}
/// A return from the current GPU thread, without a value
(`tirx.gpu_thread_return`).
diff --git a/rust/tests/ir/prim/op.rs b/rust/tests/ir/prim/op.rs
index d19061a41..0179290ae 100644
--- a/rust/tests/ir/prim/op.rs
+++ b/rust/tests/ir/prim/op.rs
@@ -20,10 +20,11 @@
//! Tests of `ir::prim::op`: the type-promoting operators and the math
functions written in Rust,
//! against the native functions they follow (`prim._OpAdd` and the like).
Every binary operator
//! runs on every pair of a grid of operands, which must give the same
expression or an error of
-//! the same kind with the same message. Literals are typed, and `-`, `!` and
an `Ex` chain build,
+//! the same kind with the same message. Literals are typed, and `-`, `!` and
an `ExprResult` chain build,
//! as in C++.
-use tvm_tirx::ir::prim::op::{self, ex, Ex, PrimOps};
+use tvm_tirx::ir::prim::op::{self, ExprResult, PrimOps};
+use tvm_tirx::ir::IntoExpr;
use tvm_tirx::ir::prim::{self, Add, Broadcast, BroadcastObj, Cast, CastObj,
Not, Ramp, RampObj, Sub};
use tvm_tirx::ir::{
Call, Expr, FloatImm, IntImm, Location, Op, PrimType, Range, SourceLoc,
SourceName, StringImm,
@@ -79,8 +80,8 @@ fn native1(name: &str, x: &Expr) -> Result<Any> {
global(name).call_tuple((x.clone(), Location::unknown()))
}
-fn same(built: Ex, expected: &Expr) -> bool {
- structural_equal(&built.expr().unwrap(), expected)
+fn same(built: ExprResult, expected: &Expr) -> bool {
+ structural_equal(&built.into_expr().unwrap(), expected)
}
/// The cases where the Rust operator and the native one disagree, and how
many were compared.
@@ -215,7 +216,7 @@ fn operands() -> Vec<Expr> {
operands
}
-type Binary = fn(&Expr, &Expr) -> Ex;
+type Binary = fn(&Expr, &Expr) -> ExprResult;
#[test]
fn binary_operators_agree_with_cpp() {
@@ -257,7 +258,7 @@ fn binary_operators_agree_with_cpp() {
for b in &operands {
let native = native_op.call_tuple((a.clone(), b.clone(),
Location::unknown()));
let what = || format!("{name}({}, {})", repr(a), repr(b));
- outcome.check(what, operator(a, b).expr(), native);
+ outcome.check(what, operator(a, b).into_expr(), native);
}
}
outcome.assert_agrees(name);
@@ -278,7 +279,7 @@ fn unary_and_ternary_operators_agree_with_cpp() {
let bitwise_not = global("prim.BitwiseNot");
for a in &operands {
let native = bitwise_not.call_tuple((a.clone(), Location::unknown()));
- match a.bitwise_not().expr() {
+ match a.bitwise_not().into_expr() {
Ok(rust) => outcome.check(|| format!("~{}", repr(a)), Ok(rust),
native),
Err(_) => assert!(native.is_err(), "~{}: only Rust rejects it",
repr(a)),
}
@@ -302,7 +303,7 @@ fn unary_and_ternary_operators_agree_with_cpp() {
let native =
if_then_else.call_tuple((c.clone(), t.clone(), f.clone(),
Location::unknown()));
let what = || format!("if_then_else({}, {}, {})", repr(c),
repr(t), repr(f));
- outcome.check(what, c.if_then_else(t, f).expr(), native);
+ outcome.check(what, c.if_then_else(t, f).into_expr(), native);
}
}
}
@@ -318,19 +319,19 @@ fn literals_and_compound_expressions_agree_with_cpp() {
let mut outcome = Outcome::default();
outcome.check(
|| "x + 3".into(),
- (ex(&x) + 3).expr(),
+ (&x + 3).into_expr(),
add.call_tuple((x.clone(), imm("int64", 3), Location::unknown())),
);
outcome.check(
|| "3 + x".into(),
- (3i32 + &x).expr(),
+ (3i32 + &x).into_expr(),
add.call_tuple((imm("int64", 3), x.clone(), Location::unknown())),
);
let floordiv = global("prim._OpFloorDiv");
- let rust = ((ex(&x) + 0) * 1).floordiv(1);
+ let rust = ((&x + 0) * 1).floordiv(1);
outcome.check(
|| "((x + 0) * 1) // 1".into(),
- rust.expr(),
+ rust.into_expr(),
floordiv.call_tuple((x.clone(), imm("int64", 1), Location::unknown())),
);
// Unary minus folds a constant whose negation fits its type and is `0 -
x` otherwise.
@@ -340,7 +341,7 @@ fn literals_and_compound_expressions_agree_with_cpp() {
let ty: PrimType = value.ty.clone().try_cast().unwrap();
let zero = op::make_const(ty, 0).unwrap();
let native = sub.call_tuple((zero, value.clone(),
Location::unknown()));
- outcome.check(|| format!("-{}", repr(&value)), (-&value).expr(),
native);
+ outcome.check(|| format!("-{}", repr(&value)), (-&value).into_expr(),
native);
}
outcome.assert_agrees("literals");
}
@@ -381,9 +382,9 @@ fn literals_are_typed_as_in_cpp() {
));
}
-/// An `Ex` passes into the free functions and the constructors, which report
its error.
+/// An `ExprResult` passes into the free functions and the constructors, which
report its error.
#[test]
-fn an_ex_passes_into_functions_and_constructors() {
+fn expression_results_pass_into_functions_and_constructors() {
let _serial = serial();
let x = var("x", PrimType::int(32));
// C++ `floordiv(x + 1, 4)`.
@@ -405,7 +406,7 @@ fn an_ex_passes_into_functions_and_constructors() {
// The first error of the chain is what the function or constructor
reports.
let f = var("f", PrimType::float(32));
let failed = f.truncdiv(&x) + 1;
- let expected = failed.clone().expr().err().unwrap();
+ let expected = failed.clone().into_expr().err().unwrap();
for error in [
op::floordiv(failed.clone(), 4).err().unwrap(),
op::floordiv(4, &failed).err().unwrap(),
@@ -448,14 +449,14 @@ fn unary_operators_follow_neg_and_logical_not() {
let p = var("p", PrimType::bool());
// `-x` is `MakeConst(x.ty(), 0) - x`; a constant is negated in place when
the result fits.
assert!(same(-&x, &native2("prim._OpSub", imm("int32", 0), &x)));
- assert!(same(-ex(imm("int32", 5)), &imm("int32", -5)));
+ assert!(same(-imm("int32", 5), &imm("int32", -5)));
let min = imm("int64", i64::MIN);
- assert!(same(-ex(&min), &native2("prim._OpSub", imm("int64", 0), &min)));
- assert!(same(-ex(fimm("float32", 0.0)), &fimm("float32", -0.0)));
+ assert!(same(-&min, &native2("prim._OpSub", imm("int64", 0), &min)));
+ assert!(same(-fimm("float32", 0.0), &fimm("float32", -0.0)));
// `!p` is the raw `Not`; a constant folds.
let not_p = Not::new(&p, UNKNOWN_LOC).unwrap();
assert!(same(!&p, ¬_p.into()));
- assert!(same(!ex(imm("bool", 1)), &imm("bool", 0)));
+ assert!(same(!imm("bool", 1), &imm("bool", 0)));
}
#[test]
@@ -464,7 +465,7 @@ fn select_folding_and_the_first_error() {
let x = var("x", PrimType::int(32));
let r = var("r", PrimType::int(32));
// `select` is the raw constructor, built from operands under construction.
- let built = r.greater_equal(0).select(&r, &r - 1).expr().unwrap();
+ let built = r.greater_equal(0).select(&r, &r - 1).into_expr().unwrap();
let cond = native2("prim._OpGE", &r, imm("int32", 0));
let minus = native2("prim._OpSub", &r, imm("int32", 1));
let expected: Expr = global("prim.Select")
@@ -476,11 +477,11 @@ fn select_folding_and_the_first_error() {
// The raw `Sub` does not fold; the operator does.
let raw = Sub::new(3, 1, UNKNOWN_LOC).unwrap();
assert!(!structural_equal(&Expr::from(raw), &imm("int32", 2)));
- assert!(same(ex(imm("int32", 3)) - 1, &imm("int32", 2)));
+ assert!(same(imm("int32", 3) - 1, &imm("int32", 2)));
// Integer division of a float raises natively; later steps pass the error
through.
let f = var("f", PrimType::float(32));
- let Err(error) = (f.truncdiv(&x) + 1).less(&x).expr() else {
panic!("expected an error") };
+ let Err(error) = (f.truncdiv(&x) + 1).less(&x).into_expr() else {
panic!("expected an error") };
let direct = global("prim._OpTruncDiv").call_tuple((
Expr::from(&f),
Expr::from(&x),
@@ -491,7 +492,7 @@ fn select_folding_and_the_first_error() {
assert_eq!(error.message(), direct.message());
// A literal next to a non-PrimType expression has no dtype to take: an
error, not a panic.
let string: Expr = StringImm::new("s", UNKNOWN_LOC).into();
- assert!((ex(&string) + 1).expr().is_err());
+ assert!((&string + 1).into_expr().is_err());
}
/// Variables and constants of the types the math functions treat differently.
@@ -533,7 +534,7 @@ fn values() -> Vec<Expr> {
#[test]
fn unary_functions_agree_with_cpp() {
let _serial = serial();
- type Unary = fn(&Expr) -> Ex;
+ type Unary = fn(&Expr) -> ExprResult;
let functions: [(&str, Unary); 11] = [
("prim.floor", |x| x.floor()),
("prim.ceil", |x| x.ceil()),
@@ -549,7 +550,7 @@ fn unary_functions_agree_with_cpp() {
];
for (name, function) in functions {
for x in values() {
- agree(&format!("{name}({})", repr(&x)), function(&x).expr(),
native1(name, &x));
+ agree(&format!("{name}({})", repr(&x)), function(&x).into_expr(),
native1(name, &x));
}
}
// `round` folds a tie to even, as `std::nearbyint` does.
@@ -616,7 +617,7 @@ fn cast_agrees_with_cpp() {
let ty = prim(name);
let native =
global("prim._cast").call_tuple((x.clone(), ty.clone(),
Location::unknown()));
- agree(&format!("cast({}, {name})", repr(x)), x.cast(ty).expr(),
native);
+ agree(&format!("cast({}, {name})", repr(x)),
x.cast(ty).into_expr(), native);
}
}
}
@@ -674,7 +675,7 @@ fn pow_and_fmod_agree_with_cpp() {
];
for (x, y) in &pairs {
let native = global("prim._OpPow").call_tuple((x.clone(), y.clone(),
Location::unknown()));
- agree(&format!("pow({}, {})", repr(x), repr(y)), x.pow(y).expr(),
native);
+ agree(&format!("pow({}, {})", repr(x), repr(y)), x.pow(y).into_expr(),
native);
}
// `fmod` has no global: the sides are matched and the call is typed as
them.
let (h, f) = (prim_var("float16"), prim_var("float32"));
@@ -683,8 +684,8 @@ fn pow_and_fmod_agree_with_cpp() {
let ty: Type = PrimType::float(32).into();
let expected =
Call::new(Some(&ty), fmod, [widened, f.clone()], None,
Vec::<Type>::new(), UNKNOWN_LOC);
- assert!(structural_equal(&h.fmod(&f).expr().unwrap(), &expected.unwrap()));
- let error = prim_var("int32").fmod(imm("int32", 2)).expr().err().unwrap();
+ assert!(structural_equal(&h.fmod(&f).into_expr().unwrap(),
&expected.unwrap()));
+ let error = prim_var("int32").fmod(imm("int32",
2)).into_expr().err().unwrap();
assert!(error.message().contains("fmod only applies to float"), "{}",
error.message());
}
@@ -709,7 +710,7 @@ fn intrinsics_are_typed_calls() {
let i = prim_var("int32");
assert!(same(i.exp(), &call("int32", "prim.exp", vec![i.clone()])));
assert!(same(i.clz(), &call("int32", "prim.clz", vec![i.clone()])));
- let error = i.tan().expr().err().unwrap();
+ let error = i.tan().into_expr().err().unwrap();
assert_eq!(error.kind(), TYPE_ERROR);
assert!(error
.message()
@@ -730,9 +731,12 @@ fn intrinsics_are_typed_calls() {
assert!(same(x.copysign(&h), &call("float32", "prim.copysign",
vec![x.clone(), h.clone()])));
let fma = op::fma(&x, &h, &x).unwrap();
assert!(structural_equal(&fma, &call("float32", "prim.fma",
vec![x.clone(), h, x.clone()])));
- assert!(same(ex(op::vscale()), &call("int32", "prim.vscale", vec![])));
+ assert!(same(ExprResult::from(op::vscale()), &call("int32", "prim.vscale",
vec![])));
// Two `vscale()` calls are equal: a bool, as every comparison folds to.
- assert!(same(ex(op::vscale()).equal(ex(op::vscale())), &imm("bool", 1)));
+ assert!(same(
+ ExprResult::from(op::vscale()).equal(ExprResult::from(op::vscale())),
+ &imm("bool", 1)
+ ));
}
/// C++ `cast(value, t)` through its global.
@@ -755,7 +759,7 @@ fn cast_folds_and_broadcasts_as_cpp() {
let (constant, overflowing) = (ramp(imm("int32", 0)), ramp(imm("int32",
i32::MAX.into())));
// A value already of the type is itself.
-
assert!(x.cast(PrimType::int(32)).expr().unwrap().same_as(&Expr::from(&x)));
+
assert!(x.cast(PrimType::int(32)).into_expr().unwrap().same_as(&Expr::from(&x)));
let cases: Vec<(Expr, &str)> = vec![
// A constant folds to the new type when it is the same value there;
one the type cannot
@@ -783,7 +787,7 @@ fn cast_folds_and_broadcasts_as_cpp() {
assert!(same(value.cast(prim(name)), &native_cast(value, name)),
"{name}");
}
assert!(same(imm("int32", 5).cast(prim("int64")), &imm("int64", 5)));
- let node = |value: &Expr, name: &str|
value.cast(prim(name)).expr().unwrap();
+ let node = |value: &Expr, name: &str|
value.cast(prim(name)).into_expr().unwrap();
let broadcast = node(&Expr::from(&f), "int32x4");
let element = &broadcast.as_node::<BroadcastObj>().unwrap().value;
assert!(element.as_node::<CastObj>().is_some());
@@ -802,7 +806,9 @@ fn cast_folds_and_broadcasts_as_cpp() {
// A void value cannot be cast, with the C++ error.
let void = var("v", PrimType::void());
- let Err(error) = void.cast(PrimType::int(32)).expr() else { panic!("a void
value was cast") };
+ let Err(error) = void.cast(PrimType::int(32)).into_expr() else {
+ panic!("a void value was cast")
+ };
assert_eq!(error.kind(), TYPE_ERROR);
assert!(error.message().contains("void sentinel type"), "{}",
error.message());
}