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 e12d6cdb [Rust] Structural engine readability: one-line comments,
typed run_visitor, TVMFFIAny by value (#944)
e12d6cdb is described below
commit e12d6cdbb9872bfc64f26c446d20e190339999d7
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 12:01:43 2026 -0500
[Rust] Structural engine readability: one-line comments, typed run_visitor,
TVMFFIAny by value (#944)
Make the structural visit and mutate engines read straight through, with
no behavior or public API change. The file layout stays as is; a
follow-up reorders sections.
- `run_visitor` returns `Result<Option<VisitInterrupt>>`, so
`visit_root`
and the two arms of `run_walker` call it directly instead of
round-tripping a raw result.
- Private `extern "C-unwind"` helpers take `TVMFFIAny` by value instead
of its three scalar fields; it is still returned in registers on
AArch64.
- One statement of the `_raw` ownership convention per engine file
replaces the overview blocks; doc and SAFETY comments are one-line
contracts.
- Terminology: in-place "mode" and "view" replace "permission" and
"capability"; `CapabilitySource` -> `MutationSource`,
`dispatcher_context` -> `dispatcher_ptr`, `handle`/`visitor`/`mutator`
-> `runtime` for the native prefix, `default_mutate_raw` ->
`publish_and_descend_raw`, `_scope` & co -> one `_marker`.
- `VisitErrorContextObj` and its append function move to
`extra/visit_error_context.rs`, mirroring the C++ header, as
`update_visit_error_context`. `structural_common.rs` holds the shared
outlined errors, raw carrier utilities and the reflection prefix helpers
both engines use (field flags, region rules, field iteration, type-key
lookup); the callback-chain arity macro moves next to its user in
`dispatch.rs`. The region guard stays per engine.
- The borrow-check `compile_fail` examples move out of the user docs
into
a doctest-only item in `extra/mod.rs`, where they still run;
`visit_root`/`mutate_root` get user docs.
- Drop the in-file `VisitErrorContextObj` layout test: the binding is
covered end to end by `tests/test_structural_mutate.rs`. Document the
`backtrace` argument of `Error::new` as most recent call first.
- `downcast_ref_from_object_in_place` assumes the object-range type
index read from the object header, as `ObjectRefCore::downcast`
does, so the borrowing check drops that comparison.
---
rust/tvm-ffi/src/collections/array.rs | 15 +-
rust/tvm-ffi/src/error.rs | 19 +-
rust/tvm-ffi/src/extra/dispatch.rs | 40 ++-
rust/tvm-ffi/src/extra/mod.rs | 69 ++++
rust/tvm-ffi/src/extra/structural_common.rs | 302 +++++++---------
rust/tvm-ffi/src/extra/structural_map.rs | 2 +-
rust/tvm-ffi/src/extra/structural_mutate.rs | 473 ++++++++------------------
rust/tvm-ffi/src/extra/structural_visit.rs | 393 +++++----------------
rust/tvm-ffi/src/extra/structural_walk.rs | 23 +-
rust/tvm-ffi/src/extra/unchanged.rs | 6 +-
rust/tvm-ffi/src/extra/visit_error_context.rs | 120 +++++++
rust/tvm-ffi/src/lib.rs | 4 +-
rust/tvm-ffi/src/match_any_internal.rs | 28 +-
rust/tvm-ffi/src/object.rs | 23 +-
rust/tvm-ffi/src/type_traits.rs | 33 +-
rust/tvm-ffi/tests/test_structural_mutate.rs | 82 +++--
16 files changed, 659 insertions(+), 973 deletions(-)
diff --git a/rust/tvm-ffi/src/collections/array.rs
b/rust/tvm-ffi/src/collections/array.rs
index 7f824182..24539917 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -222,7 +222,7 @@ impl<T: ContainerElement + Clone> Array<T> {
let size = items.len();
// Allocate with capacity
- let arc = ObjectArc::<ArrayObj>::new_with_extra_items(ArrayObj {
+ let mut arc = ObjectArc::<ArrayObj>::new_with_extra_items(ArrayObj {
object: Object::new(),
data: core::ptr::null_mut(),
size: size as i64,
@@ -231,16 +231,13 @@ impl<T: ContainerElement + Clone> Array<T> {
});
unsafe {
- let raw_ptr = ObjectArc::as_ptr(&arc) as *mut ArrayObj;
- let container = &mut *raw_ptr;
+ 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() {
- let mut raw = TVMFFIAny::new();
- T::container_move_to_any(item, &mut raw);
- core::ptr::write(base_ptr.add(i), raw);
+ write_cell(base_ptr.add(i), item);
}
}
// SAFETY: `arc` was allocated and initialized above as an `Array<T>`.
@@ -466,8 +463,6 @@ impl<T: ContainerElement + Clone> Array<T> {
}
}
-// --- Index Implementation ---
-
/// 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> {
@@ -490,8 +485,6 @@ impl<T: ContainerElement + Clone + RefFromAnyUnchecked>
std::ops::Index<usize> f
}
}
-// --- Iterator Implementations ---
-
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.
@@ -609,8 +602,6 @@ impl<T: ContainerElement + Clone> FromIterator<T> for
Array<T> {
}
}
-// --- Any Type System Conversions ---
-
unsafe impl<T> AnyCompatible for Array<T>
where
T: ContainerElement + Clone,
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index 5dd42ef8..1fc4e7bd 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -73,7 +73,7 @@ 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");
-/// error object
+/// The object behind an [`Error`]: its kind, message, backtrace, cause and
context.
#[repr(C)]
#[derive(Object)]
#[type_key = "ffi.Error"]
@@ -177,7 +177,7 @@ impl Error {
///
/// * `kind` - The kind of the error, such as [`VALUE_ERROR`].
/// * `message` - The message of the error.
- /// * `backtrace` - The backtrace of the error, which may be empty.
+ /// * `backtrace` - The backtrace of the error, most recent call first;
may be empty.
///
/// # Returns
///
@@ -192,7 +192,7 @@ impl Error {
///
/// * `kind` - The kind of the error.
/// * `message` - The message of the error.
- /// * `backtrace` - The backtrace of the error, which may be empty.
+ /// * `backtrace` - The backtrace of the error, most recent call first;
may be empty.
/// * `cause_chain` - The error that caused this one, if any.
/// * `extra_context` - An object of application-specific context, if any.
///
@@ -291,11 +291,8 @@ impl Error {
self.data.cell.backtrace.as_str()
}
- /// Return the cause, if one was attached to this error.
- ///
- /// # Returns
- ///
- /// The error that caused this one, or `None`.
+ /// The error that caused this one, the next link of the cause chain, as
C++
+ /// `cause_chain()`. See also `source`.
pub fn cause_chain(&self) -> Option<&Error> {
let handle = &self.data.cell.cause_chain;
if handle.is_null() {
@@ -306,11 +303,7 @@ impl Error {
Some(unsafe { &*(handle as *const _ as *const Error) })
}
- /// Return the application-specific context, if present.
- ///
- /// # Returns
- ///
- /// The context object, or `None`.
+ /// The application-specific context object, if present.
pub fn extra_context(&self) -> Option<&object::ObjectRef> {
let handle = &self.data.cell.extra_context;
if handle.is_null() {
diff --git a/rust/tvm-ffi/src/extra/dispatch.rs
b/rust/tvm-ffi/src/extra/dispatch.rs
index 170a11b0..0d749f04 100644
--- a/rust/tvm-ffi/src/extra/dispatch.rs
+++ b/rust/tvm-ffi/src/extra/dispatch.rs
@@ -53,7 +53,7 @@ impl<'a> DispatchInput for AnyView<'a> {
type Source = crate::match_any_internal::ViewSource<'a>;
}
impl<'a, T> DispatchInput for crate::MutationView<'a, T> {
- type Source = crate::match_any_internal::CapabilitySource<'a>;
+ type Source = crate::match_any_internal::MutationSource<'a>;
}
// Generated alias bounds must not make recovery depend on a temporary borrow.
@@ -70,7 +70,6 @@ impl<'source, 'value>
DispatchRecovery<crate::match_any_internal::ViewSource<'so
type Recovery = ();
}
-use super::structural_common::impl_callback_chain_tuple_arities;
use std::{borrow::BorrowMut, marker::PhantomData};
/// Inferred selectors for dispatcher normalization.
@@ -101,7 +100,7 @@ pub mod marker {
pub struct BorrowedObject<T>(PhantomData<fn() -> T>);
/// Pass the first `AnyView` without a runtime type filter.
pub enum AnyView {}
- /// Match a mutation capability for `T`, retaining its scoped permission.
+ /// Match a mutation view for `T`, keeping its in-place mode.
pub struct Mutation<T = crate::Any>(PhantomData<fn() -> T>);
}
@@ -363,4 +362,39 @@ macro_rules! dispatch_tuple_links {
}
};
}
+// Generate the tuple arities supported by the standard library (1 through 12).
+macro_rules! impl_callback_chain_tuple_arities {
+ ($impl_chain:ident) => {
+ impl_callback_chain_tuple_arities!(
+ @prefixes $impl_chain;
+ [];
+ (F0, M0, 0),
+ (F1, M1, 1),
+ (F2, M2, 2),
+ (F3, M3, 3),
+ (F4, M4, 4),
+ (F5, M5, 5),
+ (F6, M6, 6),
+ (F7, M7, 7),
+ (F8, M8, 8),
+ (F9, M9, 9),
+ (F10, M10, 10),
+ (F11, M11, 11)
+ );
+ };
+ (@prefixes $impl_chain:ident; [$($prefix:tt)*];) => {};
+ (
+ @prefixes $impl_chain:ident;
+ [$($prefix:tt)*];
+ $next:tt $(, $rest:tt)*
+ ) => {
+ $impl_chain!($($prefix)* $next);
+ impl_callback_chain_tuple_arities!(
+ @prefixes $impl_chain;
+ [$($prefix)* $next,];
+ $($rest),*
+ );
+ };
+}
+
impl_callback_chain_tuple_arities!(dispatch_tuple_links);
diff --git a/rust/tvm-ffi/src/extra/mod.rs b/rust/tvm-ffi/src/extra/mod.rs
index 9b3484f3..3fd2503b 100644
--- a/rust/tvm-ffi/src/extra/mod.rs
+++ b/rust/tvm-ffi/src/extra/mod.rs
@@ -25,3 +25,72 @@ pub mod structural_mutate;
pub mod structural_visit;
pub mod structural_walk;
pub mod unchanged;
+mod visit_error_context;
+
+/// Borrow guarantees of the structural views, checked as `compile_fail`
doctests.
+///
+/// `StructuralVisitorView::base` cannot outlive its view:
+/// ```compile_fail
+/// use tvm_ffi::{StructuralVisitorObj, StructuralVisitorView};
+/// fn retain<D>(view: &StructuralVisitorView<'_, D>) -> &'static
StructuralVisitorObj {
+/// view.base()
+/// }
+/// ```
+///
+/// The visitor prefix cannot be converted into an owning FFI value:
+///
+/// ```compile_fail
+/// use tvm_ffi::{Any, StructuralVisitorView};
+/// fn retain<D>(view: &StructuralVisitorView<'_, D>) -> Any {
+/// Any::from(view.base())
+/// }
+/// ```
+///
+/// A node borrowed through a copied `MutationView` cannot survive consumption
of the view:
+/// ```compile_fail,E0505
+/// use tvm_ffi::{StructuralMutatorView, MutationView};
+/// fn invalid<D>(value: MutationView<'_>, dispatch: &mut D, mutator: &mut
StructuralMutatorView<'_, D>) {
+/// let view = value.as_any_view();
+/// let copy = view;
+/// let node =
copy.downcast_ref::<tvm_ffi::collections::array::ArrayObj>().unwrap();
+/// mutator.default_mutate(dispatch, value).unwrap();
+/// println!("{}", node.size);
+/// }
+/// ```
+///
+/// Moving the handle into a nested callback cannot bypass that borrow:
+/// ```compile_fail,E0505
+/// use tvm_ffi::{dispatch, structural_mutate, Any, MutationView,
StructuralMutatorView, Result, UnchangedOr};
+/// struct Inner<'a>(Option<MutationView<'a>>);
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
+/// impl<'a> Inner<'a> {
+/// fn mutate_bool(&mut self, _: bool, mutator: &mut
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+/// let pending = self.0.take().unwrap();
+/// mutator.default_mutate(self, pending)
+/// }
+/// }
+/// fn invalid(value: MutationView<'_>) {
+/// let view = value.as_any_view();
+/// let node =
view.downcast_ref::<tvm_ffi::collections::array::ArrayObj>().unwrap();
+/// structural_mutate(true, Inner(Some(value))).unwrap();
+/// println!("{}", node.size);
+/// }
+/// ```
+///
+/// A copied inspection view also cannot survive low-level default mutation:
+/// ```compile_fail,E0505
+/// use tvm_ffi::{dispatch, Any, MutationView, StructuralMutatorView, Result,
Unchanged, UnchangedOr};
+/// struct Pass;
+/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
+/// impl Pass {
+/// fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
+/// let view = value.as_any_view();
+/// let copy = view;
+/// mutator.default_mutate(self, value).unwrap();
+/// println!("{}", copy.type_index());
+/// Ok(Unchanged.into())
+/// }
+/// }
+/// ```
+#[cfg(doctest)]
+pub struct StructuralViewBorrowChecks;
diff --git a/rust/tvm-ffi/src/extra/structural_common.rs
b/rust/tvm-ffi/src/extra/structural_common.rs
index 8c931e92..cfe39842 100644
--- a/rust/tvm-ffi/src/extra/structural_common.rs
+++ b/rust/tvm-ffi/src/extra/structural_common.rs
@@ -17,12 +17,18 @@
* under the License.
*/
-use crate::any::{Any, AnyView};
-use crate::collections::array::Array;
-use crate::derive::Object;
+use super::structural_visit::DefRegionKind;
+use crate::any::Any;
use crate::error::{Error, Result, RUNTIME_ERROR};
-use crate::object::{self, Object, ObjectArc, ObjectRef, ObjectRefCore};
-use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFIAnyDataUnion, TVMFFIObject,
TVMFFITypeIndex};
+use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
+ kTVMFFIFieldFlagBitMaskSEqHashDefPattern,
kTVMFFIFieldFlagBitMaskSEqHashDefSimple,
+ kTVMFFIFieldFlagBitMaskSEqHashIgnore,
kTVMFFIFieldFlagBitSetterIsFunctionObj,
+};
+use crate::tvm_ffi_sys::{
+ TVMFFIAny, TVMFFIByteArray, TVMFFIFieldInfo, TVMFFISEqHashKind,
TVMFFITypeIndex,
+ TVMFFITypeInfo, TVMFFITypeKeyToIndex,
+};
+use std::ops::ControlFlow;
//
-----------------------------------------------------------------------------
// Shared outlined errors
@@ -36,6 +42,12 @@ pub(super) fn runtime_error(message: &str) -> Error {
Error::new(RUNTIME_ERROR, message, "")
}
+#[cold]
+#[inline(never)]
+pub(super) fn wrong_dispatcher() -> ! {
+ panic!("structural traversal view used with a different dispatcher
instance")
+}
+
#[cold]
#[inline(never)]
pub(super) fn missing_field_getter_error(operation: &str, name: &str) -> Error
{
@@ -58,21 +70,13 @@ pub(super) fn with_structural_field_error_context(
/// inside the cold path instead of successful recursive traversal frames.
///
/// # Safety
-/// The fields must describe one owning TVMFFIAny that has not been consumed.
+/// `raw` is one owning TVMFFIAny that has not been consumed.
// Perf note: Construct the addressable Any and perform checked Error
conversion
// inside this outline boundary, not in the successful recursive caller's
frame.
#[cold]
#[inline(never)]
-pub(super) unsafe fn structural_error_from_raw(
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
-) -> Error {
- let value = Any::from_raw_ffi_any(TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- });
+pub(super) unsafe fn structural_error_from_raw(raw: TVMFFIAny) -> Error {
+ let value = Any::from_raw_ffi_any(raw);
match Error::try_from(value) {
Ok(error) | Err(error) => error,
}
@@ -87,134 +91,6 @@ pub(crate) fn with_structural_error_context(error: Error,
operation: &str, frame
Error::with_appended_backtrace(error, &format!("[native structural
{operation}] {frame}\n"))
}
-/// Rust binding of C++ `VisitErrorContextObj`, the payload that
-/// `Error::extra_context` carries to name the nodes a failed visit unwound
through.
-#[repr(C)]
-#[derive(Object)]
-#[type_key = "ffi.VisitErrorContext"]
-#[type_final]
-pub(crate) struct VisitErrorContextObj {
- base: Object,
- /// Visited nodes, innermost first; C++ `ObjectRef` is nullable.
- reverse_visit_pattern: Array<Option<ObjectRef>>,
- /// The `extra_context` the error carried before this context replaced it.
- prev_error_context: Option<ObjectRef>,
-}
-
-/// Name the visited object in `raw` in the error's visit context, like C++
-/// `detail::UpdateVisitErrorContext`. Copy-on-write at each level: the Error,
its
-/// context and the context's pattern are each copied only when shared, so an
error
-/// owned by the unwinding frame is appended in place.
-#[cold]
-pub(crate) fn with_visit_error_context(mut error: Error, raw: TVMFFIAny) ->
Error {
- if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
- return error;
- }
- // SAFETY: the traversal input remains live during annotation. Conversion
- // acquires ownership before the temporary view ends; raw is an object tag.
- let Ok(node) = ObjectRef::try_from(unsafe { AnyView::from_raw_ffi_any(raw)
}) else {
- return error;
- };
- let as_context = |context: &ObjectRef| {
- let object = ObjectArc::as_ptr(ObjectRef::data(context));
- // SAFETY: `context` keeps its object alive; the type is checked first.
- unsafe {
- object::is_instance_of::<VisitErrorContextObj>(
- (*object.cast::<TVMFFIObject>()).type_index,
- )
- .then(|| &*object.cast::<VisitErrorContextObj>())
- }
- };
- // Callback, policy and default descent can report the same frame.
- if let Some(context) = error.extra_context().and_then(as_context) {
- if context
- .reverse_visit_pattern
- .iter()
- .next_back()
- .and_then(Option::as_ref)
- .is_some_and(|last| last.same_as(&node))
- {
- return error;
- }
- }
- let slot = error.extra_context_mut();
- let previous = slot.take();
- let context = match previous {
- Some(previous) if as_context(&previous).is_some() => {
- // SAFETY: `previous` was checked to be a `VisitErrorContextObj`.
- let mut context = unsafe {
- ObjectArc::from_raw(
- ObjectArc::into_raw(ObjectRef::into_data(previous))
- .cast::<VisitErrorContextObj>(),
- )
- };
- if !ObjectArc::is_unique(&context) {
- context = ObjectArc::new(VisitErrorContextObj {
- base: Object::new(),
- reverse_visit_pattern:
context.reverse_visit_pattern.clone(),
- prev_error_context: context.prev_error_context.clone(),
- });
- }
- // SAFETY: this handle is the context's only reference.
- unsafe {
- (*ObjectArc::as_mut_ptr(&mut context))
- .reverse_visit_pattern
- .push(Some(node))
- };
- context
- }
- previous => ObjectArc::new(VisitErrorContextObj {
- base: Object::new(),
- reverse_visit_pattern: Array::from_vec(vec![Some(node)]),
- prev_error_context: previous,
- }),
- };
- // SAFETY: every object starts with the root `Object` header.
- *slot = Some(unsafe {
- ObjectRef::from_data(ObjectArc::from_raw(
- ObjectArc::into_raw(context).cast::<Object>(),
- ))
- });
- error
-}
-
-// Generate the tuple arities supported by the standard library (1 through 12).
-macro_rules! impl_callback_chain_tuple_arities {
- ($impl_chain:ident) => {
- impl_callback_chain_tuple_arities!(
- @prefixes $impl_chain;
- [];
- (F0, M0, 0),
- (F1, M1, 1),
- (F2, M2, 2),
- (F3, M3, 3),
- (F4, M4, 4),
- (F5, M5, 5),
- (F6, M6, 6),
- (F7, M7, 7),
- (F8, M8, 8),
- (F9, M9, 9),
- (F10, M10, 10),
- (F11, M11, 11)
- );
- };
- (@prefixes $impl_chain:ident; [$($prefix:tt)*];) => {};
- (
- @prefixes $impl_chain:ident;
- [$($prefix:tt)*];
- $next:tt $(, $rest:tt)*
- ) => {
- $impl_chain!($($prefix)* $next);
- impl_callback_chain_tuple_arities!(
- @prefixes $impl_chain;
- [$($prefix)* $next,];
- $($rest),*
- );
- };
-}
-
-pub(crate) use impl_callback_chain_tuple_arities;
-
//
-----------------------------------------------------------------------------
// Raw carrier utilities
//
-----------------------------------------------------------------------------
@@ -258,49 +134,107 @@ pub(crate) unsafe fn raw_ffi_any_into_result(raw:
TVMFFIAny) -> Result<Any> {
return Ok(Any::from_raw_ffi_any(raw));
}
core::hint::cold_path();
- Err(structural_error_from_raw(
- raw.type_index,
- raw.small_str_len,
- raw.data_union,
- ))
+ Err(structural_error_from_raw(raw))
+}
+
+pub(super) const FLAG_SEQ_HASH_IGNORE: i64 =
kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
+pub(super) const FLAG_SEQ_HASH_DEF_PATTERN: i64 =
kTVMFFIFieldFlagBitMaskSEqHashDefPattern as i64;
+pub(super) const FLAG_SEQ_HASH_DEF_SIMPLE: i64 =
kTVMFFIFieldFlagBitMaskSEqHashDefSimple as i64;
+pub(super) const FLAG_SETTER_IS_FUNCTION: i64 =
kTVMFFIFieldFlagBitSetterIsFunctionObj as i64;
+
+/// The runtime type index of the native type `key`; panics when it is
unregistered.
+#[cold]
+pub(super) fn registered_type_index(key: &str) -> i32 {
+ let mut type_index = 0;
+ // SAFETY: `key` outlives the lookup.
+ if unsafe { TVMFFITypeKeyToIndex(&TVMFFIByteArray::from_str(key), &mut
type_index) } != 0 {
+ panic!("{key} is not registered: {}", Error::from_raised());
+ }
+ type_index
+}
+
+/// The registered `structural_eq_hash_kind` of `info`, `Unsupported` without
metadata.
+///
+/// # Safety
+///
+/// `info` is a registered type-info record.
+#[inline(always)]
+pub(super) unsafe fn seq_hash_kind(info: *const TVMFFITypeInfo) -> i32 {
+ let metadata = (*info).metadata;
+ if metadata.is_null() {
+ TVMFFISEqHashKind::kTVMFFISEqHashKindUnsupported as i32
+ } else {
+ (*metadata).structural_eq_hash_kind
+ }
}
-#[cfg(test)]
-mod tests {
- use super::VisitErrorContextObj;
- use crate::object::ObjectCore;
+#[inline(always)]
+pub(super) fn field_def_region(field: &TVMFFIFieldInfo, inherited:
DefRegionKind) -> DefRegionKind {
+ // Precedence: a pattern region propagates; entering any kind inside it
has no effect.
+ if inherited == DefRegionKind::Pattern {
+ DefRegionKind::Pattern
+ } else if field.flags & FLAG_SEQ_HASH_DEF_SIMPLE != 0 {
+ DefRegionKind::Simple
+ } else if field.flags & FLAG_SEQ_HASH_DEF_PATTERN != 0 {
+ DefRegionKind::Pattern
+ } else {
+ inherited
+ }
+}
- // The binding must match the layout C++ registers for
`ffi.VisitErrorContext`.
- #[test]
- fn visit_error_context_layout_matches_registered_fields() {
- let expected = [
- (
- "reverse_visit_pattern",
- std::mem::offset_of!(VisitErrorContextObj,
reverse_visit_pattern),
- ),
- (
- "prev_error_context",
- std::mem::offset_of!(VisitErrorContextObj, prev_error_context),
- ),
- ];
- // SAFETY: the type is registered by the runtime; its field table
lives as long.
- let fields = unsafe {
- let info =
crate::tvm_ffi_sys::TVMFFIGetTypeInfo(VisitErrorContextObj::type_index());
- assert!(!info.is_null());
- std::slice::from_raw_parts((*info).fields, (*info).num_fields as
usize)
- };
- assert_eq!(fields.len(), expected.len());
- for (name, offset) in expected {
- let field = fields
- .iter()
- .find(|field| field.name.as_str() == name)
- .unwrap_or_else(|| panic!("`{name}` is not a registered
field"));
- assert_eq!(field.offset as usize, offset, "offset of `{name}`");
- assert_eq!(
- field.size as usize,
- std::mem::size_of::<usize>(),
- "size of `{name}`"
- );
+/// A simple definition applies to a FreeVar value itself, but not to
+/// the FreeVar's own reflected children: nested free vars there must resolve
+/// against an outer binding instead of rebinding. Mirrors C++
+/// `VisitReflectedFieldsExpected`.
+#[inline(always)]
+pub(super) fn free_var_child_region(
+ inherited: DefRegionKind,
+ structural_eq_hash_kind: i32,
+) -> DefRegionKind {
+ if inherited == DefRegionKind::Simple
+ && structural_eq_hash_kind ==
TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32
+ {
+ DefRegionKind::None
+ } else {
+ inherited
+ }
+}
+
+/// Visit every reflected field described by `info` and its ancestors in the
+/// same parent-to-child order as C++ `ForEachFieldInfoWithEarlyStop`.
+///
+/// # Safety
+///
+/// `info` must point to an immortal registered type-info record.
+pub(super) unsafe fn for_each_field_info<B>(
+ info: *const TVMFFITypeInfo,
+ callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
+) -> Option<B> {
+ // Ancestor slot 0 is the root Object. C++ starts at slot 1, walks toward
+ // the immediate parent, then visits the concrete type's own fields.
+ for depth in 1..(*info).type_depth {
+ let ancestor = *(*info).type_acenstors.offset(depth as isize);
+ if let Some(value) = visit_field_level(ancestor, callback) {
+ return Some(value);
+ }
+ }
+ visit_field_level(info, callback)
+}
+
+unsafe fn visit_field_level<B>(
+ info: *const TVMFFITypeInfo,
+ callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
+) -> Option<B> {
+ if info.is_null() || (*info).fields.is_null() {
+ return None;
+ }
+ let fields = std::slice::from_raw_parts((*info).fields, (*info).num_fields
as usize);
+ for field in fields {
+ // C reflection tables are immortal once registered.
+ let field: &'static TVMFFIFieldInfo = &*(field as *const
TVMFFIFieldInfo);
+ if let ControlFlow::Break(value) = callback(field) {
+ return Some(value);
}
}
+ None
}
diff --git a/rust/tvm-ffi/src/extra/structural_map.rs
b/rust/tvm-ffi/src/extra/structural_map.rs
index cf25e3f6..852389fa 100644
--- a/rust/tvm-ffi/src/extra/structural_map.rs
+++ b/rust/tvm-ffi/src/extra/structural_map.rs
@@ -114,7 +114,7 @@ where
let raw = *value.as_any_view().as_raw_ffi_any();
let kind = mutator.def_region_kind();
if PRE_ORDER {
- // Snapshot permission before a callback can retain or release
ownership.
+ // Snapshot the mode before a callback can retain or release
ownership.
// SAFETY: the input owner remains live through this synchronous
callback;
// its higher-ranked view cannot escape into the owning result.
let Some(callback_result) = self
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 75aeec40..7a5c433a 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -22,26 +22,8 @@
//! [`structural_mutate`] lets a mutator drive recursion, while
//! [`structural_map`] applies callbacks around engine-owned recursion.
-//
-----------------------------------------------------------------------------
-// Implementation overview
-//
-----------------------------------------------------------------------------
-//
-// This file is organized into:
-// - Data structures and mutation capabilities.
-// - Outlined errors and private utility functions.
-// - Engine implementation and the public entry point.
-//
-// We use narrowly scoped unsafe code for native interoperation and efficient
-// traversal. Safety and ownership contracts are documented at each boundary.
-//
-// The main performance strategies are:
-// - Outline cold work to keep its temporary storage out of recursive frames.
-// - Use _raw helpers and private extern "C-unwind" boundaries for register
-// transport on AArch64 while preserving Rust unwinding.
-// - Inline small conversions and forwarding code so ownership transfers and
-// redundant cleanup can optimize away.
-//
-// Local Perf note comments explain individual implementation choices.
+// `_raw` functions move one owned `TVMFFIAny`: forward it once, or import it
into `Any` before
+// anything can unwind. Private `extern "C-unwind"` helpers return it in
registers on AArch64.
use std::cell::{RefCell, UnsafeCell};
use std::collections::{hash_map::Entry, HashMap};
@@ -63,32 +45,27 @@ use crate::error::{Error, Result, TYPE_ERROR};
use crate::function::Function;
use crate::object::{self, Object, ObjectArc};
use crate::reflection::TypeAttrColumn;
-use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
- kTVMFFIFieldFlagBitMaskSEqHashIgnore,
kTVMFFIFieldFlagBitSetterIsFunctionObj,
-};
use crate::tvm_ffi_sys::{
- TVMFFIAny, TVMFFIAnyDataUnion, TVMFFIAnyViewToOwnedAny, TVMFFIByteArray,
TVMFFIFieldInfo,
- TVMFFIFieldSetter, TVMFFIFunctionCall, TVMFFIGetTypeInfo, TVMFFIObject,
TVMFFITypeIndex,
- TVMFFITypeKeyToIndex,
+ TVMFFIAny, TVMFFIAnyViewToOwnedAny, TVMFFIFieldInfo, TVMFFIFieldSetter,
TVMFFIFunctionCall,
+ TVMFFIGetTypeInfo, TVMFFIObject, TVMFFITypeIndex,
};
use crate::tvm_ffi_sys::{TVMFFIObjectHandle, TVMFFISEqHashKind};
use super::structural_common::{
- missing_field_getter_error, raw_ffi_any_into_result, raw_ffi_any_same_as,
- result_into_raw_ffi_any, runtime_error, with_structural_error_context,
- with_structural_field_error_context, with_visit_error_context,
-};
-use super::structural_visit::{
- field_def_region, for_each_field_info, free_var_child_region,
DefRegionKind,
+ field_def_region, for_each_field_info, free_var_child_region,
missing_field_getter_error,
+ raw_ffi_any_into_result, raw_ffi_any_same_as, registered_type_index,
result_into_raw_ffi_any,
+ runtime_error, seq_hash_kind, with_structural_error_context,
+ with_structural_field_error_context, wrong_dispatcher,
FLAG_SEQ_HASH_IGNORE,
+ FLAG_SETTER_IS_FUNCTION,
};
+use super::structural_visit::DefRegionKind;
use super::unchanged::is_unchanged;
pub use super::unchanged::{Unchanged, UnchangedOr};
+use super::visit_error_context::update_visit_error_context;
const STRUCTURAL_MUTATE_ATTR: &str = "__s_mutate__";
const STRUCTURAL_MAYBE_INPLACE_MUTATE_ATTR: &str =
"__s_maybe_inplace_mutate__";
const SHALLOW_COPY_ATTR: &str = "__ffi_shallow_copy__";
-const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
-const FLAG_SETTER_IS_FUNCTION: i64 = kTVMFFIFieldFlagBitSetterIsFunctionObj as
i64;
//
-----------------------------------------------------------------------------
// Data structures and FFI layout
@@ -148,7 +125,7 @@ impl StructuralMutatorObj {
/// state only synchronously, on the calling thread.
///
/// `default_mutate_raw` returns one owning ABI replacement/Unchanged/Error
and recurses
-/// only through the installed vtable, preserving the input's mutation
permission.
+/// only through the installed vtable, preserving the input's in-place mode.
/// No whole-base mutable reference may span recursion. Failures use the result
/// carrier; panics must not cross native callbacks.
pub unsafe trait StructuralMutatorBase: Sized {
@@ -158,7 +135,7 @@ pub unsafe trait StructuralMutatorBase: Sized {
/// Native type registered for this complete base layout.
fn type_index() -> i32;
- /// Descend from a borrowed capability without dispatching that value
again.
+ /// Descend from a borrowed view without dispatching that value again.
///
/// # Safety
/// `runtime` is the base of a live `StructuralMutateEngine<Self>` with its
@@ -177,17 +154,8 @@ unsafe impl StructuralMutatorBase for StructuralMutatorObj
{
}
fn type_index() -> i32 {
- static TYPE_INDEX: ColdInitLazyLock<i32> = ColdInitLazyLock::new(||
unsafe {
- let key =
TVMFFIByteArray::from_str(StructuralMutatorObj::TYPE_KEY);
- let mut type_index = 0;
- if TVMFFITypeKeyToIndex(&key, &mut type_index) != 0 {
- panic!(
- "ffi.StructuralMutator is not registered: {}",
- Error::from_raised()
- );
- }
- type_index
- });
+ static TYPE_INDEX: ColdInitLazyLock<i32> =
+ ColdInitLazyLock::new(||
registered_type_index(StructuralMutatorObj::TYPE_KEY));
*TYPE_INDEX
}
@@ -199,7 +167,7 @@ unsafe impl StructuralMutatorBase for StructuralMutatorObj {
if let Some(attr) = Self::structural_mutate_hook(raw, value.mode) {
return Self::call_structural_mutate_hook_raw(runtime, raw, attr);
}
- Self::mutate_without_hook_raw(runtime, raw.type_index,
raw.small_str_len, raw.data_union)
+ Self::mutate_without_hook_raw(runtime, raw)
}
}
@@ -284,7 +252,7 @@ impl Drop for StructuralVarRemap {
}
impl<'source, 'value, T: crate::type_traits::ContainerElement>
-
super::dispatch::DispatchRecovery<crate::match_any_internal::CapabilitySource<'source>>
+
super::dispatch::DispatchRecovery<crate::match_any_internal::MutationSource<'source>>
for crate::MutationView<'value, T>
{
type Recovery = crate::MutationView<'source>;
@@ -346,7 +314,7 @@ pub enum InplaceMode {
}
impl InplaceMode {
- // Permission is only an upper bound; ownership must still be unique here.
+ // The mode is only an upper bound; ownership must still be unique here.
#[inline(always)]
fn for_value(self, raw: TVMFFIAny) -> Self {
if self == Self::Allow && object_is_unique(raw) {
@@ -369,63 +337,15 @@ impl InplaceMode {
/// `T` is the matched type: `MutationView<'_, Array<Any>>` derefs to the
/// array's node, and the default `MutationView<'_>` matches every value. The
/// view is move-only: it cannot be copied or cloned, and consuming it passes
-/// on the same permission. A view made from `&mut Any` may reuse storage;
+/// on the same mode. A view made from `&mut Any` may reuse storage;
/// one made with [`borrowed`](MutationView::borrowed) never does.
///
/// See also [`structural_mutate`].
-///
-/// A node borrowed through a copied view cannot survive consumption of the
handle:
-/// ```compile_fail,E0505
-/// use tvm_ffi::{StructuralMutatorView, MutationView};
-/// fn invalid<D>(value: MutationView<'_>, dispatch: &mut D, mutator: &mut
StructuralMutatorView<'_, D>) {
-/// let view = value.as_any_view();
-/// let copy = view;
-/// let node =
copy.downcast_ref::<tvm_ffi::collections::array::ArrayObj>().unwrap();
-/// mutator.default_mutate(dispatch, value).unwrap();
-/// println!("{}", node.size);
-/// }
-/// ```
-///
-/// Moving the handle into a nested callback cannot bypass that borrow:
-/// ```compile_fail,E0505
-/// use tvm_ffi::{dispatch, structural_mutate, Any, MutationView,
StructuralMutatorView, Result, UnchangedOr};
-/// struct Inner<'a>(Option<MutationView<'a>>);
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
-/// impl<'a> Inner<'a> {
-/// fn mutate_bool(&mut self, _: bool, mutator: &mut
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
-/// let pending = self.0.take().unwrap();
-/// mutator.default_mutate(self, pending)
-/// }
-/// }
-/// fn invalid(value: MutationView<'_>) {
-/// let view = value.as_any_view();
-/// let node =
view.downcast_ref::<tvm_ffi::collections::array::ArrayObj>().unwrap();
-/// structural_mutate(true, Inner(Some(value))).unwrap();
-/// println!("{}", node.size);
-/// }
-/// ```
-///
-/// A copied inspection view also cannot survive low-level default mutation:
-/// ```compile_fail,E0505
-/// use tvm_ffi::{dispatch, Any, MutationView, StructuralMutatorView, Result,
Unchanged, UnchangedOr};
-/// struct Pass;
-/// #[dispatch(mutate, (MutationView<'_>, &mut StructuralMutatorView<'_,
Self>), Result<UnchangedOr<Any>>)]
-/// impl Pass {
-/// fn mutate_any(&mut self, value: MutationView<'_>, mutator: &mut
StructuralMutatorView<'_, Self>) -> Result<UnchangedOr<Any>> {
-/// let view = value.as_any_view();
-/// let copy = view;
-/// mutator.default_mutate(self, value).unwrap();
-/// println!("{}", copy.type_index());
-/// Ok(Unchanged.into())
-/// }
-/// }
-/// ```
pub struct MutationView<'a, T = Any> {
value: TVMFFIAny,
mode: InplaceMode,
- _scope: PhantomData<AnyView<'a>>,
- _type: PhantomData<fn() -> T>,
- _not_send_sync: PhantomData<Rc<()>>,
+ #[allow(clippy::type_complexity)]
+ _marker: PhantomData<(AnyView<'a>, fn() -> T, Rc<()>)>,
}
impl<T> MutationView<'_, T> {
@@ -439,13 +359,7 @@ impl<T> MutationView<'_, T> {
impl<'a> MutationView<'a> {
#[inline(always)]
pub(super) fn new(value: AnyView<'a>, mode: InplaceMode) -> Self {
- Self {
- value: *value.as_raw_ffi_any(),
- mode,
- _scope: PhantomData,
- _type: PhantomData,
- _not_send_sync: PhantomData,
- }
+ Self::from_raw(*value.as_raw_ffi_any(), mode)
}
/// Create a view of a borrowed value that never reuses its storage.
@@ -477,20 +391,23 @@ impl<'a> From<&'a mut Any> for MutationView<'a> {
}
impl<'a, T> MutationView<'a, T> {
- // A chain may lend its capability to one link at a time. The shorter
lifetime
+ #[inline(always)]
+ fn from_raw(value: TVMFFIAny, mode: InplaceMode) -> Self {
+ Self {
+ value,
+ mode,
+ _marker: PhantomData,
+ }
+ }
+
+ // A chain may lend its view to one link at a time. The shorter lifetime
// prevents a matched link from retaining a borrow across the next link.
#[inline(always)]
pub(crate) fn reborrow(&mut self) -> MutationView<'_, T> {
- MutationView {
- value: self.value,
- mode: self.mode,
- _scope: PhantomData,
- _type: PhantomData,
- _not_send_sync: PhantomData,
- }
+ MutationView::from_raw(self.value, self.mode)
}
- /// Return the permission this view carries.
+ /// Return the in-place mode this view carries.
///
/// # Returns
///
@@ -500,18 +417,18 @@ impl<'a, T> MutationView<'a, T> {
self.mode
}
- /// Restrict the permission, without ever upgrading it.
+ /// Restrict the mode, without ever upgrading it.
///
/// `Disallow` turns off reuse. `Allow` keeps the current mode, so it never
/// grants reuse to a view that does not have it, such as a borrowed one.
///
/// # Arguments
///
- /// * `requested` - The permission to restrict to.
+ /// * `requested` - The mode to restrict to.
///
/// # Returns
///
- /// The view with the restricted permission.
+ /// The view with the restricted mode.
#[inline(always)]
pub fn with_inplace_mode(mut self, requested: InplaceMode) -> Self {
if requested == InplaceMode::Disallow {
@@ -530,12 +447,12 @@ impl<'a, T> MutationView<'a, T> {
/// The value as an [`AnyView`].
#[inline(always)]
pub fn as_any_view(&self) -> AnyView<'_> {
- // SAFETY: the input owner keeps this payload live while the
capability is
+ // SAFETY: the input owner keeps this payload live while the view is
// borrowed. The returned lifetime is bounded by self, not by 'a.
unsafe { AnyView::from_raw_ffi_any(self.value) }
}
- /// Borrow the value as `U` without changing ownership or permission.
+ /// Borrow the value as `U` without changing ownership or mode.
///
/// `U` is anything [`AnyView::downcast_ref`] borrows: a managed reference
such as
/// `Array<i64>`, an object type such as `ArrayObj`, or [`Any`], borrowed
in place
@@ -555,7 +472,7 @@ impl<'a, T> MutationView<'a, T> {
/// Narrow the matched type without acquiring an owning reference.
///
/// The check is the strict one of [`Any::downcast_ref`], and the view
keeps its
- /// permission.
+ /// mode.
///
/// # Returns
///
@@ -572,32 +489,20 @@ impl<'a, T> MutationView<'a, T> {
// SAFETY: the input owner keeps the borrowed value live for this
handle's
// lifetime. This only checks its type and does not acquire ownership.
if unsafe { U::container_check_any_strict(&self.value) } {
- Ok(MutationView {
- value: self.value,
- mode: self.mode,
- _scope: PhantomData,
- _type: PhantomData,
- _not_send_sync: PhantomData,
- })
+ Ok(MutationView::from_raw(self.value, self.mode))
} else {
Err(self)
}
}
- /// Erase the matched type while preserving the carried permission.
+ /// Erase the matched type while preserving the carried mode.
///
/// # Returns
///
/// The view as `MutationView<'_>`.
#[inline(always)]
pub fn into_untyped(self) -> MutationView<'a> {
- MutationView {
- value: self.value,
- mode: self.mode,
- _scope: PhantomData,
- _type: PhantomData,
- _not_send_sync: PhantomData,
- }
+ MutationView::from_raw(self.value, self.mode)
}
}
@@ -619,7 +524,7 @@ impl<T: object::ObjectRefCore> std::ops::Deref for
MutationView<'_, T> {
#[inline(always)]
fn deref(&self) -> &Self::Target {
- // SAFETY: typed capability construction establishes T's object layout;
+ // SAFETY: typed view construction establishes T's object layout;
// this shared borrow cannot outlive or survive consumption of the
handle.
unsafe { &*self.value.data_union.v_obj.cast::<T::ContainerType>() }
}
@@ -687,17 +592,11 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
);
let mut value = TVMFFIAny::new();
// SAFETY: child is a live owning field. Copying its view acquires no
owner;
- // the returned capability exclusively borrows the parent capability.
+ // the returned view exclusively borrows the parent view.
unsafe { U::container_copy_to_any_view(child, &mut value) };
// Selection and conversion may retain aliases, so check the parent
last.
let mode = self.mode.for_value(self.value);
- MutationView {
- value,
- mode,
- _scope: PhantomData,
- _type: PhantomData,
- _not_send_sync: PhantomData,
- }
+ MutationView::from_raw(value, mode)
}
}
@@ -712,9 +611,8 @@ impl<T: object::ObjectRefCore> MutationView<'_, T> {
/// See also [`structural_mutate`].
pub struct StructuralMutatorView<'a, D, Base = StructuralMutatorObj> {
runtime: StructuralMutatorHandle,
- _scope: PhantomData<&'a mut ()>,
- _dispatcher: PhantomData<fn(&mut D) -> Base>,
- _not_send_sync: PhantomData<Rc<()>>,
+ #[allow(clippy::type_complexity)]
+ _marker: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
}
impl<D, Base: StructuralMutatorBase> StructuralMutatorView<'_, D, Base> {
@@ -734,9 +632,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
fn reborrow(&self) -> StructuralMutatorView<'_, D, Base> {
StructuralMutatorView {
runtime: self.runtime,
- _scope: PhantomData,
- _dispatcher: PhantomData,
- _not_send_sync: PhantomData,
+ _marker: PhantomData,
}
}
@@ -750,6 +646,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
/// The current definition region.
#[inline(always)]
pub fn def_region_kind(&self) -> DefRegionKind {
+ // SAFETY: the view's runtime is live.
unsafe { std::mem::transmute(*(*self.runtime).def_region_mode.get()) }
}
@@ -773,6 +670,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
kind: DefRegionKind,
callback: impl FnOnce(&mut StructuralMutatorView<'_, D, Base>) -> R,
) -> R {
+ // SAFETY: the view's runtime outlives the callback.
StructuralMutatorObj::with_def_region_kind(self.runtime, kind, |_| {
callback(&mut self.reborrow())
})
@@ -780,7 +678,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
/// Mutate `value` through the dispatcher, as if the traversal had reached
it.
///
- /// `value` keeps the permission it carries. Use it to rewrite a value
other
+ /// `value` keeps the mode it carries. Use it to rewrite a value other
/// than the current node, such as a field selected with
/// [`MutationView::field`].
///
@@ -843,7 +741,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
unsafe {
let context =
StructuralMutateEngine::<Base>::checked_dispatcher(self.runtime, dispatch);
-
raw_ffi_any_into_result(StructuralMutateEngine::<Base>::default_mutate_raw(
+
raw_ffi_any_into_result(StructuralMutateEngine::<Base>::publish_and_descend_raw(
self.runtime,
context,
value.value,
@@ -913,10 +811,7 @@ impl<D, Base: StructuralMutatorBase>
StructuralMutatorView<'_, D, Base> {
}
impl<Base: StructuralMutatorBase> StructuralMutateEngine<Base> {
- /// Normalize the input once and traverse using this engine's native base.
- /// Named dispatchers keep their actual recursive view type. Callback and
- /// tuple targets are their aggregate adapters; closures taking their own
- /// recursive view type remain unsupported.
+ /// Mutate `root` with `dispatcher` on the native base `Base`. See also
[`structural_mutate`].
#[inline(always)]
pub fn mutate_root<R, H, M, D, O>(root: R, dispatcher: H) -> Result<Any>
where
@@ -934,6 +829,8 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
Option<Result<UnchangedOr<Any>>>,
>,
{
+ // Normalize the input once. Named dispatchers keep their own
recursive view
+ // type; callback and tuple targets are their aggregate adapters.
let mut storage = dispatcher.into_dispatcher();
Self::run_mutator::<D, true>(root.into(), storage.borrow_mut())
}
@@ -942,7 +839,6 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
//
-----------------------------------------------------------------------------
// Outlined errors
//
-----------------------------------------------------------------------------
-// Keep construction and formatting scratch outside successful traversal
frames.
#[cold]
#[inline(never)]
@@ -1023,20 +919,13 @@ fn unregistered_type_error(type_index: i32) -> Error {
))
}
-// Keep panic construction out of the successful dispatcher identity check.
-#[cold]
-#[inline(never)]
-fn wrong_mutation_dispatcher() -> ! {
- panic!("mutation context used with a different dispatcher instance")
-}
-
#[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(
- with_visit_error_context(error, raw),
+ update_visit_error_context(error, raw),
operation,
&format!(
"object `{}`",
@@ -1228,16 +1117,12 @@ static SHALLOW_COPY_COLUMN:
ColdInitLazyLock<TypeAttrColumn> = ColdInitLazyLock:
// 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> {
- /// Recover the engine prefix for internal runtime projections.
+ /// The engine whose first field is the native prefix `runtime`.
///
/// # Safety
- /// `runtime` must be this live Rust engine, not an arbitrary native
mutator.
- /// The returned pointer borrows it; no whole-engine reference may span
recursion.
- // Unsafe internals preserve a safe API: the matching runtime and
dispatcher
- // remain live for this synchronous call, and each recursive entry
publishes
- // its actual exclusive reborrow. No whole-engine reference spans
recursion.
- // Callbacks use Result for failures and must not unwind across the C ABI.
- // The live handle starts this exact Rust engine; only raw projections
cross recursion.
+ ///
+ /// `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()
@@ -1256,7 +1141,7 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
let context = std::ptr::from_mut(dispatch).cast();
if context != (*Self::from_handle(runtime)).dispatcher {
core::hint::cold_path();
- wrong_mutation_dispatcher();
+ wrong_dispatcher();
}
context
}
@@ -1280,7 +1165,7 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
}
/// Enter a native callback with borrowed `value` and return one owning
raw result.
- /// `INPLACE` grants permission only; uniqueness is checked before
dispatch.
+ /// `INPLACE` sets the mode only; uniqueness is checked before dispatch.
///
/// # Safety
/// `runtime` must have this `D` vtable and a live published exclusive
reborrow.
@@ -1307,9 +1192,7 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
let dispatch = &mut *context.cast::<D>();
let mut mutator = StructuralMutatorView {
runtime,
- _scope: PhantomData,
- _dispatcher: PhantomData,
- _not_send_sync: PhantomData,
+ _marker: PhantomData,
};
let result = match Dispatcher::dispatch_tuple(
&mut *dispatch,
@@ -1325,9 +1208,12 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
result
})
}
- None => {
- Self::default_mutate_raw(runtime,
std::ptr::from_mut(dispatch).cast(), raw, mode)
- }
+ None => Self::publish_and_descend_raw(
+ runtime,
+ std::ptr::from_mut(dispatch).cast(),
+ raw,
+ mode,
+ ),
};
// All callback reborrows have ended before restoring the caller's
provenance.
(*Self::from_handle(runtime)).dispatcher = context;
@@ -1335,13 +1221,13 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
}
/// Publish `context`, then descend through hooks or reflection for
borrowed `raw`.
- /// `mode` grants reuse permission, not proof of uniqueness. Returns one
owning token.
+ /// `mode` is an upper bound, not proof of uniqueness. Returns one owning
token.
///
/// # Safety
/// `runtime` must be live; `context` must be its current exclusive
dispatcher
/// reborrow, and `raw` must remain valid throughout synchronous descent.
#[inline(always)]
- unsafe fn default_mutate_raw(
+ unsafe fn publish_and_descend_raw(
runtime: StructuralMutatorHandle,
context: *mut c_void,
raw: TVMFFIAny,
@@ -1452,94 +1338,60 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
type_index,
)
};
- let handle = ObjectArc::as_mut_ptr(&mut
engine).cast::<StructuralMutatorObj>();
- unsafe { (*handle).vtable = Self::vtable::<D, CALLBACK_CONTEXT>() };
+ let runtime = ObjectArc::as_mut_ptr(&mut
engine).cast::<StructuralMutatorObj>();
+ unsafe { (*runtime).vtable = Self::vtable::<D, CALLBACK_CONTEXT>() };
let result =
- StructuralMutatorObj::call_mutator(handle, *root.as_raw_ffi_any(),
InplaceMode::Allow);
+ StructuralMutatorObj::call_mutator(runtime,
*root.as_raw_ffi_any(), InplaceMode::Allow);
// Native hooks release temporary runtime owners before this
synchronous run ends.
drop(engine);
result.map(|value| if is_unchanged(&value) { root } else { value })
}
}
-// Public prefix helpers use only the native layout and installed vtable.
+// Prefix operations use only the native layout and installed vtable.
impl StructuralMutatorObj {
- /// Consume the native hook owning `result`, annotating errors with
borrowed `raw`.
- /// Nonerrors transfer unchanged; packed successful Error values do not
use this path.
+ /// Consume a native hook's `result`, naming the borrowed `raw` in it when
it is an Error.
///
/// # Safety
- /// `result` must own one valid ABI value and `raw` must remain live
during annotation.
+ ///
+ /// `result` owns one valid value and `raw` outlives the call.
#[inline(always)]
unsafe fn with_native_mutation_context_raw(result: TVMFFIAny, raw:
TVMFFIAny) -> TVMFFIAny {
if result.type_index != TVMFFITypeIndex::kTVMFFIError as i32 {
result
} else {
core::hint::cold_path();
- Self::with_mutation_context_raw(
- result.type_index,
- result.small_str_len,
- result.data_union,
- raw.type_index,
- raw.small_str_len,
- raw.data_union,
- )
+ Self::with_mutation_context_raw(result, raw)
}
}
- /// Reconstruct an owning error from `result_*` and borrowed input from
the other
- /// scalar fields, then transfer one context-annotated owning raw result.
- /// This private C-unwind helper uses AArch64 register returns and permits
Rust
- /// cleanup on unwind; it changes no public or native callback ABI.
+ /// The cold arm of [`Self::with_native_mutation_context_raw`].
///
/// # Safety
- /// The result fields must describe one owned Error; the input fields must
describe
- /// a live borrowed value. No caller may release the transferred error
token.
- // Outline error annotation scratch; the AArch64 C ABI returns the raw
- // carrier in registers while C-unwind preserves Rust cleanup on panic.
+ ///
+ /// `result` owns one Error, and `raw` is a live borrowed value.
#[cold]
#[inline(never)]
unsafe extern "C-unwind" fn with_mutation_context_raw(
- result_type_index: i32,
- result_small_str_len: u32,
- result_data: TVMFFIAnyDataUnion,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
+ result: TVMFFIAny,
+ raw: TVMFFIAny,
) -> TVMFFIAny {
- let result = TVMFFIAny {
- type_index: result_type_index,
- small_str_len: result_small_str_len,
- data_union: result_data,
- };
- let raw = TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- };
result_into_raw_ffi_any(
raw_ffi_any_into_result(result)
.map_err(|error| with_mutation_error_context(error, raw,
"mutate")),
)
}
- /// Map registered fields of borrowed `raw` using live `runtime` and
`type_info`.
- /// `def_region_kind` is the fallback-entry snapshot. Own the shallow copy
and
- /// getter results through recursion/setters; return a replacement or
Unchanged.
- /// Internal callers provide valid registered metadata and a live input
owner.
+ /// Mutate the reflected fields of a shallow copy of borrowed `raw`;
`Unchanged` when no
+ /// field changed.
fn map_reflected(
runtime: StructuralMutatorHandle,
raw: TVMFFIAny,
def_region_kind: DefRegionKind,
type_info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
) -> Result<Any> {
- let seq_hash_kind = unsafe {
- if (*type_info).metadata.is_null() {
- TVMFFISEqHashKind::kTVMFFISEqHashKindUnsupported as i32
- } else {
- (*(*type_info).metadata).structural_eq_hash_kind
- }
- };
- let inherited_region = free_var_child_region(def_region_kind,
seq_hash_kind);
+ let inherited_region =
+ free_var_child_region(def_region_kind, unsafe {
seq_hash_kind(type_info) });
// Match the C++ reflected-mutation contract: resolve and invoke the
// shallow-copy hook before inspecting any fields. Besides providing
// isolated setter storage, this means a missing or failing hook is an
@@ -1585,13 +1437,12 @@ impl StructuralMutatorObj {
}
}
- /// Map one field of the owned shallow copy and set `field_changed` on
replacement.
- /// `inherited_region` supplies the parent definition policy.
Getter/result owners
- /// survive recursion and setters; errors retain field context.
+ /// Mutate one reflected `field` of `output_object`, setting
`field_changed` on a
+ /// replacement.
///
/// # Safety
- /// `runtime` must be live; `output_object` and registered `field`
metadata must
- /// describe a valid live copy, including its getter/setter offsets and
ABIs.
+ ///
+ /// `runtime` is live, and `field` describes `output_object`'s storage.
unsafe fn map_reflected_field(
runtime: StructuralMutatorHandle,
output_object: *mut u8,
@@ -1644,36 +1495,32 @@ impl StructuralMutatorObj {
Ok(())
}
- /// Invoke a live mutator vtable on borrowed `raw` and import its owning
result.
- /// Internal callers keep both live; `mode` permits reuse only after a
fresh
- /// uniqueness check. Native callbacks cannot unwind through their C ABI.
+ /// Dispatch borrowed `raw` through the runtime's vtable, in place when
`mode` allows
+ /// it and `raw` is unique.
#[inline(always)]
fn call_mutator(
- mutator: StructuralMutatorHandle,
+ runtime: StructuralMutatorHandle,
raw: TVMFFIAny,
mode: InplaceMode,
) -> Result<Any> {
- let use_inplace = mode == InplaceMode::Allow && object_is_unique(raw);
let callback = unsafe {
- if use_inplace {
- (*(*mutator).vtable).maybe_inplace_mutate
+ if mode.for_value(raw) == InplaceMode::Allow {
+ (*(*runtime).vtable).maybe_inplace_mutate
} else {
- (*(*mutator).vtable).mutate
+ (*(*runtime).vtable).mutate
}
};
unsafe {
let view = AnyView::from_raw_ffi_any(raw);
- raw_ffi_any_into_result(callback(mutator, view))
+ raw_ffi_any_into_result(callback(runtime, view))
}
}
- /// Borrow the registered hook cell for live `raw`. Recheck uniqueness for
- /// `mode` before selecting an in-place hook, otherwise use the normal
column.
- /// The registry owns the returned attribute; no reference count is
acquired.
+ /// The registered mutation hook of `raw`: the in-place one when `mode`
allows it and
+ /// `raw` is unique, otherwise the copying one.
#[inline(always)]
fn structural_mutate_hook(raw: TVMFFIAny, mode: InplaceMode) ->
Option<TVMFFIAny> {
- let use_inplace = mode == InplaceMode::Allow && object_is_unique(raw);
- if use_inplace {
+ if mode.for_value(raw) == InplaceMode::Allow {
if let Some(attr) =
structural_mutate_column::<true>().get_raw_ffi_any(raw.type_index) {
if attr.type_index == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32
|| attr.type_index == TVMFFITypeIndex::kTVMFFIFunction as
i32
@@ -1686,18 +1533,17 @@ impl StructuralMutatorObj {
(attr.type_index != TVMFFITypeIndex::kTVMFFINone as
i32).then_some(attr)
}
- /// Invoke a native or packed hook with borrowed `mutator`, `value` and
`hook`.
- /// Return one owning result; native Error results receive input context.
+ /// Call the registered mutation `hook` (native pointer or packed
function) on `value`.
///
/// # Safety
- /// The runtime and inputs must remain live for this synchronous call.
Publish
- /// the current exclusive dispatcher reborrow first. A native hook must be
a
- /// non-null function with the mutation ABI accepting this complete runtime
- /// layout. All hooks obey [`StructuralMutatorBase`]'s non-escaping
contract.
- /// The caller must have permission for any in-place mutation the hook
performs.
+ ///
+ /// `runtime` is live with its dispatcher published, `value` and `hook`
outlive the call,
+ /// the caller's mode allows any in-place mutation the hook performs, and
a native hook
+ /// has the mutation ABI for this runtime's layout and obeys
[`StructuralMutatorBase`]'s
+ /// non-escaping contract.
#[inline(always)]
pub unsafe fn call_structural_mutate_hook_raw(
- mutator: *mut Self,
+ runtime: *mut Self,
value: TVMFFIAny,
hook: TVMFFIAny,
) -> TVMFFIAny {
@@ -1705,59 +1551,31 @@ impl StructuralMutatorObj {
if hook.type_index == TVMFFITypeIndex::kTVMFFIOpaquePtr as i32 {
let callback: FStructuralMutate =
std::mem::transmute(hook.data_union.v_ptr);
Self::with_native_mutation_context_raw(
- callback(mutator, AnyView::from_raw_ffi_any(value)),
+ callback(runtime, AnyView::from_raw_ffi_any(value)),
value,
)
} else {
- Self::call_structural_mutate_hook_slow_raw(
- mutator,
- value.type_index,
- value.small_str_len,
- value.data_union,
- hook.type_index,
- hook.small_str_len,
- hook.data_union,
- )
+ Self::call_structural_mutate_hook_slow_raw(runtime, value,
hook)
}
}
}
- /// Reconstruct borrowed input and attribute from their scalar fields for
the
- /// packed/malformed-hook fallback. `mutator` is borrowed for the call.
Return
- /// one owning token after Function/argument cleanup; annotate actual Err
only.
- /// The private C-unwind ABI preserves Rust cleanup and AArch64 register
returns
- /// without changing public or native callback ABIs.
+ /// The packed-function arm of [`Self::call_structural_mutate_hook_raw`],
outlined as cold.
///
/// # Safety
- /// `mutator`, input fields, and registered attribute fields must remain
valid
- /// through this synchronous call; none of these inputs transfers
ownership.
- // Outline packed-call scratch; the AArch64 C ABI keeps raw results in
- // registers while C-unwind preserves Rust cleanup inside this private
tail.
+ ///
+ /// As [`Self::call_structural_mutate_hook_raw`].
#[cold]
#[inline(never)]
unsafe extern "C-unwind" fn call_structural_mutate_hook_slow_raw(
- mutator: StructuralMutatorHandle,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
- attr_type_index: i32,
- attr_small_str_len: u32,
- attr_data_union: TVMFFIAnyDataUnion,
+ runtime: StructuralMutatorHandle,
+ raw: TVMFFIAny,
+ attr: TVMFFIAny,
) -> TVMFFIAny {
- let raw = TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- };
- let attr = TVMFFIAny {
- type_index: attr_type_index,
- small_str_len: attr_small_str_len,
- data_union: attr_data_union,
- };
let result = if attr.type_index == TVMFFITypeIndex::kTVMFFIFunction as
i32 {
Function::try_from(AnyView::from_raw_ffi_any(attr)).and_then(|function| {
function.call_packed(&[
- Self::borrowed_mutator_view(mutator),
+ Self::borrowed_mutator_view(runtime),
AnyView::from_raw_ffi_any(raw),
])
})
@@ -1770,15 +1588,14 @@ impl StructuralMutatorObj {
)
}
- /// Borrow a live runtime mutator as an object-valued ABI argument.
+ /// Borrow the live `runtime` as an object argument, without a reference
count.
///
/// # Safety
///
- /// `mutator` must point to a live object and outlive the returned view.
The
- /// view does not increment the object's reference count.
+ /// `runtime` outlives the returned view.
#[inline(always)]
- unsafe fn borrowed_mutator_view<'a>(mutator: StructuralMutatorHandle) ->
AnyView<'a> {
- let object = mutator.cast::<TVMFFIObject>();
+ unsafe fn borrowed_mutator_view<'a>(runtime: StructuralMutatorHandle) ->
AnyView<'a> {
+ let object = runtime.cast::<TVMFFIObject>();
let mut raw = TVMFFIAny::new();
raw.type_index = (*object).type_index;
raw.small_str_len = 0;
@@ -1819,34 +1636,21 @@ impl StructuralMutatorObj {
}
}
- /// Snapshot the live `runtime` region before fallback work, reconstruct
borrowed
- /// input from scalar fields, and transfer one owning
replacement/Unchanged/Error.
- /// The private C-unwind ABI keeps AArch64 results in registers while
permitting
- /// Rust cleanup; public/native callback ABIs remain unchanged.
+ /// Mutate borrowed `raw` by reflection, caching free variables and DAG
nodes
+ /// through the vtable remap.
///
/// # Safety
- /// `runtime` must be a live mutator prefix and the input fields a valid
borrowed value.
- /// The input owner must survive reflection, remap updates, and error
annotation.
- // Perf note: Keep reflection scratch outside recursive frames. This
fallback
- // can be frequent, so inline(never) is intentional without cold.
- // The AArch64 C ABI keeps raw results in registers; C-unwind preserves
- // Rust unwinding inside this private helper.
+ ///
+ /// `runtime` is a live mutator prefix with its vtable, and `raw` outlives
the call.
+ /// Outlined (frequent, not cold) to keep reflection scratch out of
recursive frames.
#[inline(never)]
unsafe extern "C-unwind" fn mutate_without_hook_raw(
runtime: StructuralMutatorHandle,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
+ raw: TVMFFIAny,
) -> TVMFFIAny {
- // Perf note: Read region state here, before fallback semantics need
it,
- // rather than loading it on the native-hook path that does not use it.
+ // Read the region here rather than on the native-hook path, which
does not use it.
let def_region_kind =
std::mem::transmute::<i32,
DefRegionKind>(*(*runtime).def_region_mode.get());
- let raw = TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- };
result_into_raw_ffi_any(
Self::mutate_without_hook(runtime, raw, def_region_kind)
.map_err(|error| with_mutation_error_context(error, raw,
"mutate")),
@@ -1882,10 +1686,7 @@ impl StructuralMutatorObj {
.map(|_| ())
}
- /// Resolve fallback mutation for live borrowed `raw` and `runtime`, using
the
- /// entry snapshot `def_region_kind`. Preserve cached identities,
free-variable
- /// policy and owning reflected results. Internal callers guarantee valid
inputs;
- /// normal Rust owners clean up partial work if reflection fails or
unwinds.
+ /// The reflective mutation of [`Self::mutate_without_hook_raw`], on owned
results.
#[inline(always)]
fn mutate_without_hook(
runtime: StructuralMutatorHandle,
@@ -1897,15 +1698,9 @@ impl StructuralMutatorObj {
}
let type_info = checked_type_info(raw.type_index)?;
- let kind = unsafe {
- if (*type_info).metadata.is_null() {
- None
- } else {
- Some((*(*type_info).metadata).structural_eq_hash_kind)
- }
- };
- let is_free_var = kind ==
Some(TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32);
- let is_dag_node = kind ==
Some(TVMFFISEqHashKind::kTVMFFISEqHashKindDAGNode as i32);
+ 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)?
} {
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
index 7ca70d05..38e96019 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -46,26 +46,8 @@
//! reaching the non-unwinding `extern "C"` callback boundary aborts the
process;
//! visitor callbacks do not catch or transport panic payloads across native
frames.
-//
-----------------------------------------------------------------------------
-// Implementation overview
-//
-----------------------------------------------------------------------------
-//
-// This file is organized into:
-// - Data structures and visiting capabilities.
-// - Outlined errors and private utility functions.
-// - Engine implementation and the public entry point.
-//
-// We use narrowly scoped unsafe code for native interoperation and efficient
-// traversal. Safety and ownership contracts are documented at each boundary.
-//
-// The main performance strategies are:
-// - Outline cold work to keep its temporary storage out of recursive frames.
-// - Use _raw helpers and private extern "C-unwind" boundaries for register
-// transport on AArch64 while preserving Rust unwinding.
-// - Inline small conversions and forwarding code so ownership transfers and
-// redundant cleanup can optimize away.
-//
-// Local Perf note comments explain individual implementation choices.
+// `_raw` functions move one owned `TVMFFIAny`: forward it once, or import it
into `Any` before
+// anything can unwind. Private `extern "C-unwind"` helpers return it in
registers on AArch64.
use std::cell::UnsafeCell;
use std::marker::PhantomData;
@@ -85,24 +67,19 @@ use crate::error::{Error, Result, TYPE_ERROR};
use crate::function::Function;
use crate::object::{Object, ObjectArc};
use crate::reflection::TypeAttrColumn;
-use crate::tvm_ffi_sys::TVMFFIFieldFlagBitMask::{
- kTVMFFIFieldFlagBitMaskSEqHashDefPattern,
kTVMFFIFieldFlagBitMaskSEqHashDefSimple,
- kTVMFFIFieldFlagBitMaskSEqHashIgnore,
-};
use crate::tvm_ffi_sys::{
- TVMFFIAny, TVMFFIAnyDataUnion, TVMFFIByteArray, TVMFFIDefRegionKind,
TVMFFIFieldInfo,
- TVMFFIGetTypeInfo, TVMFFIObject, TVMFFISEqHashKind, TVMFFITypeIndex,
TVMFFITypeKeyToIndex,
+ TVMFFIAny, TVMFFIDefRegionKind, TVMFFIFieldInfo, TVMFFIGetTypeInfo,
TVMFFIObject,
+ TVMFFITypeIndex,
};
use super::structural_common::{
- missing_field_getter_error, runtime_error, with_structural_error_context,
- with_structural_field_error_context, with_visit_error_context,
+ field_def_region, for_each_field_info, free_var_child_region,
missing_field_getter_error,
+ registered_type_index, runtime_error, seq_hash_kind,
with_structural_error_context,
+ with_structural_field_error_context, wrong_dispatcher,
FLAG_SEQ_HASH_IGNORE,
};
+use super::visit_error_context::update_visit_error_context;
const STRUCTURAL_VISIT_ATTR: &str = "__s_visit__";
-const FLAG_SEQ_HASH_IGNORE: i64 = kTVMFFIFieldFlagBitMaskSEqHashIgnore as i64;
-const FLAG_SEQ_HASH_DEF_PATTERN: i64 =
kTVMFFIFieldFlagBitMaskSEqHashDefPattern as i64;
-const FLAG_SEQ_HASH_DEF_SIMPLE: i64 = kTVMFFIFieldFlagBitMaskSEqHashDefSimple
as i64;
//
-----------------------------------------------------------------------------
// Data structures and FFI layout
@@ -199,18 +176,8 @@ unsafe impl StructuralVisitorBase for StructuralVisitorObj
{
}
fn type_index() -> i32 {
- static TYPE_INDEX: ColdInitLazyLock<i32> = ColdInitLazyLock::new(||
unsafe {
- let key =
TVMFFIByteArray::from_str(StructuralVisitorObj::TYPE_KEY);
- let mut type_index = 0;
- let return_code = TVMFFITypeKeyToIndex(&key, &mut type_index);
- if return_code != 0 {
- panic!(
- "ffi.StructuralVisitor is not registered: {}",
- Error::from_raised()
- );
- }
- type_index
- });
+ static TYPE_INDEX: ColdInitLazyLock<i32> =
+ ColdInitLazyLock::new(||
registered_type_index(StructuralVisitorObj::TYPE_KEY));
*TYPE_INDEX
}
@@ -311,10 +278,6 @@ impl VisitInterrupt {
// Public visitor API
//
-----------------------------------------------------------------------------
-// Internal visit protocol matches the C++ owning TVMFFIAny result: None means
-// continue; VisitInterrupt (including None/Error payloads) and Error stop
descent.
-// Each ABI ingress/import and egress/export transfers ownership exactly once.
-// Recursive drivers move this carrier unchanged; typed public APIs decode it.
/// The running structural visit, passed to each handler.
///
/// A handler that matches a node decides which children are visited: call
@@ -328,33 +291,11 @@ impl VisitInterrupt {
pub struct StructuralVisitorView<'a, D, Base = StructuralVisitorObj> {
runtime: StructuralVisitorHandle,
#[allow(clippy::type_complexity)]
- _scope: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
+ _marker: PhantomData<(&'a mut (), fn(&mut D) -> Base, Rc<()>)>,
}
impl<D, Base: StructuralVisitorBase> StructuralVisitorView<'_, D, Base> {
- /// Inspect the base state without retaining the scoped visitor runtime.
- ///
- /// The reference cannot outlive its view:
- ///
- /// # Returns
- ///
- /// The base state, borrowed for the lifetime of this view.
- ///
- /// ```compile_fail
- /// use tvm_ffi::{StructuralVisitorObj, StructuralVisitorView};
- /// fn retain<D>(view: &StructuralVisitorView<'_, D>) -> &'static
StructuralVisitorObj {
- /// view.base()
- /// }
- /// ```
- ///
- /// The prefix cannot be converted into an owning FFI value:
- ///
- /// ```compile_fail
- /// use tvm_ffi::{Any, StructuralVisitorView};
- /// fn retain<D>(view: &StructuralVisitorView<'_, D>) -> Any {
- /// Any::from(view.base())
- /// }
- /// ```
+ /// Inspect the base state; the borrow cannot outlive this view.
#[inline(always)]
pub fn base(&self) -> &Base {
// SAFETY: the unsafe base contract permits shared inspection,
including
@@ -372,7 +313,7 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
/// The current definition region.
#[inline(always)]
pub fn def_region_kind(&self) -> DefRegionKind {
- // SAFETY: engine scopes and trusted ABI hooks store valid enum values.
+ // SAFETY: the view's runtime is live.
unsafe { std::mem::transmute(*(*self.runtime).def_region_mode.get()) }
}
@@ -395,6 +336,7 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
kind: DefRegionKind,
callback: impl FnOnce(&mut Self) -> R,
) -> R {
+ // SAFETY: the view's runtime outlives the callback.
StructuralVisitorObj::with_def_region_kind(self.runtime, kind, ||
callback(self))
}
@@ -475,10 +417,7 @@ impl<D, Base: StructuralVisitorBase>
StructuralVisitorView<'_, D, Base> {
}
impl<Base: StructuralVisitorBase> StructuralVisitEngine<Base> {
- /// Normalize the input once and traverse using this engine's native base.
- /// Named dispatchers keep their actual recursive view type. Callback and
- /// tuple targets are their aggregate adapters; closures taking their own
- /// recursive view type remain unsupported.
+ /// Visit `root` with `dispatcher` on the native base `Base`. See also
[`structural_visit`].
#[inline(always)]
pub fn visit_root<'input, R, H, M, D, O>(
root: R,
@@ -499,14 +438,11 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
Option<Result<Option<VisitInterrupt>>>,
>,
{
+ // Normalize the input once. Named dispatchers keep their own
recursive view
+ // type; callback and tuple targets are their aggregate adapters.
let mut storage = dispatcher.into_dispatcher();
let root = root.into();
- visit_result_from_any(unsafe {
- Any::from_raw_ffi_any(Self::run_visitor_raw::<D>(
- *root.as_raw_ffi_any(),
- storage.borrow_mut(),
- ))
- })
+ Self::run_visitor::<D>(*root.as_raw_ffi_any(), storage.borrow_mut())
}
}
@@ -522,7 +458,6 @@ impl
super::dispatch::CallbackResult<Result<Option<VisitInterrupt>>>
//
-----------------------------------------------------------------------------
// Outlined errors
//
-----------------------------------------------------------------------------
-// Keep construction and formatting scratch outside successful traversal
frames.
#[cold]
#[inline(never)]
@@ -547,18 +482,8 @@ fn invalid_visit_hook_result_error() -> Error {
// Perf note: Keep error formatting and context-allocation scratch out of
visit frames.
#[cold]
#[inline(never)]
-fn annotate_visit_error(
- error: Error,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
-) -> Error {
- let value = TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- };
- let error = with_visit_error_context(error, value);
+fn annotate_visit_error(error: Error, value: TVMFFIAny) -> Error {
+ let error = update_visit_error_context(error, value);
with_structural_error_context(
error,
"visit",
@@ -569,22 +494,16 @@ fn annotate_visit_error(
)
}
-// Perf note: Outline panic construction; the successful identity check stays
small.
-#[cold]
-#[inline(never)]
-fn wrong_visitor_dispatcher() -> ! {
- panic!("structural visitor called with a different dispatcher instance")
-}
-
//
-----------------------------------------------------------------------------
// Private utility functions
//
-----------------------------------------------------------------------------
#[inline(always)]
-fn dispatcher_context<D>(dispatch: &mut D) -> *mut c_void {
+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 {
@@ -619,38 +538,6 @@ unsafe fn take_error(value: Any) -> Error {
Error::move_from_any_after_check(&mut raw)
}
-#[inline(always)]
-pub(crate) fn field_def_region(field: &TVMFFIFieldInfo, inherited:
DefRegionKind) -> DefRegionKind {
- // Precedence: a pattern region propagates; entering any kind inside it
has no effect.
- if inherited == DefRegionKind::Pattern {
- DefRegionKind::Pattern
- } else if field.flags & FLAG_SEQ_HASH_DEF_SIMPLE != 0 {
- DefRegionKind::Simple
- } else if field.flags & FLAG_SEQ_HASH_DEF_PATTERN != 0 {
- DefRegionKind::Pattern
- } else {
- inherited
- }
-}
-
-/// A simple definition applies to a FreeVar value itself, but not to
-/// the FreeVar's own reflected children: nested free vars there must resolve
-/// against an outer binding instead of rebinding. Mirrors C++
-/// `VisitReflectedFieldsExpected`.
-#[inline(always)]
-pub(crate) fn free_var_child_region(
- inherited: DefRegionKind,
- structural_eq_hash_kind: i32,
-) -> DefRegionKind {
- if inherited == DefRegionKind::Simple
- && structural_eq_hash_kind ==
TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32
- {
- DefRegionKind::None
- } else {
- inherited
- }
-}
-
#[inline(always)]
fn with_field_error_context(result: Any, name: &str) -> Any {
if result.type_index() == TVMFFITypeIndex::kTVMFFIError as i32 {
@@ -674,60 +561,12 @@ fn structural_visit_column() -> TypeAttrColumn {
*STRUCTURAL_VISIT_COLUMN
}
-/// Visit every reflected field described by `info` and its ancestors in the
-/// same parent-to-child order as C++ `ForEachFieldInfoWithEarlyStop`.
-///
-/// # Safety
-///
-/// `info` must point to an immortal registered type-info record.
-pub(crate) unsafe fn for_each_field_info<B>(
- info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
- callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
-) -> Option<B> {
- // Ancestor slot 0 is the root Object. C++ starts at slot 1, walks toward
- // the immediate parent, then visits the concrete type's own fields.
- for depth in 1..(*info).type_depth {
- let ancestor = *(*info).type_acenstors.offset(depth as isize);
- if let Some(value) = visit_field_level(ancestor, callback) {
- return Some(value);
- }
- }
- visit_field_level(info, callback)
-}
-
-unsafe fn visit_field_level<B>(
- info: *const crate::tvm_ffi_sys::TVMFFITypeInfo,
- callback: &mut impl FnMut(&'static TVMFFIFieldInfo) -> ControlFlow<B>,
-) -> Option<B> {
- if info.is_null() || (*info).fields.is_null() {
- return None;
- }
- let fields = std::slice::from_raw_parts((*info).fields, (*info).num_fields
as usize);
- for field in fields {
- // C reflection tables are immortal once registered.
- let field: &'static TVMFFIFieldInfo = &*(field as *const
TVMFFIFieldInfo);
- if let ControlFlow::Break(value) = callback(field) {
- return Some(value);
- }
- }
- None
-}
-
//
-----------------------------------------------------------------------------
// Prefix-only default traversal and native ABI operations
//
-----------------------------------------------------------------------------
-// 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: each `_raw` result carries one owned
ABI
-// value. Forward it exactly once or import it into Any before any operation
-// that can unwind or discard it, including destruction of an observer.
impl StructuralVisitorObj {
- /// Consume owning `result` from descent, adding borrowed `value` context
on Error.
- /// Internal callers supply valid tokens and keep the input live during
annotation;
- /// nonerrors transfer unchanged, and Error ownership is guarded through
formatting.
+ /// 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::kTVMFFIError as i32
@@ -739,29 +578,17 @@ impl StructuralVisitorObj {
// SAFETY: the Error tag establishes the owned object's
representation.
unsafe {
let error = take_error(Any::from_raw_ffi_any(result));
- Any::into_raw_ffi_any(
- annotate_visit_error(
- error,
- value.type_index,
- value.small_str_len,
- value.data_union,
- )
- .into(),
- )
+ Any::into_raw_ffi_any(annotate_visit_error(error,
value).into())
}
}
}
- /// Descend through the native hook or reflected fields using this runtime.
- ///
- /// The dispatcher field must already hold the caller's actual reborrow.
- /// Descent itself never accesses D: every child reenters the same visitor
- /// callback, which restores its incoming pointer before the next sibling.
- /// Returns one owning raw None/interrupt/Error token.
+ /// Visit the children of `value` through its hook or reflected fields,
without
+ /// dispatching `value` itself.
///
/// # Safety
- /// `runtime` must be this live engine and `value` a valid borrowed input
whose
- /// owner survives descent. Hooks may not retain the runtime or input
borrow.
+ ///
+ /// `runtime` is live with its dispatcher published, and `value` outlives
descent.
#[inline(always)]
unsafe fn visit_children_raw(runtime: StructuralVisitorHandle, value:
TVMFFIAny) -> TVMFFIAny {
if let Some(attr) =
structural_visit_column().get_raw_ffi_any(value.type_index) {
@@ -773,53 +600,29 @@ impl StructuralVisitorObj {
if value.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32
{
TVMFFIAny::new()
} else {
- Self::visit_reflected_fields_raw(
- runtime,
- value.type_index,
- value.small_str_len,
- value.data_union,
- )
+ Self::visit_reflected_fields_raw(runtime, value)
}
}
- /// Reconstruct borrowed input from its scalar fields and visit registered
fields
- /// through live `runtime`. Return one owning None/interrupt/Error after
local cleanup.
- /// The private C-unwind ABI preserves Rust unwinding and AArch64 register
returns;
- /// public/native callback ABIs are unchanged.
+ /// Visit each reflected field of the object in `value`.
///
/// # Safety
- /// The input fields and runtime must be valid for synchronous descent;
registered
- /// reflection metadata must satisfy its getter/offset contracts.
- // Perf note: Keep reflection scratch outside recursive frames. This
fallback
- // can be frequent, so inline(never) is intentional without cold.
- // The AArch64 C ABI returns TVMFFIAny in registers; C-unwind preserves
- // Rust cleanup if reflected traversal panics inside this private helper.
+ ///
+ /// As [`Self::visit_children_raw`]. Outlined (frequent, not cold) to keep
reflection
+ /// scratch out of recursive frames.
#[inline(never)]
unsafe extern "C-unwind" fn visit_reflected_fields_raw(
runtime: StructuralVisitorHandle,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
+ value: TVMFFIAny,
) -> TVMFFIAny {
- let value = TVMFFIAny {
- type_index,
- small_str_len,
- data_union,
- };
let type_info = unsafe { TVMFFIGetTypeInfo(value.type_index) };
if type_info.is_null() {
return
Any::into_raw_ffi_any(unregistered_visit_type_error(value.type_index).into());
}
- let seq_hash_kind = unsafe {
- let metadata = (*type_info).metadata;
- if metadata.is_null() {
- TVMFFISEqHashKind::kTVMFFISEqHashKindUnsupported as i32
- } else {
- (*metadata).structural_eq_hash_kind
- }
- };
- let kind = std::mem::transmute::<i32,
DefRegionKind>(*(*runtime).def_region_mode.get());
- let def_region_kind = free_var_child_region(kind, seq_hash_kind);
+ let def_region_kind = free_var_child_region(
+ std::mem::transmute::<i32,
DefRegionKind>(*(*runtime).def_region_mode.get()),
+ seq_hash_kind(type_info),
+ );
let object = unsafe { value.data_union.v_obj } as *mut u8;
let halted = unsafe {
for_each_field_info(type_info, &mut |field| {
@@ -834,12 +637,11 @@ impl StructuralVisitorObj {
Any::into_raw_ffi_any(halted.unwrap_or_else(Any::new))
}
- /// Visit one registered `field` in borrowed `object` using
`inherited_region`.
- /// Own getter output through recursive traversal and return an owning
result.
+ /// Visit one reflected `field` of `object` under `inherited_region`.
///
/// # Safety
- /// `runtime` and `object` must be live; `field` must describe valid
storage and a
- /// registered getter for that object. Neither raw input transfers
ownership.
+ ///
+ /// `runtime` and `object` are live, and `field` describes `object`'s
storage.
unsafe fn visit_reflected_field(
runtime: StructuralVisitorHandle,
object: *mut u8,
@@ -870,33 +672,29 @@ impl StructuralVisitorObj {
)
}
- /// Invoke the live visitor vtable on borrowed `value`, returning one
owning token.
- /// Internal callers keep runtime/input valid and publish the dispatcher
reborrow.
- /// None bypasses dispatch; native callbacks report failures without
unwinding.
+ /// Dispatch borrowed `value` through the runtime's vtable.
#[inline(always)]
- fn call_visitor_raw(visitor: StructuralVisitorHandle, value: TVMFFIAny) ->
TVMFFIAny {
+ fn call_visitor_raw(runtime: StructuralVisitorHandle, value: TVMFFIAny) ->
TVMFFIAny {
if value.type_index == TVMFFITypeIndex::kTVMFFINone as i32 {
core::hint::cold_path();
return TVMFFIAny::new();
}
unsafe {
- let callback = (*(*visitor).vtable).visit;
- callback(visitor, AnyView::from_raw_ffi_any(value))
+ let callback = (*(*runtime).vtable).visit;
+ callback(runtime, AnyView::from_raw_ffi_any(value))
}
}
- /// Invoke a native or packed hook with borrowed `visitor`, `value` and
`hook`.
- /// Return one owning None/Error/VisitInterrupt; packed results are
checked.
+ /// Call the registered visit `hook` (native pointer or packed function)
on `value`.
///
/// # Safety
- /// The runtime and both borrowed inputs must be valid and live for this
call;
- /// the inputs transfer no ownership. Publish the current exclusive
dispatcher
- /// reborrow first. A native hook must be a non-null function with the
visit ABI
- /// accepting this runtime's complete layout. All hooks must obey
- /// [`StructuralVisitorBase`]'s synchronous, same-thread, non-escaping
contract.
+ ///
+ /// `runtime` is live with its dispatcher published, `value` and `hook`
outlive the
+ /// call, and a native hook has the visit ABI for this runtime's layout
and obeys
+ /// [`StructuralVisitorBase`]'s non-escaping contract.
#[inline(always)]
pub unsafe fn call_structural_visit_hook_raw(
- visitor: *mut Self,
+ runtime: *mut Self,
value: TVMFFIAny,
hook: TVMFFIAny,
) -> TVMFFIAny {
@@ -906,59 +704,29 @@ impl StructuralVisitorObj {
// Native pointer registration is an unsafe contract: a
non-null
// callback with this ABI, borrowed runtime lifetime and
owning result.
let hook: FStructuralVisit = std::mem::transmute(pointer);
- hook(visitor, AnyView::from_raw_ffi_any(value))
+ hook(runtime, AnyView::from_raw_ffi_any(value))
} else {
core::hint::cold_path();
- Self::call_structural_visit_hook_slow_raw(
- visitor,
- value.type_index,
- value.small_str_len,
- value.data_union,
- hook.type_index,
- hook.small_str_len,
- hook.data_union,
- )
+ Self::call_structural_visit_hook_slow_raw(runtime, value, hook)
}
}
}
- /// Reconstruct borrowed value/hook from their scalar fields for a packed
call.
- /// Internal callers keep `visitor` and both inputs live; no input
transfers ownership.
- /// Validate the owning packed result and release temporary owners before
exporting
- /// one raw token. Private C-unwind preserves Rust cleanup and AArch64
register
- /// returns without changing the public or native callback ABI.
- // Keep packed calls and malformed registrations out of the native-hook
hot path.
- // Outline uncommon packed-call scratch to keep native visit frames small.
- // Match the native raw-result ABI (registers on AArch64), without adding
- // an abort boundary around Rust packed-call cleanup.
+ /// The packed-function arm of [`Self::call_structural_visit_hook_raw`],
outlined as cold;
+ /// its result tag is checked.
#[cold]
#[inline(never)]
extern "C-unwind" fn call_structural_visit_hook_slow_raw(
- visitor: StructuralVisitorHandle,
- value_type_index: i32,
- value_small_str_len: u32,
- value_data: TVMFFIAnyDataUnion,
- hook_type_index: i32,
- hook_small_str_len: u32,
- hook_data: TVMFFIAnyDataUnion,
+ runtime: StructuralVisitorHandle,
+ value: TVMFFIAny,
+ hook: TVMFFIAny,
) -> TVMFFIAny {
- // Construct addressable packed-call arguments only on this cold path.
- let value = TVMFFIAny {
- type_index: value_type_index,
- small_str_len: value_small_str_len,
- data_union: value_data,
- };
- let hook = TVMFFIAny {
- type_index: hook_type_index,
- small_str_len: hook_small_str_len,
- data_union: hook_data,
- };
// Packed hooks (including Python class methods) can return any
well-formed
// Any through safe APIs. Their result tag needs validation before
decoding.
let result = unsafe {
Function::try_from(AnyView::from_raw_ffi_any(hook)).and_then(|function| {
// Borrow the live runtime for this synchronous hook; it may
not retain it.
- let object = visitor.cast::<TVMFFIObject>();
+ let object = runtime.cast::<TVMFFIObject>();
let mut runtime = TVMFFIAny::new();
runtime.type_index = (*object).type_index;
runtime.data_union.v_obj = object;
@@ -1009,14 +777,14 @@ impl StructuralVisitorObj {
// Generic runtime: only this implementation projects the dispatcher after
Base.
impl<Base: StructuralVisitorBase> StructuralVisitEngine<Base> {
- /// Recover the Rust engine whose first field is this ABI base.
+ /// The engine whose first field is the native prefix `runtime`.
///
/// # Safety
- /// The handle must belong to the live, synchronous Rust traversal that
created
+ /// `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(handle: StructuralVisitorHandle) -> *mut Self {
- handle.cast()
+ unsafe fn from_handle(runtime: StructuralVisitorHandle) -> *mut Self {
+ runtime.cast()
}
/// Publish the current exclusive `dispatch` reborrow and visit borrowed
`raw`.
@@ -1048,10 +816,10 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
runtime: StructuralVisitorHandle,
dispatch: &mut D,
) -> *mut c_void {
- let current = dispatcher_context(dispatch);
+ let current = dispatcher_ptr(dispatch);
if current != (*Self::from_handle(runtime)).dispatcher {
core::hint::cold_path();
- wrong_visitor_dispatcher();
+ wrong_dispatcher();
}
current
}
@@ -1103,7 +871,7 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
(AnyView::from_raw_ffi_any(raw), &mut visitor),
) {
Some(result) => visit_result_raw(result, raw),
- None => Self::default_visit_raw(runtime,
dispatcher_context(dispatch), raw),
+ None => Self::default_visit_raw(runtime,
dispatcher_ptr(dispatch), raw),
}
};
// The enclosing callback may resume using its ancestor mutable borrow.
@@ -1123,7 +891,7 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
) -> StructuralVisitorView<'a, D, Base> {
StructuralVisitorView {
runtime,
- _scope: PhantomData,
+ _marker: PhantomData,
}
}
@@ -1148,21 +916,18 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
)
}
- /// Run the public visit/walk adapter with borrowed `root` and exclusive
`dispatch`.
- /// Internal callers keep the root owner live. Own the result across
engine Drop,
- /// including unwind, then transfer one raw None/interrupt/Error token.
Hooks must
- /// release temporary runtime owners and cannot retain the scoped
dispatcher.
- pub(super) fn run_visitor_raw<D>(root: TVMFFIAny, dispatch: &mut D) ->
TVMFFIAny
+ /// 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 = match Base::new() {
- Ok(base) => base,
- Err(error) => return Any::into_raw_ffi_any(error.into()),
- };
+ 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.
@@ -1170,24 +935,24 @@ impl<Base: StructuralVisitorBase>
StructuralVisitEngine<Base> {
ObjectArc::<Object>::new_opaque_unchecked(
StructuralVisitEngine {
base,
- dispatcher: dispatcher_context(dispatch),
+ dispatcher: dispatcher_ptr(dispatch),
},
type_index,
)
};
- let handle = ObjectArc::as_mut_ptr(&mut
engine).cast::<StructuralVisitorObj>();
+ 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 {
- (*handle).vtable = Self::vtable::<D>();
- *(*handle).def_region_mode.get() = DefRegionKind::None as i32;
+ (*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(handle, root)) };
+ unsafe {
Any::from_raw_ffi_any(StructuralVisitorObj::call_visitor_raw(runtime, root)) };
// Trusted hooks have released every temporary strong/weak runtime
reference.
drop(engine);
- Any::into_raw_ffi_any(result)
+ visit_result_from_any(result)
}
}
diff --git a/rust/tvm-ffi/src/extra/structural_walk.rs
b/rust/tvm-ffi/src/extra/structural_walk.rs
index e92b0eeb..99e0b580 100644
--- a/rust/tvm-ffi/src/extra/structural_walk.rs
+++ b/rust/tvm-ffi/src/extra/structural_walk.rs
@@ -23,8 +23,7 @@
//! the non-unwinding native callback boundary aborts the process.
use super::structural_visit::{
- visit_result_from_any, DefRegionKind, StructuralVisitEngine,
StructuralVisitorView,
- VisitInterrupt,
+ DefRegionKind, StructuralVisitEngine, StructuralVisitorView,
VisitInterrupt,
};
use crate::{Any, AnyView, Dispatcher, IntoDispatcher, Result};
use std::borrow::BorrowMut;
@@ -171,26 +170,14 @@ where
for<'s, 'v> &'s mut D: Dispatcher<(AnyView<'v>, DefRegionKind),
Option<Result<WalkResult>>>,
{
let root = *root.as_raw_ffi_any();
- // Take ownership of the raw result right after the native call.
- let result = match order {
+ match order {
WalkOrder::PreOrder => {
- let mut visitor = WalkVisitor::<D, true> { observer };
- let raw = StructuralVisitEngine::run_visitor_raw::<WalkVisitor<'_,
D, true>>(
- root,
- &mut visitor,
- );
- unsafe { Any::from_raw_ffi_any(raw) }
+ StructuralVisitEngine::run_visitor(root, &mut WalkVisitor::<D,
true> { observer })
}
WalkOrder::PostOrder => {
- let mut visitor = WalkVisitor::<D, false> { observer };
- let raw = StructuralVisitEngine::run_visitor_raw::<WalkVisitor<'_,
D, false>>(
- root,
- &mut visitor,
- );
- unsafe { Any::from_raw_ffi_any(raw) }
+ StructuralVisitEngine::run_visitor(root, &mut WalkVisitor::<D,
false> { observer })
}
- };
- visit_result_from_any(result)
+ }
}
/// Walk `root` with an observer, the Rust analog of C++
diff --git a/rust/tvm-ffi/src/extra/unchanged.rs
b/rust/tvm-ffi/src/extra/unchanged.rs
index 66678193..4a0c8182 100644
--- a/rust/tvm-ffi/src/extra/unchanged.rs
+++ b/rust/tvm-ffi/src/extra/unchanged.rs
@@ -302,11 +302,7 @@ impl UnchangedOr<Any> {
// Transfer ownership directly into the checked cold conversion.
return unsafe {
let raw = Any::into_raw_ffi_any(data);
- Err(structural_error_from_raw(
- raw.type_index,
- raw.small_str_len,
- raw.data_union,
- ))
+ Err(structural_error_from_raw(raw))
};
}
Ok(Self {
diff --git a/rust/tvm-ffi/src/extra/visit_error_context.rs
b/rust/tvm-ffi/src/extra/visit_error_context.rs
new file mode 100644
index 00000000..c249a544
--- /dev/null
+++ b/rust/tvm-ffi/src/extra/visit_error_context.rs
@@ -0,0 +1,120 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+//! Rust side of C++ `tvm/ffi/extra/visit_error_context.h`: the context an
error
+//! carries to name the nodes a failed visit unwound through.
+
+use crate::any::AnyView;
+use crate::collections::array::Array;
+use crate::derive::Object;
+use crate::error::Error;
+use crate::object::{self, Object, ObjectArc, ObjectRef, ObjectRefCore};
+use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFIObject, TVMFFITypeIndex};
+
+/// Rust binding of C++ `VisitErrorContextObj`, the payload that
+/// `Error::extra_context` carries to name the nodes a failed visit unwound
through.
+#[repr(C)]
+#[derive(Object)]
+#[type_key = "ffi.VisitErrorContext"]
+#[type_final]
+pub(crate) struct VisitErrorContextObj {
+ base: Object,
+ /// Visited nodes, innermost first; C++ `ObjectRef` is nullable.
+ reverse_visit_pattern: Array<Option<ObjectRef>>,
+ /// The `extra_context` the error carried before this context replaced it.
+ prev_error_context: Option<ObjectRef>,
+}
+
+/// Name the visited object in `raw` in the error's visit context, as C++
+/// `detail::UpdateVisitErrorContext`.
+#[cold]
+pub(crate) fn update_visit_error_context(mut error: Error, raw: TVMFFIAny) ->
Error {
+ // Copy-on-write at each level: the Error, its context and the context's
pattern
+ // are each copied only when shared, so an error owned by the unwinding
frame is
+ // appended in place.
+ if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
+ return error;
+ }
+ // SAFETY: the traversal input remains live during annotation. Conversion
+ // acquires ownership before the temporary view ends; raw is an object tag.
+ let Ok(node) = ObjectRef::try_from(unsafe { AnyView::from_raw_ffi_any(raw)
}) else {
+ return error;
+ };
+ let as_context = |context: &ObjectRef| {
+ let object = ObjectArc::as_ptr(ObjectRef::data(context));
+ // SAFETY: `context` keeps its object alive; the type is checked first.
+ unsafe {
+ object::is_instance_of::<VisitErrorContextObj>(
+ (*object.cast::<TVMFFIObject>()).type_index,
+ )
+ .then(|| &*object.cast::<VisitErrorContextObj>())
+ }
+ };
+ // Callback, policy and default descent can report the same frame.
+ if let Some(context) = error.extra_context().and_then(as_context) {
+ if context
+ .reverse_visit_pattern
+ .iter()
+ .next_back()
+ .and_then(Option::as_ref)
+ .is_some_and(|last| last.same_as(&node))
+ {
+ return error;
+ }
+ }
+ let slot = error.extra_context_mut();
+ let previous = slot.take();
+ let context = match previous {
+ Some(previous) if as_context(&previous).is_some() => {
+ // SAFETY: `previous` was checked to be a `VisitErrorContextObj`.
+ let mut context = unsafe {
+ ObjectArc::from_raw(
+ ObjectArc::into_raw(ObjectRef::into_data(previous))
+ .cast::<VisitErrorContextObj>(),
+ )
+ };
+ if !ObjectArc::is_unique(&context) {
+ context = ObjectArc::new(VisitErrorContextObj {
+ base: Object::new(),
+ reverse_visit_pattern:
context.reverse_visit_pattern.clone(),
+ prev_error_context: context.prev_error_context.clone(),
+ });
+ }
+ // SAFETY: this handle is the context's only reference.
+ unsafe {
+ (*ObjectArc::as_mut_ptr(&mut context))
+ .reverse_visit_pattern
+ .push(Some(node))
+ };
+ context
+ }
+ previous => ObjectArc::new(VisitErrorContextObj {
+ base: Object::new(),
+ reverse_visit_pattern: Array::from_vec(vec![Some(node)]),
+ prev_error_context: previous,
+ }),
+ };
+ // SAFETY: every object starts with the root `Object` header.
+ *slot = Some(unsafe {
+ ObjectRef::from_data(ObjectArc::from_raw(
+ ObjectArc::into_raw(context).cast::<Object>(),
+ ))
+ });
+ error
+}
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index 76b19503..7431787c 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -116,7 +116,7 @@ pub use crate::type_traits::AnyCompatible;
/// strictly; `&Type` (such as `&Array<i64>` or `&tvm_ffi::String`) or
/// `&NodeObj`, which borrow the value or the object node in place without
/// changing a reference count; `MutationView<'_, T>`,
-/// which keeps the permission to mutate in place; or `AnyView<'_>`, which
+/// which keeps the in-place mode; or `AnyView<'_>`, which
/// matches every value and must be the last handler.
///
/// Only the outer `None` means that no handler matched: a handler error, or a
@@ -169,7 +169,7 @@ pub use tvm_ffi_macros::dispatch;
/// `Array<Any>`.
/// * `Type(ref name)` borrows a managed reference, and `NodeObj(ref name)`
/// borrows the object node, without changing its reference count.
-/// * `MutationView<Type>(name)` moves the permission to mutate in place into a
+/// * `MutationView<Type>(name)` moves the in-place mode into a
/// typed view.
///
/// Matching is strict: an `i64` arm does not match a stored `f64`, and a value
diff --git a/rust/tvm-ffi/src/match_any_internal.rs
b/rust/tvm-ffi/src/match_any_internal.rs
index c999b807..b57009a6 100644
--- a/rust/tvm-ffi/src/match_any_internal.rs
+++ b/rust/tvm-ffi/src/match_any_internal.rs
@@ -50,10 +50,10 @@ pub struct OwnedSource(crate::Any);
#[doc(hidden)]
pub struct ViewSource<'a>(AnyView<'a>);
#[doc(hidden)]
-pub struct CapabilitySource<'a>(Option<crate::MutationView<'a>>);
+pub struct MutationSource<'a>(Option<crate::MutationView<'a>>);
/// Object references move into an owning Any; inherent adapters preserve view
and
-/// capability lifetimes independently of the local holder's lifetime.
+/// view lifetimes independently of the local holder's lifetime.
#[doc(hidden)]
pub trait PrepareSource {
type Source;
@@ -89,8 +89,8 @@ impl<'a, T: ?Sized> SourceProbe<&'a T> {
}
impl<'a, T> SourceProbe<crate::MutationView<'a, T>> {
#[inline(always)]
- pub fn prepare(&mut self) -> CapabilitySource<'a> {
- CapabilitySource(Some(self.0.take().unwrap().into_untyped()))
+ pub fn prepare(&mut self) -> MutationSource<'a> {
+ MutationSource(Some(self.0.take().unwrap().into_untyped()))
}
}
@@ -120,7 +120,7 @@ impl SourceView for ViewSource<'_> {
self.0.as_raw_ffi_any()
}
}
-impl SourceView for CapabilitySource<'_> {
+impl SourceView for MutationSource<'_> {
#[inline(always)]
fn view(&self) -> AnyView<'_> {
self.0.as_ref().unwrap().as_any_view()
@@ -131,7 +131,7 @@ impl SourceView for CapabilitySource<'_> {
}
}
-/// A rejected guard drops an ordinary result or returns the original
capability.
+/// A rejected guard drops an ordinary result or returns the original view.
/// There is no Drop implementation, so native match guards retain Rust's usual
/// restriction against moving their bindings before the guard succeeds.
#[doc(hidden)]
@@ -153,7 +153,7 @@ impl<S> Restore<()> for S {
#[inline(always)]
fn restore(&mut self, _: ()) {}
}
-impl<'a> Restore<crate::MutationView<'a>> for CapabilitySource<'a> {
+impl<'a> Restore<crate::MutationView<'a>> for MutationSource<'a> {
#[inline(always)]
fn restore(&mut self, recovery: crate::MutationView<'a>) {
debug_assert!(self.0.is_none());
@@ -162,7 +162,7 @@ impl<'a> Restore<crate::MutationView<'a>> for
CapabilitySource<'a> {
}
/// Type-directed matching. The source parameter also expresses whether an
-/// aliased result can retain the input lifetime or transfer its capability.
+/// aliased result can retain the input lifetime or transfer its view.
#[doc(hidden)]
pub trait MatchPattern<S>: Sized {
type Recovery;
@@ -210,24 +210,22 @@ impl<'b> MatchPattern<&'b mut OwnedSource> for
AnyView<'b> {
})
}
}
-impl<'a, 'b> MatchPattern<&'b mut CapabilitySource<'a>> for AnyView<'b> {
+impl<'a, 'b> MatchPattern<&'b mut MutationSource<'a>> for AnyView<'b> {
type Recovery = ();
#[inline(always)]
- fn try_match(source: &'b mut CapabilitySource<'a>) -> Result<Matched<Self,
()>, ()> {
+ fn try_match(source: &'b mut MutationSource<'a>) -> Result<Matched<Self,
()>, ()> {
Ok(Matched {
value: source.view(),
recover: drop,
})
}
}
-impl<'a, 'b, T: crate::type_traits::ContainerElement> MatchPattern<&'b mut
CapabilitySource<'a>>
+impl<'a, 'b, T: crate::type_traits::ContainerElement> MatchPattern<&'b mut
MutationSource<'a>>
for crate::MutationView<'a, T>
{
type Recovery = crate::MutationView<'a>;
#[inline(always)]
- fn try_match(
- source: &'b mut CapabilitySource<'a>,
- ) -> Result<Matched<Self, Self::Recovery>, ()> {
+ fn try_match(source: &'b mut MutationSource<'a>) -> Result<Matched<Self,
Self::Recovery>, ()> {
match source.0.take().unwrap().downcast::<T>() {
Ok(value) => {
if unsafe {
T::container_match_constraints(value.as_any_view().as_raw_ffi_any()) } {
@@ -292,7 +290,7 @@ impl<'b, T: crate::type_traits::DowncastRefFromAny +
'static, S: SourceView>
// such as `&Add` points into the cell, an object type into the heap.
let node = unsafe { T::downcast_ref_from_any(source.cell())
}.ok_or(())? as *const T;
// SAFETY: the checked value is borrowed from source for 'b. No owning
- // conversion or capability transfer can occur while this borrow lives.
+ // conversion or view transfer can occur while this borrow lives.
Ok(Matched {
value: unsafe { &*node },
recover: drop,
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 316b3243..ccfc8cc6 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -48,19 +48,8 @@ pub struct ObjectArc<T: ObjectCore> {
_phantom: std::marker::PhantomData<T>,
}
-// Not `Send`/`Sync` by default: an object's thread safety is that of its
(often
-// C++) implementation, which the handle cannot see. An object type opts in
with
-// `ImmutableObject`; tvm-ffi's own immutable handles opt in explicitly.
-
-/// An object type whose shared instances are never mutated, so handles to it
may
-/// cross threads.
-///
-/// A handle `ObjectArc<T>`, and every reference built on it, is `Send + Sync`
-/// when `T: ImmutableObject` and `T`'s fields are themselves `Send + Sync`, so
-/// a field such as `Any`, which may hold a mutable object, keeps the handle
-/// single-threaded. The stub generator implements it for each class whose C++
-/// declaration is not `_type_mutable`. An erased `ObjectRef` may point at any
-/// object, so it never crosses threads.
+/// An object type whose shared instances are never mutated, so `ObjectArc<T>`
is
+/// `Send + Sync` when `T`'s fields are.
///
/// # Safety
///
@@ -655,10 +644,6 @@ pub mod unsafe_ {
}
}
-//---------------------
-// Object
-//---------------------
-
impl Object {
#[allow(clippy::new_without_default)]
pub fn new() -> Self {
@@ -681,10 +666,6 @@ unsafe impl ObjectCore for Object {
}
}
-//---------------------
-// ObjectArc
-//---------------------
-
// 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> {
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index cb849cfe..268a82aa 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -240,14 +240,17 @@ pub trait ContainerElement: container_element_ops::Ops {}
impl<T: AnyCompatible> ContainerElement for T {}
impl ContainerElement for Any {}
-/// Types whose value an FFI `Any` cell stores in place, so `&Self` can be
-/// borrowed from the cell, as [`crate::Array`] indexing does.
+/// A type whose value an FFI cell stores in place, so `&Self` borrows from the
+/// cell, as [`Array`](crate::Array) indexing does.
+///
+/// A reference type implemented by hand gets it from
+/// [`unsafe_impl_ref_downcast!`](crate::unsafe_impl_ref_downcast).
///
/// # Safety
///
-/// For a cell that passes `container_check_any_strict` for `Self`,
-/// `ref_from_any_unchecked` must return a reference into that cell that is a
valid
-/// `Self` for as long as the cell is borrowed.
+/// For a cell that `Self` strictly accepts, `ref_from_any_unchecked` must
+/// return a reference into that cell that is a valid `Self` for as long as the
+/// cell is borrowed.
#[doc(hidden)]
pub unsafe trait RefFromAnyUnchecked: ContainerElement {
/// Borrow the value stored in `cell`.
@@ -351,10 +354,10 @@ unsafe impl<T: crate::object::ObjectRefCore>
DowncastRefFromAny for Option<T> {
}
}
-/// Types that [`crate::Any::downcast_ref`] and
[`crate::AnyView::downcast_ref`]
-/// can borrow from an FFI cell in place: object types (`ArrayObj`), and the
-/// [`RefFromAnyUnchecked`] types (managed references such as `Array<T>`,
`String`,
-/// `i64`).
+/// A type that [`Any::downcast_ref`] and [`AnyView::downcast_ref`] borrow from
+/// an FFI cell in place: object types (`ArrayObj`), and the
+/// [`RefFromAnyUnchecked`] types (managed references such as `Array<T>`,
+/// `String`, `i64`).
///
/// # Safety
///
@@ -385,7 +388,8 @@ unsafe impl<T: crate::object::ObjectCore>
DowncastRefFromAny for T {
}
}
-/// Borrow `cell` as a [`RefFromAnyUnchecked`] type when it strictly holds one.
+/// Borrow `cell` as a [`RefFromAnyUnchecked`] type when it strictly holds one:
+/// the [`DowncastRefFromAny`] of such a type.
///
/// # Safety
///
@@ -402,7 +406,7 @@ pub unsafe fn downcast_ref_from_any_in_place<T:
RefFromAnyUnchecked>(
}
}
-/// Types that an object reference's [`ObjectRefCore::downcast_ref`] can borrow
+/// A type that an object reference's [`ObjectRefCore::downcast_ref`] borrows
/// from the reference's object pointer: object types (`ArrayObj`), and managed
/// references that are exactly one object pointer (`Array<T>`, every
/// `#[derive(ObjectRef)]` type).
@@ -438,7 +442,8 @@ unsafe impl<T: crate::object::ObjectCore>
DowncastRefFromObject for T {
}
/// Borrow the object pointer at `place` as the one-pointer reference type `T`
-/// when the object strictly is a `T`.
+/// when the object strictly is a `T`: the [`DowncastRefFromObject`] of such a
+/// type.
///
/// # Safety
///
@@ -456,6 +461,10 @@ pub unsafe fn downcast_ref_from_object_in_place<T:
RefFromAnyUnchecked>(
)
};
let object = *place;
+ // An object header always carries an object-range type index.
+ core::hint::assert_unchecked(
+ (*object).type_index >= TypeIndex::kTVMFFIStaticObjectBegin as i32,
+ );
let mut cell = TVMFFIAny::new();
cell.type_index = (*object).type_index;
cell.data_union.v_obj = object;
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index 914e0c24..5f0a2faf 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -69,6 +69,49 @@ struct RecursiveEntryMutator {
owned_value_pointer: Option<usize>,
}
+fn probe_remap(
+ dispatch: &mut ManualIncrement,
+ mutator: &mut StructuralMutatorView<'_, ManualIncrement>,
+ key: Any,
+) -> Result<UnchangedOr<Any>> {
+ assert!(mutator
+ .var_remap_get(dispatch, AnyView::from(&key))?
+ .is_none());
+ assert!(mutator
+ .var_remap_get(dispatch, AnyView::from(&1_i64))
+ .is_err());
+ assert!(mutator
+ .var_remap_set(dispatch, AnyView::from(&1_i64), &Any::new())
+ .is_err());
+ let initial = mutator.def_region_kind();
+ mutator.with_def_region_kind(DefRegionKind::Pattern, |mutator| {
+ assert_eq!(mutator.def_region_kind(), DefRegionKind::Pattern);
+ mutator.var_remap_set(dispatch, AnyView::from(&key), &Any::new())?;
+ assert_eq!(
+ mutator
+ .var_remap_get(dispatch, AnyView::from(&key))?
+ .unwrap()
+ .type_index(),
+ TypeIndex::kTVMFFINone as i32
+ );
+ // A None replacement does not suppress reflected descent.
+ let result = mutator.mutate(dispatch,
MutationView::borrowed(AnyView::from(&key)))?;
+ assert_eq!(
+ RemapNode::try_from(result.into_option().unwrap_or_else(||
key.clone()))?
+ .data
+ .value,
+ 8
+ );
+ mutator.var_remap_set(dispatch, AnyView::from(&key), &Any::from(41i64))
+ })?;
+ assert_eq!(mutator.def_region_kind(), initial);
+ let replacement = mutator
+ .var_remap_get(dispatch, AnyView::from(&key))?
+ .unwrap();
+ dispatch.remapped = Some(replacement.clone());
+ Ok(UnchangedOr::changed(replacement))
+}
+
impl<'v, 'c, 'r>
Dispatcher<
(
@@ -88,39 +131,7 @@ impl<'v, 'c, 'r>
) -> Option<Result<UnchangedOr<Any>>> {
Some(if strict_bool(value.as_any_view()) == Some(true) {
let key = self.remap_probe.as_ref().unwrap().clone();
- (|| {
- assert!(mutator.var_remap_get(self,
AnyView::from(&key))?.is_none());
- assert!(mutator.var_remap_get(self,
AnyView::from(&1_i64)).is_err());
- assert!(mutator
- .var_remap_set(self, AnyView::from(&1_i64), &Any::new())
- .is_err());
- let initial = mutator.def_region_kind();
- mutator.with_def_region_kind(DefRegionKind::Pattern, |mutator|
{
- assert_eq!(mutator.def_region_kind(),
DefRegionKind::Pattern);
- mutator.var_remap_set(self, AnyView::from(&key),
&Any::new())?;
- assert_eq!(
- mutator
- .var_remap_get(self, AnyView::from(&key))?
- .unwrap()
- .type_index(),
- TypeIndex::kTVMFFINone as i32
- );
- // A None replacement does not suppress reflected descent.
- let result =
- mutator.mutate(self,
MutationView::borrowed(AnyView::from(&key)))?;
- assert_eq!(
-
RemapNode::try_from(result.into_option().unwrap_or_else(|| key.clone()))?
- .data
- .value,
- 8
- );
- mutator.var_remap_set(self, AnyView::from(&key),
&Any::from(41i64))
- })?;
- assert_eq!(mutator.def_region_kind(), initial);
- let replacement = mutator.var_remap_get(self,
AnyView::from(&key))?.unwrap();
- self.remapped = Some(replacement.clone());
- Ok(UnchangedOr::changed(replacement))
- })()
+ probe_remap(self, mutator, key)
} else if let Some(integer) =
value.as_any_view().downcast_ref::<i64>().copied() {
Ok((integer + 1).into())
} else {
@@ -1209,8 +1220,11 @@ fn
callback_errors_preserve_message_and_add_object_context() {
any_object_pointer(expected)
);
}
+}
- // The failing node may be a pre-order replacement or a post-order rebuilt
parent.
+// The failing node may be a pre-order replacement or a post-order rebuilt
parent.
+#[test]
+fn map_errors_name_the_replaced_or_rebuilt_node() {
for order in [WalkOrder::PreOrder, WalkOrder::PostOrder] {
let root = Array::from_vec(vec![0i64]);
let mut failed_node = None;