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 c7a8cead [RUST] Add borrowed object node cast (#734)
c7a8cead is described below
commit c7a8cead2523c8a26bdd99f3a2aa8db7ca98919e
Author: Shushi Hong <[email protected]>
AuthorDate: Thu Sep 3 16:16:14 2026 -0400
[RUST] Add borrowed object node cast (#734)
This PR adds ObjectRefCore::as_node<N>() for zero-copy borrowed
object-node casts.
Checks such as is_zero currently clone an owning object handle before
calling try_cast. This performs an atomic reference-count increment and
decrement even though the caller only needs to inspect the node
temporarily.
The new API checks the runtime type directly from the existing object
header and returns a reference tied to the source handle. It does not
clone the handle, construct an owning cast result, or update the
reference count. Final node types, such as IntImmObj, use a fast exact
type-index comparison.
---
rust/tvm-ffi/src/object.rs | 20 ++++++++++++++++++++
rust/tvm-ffi/tests/test_cast.rs | 21 +++++++++++++++++++++
2 files changed, 41 insertions(+)
diff --git a/rust/tvm-ffi/src/object.rs b/rust/tvm-ffi/src/object.rs
index ddfbcf55..d690a64b 100644
--- a/rust/tvm-ffi/src/object.rs
+++ b/rust/tvm-ffi/src/object.rs
@@ -144,6 +144,21 @@ pub unsafe trait ObjectRefCore: Sized + Clone {
== ObjectArc::as_raw(Other::data(other)).cast::<()>()
}
}
+
+ /// Borrow the underlying object as node type `N` when its runtime type
matches.
+ ///
+ /// Unlike [`ObjectRefCast::try_cast`], this method neither consumes the
+ /// reference nor changes the object's reference count. The returned node
+ /// cannot outlive `self`.
+ #[inline(always)]
+ fn as_node<N: ObjectCore>(&self) -> Option<&N> {
+ let object = unsafe { ObjectArc::as_raw(Self::data(self)) };
+ let type_index = unsafe { (*object.cast::<TVMFFIObject>()).type_index
};
+ if !is_instance_of::<N>(type_index) {
+ return None;
+ }
+ Some(unsafe { &*object.cast::<N>() })
+ }
}
/// An owning, hashable identity key for an FFI object.
@@ -217,6 +232,11 @@ pub fn is_instance_of<Target:
ObjectCore>(object_type_index: i32) -> bool {
if object_type_index == target_type_index {
return true;
}
+ // A final type cannot have a separately registered subtype. Keep common
+ // borrowed checks, such as `IntImmObj`, to one integer comparison.
+ if Target::TYPE_FINAL {
+ return false;
+ }
let object_begin = TypeIndex::kTVMFFIStaticObjectBegin as i32;
// Only object types participate in the type hierarchy.
if object_type_index < object_begin || target_type_index < object_begin {
diff --git a/rust/tvm-ffi/tests/test_cast.rs b/rust/tvm-ffi/tests/test_cast.rs
index 29689808..4cd1ce90 100644
--- a/rust/tvm-ffi/tests/test_cast.rs
+++ b/rust/tvm-ffi/tests/test_cast.rs
@@ -54,6 +54,7 @@ struct TestBase {
#[repr(C)]
#[derive(Object)]
#[type_key = "testing.TestObjectDerived"]
+#[type_final]
struct TestDerivedObj {
base: TestBaseObj,
extra: i64,
@@ -142,6 +143,26 @@ fn test_upcast_downcast_roundtrip() {
assert_eq!(delete_counter.load(Ordering::Relaxed), 1);
}
+#[test]
+fn test_borrowed_node_cast_preserves_reference_count() {
+ let delete_counter = Arc::new(AtomicU32::new(0));
+ let base: TestBase = new_derived(7, 8, delete_counter.clone())
+ .try_cast()
+ .unwrap();
+ let strong_count = ObjectArc::strong_count(TestBase::data(&base));
+
+ let derived = base.as_node::<TestDerivedObj>().unwrap();
+ assert_eq!(derived.base.value, 7);
+ assert_eq!(derived.extra, 8);
+ let base_node = base.as_node::<TestBaseObj>().unwrap();
+ assert_eq!(base_node.value, 7);
+ assert_eq!(ObjectArc::strong_count(TestBase::data(&base)), strong_count);
+
+ let base_only = new_base(1, delete_counter.clone());
+ assert!(base_only.as_node::<TestDerivedObj>().is_none());
+ assert_eq!(ObjectArc::strong_count(TestBase::data(&base_only)), 1);
+}
+
#[test]
fn test_generated_borrow_and_upcast_conversions() {
let delete_counter = Arc::new(AtomicU32::new(0));