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 37b583953 [REFACTOR][RUST] Look up operators with Op::get and remove
OpHandle (#160)
37b583953 is described below
commit 37b583953d693c232139d299d6b92ccc96c98475
Author: Linzhang Li <[email protected]>
AuthorDate: Mon Nov 2 10:53:06 2026 -0500
[REFACTOR][RUST] Look up operators with Op::get and remove OpHandle (#160)
## Summary
Operators defined in Rust are registered in
`tvm_ffi::static_init_block!`s, which run when their
library loads, so `Op::get` finds them wherever C++ `Op::Get` would find
theirs. A pass looks its
operators up with `Op::get` and tests a call with
`call.op.same_as(&op)`, as in C++:
```rust
let cmul = Op::get("my_ext.cmul")?;
if call.op.same_as(&cmul) { ... }
```
`OpHandle`, the `static` handle that looked an operator up on first use,
is removed. The extension
example and its tutorial use `Op::get`.
---
docs/advanced/defining_ir_extensions.rst | 40 +++++-----
examples/extension/src/lib.rs | 69 +++++++++++------
rust/src/ir/op_def.rs | 127 +------------------------------
rust/src/lib.rs | 12 +--
rust/tests/ir/op_def.rs | 42 ++++------
5 files changed, 90 insertions(+), 200 deletions(-)
diff --git a/docs/advanced/defining_ir_extensions.rst
b/docs/advanced/defining_ir_extensions.rst
index 1ba916634..50458ea45 100644
--- a/docs/advanced/defining_ir_extensions.rst
+++ b/docs/advanced/defining_ir_extensions.rst
@@ -153,25 +153,26 @@ that pair. This lets the rules apply to the step-by-step
function from
R[i] = product_re
Im[i] = product_im
-Only scalar arithmetic remains. The pass refers to the four operators through
-handles:
+Only scalar arithmetic remains. The pass looks up the four operators by name,
+as C++ does with ``Op::Get``:
.. literalinclude:: ../../examples/extension/src/lib.rs
:language: rust
- :start-after: // begin handles
- :end-before: // end handles
+ :start-after: // begin operators
+ :end-before: // end operators
-A handle names a registered operator and looks it up on first use.
-``CMUL.is(&call.op)`` tests whether a call's ``op`` is ``my_ext.cmul``, and
-``COMPLEX.get()?`` returns ``my_ext.complex`` to build a new call. ``Lowering``
-implements these rewrites with them:
+The operators are registered when the library loads, and ``Op::get`` returns
+the registry's single object for a name. ``call.op.same_as(&ops.cmul)``
+therefore tests whether a call's ``op`` is ``my_ext.cmul`` by comparing
+pointers. A lookup goes through the registry, so the pass fetches the operators
+once per run and keeps them. ``Lowering`` implements the rewrites with them:
.. literalinclude:: ../../examples/extension/src/lib.rs
:language: rust
:start-after: // begin lowering
:end-before: // end lowering
- :prepend: #[derive(Default)]
- struct Lowering {
+ :prepend: struct Lowering {
+ ops: ComplexOps,
bindings: Vec<(Var, Var, Var)>,
}
impl Lowering {
@@ -182,13 +183,13 @@ implements these rewrites with them:
fn map_sequence(&mut self, seq: SeqStmt) -> SeqStmt { ... }
:append: }
-``bindings`` records each complex variable with its two scalar replacements,
-and ``parts`` reads the real and imaginary parts of an operand, either a
-``complex(re, im)`` call or a variable already split. ``#[dispatch(map)]``
-sends each node to the ``map_`` method of its type, and leaves other nodes
-unchanged: ``map_call`` applies the three rules, ``map_bind`` splits a complex
-binding, and ``map_sequence`` flattens the sequence that a split binding leaves
-inside another.
+``ops`` holds the four operators, ``bindings`` records each complex variable
+with its two scalar replacements, and ``parts`` reads the real and imaginary
+parts of an operand, either a ``complex(re, im)`` call or a variable already
+split. ``#[dispatch(map)]`` sends each node to the ``map_`` method of its type,
+and leaves other nodes unchanged: ``map_call`` applies the three rules,
+``map_bind`` splits a complex binding, and ``map_sequence`` flattens the
+sequence that a split binding leaves inside another.
The pass is built from an ordinary named function:
@@ -202,7 +203,8 @@ rewritten function; ``Some`` replaces the original. The
first ``structural_map``
walks the function with ``Lowering``. ``PostOrder`` visits children before
their
parent, so the operands of ``cmul`` are lowered before ``cmul`` itself. The
second ``structural_map`` only checks the result. It takes a tuple of three
-functions, and calls each on the nodes of its parameter type:
+functions, and calls each on the nodes of its parameter type; the closure
+passes the operators to ``check_call_lowered``:
.. code-block:: rust
@@ -213,7 +215,7 @@ functions, and calls each on the nodes of its parameter
type:
fn check_expr(expr: Expr) -> Result<Expr> { ... }
/// Reject a call to one of the four complex operators that the rewrite did
not consume.
- fn check_call_lowered(call: Call) -> Result<Call> { ... }
+ fn check_call_lowered(ops: &ComplexOps, call: Call) -> Result<Call> { ... }
Each returns its node unchanged and raises a ``ValueError`` on an unsupported
use, such as a complex function parameter, so the pass reports such a use
diff --git a/examples/extension/src/lib.rs b/examples/extension/src/lib.rs
index 0f6fdeda1..5fa78ebb7 100644
--- a/examples/extension/src/lib.rs
+++ b/examples/extension/src/lib.rs
@@ -24,8 +24,8 @@ use tvm_tirx::ir::op_attr::CALL_EFFECT_KIND;
use tvm_tirx::ir::sig::{self, PrimExpr};
use tvm_tirx::ir::transform::{Pass, PassContext, PassInfo};
use tvm_tirx::ir::{
- dtype, register_type, Bind, Call, CallEffectKind, CallObj, Expr, IRModule,
Op, OpDef, OpHandle,
- PrimType, SeqStmt, Stmt, Type, TypeObj, Var,
+ dtype, register_type, Bind, Call, CallEffectKind, CallObj, Expr, IRModule,
Op, OpDef, PrimType,
+ SeqStmt, Stmt, Type, TypeObj, Var,
};
use tvm_tirx::tirx::transform::FunctionPass;
use tvm_tirx::tirx::Function;
@@ -115,12 +115,11 @@ fn infer_part(call: &CallObj) -> Result<Type> {
// Explicit result types bypass inference, so validation checks the inputs and
result again.
fn validate_call(call: &CallObj) -> Result<()> {
- let expected = if COMPLEX.is(&call.op) {
- infer_complex(call)?
- } else if CMUL.is(&call.op) {
- infer_cmul(call)?
- } else {
- infer_part(call)?
+ let op: Op = call.op.clone().try_cast()?;
+ let expected = match op.name.as_str() {
+ "my_ext.complex" => infer_complex(call)?,
+ "my_ext.cmul" => infer_cmul(call)?,
+ _ => infer_part(call)?,
};
let matches: bool = tvm_ffi::cached_global_func!("ffi.StructuralEqual")
.call_tuple((&expected, &call.ty, false, false))?
@@ -162,15 +161,33 @@ tvm_ffi::static_init_block! {
.set_attr(CALL_EFFECT_KIND, CallEffectKind::Pure);
}
-// begin handles
-static COMPLEX: OpHandle = OpHandle::new("my_ext.complex");
-static CMUL: OpHandle = OpHandle::new("my_ext.cmul");
-static REAL: OpHandle = OpHandle::new("my_ext.real");
-static IMAG: OpHandle = OpHandle::new("my_ext.imag");
-// end handles
+// begin operators
+struct ComplexOps {
+ complex: Op,
+ cmul: Op,
+ real: Op,
+ imag: Op,
+}
+
+impl ComplexOps {
+ fn get() -> Result<Self> {
+ Ok(Self {
+ complex: Op::get("my_ext.complex")?,
+ cmul: Op::get("my_ext.cmul")?,
+ real: Op::get("my_ext.real")?,
+ imag: Op::get("my_ext.imag")?,
+ })
+ }
+
+ /// Whether `op`, such as a call's `op`, is one of the four operators.
+ fn contains(&self, op: &Expr) -> bool {
+ [&self.complex, &self.cmul, &self.real,
&self.imag].into_iter().any(|own| op.same_as(own))
+ }
+}
+// end operators
-#[derive(Default)]
struct Lowering {
+ ops: ComplexOps,
// Each complex local variable is replaced by two scalar local variables.
bindings: Vec<(Var, Var, Var)>,
}
@@ -178,7 +195,7 @@ struct Lowering {
impl Lowering {
fn parts(&self, expr: &Expr) -> Result<(Expr, Expr)> {
if let Some(call) = expr.as_node::<CallObj>() {
- if COMPLEX.is(&call.op) {
+ if call.op.same_as(&self.ops.complex) {
return Ok((call.args.get(0)?, call.args.get(1)?));
}
}
@@ -199,10 +216,11 @@ impl Lowering {
// begin lowering
fn map_call(&mut self, call: Call) -> Result<Expr> {
- if COMPLEX.is(&call.op) || CMUL.is(&call.op) || REAL.is(&call.op) ||
IMAG.is(&call.op) {
+ let ops = &self.ops;
+ if ops.contains(&call.op) {
validate_call(&call)?;
}
- if CMUL.is(&call.op) {
+ if call.op.same_as(&ops.cmul) {
let (a, b) = self.parts(&call.args.get(0)?)?;
let (c, d) = self.parts(&call.args.get(1)?)?;
// (a + bi)(c + di) = (ac - bd) + (ad + bc)i
@@ -210,7 +228,7 @@ impl Lowering {
let im = &a * &d + &b * &c;
return Ok(Call::new(
None,
- COMPLEX.get()?,
+ ops.complex.clone(),
[re, im],
None,
Vec::<Type>::new(),
@@ -218,9 +236,9 @@ impl Lowering {
)?
.into());
}
- if REAL.is(&call.op) || IMAG.is(&call.op) {
+ if call.op.same_as(&ops.real) || call.op.same_as(&ops.imag) {
let (re, im) = self.parts(&call.args.get(0)?)?;
- return Ok(if REAL.is(&call.op) { re } else { im });
+ return Ok(if call.op.same_as(&ops.real) { re } else { im });
}
Ok(call.into())
}
@@ -262,8 +280,8 @@ fn check_expr(expr: Expr) -> Result<Expr> {
}
/// Reject a call to one of the four complex operators that the rewrite did
not consume.
-fn check_call_lowered(call: Call) -> Result<Call> {
- if COMPLEX.is(&call.op) || CMUL.is(&call.op) || REAL.is(&call.op) ||
IMAG.is(&call.op) {
+fn check_call_lowered(ops: &ComplexOps, call: Call) -> Result<Call> {
+ if ops.contains(&call.op) {
return Err(Error::new(
tvm_ffi::VALUE_ERROR,
"LowerComplex left an unsupported complex call",
@@ -279,11 +297,12 @@ fn lower_function(
_module: IRModule,
_ctx: PassContext,
) -> Result<Option<Function>> {
- let mut lowering = Lowering::default();
+ let mut lowering = Lowering { ops: ComplexOps::get()?, bindings:
Vec::new() };
let rewritten = tvm_ffi::structural_map(func, &mut lowering,
WalkOrder::PostOrder)?;
+ let ops = &lowering.ops;
let checked = tvm_ffi::structural_map(
rewritten,
- (check_type, check_call_lowered, check_expr),
+ (check_type, |call| check_call_lowered(ops, call), check_expr),
WalkOrder::PostOrder,
)?;
let function: Function = checked.try_into()?;
diff --git a/rust/src/ir/op_def.rs b/rust/src/ir/op_def.rs
index 07d846578..c91b09345 100644
--- a/rust/src/ir/op_def.rs
+++ b/rust/src/ir/op_def.rs
@@ -19,8 +19,7 @@
//! Registering operators from Rust: C++ `OpDef`, with its signature
descriptors ([`sig`]) and
//! attribute keys ([`op_attr`]), whose chains a
[`tvm_ffi::static_init_block!`] runs to define
-//! operators, as C++ defines them in a `TVM_FFI_STATIC_INIT_BLOCK`; and the
handles that keep
-//! operators in `static`s.
+//! operators, as C++ defines them in a `TVM_FFI_STATIC_INIT_BLOCK`.
//!
//! Every hook an operator gets here is native, as the C++ ones are: a Rust
function compiled into
//! the C ABI adapter that C++ `NativeFunctionView` calls through a function
pointer, without
@@ -34,17 +33,12 @@
//! links its own copy of a crate they share, runs once.
use std::any::TypeId;
-use std::cell::RefCell;
use std::ffi::c_void;
use std::marker::PhantomData;
use std::panic::{catch_unwind, AssertUnwindSafe};
-use std::sync::atomic::{AtomicPtr, AtomicUsize, Ordering};
-use std::sync::OnceLock;
-use tvm_ffi::tvm_ffi_sys::{TVMFFIAny, TVMFFIObject, TVMFFITypeIndex};
-use tvm_ffi::{
- Any, AnyView, Array, Error, ObjectArc, ObjectRefCore, Result, String,
TYPE_ERROR, VALUE_ERROR,
-};
+use tvm_ffi::tvm_ffi_sys::{TVMFFIAny, TVMFFITypeIndex};
+use tvm_ffi::{Any, AnyView, Array, Error, ObjectRefCore, Result, String,
TYPE_ERROR, VALUE_ERROR};
use super::attrs::Attrs;
use super::expr::{CallObj, Expr, Var};
@@ -345,121 +339,6 @@ impl From<CallEffectKind> for Any {
}
}
-/// A handle to an operator, kept in a `static` as C++ keeps one in `const Op&
xxx_op()`. The
-/// operator is looked up by name once per thread; [`OpHandle::get`] returns
it and
-/// [`OpHandle::is`] compares with it. The [`tvm_ffi::static_init_block!`]s
that define operators
-/// run when their program or library loads; where the loader does not run
them, the first lookup
-/// of an operator that is not registered runs them, with
[`tvm_ffi::run_static_init_blocks`].
-///
-/// ```no_run
-/// use tvm_tirx::ir::sig::{self, IntExpr};
-/// use tvm_tirx::ir::{Call, CallObj, Expr, OpDef, OpHandle, PrimType, Type,
Var, UNKNOWN_LOC};
-/// use tvm_tirx::tvm_ffi::{self, Result};
-///
-/// fn first_arg_type(call: &CallObj) -> Result<Type> {
-/// Ok(call.args.get(0)?.ty.clone())
-/// }
-///
-/// tvm_ffi::static_init_block! {
-/// OpDef::new("my_ext.clamp", "Clamp an index to a bound.")
-/// .signature((sig::arg::<IntExpr>("index", ""),
sig::arg::<IntExpr>("bound", "")))
-/// .set_infer_type(first_arg_type);
-/// }
-///
-/// static CLAMP: OpHandle = OpHandle::new("my_ext.clamp");
-/// static LAUNCH_THREAD: OpHandle = OpHandle::new("tirx.launch_thread");
-///
-/// let index = Var::new("i", PrimType::int(32), UNKNOWN_LOC);
-/// let bound = Var::new("n", PrimType::int(32), UNKNOWN_LOC);
-/// let args = [Expr::from(&index), Expr::from(&bound)];
-/// let clamped = Call::new(None, CLAMP.get()?, args, None,
Vec::<Type>::new(), UNKNOWN_LOC)?;
-/// assert!(CLAMP.is(&clamped.op) && !LAUNCH_THREAD.is(&clamped.op));
-/// # Ok::<(), tvm_tirx::tvm_ffi::Error>(())
-/// ```
-pub struct OpHandle {
- name: &'static str,
- /// This handle's slot in each thread's cache of fetched operators.
- slot: OnceLock<usize>,
- /// The operator once fetched, for identity tests: operators are registry
singletons that
- /// live for the process, and `is` is the hot path of every pass that
looks at `Call::op`.
- object: AtomicPtr<TVMFFIObject>,
-}
-
-/// The number of slots handed out to handles.
-static SLOTS: AtomicUsize = AtomicUsize::new(0);
-
-thread_local! {
- /// The operators this thread fetched, by handle slot.
- static FETCHED: RefCell<Vec<Option<Op>>> = const {
RefCell::new(Vec::new()) };
-}
-
-impl OpHandle {
- /// The operator `name`, registered in C++, in Python or by a Rust
[`OpDef`].
- pub const fn new(name: &'static str) -> Self {
- Self { name, slot: OnceLock::new(), object:
AtomicPtr::new(std::ptr::null_mut()) }
- }
-
- pub fn name(&self) -> &'static str {
- self.name
- }
-
- /// The operator. One that is not registered runs the blocks first; if it
is still missing, the
- /// error says so, followed by the failures of the blocks, if any.
- pub fn get(&self) -> Result<Op> {
- self.with(Op::clone)
- }
-
- /// Whether `value`, such as a Call's `op`, is this operator (C++
`value.same_as(op)`). No
- /// value is an operator that cannot be fetched, such as one this build
does not register.
- pub fn is(&self, value: &impl ObjectRefCore) -> bool {
- let cached = self.object.load(Ordering::Acquire);
- if !cached.is_null() {
- // SAFETY: the pointer is only compared, never dereferenced.
- let pointer = unsafe {
ObjectArc::as_raw(ObjectRefCore::data(value)) };
- return pointer.cast::<TVMFFIObject>().cast_mut() == cached;
- }
- self.with(|op| {
- // Keep one reference so the pointer stays valid for the process
(a second thread
- // racing here leaks one more, which is harmless for a registry
singleton).
- // SAFETY: the raw pointer is never turned back into an
`ObjectArc`.
- let pointer = unsafe {
ObjectArc::into_raw(Op::into_data(op.clone())) };
- self.object.store(pointer.cast::<TVMFFIObject>().cast_mut(),
Ordering::Release);
- value.same_as(op)
- })
- .unwrap_or(false)
- }
-
- /// `use_op` applied to the operator, fetched into this thread's slot the
first time.
- fn with<R>(&self, use_op: impl FnOnce(&Op) -> R) -> Result<R> {
- let slot = *self.slot.get_or_init(|| SLOTS.fetch_add(1,
Ordering::Relaxed));
- let cached = FETCHED.with(|fetched|
fetched.borrow().get(slot).cloned().flatten());
- let op = match cached {
- Some(op) => op,
- None => {
- let op = Op::get(self.name).or_else(|missing| {
- let registered = tvm_ffi::run_static_init_blocks();
- Op::get(self.name).map_err(|_| match registered {
- Ok(()) => missing,
- Err(failed) => {
- let message = format!("{}\n{}", missing.message(),
failed.message());
- Error::new(missing.kind(), &message, "")
- }
- })
- })?;
- FETCHED.with(|fetched| {
- let mut fetched = fetched.borrow_mut();
- if fetched.len() <= slot {
- fetched.resize(slot + 1, None);
- }
- fetched[slot] = Some(op.clone());
- });
- op
- }
- };
- Ok(use_op(&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 {
diff --git a/rust/src/lib.rs b/rust/src/lib.rs
index f92a0945e..83fc8b6fb 100644
--- a/rust/src/lib.rs
+++ b/rust/src/lib.rs
@@ -110,7 +110,7 @@
//! ```no_run
//! use tvm_tirx::ir::op_attr::CALL_EFFECT_KIND;
//! use tvm_tirx::ir::sig::{self, IntExpr};
-//! use tvm_tirx::ir::{register_type, CallEffectKind, CallObj, OpDef,
OpHandle, Type};
+//! use tvm_tirx::ir::{register_type, CallEffectKind, CallObj, Op, OpDef,
Type};
//! use tvm_tirx::tvm_ffi::{self, Result};
//! # use tvm_tirx::ir::TypeObj;
//! # use tvm_tirx::tvm_ffi::{def_object_ref, rs_class, ObjectRefCore};
@@ -133,13 +133,15 @@
//! register_type::<PairType>()?;
//! }
//!
-//! static CLAMP: OpHandle = OpHandle::new("my_ext.clamp");
+//! let clamp = Op::get("my_ext.clamp")?;
+//! # Ok::<(), tvm_ffi::Error>(())
//! ```
//!
-//! An [`ir::OpHandle`] in a `static` keeps an operator as C++ keeps one in
`const Op& xxx_op()`.
//! A block runs when its program or library loads, as a C++ one does,
wherever in it the block
-//! is. Every library built on the crate exports
[`tvm_ffi::run_static_init_blocks`], which
-//! reports the errors of the blocks that failed, as the TVM FFI function
`tirx_register_ir`.
+//! is. [`ir::Op::get`] then returns the operator, as C++ `Op::Get` does: the
registry keeps one
+//! object per name, so `call.op.same_as(&clamp)` tests whether a call is to
it. Every library
+//! built on the crate exports [`tvm_ffi::run_static_init_blocks`], which
reports the errors of the
+//! blocks that failed, as the TVM FFI function `tirx_register_ir`.
//!
//! # The library Python loads
//!
diff --git a/rust/tests/ir/op_def.rs b/rust/tests/ir/op_def.rs
index 80d981346..10d521e50 100644
--- a/rust/tests/ir/op_def.rs
+++ b/rust/tests/ir/op_def.rs
@@ -18,8 +18,7 @@
*/
//! Tests of `ir::op_def`: operators registered from Rust, by an `OpDef` or in
static init blocks,
-//! which run when the test binary loads or at the first lookup through an
`OpHandle`, are used by
-//! the native registry, `Call` constructor and validation, and their type
inference, validators
+//! which run when the test binary loads, are used by the native registry,
`Call` constructor and validation, and their type inference, validators
//! and region contracts are native hooks; and types registered with them.
//!
//! The registry does not synchronize registration, so the tests take
`serial()`; the tests
@@ -32,8 +31,8 @@ use tvm_tirx::ir::op_attr::{CALL_EFFECT_KIND,
FIXED_RETURN_TYPE};
use tvm_tirx::ir::sig::{self, IntExpr, PrimExpr, PrimVar};
use tvm_tirx::ir::{
register_type, AttrKey, Attrs, Call, CallEffectKind, CallObj, DictAttrs,
Evaluate, Expr,
- FuncType, IntImm, Op, OpDef, OpHandle, PrimType, RegionStmt,
RegionStmtObj, StringImm, Type,
- TypeObj, Var, VarObj, UNKNOWN_LOC,
+ FuncType, IntImm, Op, OpDef, PrimType, RegionStmt, RegionStmtObj,
StringImm, Type, TypeObj,
+ Var, VarObj, UNKNOWN_LOC,
};
use tvm_tirx::tirx::{CallFFIKernelAttr, TensorVar};
use tvm_tirx::tvm_ffi::static_init::{StaticInitBlock, STATIC_INIT_BLOCKS};
@@ -487,12 +486,6 @@ fn is_block_registered(block: &StaticInitBlock) -> bool {
Function::get_global(&block.marker()).is_ok()
}
-static IDENTITY: OpHandle = OpHandle::new("rust_test.defined_identity");
-static LAUNCH: OpHandle = OpHandle::new("rust_test.launch");
-static PANICKING: OpHandle = OpHandle::new("rust_test.panicking_region");
-static LAUNCH_THREAD: OpHandle = OpHandle::new("tirx.launch_thread");
-static UNREGISTERED: OpHandle = OpHandle::new("rust_test.unregistered");
-
fn launch_body_params(call: &CallObj) -> Result<Array<Var>> {
let tag = call.args.get(0)?.try_cast::<StringImm>()?;
let extent_ty: PrimType = call.args.get(1)?.ty.clone().try_cast()?;
@@ -593,7 +586,6 @@ fn static_init_blocks_define_operators_and_types() {
// The blocks ran when the test binary loaded; a block that fails does not
keep the others from
// registering theirs.
let identity = Op::get("rust_test.defined_identity").unwrap();
- assert!(identity.same_as(&IDENTITY.get().unwrap()));
assert_eq!(identity.doc.as_str(), "Return the value.\n\nThe documentation
is dedented.");
assert_eq!(identity.args_info.get(0).unwrap().name.as_str(), "value");
let effect = Op::attr_map::<i64>("TCallEffectKind").unwrap().unwrap();
@@ -652,29 +644,26 @@ fn static_init_blocks_define_operators_and_types() {
assert_eq!(failing.len(), 2);
assert!(!failing.into_iter().any(is_block_registered));
- // The handle keeps the operator: the Rust `FInferType` types a Call of
it, and another thread
- // sees the same operator.
- assert!(IDENTITY.get().unwrap().same_as(&identity));
+ // The Rust `FInferType` types a Call of the operator. The registry keeps
one object per name,
+ // for every lookup and thread, so `same_as` tests which operator a Call
is to.
let x = var("x", "int32");
let call = call(&identity, vec![Expr::from(&x)]).unwrap();
assert!(call.ty.same_as(&x.ty), "the Rust FInferType types the Call");
- assert!(IDENTITY.is(&call.op));
- assert!(!LAUNCH_THREAD.is(&call.op));
- let name = std::thread::spawn(||
IDENTITY.get().unwrap().name.as_str().to_string());
+ assert!(call.op.same_as(&Op::get("rust_test.defined_identity").unwrap()));
+ let native = Op::get("tirx.launch_thread").unwrap();
+ assert!(!call.op.same_as(&native));
+ let name = std::thread::spawn(|| {
+
Op::get("rust_test.defined_identity").unwrap().name.as_str().to_string()
+ });
assert_eq!(name.join().unwrap(), "rust_test.defined_identity");
-
- // A handle to an operator C++ registers looks it up; one to an operator
nothing registers
- // fails, with the failures of the blocks.
-
assert!(LAUNCH_THREAD.get().unwrap().same_as(&Op::get("tirx.launch_thread").unwrap()));
assert_error(
- UNREGISTERED.get(),
+ Op::get("rust_test.unregistered"),
ATTRIBUTE_ERROR,
- "Operator rust_test.unregistered is not registered\n2 static init
blocks failed:",
+ "Operator rust_test.unregistered is not registered",
);
- assert!(!UNREGISTERED.is(&call.op));
// The region hooks are native function pointers.
- let launch = LAUNCH.get().unwrap();
+ let launch = Op::get("rust_test.launch").unwrap();
for key in ["FRegionGetBodyParams", "FRegionValidate"] {
let hooks = Op::attr_map::<*mut c_void>(key).unwrap().unwrap();
assert!(hooks.get(&launch).unwrap().is_some(), "{key}");
@@ -682,7 +671,6 @@ fn static_init_blocks_define_operators_and_types() {
// The `RegionStmt` constructor checks a region of the Rust operator as it
checks one of
// `tirx.launch_thread`: the same outcome, and the same error but for the
operator's name.
- let native = LAUNCH_THREAD.get().unwrap();
let extent: Expr = int32(8);
let wide: Expr = IntImm::from_dtype(parse_dtype("int64"), 8,
UNKNOWN_LOC).unwrap().into();
let flag: Expr = IntImm::bool(true).into();
@@ -712,7 +700,7 @@ fn static_init_blocks_define_operators_and_types() {
}
// A panic in a hook becomes an error instead of unwinding into C++.
- let panicking = PANICKING.get().unwrap();
+ let panicking = Op::get("rust_test.panicking_region").unwrap();
let body = Evaluate::new(0, UNKNOWN_LOC);
let empty = DictAttrs::new(Map::new());
let built = RegionStmt::new(