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 a472db8d [FFI] Refcount ordering, Array copy-on-write mutators, Rust
API surface cleanup (#943)
a472db8d is described below
commit a472db8d80b57c6d6be34965e84133f7ce450c57
Author: Tianqi Chen <[email protected]>
AuthorDate: Fri Nov 6 10:17:06 2026 -0500
[FFI] Refcount ordering, Array copy-on-write mutators, Rust API surface
cleanup (#943)
The strong-count decrement in `dec_ref` used `Relaxed`, so the Acquire
fence before the deleter had no Release to pair with: writes made by
another owner before it dropped its reference were not guaranteed to be
visible to the thread running the destructor. Use `Release`, matching
C++ `Object::DecRef` and `std::sync::Arc`.
In-place mutation (error context, backtrace append, `Array::push`,
structural mutate) was gated on a strong count of 1, which ignores weak
references: one could be upgraded concurrently and observe the write.
Gate it on a new crate-internal `is_unique`, which requires one strong
and no weak reference, as C++ `Object::unique()`, and loads with
`Acquire` to pair with the releasing decrements, as `Arc::get_mut`.
`strong_count` stays a `Relaxed` diagnostic.
C++ `Object::unique()` loaded the count `Relaxed` (MSVC: a volatile
read, not an acquire on arm64), yet copy-on-write paths write in place
after it. Load with acquire (`ReadAcquire64` on MSVC), pairing with
`DecRef`'s release.
Document on `Function::from_extern_c` that `safe_call`, `deleter` and
the handle state must be safe to use from any thread.
Implement `std::error::Error::source()` for `Error` from its cause
chain, so generic error tooling (anyhow, eyre, `Box<dyn Error>`) sees
the cause.
Make the documented Rust surface match the supported API before it is
locked, without changing what is reachable:
- hide the `macros` module and its `paste` re-export (the exported
macros live at the crate root);
- document the items a hand-written reference type relies on:
`type_traits::ContainerElement`, `object::is_instance_of` and
`ObjectArc::is_null`; add `unsafe_impl_ref_downcast!`, which implements
the in-place reference downcasts (`array[i]`, `downcast_ref`) for such a
type, so their traits and helpers stay hidden;
- state what `impl_try_from_any!`, `impl_arg_into_ref!` and
`impl_into_arg_holder_default!` enable, and give `cached_global_func!`
an example;
- add a casts section to `ObjectRefCore`: `downcast_ref` borrows
strictly, `downcast` takes the value and hands it back on a mismatch,
and `TryFrom` returns a `TypeError`;
- make `StructuralVarRemap` crate-private (engine internal).
Declare `rust-version = "1.95"` on the three crates, the oldest
toolchain that builds them (`core::hint::cold_path`), as TIRx does.
Rust `Array` gains public `push`, `insert`, `pop`, `truncate` and `set`,
all `&mut self`, with C++ `Array`'s copy-on-write contract stated once
on
the type: copied when shared, edited in place when this handle is the
only reference. A private `make_mut` (as C++ `Array::CopyOnWrite` and
`Rc::make_mut`) holds the copy logic previously inline in `push`. Index
errors match `List`: `insert`, `set` and `pop` return an `IndexError`.
Structural mutate has one default path: `StructuralMutatorObj`'s
`StructuralMutatorBase::default_mutate_raw` reaches the variable remap
through the installed vtable, as every other native prefix does, so a
base that reuses it is correct by construction. This removes the
`ORDINARY_ENGINE` fast path and the same-named inherent
`default_mutate_raw`; performance is confirmed separately.
`Error` updates in place go through one private `make_mut` (copy when
shared), and `Array::make_mut` keeps the reallocation in a helper.
Structural mutate errors are labeled "mutate" (the map adapter keeps
"map"), and the duplicate type-key lookup is removed. A test checks the
Rust `VisitErrorContextObj` binding against the field offsets C++
registers.
C++ `UpdateVisitErrorContext` skips the append when the context's last
frame is already `node` (callback, policy and default descent can report
the same frame), before copying the error, as Rust does.
---
include/tvm/ffi/extra/visit_error_context.h | 8 ++
include/tvm/ffi/object.h | 9 +-
rust/tvm-ffi-macros/Cargo.toml | 2 +
rust/tvm-ffi-sys/Cargo.toml | 2 +
rust/tvm-ffi/Cargo.toml | 2 +
rust/tvm-ffi/src/collections/array.rs | 208 +++++++++++++++++++++------
rust/tvm-ffi/src/error.rs | 88 ++++++------
rust/tvm-ffi/src/extra/structural_common.rs | 64 ++++++---
rust/tvm-ffi/src/extra/structural_map.rs | 8 +-
rust/tvm-ffi/src/extra/structural_mutate.rs | 184 ++++++------------------
rust/tvm-ffi/src/extra/structural_visit.rs | 7 +-
rust/tvm-ffi/src/function.rs | 10 +-
rust/tvm-ffi/src/lib.rs | 3 +-
rust/tvm-ffi/src/macros.rs | 164 ++++++++++++++++++---
rust/tvm-ffi/src/object.rs | 91 ++++++------
rust/tvm-ffi/src/type_traits.rs | 14 +-
rust/tvm-ffi/tests/test_array.rs | 27 ++++
rust/tvm-ffi/tests/test_error.rs | 1 +
rust/tvm-ffi/tests/test_structural_mutate.rs | 12 +-
19 files changed, 551 insertions(+), 353 deletions(-)
diff --git a/include/tvm/ffi/extra/visit_error_context.h
b/include/tvm/ffi/extra/visit_error_context.h
index 4d5bde91..27903443 100644
--- a/include/tvm/ffi/extra/visit_error_context.h
+++ b/include/tvm/ffi/extra/visit_error_context.h
@@ -290,6 +290,14 @@ inline VisitErrorContext MakeVisitErrorContext(const
ObjectRef& node) {
*/
TVM_FFI_COLD_CODE
inline void UpdateVisitErrorContext(Error* err, const ObjectRef& node) { //
NOLINT(*)
+ // Callback, policy and default descent can report the same frame.
+ const Object* current = static_cast<const Object*>(
+
static_cast<ErrorObj*>(detail::ObjectUnsafe::RawObjectPtrFromObjectRef(*err))->extra_context);
+ if (current != nullptr && current->IsInstance<VisitErrorContextObj>()) {
+ const Array<ObjectRef>& pattern =
+ static_cast<const
VisitErrorContextObj*>(current)->reverse_visit_pattern;
+ if (!pattern.empty() && pattern.back().same_as(node)) return;
+ }
// Copy-on-write at each level: the Error is replaced by a shallow copy when
shared, and its
// VisitErrorContext likewise. Each level is borrowed through its parent's
field, so an Error
// owned only by the unwinding frame is updated in place.
diff --git a/include/tvm/ffi/object.h b/include/tvm/ffi/object.h
index 7144b432..7cc3fa5e 100644
--- a/include/tvm/ffi/object.h
+++ b/include/tvm/ffi/object.h
@@ -187,12 +187,13 @@ class Object {
* copy-on-write in multi-threaded setting.
*/
bool unique() const {
+ // Acquire pairs with the releasing decrement: other owners' accesses
precede an in-place write.
#ifdef _MSC_VER
- return (reinterpret_cast<const volatile uint64_t*>(
- &header_.combined_ref_count))[0] == // NOLINT(*)
- kCombinedRefCountBothOne;
+ return static_cast<uint64_t>(
+ ReadAcquire64(reinterpret_cast<const volatile LONG64*>( //
NOLINT(*)
+ &header_.combined_ref_count))) == kCombinedRefCountBothOne;
#else
- return __atomic_load_n(&(header_.combined_ref_count), __ATOMIC_RELAXED) ==
+ return __atomic_load_n(&(header_.combined_ref_count), __ATOMIC_ACQUIRE) ==
kCombinedRefCountBothOne;
#endif
}
diff --git a/rust/tvm-ffi-macros/Cargo.toml b/rust/tvm-ffi-macros/Cargo.toml
index 6c646904..259ba5f3 100644
--- a/rust/tvm-ffi-macros/Cargo.toml
+++ b/rust/tvm-ffi-macros/Cargo.toml
@@ -22,6 +22,8 @@ description = "Procedural macro crate for tvm-ffi"
# Development placeholder; publish_crates.yml sets the release version from
the Git tag.
version = "0.1.0-alpha.0"
edition = "2021"
+# The oldest toolchain that builds the crates (`core::hint::cold_path`).
+rust-version = "1.95"
license = "Apache-2.0"
diff --git a/rust/tvm-ffi-sys/Cargo.toml b/rust/tvm-ffi-sys/Cargo.toml
index 04ad1744..1ae83a30 100644
--- a/rust/tvm-ffi-sys/Cargo.toml
+++ b/rust/tvm-ffi-sys/Cargo.toml
@@ -22,6 +22,8 @@ description = "Low-level sys crate for tvm-ffi"
# Development placeholder; publish_crates.yml sets the release version from
the Git tag.
version = "0.1.0-alpha.0"
edition = "2021"
+# The oldest toolchain that builds the crates (`core::hint::cold_path`).
+rust-version = "1.95"
license = "Apache-2.0"
diff --git a/rust/tvm-ffi/Cargo.toml b/rust/tvm-ffi/Cargo.toml
index 97616acb..e83b3fd6 100644
--- a/rust/tvm-ffi/Cargo.toml
+++ b/rust/tvm-ffi/Cargo.toml
@@ -22,6 +22,8 @@ description = "tvm-ffi rust support"
# Development placeholder; publish_crates.yml sets the release version from
the Git tag.
version = "0.1.0-alpha.0"
edition = "2021"
+# The oldest toolchain that builds the crates (`core::hint::cold_path`).
+rust-version = "1.95"
license = "Apache-2.0"
diff --git a/rust/tvm-ffi/src/collections/array.rs
b/rust/tvm-ffi/src/collections/array.rs
index 930458d6..7f824182 100644
--- a/rust/tvm-ffi/src/collections/array.rs
+++ b/rust/tvm-ffi/src/collections/array.rs
@@ -52,6 +52,13 @@ unsafe impl ObjectCoreWithExtraItems for ArrayObj {
}
}
+/// Move `value` into the uninitialized `cell`.
+unsafe fn write_cell<T: ContainerElement>(cell: *mut TVMFFIAny, value: T) {
+ let mut raw = TVMFFIAny::new();
+ T::container_move_to_any(value, &mut raw);
+ std::ptr::write(cell, raw);
+}
+
// Runs only for a Rust-allocated array; one allocated in C++ is released by
its
// own deleter.
impl Drop for ArrayObj {
@@ -64,26 +71,39 @@ impl Drop for ArrayObj {
}
}
-/// An immutable, reference-counted sequence of `T`, shared with C++ and
-/// Python as `ffi.Array`.
+/// A reference-counted sequence of `T` with value semantics, shared with C++
+/// and Python as `ffi.Array`.
///
-/// Cloning an `Array` is cheap: it shares the same elements. Use [`List`]
-/// for a sequence that changes in place.
+/// Cloning an `Array` is cheap: it shares the same elements. The mutators
+/// ([`push`], [`insert`], [`pop`], [`truncate`], [`set`]) are copy-on-write:
+/// they copy the elements when the array is shared, and write in place when
+/// this handle is the only reference, so other handles never see the change.
+/// Use [`List`] for a sequence whose changes all handles see.
///
/// [`List`]: crate::List
+/// [`push`]: Array::push
+/// [`insert`]: Array::insert
+/// [`pop`]: Array::pop
+/// [`truncate`]: Array::truncate
+/// [`set`]: Array::set
///
/// # Examples
///
/// ```
/// use tvm_ffi::Array;
///
-/// let array = Array::from_vec(vec![1_i64, 2, 3]);
+/// let mut array = Array::from_vec(vec![1_i64, 2, 3]);
/// assert_eq!(array.len(), 3);
/// assert_eq!(array.get(1)?, 2);
/// assert!(array.get(3).is_err());
///
+/// let snapshot = array.clone();
+/// array.push(4);
+/// assert_eq!(array.len(), 4);
+/// assert_eq!(snapshot.len(), 3);
+///
/// let doubled: Array<i64> = array.iter().map(|x| x * 2).collect();
-/// assert_eq!(doubled.iter().copied().collect::<Vec<_>>(), vec![2, 4, 6]);
+/// assert_eq!(doubled.iter().copied().collect::<Vec<_>>(), vec![2, 4, 6, 8]);
/// # Ok::<(), tvm_ffi::Error>(())
/// ```
#[repr(C)]
@@ -93,7 +113,8 @@ pub struct Array<T> {
_marker: PhantomData<T>,
}
-// SAFETY: an `Array` is immutable, so sharing it is as safe as sharing its
elements.
+// SAFETY: a shared `Array` is never mutated (mutators copy unless this handle
+// is unique), so sharing it is as safe as sharing its elements.
unsafe impl<T: Send + Sync> Send for Array<T> {}
unsafe impl<T: Send + Sync> Sync for Array<T> {}
@@ -226,48 +247,149 @@ impl<T: ContainerElement + Clone> Array<T> {
unsafe { Self::from_data(arc) }
}
- /// Append `value`, copy-on-write like C++ `Array::push_back`: in place
when
- /// this handle is the only reference and the buffer has room, otherwise
into
- /// a new buffer that grows geometrically when full.
- pub(crate) fn push(&mut self, value: T) {
+ /// Append `value` to the end.
+ ///
+ /// See also [`List::push`](crate::List::push).
+ pub fn push(&mut self, value: T) {
let size = self.len();
- let capacity = self.data.capacity as usize;
- let unique = ObjectArc::strong_count(&self.data) == 1;
- if !unique || size == capacity {
- let capacity = if size == capacity {
- (capacity * 2).max(size + 1)
+ let container = self.make_mut(size + 1);
+ // SAFETY: `make_mut` left room for one more cell past `size`.
+ unsafe { write_cell((container.data as *mut TVMFFIAny).add(size),
value) };
+ container.size += 1;
+ }
+
+ /// Insert `value` at `index`, shifting the elements after it.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `IndexError` if `index > len`.
+ ///
+ /// See also [`List::insert`](crate::List::insert).
+ pub fn insert(&mut self, index: usize, value: T) -> Result<(),
crate::Error> {
+ let size = self.len();
+ if index > size {
+ crate::bail!(crate::error::INDEX_ERROR, "Array insert index out of
bound");
+ }
+ let container = self.make_mut(size + 1);
+ // SAFETY: `make_mut` left room for one more cell; the tail moves up
by one.
+ unsafe {
+ let cell = (container.data as *mut TVMFFIAny).add(index);
+ std::ptr::copy(cell, cell.add(1), size - index);
+ write_cell(cell, value);
+ }
+ container.size += 1;
+ Ok(())
+ }
+
+ /// Remove and return the last element.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `IndexError` if the array is empty.
+ ///
+ /// See also [`List::pop`](crate::List::pop).
+ pub fn pop(&mut self) -> Result<T, crate::Error> {
+ let size = self.len();
+ if size == 0 {
+ crate::bail!(crate::error::INDEX_ERROR, "Array pop on an empty
array");
+ }
+ let container = self.make_mut(size);
+ container.size -= 1;
+ // SAFETY: the last cell is initialized, holds a `T`, and is no longer
+ // counted by `size`, so it is moved out exactly once.
+ unsafe {
+ let mut cell = std::ptr::read((container.data as *const
TVMFFIAny).add(size - 1));
+ debug_assert!(T::container_check_any_strict(&cell));
+ Ok(T::container_move_from_any_after_check(&mut cell))
+ }
+ }
+
+ /// Shorten the array to `len` elements, dropping the rest; a no-op when it
+ /// is already that short.
+ ///
+ /// See also [`List::truncate`](crate::List::truncate).
+ pub fn truncate(&mut self, len: usize) {
+ let size = self.len();
+ if len >= size {
+ return;
+ }
+ let container = self.make_mut(size);
+ // Shrink `size` first, so a panicking element drop leaks rather than
+ // double-frees the remaining cells.
+ container.size = len as i64;
+ let cells = container.data as *const TVMFFIAny;
+ for i in len..size {
+ // SAFETY: cells `len..size` are initialized and no longer counted.
+ drop(unsafe { Any::from_raw_ffi_any(*cells.add(i)) });
+ }
+ }
+
+ /// Replace the element at `index`.
+ ///
+ /// # Errors
+ ///
+ /// Returns an `IndexError` if `index >= len`.
+ ///
+ /// See also [`List::set`](crate::List::set).
+ pub fn set(&mut self, index: usize, value: T) -> Result<(), crate::Error> {
+ let size = self.len();
+ if index >= size {
+ crate::bail!(crate::error::INDEX_ERROR, "Array set index out of
bound");
+ }
+ let container = self.make_mut(size);
+ // SAFETY: the cell at `index` is initialized; its old value is dropped
+ // after the new one is written.
+ unsafe {
+ let cell = (container.data as *mut TVMFFIAny).add(index);
+ let old = Any::from_raw_ffi_any(std::ptr::read(cell));
+ write_cell(cell, value);
+ drop(old);
+ }
+ Ok(())
+ }
+
+ /// Make this handle the only reference to its cells, with room for at
least
+ /// `capacity` of them, and return the container to edit in place.
+ fn make_mut(&mut self, capacity: usize) -> &mut ArrayObj {
+ // As C++ `Array::CopyOnWrite(reserve)`.
+ let unique = ObjectArc::is_unique(&self.data);
+ let current = self.data.capacity as usize;
+ if !unique || capacity > current {
+ let capacity = if capacity > current {
+ (current * 2).max(capacity)
} else {
- capacity
+ current
};
- let mut grown = Self::new_with_capacity(Vec::new(), capacity);
- let source = self.as_container().data as *const TVMFFIAny;
- // SAFETY: `grown` is fresh with room for `size` cells. A unique
source
- // moves its cells and forgets them; a shared one keeps its cells,
so each
- // copy takes its own reference.
- unsafe {
- let target = ObjectArc::as_mut_ptr(&mut grown.data);
- let cells = (*target).data as *mut TVMFFIAny;
- if unique {
- std::ptr::copy_nonoverlapping(source, cells, size);
- (*ObjectArc::as_mut_ptr(&mut self.data)).size = 0;
- } else {
- for i in 0..size {
- let copy =
Any::from(AnyView::from_raw_ffi_any(*source.add(i)));
- std::ptr::write(cells.add(i),
Any::into_raw_ffi_any(copy));
- }
- }
- (*target).size = size as i64;
- }
- *self = grown;
+ self.copy_to_new_buffer(capacity, unique);
}
- // SAFETY: this handle is now the only reference, with room for one
more cell.
+ // SAFETY: this handle is now the only reference, so no one else
observes
+ // the container while it is mutably borrowed.
+ unsafe { &mut *ObjectArc::as_mut_ptr(&mut self.data) }
+ }
+
+ /// Replace `self` with a unique array of `capacity` cells holding the same
+ /// elements: moved out of a `unique` source, otherwise each re-referenced.
+ fn copy_to_new_buffer(&mut self, capacity: usize, unique: bool) {
+ let size = self.len();
+ let mut grown = Self::new_with_capacity(Vec::new(), capacity);
+ let source = self.as_container().data as *const TVMFFIAny;
+ // SAFETY: `grown` is fresh with room for `size` cells. A unique source
+ // forgets the cells it moved; a shared one keeps its own references.
unsafe {
- let container = ObjectArc::as_mut_ptr(&mut self.data);
- let mut cell = TVMFFIAny::new();
- T::container_move_to_any(value, &mut cell);
- std::ptr::write(((*container).data as *mut TVMFFIAny).add(size),
cell);
- (*container).size += 1;
+ let target = ObjectArc::as_mut_ptr(&mut grown.data);
+ let cells = (*target).data as *mut TVMFFIAny;
+ if unique {
+ std::ptr::copy_nonoverlapping(source, cells, size);
+ (*ObjectArc::as_mut_ptr(&mut self.data)).size = 0;
+ } else {
+ for i in 0..size {
+ let copy =
Any::from(AnyView::from_raw_ffi_any(*source.add(i)));
+ std::ptr::write(cells.add(i), Any::into_raw_ffi_any(copy));
+ }
+ }
+ (*target).size = size as i64;
}
+ *self = grown;
}
/// Return the number of elements.
diff --git a/rust/tvm-ffi/src/error.rs b/rust/tvm-ffi/src/error.rs
index 1cdf41be..5dd42ef8 100644
--- a/rust/tvm-ffi/src/error.rs
+++ b/rust/tvm-ffi/src/error.rs
@@ -108,8 +108,7 @@ pub struct ErrorObj {
/// assert_eq!(error.message(), "not a digit: 'x'");
/// ```
///
-/// An `Error` is `Send + Sync`, so a `Result` can cross threads; its attached
-/// context is treated as immutable diagnostic data.
+/// An `Error` is `Send + Sync`, so a `Result` can cross threads.
#[repr(C)]
#[derive(Clone, ObjectRef)]
#[custom_debug]
@@ -117,8 +116,7 @@ pub struct Error {
data: ObjectArc<ErrorObj>,
}
-// SAFETY: an `Error` is immutable once built; `extra_context` is diagnostic
data.
-// Deliberately thread-safe so a `Result` can cross threads.
+// SAFETY: a shared Error is never mutated: updates copy unless this handle is
unique.
unsafe impl Send for Error {}
unsafe impl Sync for Error {}
@@ -179,13 +177,13 @@ impl Error {
///
/// * `kind` - The kind of the error, such as [`VALUE_ERROR`].
/// * `message` - The message of the error.
- /// * `traceback` - The backtrace of the error, which may be empty.
+ /// * `backtrace` - The backtrace of the error, which may be empty.
///
/// # Returns
///
/// The error.
- pub fn new(kind: ErrorKind<'_>, message: &str, traceback: &str) -> Self {
- Self::new_with_cause_and_extra_context(kind, message, traceback, None,
None)
+ pub fn new(kind: ErrorKind<'_>, message: &str, backtrace: &str) -> Self {
+ Self::new_with_cause_and_extra_context(kind, message, backtrace, None,
None)
}
/// Create an error retaining its cause and any application-specific
context.
@@ -194,7 +192,7 @@ impl Error {
///
/// * `kind` - The kind of the error.
/// * `message` - The message of the error.
- /// * `traceback` - The backtrace of the error, which may be empty.
+ /// * `backtrace` - The backtrace of the error, which may be empty.
/// * `cause_chain` - The error that caused this one, if any.
/// * `extra_context` - An object of application-specific context, if any.
///
@@ -204,19 +202,19 @@ impl Error {
pub fn new_with_cause_and_extra_context(
kind: ErrorKind<'_>,
message: &str,
- traceback: &str,
+ backtrace: &str,
cause_chain: Option<&Error>,
extra_context: Option<&object::ObjectRef>,
) -> Self {
unsafe {
let kind_data = TVMFFIByteArray::from_str(kind.as_str());
let message_data = TVMFFIByteArray::from_str(message);
- let traceback_data = TVMFFIByteArray::from_str(traceback);
+ let backtrace_data = TVMFFIByteArray::from_str(backtrace);
let mut error_handle: TVMFFIObjectHandle = std::ptr::null_mut();
let ret = TVMFFIErrorCreateWithCauseAndExtraContext(
&kind_data,
&message_data,
- &traceback_data,
+ &backtrace_data,
cause_chain.map_or(std::ptr::null_mut(), |cause| {
ObjectArc::as_ptr(&cause.data) as TVMFFIObjectHandle
}),
@@ -323,10 +321,21 @@ impl Error {
Some(unsafe { &*(handle as *const _ as *const object::ObjectRef) })
}
- /// The `extra_context` slot, to update in place. A shared error is first
- /// replaced by a shallow copy, so the update reaches no other holder.
+ /// The `extra_context` slot, to update in place.
pub(crate) fn extra_context_mut(&mut self) -> &mut
Option<object::ObjectRef> {
- if ObjectArc::strong_count(&self.data) != 1 {
+ let error = self.make_mut();
+ // SAFETY: this handle is the only reference, and the slot owns a
nullable
+ // object handle, which is exactly an `Option<ObjectRef>`.
+ unsafe {
+ &mut *std::ptr::addr_of_mut!((*error).cell.extra_context)
+ .cast::<Option<object::ObjectRef>>()
+ }
+ }
+
+ /// Make this handle the only reference to its error, copying a shared
one, as
+ /// `Arc::make_mut`.
+ fn make_mut(&mut self) -> *mut ErrorObj {
+ if !ObjectArc::is_unique(&self.data) {
*self = Self::new_with_cause_and_extra_context(
self.kind(),
self.message(),
@@ -335,13 +344,7 @@ impl Error {
self.extra_context(),
);
}
- // SAFETY: this handle is the only reference, and the slot owns a
nullable
- // object handle, which is exactly an `Option<ObjectRef>`.
- unsafe {
- let error = ObjectArc::as_mut_ptr(&mut self.data);
- &mut *std::ptr::addr_of_mut!((*error).cell.extra_context)
- .cast::<Option<object::ObjectRef>>()
- }
+ ObjectArc::as_mut_ptr(&mut self.data)
}
/// Get the traceback of the error in the order of most recent call last,
as
@@ -375,33 +378,18 @@ impl Error {
/// # Returns
///
/// The error with the appended backtrace.
- pub fn with_appended_backtrace(this: Self, backtrace: &str) -> Self {
- if ObjectArc::strong_count(&this.data) == 1 {
- // this is the only reference to the error
- // we can safely mutate the error
- unsafe {
- let backtrace_data = TVMFFIByteArray::from_str(backtrace);
- (this.data.cell.update_backtrace)(
- ObjectArc::as_ptr(&this.data) as *mut ErrorObj as *mut
c_void,
- &backtrace_data,
- kTVMFFIBacktraceUpdateModeAppend as i32,
- );
- this
- }
- } else {
- // we need to create a new error because there is more than one
unique reference
- // to the error
- let mut new_backtrace = String::new();
- new_backtrace.push_str(this.backtrace());
- new_backtrace.push_str(backtrace);
- Self::new_with_cause_and_extra_context(
- this.kind(),
- this.message(),
- &new_backtrace,
- this.cause_chain(),
- this.extra_context(),
- )
+ pub fn with_appended_backtrace(mut this: Self, backtrace: &str) -> Self {
+ let error = this.make_mut();
+ // SAFETY: `make_mut` made this handle the only reference to `error`.
+ unsafe {
+ let backtrace_data = TVMFFIByteArray::from_str(backtrace);
+ ((*error).cell.update_backtrace)(
+ error.cast::<c_void>(),
+ &backtrace_data,
+ kTVMFFIBacktraceUpdateModeAppend as i32,
+ );
}
+ this
}
}
@@ -423,4 +411,8 @@ impl std::fmt::Debug for Error {
}
}
-impl std::error::Error for Error {}
+impl std::error::Error for Error {
+ fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
+ self.cause_chain().map(|cause| cause as _)
+ }
+}
diff --git a/rust/tvm-ffi/src/extra/structural_common.rs
b/rust/tvm-ffi/src/extra/structural_common.rs
index 0c1ed75f..8c931e92 100644
--- a/rust/tvm-ffi/src/extra/structural_common.rs
+++ b/rust/tvm-ffi/src/extra/structural_common.rs
@@ -22,9 +22,7 @@ use crate::collections::array::Array;
use crate::derive::Object;
use crate::error::{Error, Result, RUNTIME_ERROR};
use crate::object::{self, Object, ObjectArc, ObjectRef, ObjectRefCore};
-use crate::tvm_ffi_sys::{
- TVMFFIAny, TVMFFIAnyDataUnion, TVMFFIGetTypeInfo, TVMFFIObject,
TVMFFITypeIndex,
-};
+use crate::tvm_ffi_sys::{TVMFFIAny, TVMFFIAnyDataUnion, TVMFFIObject,
TVMFFITypeIndex};
//
-----------------------------------------------------------------------------
// Shared outlined errors
@@ -80,24 +78,6 @@ pub(super) unsafe fn structural_error_from_raw(
}
}
-/// Return the registered type key for `type_index`.
-///
-/// When the runtime returns no type information, return `<type_index N>` for
diagnostics.
-///
-/// # Arguments
-///
-/// * `type_index` - Runtime type index whose registered type key is requested.
-pub(crate) fn type_key_of(type_index: i32) -> String {
- unsafe {
- let info = TVMFFIGetTypeInfo(type_index);
- if info.is_null() {
- format!("<type_index {type_index}>")
- } else {
- (*info).type_key.as_str().to_string()
- }
- }
-}
-
// Perf note: cold marks these error-composition paths as uncommon; inlining
// within error paths is compiler-selected. Use explicit inline(never) at
boundaries
// where scratch must stay outside recursive callers.
@@ -168,7 +148,7 @@ pub(crate) fn with_visit_error_context(mut error: Error,
raw: TVMFFIAny) -> Erro
.cast::<VisitErrorContextObj>(),
)
};
- if ObjectArc::strong_count(&context) != 1 {
+ if !ObjectArc::is_unique(&context) {
context = ObjectArc::new(VisitErrorContextObj {
base: Object::new(),
reverse_visit_pattern:
context.reverse_visit_pattern.clone(),
@@ -284,3 +264,43 @@ pub(crate) unsafe fn raw_ffi_any_into_result(raw:
TVMFFIAny) -> Result<Any> {
raw.data_union,
))
}
+
+#[cfg(test)]
+mod tests {
+ use super::VisitErrorContextObj;
+ use crate::object::ObjectCore;
+
+ // 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}`"
+ );
+ }
+ }
+}
diff --git a/rust/tvm-ffi/src/extra/structural_map.rs
b/rust/tvm-ffi/src/extra/structural_map.rs
index 81313a9e..cf25e3f6 100644
--- a/rust/tvm-ffi/src/extra/structural_map.rs
+++ b/rust/tvm-ffi/src/extra/structural_map.rs
@@ -124,7 +124,7 @@ where
return mutator.default_mutate(self, value).map(Into::into);
};
let mapped: Any = callback_result
- .map_err(|error| with_mutation_error_context(error, raw))?
+ .map_err(|error| with_mutation_error_context(error, raw,
"map"))?
.into();
if is_unchanged(&mapped) || raw_ffi_any_same_as(raw,
*mapped.as_raw_ffi_any()) {
// Drop temporary ownership before default descent checks
uniqueness.
@@ -150,9 +150,9 @@ where
};
match self.dispatch.dispatch_tuple((current, kind)) {
Some(Ok(result)) if result.is_unchanged() => Ok(mapped),
- Some(result) => result
- .map(Any::from)
- .map_err(|error| with_mutation_error_context(error,
*current.as_raw_ffi_any())),
+ Some(result) => result.map(Any::from).map_err(|error| {
+ with_mutation_error_context(error,
*current.as_raw_ffi_any(), "map")
+ }),
None => Ok(mapped),
}
}
diff --git a/rust/tvm-ffi/src/extra/structural_mutate.rs
b/rust/tvm-ffi/src/extra/structural_mutate.rs
index 224dfa8c..75aeec40 100644
--- a/rust/tvm-ffi/src/extra/structural_mutate.rs
+++ b/rust/tvm-ffi/src/extra/structural_mutate.rs
@@ -75,7 +75,7 @@ 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, type_key_of,
with_structural_error_context,
+ result_into_raw_ffi_any, runtime_error, with_structural_error_context,
with_structural_field_error_context, with_visit_error_context,
};
use super::structural_visit::{
@@ -193,10 +193,13 @@ unsafe impl StructuralMutatorBase for
StructuralMutatorObj {
#[inline(always)]
unsafe fn default_mutate_raw(runtime: *mut Self, value: MutationView<'_>)
-> TVMFFIAny {
- // This implementation is entered only by the ordinary Rust engine, so
- // reflection can access its remap directly. Prefix-only callers use
- // `default_mutate_raw`, which dispatches remaps through the native
vtable.
- Self::default_mutate_impl::<true>(runtime, value)
+ // Prefix-only: remaps go through the vtable, so any base may reuse
this.
+ let raw = value.value;
+ // Registered hooks own their remapping policy; reflection handles its
own.
+ 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)
}
}
@@ -231,19 +234,18 @@ impl Default for StructuralMutatorObj {
/// Each pointer key owns one strong reference to its original object,
preventing
/// address reuse until removal. Values are owning replacements, including
/// [`Unchanged`] markers. Replacing a value does not acquire another key
reference;
-/// lookup clones the replacement, and clearing or dropping releases every
owner.
+/// lookup clones the replacement, and dropping releases every owner.
// Perf note: Store only the replacement Any; raw identity keys avoid a second
// Any payload, but still own one retained reference per distinct key.
Replacing
-// a value does not retain the key again; clear/Drop release each key exactly
once.
-#[doc(hidden)]
+// a value does not retain the key again; Drop releases each key exactly once.
#[derive(Default)]
-pub struct StructuralVarRemap {
+pub(crate) struct StructuralVarRemap {
entries: HashMap<NonNull<TVMFFIObject>, Any>,
}
impl StructuralVarRemap {
/// Look up an identity replacement previously stored for `var`.
- pub fn get(&self, var: AnyView<'_>) -> Result<Option<Any>> {
+ pub(crate) fn get(&self, var: AnyView<'_>) -> Result<Option<Any>> {
let key = object_identity_key(*var.as_raw_ffi_any())?;
Ok(self.entries.get(&key).cloned())
}
@@ -252,7 +254,7 @@ impl StructuralVarRemap {
///
/// A new key acquires one strong reference; replacing its value preserves
/// that reference. The replacement is cloned before modifying the table.
- pub fn set(&mut self, var: AnyView<'_>, mutated_value: &Any) -> Result<()>
{
+ pub(crate) fn set(&mut self, var: AnyView<'_>, mutated_value: &Any) ->
Result<()> {
let key = object_identity_key(*var.as_raw_ffi_any())?;
let value = mutated_value.clone();
match self.entries.entry(key) {
@@ -268,27 +270,8 @@ impl StructuralVarRemap {
}
Ok(())
}
-
- /// Remove every substitution, releasing each key and replacement owner.
- pub fn clear(&mut self) {
- let empty = HashMap::with_hasher(self.entries.hasher().clone());
- let mut entries = std::mem::replace(&mut self.entries, empty);
- // Release all key references before dropping values. Object deleters
- // use the non-unwinding C ABI, so this pass cannot leave keys
unreleased.
- // Subsequent cleanup only drops pointer bits, never dereferences them.
- for key in entries.keys() {
- unsafe { object::unsafe_::dec_ref(key.as_ptr()) };
- }
- entries.clear();
- // Reuse capacity without replacing any entries created during cleanup.
- if self.entries.is_empty() {
- self.entries = entries;
- }
- }
}
-// Perf note: Terminal destruction needs no replacement table or reusable
capacity.
-// Keep it separate from clear(), which leaves a valid remap ready for later
use.
impl Drop for StructuralVarRemap {
fn drop(&mut self) {
// Release keys first: their C ABI deleters cannot unwind. Field Drop
@@ -968,7 +951,7 @@ fn missing_shallow_copy_error(type_index: i32, attribute:
&str) -> Error {
TYPE_ERROR,
&format!(
"type `{}` cannot use reflected structural mutation because it
does not define `{attribute}`",
- type_key_of(type_index)
+ crate::object::type_key_of_index(type_index)
),
"",
)
@@ -1008,7 +991,7 @@ fn missing_field_setter_error(name: &str) -> Error {
#[inline(never)]
fn invalid_field_offset_error(name: &str) -> Error {
runtime_error(&format!(
- "native structural map: reflected field `{name}` has an invalid offset"
+ "native structural mutate: reflected field `{name}` has an invalid
offset"
))
}
@@ -1036,7 +1019,7 @@ fn var_remap_key_error() -> Error {
#[inline(never)]
fn unregistered_type_error(type_index: i32) -> Error {
runtime_error(&format!(
- "native structural map: unregistered type index {type_index}"
+ "native structural mutate: unregistered type index {type_index}"
))
}
@@ -1048,14 +1031,17 @@ fn wrong_mutation_dispatcher() -> ! {
}
#[cold]
-pub(super) fn with_mutation_error_context(error: Error, raw: TVMFFIAny) ->
Error {
+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),
- "map",
- &format!("object `{}`", type_key_of(raw.type_index)),
+ operation,
+ &format!(
+ "object `{}`",
+ crate::object::type_key_of_index(raw.type_index)
+ ),
)
}
}
@@ -1148,7 +1134,7 @@ fn object_identity_key(raw: TVMFFIAny) ->
Result<NonNull<TVMFFIObject>> {
let pointer = unsafe { raw.data_union.v_obj };
NonNull::new(pointer).ok_or_else(|| {
core::hint::cold_path();
- runtime_error("native structural map: identity object has a null
pointer")
+ runtime_error("native structural mutate: identity object has a null
pointer")
})
}
@@ -1168,7 +1154,7 @@ fn object_is_unique(raw: TVMFFIAny) -> bool {
return false;
}
let pointer = unsafe { raw.data_union.v_obj };
- !pointer.is_null() && unsafe { object::unsafe_::strong_count(pointer) == 1
}
+ !pointer.is_null() && unsafe { object::unsafe_::is_unique(pointer) }
}
/// Acquire an owning value from a valid borrowed carrier.
@@ -1187,7 +1173,7 @@ fn raw_ffi_any_to_owned_any(raw: TVMFFIAny) ->
Result<Any> {
let object = unsafe { raw.data_union.v_obj };
if object.is_null() {
return Err(runtime_error(
- "native structural map: object-backed value has a null
pointer",
+ "native structural mutate: object-backed value has a null
pointer",
));
}
unsafe { object::unsafe_::inc_ref(object) };
@@ -1334,7 +1320,7 @@ impl<Base: StructuralMutatorBase>
StructuralMutateEngine<Base> {
Some(result) => {
let result = result.map(Any::from);
result_into_raw_ffi_any(if CALLBACK_CONTEXT {
- result.map_err(|error| with_mutation_error_context(error,
raw))
+ result.map_err(|error| with_mutation_error_context(error,
raw, "mutate"))
} else {
result
})
@@ -1532,7 +1518,7 @@ impl StructuralMutatorObj {
};
result_into_raw_ffi_any(
raw_ffi_any_into_result(result)
- .map_err(|error| with_mutation_error_context(error, raw)),
+ .map_err(|error| with_mutation_error_context(error, raw,
"mutate")),
)
}
@@ -1563,7 +1549,7 @@ impl StructuralMutatorObj {
let output_object = unsafe { output_raw.data_union.v_obj.cast::<u8>()
};
if output_object.is_null() {
return Err(runtime_error(
- "native structural map: shallow copy has a null object
pointer",
+ "native structural mutate: shallow copy has a null object
pointer",
));
}
@@ -1615,7 +1601,7 @@ impl StructuralMutatorObj {
) -> Result<()> {
let Some(getter) = field.getter else {
return Err(missing_field_getter_error(
- "structural map",
+ "structural mutate",
field.name.as_str(),
));
};
@@ -1633,7 +1619,7 @@ impl StructuralMutatorObj {
if getter(source_address, Any::as_raw_ffi_any_mut(&mut child)) != 0 {
return Err(with_structural_field_error_context(
Error::from_raised(),
- "map",
+ "mutate",
field.name.as_str(),
));
}
@@ -1644,13 +1630,15 @@ impl StructuralMutatorObj {
let mapped = Self::with_def_region_kind(runtime, child_region, |_| {
Self::call_mutator(runtime, child_raw, InplaceMode::Disallow)
})
- .map_err(|error| with_structural_field_error_context(error, "map",
field.name.as_str()))?;
+ .map_err(|error| {
+ with_structural_field_error_context(error, "mutate",
field.name.as_str())
+ })?;
if is_unchanged(&mapped) || raw_ffi_any_same_as(child_raw,
*mapped.as_raw_ffi_any()) {
return Ok(());
}
call_field_setter(field, source_address,
mapped.as_raw_ffi_any()).map_err(|error| {
- with_structural_field_error_context(error, "map",
field.name.as_str())
+ with_structural_field_error_context(error, "mutate",
field.name.as_str())
})?;
*field_changed = true;
Ok(())
@@ -1777,7 +1765,9 @@ impl StructuralMutatorObj {
Err(invalid_mutation_hook_error())
};
// Preserve successful Error-valued returns until the public
conversion.
- result_into_raw_ffi_any(result.map_err(|error|
with_mutation_error_context(error, raw)))
+ result_into_raw_ffi_any(
+ result.map_err(|error| with_mutation_error_context(error, raw,
"mutate")),
+ )
}
/// Borrow a live runtime mutator as an object-valued ABI argument.
@@ -1829,55 +1819,6 @@ impl StructuralMutatorObj {
}
}
- /// Default descent through native hooks or reflected fields, preserving
the
- /// capability's reuse permission. Return one owning
replacement/Unchanged/Error.
- ///
- /// # Safety
- /// `runtime` must be a live initialized mutator prefix with a matching
installed
- /// vtable. Publish the current exclusive dispatcher reborrow first. The
runtime
- /// and input remain borrowed for this synchronous call; hooks obey
- /// [`StructuralMutatorBase`]'s non-escaping contract.
- // Operate in the raw path to optimize stack frame cost. Each returned raw
- // carrier transfers one owning result; fallible/destructor-bearing code
- // imports it into Any before performing work that may unwind.
- #[inline(always)]
- pub unsafe fn default_mutate_raw(runtime: *mut Self, value:
MutationView<'_>) -> TVMFFIAny {
- Self::default_mutate_impl::<false>(runtime, value)
- }
-
- // ORDINARY_ENGINE is true only for the StructuralMutatorBase
implementation
- // above, whose runtime has the exact ordinary Rust engine layout.
Arbitrary
- // native prefixes must keep using the installed remap callbacks.
- #[inline(always)]
- unsafe fn default_mutate_impl<const ORDINARY_ENGINE: bool>(
- runtime: *mut Self,
- value: MutationView<'_>,
- ) -> TVMFFIAny {
- let raw = value.value;
- let mode = value.mode;
- // Registered hooks own their remapping policy; reflection handles its
own.
- if let Some(attr) = Self::structural_mutate_hook(raw, mode) {
- return Self::call_structural_mutate_hook_raw(runtime, raw, attr);
- }
- unsafe {
- if ORDINARY_ENGINE {
- Self::ordinary_mutate_without_hook_raw(
- runtime,
- raw.type_index,
- raw.small_str_len,
- raw.data_union,
- )
- } else {
- Self::mutate_without_hook_raw(
- runtime,
- raw.type_index,
- raw.small_str_len,
- raw.data_union,
- )
- }
- }
- }
-
/// 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
@@ -1896,28 +1837,6 @@ impl StructuralMutatorObj {
type_index: i32,
small_str_len: u32,
data_union: TVMFFIAnyDataUnion,
- ) -> TVMFFIAny {
- Self::mutate_without_hook_impl::<false>(runtime, type_index,
small_str_len, data_union)
- }
-
- // Keep the ordinary fallback in this crate, like the native-prefix
fallback;
- // it does not depend on the caller's dispatcher monomorphization.
- #[inline(never)]
- unsafe extern "C-unwind" fn ordinary_mutate_without_hook_raw(
- runtime: StructuralMutatorHandle,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
- ) -> TVMFFIAny {
- Self::mutate_without_hook_impl::<true>(runtime, type_index,
small_str_len, data_union)
- }
-
- #[inline(always)]
- unsafe fn mutate_without_hook_impl<const ORDINARY_ENGINE: bool>(
- runtime: StructuralMutatorHandle,
- type_index: i32,
- small_str_len: u32,
- data_union: TVMFFIAnyDataUnion,
) -> 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.
@@ -1929,8 +1848,8 @@ impl StructuralMutatorObj {
data_union,
};
result_into_raw_ffi_any(
- Self::mutate_without_hook::<ORDINARY_ENGINE>(runtime, raw,
def_region_kind)
- .map_err(|error| with_mutation_error_context(error, raw)),
+ Self::mutate_without_hook(runtime, raw, def_region_kind)
+ .map_err(|error| with_mutation_error_context(error, raw,
"mutate")),
)
}
@@ -1968,7 +1887,7 @@ impl StructuralMutatorObj {
/// policy and owning reflected results. Internal callers guarantee valid
inputs;
/// normal Rust owners clean up partial work if reflection fails or
unwinds.
#[inline(always)]
- fn mutate_without_hook<const ORDINARY_ENGINE: bool>(
+ fn mutate_without_hook(
runtime: StructuralMutatorHandle,
raw: TVMFFIAny,
def_region_kind: DefRegionKind,
@@ -1988,18 +1907,9 @@ impl StructuralMutatorObj {
let is_free_var = kind ==
Some(TVMFFISEqHashKind::kTVMFFISEqHashKindFreeVar as i32);
let is_dag_node = kind ==
Some(TVMFFISEqHashKind::kTVMFFISEqHashKindDAGNode as i32);
if is_free_var || is_dag_node {
- let mutated = unsafe {
- if ORDINARY_ENGINE {
-
StructuralMutateEngine::<Self>::runtime_var_remap_get(runtime, raw)?
- } else {
- Self::var_remap_get(runtime, raw)?
- }
- };
- if let Some(mutated) = mutated {
- // The ABI uses None for a miss; an Unchanged marker is a
cached result.
- if mutated.type_index() != TVMFFITypeIndex::kTVMFFINone as i32
{
- return Ok(mutated);
- }
+ // The ABI uses None for a miss; an Unchanged marker is a cached
result.
+ if let Some(mutated) = unsafe { Self::var_remap_get(runtime, raw)?
} {
+ return Ok(mutated);
}
}
// A variable use with no binding retains its identity without
visiting fields.
@@ -2014,13 +1924,7 @@ impl StructuralMutatorObj {
{
// Keep markers for DAG nodes and pattern definitions. An
unchanged simple
// definition needs no binding: subsequent uses retain the
original on a miss.
- unsafe {
- if ORDINARY_ENGINE {
-
StructuralMutateEngine::<Self>::runtime_var_remap_set(runtime, raw, &result)?;
- } else {
- Self::var_remap_set(runtime, raw, &result)?;
- }
- }
+ unsafe { Self::var_remap_set(runtime, raw, &result)? };
}
Ok(result)
}
diff --git a/rust/tvm-ffi/src/extra/structural_visit.rs
b/rust/tvm-ffi/src/extra/structural_visit.rs
index a27c9158..7ca70d05 100644
--- a/rust/tvm-ffi/src/extra/structural_visit.rs
+++ b/rust/tvm-ffi/src/extra/structural_visit.rs
@@ -95,7 +95,7 @@ use crate::tvm_ffi_sys::{
};
use super::structural_common::{
- missing_field_getter_error, runtime_error, type_key_of,
with_structural_error_context,
+ missing_field_getter_error, runtime_error, with_structural_error_context,
with_structural_field_error_context, with_visit_error_context,
};
@@ -562,7 +562,10 @@ fn annotate_visit_error(
with_structural_error_context(
error,
"visit",
- &format!("object `{}`", type_key_of(value.type_index)),
+ &format!(
+ "object `{}`",
+ crate::object::type_key_of_index(value.type_index)
+ ),
)
}
diff --git a/rust/tvm-ffi/src/function.rs b/rust/tvm-ffi/src/function.rs
index a71b1981..520fe300 100644
--- a/rust/tvm-ffi/src/function.rs
+++ b/rust/tvm-ffi/src/function.rs
@@ -72,7 +72,8 @@ pub struct Function {
data: ObjectArc<FunctionObj>,
}
-// SAFETY: a `Function` is immutable, and every Rust callback it wraps is
`Send + Sync`.
+// SAFETY: a `Function` is immutable, every Rust callback it wraps is `Send +
Sync`,
+// and `from_extern_c` requires the same of foreign callbacks.
unsafe impl Send for Function {}
unsafe impl Sync for Function {}
@@ -467,11 +468,12 @@ impl Function {
Self::from_packed(closure)
}
+ /// Wrap a C callback as a Function.
+ ///
/// # Safety
///
- /// `handle` must be a valid pointer (or null) that is compatible with
- /// `safe_call` and `deleter`. The caller must ensure the handle outlives
- /// the returned `Function` (or that `deleter` properly frees it).
+ /// `handle` (may be null) is the state `safe_call` receives; `deleter`
frees it
+ /// when the Function drops. All three must be safe to use from any thread.
///
/// # Errors
///
diff --git a/rust/tvm-ffi/src/lib.rs b/rust/tvm-ffi/src/lib.rs
index f6163a62..76b19503 100644
--- a/rust/tvm-ffi/src/lib.rs
+++ b/rust/tvm-ffi/src/lib.rs
@@ -30,6 +30,7 @@ pub mod extra;
pub mod function;
#[doc(hidden)]
pub mod function_internal;
+#[doc(hidden)]
pub mod macros;
#[doc(hidden)]
pub mod match_any_internal;
@@ -59,8 +60,6 @@ pub use crate::error::{
pub use crate::extra::dispatch::{Dispatcher, IntoDispatcher};
pub use crate::extra::module::Module;
pub use crate::extra::structural_map::structural_map;
-#[doc(hidden)]
-pub use crate::extra::structural_mutate::StructuralVarRemap;
pub use crate::extra::structural_mutate::{
structural_mutate, InplaceMode, MutationView, StructuralMutateEngine,
StructuralMutatorBase,
StructuralMutatorObj, StructuralMutatorView,
diff --git a/rust/tvm-ffi/src/macros.rs b/rust/tvm-ffi/src/macros.rs
index 9e1d4839..47a43e81 100644
--- a/rust/tvm-ffi/src/macros.rs
+++ b/rust/tvm-ffi/src/macros.rs
@@ -16,7 +16,8 @@
* specific language governing permissions and limitations
* under the License.
*/
-// rexport paste under macro namespace so downstream do not need to specify dep
+// Re-export paste for the export macros, so downstream crates need not depend
on it.
+#[doc(hidden)]
pub use paste;
// ----------------------------------------------------------------------------
// Macros for error handling
@@ -41,12 +42,24 @@ macro_rules! function_name {
}};
}
-/// Resolves a registered global [`Function`](crate::function::Function) by
name
-/// and caches it for the lifetime of the process (lock-free after the first
-/// lookup). Each call site gets its own cache and the macro evaluates to a
-/// `&'static Function`. Panics — naming the function — if it is not
registered.
+/// Look up a registered global [`Function`](crate::Function) by name once, and
+/// keep it for the process: a `&'static Function`.
+///
+/// Each call site has its own cache, which later calls read without a lock.
+/// Use it for a function that must be registered, such as a built-in one; it
+/// panics, naming the function, if the function is not. See also
+/// [`Function::get_global`](crate::Function::get_global), which returns an
+/// error instead.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::{cached_global_func, Array};
///
-/// Usage: `cached_global_func!("ffi.Map").call_packed(args)`
+/// let array = cached_global_func!("ffi.Array").call_tuple((1_i64, 2_i64))?;
+/// assert_eq!(Array::<i64>::try_from(array)?.len(), 2);
+/// # Ok::<(), tvm_ffi::Error>(())
+/// ```
#[macro_export]
macro_rules! cached_global_func {
($name:literal) => {{
@@ -172,6 +185,113 @@ macro_rules! unsafe_impl_object_upcast {
};
}
+/// Implement the in-place reference downcasts (`array[i]`,
`Any::downcast_ref`,
+/// [`ObjectRefCore::downcast_ref`](crate::ObjectRefCore::downcast_ref)) for
each
+/// listed reference type implemented by hand, as `#[derive(ObjectRef)]` does.
+///
+/// # Safety
+///
+/// Each type is `repr(transparent)` over one object pointer, and its
+/// [`AnyCompatible::check_any_strict`](crate::AnyCompatible::check_any_strict)
+/// accepts exactly the objects that are a valid value of it.
+///
+/// # Examples
+///
+/// ```
+/// use tvm_ffi::object::{ObjectArc, ObjectRef};
+/// use tvm_ffi::{def_object_ref, rs_object, unsafe_impl_ref_downcast, Object,
ObjectRefCore};
+/// # use tvm_ffi::{tvm_ffi_sys::TVMFFIAny, AnyCompatible};
+///
+/// #[rs_object("my_ext.MyPair", final)]
+/// pub struct MyPairObj {
+/// base: Object,
+/// pub first: i64,
+/// pub second: i64,
+/// }
+/// def_object_ref!(pub MyPair, ObjectRef, MyPairObj);
+///
+/// /// A `MyPair` whose `first` is positive.
+/// #[repr(transparent)]
+/// #[derive(Clone)]
+/// pub struct PositivePair(ObjectArc<MyPairObj>);
+/// # unsafe impl ObjectRefCore for PositivePair {
+/// # type ContainerType = MyPairObj;
+/// # fn data(this: &Self) -> &ObjectArc<MyPairObj> { &this.0 }
+/// # fn into_data(this: Self) -> ObjectArc<MyPairObj> { this.0 }
+/// # unsafe fn from_data(data: ObjectArc<MyPairObj>) -> Self { Self(data)
}
+/// # }
+/// # unsafe impl AnyCompatible for PositivePair {
+/// # fn type_str() -> String { "PositivePair".into() }
+/// # unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
+/// # MyPair::copy_to_any_view(std::mem::transmute::<&Self,
&MyPair>(src), data)
+/// # }
+/// # unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
+/// # MyPair::move_to_any(MyPair::from_data(src.0), data)
+/// # }
+/// # unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
+/// # MyPair::check_any_strict(data) &&
(*data.data_union.v_obj.cast::<MyPairObj>()).first > 0
+/// # }
+/// # unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
+/// #
Self(MyPair::into_data(MyPair::copy_from_any_view_after_check(data)))
+/// # }
+/// # unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
+/// # Self(MyPair::into_data(MyPair::move_from_any_after_check(data)))
+/// # }
+/// # unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self,
()> {
+/// # if Self::check_any_strict(data) {
Ok(Self::copy_from_any_view_after_check(data)) } else { Err(()) }
+/// # }
+/// # }
+/// // (`ObjectRefCore` and `AnyCompatible` for `PositivePair` are written by
hand.)
+/// unsafe_impl_ref_downcast!(PositivePair);
+///
+/// let pair: ObjectRef = MyPair::from(MyPairObj::new(Object::new(), 1,
2)).into();
+/// assert!(pair.downcast_ref::<PositivePair>().is_some());
+/// ```
+#[macro_export]
+macro_rules! unsafe_impl_ref_downcast {
+ ($($name:ty),+ $(,)?) => {
+ $(
+ // SAFETY: the macro declaration promises that `$name` is one
object
+ // pointer whose `check_any_strict` is its type check.
+ unsafe impl $crate::type_traits::RefFromAnyUnchecked for $name {
+ #[inline(always)]
+ unsafe fn ref_from_any_unchecked(
+ cell: &$crate::tvm_ffi_sys::TVMFFIAny,
+ ) -> &Self {
+ const {
+ assert!(
+ ::core::mem::size_of::<$name>()
+ == ::core::mem::size_of::<*mut
$crate::tvm_ffi_sys::TVMFFIObject>()
+ && ::core::mem::align_of::<$name>()
+ == ::core::mem::align_of::<*mut
$crate::tvm_ffi_sys::TVMFFIObject>(),
+ "a reference downcast target must be exactly one
object pointer"
+ )
+ };
+ unsafe { &*(::core::ptr::addr_of!(cell.data_union) as
*const Self) }
+ }
+ }
+
+ unsafe impl $crate::type_traits::DowncastRefFromAny for $name {
+ #[inline(always)]
+ unsafe fn downcast_ref_from_any(
+ cell: &$crate::tvm_ffi_sys::TVMFFIAny,
+ ) -> ::core::option::Option<&Self> {
+ unsafe {
$crate::type_traits::downcast_ref_from_any_in_place::<Self>(cell) }
+ }
+ }
+
+ unsafe impl $crate::type_traits::DowncastRefFromObject for $name {
+ #[inline(always)]
+ unsafe fn downcast_ref_from_object(
+ place: &*mut $crate::tvm_ffi_sys::TVMFFIObject,
+ ) -> ::core::option::Option<&Self> {
+ unsafe {
$crate::type_traits::downcast_ref_from_object_in_place::<Self>(place) }
+ }
+ }
+ )+
+ };
+}
+
/// Check the return code of the safe call
///
/// # Arguments
@@ -332,15 +452,15 @@ macro_rules! attach_context {
// Macros for any definitions
// ----------------------------------------------------------------------------
-// implements try from any for all integer types
/// Implement `TryFrom<AnyView>`, `TryFrom<Any>` and `TryFrom<&Any>` for each
listed
-/// [`AnyCompatible`](crate::type_traits::AnyCompatible) type, so it converts
out of
-/// FFI values.
-///
-/// `#[derive(ObjectRef)]` calls this. Call it yourself for a reference type
you
-/// implement by hand, such as a typed view over another reference, together
with
-/// [`impl_arg_into_ref!`](crate::impl_arg_into_ref) and
-/// [`impl_into_arg_holder_default!`](crate::impl_into_arg_holder_default).
+/// [`AnyCompatible`](crate::AnyCompatible) type, so it converts out of FFI
values
+/// by its `AnyCompatible` checks.
+///
+/// `#[derive(ObjectRef)]` calls it. A reference type implemented by hand, such
+/// as a typed view over another reference, calls it with
+/// [`impl_arg_into_ref!`](crate::impl_arg_into_ref),
+/// [`impl_into_arg_holder_default!`](crate::impl_into_arg_holder_default) and
+/// [`unsafe_impl_ref_downcast!`](crate::unsafe_impl_ref_downcast).
#[macro_export]
macro_rules! impl_try_from_any {
($($t:ty),* $(,)?) => {
@@ -417,11 +537,11 @@ macro_rules! impl_try_from_any_for_parametric {
};
}
-/// Implement `IntoArgHolder` for each listed type, so it can be passed to a
-/// [`Function`](crate::Function) call.
+/// Let each listed [`AnyCompatible`](crate::AnyCompatible) type, owned or
+/// borrowed, be an argument of a function from
+/// [`into_typed_fn!`](crate::into_typed_fn).
///
-/// `#[derive(ObjectRef)]` calls this. Call it yourself for a reference type
you
-/// implement by hand, such as a typed view over another reference, together
with
+/// A reference type implemented by hand calls it with
/// [`impl_try_from_any!`](crate::impl_try_from_any) and
/// [`impl_arg_into_ref!`](crate::impl_arg_into_ref).
#[macro_export]
@@ -444,11 +564,11 @@ macro_rules! impl_into_arg_holder_default {
};
}
-/// Implement `ArgIntoRef` for each listed type, so a typed function can take
it
-/// as an argument by reference.
+/// Let each listed [`AnyCompatible`](crate::AnyCompatible) type, owned or
+/// borrowed, be an element of the arguments of
+/// [`Function::call_tuple`](crate::Function::call_tuple).
///
-/// `#[derive(ObjectRef)]` calls this. Call it yourself for a reference type
you
-/// implement by hand, such as a typed view over another reference, together
with
+/// A reference type implemented by hand calls it with
/// [`impl_try_from_any!`](crate::impl_try_from_any) and
/// [`impl_into_arg_holder_default!`](crate::impl_into_arg_holder_default).
#[macro_export]
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index 36c0ff51..316b3243 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -154,19 +154,21 @@ pub unsafe trait ObjectCoreWithExtraItems: ObjectCore {
}
}
-/// A managed object reference: the category of types such as
[`ObjectRef`](struct@ObjectRef),
-/// `Array<T>` and every `#[derive(ObjectRef)]` type, with the operations every
-/// reference supports: identity ([`same_as`](Self::same_as)) and strict casts
-/// between reference types, which check the object's runtime type and never
-/// convert. [`downcast_ref`](Self::downcast_ref) borrows in place, and
-/// [`downcast`](Self::downcast) takes the reference, as std's
-/// `dyn Any::downcast_ref` and `Box<dyn Any>::downcast` do. Upcasts are
-/// `From`/`Into`, which `#[derive(ObjectRef)]` and
-/// [`unsafe_impl_object_upcast!`](crate::unsafe_impl_object_upcast) generate.
+/// A managed object reference, such as [`ObjectRef`](struct@ObjectRef),
`Array<T>`
+/// or a `#[derive(ObjectRef)]` type, with identity
([`same_as`](Self::same_as))
+/// and strict casts that check the runtime type and never convert; upcasts are
+/// `From`/`Into`.
///
-/// The derive implements it. `data`, `into_data` and `from_data` are the
-/// handle plumbing the FFI uses, called as associated functions; the trait is
-/// `unsafe` to implement because they move the reference's internals.
+/// # Casts
+///
+/// * [`downcast_ref`](Self::downcast_ref) borrows, strictly, as std's
+/// `dyn Any::downcast_ref` does.
+/// * [`downcast`](Self::downcast) takes the value, strictly, and hands it back
+/// on a mismatch, as `Box<dyn Any>::downcast` does; the counterpart of C++
+/// `as<T>()`.
+/// * `TryFrom` takes the value and returns a `TypeError`, for `?`. Between
+/// references it only checks; from an [`Any`](crate::Any) it converts by
+/// the FFI rules, such as an integer into a float.
///
/// # Safety
///
@@ -222,13 +224,9 @@ pub unsafe trait ObjectRefCore: Sized + Clone +
AnyCompatible {
}
}
- /// Take `self` as the reference type `B` when its object is one, without
- /// copying the object or changing its reference count.
- ///
- /// `B` is a subtype, or a parameterized container such as `Array<i64>`,
- /// whose elements are checked too. On a mismatch the reference is returned
- /// unchanged, so another type can be tried. For `?`, use the `TryFrom`
that
- /// each upcast provides, which returns a `TypeError` instead.
+ /// Take `self` as `B`, a subtype or a container such as `Array<i64>` whose
+ /// elements are checked too, when its object is one, without copying it.
+ /// On a mismatch the reference is returned unchanged.
///
/// ```
/// use tvm_ffi::{object::ObjectRef, Array, ObjectRefCore};
@@ -385,21 +383,17 @@ impl std::fmt::Debug for ObjectIdentity {
}
}
-/// Check whether a runtime type index refers to `Target` or one of its
-/// subtypes.
-///
-/// The subtype relation lives in the process-wide type table maintained by the
-/// tvm-ffi library: every registered type records its depth in the single
-/// inheritance tree together with the chain of its ancestors. The check is
-/// O(1) — if `target` really is an ancestor, it must appear in the candidate's
-/// ancestor array exactly at `target`'s depth.
+/// Whether the runtime type index of an object is `Target` or one of its
+/// subtypes, as C++ `Object::IsInstance<T>()` checks.
///
-/// This is a hidden support function for derive-generated object checks.
Object
-/// indices in the registered range must refer to entries in the runtime type
-/// table.
-#[doc(hidden)]
+/// Use it in the strict check of a reference type implemented by hand; the
+/// casts of [`ObjectRefCore`] call it already. An index in the object range
+/// must be registered.
#[inline(always)]
pub fn is_instance_of<Target: ObjectCore>(object_type_index: i32) -> bool {
+ // Every registered type records its depth in the single inheritance tree
+ // and its ancestors, so the check is O(1): an ancestor `Target` appears in
+ // the candidate's ancestor array exactly at `Target`'s depth.
let target_type_index = Target::type_index();
if object_type_index == target_type_index {
return true;
@@ -540,32 +534,23 @@ pub mod unsafe_ {
kTVMFFIObjectDeleterFlagBitMaskWeak,
};
- /// Increase the strong reference count of the object
- ///
- /// This function is same as TVMFFIObjectIncRef but implemented natively
in Rust
- ///
- /// # Arguments
- /// * `obj` - The object to increase the reference count
+ /// Increase the strong reference count of `handle`.
#[inline]
pub unsafe fn inc_ref(handle: *mut TVMFFIObject) {
let obj = &*handle;
obj.combined_ref_count.fetch_add(1, Ordering::Relaxed);
}
- /// Decrease the strong reference count of the object
- ///
- /// This function is same as TVMFFIObjectDecRef but implemented natively
in Rust
- ///
- /// # Arguments
- /// * `obj` - The object to decrease the reference count
+ /// Decrease the strong reference count of `handle`, deleting the object
when it was the last.
#[inline]
pub(crate) unsafe fn dec_ref(handle: *mut TVMFFIObject) {
+ // Release publishes this owner's writes; the Acquire fence before the
deleter pairs with it.
let old_combined_count = (*handle)
.combined_ref_count
- .fetch_sub(COMBINED_REF_COUNT_STRONG_ONE, Ordering::Relaxed);
+ .fetch_sub(COMBINED_REF_COUNT_STRONG_ONE, Ordering::Release);
if old_combined_count == COMBINED_REF_COUNT_BOTH_ONE {
+ fence(Ordering::Acquire);
if let Some(deleter) = (*handle).deleter {
- fence(Ordering::Acquire);
deleter(
handle.cast::<c_void>(),
kTVMFFIObjectDeleterFlagBitMaskBoth as i32,
@@ -604,6 +589,13 @@ pub mod unsafe_ {
(obj.combined_ref_count.load(Ordering::Relaxed) &
COMBINED_REF_COUNT_MASK_U32) as usize
}
+ /// Whether `handle` is the only reference, so it may be mutated in place
(C++ `unique()`).
+ #[inline]
+ pub(crate) unsafe fn is_unique(handle: *mut TVMFFIObject) -> bool {
+ // Acquire pairs with the releasing decrement: other owners' accesses
precede an in-place write.
+ (*handle).combined_ref_count.load(Ordering::Acquire) ==
COMBINED_REF_COUNT_BOTH_ONE
+ }
+
#[inline]
pub(crate) unsafe fn weak_count(handle: *mut TVMFFIObject) -> usize {
let obj = &*handle;
@@ -787,10 +779,11 @@ impl<T: ObjectCore> ObjectArc<T> {
/// Whether `this` holds a null pointer, as all-zero bytes do: no
/// `ObjectArc`, but the field of an object that a creator left undefined.
///
+ /// See also [`AnyCompatible::is_undefined`].
+ ///
/// # Safety
///
/// `this` points to an `ObjectArc<T>` or to all-zero bytes.
- #[doc(hidden)]
#[inline]
pub unsafe fn is_null(this: *const Self) -> bool {
(*std::ptr::addr_of!((*this).ptr).cast::<*const T>()).is_null()
@@ -873,6 +866,12 @@ impl<T: ObjectCore> ObjectArc<T> {
unsafe {
unsafe_::strong_count(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
}
+ /// Whether this is the only reference, so it may be mutated in place.
+ #[inline]
+ pub(crate) fn is_unique(this: &Self) -> bool {
+ unsafe { unsafe_::is_unique(this.ptr.as_ptr().cast::<TVMFFIObject>()) }
+ }
+
/// Get the weak reference count of the ObjectArc
///
/// # Arguments
diff --git a/rust/tvm-ffi/src/type_traits.rs b/rust/tvm-ffi/src/type_traits.rs
index 9a541b19..cb849cfe 100644
--- a/rust/tvm-ffi/src/type_traits.rs
+++ b/rust/tvm-ffi/src/type_traits.rs
@@ -229,16 +229,12 @@ impl TypeSchema for AnyView<'_> {
}
}
-/// Marker for a value that can be stored in an FFI container.
+/// A value an FFI container such as [`Array`](crate::Array) or
+/// [`Map`](crate::Map) holds: every [`AnyCompatible`] type, and [`Any`].
///
-/// This is an implementation detail of [`crate::Array`] and [`crate::Map`].
-/// Users should implement [`AnyCompatible`]; the blanket implementation below
-/// then makes that type a container element automatically. [`Any`] is handled
-/// separately so heterogeneous containers such as `Array<Any>` also work.
-///
-/// The private supertrait seals this marker and owns all conversion
operations,
-/// keeping them out of the user-facing trait API.
-#[doc(hidden)]
+/// Use it as the bound of code generic over container elements, as
+/// `Array<T: ContainerElement>` is. The trait is sealed: implement
+/// [`AnyCompatible`] instead, and the type is an element.
pub trait ContainerElement: container_element_ops::Ops {}
impl<T: AnyCompatible> ContainerElement for T {}
diff --git a/rust/tvm-ffi/tests/test_array.rs b/rust/tvm-ffi/tests/test_array.rs
index 3b9c3d81..0288da8d 100644
--- a/rust/tvm-ffi/tests/test_array.rs
+++ b/rust/tvm-ffi/tests/test_array.rs
@@ -171,3 +171,30 @@ fn test_array_parametric_heterogeneity() {
&[1, 2, 3]
);
}
+
+#[test]
+fn test_array_copy_on_write() {
+ let ptr = |a: &Array<String>| ObjectArc::as_ptr(<Array<String> as
ObjectRefCore>::data(a));
+ let s = |x: &str| String::from(x);
+ let values = |a: &Array<String>| a.iter().map(|x|
x.as_str().to_owned()).collect::<Vec<_>>();
+
+ let mut array = Array::from_vec(vec![s("a"), s("b"), s("c")]);
+ let shared = array.clone();
+ array.set(0, s("x")).unwrap();
+ assert_ne!(ptr(&array), ptr(&shared));
+ assert_eq!(values(&shared), ["a", "b", "c"]);
+
+ let unique = ptr(&array);
+ array.set(1, s("y")).unwrap();
+ array.truncate(2);
+ assert_eq!(array.pop().unwrap().as_str(), "y");
+ assert_eq!(ptr(&array), unique);
+
+ array.push(s("z"));
+ array.insert(0, s("w")).unwrap();
+ assert_eq!(values(&array), ["w", "x", "z"]);
+
+ assert!(array.set(3, s("q")).is_err());
+ assert!(array.insert(4, s("q")).is_err());
+ assert!(Array::<String>::new().pop().is_err());
+}
diff --git a/rust/tvm-ffi/tests/test_error.rs b/rust/tvm-ffi/tests/test_error.rs
index 96e31fb7..aba8619d 100644
--- a/rust/tvm-ffi/tests/test_error.rs
+++ b/rust/tvm-ffi/tests/test_error.rs
@@ -73,6 +73,7 @@ fn test_error_with_context() {
let output = Error::with_appended_backtrace(error, " frame");
assert_eq!(output.backtrace(), "origin frame");
assert!(output.cause_chain().unwrap().same_as(&error0));
+ assert!(std::error::Error::source(&output).is_some());
assert!(output.extra_context().unwrap().same_as(&payload));
if let Some(retained) = retained {
assert_eq!(retained.backtrace(), "origin");
diff --git a/rust/tvm-ffi/tests/test_structural_mutate.rs
b/rust/tvm-ffi/tests/test_structural_mutate.rs
index bac6563c..914e0c24 100644
--- a/rust/tvm-ffi/tests/test_structural_mutate.rs
+++ b/rust/tvm-ffi/tests/test_structural_mutate.rs
@@ -27,8 +27,8 @@ use tvm_ffi::{
dispatch, structural_map, structural_mutate, structural_visit,
structural_walk, Any, AnyView,
Array, DefRegionKind, Dispatcher, Error, FieldGetter, Function,
InplaceMode, IntoDispatcher,
Map, MutationView, Object, ObjectArc, Result, String as FfiString,
StructuralMutatorView,
- StructuralVarRemap, StructuralVisitorView, TypeIndex, Unchanged,
UnchangedOr, VisitInterrupt,
- WalkOrder, WalkResult, RUNTIME_ERROR,
+ StructuralVisitorView, TypeIndex, Unchanged, UnchangedOr, VisitInterrupt,
WalkOrder,
+ WalkResult, RUNTIME_ERROR,
};
/// The value when it is stored as a `bool`, not converted from an integer.
@@ -55,7 +55,7 @@ impl<'value> Dispatcher<(AnyView<'value>, DefRegionKind),
Option<Result<Unchange
#[derive(Default)]
struct ManualIncrement {
- remap: StructuralVarRemap,
+ remapped: Option<Any>,
remap_probe: Option<Any>,
}
@@ -118,7 +118,7 @@ impl<'v, 'c, 'r>
})?;
assert_eq!(mutator.def_region_kind(), initial);
let replacement = mutator.var_remap_get(self,
AnyView::from(&key))?.unwrap();
- self.remap.set(AnyView::from(&key), &replacement)?;
+ self.remapped = Some(replacement.clone());
Ok(UnchangedOr::changed(replacement))
})()
} else if let Some(integer) =
value.as_any_view().downcast_ref::<i64>().copied() {
@@ -404,12 +404,10 @@ fn
user_driven_mutator_controls_default_recursion_and_in_place_opt_in() {
41
);
assert_eq!(
-
i64::try_from(dispatch.remap.get(AnyView::from(&key)).unwrap().unwrap()).unwrap(),
+ i64::try_from(dispatch.remapped.take().unwrap()).unwrap(),
41
);
}
- dispatch.remap.clear();
- assert!(dispatch.remap.get(AnyView::from(&key)).unwrap().is_none());
}
#[test]