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-ffi.git
The following commit(s) were added to refs/heads/main by this push:
new 9faeaff3 [Rust] Reorder object, error, array, macro and structural
engine files; inline dispatch handlers, cold visitor stop path (#946)
9faeaff3 is described below
commit 9faeaff3532b89746f6af841e616e1ec9b520871
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 15:53:14 2026 -0500
[Rust] Reorder object, error, array, macro and structural engine files;
inline dispatch handlers, cold visitor stop path (#946)
Reorder items within each file so it reads top-down: key data structures
first, then the main flow, then helpers. `object.rs` leads with
`Object`, `ObjectArc` (with its impls, `ImmutableObject` and its
`Send`/`Sync` next to `Clone`) and `ObjectRef`, then the traits, then
`ObjectIdentity`/`is_instance_of`/`fmt_object_ref`, then a Helpers
section (the crate-private `root_*` helpers, `type_key_of_index`,
`write_object_cell`, `allocate_object`), then `unsafe_`. `error.rs`
leads with `ErrorObj`, `Error` and `Result`, then `ErrorKind` and its
constants, then `impl Error` with the private `make_mut` after the
public accessors, with the std impls at the end. `collections/array.rs`
puts the private `Array` methods after the public ones and groups the
rest into Index, Iterators, Any conversions and Helpers (`write_cell`)
sections. `macros.rs` moves `unsafe_impl_object_ref_upcast!` and
`unsafe_impl_object_ref_downcast_ref!` out of the error-handling section
into their own "Macros for object references" section.
`extra/structural_visit.rs` and `extra/structural_mutate.rs` now read:
data structures (each type with its impls), public API and entrypoint,
the engine (its two `impl` blocks joined), prefix operations, private
utilities, outlined errors. The reorder commit is moves only; review it
with `--color-moved`.
Three small follow-ups ride along. `#[dispatch]` marks handlers
`#[inline(always)]` unless they already carry an `inline` attribute, so
a traversal no longer pays an outlined handler frame per node (no-op
mutate 210 to 158 ns on a 17-node tree, matching map).
`StructuralVisitorObj::with_visit_context_raw` returns a None carrier
directly and sends the rest to a `#[cold]` `#[inline(never)]` helper, so
continuing visits keep no input copies for error annotation (native-hook
visits 7–9% faster, DAG walk/visit about 3.5%).
`tests/python/test_stubgen_rust.py` gets the ruff-format fix that `main`
has been failing since #945.
---
rust/tvm-ffi-macros/src/dispatch.rs | 18 +-
rust/tvm-ffi/src/collections/array.rs | 172 ++---
rust/tvm-ffi/src/error.rs | 124 ++--
rust/tvm-ffi/src/extra/structural_mutate.rs | 979 ++++++++++++++--------------
rust/tvm-ffi/src/extra/structural_visit.rs | 941 +++++++++++++-------------
rust/tvm-ffi/src/lib.rs | 3 +-
rust/tvm-ffi/src/macros.rs | 317 ++++-----
rust/tvm-ffi/src/object.rs | 614 ++++++++---------
tests/python/test_stubgen_rust.py | 4 +-
9 files changed, 1617 insertions(+), 1555 deletions(-)
diff --git a/rust/tvm-ffi-macros/src/dispatch.rs
b/rust/tvm-ffi-macros/src/dispatch.rs
index 2e9b4ff1..f2bbee50 100644
--- a/rust/tvm-ffi-macros/src/dispatch.rs
+++ b/rust/tvm-ffi-macros/src/dispatch.rs
@@ -29,7 +29,8 @@ use crate::utils::get_tvm_ffi_crate;
pub(crate) fn dispatch(attr: TokenStream, item: TokenStream) -> TokenStream {
let args = parse_macro_input!(attr as DispatchArgs);
- let item_impl = parse_macro_input!(item as ItemImpl);
+ let mut item_impl = parse_macro_input!(item as ItemImpl);
+ inline_handlers(&mut item_impl, &args);
match expand(&item_impl, &args) {
Ok(generated) => quote!(#item_impl #generated).into(),
Err(error) => {
@@ -40,6 +41,21 @@ pub(crate) fn dispatch(attr: TokenStream, item: TokenStream)
-> TokenStream {
}
}
+// Handlers sit on the hot path of every dispatched value, and outlining them
+// costs a frame per node, so inline them unless the user chose otherwise.
+fn inline_handlers(item: &mut ItemImpl, args: &DispatchArgs) {
+ let method_prefix = format!("{}_", args.prefix);
+ for member in &mut item.items {
+ if let ImplItem::Method(method) = member {
+ if method.sig.ident.to_string().starts_with(&method_prefix)
+ && !method.attrs.iter().any(|attr|
attr.path.is_ident("inline"))
+ {
+ method.attrs.push(syn::parse_quote!(#[inline(always)]));
+ }
+ }
+ }
+}
+
struct DispatchArgs {
prefix: syn::Ident,
arguments: syn::TypeTuple,
diff --git a/rust/tvm-ffi/src/collections/array.rs
b/rust/tvm-ffi/src/collections/array.rs
index 24539917..5d00e5aa 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -52,13 +52,6 @@ unsafe impl ObjectCoreWithExtraItems for ArrayObj {
}
}
-/// Move `value` into the uninitialized `cell`.
-unsafe fn write_cell<T: ContainerElement>(cell: *mut TVMFFIAny, value: T) {
- let mut raw = TVMFFIAny::new();
- T::container_move_to_any(value, &mut raw);
- std::ptr::write(cell, raw);
-}
-
// Runs only for a Rust-allocated array; one allocated in C++ is released by
its
// own deleter.
impl Drop for ArrayObj {
@@ -217,33 +210,6 @@ impl<T: ContainerElement + Clone> Array<T> {
Self::new_with_capacity(items, capacity)
}
- /// Internal helper to allocate an ArrayObj with specific headroom.
- fn new_with_capacity(items: Vec<T>, capacity: usize) -> Self {
- let size = items.len();
-
- // Allocate with capacity
- let mut arc = ObjectArc::<ArrayObj>::new_with_extra_items(ArrayObj {
- object: Object::new(),
- data: core::ptr::null_mut(),
- size: size as i64,
- capacity: capacity as i64,
- data_deleter: None,
- });
-
- unsafe {
- let container = &mut *ObjectArc::as_mut_ptr(&mut arc);
-
- let base_ptr = ArrayObj::extra_items_mut(container).as_mut_ptr();
- container.data = base_ptr as *mut _;
-
- for (i, item) in items.into_iter().enumerate() {
- write_cell(base_ptr.add(i), item);
- }
- }
- // SAFETY: `arc` was allocated and initialized above as an `Array<T>`.
- unsafe { Self::from_data(arc) }
- }
-
/// Append `value` to the end.
///
/// See also [`List::push`](crate::List::push).
@@ -345,50 +311,6 @@ impl<T: ContainerElement + Clone> Array<T> {
Ok(())
}
- /// Make this handle the only reference to its cells, with room for at
least
- /// `capacity` of them, and return the container to edit in place.
- fn make_mut(&mut self, capacity: usize) -> &mut ArrayObj {
- // As C++ `Array::CopyOnWrite(reserve)`.
- let unique = ObjectArc::is_unique(&self.data);
- let current = self.data.capacity as usize;
- if !unique || capacity > current {
- let capacity = if capacity > current {
- (current * 2).max(capacity)
- } else {
- current
- };
- self.copy_to_new_buffer(capacity, unique);
- }
- // SAFETY: this handle is now the only reference, so no one else
observes
- // the container while it is mutably borrowed.
- unsafe { &mut *ObjectArc::as_mut_ptr(&mut self.data) }
- }
-
- /// Replace `self` with a unique array of `capacity` cells holding the same
- /// elements: moved out of a `unique` source, otherwise each re-referenced.
- fn copy_to_new_buffer(&mut self, capacity: usize, unique: bool) {
- let size = self.len();
- let mut grown = Self::new_with_capacity(Vec::new(), capacity);
- let source = self.as_container().data as *const TVMFFIAny;
- // SAFETY: `grown` is fresh with room for `size` cells. A unique source
- // forgets the cells it moved; a shared one keeps its own references.
- unsafe {
- let target = ObjectArc::as_mut_ptr(&mut grown.data);
- let cells = (*target).data as *mut TVMFFIAny;
- if unique {
- std::ptr::copy_nonoverlapping(source, cells, size);
- (*ObjectArc::as_mut_ptr(&mut self.data)).size = 0;
- } else {
- for i in 0..size {
- let copy =
Any::from(AnyView::from_raw_ffi_any(*source.add(i)));
- std::ptr::write(cells.add(i), Any::into_raw_ffi_any(copy));
- }
- }
- (*target).size = size as i64;
- }
- *self = grown;
- }
-
/// Return the number of elements.
///
/// # Returns
@@ -442,6 +364,77 @@ impl<T: ContainerElement + Clone> Array<T> {
}
}
+ /// Internal helper to allocate an ArrayObj with specific headroom.
+ fn new_with_capacity(items: Vec<T>, capacity: usize) -> Self {
+ let size = items.len();
+
+ // Allocate with capacity
+ let mut arc = ObjectArc::<ArrayObj>::new_with_extra_items(ArrayObj {
+ object: Object::new(),
+ data: core::ptr::null_mut(),
+ size: size as i64,
+ capacity: capacity as i64,
+ data_deleter: None,
+ });
+
+ unsafe {
+ let container = &mut *ObjectArc::as_mut_ptr(&mut arc);
+
+ let base_ptr = ArrayObj::extra_items_mut(container).as_mut_ptr();
+ container.data = base_ptr as *mut _;
+
+ for (i, item) in items.into_iter().enumerate() {
+ write_cell(base_ptr.add(i), item);
+ }
+ }
+ // SAFETY: `arc` was allocated and initialized above as an `Array<T>`.
+ unsafe { Self::from_data(arc) }
+ }
+
+ /// Make this handle the only reference to its cells, with room for at
least
+ /// `capacity` of them, and return the container to edit in place.
+ fn make_mut(&mut self, capacity: usize) -> &mut ArrayObj {
+ // As C++ `Array::CopyOnWrite(reserve)`.
+ let unique = ObjectArc::is_unique(&self.data);
+ let current = self.data.capacity as usize;
+ if !unique || capacity > current {
+ let capacity = if capacity > current {
+ (current * 2).max(capacity)
+ } else {
+ current
+ };
+ self.copy_to_new_buffer(capacity, unique);
+ }
+ // SAFETY: this handle is now the only reference, so no one else
observes
+ // the container while it is mutably borrowed.
+ unsafe { &mut *ObjectArc::as_mut_ptr(&mut self.data) }
+ }
+
+ /// Replace `self` with a unique array of `capacity` cells holding the same
+ /// elements: moved out of a `unique` source, otherwise each re-referenced.
+ fn copy_to_new_buffer(&mut self, capacity: usize, unique: bool) {
+ let size = self.len();
+ let mut grown = Self::new_with_capacity(Vec::new(), capacity);
+ let source = self.as_container().data as *const TVMFFIAny;
+ // SAFETY: `grown` is fresh with room for `size` cells. A unique source
+ // forgets the cells it moved; a shared one keeps its own references.
+ unsafe {
+ let target = ObjectArc::as_mut_ptr(&mut grown.data);
+ let cells = (*target).data as *mut TVMFFIAny;
+ if unique {
+ std::ptr::copy_nonoverlapping(source, cells, size);
+ (*ObjectArc::as_mut_ptr(&mut self.data)).size = 0;
+ } else {
+ for i in 0..size {
+ let copy =
Any::from(AnyView::from_raw_ffi_any(*source.add(i)));
+ std::ptr::write(cells.add(i), Any::into_raw_ffi_any(copy));
+ }
+ }
+ (*target).size = size as i64;
+ }
+ *self = grown;
+ }
+
/// The cells of the array, one per element.
#[inline]
fn cells(&self) -> &[TVMFFIAny] {
@@ -463,6 +456,10 @@ impl<T: ContainerElement + Clone> Array<T> {
}
}
+//
-----------------------------------------------------------------------------
+// Index
+//
-----------------------------------------------------------------------------
+
/// Lends `&T` from the array's cell in place. Every cell of an `Array<T>`
/// holds a `T` (checked when the array is built), so only a debug check runs.
impl<T: ContainerElement + Clone + RefFromAnyUnchecked> std::ops::Index<usize>
for Array<T> {
@@ -485,6 +482,10 @@ impl<T: ContainerElement + Clone + RefFromAnyUnchecked>
std::ops::Index<usize> f
}
}
+//
-----------------------------------------------------------------------------
+// Iterators
+//
-----------------------------------------------------------------------------
+
impl<T: ContainerElement + Clone + RefFromAnyUnchecked> Array<T> {
/// Return an iterator that borrows each element in place, without a
/// reference-count bump, as `array[i]` does.
@@ -602,6 +603,10 @@ impl<T: ContainerElement + Clone> FromIterator<T> for
Array<T> {
}
}
+//
-----------------------------------------------------------------------------
+// Any conversions
+//
-----------------------------------------------------------------------------
+
unsafe impl<T> AnyCompatible for Array<T>
where
T: ContainerElement + Clone,
@@ -770,3 +775,14 @@ where
Ok(TryFromTemp::into_value(temp))
}
}
+
+//
-----------------------------------------------------------------------------
+// Helpers
+//
-----------------------------------------------------------------------------
+
+/// Move `value` into the uninitialized `cell`.
+unsafe fn write_cell<T: ContainerElement>(cell: *mut TVMFFIAny, value: T) {
+ let mut raw = TVMFFIAny::new();
+ T::container_move_to_any(value, &mut raw);
+ std::ptr::write(cell, raw);
+}
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index f4777f06..7df6c6ab 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -26,53 +26,6 @@ use tvm_ffi_sys::{
TVMFFIErrorMoveFromRaised, TVMFFIErrorSetRaised, TVMFFIObjectHandle,
TVMFFITypeIndex,
};
-/// The kind of an [`Error`], such as `ValueError`, matching the exception
-/// type that Python raises.
-///
-/// Use the constants such as [`VALUE_ERROR`] and [`TYPE_ERROR`], or
-/// [`ErrorKind::new("MyError")`](ErrorKind::new) for a custom kind.
-#[derive(Debug, Clone, PartialEq, Eq)]
-pub struct ErrorKind<'a>(&'a str);
-
-impl<'a> ErrorKind<'a> {
- /// The kind named `name`, which Python raises as that exception type.
- ///
- /// ```
- /// use tvm_ffi::{Error, ErrorKind};
- /// const MY_ERROR: ErrorKind = ErrorKind::new("MyError");
- /// let error = Error::new(MY_ERROR, "bad input", "");
- /// assert_eq!(error.kind().as_str(), "MyError");
- /// ```
- pub const fn new(name: &'a str) -> Self {
- Self(name)
- }
-
- /// Get the name of the kind.
- ///
- /// # Returns
- ///
- /// The name, such as `"ValueError"`.
- pub fn as_str(&self) -> &str {
- self.0
- }
-}
-
-impl<'a> std::fmt::Display for ErrorKind<'a> {
- fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
- write!(f, "{}", self.0)
- }
-}
-
-pub const VALUE_ERROR: ErrorKind = ErrorKind::new("ValueError");
-pub const TYPE_ERROR: ErrorKind = ErrorKind::new("TypeError");
-pub const RUNTIME_ERROR: ErrorKind = ErrorKind::new("RuntimeError");
-pub const ATTRIBUTE_ERROR: ErrorKind = ErrorKind::new("AttributeError");
-pub const KEY_ERROR: ErrorKind = ErrorKind::new("KeyError");
-pub const INDEX_ERROR: ErrorKind = ErrorKind::new("IndexError");
-pub const INTERNAL_ERROR: ErrorKind = ErrorKind::new("InternalError");
-pub const OVERFLOW_ERROR: ErrorKind = ErrorKind::new("OverflowError");
-pub const ZERO_DIVISION_ERROR: ErrorKind = ErrorKind::new("ZeroDivisionError");
-
/// The object behind an [`Error`]: its kind, message, backtrace, cause and
context.
#[repr(C)]
#[derive(Object)]
@@ -123,6 +76,53 @@ unsafe impl Sync for Error {}
/// A `Result` whose error type defaults to [`Error`].
pub type Result<T, E = Error> = std::result::Result<T, E>;
+/// The kind of an [`Error`], such as `ValueError`, matching the exception
+/// type that Python raises.
+///
+/// Use the constants such as [`VALUE_ERROR`] and [`TYPE_ERROR`], or
+/// [`ErrorKind::new("MyError")`](ErrorKind::new) for a custom kind.
+#[derive(Debug, Clone, PartialEq, Eq)]
+pub struct ErrorKind<'a>(&'a str);
+
+impl<'a> ErrorKind<'a> {
+ /// The kind named `name`, which Python raises as that exception type.
+ ///
+ /// ```
+ /// use tvm_ffi::{Error, ErrorKind};
+ /// const MY_ERROR: ErrorKind = ErrorKind::new("MyError");
+ /// let error = Error::new(MY_ERROR, "bad input", "");
+ /// assert_eq!(error.kind().as_str(), "MyError");
+ /// ```
+ pub const fn new(name: &'a str) -> Self {
+ Self(name)
+ }
+
+ /// Get the name of the kind.
+ ///
+ /// # Returns
+ ///
+ /// The name, such as `"ValueError"`.
+ pub fn as_str(&self) -> &str {
+ self.0
+ }
+}
+
+impl<'a> std::fmt::Display for ErrorKind<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ write!(f, "{}", self.0)
+ }
+}
+
+pub const VALUE_ERROR: ErrorKind = ErrorKind::new("ValueError");
+pub const TYPE_ERROR: ErrorKind = ErrorKind::new("TypeError");
+pub const RUNTIME_ERROR: ErrorKind = ErrorKind::new("RuntimeError");
+pub const ATTRIBUTE_ERROR: ErrorKind = ErrorKind::new("AttributeError");
+pub const KEY_ERROR: ErrorKind = ErrorKind::new("KeyError");
+pub const INDEX_ERROR: ErrorKind = ErrorKind::new("IndexError");
+pub const INTERNAL_ERROR: ErrorKind = ErrorKind::new("InternalError");
+pub const OVERFLOW_ERROR: ErrorKind = ErrorKind::new("OverflowError");
+pub const ZERO_DIVISION_ERROR: ErrorKind = ErrorKind::new("ZeroDivisionError");
+
impl Error {
/// The `InternalError` for a panic caught at an FFI boundary, where
unwinding
/// into the caller would abort the process. The message is the panic's,
when it
@@ -325,21 +325,6 @@ impl Error {
}
}
- /// Make this handle the only reference to its error, copying a shared
one, as
- /// `Arc::make_mut`.
- fn make_mut(&mut self) -> *mut ErrorObj {
- if !ObjectArc::is_unique(&self.data) {
- *self = Self::new_with_cause_and_extra_context(
- self.kind(),
- self.message(),
- self.backtrace(),
- self.cause_chain(),
- self.extra_context(),
- );
- }
- ObjectArc::as_mut_ptr(&mut self.data)
- }
-
/// Get the traceback of the error in the order of most recent call last,
as
/// Python prints it.
///
@@ -384,6 +369,21 @@ impl Error {
}
this
}
+
+ /// Make this handle the only reference to its error, copying a shared
one, as
+ /// `Arc::make_mut`.
+ fn make_mut(&mut self) -> *mut ErrorObj {
+ if !ObjectArc::is_unique(&self.data) {
+ *self = Self::new_with_cause_and_extra_context(
+ self.kind(),
+ self.message(),
+ self.backtrace(),
+ self.cause_chain(),
+ self.extra_context(),
+ );
+ }
+ ObjectArc::as_mut_ptr(&mut self.data)
+ }
}
impl std::fmt::Display for Error {
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index eb348284..a92cd04b 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -72,10 +72,13 @@ const SHALLOW_COPY_ATTR: &str = "__ffi_shallow_copy__";
//
-----------------------------------------------------------------------------
pub(super) type StructuralMutatorHandle = *mut StructuralMutatorObj;
+
type FStructuralMutate =
unsafe extern "C" fn(StructuralMutatorHandle, AnyView<'static>) ->
TVMFFIAny;
+
type FStructuralVarRemapGet =
unsafe extern "C" fn(StructuralMutatorHandle, AnyView<'static>) ->
TVMFFIAny;
+
type FStructuralVarRemapSet =
unsafe extern "C" fn(StructuralMutatorHandle, AnyView<'static>,
AnyView<'static>) -> TVMFFIAny;
@@ -95,6 +98,22 @@ pub struct StructuralMutatorObj {
def_region_mode: UnsafeCell<i32>,
}
+// Scoped runtime prefixes are not independently allocatable ObjectCore values.
+impl StructuralMutatorObj {
+ const TYPE_KEY: &'static str = "ffi.StructuralMutator";
+}
+
+impl Default for StructuralMutatorObj {
+ #[inline(always)]
+ fn default() -> Self {
+ Self {
+ base: Object::new(),
+ vtable: std::ptr::null(),
+ def_region_mode: UnsafeCell::new(DefRegionKind::None as i32),
+ }
+ }
+}
+
const _: () = {
assert!(
std::mem::offset_of!(StructuralMutatorObj, vtable) ==
std::mem::size_of::<TVMFFIObject>()
@@ -105,11 +124,6 @@ const _: () = {
);
};
-// Scoped runtime prefixes are not independently allocatable ObjectCore values.
-impl StructuralMutatorObj {
- const TYPE_KEY: &'static str = "ffi.StructuralMutator";
-}
-
/// Native state and default mutation for [`StructuralMutateEngine`].
/// The engine owns allocation, scoped dispatch, identity remaps and results.
///
@@ -183,17 +197,6 @@ pub struct StructuralMutateEngine<Base =
StructuralMutatorObj> {
remap: RefCell<StructuralVarRemap>,
}
-impl Default for StructuralMutatorObj {
- #[inline(always)]
- fn default() -> Self {
- Self {
- base: Object::new(),
- vtable: std::ptr::null(),
- def_region_mode: UnsafeCell::new(DefRegionKind::None as i32),
- }
- }
-}
-
/// Identity substitutions for structural mutation within one invocation.
///
/// Internal to the engine: handlers reach the remap through
@@ -251,52 +254,6 @@ impl Drop for StructuralVarRemap {
}
}
-impl<'source, 'value, T: crate::type_traits::ContainerElement>
-
super::dispatch::DispatchRecovery<crate::match_any_internal::MutationSource<'source>>
- for crate::MutationView<'value, T>
-{
- type Recovery = crate::MutationView<'source>;
-}
-
-//
-----------------------------------------------------------------------------
-// Public mutation API
-//
-----------------------------------------------------------------------------
-
-mod callback_result_sealed {
- use super::{Any, Result};
-
- pub trait Sealed {}
-
- impl<T: Into<Any>> Sealed for T {}
- impl<T: Into<Any>> Sealed for Result<T> {}
-}
-
-/// Convert an infallible or fallible map or mutation callback result into
-/// [`Result<UnchangedOr<Any>>`].
-///
-/// A callback may return any value convertible into [`Any`], including
-/// [`Unchanged`] and [`UnchangedOr`], or wrap it in [`Result`] to use `?`.
-///
-/// This trait is sealed and is not an extension point.
-#[doc(hidden)]
-pub trait IntoMutateResult: callback_result_sealed::Sealed {
- fn into_mutate_result(self) -> Result<UnchangedOr<Any>>;
-}
-
-impl<T: Into<Any>> IntoMutateResult for T {
- #[inline(always)]
- fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
- Ok(UnchangedOr::from_any(self.into()))
- }
-}
-
-impl<T: Into<Any>> IntoMutateResult for Result<T> {
- #[inline(always)]
- fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
- self.map(|value| UnchangedOr::from_any(value.into()))
- }
-}
-
/// Whether a [`MutationView`] may reuse its value's storage in place.
///
/// `Allow` is an upper bound: a value is reused in place only when it is also
@@ -516,7 +473,9 @@ mod mutation_field {
/// Sealed to `Any` and owning object handles; borrowed conversion adapters do
not qualify.
#[doc(hidden)]
pub trait MutationField: crate::type_traits::ContainerElement +
mutation_field::Sealed {}
+
impl MutationField for Any {}
+
impl<T: object::ObjectRefCore> MutationField for T {}
impl<T: object::ObjectRefCore> std::ops::Deref for MutationView<'_, T> {
@@ -601,6 +560,13 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
}
}
+impl<'source, 'value, T: crate::type_traits::ContainerElement>
+
super::dispatch::DispatchRecovery<crate::match_any_internal::MutationSource<'source>>
+ for crate::MutationView<'value, T>
+{
+ type Recovery = crate::MutationView<'source>;
+}
+
/// The running structural mutation, passed to each handler.
///
/// A handler that matches a node decides how its children are rewritten: call
@@ -616,6 +582,10 @@ pub struct StructuralMutatorView<'a, D, Base =
StructuralMutatorObj> {
_marker: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
}
+//
-----------------------------------------------------------------------------
+// Public mutation API and entrypoint
+//
-----------------------------------------------------------------------------
+
impl<D, Base: StructuralMutatorBase> StructuralMutatorView<'_, D, Base> {
/// Inspect the base state without retaining the scoped mutator runtime.
///
@@ -811,6 +781,120 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
}
}
+/// Mutate a structured value with an owned or borrowed [`Dispatcher`].
+///
+/// The root is consumed to establish the ownership boundary for optional
+/// in-place mutation. A matching callback supplies the final value; unmatched
+/// values use default mutation.
+///
+/// # Arguments
+///
+/// * `root` - Owned value to mutate, convertible into an [`Any`]. A uniquely
+/// owned container may be reused in place; clone first to preserve the
original.
+/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing
[`IntoDispatcher`].
+/// Matching handlers return replacements and control recursion through
+/// [`StructuralMutatorView`]; unmatched values use default mutation. For
closure
+/// callbacks with automatic recursion, use [`structural_map`].
+///
+/// # Examples
+///
+/// Double the integers in nested arrays; default mutation descends into the
+/// arrays:
+///
+/// ```
+/// use tvm_ffi::{
+/// dispatch, structural_mutate, Any, Array, MutationView, Result,
StructuralMutatorView,
+/// UnchangedOr,
+/// };
+///
+/// struct Double;
+///
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
+/// impl Double {
+/// fn mutate_int(
+/// &mut self,
+/// value: i64,
+/// _: &mut StructuralMutatorView<'_, Self>,
+/// ) -> Result<UnchangedOr<Any>> {
+/// Ok((value * 2).into())
+/// }
+/// }
+///
+/// let root = Array::from_vec(vec![Array::from_vec(vec![1_i64, 2]),
Array::from_vec(vec![3_i64])]);
+/// let result = Array::<Array<i64>>::try_from(structural_mutate(root,
Double)?)?;
+/// assert_eq!(result.get(0)?.iter().copied().collect::<Vec<_>>(), vec![2, 4]);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+pub fn structural_mutate<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
+where
+ R: Into<Any>,
+ O: BorrowMut<D>,
+ for<'v, 'c, 'r> H: IntoDispatcher<
+ (
+ MutationView<'v>,
+ &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
+ ),
+ Option<Result<UnchangedOr<Any>>>,
+ M,
+ Target = D,
+ Output = O,
+ >,
+ for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+ (
+ MutationView<'v>,
+ &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
+ ),
+ Option<Result<UnchangedOr<Any>>>,
+ >,
+{
+ StructuralMutateEngine::mutate_root(root, dispatcher)
+}
+
+mod callback_result_sealed {
+ use super::{Any, Result};
+
+ pub trait Sealed {}
+
+ impl<T: Into<Any>> Sealed for T {}
+ impl<T: Into<Any>> Sealed for Result<T> {}
+}
+
+/// Convert an infallible or fallible map or mutation callback result into
+/// [`Result<UnchangedOr<Any>>`].
+///
+/// A callback may return any value convertible into [`Any`], including
+/// [`Unchanged`] and [`UnchangedOr`], or wrap it in [`Result`] to use `?`.
+///
+/// This trait is sealed and is not an extension point.
+#[doc(hidden)]
+pub trait IntoMutateResult: callback_result_sealed::Sealed {
+ fn into_mutate_result(self) -> Result<UnchangedOr<Any>>;
+}
+
+impl<T: Into<Any>> IntoMutateResult for T {
+ #[inline(always)]
+ fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
+ Ok(UnchangedOr::from_any(self.into()))
+ }
+}
+
+impl<T: Into<Any>> IntoMutateResult for Result<T> {
+ #[inline(always)]
+ fn into_mutate_result(self) -> Result<UnchangedOr<Any>> {
+ self.map(|value| UnchangedOr::from_any(value.into()))
+ }
+}
+
+//
-----------------------------------------------------------------------------
+// StructuralMutateEngine implementation
+//
-----------------------------------------------------------------------------
+
+// Perf note: Raw carriers avoid materializing an owning Any on the success
path.
+// Private C-unwind boundaries return this 16-byte carrier in x0/x1 on AArch64,
+// avoiding the hidden result slot observed with the Rust ABI while allowing
+// Rust cleanup to unwind. Native vtables remain extern "C".
+// Raw bits provide no ownership guard: import an owning result into Any before
+// fallible work or destruction of an observer; borrowed inputs must stay live.
impl<Base: StructuralMutatorBase> StructuralMutateEngine<Base> {
/// Mutate `root` with `dispatcher` on the native base `Base`. See also
[`structural_mutate`].
#[inline(always)]
@@ -835,317 +919,35 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
let mut storage = dispatcher.into_dispatcher();
Self::run_mutator::<D, true>(root.into(), storage.borrow_mut())
}
-}
-//
-----------------------------------------------------------------------------
-// Outlined errors
-//
-----------------------------------------------------------------------------
+ /// The engine whose first field is the native prefix `runtime`.
+ ///
+ /// # Safety
+ ///
+ /// `runtime` is this live Rust engine, not an arbitrary native mutator;
only raw
+ /// projections of the result may cross recursion.
+ #[inline(always)]
+ unsafe fn from_handle(runtime: StructuralMutatorHandle) -> *mut Self {
+ runtime.cast()
+ }
-#[cold]
-#[inline(never)]
-fn missing_shallow_copy_error(type_index: i32, attribute: &str) -> Error {
- Error::new(
- TYPE_ERROR,
- &format!(
- "type `{}` cannot use reflected structural mutation because it
does not define `{attribute}`",
- crate::object::type_key_of_index(type_index)
- ),
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn invalid_shallow_copy_hook_error(attribute: &str) -> Error {
- Error::new(
- TYPE_ERROR,
- &format!("{attribute} must be an ffi.Function"),
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn invalid_shallow_copy_result_error() -> Error {
- Error::new(
- TYPE_ERROR,
- "shallow copy callback must return a distinct object with the same
type as its input",
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn missing_field_setter_error(name: &str) -> Error {
- Error::new(
- TYPE_ERROR,
- &format!("cannot structurally mutate field `{name}` because it does
not define a setter"),
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn invalid_field_offset_error(name: &str) -> Error {
- runtime_error(&format!(
- "native structural mutate: reflected field `{name}` has an invalid
offset"
- ))
-}
-
-#[cold]
-#[inline(never)]
-fn invalid_mutation_hook_error() -> Error {
- Error::new(
- TYPE_ERROR,
- "__s_mutate__ must be an opaque function pointer or ffi.Function",
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn var_remap_key_error() -> Error {
- Error::new(
- TYPE_ERROR,
- "variable-remap keys must be object-backed values",
- "",
- )
-}
-
-#[cold]
-#[inline(never)]
-fn unregistered_type_error(type_index: i32) -> Error {
- runtime_error(&format!(
- "native structural mutate: unregistered type index {type_index}"
- ))
-}
-
-#[cold]
-pub(super) fn with_mutation_error_context(error: Error, raw: TVMFFIAny,
operation: &str) -> Error {
- if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- error
- } else {
- with_structural_error_context(
- update_visit_error_context(error, raw),
- operation,
- &format!(
- "object `{}`",
- crate::object::type_key_of_index(raw.type_index)
- ),
- )
- }
-}
-
-//
-----------------------------------------------------------------------------
-// Private utility functions
-//
-----------------------------------------------------------------------------
-
-fn shallow_copy(raw: TVMFFIAny) -> Result<Any> {
- let Some(attr) = SHALLOW_COPY_COLUMN.get_raw_ffi_any(raw.type_index) else {
- return Err(missing_shallow_copy_error(
- raw.type_index,
- SHALLOW_COPY_ATTR,
- ));
- };
- if attr.type_index != TVMFFITypeIndex::kTVMFFIFunction as i32 {
- return Err(invalid_shallow_copy_hook_error(SHALLOW_COPY_ATTR));
- }
- let function = unsafe { attr.data_union.v_obj };
- if function.is_null() {
- return Err(runtime_error("shallow copy function pointer is null"));
- }
- // The registry owns this function throughout the call; borrowing it avoids
- // an atomic retain/release for every reflected node.
- let mut result = Any::new();
- let status = unsafe {
- TVMFFIFunctionCall(
- function.cast(),
- &raw,
- 1,
- Any::as_raw_ffi_any_mut(&mut result),
- )
- };
- if status != 0 {
- return Err(Error::from_raised());
- }
- let result_raw = *result.as_raw_ffi_any();
- let result_pointer = unsafe { result_raw.data_union.v_obj };
- let source_pointer = unsafe { raw.data_union.v_obj };
- if result_raw.type_index != raw.type_index
- || result_pointer.is_null()
- || result_pointer == source_pointer
- {
- return Err(invalid_shallow_copy_result_error());
- }
- Ok(result)
-}
-
-fn call_field_setter(
- field: &TVMFFIFieldInfo,
- field_address: *mut c_void,
- value: &TVMFFIAny,
-) -> Result<()> {
- if field.setter.is_null() {
- return Err(missing_field_setter_error(field.name.as_str()));
- }
- let return_code = unsafe {
- if field.flags & FLAG_SETTER_IS_FUNCTION == 0 {
- // SAFETY: reflection registration requires a non-Function setter
- // pointer to use the TVMFFIFieldSetter signature.
- let setter: TVMFFIFieldSetter = std::mem::transmute(field.setter);
- setter(field_address, value)
- } else {
- let mut args = [TVMFFIAny::new(), *value];
- args[0].type_index = TVMFFITypeIndex::kTVMFFIOpaquePtr as i32;
- args[0].data_union.v_ptr = field_address;
- // Own the result slot before entering foreign code so a partial
- // owning result is released on both success and failure.
- let mut result = Any::new();
- TVMFFIFunctionCall(
- field.setter as TVMFFIObjectHandle,
- args.as_ptr(),
- 2,
- Any::as_raw_ffi_any_mut(&mut result),
- )
- }
- };
- if return_code == 0 {
- Ok(())
- } else {
- Err(Error::from_raised())
- }
-}
-
-fn object_identity_key(raw: TVMFFIAny) -> Result<NonNull<TVMFFIObject>> {
- if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- core::hint::cold_path();
- return Err(var_remap_key_error());
- }
- let pointer = unsafe { raw.data_union.v_obj };
- NonNull::new(pointer).ok_or_else(|| {
- core::hint::cold_path();
- runtime_error("native structural mutate: identity object has a null
pointer")
- })
-}
-
-#[inline]
-fn checked_type_info(type_index: i32) -> Result<*const
crate::tvm_ffi_sys::TVMFFITypeInfo> {
- let info = unsafe { TVMFFIGetTypeInfo(type_index) };
- if info.is_null() {
- Err(unregistered_type_error(type_index))
- } else {
- Ok(info)
- }
-}
-
-#[inline(always)]
-fn object_is_unique(raw: TVMFFIAny) -> bool {
- if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- return false;
- }
- let pointer = unsafe { raw.data_union.v_obj };
- !pointer.is_null() && unsafe { object::unsafe_::is_unique(pointer) }
-}
-
-/// Acquire an owning value from a valid borrowed carrier.
-///
-/// Inline values are copied and objects are retained once, including Error
objects
-/// as ordinary values. String/byte views are normalized by the runtime; an
rvalue
-/// slot transfers its reference. Internal callers keep borrowed resources
live and
-/// uniquely own each writable rvalue slot, converting it at most once. A null
-/// object pointer or failed runtime conversion returns an error; successful
Error
-/// objects remain ordinary values rather than entering the error channel.
-fn raw_ffi_any_to_owned_any(raw: TVMFFIAny) -> Result<Any> {
- if is_plain_inline(raw.type_index) {
- return Ok(unsafe { Any::from_raw_ffi_any(raw) });
- }
- if raw.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- let object = unsafe { raw.data_union.v_obj };
- if object.is_null() {
- return Err(runtime_error(
- "native structural mutate: object-backed value has a null
pointer",
- ));
- }
- unsafe { object::unsafe_::inc_ref(object) };
- return Ok(unsafe { Any::from_raw_ffi_any(raw) });
- }
-
- // Raw string/bytes views and ObjectRValueRef require normalization (or a
- // move) rather than a bitwise copy; keep the generic C ABI conversion for
- // those uncommon representations.
- let mut owned = Any::new();
- let return_code = unsafe { TVMFFIAnyViewToOwnedAny(&raw,
Any::as_raw_ffi_any_mut(&mut owned)) };
- if return_code == 0 {
- Ok(owned)
- } else {
- Err(Error::from_raised())
- }
-}
-
-// Cache registry columns, not their optional per-type cells. Absent cells
retain
-// the normal hook/reflection fallback.
-static STRUCTURAL_MUTATE_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
- TypeAttrColumn::new(STRUCTURAL_MUTATE_ATTR).expect("missing __s_mutate__
column")
-});
-static STRUCTURAL_MAYBE_INPLACE_MUTATE_COLUMN:
ColdInitLazyLock<TypeAttrColumn> =
- ColdInitLazyLock::new(|| {
- TypeAttrColumn::new(STRUCTURAL_MAYBE_INPLACE_MUTATE_ATTR)
- .expect("missing __s_maybe_inplace_mutate__ column")
- });
-/// Return the shared column for normal or permitted in-place mutation hooks.
-/// Cache the column, not its per-type cells, so absent hooks still use
fallback.
-#[inline(always)]
-fn structural_mutate_column<const INPLACE: bool>() -> TypeAttrColumn {
- if INPLACE {
- *STRUCTURAL_MAYBE_INPLACE_MUTATE_COLUMN
- } else {
- *STRUCTURAL_MUTATE_COLUMN
- }
-}
-
-static SHALLOW_COPY_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
- TypeAttrColumn::new(SHALLOW_COPY_ATTR).expect("missing
__ffi_shallow_copy__ column")
-});
-
-//
-----------------------------------------------------------------------------
-// StructuralMutateEngine implementation
-//
-----------------------------------------------------------------------------
-
-// Perf note: Raw carriers avoid materializing an owning Any on the success
path.
-// Private C-unwind boundaries return this 16-byte carrier in x0/x1 on AArch64,
-// avoiding the hidden result slot observed with the Rust ABI while allowing
-// Rust cleanup to unwind. Native vtables remain extern "C".
-// Raw bits provide no ownership guard: import an owning result into Any before
-// fallible work or destruction of an observer; borrowed inputs must stay live.
-impl<Base: StructuralMutatorBase> StructuralMutateEngine<Base> {
- /// The engine whose first field is the native prefix `runtime`.
- ///
- /// # Safety
- ///
- /// `runtime` is this live Rust engine, not an arbitrary native mutator;
only raw
- /// projections of the result may cross recursion.
- #[inline(always)]
- unsafe fn from_handle(runtime: StructuralMutatorHandle) -> *mut Self {
- runtime.cast()
- }
-
- /// Validate a recursive caller and return its current exclusive reborrow.
- ///
- /// # Safety
- /// `runtime` must belong to the live invocation of `dispatch` with
matching `D`.
- /// The identity check does not replace that lifetime/type contract;
mismatch panics.
- #[inline(always)]
- unsafe fn checked_dispatcher<D>(
- runtime: StructuralMutatorHandle,
- dispatch: &mut D,
- ) -> *mut c_void {
- let context = std::ptr::from_mut(dispatch).cast();
- if context != (*Self::from_handle(runtime)).dispatcher {
- core::hint::cold_path();
- wrong_dispatcher();
- }
- context
- }
+ /// Validate a recursive caller and return its current exclusive reborrow.
+ ///
+ /// # Safety
+ /// `runtime` must belong to the live invocation of `dispatch` with
matching `D`.
+ /// The identity check does not replace that lifetime/type contract;
mismatch panics.
+ #[inline(always)]
+ unsafe fn checked_dispatcher<D>(
+ runtime: StructuralMutatorHandle,
+ dispatch: &mut D,
+ ) -> *mut c_void {
+ let context = std::ptr::from_mut(dispatch).cast();
+ if context != (*Self::from_handle(runtime)).dispatcher {
+ core::hint::cold_path();
+ wrong_dispatcher();
+ }
+ context
+ }
/// Build native callbacks for dispatcher `D`. `CALLBACK_CONTEXT` selects
/// whether direct callback failures receive input context; map adapters
add their own.
@@ -1349,6 +1151,10 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
}
}
+//
-----------------------------------------------------------------------------
+// Prefix-only default traversal and native ABI operations
+//
-----------------------------------------------------------------------------
+
// Prefix operations use only the native layout and installed vtable.
impl StructuralMutatorObj {
/// Consume a native hook's `result`, naming the borrowed `raw` in it when
it is an Error.
@@ -1670,131 +1476,330 @@ impl StructuralMutatorObj {
Ok((value.type_index() != TVMFFITypeIndex::kTVMFFINone as
i32).then_some(value))
}
- /// Borrow both inputs through the native remap interface; it retains the
key
- /// and replacement. Import the owning None/Error result before returning.
- #[inline(always)]
- unsafe fn var_remap_set(
- runtime: StructuralMutatorHandle,
- raw: TVMFFIAny,
- value: &Any,
- ) -> Result<()> {
- let callback = (*(*runtime).vtable).var_remap_set;
- raw_ffi_any_into_result(callback(
- runtime,
- AnyView::from_raw_ffi_any(raw),
- AnyView::from_raw_ffi_any(*value.as_raw_ffi_any()),
- ))
- .map(|_| ())
+ /// Borrow both inputs through the native remap interface; it retains the
key
+ /// and replacement. Import the owning None/Error result before returning.
+ #[inline(always)]
+ unsafe fn var_remap_set(
+ runtime: StructuralMutatorHandle,
+ raw: TVMFFIAny,
+ value: &Any,
+ ) -> Result<()> {
+ let callback = (*(*runtime).vtable).var_remap_set;
+ raw_ffi_any_into_result(callback(
+ runtime,
+ AnyView::from_raw_ffi_any(raw),
+ AnyView::from_raw_ffi_any(*value.as_raw_ffi_any()),
+ ))
+ .map(|_| ())
+ }
+
+ /// The reflective mutation of [`Self::mutate_without_hook_raw`], on owned
results.
+ #[inline(always)]
+ fn mutate_without_hook(
+ runtime: StructuralMutatorHandle,
+ raw: TVMFFIAny,
+ def_region_kind: DefRegionKind,
+ ) -> Result<Any> {
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ return raw_ffi_any_to_owned_any(raw);
+ }
+
+ let type_info = checked_type_info(raw.type_index)?;
+ let kind = unsafe { seq_hash_kind(type_info) };
+ let is_free_var = kind == TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar
as i32;
+ let is_dag_node = kind == TVMFFISEqHashKind::kTVMFFISEqHashKindDAGNode
as i32;
+ if is_free_var || is_dag_node {
+ // The ABI uses None for a miss; an Unchanged marker is a cached
result.
+ if let Some(mutated) = unsafe { Self::var_remap_get(runtime, raw)?
} {
+ return Ok(mutated);
+ }
+ }
+ // A variable use with no binding retains its identity without
visiting fields.
+ if is_free_var && def_region_kind == DefRegionKind::None {
+ return Ok(Unchanged.into());
+ }
+
+ let result = Self::map_reflected(runtime, raw, def_region_kind,
type_info)?;
+ if is_dag_node
+ || (is_free_var
+ && (def_region_kind == DefRegionKind::Pattern ||
!is_unchanged(&result)))
+ {
+ // Keep markers for DAG nodes and pattern definitions. An
unchanged simple
+ // definition needs no binding: subsequent uses retain the
original on a miss.
+ unsafe { Self::var_remap_set(runtime, raw, &result)? };
+ }
+ Ok(result)
+ }
+}
+
+//
-----------------------------------------------------------------------------
+// Private utility functions
+//
-----------------------------------------------------------------------------
+
+fn shallow_copy(raw: TVMFFIAny) -> Result<Any> {
+ let Some(attr) = SHALLOW_COPY_COLUMN.get_raw_ffi_any(raw.type_index) else {
+ return Err(missing_shallow_copy_error(
+ raw.type_index,
+ SHALLOW_COPY_ATTR,
+ ));
+ };
+ if attr.type_index != TVMFFITypeIndex::kTVMFFIFunction as i32 {
+ return Err(invalid_shallow_copy_hook_error(SHALLOW_COPY_ATTR));
+ }
+ let function = unsafe { attr.data_union.v_obj };
+ if function.is_null() {
+ return Err(runtime_error("shallow copy function pointer is null"));
+ }
+ // The registry owns this function throughout the call; borrowing it avoids
+ // an atomic retain/release for every reflected node.
+ let mut result = Any::new();
+ let status = unsafe {
+ TVMFFIFunctionCall(
+ function.cast(),
+ &raw,
+ 1,
+ Any::as_raw_ffi_any_mut(&mut result),
+ )
+ };
+ if status != 0 {
+ return Err(Error::from_raised());
+ }
+ let result_raw = *result.as_raw_ffi_any();
+ let result_pointer = unsafe { result_raw.data_union.v_obj };
+ let source_pointer = unsafe { raw.data_union.v_obj };
+ if result_raw.type_index != raw.type_index
+ || result_pointer.is_null()
+ || result_pointer == source_pointer
+ {
+ return Err(invalid_shallow_copy_result_error());
+ }
+ Ok(result)
+}
+
+fn call_field_setter(
+ field: &TVMFFIFieldInfo,
+ field_address: *mut c_void,
+ value: &TVMFFIAny,
+) -> Result<()> {
+ if field.setter.is_null() {
+ return Err(missing_field_setter_error(field.name.as_str()));
+ }
+ let return_code = unsafe {
+ if field.flags & FLAG_SETTER_IS_FUNCTION == 0 {
+ // SAFETY: reflection registration requires a non-Function setter
+ // pointer to use the TVMFFIFieldSetter signature.
+ let setter: TVMFFIFieldSetter = std::mem::transmute(field.setter);
+ setter(field_address, value)
+ } else {
+ let mut args = [TVMFFIAny::new(), *value];
+ args[0].type_index = TVMFFITypeIndex::kTVMFFIOpaquePtr as i32;
+ args[0].data_union.v_ptr = field_address;
+ // Own the result slot before entering foreign code so a partial
+ // owning result is released on both success and failure.
+ let mut result = Any::new();
+ TVMFFIFunctionCall(
+ field.setter as TVMFFIObjectHandle,
+ args.as_ptr(),
+ 2,
+ Any::as_raw_ffi_any_mut(&mut result),
+ )
+ }
+ };
+ if return_code == 0 {
+ Ok(())
+ } else {
+ Err(Error::from_raised())
+ }
+}
+
+fn object_identity_key(raw: TVMFFIAny) -> Result<NonNull<TVMFFIObject>> {
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ core::hint::cold_path();
+ return Err(var_remap_key_error());
+ }
+ let pointer = unsafe { raw.data_union.v_obj };
+ NonNull::new(pointer).ok_or_else(|| {
+ core::hint::cold_path();
+ runtime_error("native structural mutate: identity object has a null
pointer")
+ })
+}
+
+#[inline]
+fn checked_type_info(type_index: i32) -> Result<*const
crate::tvm_ffi_sys::TVMFFITypeInfo> {
+ let info = unsafe { TVMFFIGetTypeInfo(type_index) };
+ if info.is_null() {
+ Err(unregistered_type_error(type_index))
+ } else {
+ Ok(info)
+ }
+}
+
+#[inline(always)]
+fn object_is_unique(raw: TVMFFIAny) -> bool {
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ return false;
+ }
+ let pointer = unsafe { raw.data_union.v_obj };
+ !pointer.is_null() && unsafe { object::unsafe_::is_unique(pointer) }
+}
+
+/// Acquire an owning value from a valid borrowed carrier.
+///
+/// Inline values are copied and objects are retained once, including Error
objects
+/// as ordinary values. String/byte views are normalized by the runtime; an
rvalue
+/// slot transfers its reference. Internal callers keep borrowed resources
live and
+/// uniquely own each writable rvalue slot, converting it at most once. A null
+/// object pointer or failed runtime conversion returns an error; successful
Error
+/// objects remain ordinary values rather than entering the error channel.
+fn raw_ffi_any_to_owned_any(raw: TVMFFIAny) -> Result<Any> {
+ if is_plain_inline(raw.type_index) {
+ return Ok(unsafe { Any::from_raw_ffi_any(raw) });
+ }
+ if raw.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ let object = unsafe { raw.data_union.v_obj };
+ if object.is_null() {
+ return Err(runtime_error(
+ "native structural mutate: object-backed value has a null
pointer",
+ ));
+ }
+ unsafe { object::unsafe_::inc_ref(object) };
+ return Ok(unsafe { Any::from_raw_ffi_any(raw) });
+ }
+
+ // Raw string/bytes views and ObjectRValueRef require normalization (or a
+ // move) rather than a bitwise copy; keep the generic C ABI conversion for
+ // those uncommon representations.
+ let mut owned = Any::new();
+ let return_code = unsafe { TVMFFIAnyViewToOwnedAny(&raw,
Any::as_raw_ffi_any_mut(&mut owned)) };
+ if return_code == 0 {
+ Ok(owned)
+ } else {
+ Err(Error::from_raised())
}
+}
- /// The reflective mutation of [`Self::mutate_without_hook_raw`], on owned
results.
- #[inline(always)]
- fn mutate_without_hook(
- runtime: StructuralMutatorHandle,
- raw: TVMFFIAny,
- def_region_kind: DefRegionKind,
- ) -> Result<Any> {
- if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- return raw_ffi_any_to_owned_any(raw);
- }
+// Cache registry columns, not their optional per-type cells. Absent cells
retain
+// the normal hook/reflection fallback.
+static STRUCTURAL_MUTATE_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
+ TypeAttrColumn::new(STRUCTURAL_MUTATE_ATTR).expect("missing __s_mutate__
column")
+});
- let type_info = checked_type_info(raw.type_index)?;
- let kind = unsafe { seq_hash_kind(type_info) };
- let is_free_var = kind == TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar
as i32;
- let is_dag_node = kind == TVMFFISEqHashKind::kTVMFFISEqHashKindDAGNode
as i32;
- if is_free_var || is_dag_node {
- // The ABI uses None for a miss; an Unchanged marker is a cached
result.
- if let Some(mutated) = unsafe { Self::var_remap_get(runtime, raw)?
} {
- return Ok(mutated);
- }
- }
- // A variable use with no binding retains its identity without
visiting fields.
- if is_free_var && def_region_kind == DefRegionKind::None {
- return Ok(Unchanged.into());
- }
+static STRUCTURAL_MAYBE_INPLACE_MUTATE_COLUMN:
ColdInitLazyLock<TypeAttrColumn> =
+ ColdInitLazyLock::new(|| {
+ TypeAttrColumn::new(STRUCTURAL_MAYBE_INPLACE_MUTATE_ATTR)
+ .expect("missing __s_maybe_inplace_mutate__ column")
+ });
- let result = Self::map_reflected(runtime, raw, def_region_kind,
type_info)?;
- if is_dag_node
- || (is_free_var
- && (def_region_kind == DefRegionKind::Pattern ||
!is_unchanged(&result)))
- {
- // Keep markers for DAG nodes and pattern definitions. An
unchanged simple
- // definition needs no binding: subsequent uses retain the
original on a miss.
- unsafe { Self::var_remap_set(runtime, raw, &result)? };
- }
- Ok(result)
+/// Return the shared column for normal or permitted in-place mutation hooks.
+/// Cache the column, not its per-type cells, so absent hooks still use
fallback.
+#[inline(always)]
+fn structural_mutate_column<const INPLACE: bool>() -> TypeAttrColumn {
+ if INPLACE {
+ *STRUCTURAL_MAYBE_INPLACE_MUTATE_COLUMN
+ } else {
+ *STRUCTURAL_MUTATE_COLUMN
}
}
+static SHALLOW_COPY_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
+ TypeAttrColumn::new(SHALLOW_COPY_ATTR).expect("missing
__ffi_shallow_copy__ column")
+});
+
//
-----------------------------------------------------------------------------
-// Public entrypoint
+// Outlined errors
//
-----------------------------------------------------------------------------
-/// Mutate a structured value with an owned or borrowed [`Dispatcher`].
-///
-/// The root is consumed to establish the ownership boundary for optional
-/// in-place mutation. A matching callback supplies the final value; unmatched
-/// values use default mutation.
-///
-/// # Arguments
-///
-/// * `root` - Owned value to mutate, convertible into an [`Any`]. A uniquely
-/// owned container may be reused in place; clone first to preserve the
original.
-/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing
[`IntoDispatcher`].
-/// Matching handlers return replacements and control recursion through
-/// [`StructuralMutatorView`]; unmatched values use default mutation. For
closure
-/// callbacks with automatic recursion, use [`structural_map`].
-///
-/// # Examples
-///
-/// Double the integers in nested arrays; default mutation descends into the
-/// arrays:
-///
-/// ```
-/// use tvm_ffi::{
-/// dispatch, structural_mutate, Any, Array, MutationView, Result,
StructuralMutatorView,
-/// UnchangedOr,
-/// };
-///
-/// struct Double;
-///
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
-/// impl Double {
-/// fn mutate_int(
-/// &mut self,
-/// value: i64,
-/// _: &mut StructuralMutatorView<'_, Self>,
-/// ) -> Result<UnchangedOr<Any>> {
-/// Ok((value * 2).into())
-/// }
-/// }
-///
-/// let root = Array::from_vec(vec![Array::from_vec(vec![1_i64, 2]),
Array::from_vec(vec![3_i64])]);
-/// let result = Array::<Array<i64>>::try_from(structural_mutate(root,
Double)?)?;
-/// assert_eq!(result.get(0)?.iter().copied().collect::<Vec<_>>(), vec![2, 4]);
-/// # Ok::<(), tvm_ffi::Error>(())
-/// ```
-pub fn structural_mutate<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
-where
- R: Into<Any>,
- O: BorrowMut<D>,
- for<'v, 'c, 'r> H: IntoDispatcher<
- (
- MutationView<'v>,
- &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
- ),
- Option<Result<UnchangedOr<Any>>>,
- M,
- Target = D,
- Output = O,
- >,
- for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
- (
- MutationView<'v>,
- &'c mut StructuralMutatorView<'r, D, StructuralMutatorObj>,
+#[cold]
+#[inline(never)]
+fn missing_shallow_copy_error(type_index: i32, attribute: &str) -> Error {
+ Error::new(
+ TYPE_ERROR,
+ &format!(
+ "type `{}` cannot use reflected structural mutation because it
does not define `{attribute}`",
+ crate::object::type_key_of_index(type_index)
),
- Option<Result<UnchangedOr<Any>>>,
- >,
-{
- StructuralMutateEngine::mutate_root(root, dispatcher)
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn invalid_shallow_copy_hook_error(attribute: &str) -> Error {
+ Error::new(
+ TYPE_ERROR,
+ &format!("{attribute} must be an ffi.Function"),
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn invalid_shallow_copy_result_error() -> Error {
+ Error::new(
+ TYPE_ERROR,
+ "shallow copy callback must return a distinct object with the same
type as its input",
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn missing_field_setter_error(name: &str) -> Error {
+ Error::new(
+ TYPE_ERROR,
+ &format!("cannot structurally mutate field `{name}` because it does
not define a setter"),
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn invalid_field_offset_error(name: &str) -> Error {
+ runtime_error(&format!(
+ "native structural mutate: reflected field `{name}` has an invalid
offset"
+ ))
+}
+
+#[cold]
+#[inline(never)]
+fn invalid_mutation_hook_error() -> Error {
+ Error::new(
+ TYPE_ERROR,
+ "__s_mutate__ must be an opaque function pointer or ffi.Function",
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn var_remap_key_error() -> Error {
+ Error::new(
+ TYPE_ERROR,
+ "variable-remap keys must be object-backed values",
+ "",
+ )
+}
+
+#[cold]
+#[inline(never)]
+fn unregistered_type_error(type_index: i32) -> Error {
+ runtime_error(&format!(
+ "native structural mutate: unregistered type index {type_index}"
+ ))
+}
+
+#[cold]
+pub(super) fn with_mutation_error_context(error: Error, raw: TVMFFIAny,
operation: &str) -> Error {
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ error
+ } else {
+ with_structural_error_context(
+ update_visit_error_context(error, raw),
+ operation,
+ &format!(
+ "object `{}`",
+ crate::object::type_key_of_index(raw.type_index)
+ ),
+ )
+ }
}
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 1a72734b..2fdfe126 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -86,6 +86,7 @@ const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
//
-----------------------------------------------------------------------------
type StructuralVisitorHandle = *mut StructuralVisitorObj;
+
type FStructuralVisit =
unsafe extern "C" fn(StructuralVisitorHandle, AnyView<'static>) ->
TVMFFIAny;
@@ -103,6 +104,22 @@ pub struct StructuralVisitorObj {
def_region_mode: UnsafeCell<i32>,
}
+// Scoped runtime prefixes are not independently allocatable ObjectCore values.
+impl StructuralVisitorObj {
+ const TYPE_KEY: &'static str = "ffi.StructuralVisitor";
+}
+
+impl Default for StructuralVisitorObj {
+ #[inline(always)]
+ fn default() -> Self {
+ Self {
+ base: Object::new(),
+ vtable: std::ptr::null(),
+ def_region_mode: UnsafeCell::new(DefRegionKind::None as i32),
+ }
+ }
+}
+
/// Native `ffi.VisitInterrupt` object containing the interruption payload.
#[repr(C)]
#[derive(Object)]
@@ -126,9 +143,66 @@ const _: () = {
assert!(std::mem::offset_of!(VisitInterruptObj, value) ==
std::mem::size_of::<TVMFFIObject>());
};
-// Scoped runtime prefixes are not independently allocatable ObjectCore values.
-impl StructuralVisitorObj {
- const TYPE_KEY: &'static str = "ffi.StructuralVisitor";
+/// Definition-region state active at the current value.
+///
+/// Reflected fields marked `SEqHashDefPattern` or
+/// `SEqHashDefSimple` override the inherited state for that field's
+/// complete recursive visit.
+#[repr(i32)]
+#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
+pub enum DefRegionKind {
+ /// The value is outside a definition region.
+ #[default]
+ None = 0,
+ /// Definitions apply recursively through the visited value.
+ Pattern = 1,
+ /// Definitions apply to the visited value alone; its type is walked as
uses.
+ Simple = 2,
+}
+
+const _: () = {
+ assert!(DefRegionKind::None as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
+ assert!(
+ DefRegionKind::Pattern as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
+ );
+ assert!(DefRegionKind::Simple as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32);
+};
+
+/// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
+///
+/// Entry points and visitor-layer calls return
+/// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
+/// visited completely, `Ok(Some(..))` means a handler halted the traversal
+/// with this interrupt, and `Err` means it failed.
+///
+/// This is an owning object reference, like [`Error`]. Payload reads through
+/// `interrupt.value` borrow the shared [`VisitInterruptObj`]; use
[`Self::new`]
+/// instead of a payload-only struct literal. Moving or replacing the payload
+/// through this reference is not supported; clone it to retain a separate
owner.
+#[repr(C)]
+#[derive(Clone, ObjectRef)]
+pub struct VisitInterrupt {
+ data: ObjectArc<VisitInterruptObj>,
+}
+
+impl VisitInterrupt {
+ /// Create an interrupt carrying an FFI-compatible payload back to the
caller of
+ /// the visit.
+ ///
+ /// # Arguments
+ ///
+ /// * `payload` - The payload, such as the value that was found.
+ ///
+ /// # Returns
+ ///
+ /// The interrupt.
+ #[inline(always)]
+ pub fn new<T: Into<Any>>(payload: T) -> Self {
+ Self::from_object(VisitInterruptObj {
+ base: Object::new(),
+ value: payload.into(),
+ })
+ }
}
/// Native state and default traversal for [`StructuralVisitEngine`].
@@ -202,83 +276,6 @@ pub struct StructuralVisitEngine<Base =
StructuralVisitorObj> {
dispatcher: *mut c_void,
}
-impl Default for StructuralVisitorObj {
- #[inline(always)]
- fn default() -> Self {
- Self {
- base: Object::new(),
- vtable: std::ptr::null(),
- def_region_mode: UnsafeCell::new(DefRegionKind::None as i32),
- }
- }
-}
-
-/// Definition-region state active at the current value.
-///
-/// Reflected fields marked `SEqHashDefPattern` or
-/// `SEqHashDefSimple` override the inherited state for that field's
-/// complete recursive visit.
-#[repr(i32)]
-#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
-pub enum DefRegionKind {
- /// The value is outside a definition region.
- #[default]
- None = 0,
- /// Definitions apply recursively through the visited value.
- Pattern = 1,
- /// Definitions apply to the visited value alone; its type is walked as
uses.
- Simple = 2,
-}
-
-const _: () = {
- assert!(DefRegionKind::None as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindNone as i32);
- assert!(
- DefRegionKind::Pattern as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindPattern as i32
- );
- assert!(DefRegionKind::Simple as i32 ==
TVMFFIDefRegionKind::kTVMFFIDefRegionKindSimple as i32);
-};
-
-/// Interrupt state of a traversal, mirroring C++ `ffi.VisitInterrupt`.
-///
-/// Entry points and visitor-layer calls return
-/// `Result<Option<VisitInterrupt>>`: `Ok(None)` means the (sub)graph was
-/// visited completely, `Ok(Some(..))` means a handler halted the traversal
-/// with this interrupt, and `Err` means it failed.
-///
-/// This is an owning object reference, like [`Error`]. Payload reads through
-/// `interrupt.value` borrow the shared [`VisitInterruptObj`]; use
[`Self::new`]
-/// instead of a payload-only struct literal. Moving or replacing the payload
-/// through this reference is not supported; clone it to retain a separate
owner.
-#[repr(C)]
-#[derive(Clone, ObjectRef)]
-pub struct VisitInterrupt {
- data: ObjectArc<VisitInterruptObj>,
-}
-
-impl VisitInterrupt {
- /// Create an interrupt carrying an FFI-compatible payload back to the
caller of
- /// the visit.
- ///
- /// # Arguments
- ///
- /// * `payload` - The payload, such as the value that was found.
- ///
- /// # Returns
- ///
- /// The interrupt.
- #[inline(always)]
- pub fn new<T: Into<Any>>(payload: T) -> Self {
- Self::from_object(VisitInterruptObj {
- base: Object::new(),
- value: payload.into(),
- })
- }
-}
-
-//
-----------------------------------------------------------------------------
-// Public visitor API
-//
-----------------------------------------------------------------------------
-
/// The running structural visit, passed to each handler.
///
/// A handler that matches a node decides which children are visited: call
@@ -295,6 +292,10 @@ pub struct StructuralVisitorView<'a, D, Base =
StructuralVisitorObj> {
_marker: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
}
+//
-----------------------------------------------------------------------------
+// Public visitor API and entrypoint
+//
-----------------------------------------------------------------------------
+
impl<D, Base: StructuralVisitorBase> StructuralVisitorView<'_, D, Base> {
/// Inspect the base state; the borrow cannot outlive this view.
#[inline(always)]
@@ -417,6 +418,96 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
}
}
+/// Visit `root` with an owned or borrowed [`Dispatcher`](crate::Dispatcher).
+///
+/// Handlers receive [`StructuralVisitorView`] for explicit recursion and keep
state on the
+/// dispatcher. Unmatched values use default child traversal. Pass `&mut D` to
+/// keep the dispatcher and inspect its state after the visit. An owned
dispatcher
+/// is held in the outer call and uses the same borrowed traversal engine.
+///
+/// Callbacks must report failures through [`Result`] and must not panic: a
panic
+/// reaching the non-unwinding native callback boundary aborts the process.
+///
+/// # Arguments
+///
+/// * `root` - Value borrowed for traversal, convertible into an [`AnyView`].
+/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing
[`IntoDispatcher`].
+/// Matching handlers control recursion through [`StructuralVisitorView`];
+/// unmatched values use default child traversal.
+///
+/// # Examples
+///
+/// Sum integer leaves while default traversal descends into the array:
+///
+/// ```
+/// # fn main() -> tvm_ffi::Result<()> {
+/// use tvm_ffi::{
+/// dispatch, structural_visit, AnyView, Array, Result,
StructuralVisitorView,
+/// VisitInterrupt,
+/// };
+///
+/// struct Sum(i64);
+/// #[dispatch(visit, (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
Result<Option<VisitInterrupt>>)]
+/// impl Sum {
+/// fn visit_integer(
+/// &mut self,
+/// value: i64,
+/// _: &mut StructuralVisitorView<'_, Self>,
+/// ) -> Result<Option<VisitInterrupt>> {
+/// self.0 += value;
+/// Ok(None)
+/// }
+/// }
+///
+/// let root = Array::from_vec(vec![1_i64, 2, 3]);
+/// let mut sum = Sum(0);
+/// assert!(structural_visit(&root, &mut sum)?.is_none());
+/// assert_eq!(sum.0, 6);
+/// # Ok(())
+/// # }
+/// ```
+pub fn structural_visit<'input, R, H, M, D, O>(
+ root: R,
+ dispatcher: H,
+) -> Result<Option<VisitInterrupt>>
+where
+ R: Into<AnyView<'input>>,
+ O: BorrowMut<D>,
+ for<'v, 'c, 'r> H: IntoDispatcher<
+ (
+ AnyView<'v>,
+ &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
+ ),
+ Option<Result<Option<VisitInterrupt>>>,
+ M,
+ Target = D,
+ Output = O,
+ >,
+ for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+ (
+ AnyView<'v>,
+ &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
+ ),
+ Option<Result<Option<VisitInterrupt>>>,
+ >,
+{
+ StructuralVisitEngine::visit_root(root, dispatcher)
+}
+
+impl super::dispatch::CallbackResult<Result<Option<VisitInterrupt>>>
+ for Result<Option<VisitInterrupt>>
+{
+ #[inline(always)]
+ fn callback_result(self) -> Result<Option<VisitInterrupt>> {
+ self
+ }
+}
+
+//
-----------------------------------------------------------------------------
+// StructuralVisitEngine implementation
+//
-----------------------------------------------------------------------------
+
+// Generic runtime: only this implementation projects the dispatcher after
Base.
impl<Base: StructuralVisitorBase> StructuralVisitEngine<Base> {
/// Visit `root` with `dispatcher` on the native base `Base`. See also
[`structural_visit`].
#[inline(always)]
@@ -445,121 +536,184 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
let root = root.into();
Self::run_visitor::<D>(*root.as_raw_ffi_any(), storage.borrow_mut())
}
-}
-impl super::dispatch::CallbackResult<Result<Option<VisitInterrupt>>>
- for Result<Option<VisitInterrupt>>
-{
- #[inline(always)]
- fn callback_result(self) -> Result<Option<VisitInterrupt>> {
- self
- }
-}
-
-//
-----------------------------------------------------------------------------
-// Outlined errors
-//
-----------------------------------------------------------------------------
-
-#[cold]
-#[inline(never)]
-fn unregistered_visit_type_error(type_index: i32) -> Error {
- runtime_error(&format!(
- "native visitor: unregistered type index {type_index}"
- ))
-}
-
-#[cold]
-#[inline(never)]
-fn invalid_visit_hook_result_error() -> Error {
- Error::new(
- TYPE_ERROR,
- "structural visit hook must return None or ffi.VisitInterrupt",
- "",
- )
-}
-
-/// Consume `error` and annotate it from borrowed input scalar fields. Internal
-/// callers keep that input live; the Error remains owned if formatting
unwinds.
-// Perf note: Keep error formatting and context-allocation scratch out of
visit frames.
-#[cold]
-#[inline(never)]
-fn annotate_visit_error(error: Error, value: TVMFFIAny) -> Error {
- let error = update_visit_error_context(error, value);
- with_structural_error_context(
- error,
- "visit",
- &format!(
- "object `{}`",
- crate::object::type_key_of_index(value.type_index)
- ),
- )
-}
-
-//
-----------------------------------------------------------------------------
-// Private utility functions
-//
-----------------------------------------------------------------------------
+ /// The engine whose first field is the native prefix `runtime`.
+ ///
+ /// # Safety
+ /// `runtime` must belong to the live, synchronous Rust traversal that
created
+ /// this exact repr(C) engine. An arbitrary native visitor is not such an
engine.
+ #[inline(always)]
+ unsafe fn from_handle(runtime: StructuralVisitorHandle) -> *mut Self {
+ runtime.cast()
+ }
-#[inline(always)]
-fn dispatcher_ptr<D>(dispatch: &mut D) -> *mut c_void {
- std::ptr::from_mut(dispatch).cast()
-}
+ /// Publish the current exclusive `dispatch` reborrow and visit borrowed
`raw`.
+ /// Return one owning raw None/interrupt/Error token.
+ ///
+ /// # Safety
+ /// `runtime` must belong to this live `D` invocation and `raw` remain
valid
+ /// through recursion; the caller cannot use competing dispatcher borrows.
+ #[inline(always)]
+ unsafe fn visit_raw<D>(
+ runtime: StructuralVisitorHandle,
+ dispatch: &mut D,
+ raw: TVMFFIAny,
+ ) -> TVMFFIAny {
+ let context = Self::checked_dispatcher(runtime, dispatch);
+ (*Self::from_handle(runtime)).dispatcher = context;
+ StructuralVisitorObj::call_visitor_raw(runtime, raw)
+ }
-// The visit result carrier, as C++: None continues; a VisitInterrupt or an
Error stops.
-#[inline(always)]
-fn visit_result_raw(result: Result<Option<VisitInterrupt>>, raw: TVMFFIAny) ->
TVMFFIAny {
- match result {
- Ok(None) => TVMFFIAny::new(),
- Ok(Some(interrupt)) => Any::into_raw_ffi_any(interrupt.into()),
- Err(error) => {
+ /// Check runtime identity and return the current `dispatch` reborrow
pointer.
+ ///
+ /// # Safety
+ /// `runtime` must be the live engine for `D`; address equality does not
establish
+ /// type or lifetime validity. Mismatch panics before a pointer is
published.
+ // The caller has already established matching runtime/D types and scope.
+ // Read only the stored raw address: do not create a competing mutable
borrow.
+ #[inline(always)]
+ unsafe fn checked_dispatcher<D>(
+ runtime: StructuralVisitorHandle,
+ dispatch: &mut D,
+ ) -> *mut c_void {
+ let current = dispatcher_ptr(dispatch);
+ if current != (*Self::from_handle(runtime)).dispatcher {
core::hint::cold_path();
-
StructuralVisitorObj::with_visit_context_raw(Any::into_raw_ffi_any(error.into()),
raw)
+ wrong_dispatcher();
}
+ current
}
-}
-// A packed hook's owning Any is valid, but its result type is dynamic.
-#[inline(always)]
-fn checked_hook_result(value: Any) -> Any {
- let tag = value.type_index();
- if tag == TVMFFITypeIndex::kTVMFFINone as i32
- || tag == TVMFFITypeIndex::kTVMFFIError as i32
- || tag == TVMFFITypeIndex::kTVMFFIVisitInterrupt as i32
+ /// Build the native visitor callback for concrete dispatcher `D`. Its
higher-ranked
+ /// view arguments restrict all borrows to the synchronous handler
invocation.
+ #[inline(always)]
+ fn vtable<D>() -> &'static StructuralVisitorVTable
+ where
+ for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+ (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+ Option<Result<Option<VisitInterrupt>>>,
+ >,
{
- value
- } else {
- invalid_visit_hook_result_error().into()
+ &StructuralVisitorVTable {
+ visit: Self::rust_vtable_visit_raw::<D>,
+ }
}
-}
-/// Consume exactly one Error reference after its integer tag was checked.
-#[inline(always)]
-unsafe fn take_error(value: Any) -> Error {
- use crate::type_traits::AnyCompatible;
- let mut raw = Any::into_raw_ffi_any(value);
- Error::move_from_any_after_check(&mut raw)
-}
-
-#[inline(always)]
-fn with_field_error_context(result: Any, name: &str) -> Any {
- if result.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
- core::hint::cold_path();
- // SAFETY: direct tag check establishes the Error representation.
- with_structural_field_error_context(unsafe { take_error(result) },
"visit", name).into()
- } else {
+ /// Enter a native visitor callback with borrowed `value`; return one
owning token.
+ /// Restore the incoming dispatcher provenance before returning to the
caller.
+ ///
+ /// # Safety
+ /// `runtime` must have this `D` vtable and a live published reborrow. The
erased
+ /// ABI lifetime on `value` is valid only for this call. Callback failures
use
+ /// Result; Rust panics cannot cross this native C boundary.
+ unsafe extern "C" fn rust_vtable_visit_raw<D>(
+ runtime: StructuralVisitorHandle,
+ value: AnyView<'static>,
+ ) -> TVMFFIAny
+ where
+ for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+ (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+ Option<Result<Option<VisitInterrupt>>>,
+ >,
+ {
+ let context = (*Self::from_handle(runtime)).dispatcher;
+ let raw = *value.as_raw_ffi_any();
+ let result = if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ core::hint::cold_path();
+ TVMFFIAny::new()
+ } else {
+ let dispatch = &mut *context.cast::<D>();
+ let mut visitor = Self::visitor_context::<D>(runtime);
+ // SAFETY: the native caller keeps the input alive for this
synchronous
+ // dispatch; the higher-ranked callback cannot retain its borrowed
view.
+ match crate::Dispatcher::dispatch_tuple(
+ &mut *dispatch,
+ (AnyView::from_raw_ffi_any(raw), &mut visitor),
+ ) {
+ Some(result) => visit_result_raw(result, raw),
+ None => Self::default_visit_raw(runtime,
dispatcher_ptr(dispatch), raw),
+ }
+ };
+ // The enclosing callback may resume using its ancestor mutable borrow.
+ // Native hooks cannot restore Rust-only borrow provenance for us.
+ (*Self::from_handle(runtime)).dispatcher = context;
result
}
-}
-// Columns are process-lifetime registry entries; a missing per-type cell still
-// falls back to reflected traversal. A missing required column is an error.
-static STRUCTURAL_VISIT_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
- TypeAttrColumn::new(STRUCTURAL_VISIT_ATTR).expect("missing __s_visit__
column")
-});
+ /// Create the handler view for borrowed `runtime`, without retaining it.
+ ///
+ /// # Safety
+ /// The live runtime's vtable must match D. Keep this view within the
current
+ /// higher-ranked handler call, never outliving the runtime or dispatcher.
+ #[inline(always)]
+ unsafe fn visitor_context<'a, D>(
+ runtime: StructuralVisitorHandle,
+ ) -> StructuralVisitorView<'a, D, Base> {
+ StructuralVisitorView {
+ runtime,
+ _marker: PhantomData,
+ }
+ }
-/// Return the shared visit-hook column without caching optional per-type
cells.
-#[inline(always)]
-fn structural_visit_column() -> TypeAttrColumn {
- *STRUCTURAL_VISIT_COLUMN
+ /// Publish `context` and descend through borrowed `raw`, then annotate
errors.
+ /// The raw return transfers one owning None/interrupt/Error token.
+ ///
+ /// # Safety
+ /// `runtime` must be live and `context` its current exclusive dispatcher
reborrow;
+ /// `raw` remains borrowed and must survive the complete synchronous
descent.
+ #[inline(always)]
+ unsafe fn default_visit_raw(
+ runtime: StructuralVisitorHandle,
+ context: *mut c_void,
+ raw: TVMFFIAny,
+ ) -> TVMFFIAny {
+ // Context is the supplied exclusive reborrow. Publish it before
descent;
+ // the handle-only scanner never creates or uses a competing D
reference.
+ (*Self::from_handle(runtime)).dispatcher = context;
+ StructuralVisitorObj::with_visit_context_raw(
+ Base::default_visit_raw(runtime.cast(),
AnyView::from_raw_ffi_any(raw)),
+ raw,
+ )
+ }
+
+ /// Visit borrowed `root` with `dispatch` on a fresh engine; the root's
owner outlives it.
+ pub(super) fn run_visitor<D>(
+ root: TVMFFIAny,
+ dispatch: &mut D,
+ ) -> Result<Option<VisitInterrupt>>
+ where
+ for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
+ (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
+ Option<Result<Option<VisitInterrupt>>>,
+ >,
+ {
+ let base = Base::new()?;
+ let type_index = Base::type_index();
+ // The managed runtime may borrow its dispatcher only for this call.
+ // Trusted hooks release all temporary handles before the owner is
dropped.
+ let mut engine = unsafe {
+ ObjectArc::<Object>::new_opaque_unchecked(
+ StructuralVisitEngine {
+ base,
+ dispatcher: dispatcher_ptr(dispatch),
+ },
+ type_index,
+ )
+ };
+ let runtime = ObjectArc::as_mut_ptr(&mut
engine).cast::<StructuralVisitorObj>();
+ // The engine and outer dispatcher storage stay stationary for the
whole run.
+ // Only raw field projections cross recursion; no whole-engine borrow
survives.
+ unsafe {
+ (*runtime).vtable = Self::vtable::<D>();
+ *(*runtime).def_region_mode.get() = DefRegionKind::None as i32;
+ }
+ // Keep the result owned while releasing the runtime, including on
unwind.
+ let result =
+ unsafe {
Any::from_raw_ffi_any(StructuralVisitorObj::call_visitor_raw(runtime, root)) };
+ // Trusted hooks have released every temporary strong/weak runtime
reference.
+ drop(engine);
+ visit_result_from_any(result)
+ }
}
//
-----------------------------------------------------------------------------
@@ -570,12 +724,26 @@ impl StructuralVisitorObj {
/// Consume `result`, naming the borrowed `value` in it when it is an
Error.
#[inline(always)]
fn with_visit_context_raw(result: TVMFFIAny, value: TVMFFIAny) ->
TVMFFIAny {
+ if result.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
+ result
+ } else {
+ Self::with_visit_stop_context_raw(result, value)
+ }
+ }
+
+ // Continuing visits need no owning conversion or input inspection. Keep
+ // interrupt/error handling out of their recursive frames.
+ #[cold]
+ #[inline(never)]
+ extern "C-unwind" fn with_visit_stop_context_raw(
+ result: TVMFFIAny,
+ value: TVMFFIAny,
+ ) -> TVMFFIAny {
if result.type_index != TVMFFITypeIndex::kTVMFFIError as i32
|| value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as
i32
{
result
} else {
- core::hint::cold_path();
// SAFETY: the Error tag establishes the owned object's
representation.
unsafe {
let error = take_error(Any::from_raw_ffi_any(result));
@@ -744,297 +912,58 @@ impl StructuralVisitorObj {
Any::into_raw_ffi_any(result)
}
- /// Run `callback` under requested `kind` on live borrowed `visitor`,
preserving
- /// Pattern precedence. Restore prior region on return or Rust unwind.
Internal
- /// callers keep the runtime live and retain no reference to its region
field.
- #[inline(always)]
- fn with_def_region_kind<T>(
- visitor: StructuralVisitorHandle,
- kind: DefRegionKind,
- callback: impl FnOnce() -> T,
- ) -> T {
- unsafe {
- let previous = *(*visitor).def_region_mode.get();
- if previous == kind as i32 || previous == DefRegionKind::Pattern
as i32 {
- // Inherited descent does not change state; Pattern keeps
precedence.
- return callback();
- }
- *(*visitor).def_region_mode.get() = kind as i32;
- struct Restore {
- visitor: StructuralVisitorHandle,
- previous: i32,
- }
- impl Drop for Restore {
- #[inline(always)]
- fn drop(&mut self) {
- unsafe { *(*self.visitor).def_region_mode.get() =
self.previous };
- }
- }
- let _restore = Restore { visitor, previous };
- callback()
- }
- }
-}
-
-// Generic runtime: only this implementation projects the dispatcher after
Base.
-impl<Base: StructuralVisitorBase> StructuralVisitEngine<Base> {
- /// The engine whose first field is the native prefix `runtime`.
- ///
- /// # Safety
- /// `runtime` must belong to the live, synchronous Rust traversal that
created
- /// this exact repr(C) engine. An arbitrary native visitor is not such an
engine.
- #[inline(always)]
- unsafe fn from_handle(runtime: StructuralVisitorHandle) -> *mut Self {
- runtime.cast()
- }
-
- /// Publish the current exclusive `dispatch` reborrow and visit borrowed
`raw`.
- /// Return one owning raw None/interrupt/Error token.
- ///
- /// # Safety
- /// `runtime` must belong to this live `D` invocation and `raw` remain
valid
- /// through recursion; the caller cannot use competing dispatcher borrows.
- #[inline(always)]
- unsafe fn visit_raw<D>(
- runtime: StructuralVisitorHandle,
- dispatch: &mut D,
- raw: TVMFFIAny,
- ) -> TVMFFIAny {
- let context = Self::checked_dispatcher(runtime, dispatch);
- (*Self::from_handle(runtime)).dispatcher = context;
- StructuralVisitorObj::call_visitor_raw(runtime, raw)
- }
-
- /// Check runtime identity and return the current `dispatch` reborrow
pointer.
- ///
- /// # Safety
- /// `runtime` must be the live engine for `D`; address equality does not
establish
- /// type or lifetime validity. Mismatch panics before a pointer is
published.
- // The caller has already established matching runtime/D types and scope.
- // Read only the stored raw address: do not create a competing mutable
borrow.
- #[inline(always)]
- unsafe fn checked_dispatcher<D>(
- runtime: StructuralVisitorHandle,
- dispatch: &mut D,
- ) -> *mut c_void {
- let current = dispatcher_ptr(dispatch);
- if current != (*Self::from_handle(runtime)).dispatcher {
- core::hint::cold_path();
- wrong_dispatcher();
- }
- current
- }
-
- /// Build the native visitor callback for concrete dispatcher `D`. Its
higher-ranked
- /// view arguments restrict all borrows to the synchronous handler
invocation.
- #[inline(always)]
- fn vtable<D>() -> &'static StructuralVisitorVTable
- where
- for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
- (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
- Option<Result<Option<VisitInterrupt>>>,
- >,
- {
- &StructuralVisitorVTable {
- visit: Self::rust_vtable_visit_raw::<D>,
- }
- }
-
- /// Enter a native visitor callback with borrowed `value`; return one
owning token.
- /// Restore the incoming dispatcher provenance before returning to the
caller.
- ///
- /// # Safety
- /// `runtime` must have this `D` vtable and a live published reborrow. The
erased
- /// ABI lifetime on `value` is valid only for this call. Callback failures
use
- /// Result; Rust panics cannot cross this native C boundary.
- unsafe extern "C" fn rust_vtable_visit_raw<D>(
- runtime: StructuralVisitorHandle,
- value: AnyView<'static>,
- ) -> TVMFFIAny
- where
- for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
- (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
- Option<Result<Option<VisitInterrupt>>>,
- >,
- {
- let context = (*Self::from_handle(runtime)).dispatcher;
- let raw = *value.as_raw_ffi_any();
- let result = if raw.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
- core::hint::cold_path();
- TVMFFIAny::new()
- } else {
- let dispatch = &mut *context.cast::<D>();
- let mut visitor = Self::visitor_context::<D>(runtime);
- // SAFETY: the native caller keeps the input alive for this
synchronous
- // dispatch; the higher-ranked callback cannot retain its borrowed
view.
- match crate::Dispatcher::dispatch_tuple(
- &mut *dispatch,
- (AnyView::from_raw_ffi_any(raw), &mut visitor),
- ) {
- Some(result) => visit_result_raw(result, raw),
- None => Self::default_visit_raw(runtime,
dispatcher_ptr(dispatch), raw),
- }
- };
- // The enclosing callback may resume using its ancestor mutable borrow.
- // Native hooks cannot restore Rust-only borrow provenance for us.
- (*Self::from_handle(runtime)).dispatcher = context;
- result
- }
-
- /// Create the handler view for borrowed `runtime`, without retaining it.
- ///
- /// # Safety
- /// The live runtime's vtable must match D. Keep this view within the
current
- /// higher-ranked handler call, never outliving the runtime or dispatcher.
- #[inline(always)]
- unsafe fn visitor_context<'a, D>(
- runtime: StructuralVisitorHandle,
- ) -> StructuralVisitorView<'a, D, Base> {
- StructuralVisitorView {
- runtime,
- _marker: PhantomData,
- }
- }
-
- /// Publish `context` and descend through borrowed `raw`, then annotate
errors.
- /// The raw return transfers one owning None/interrupt/Error token.
- ///
- /// # Safety
- /// `runtime` must be live and `context` its current exclusive dispatcher
reborrow;
- /// `raw` remains borrowed and must survive the complete synchronous
descent.
+ /// Run `callback` under requested `kind` on live borrowed `visitor`,
preserving
+ /// Pattern precedence. Restore prior region on return or Rust unwind.
Internal
+ /// callers keep the runtime live and retain no reference to its region
field.
#[inline(always)]
- unsafe fn default_visit_raw(
- runtime: StructuralVisitorHandle,
- context: *mut c_void,
- raw: TVMFFIAny,
- ) -> TVMFFIAny {
- // Context is the supplied exclusive reborrow. Publish it before
descent;
- // the handle-only scanner never creates or uses a competing D
reference.
- (*Self::from_handle(runtime)).dispatcher = context;
- StructuralVisitorObj::with_visit_context_raw(
- Base::default_visit_raw(runtime.cast(),
AnyView::from_raw_ffi_any(raw)),
- raw,
- )
- }
-
- /// Visit borrowed `root` with `dispatch` on a fresh engine; the root's
owner outlives it.
- pub(super) fn run_visitor<D>(
- root: TVMFFIAny,
- dispatch: &mut D,
- ) -> Result<Option<VisitInterrupt>>
- where
- for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
- (AnyView<'v>, &'c mut StructuralVisitorView<'r, D, Base>),
- Option<Result<Option<VisitInterrupt>>>,
- >,
- {
- let base = Base::new()?;
- let type_index = Base::type_index();
- // The managed runtime may borrow its dispatcher only for this call.
- // Trusted hooks release all temporary handles before the owner is
dropped.
- let mut engine = unsafe {
- ObjectArc::<Object>::new_opaque_unchecked(
- StructuralVisitEngine {
- base,
- dispatcher: dispatcher_ptr(dispatch),
- },
- type_index,
- )
- };
- let runtime = ObjectArc::as_mut_ptr(&mut
engine).cast::<StructuralVisitorObj>();
- // The engine and outer dispatcher storage stay stationary for the
whole run.
- // Only raw field projections cross recursion; no whole-engine borrow
survives.
+ fn with_def_region_kind<T>(
+ visitor: StructuralVisitorHandle,
+ kind: DefRegionKind,
+ callback: impl FnOnce() -> T,
+ ) -> T {
unsafe {
- (*runtime).vtable = Self::vtable::<D>();
- *(*runtime).def_region_mode.get() = DefRegionKind::None as i32;
+ let previous = *(*visitor).def_region_mode.get();
+ if previous == kind as i32 || previous == DefRegionKind::Pattern
as i32 {
+ // Inherited descent does not change state; Pattern keeps
precedence.
+ return callback();
+ }
+ *(*visitor).def_region_mode.get() = kind as i32;
+ struct Restore {
+ visitor: StructuralVisitorHandle,
+ previous: i32,
+ }
+ impl Drop for Restore {
+ #[inline(always)]
+ fn drop(&mut self) {
+ unsafe { *(*self.visitor).def_region_mode.get() =
self.previous };
+ }
+ }
+ let _restore = Restore { visitor, previous };
+ callback()
}
- // Keep the result owned while releasing the runtime, including on
unwind.
- let result =
- unsafe {
Any::from_raw_ffi_any(StructuralVisitorObj::call_visitor_raw(runtime, root)) };
- // Trusted hooks have released every temporary strong/weak runtime
reference.
- drop(engine);
- visit_result_from_any(result)
}
}
//
-----------------------------------------------------------------------------
-// Public entrypoint
+// Private utility functions
//
-----------------------------------------------------------------------------
-/// Visit `root` with an owned or borrowed [`Dispatcher`](crate::Dispatcher).
-///
-/// Handlers receive [`StructuralVisitorView`] for explicit recursion and keep
state on the
-/// dispatcher. Unmatched values use default child traversal. Pass `&mut D` to
-/// keep the dispatcher and inspect its state after the visit. An owned
dispatcher
-/// is held in the outer call and uses the same borrowed traversal engine.
-///
-/// Callbacks must report failures through [`Result`] and must not panic: a
panic
-/// reaching the non-unwinding native callback boundary aborts the process.
-///
-/// # Arguments
-///
-/// * `root` - Value borrowed for traversal, convertible into an [`AnyView`].
-/// * `dispatcher` - Owned or mutably borrowed dispatcher implementing
[`IntoDispatcher`].
-/// Matching handlers control recursion through [`StructuralVisitorView`];
-/// unmatched values use default child traversal.
-///
-/// # Examples
-///
-/// Sum integer leaves while default traversal descends into the array:
-///
-/// ```
-/// # fn main() -> tvm_ffi::Result<()> {
-/// use tvm_ffi::{
-/// dispatch, structural_visit, AnyView, Array, Result,
StructuralVisitorView,
-/// VisitInterrupt,
-/// };
-///
-/// struct Sum(i64);
-/// #[dispatch(visit, (AnyView<'_>, &mut StructuralVisitorView<'_, Self>),
Result<Option<VisitInterrupt>>)]
-/// impl Sum {
-/// fn visit_integer(
-/// &mut self,
-/// value: i64,
-/// _: &mut StructuralVisitorView<'_, Self>,
-/// ) -> Result<Option<VisitInterrupt>> {
-/// self.0 += value;
-/// Ok(None)
-/// }
-/// }
-///
-/// let root = Array::from_vec(vec![1_i64, 2, 3]);
-/// let mut sum = Sum(0);
-/// assert!(structural_visit(&root, &mut sum)?.is_none());
-/// assert_eq!(sum.0, 6);
-/// # Ok(())
-/// # }
-/// ```
-pub fn structural_visit<'input, R, H, M, D, O>(
- root: R,
- dispatcher: H,
-) -> Result<Option<VisitInterrupt>>
-where
- R: Into<AnyView<'input>>,
- O: BorrowMut<D>,
- for<'v, 'c, 'r> H: IntoDispatcher<
- (
- AnyView<'v>,
- &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
- ),
- Option<Result<Option<VisitInterrupt>>>,
- M,
- Target = D,
- Output = O,
- >,
- for<'s, 'v, 'c, 'r> &'s mut D: crate::Dispatcher<
- (
- AnyView<'v>,
- &'c mut StructuralVisitorView<'r, D, StructuralVisitorObj>,
- ),
- Option<Result<Option<VisitInterrupt>>>,
- >,
-{
- StructuralVisitEngine::visit_root(root, dispatcher)
+#[inline(always)]
+fn dispatcher_ptr<D>(dispatch: &mut D) -> *mut c_void {
+ std::ptr::from_mut(dispatch).cast()
+}
+
+// The visit result carrier, as C++: None continues; a VisitInterrupt or an
Error stops.
+#[inline(always)]
+fn visit_result_raw(result: Result<Option<VisitInterrupt>>, raw: TVMFFIAny) ->
TVMFFIAny {
+ match result {
+ Ok(None) => TVMFFIAny::new(),
+ Ok(Some(interrupt)) => Any::into_raw_ffi_any(interrupt.into()),
+ Err(error) => {
+ core::hint::cold_path();
+
StructuralVisitorObj::with_visit_context_raw(Any::into_raw_ffi_any(error.into()),
raw)
+ }
+ }
}
#[inline(always)]
@@ -1057,3 +986,87 @@ pub(super) fn visit_result_from_any(result: Any) ->
Result<Option<VisitInterrupt
}))
}
}
+
+// A packed hook's owning Any is valid, but its result type is dynamic.
+#[inline(always)]
+fn checked_hook_result(value: Any) -> Any {
+ let tag = value.type_index();
+ if tag == TVMFFITypeIndex::kTVMFFINone as i32
+ || tag == TVMFFITypeIndex::kTVMFFIError as i32
+ || tag == TVMFFITypeIndex::kTVMFFIVisitInterrupt as i32
+ {
+ value
+ } else {
+ invalid_visit_hook_result_error().into()
+ }
+}
+
+/// Consume exactly one Error reference after its integer tag was checked.
+#[inline(always)]
+unsafe fn take_error(value: Any) -> Error {
+ use crate::type_traits::AnyCompatible;
+ let mut raw = Any::into_raw_ffi_any(value);
+ Error::move_from_any_after_check(&mut raw)
+}
+
+#[inline(always)]
+fn with_field_error_context(result: Any, name: &str) -> Any {
+ if result.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
+ core::hint::cold_path();
+ // SAFETY: direct tag check establishes the Error representation.
+ with_structural_field_error_context(unsafe { take_error(result) },
"visit", name).into()
+ } else {
+ result
+ }
+}
+
+// Columns are process-lifetime registry entries; a missing per-type cell still
+// falls back to reflected traversal. A missing required column is an error.
+static STRUCTURAL_VISIT_COLUMN: ColdInitLazyLock<TypeAttrColumn> =
ColdInitLazyLock::new(|| {
+ TypeAttrColumn::new(STRUCTURAL_VISIT_ATTR).expect("missing __s_visit__
column")
+});
+
+/// Return the shared visit-hook column without caching optional per-type
cells.
+#[inline(always)]
+fn structural_visit_column() -> TypeAttrColumn {
+ *STRUCTURAL_VISIT_COLUMN
+}
+
+//
-----------------------------------------------------------------------------
+// Outlined errors
+//
-----------------------------------------------------------------------------
+
+#[cold]
+#[inline(never)]
+fn unregistered_visit_type_error(type_index: i32) -> Error {
+ runtime_error(&format!(
+ "native visitor: unregistered type index {type_index}"
+ ))
+}
+
+#[cold]
+#[inline(never)]
+fn invalid_visit_hook_result_error() -> Error {
+ Error::new(
+ TYPE_ERROR,
+ "structural visit hook must return None or ffi.VisitInterrupt",
+ "",
+ )
+}
+
+/// Consume `error` and annotate it from borrowed input scalar fields. Internal
+/// callers keep that input live; the Error remains owned if formatting
unwinds.
+// Perf note: Keep error formatting and context-allocation scratch out of
visit frames.
+#[cold]
+#[inline(never)]
+fn annotate_visit_error(error: Error, value: TVMFFIAny) -> Error {
+ let error = update_visit_error_context(error, value);
+ with_structural_error_context(
+ error,
+ "visit",
+ &format!(
+ "object `{}`",
+ crate::object::type_key_of_index(value.type_index)
+ ),
+ )
+}
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index c3672c82..880b6023 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -93,7 +93,8 @@ pub use crate::type_traits::AnyCompatible;
/// `None` when no handler matches. The macro also implements [`Dispatcher`]
/// for `&Self` or `&mut Self`, so the type can drive [`structural_walk`] and
/// the other traversals, whose engines call it and run their own default
-/// recursion; no prefix has a reserved meaning.
+/// recursion; no prefix has a reserved meaning. Handlers without an `inline`
+/// attribute are marked `#[inline(always)]`.
///
/// # Arguments
///
diff --git a/rust/tvm-ffi/src/macros.rs b/rust/tvm-ffi/src/macros.rs
index c87d65f3..206ec201 100644
--- a/rust/tvm-ffi/src/macros.rs
+++ b/rust/tvm-ffi/src/macros.rs
@@ -19,6 +19,7 @@
// Re-export paste for the export macros, so downstream crates need not depend
on it.
#[doc(hidden)]
pub use paste;
+
// ----------------------------------------------------------------------------
// Macros for error handling
// ----------------------------------------------------------------------------
@@ -74,6 +75,166 @@ macro_rules! cached_global_func {
}};
}
+/// Check the return code of the safe call
+///
+/// # Arguments
+/// * `ret_code` - The return code of the safe call
+///
+/// # Returns
+/// * `Result<(), Error>` - The result of the safe call
+/// Macro to check safe calls and automatically update traceback with
file/line info
+///
+/// Usage: check_safe_call!(function(args))?;
+#[doc(hidden)]
+#[macro_export]
+macro_rules! check_safe_call {
+ ($expr:expr) => {{
+ let ret_code = $expr;
+ if ret_code == 0 {
+ Ok(())
+ } else {
+ let error = $crate::error::Error::from_raised();
+ Err(error)
+ }
+ }};
+}
+
+/// Return an [`Error`](crate::Error) of a kind with a formatted message from
+/// the current function.
+///
+/// `bail!(kind, fmt, args...)` formats the message as `format!` does and
+/// records the source location in the backtrace.
+///
+/// # Arguments
+///
+/// * `kind` - The kind of the error, such as
[`VALUE_ERROR`](crate::VALUE_ERROR).
+/// * `fmt` - The format string of the message.
+/// * `args` - The format arguments.
+///
+/// # Returns
+///
+/// Returns `Err` of the error from the enclosing function.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{bail, Result, INDEX_ERROR};
+///
+/// fn get(items: &[i64], index: usize) -> Result<i64> {
+/// if index >= items.len() {
+/// bail!(INDEX_ERROR, "index {} out of range for length {}", index,
items.len());
+/// }
+/// Ok(items[index])
+/// }
+///
+/// assert_eq!(get(&[1, 2], 1)?, 2);
+/// assert_eq!(get(&[1, 2], 5).unwrap_err().message(), "index 5 out of range
for length 2");
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+#[macro_export]
+macro_rules! bail {
+ ($error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
+ let context = format!(
+ " File \"{}\", line {}, in {}\n",
+ file!(),
+ line!(),
+ $crate::function_name!()
+ );
+ return Err($crate::error::Error::new($error_kind, &format!($fmt $(,
$args)*), &context));
+ }};
+}
+
+/// Return an [`Error`](crate::Error) from the current function unless a
+/// condition holds.
+///
+/// `ensure!(cond, kind, fmt, args...)` is [`bail!`](crate::bail)`(kind, fmt,
+/// args...)` when `cond` is false.
+///
+/// # Arguments
+///
+/// * `cond` - The condition that must hold.
+/// * `kind` - The kind of the error.
+/// * `fmt` - The format string of the message.
+/// * `args` - The format arguments.
+///
+/// # Returns
+///
+/// Returns `Err` of the error from the enclosing function when `cond` is
+/// false, and does nothing otherwise.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{ensure, Result, VALUE_ERROR};
+///
+/// fn sqrt(x: f64) -> Result<f64> {
+/// ensure!(x >= 0.0, VALUE_ERROR, "sqrt of negative number {}", x);
+/// Ok(x.sqrt())
+/// }
+///
+/// assert_eq!(sqrt(4.0)?, 2.0);
+/// assert!(sqrt(-1.0).is_err());
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
+#[macro_export]
+macro_rules! ensure {
+ ($cond:expr, $error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
+ if !$cond {
+ $crate::bail!($error_kind, $fmt $(, $args)*);
+ }
+ }};
+}
+
+/// Append the current source location to the backtrace of an `Err` result,
+/// and pass an `Ok` through.
+///
+/// # Arguments
+///
+/// * `result` - The result to attach the context to.
+///
+/// # Returns
+///
+/// The result, with the location appended to an error's backtrace.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{attach_context, bail, Result, RUNTIME_ERROR};
+///
+/// fn inner() -> Result<()> {
+/// bail!(RUNTIME_ERROR, "failed");
+/// }
+///
+/// fn outer() -> Result<()> {
+/// attach_context!(inner())
+/// }
+///
+/// assert!(outer().unwrap_err().backtrace().contains("outer"));
+/// ```
+#[macro_export]
+macro_rules! attach_context {
+ ($error:expr) => {{
+ match $error {
+ Ok(value) => Ok(value),
+ Err(error) => {
+ let context = format!(
+ " File \"{}\", line {}, in {}\n",
+ file!(),
+ line!(),
+ $crate::function_name!()
+ );
+ Err($crate::error::Error::with_appended_backtrace(
+ error, &context,
+ ))
+ }
+ }
+ }};
+}
+
+// ----------------------------------------------------------------------------
+// Macros for object references
+// ----------------------------------------------------------------------------
+
/// Implement zero-copy conversions from a derived object reference to one of
/// its base object-reference types: `From<Source>` and `From<&Source>` into
/// `Target`, `AsRef<Target>`, which borrows a `&Source` as `&Target`
@@ -288,162 +449,6 @@ macro_rules! unsafe_impl_object_ref_downcast_ref {
};
}
-/// Check the return code of the safe call
-///
-/// # Arguments
-/// * `ret_code` - The return code of the safe call
-///
-/// # Returns
-/// * `Result<(), Error>` - The result of the safe call
-/// Macro to check safe calls and automatically update traceback with
file/line info
-///
-/// Usage: check_safe_call!(function(args))?;
-#[doc(hidden)]
-#[macro_export]
-macro_rules! check_safe_call {
- ($expr:expr) => {{
- let ret_code = $expr;
- if ret_code == 0 {
- Ok(())
- } else {
- let error = $crate::error::Error::from_raised();
- Err(error)
- }
- }};
-}
-
-/// Return an [`Error`](crate::Error) of a kind with a formatted message from
-/// the current function.
-///
-/// `bail!(kind, fmt, args...)` formats the message as `format!` does and
-/// records the source location in the backtrace.
-///
-/// # Arguments
-///
-/// * `kind` - The kind of the error, such as
[`VALUE_ERROR`](crate::VALUE_ERROR).
-/// * `fmt` - The format string of the message.
-/// * `args` - The format arguments.
-///
-/// # Returns
-///
-/// Returns `Err` of the error from the enclosing function.
-///
-/// # Examples
-///
-/// ```
-/// use tvm_ffi::{bail, Result, INDEX_ERROR};
-///
-/// fn get(items: &[i64], index: usize) -> Result<i64> {
-/// if index >= items.len() {
-/// bail!(INDEX_ERROR, "index {} out of range for length {}", index,
items.len());
-/// }
-/// Ok(items[index])
-/// }
-///
-/// assert_eq!(get(&[1, 2], 1)?, 2);
-/// assert_eq!(get(&[1, 2], 5).unwrap_err().message(), "index 5 out of range
for length 2");
-/// # Ok::<(), tvm_ffi::Error>(())
-/// ```
-#[macro_export]
-macro_rules! bail {
- ($error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
- let context = format!(
- " File \"{}\", line {}, in {}\n",
- file!(),
- line!(),
- $crate::function_name!()
- );
- return Err($crate::error::Error::new($error_kind, &format!($fmt $(,
$args)*), &context));
- }};
-}
-
-/// Return an [`Error`](crate::Error) from the current function unless a
-/// condition holds.
-///
-/// `ensure!(cond, kind, fmt, args...)` is [`bail!`](crate::bail)`(kind, fmt,
-/// args...)` when `cond` is false.
-///
-/// # Arguments
-///
-/// * `cond` - The condition that must hold.
-/// * `kind` - The kind of the error.
-/// * `fmt` - The format string of the message.
-/// * `args` - The format arguments.
-///
-/// # Returns
-///
-/// Returns `Err` of the error from the enclosing function when `cond` is
-/// false, and does nothing otherwise.
-///
-/// # Examples
-///
-/// ```
-/// use tvm_ffi::{ensure, Result, VALUE_ERROR};
-///
-/// fn sqrt(x: f64) -> Result<f64> {
-/// ensure!(x >= 0.0, VALUE_ERROR, "sqrt of negative number {}", x);
-/// Ok(x.sqrt())
-/// }
-///
-/// assert_eq!(sqrt(4.0)?, 2.0);
-/// assert!(sqrt(-1.0).is_err());
-/// # Ok::<(), tvm_ffi::Error>(())
-/// ```
-#[macro_export]
-macro_rules! ensure {
- ($cond:expr, $error_kind:expr, $fmt:expr $(, $args:expr)* $(,)?) => {{
- if !$cond {
- $crate::bail!($error_kind, $fmt $(, $args)*);
- }
- }};
-}
-
-/// Append the current source location to the backtrace of an `Err` result,
-/// and pass an `Ok` through.
-///
-/// # Arguments
-///
-/// * `result` - The result to attach the context to.
-///
-/// # Returns
-///
-/// The result, with the location appended to an error's backtrace.
-///
-/// # Examples
-///
-/// ```
-/// use tvm_ffi::{attach_context, bail, Result, RUNTIME_ERROR};
-///
-/// fn inner() -> Result<()> {
-/// bail!(RUNTIME_ERROR, "failed");
-/// }
-///
-/// fn outer() -> Result<()> {
-/// attach_context!(inner())
-/// }
-///
-/// assert!(outer().unwrap_err().backtrace().contains("outer"));
-/// ```
-#[macro_export]
-macro_rules! attach_context {
- ($error:expr) => {{
- match $error {
- Ok(value) => Ok(value),
- Err(error) => {
- let context = format!(
- " File \"{}\", line {}, in {}\n",
- file!(),
- line!(),
- $crate::function_name!()
- );
- Err($crate::error::Error::with_appended_backtrace(
- error, &context,
- ))
- }
- }
- }};
-}
-
// ----------------------------------------------------------------------------
// Macros for any definitions
// ----------------------------------------------------------------------------
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 51ab2919..34236e7f 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -40,6 +40,28 @@ pub struct Object {
header: TVMFFIObject,
}
+impl Object {
+ #[allow(clippy::new_without_default)]
+ pub fn new() -> Self {
+ Self {
+ header: TVMFFIObject::new(),
+ }
+ }
+}
+
+unsafe impl ObjectCore for Object {
+ const TYPE_KEY: &'static str = "ffi.Object";
+ const TYPE_DEPTH: i32 = 0;
+ #[inline]
+ fn type_index() -> i32 {
+ TypeIndex::kTVMFFIStaticObjectBegin as i32
+ }
+ #[inline]
+ unsafe fn object_header_mut(this: &mut Self) -> &mut TVMFFIObject {
+ &mut this.header
+ }
+}
+
/// Arc-like wrapper for Object that allows shared ownership
///
/// \tparam T The type of the object to be wrapped
@@ -49,6 +71,213 @@ pub struct ObjectArc<T: ObjectCore> {
_phantom: std::marker::PhantomData<T>,
}
+impl ObjectArc<Object> {
+ /// Allocate a synchronous runtime payload that may contain borrowed Rust
state.
+ ///
+ /// # Safety
+ /// `T` begins with a valid Object header and is ABI-compatible with the
+ /// registered `type_index`. The caller must keep this owner scoped to the
+ /// payload's borrows and ensure every temporary strong and weak reference
+ /// is released before those borrows end. Neither native handles nor this
+ /// erased owner may escape that synchronous scope. Destruction uses T's
+ /// exact layout and drop function, not the erased Object layout.
+ /// The erased owner and handles must remain on this thread unless the
+ /// concrete payload permits sharing or transfer; erasure grants neither.
+ pub(crate) unsafe fn new_opaque_unchecked<T>(data: T, type_index: i32) ->
Self {
+ Self {
+ ptr: allocate_object(data, type_index).cast(),
+ _phantom: std::marker::PhantomData,
+ }
+ }
+}
+
+impl<T: ObjectCore> ObjectArc<T> {
+ /// Allocate an initialized object at its stable managed address.
+ pub fn new(data: T) -> Self {
+ // Resolve metadata before moving the payload into its allocation.
+ let type_index = T::type_index();
+ unsafe {
+ Self {
+ ptr: allocate_object(data, type_index),
+ _phantom: std::marker::PhantomData,
+ }
+ }
+ }
+
+ /// Allocate a stable object followed by its type's extra item storage.
+ pub fn new_with_extra_items<U>(data: T) -> Self
+ where
+ T: ObjectCoreWithExtraItems<ExtraItem = U>,
+ {
+ unsafe {
+ // ensure strict alignment requirements
+ // so we can have { T, U*extra_items } layout
+ assert_eq!(std::mem::align_of::<T>() % std::mem::align_of::<U>(),
0);
+ assert_eq!(std::mem::size_of::<T>() % std::mem::align_of::<U>(),
0);
+ let extra_items_count = T::extra_items_count(&data);
+ let layout = unsafe_::extra_items_layout::<T,
U>(extra_items_count);
+ let raw_data_ptr = std::alloc::alloc(layout);
+ if raw_data_ptr.is_null() {
+ std::alloc::handle_alloc_error(layout);
+ }
+ let ptr = raw_data_ptr as *mut T;
+ std::ptr::write(ptr, data);
+ // now override the header directly
+ std::ptr::write(
+ ptr as *mut TVMFFIObject,
+ TVMFFIObject {
+ combined_ref_count:
AtomicU64::new(COMBINED_REF_COUNT_BOTH_ONE),
+ type_index: T::type_index(),
+ __padding: 0,
+ deleter:
Some(unsafe_::object_deleter_for_new_with_extra_items::<T, U>),
+ },
+ );
+ // move into the object arc ptr
+ Self {
+ ptr: std::ptr::NonNull::new_unchecked(ptr),
+ _phantom: std::marker::PhantomData,
+ }
+ }
+ }
+
+ /// Whether `this` holds a null pointer, as all-zero bytes do: no
+ /// `ObjectArc`, but the field of an object that a creator left undefined.
+ ///
+ /// See also [`AnyCompatible::is_undefined`].
+ ///
+ /// # Safety
+ ///
+ /// `this` points to an `ObjectArc<T>` or to all-zero bytes.
+ #[inline]
+ pub unsafe fn is_null(this: *const Self) -> bool {
+ (*std::ptr::addr_of!((*this).ptr).cast::<*const T>()).is_null()
+ }
+
+ /// Move a previously allocated object into the ObjectArc
+ ///
+ /// # Arguments
+ /// * `ptr` - The raw pointer to move into the ObjectArc
+ ///
+ /// # Returns
+ /// * `ObjectArc<T>` - The ObjectArc
+ ///
+ /// # Safety
+ /// `ptr` must own one strong reference to a live, stable managed
allocation
+ /// whose dynamic type is compatible with `T` and whose payload is valid.
+ /// Ownership of that reference is transferred into the returned handle.
+ #[inline(always)]
+ pub unsafe fn from_raw(ptr: *const T) -> Self {
+ Self {
+ ptr: std::ptr::NonNull::new_unchecked(ptr as *mut T),
+ _phantom: std::marker::PhantomData,
+ }
+ }
+
+ /// Move the ObjectArc into a raw pointer
+ ///
+ /// # Arguments
+ /// * `this` - The ObjectArc to move into a raw pointer
+ ///
+ /// # Returns
+ /// * `*const T` - The raw pointer, which owns the strong reference; pass
it
+ /// back to [`ObjectArc::from_raw`] to release it, as std
`Arc::into_raw`.
+ #[inline(always)]
+ pub fn into_raw(this: Self) -> *const T {
+ let droped_this = std::mem::ManuallyDrop::new(this);
+ droped_this.ptr.as_ptr() as *const T
+ }
+
+ /// Get the raw pointer from the ObjectArc
+ ///
+ /// Caller should view this as a non-owning reference
+ ///
+ /// # Arguments
+ /// * `this` - The ObjectArc to get the raw pointer
+ ///
+ /// # Returns
+ /// * `*const T` - The raw pointer, as std `Arc::as_ptr`. Dereferencing it
+ /// must respect existing borrows, and it cannot outlive an owning
handle.
+ #[inline]
+ pub fn as_ptr(this: &Self) -> *const T {
+ this.ptr.as_ptr() as *const T
+ }
+
+ /// Get the raw mutable pointer from the ObjectArc
+ ///
+ /// Caller should view this as a non-owning reference
+ ///
+ /// # Arguments
+ /// * `this` - The ObjectArc to get the raw pointer
+ ///
+ /// # Returns
+ /// * `*mut T` - The raw pointer. The handle need not be unique: writing
+ /// through it needs exclusive access to the object, and must not move or
+ /// replace the object or its header.
+ #[inline]
+ pub fn as_mut_ptr(this: &mut Self) -> *mut T {
+ this.ptr.as_ptr()
+ }
+
+ /// Get the strong reference count of the ObjectArc
+ ///
+ /// # Arguments
+ /// * `this` - The ObjectArc to get the strong reference count
+ ///
+ /// # Returns
+ /// * `usize` - The strong reference count
+ #[inline]
+ pub fn strong_count(this: &Self) -> usize {
+ unsafe {
unsafe_::strong_count(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
+ }
+
+ /// Whether this is the only reference, so it may be mutated in place.
+ #[inline]
+ pub(crate) fn is_unique(this: &Self) -> bool {
+ unsafe { unsafe_::is_unique(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
+ }
+
+ /// Get the weak reference count of the ObjectArc
+ ///
+ /// # Arguments
+ /// * `this` - The ObjectArc to get the weak reference count
+ ///
+ /// # Returns
+ /// * `usize` - The weak reference count
+ #[inline]
+ pub fn weak_count(this: &Self) -> usize {
+ unsafe { unsafe_::weak_count(this.ptr.as_ptr().cast::<TVMFFIObject>())
}
+ }
+}
+
+// implement Deref for ObjectArc
+impl<T: ObjectCore> Deref for ObjectArc<T> {
+ type Target = T;
+ #[inline]
+ fn deref(&self) -> &Self::Target {
+ unsafe { self.ptr.as_ref() }
+ }
+}
+
+// implement Drop for ObjectArc
+impl<T: ObjectCore> Drop for ObjectArc<T> {
+ #[inline(always)]
+ fn drop(&mut self) {
+ unsafe { unsafe_::dec_ref(self.ptr.as_ptr().cast::<TVMFFIObject>()) }
+ }
+}
+
+// implement Clone for ObjectArc
+impl<T: ObjectCore> Clone for ObjectArc<T> {
+ #[inline]
+ fn clone(&self) -> Self {
+ unsafe { unsafe_::inc_ref(self.ptr.as_ptr().cast::<TVMFFIObject>()) }
+ Self {
+ ptr: self.ptr,
+ _phantom: std::marker::PhantomData,
+ }
+ }
+}
+
/// An object type whose shared instances are never mutated, so `ObjectArc<T>`
is
/// `Send + Sync` when `T`'s fields are.
///
@@ -65,6 +294,17 @@ unsafe impl<T: ImmutableObject + Send + Sync> Send for
ObjectArc<T> {}
// SAFETY: as for `Send`.
unsafe impl<T: ImmutableObject + Send + Sync> Sync for ObjectArc<T> {}
+/// A generic managed reference to a TVM-FFI object.
+///
+/// Holds an `ObjectArc<Object>` that manages the underlying object's lifetime
+/// through reference counting.
+#[repr(C)]
+#[derive(ObjectRef, Clone)]
+#[object_ref(skip_deref)]
+pub struct ObjectRef {
+ data: ObjectArc<Object>,
+}
+
/// The native representation of an object-system type.
///
/// # Safety
@@ -351,17 +591,6 @@ pub unsafe trait ObjectRefAnyCompatible: ObjectRefCore {
}
}
-/// Write the object cell of the live object `ptr`.
-#[inline(always)]
-unsafe fn write_object_cell<T: ObjectCore>(ptr: *const T, data: &mut
TVMFFIAny) {
- let object = ptr as *mut TVMFFIObject;
- data.type_index = (*object).type_index;
- // SAFETY: a live object's header holds its registered object-range type
index.
- std::hint::assert_unchecked(data.type_index >=
TypeIndex::kTVMFFIStaticObjectBegin as i32);
- data.small_str_len = 0;
- data.data_union.v_obj = object;
-}
-
// The object cell encoding, with the strict check `is_instance_of` then
// `extra_check`, and `convert_fallback` after it in converting casts.
unsafe impl<T: ObjectRefAnyCompatible> AnyCompatible for T {
@@ -603,11 +832,44 @@ pub fn is_instance_of<Target:
ObjectCore>(object_type_index: i32) -> bool {
}
}
-/// The strict downcast from the root [`ObjectRef`] to `B`, for the containers'
-/// `TryFrom<ObjectRef>`, which are hand-written as their upcasts are.
-#[inline(always)]
-pub(crate) fn root_try_into<B>(value: ObjectRef) -> crate::error::Result<B>
-where
+/// Format a managed reference as its runtime type key and object address, as
+/// the `Debug` implementation of `#[derive(ObjectRef)]` does.
+///
+/// Call it from `Debug` for a reference type implemented by hand, such as a
+/// typed view over another reference, so that `downcast(..).unwrap()` and
+/// `assert_eq!` can print it.
+///
+/// ```
+/// use tvm_ffi::object::{fmt_object_ref, ObjectRef};
+/// use tvm_ffi::Array;
+/// struct Shown(ObjectRef);
+/// impl std::fmt::Debug for Shown {
+/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+/// fmt_object_ref(&self.0, f)
+/// }
+/// }
+/// let shown = Shown(Array::from_vec(vec![1_i64]).into());
+/// assert!(format!("{shown:?}").starts_with("ffi.Array@"));
+/// ```
+pub fn fmt_object_ref<T: ObjectRefCore>(
+ this: &T,
+ f: &mut std::fmt::Formatter<'_>,
+) -> std::fmt::Result {
+ let object = ObjectArc::as_ptr(T::data(this)).cast::<TVMFFIObject>();
+ // SAFETY: `this` keeps the object, and so its header, alive.
+ let type_index = unsafe { (*object).type_index };
+ write!(f, "{}@{:p}", type_key_of_index(type_index), object)
+}
+
+//
-----------------------------------------------------------------------------
+// Helpers
+//
-----------------------------------------------------------------------------
+
+/// The strict downcast from the root [`ObjectRef`] to `B`, for the containers'
+/// `TryFrom<ObjectRef>`, which are hand-written as their upcasts are.
+#[inline(always)]
+pub(crate) fn root_try_into<B>(value: ObjectRef) -> crate::error::Result<B>
+where
B: ObjectRefCore,
{
value
@@ -640,35 +902,6 @@ pub(crate) fn into_root_object_ref<S:
ObjectRefCore>(value: S) -> ObjectRef {
}
}
-/// Format a managed reference as its runtime type key and object address, as
-/// the `Debug` implementation of `#[derive(ObjectRef)]` does.
-///
-/// Call it from `Debug` for a reference type implemented by hand, such as a
-/// typed view over another reference, so that `downcast(..).unwrap()` and
-/// `assert_eq!` can print it.
-///
-/// ```
-/// use tvm_ffi::object::{fmt_object_ref, ObjectRef};
-/// use tvm_ffi::Array;
-/// struct Shown(ObjectRef);
-/// impl std::fmt::Debug for Shown {
-/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-/// fmt_object_ref(&self.0, f)
-/// }
-/// }
-/// let shown = Shown(Array::from_vec(vec![1_i64]).into());
-/// assert!(format!("{shown:?}").starts_with("ffi.Array@"));
-/// ```
-pub fn fmt_object_ref<T: ObjectRefCore>(
- this: &T,
- f: &mut std::fmt::Formatter<'_>,
-) -> std::fmt::Result {
- let object = ObjectArc::as_ptr(T::data(this)).cast::<TVMFFIObject>();
- // SAFETY: `this` keeps the object, and so its header, alive.
- let type_index = unsafe { (*object).type_index };
- write!(f, "{}@{:p}", type_key_of_index(type_index), object)
-}
-
/// The registered type key of `type_index`, or `<type_index N>` when it has
none.
pub(crate) fn type_key_of_index(type_index: i32) -> String {
unsafe {
@@ -681,15 +914,37 @@ pub(crate) fn type_key_of_index(type_index: i32) ->
String {
}
}
-/// A generic managed reference to a TVM-FFI object.
-///
-/// Holds an `ObjectArc<Object>` that manages the underlying object's lifetime
-/// through reference counting.
-#[repr(C)]
-#[derive(ObjectRef, Clone)]
-#[object_ref(skip_deref)]
-pub struct ObjectRef {
- data: ObjectArc<Object>,
+/// Write the object cell of the live object `ptr`.
+#[inline(always)]
+unsafe fn write_object_cell<T: ObjectCore>(ptr: *const T, data: &mut
TVMFFIAny) {
+ let object = ptr as *mut TVMFFIObject;
+ data.type_index = (*object).type_index;
+ // SAFETY: a live object's header holds its registered object-range type
index.
+ std::hint::assert_unchecked(data.type_index >=
TypeIndex::kTVMFFIStaticObjectBegin as i32);
+ data.small_str_len = 0;
+ data.data_union.v_obj = object;
+}
+
+// Allocate and initialize the normal FFI header while preserving the concrete
+// payload's alignment and drop/layout deleter. The payload starts with an
Object.
+unsafe fn allocate_object<T>(data: T, type_index: i32) -> NonNull<T> {
+ let layout = std::alloc::Layout::new::<T>();
+ let allocation = std::alloc::alloc(layout);
+ if allocation.is_null() {
+ std::alloc::handle_alloc_error(layout);
+ }
+ let ptr = allocation.cast::<T>();
+ std::ptr::write(ptr, data);
+ std::ptr::write(
+ ptr.cast::<TVMFFIObject>(),
+ TVMFFIObject {
+ combined_ref_count: AtomicU64::new(COMBINED_REF_COUNT_BOTH_ONE),
+ type_index,
+ __padding: 0,
+ deleter: Some(unsafe_::object_deleter_for_new::<T>),
+ },
+ );
+ NonNull::new_unchecked(ptr)
}
/// Unsafe operations on object
@@ -828,254 +1083,3 @@ pub mod unsafe_ {
}
}
}
-
-impl Object {
- #[allow(clippy::new_without_default)]
- pub fn new() -> Self {
- Self {
- header: TVMFFIObject::new(),
- }
- }
-}
-
-unsafe impl ObjectCore for Object {
- const TYPE_KEY: &'static str = "ffi.Object";
- const TYPE_DEPTH: i32 = 0;
- #[inline]
- fn type_index() -> i32 {
- TypeIndex::kTVMFFIStaticObjectBegin as i32
- }
- #[inline]
- unsafe fn object_header_mut(this: &mut Self) -> &mut TVMFFIObject {
- &mut this.header
- }
-}
-
-// Allocate and initialize the normal FFI header while preserving the concrete
-// payload's alignment and drop/layout deleter. The payload starts with an
Object.
-unsafe fn allocate_object<T>(data: T, type_index: i32) -> NonNull<T> {
- let layout = std::alloc::Layout::new::<T>();
- let allocation = std::alloc::alloc(layout);
- if allocation.is_null() {
- std::alloc::handle_alloc_error(layout);
- }
- let ptr = allocation.cast::<T>();
- std::ptr::write(ptr, data);
- std::ptr::write(
- ptr.cast::<TVMFFIObject>(),
- TVMFFIObject {
- combined_ref_count: AtomicU64::new(COMBINED_REF_COUNT_BOTH_ONE),
- type_index,
- __padding: 0,
- deleter: Some(unsafe_::object_deleter_for_new::<T>),
- },
- );
- NonNull::new_unchecked(ptr)
-}
-
-impl ObjectArc<Object> {
- /// Allocate a synchronous runtime payload that may contain borrowed Rust
state.
- ///
- /// # Safety
- /// `T` begins with a valid Object header and is ABI-compatible with the
- /// registered `type_index`. The caller must keep this owner scoped to the
- /// payload's borrows and ensure every temporary strong and weak reference
- /// is released before those borrows end. Neither native handles nor this
- /// erased owner may escape that synchronous scope. Destruction uses T's
- /// exact layout and drop function, not the erased Object layout.
- /// The erased owner and handles must remain on this thread unless the
- /// concrete payload permits sharing or transfer; erasure grants neither.
- pub(crate) unsafe fn new_opaque_unchecked<T>(data: T, type_index: i32) ->
Self {
- Self {
- ptr: allocate_object(data, type_index).cast(),
- _phantom: std::marker::PhantomData,
- }
- }
-}
-
-impl<T: ObjectCore> ObjectArc<T> {
- /// Allocate an initialized object at its stable managed address.
- pub fn new(data: T) -> Self {
- // Resolve metadata before moving the payload into its allocation.
- let type_index = T::type_index();
- unsafe {
- Self {
- ptr: allocate_object(data, type_index),
- _phantom: std::marker::PhantomData,
- }
- }
- }
-
- /// Allocate a stable object followed by its type's extra item storage.
- pub fn new_with_extra_items<U>(data: T) -> Self
- where
- T: ObjectCoreWithExtraItems<ExtraItem = U>,
- {
- unsafe {
- // ensure strict alignment requirements
- // so we can have { T, U*extra_items } layout
- assert_eq!(std::mem::align_of::<T>() % std::mem::align_of::<U>(),
0);
- assert_eq!(std::mem::size_of::<T>() % std::mem::align_of::<U>(),
0);
- let extra_items_count = T::extra_items_count(&data);
- let layout = unsafe_::extra_items_layout::<T,
U>(extra_items_count);
- let raw_data_ptr = std::alloc::alloc(layout);
- if raw_data_ptr.is_null() {
- std::alloc::handle_alloc_error(layout);
- }
- let ptr = raw_data_ptr as *mut T;
- std::ptr::write(ptr, data);
- // now override the header directly
- std::ptr::write(
- ptr as *mut TVMFFIObject,
- TVMFFIObject {
- combined_ref_count:
AtomicU64::new(COMBINED_REF_COUNT_BOTH_ONE),
- type_index: T::type_index(),
- __padding: 0,
- deleter:
Some(unsafe_::object_deleter_for_new_with_extra_items::<T, U>),
- },
- );
- // move into the object arc ptr
- Self {
- ptr: std::ptr::NonNull::new_unchecked(ptr),
- _phantom: std::marker::PhantomData,
- }
- }
- }
-
- /// Whether `this` holds a null pointer, as all-zero bytes do: no
- /// `ObjectArc`, but the field of an object that a creator left undefined.
- ///
- /// See also [`AnyCompatible::is_undefined`].
- ///
- /// # Safety
- ///
- /// `this` points to an `ObjectArc<T>` or to all-zero bytes.
- #[inline]
- pub unsafe fn is_null(this: *const Self) -> bool {
- (*std::ptr::addr_of!((*this).ptr).cast::<*const T>()).is_null()
- }
-
- /// Move a previously allocated object into the ObjectArc
- ///
- /// # Arguments
- /// * `ptr` - The raw pointer to move into the ObjectArc
- ///
- /// # Returns
- /// * `ObjectArc<T>` - The ObjectArc
- ///
- /// # Safety
- /// `ptr` must own one strong reference to a live, stable managed
allocation
- /// whose dynamic type is compatible with `T` and whose payload is valid.
- /// Ownership of that reference is transferred into the returned handle.
- #[inline(always)]
- pub unsafe fn from_raw(ptr: *const T) -> Self {
- Self {
- ptr: std::ptr::NonNull::new_unchecked(ptr as *mut T),
- _phantom: std::marker::PhantomData,
- }
- }
-
- /// Move the ObjectArc into a raw pointer
- ///
- /// # Arguments
- /// * `this` - The ObjectArc to move into a raw pointer
- ///
- /// # Returns
- /// * `*const T` - The raw pointer, which owns the strong reference; pass
it
- /// back to [`ObjectArc::from_raw`] to release it, as std
`Arc::into_raw`.
- #[inline(always)]
- pub fn into_raw(this: Self) -> *const T {
- let droped_this = std::mem::ManuallyDrop::new(this);
- droped_this.ptr.as_ptr() as *const T
- }
-
- /// Get the raw pointer from the ObjectArc
- ///
- /// Caller should view this as a non-owning reference
- ///
- /// # Arguments
- /// * `this` - The ObjectArc to get the raw pointer
- ///
- /// # Returns
- /// * `*const T` - The raw pointer, as std `Arc::as_ptr`. Dereferencing it
- /// must respect existing borrows, and it cannot outlive an owning
handle.
- #[inline]
- pub fn as_ptr(this: &Self) -> *const T {
- this.ptr.as_ptr() as *const T
- }
-
- /// Get the raw mutable pointer from the ObjectArc
- ///
- /// Caller should view this as a non-owning reference
- ///
- /// # Arguments
- /// * `this` - The ObjectArc to get the raw pointer
- ///
- /// # Returns
- /// * `*mut T` - The raw pointer. The handle need not be unique: writing
- /// through it needs exclusive access to the object, and must not move or
- /// replace the object or its header.
- #[inline]
- pub fn as_mut_ptr(this: &mut Self) -> *mut T {
- this.ptr.as_ptr()
- }
-
- /// Get the strong reference count of the ObjectArc
- ///
- /// # Arguments
- /// * `this` - The ObjectArc to get the strong reference count
- ///
- /// # Returns
- /// * `usize` - The strong reference count
- #[inline]
- pub fn strong_count(this: &Self) -> usize {
- unsafe {
unsafe_::strong_count(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
- }
-
- /// Whether this is the only reference, so it may be mutated in place.
- #[inline]
- pub(crate) fn is_unique(this: &Self) -> bool {
- unsafe { unsafe_::is_unique(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
- }
-
- /// Get the weak reference count of the ObjectArc
- ///
- /// # Arguments
- /// * `this` - The ObjectArc to get the weak reference count
- ///
- /// # Returns
- /// * `usize` - The weak reference count
- #[inline]
- pub fn weak_count(this: &Self) -> usize {
- unsafe { unsafe_::weak_count(this.ptr.as_ptr().cast::<TVMFFIObject>())
}
- }
-}
-
-// implement Deref for ObjectArc
-impl<T: ObjectCore> Deref for ObjectArc<T> {
- type Target = T;
- #[inline]
- fn deref(&self) -> &Self::Target {
- unsafe { self.ptr.as_ref() }
- }
-}
-
-// implement Drop for ObjectArc
-impl<T: ObjectCore> Drop for ObjectArc<T> {
- #[inline(always)]
- fn drop(&mut self) {
- unsafe { unsafe_::dec_ref(self.ptr.as_ptr().cast::<TVMFFIObject>()) }
- }
-}
-
-// implement Clone for ObjectArc
-impl<T: ObjectCore> Clone for ObjectArc<T> {
- #[inline]
- fn clone(&self) -> Self {
- unsafe { unsafe_::inc_ref(self.ptr.as_ptr().cast::<TVMFFIObject>()) }
- Self {
- ptr: self.ptr,
- _phantom: std::marker::PhantomData,
- }
- }
-}
diff --git a/tests/python/test_stubgen_rust.py
b/tests/python/test_stubgen_rust.py
index d60e5708..445ba40a 100644
--- a/tests/python/test_stubgen_rust.py
+++ b/tests/python/test_stubgen_rust.py
@@ -1666,7 +1666,9 @@ def test_blocks_are_named_by_the_module_of_their_file(
text = nested.read_text(encoding="utf-8")
assert "use crate::TestCxxClassBase;\nuse crate::TestCxxClassBaseObj;\n"
in text
assert " base: TestCxxClassBaseObj," in text
- assert "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerived =>
TestCxxClassBase);" in text
+ assert (
+ "tvm_ffi::unsafe_impl_object_ref_upcast!(TestCxxClassDerived =>
TestCxxClassBase);" in text
+ )
assert "super::" not in text
# Moving the parent's block moves the path, not the type key.
lib.write_text("", encoding="utf-8")