Skip to main content

tvm_ffi/extra/
structural_common.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use crate::any::{Any, AnyView};
21use crate::error::Error;
22use crate::function::Function;
23use crate::object::{self, ObjectCore, ObjectRefCore};
24use crate::reflection::FieldGetter;
25use crate::tvm_ffi_sys::{
26    TVMFFIAny, TVMFFIByteArray, TVMFFIGetTypeInfo, TVMFFITypeIndex, TVMFFITypeKeyToIndex,
27};
28
29/// Add one structural traversal frame to an error's backtrace.
30pub(crate) fn with_structural_error_context(error: Error, operation: &str, frame: &str) -> Error {
31    Error::with_appended_backtrace(error, &format!("[native structural {operation}] {frame}\n"))
32}
33
34#[cold]
35pub(crate) fn with_visit_error_context(error: Error, raw: TVMFFIAny) -> Error {
36    if raw.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
37        return error;
38    }
39    let context = (|| {
40        let mut context_type = 0;
41        unsafe {
42            crate::check_safe_call!(TVMFFITypeKeyToIndex(
43                &TVMFFIByteArray::from_str("ffi.VisitErrorContext"),
44                &mut context_type,
45            ))
46            .ok()?;
47        }
48        let node = StructuralView::from_raw(raw).cast::<object::ObjectRef>()?;
49        let mut previous = error.extra_context();
50        let mut nodes = Vec::new();
51        if let Some(prior) = previous.as_ref() {
52            if AnyView::from(prior).type_index() == context_type {
53                let object = &**object::ObjectRef::data(prior);
54                let records = FieldGetter::new(context_type, "reverse_visit_pattern")
55                    .ok()?
56                    .get_any(object)
57                    .ok()?;
58                let size = Function::get_global("ffi.ListSize")
59                    .ok()?
60                    .call_tuple((records.clone(),))
61                    .ok()?
62                    .try_as::<i64>()?;
63                let get_item = Function::get_global("ffi.ListGetItem").ok()?;
64                if size > 0 {
65                    let last = get_item
66                        .call_tuple((records.clone(), size - 1))
67                        .ok()?
68                        .try_as::<object::ObjectRef>();
69                    // Callback, policy and default descent can report the same frame.
70                    if last.is_some_and(|last| last.same_as(&node)) {
71                        return None;
72                    }
73                }
74                for i in 0..size {
75                    nodes.push(get_item.call_tuple((records.clone(), i)).ok()?);
76                }
77                previous = FieldGetter::new(context_type, "prev_error_context")
78                    .ok()?
79                    .get::<_, Option<object::ObjectRef>>(object)
80                    .ok()?;
81            }
82        }
83        nodes.push(Any::from(node));
84        let records = Function::get_global("ffi.List")
85            .ok()?
86            .call_packed(&nodes.iter().map(AnyView::from).collect::<Vec<_>>())
87            .ok()?;
88        Function::get_global("ffi.MakeObjectFromPackedArgs")
89            .ok()?
90            .call_tuple((
91                context_type,
92                crate::String::from("reverse_visit_pattern"),
93                records,
94                crate::String::from("prev_error_context"),
95                previous,
96            ))
97            .ok()?
98            .try_as::<object::ObjectRef>()
99    })();
100    // Diagnostic enrichment must not replace the original error on failure.
101    match context {
102        Some(context) => Error::new_with_cause_and_extra_context(
103            error.kind(),
104            error.message(),
105            error.backtrace(),
106            error.cause_chain().as_ref(),
107            Some(&context),
108        ),
109        None => error,
110    }
111}
112
113// Generate the tuple arities supported by the standard library (1 through 12).
114macro_rules! impl_callback_chain_tuple_arities {
115    ($impl_chain:ident) => {
116        impl_callback_chain_tuple_arities!(
117            @prefixes $impl_chain;
118            [];
119            (F0, M0, 0),
120            (F1, M1, 1),
121            (F2, M2, 2),
122            (F3, M3, 3),
123            (F4, M4, 4),
124            (F5, M5, 5),
125            (F6, M6, 6),
126            (F7, M7, 7),
127            (F8, M8, 8),
128            (F9, M9, 9),
129            (F10, M10, 10),
130            (F11, M11, 11)
131        );
132    };
133    (@prefixes $impl_chain:ident; [$($prefix:tt)*];) => {};
134    (
135        @prefixes $impl_chain:ident;
136        [$($prefix:tt)*];
137        $next:tt $(, $rest:tt)*
138    ) => {
139        $impl_chain!($($prefix)* $next);
140        impl_callback_chain_tuple_arities!(
141            @prefixes $impl_chain;
142            [$($prefix)* $next,];
143            $($rest),*
144        );
145    };
146}
147
148pub(crate) use impl_callback_chain_tuple_arities;
149
150/// A borrowed value shared by structural visit, walk, map, and mutate callbacks.
151///
152/// Use `&StructuralView` for an erased callback argument. [`Self::as_node`]
153/// borrows an object node, while [`Self::cast`] returns a typed value (acquiring
154/// ownership for object handles). This view does not grant in-place permission;
155/// consuming mutation callbacks use [`crate::MutateValue`] instead.
156#[repr(transparent)]
157pub struct StructuralView(TVMFFIAny);
158
159impl<'a> From<&'a StructuralView> for AnyView<'a> {
160    #[inline]
161    fn from(value: &'a StructuralView) -> Self {
162        // SAFETY: the view cannot outlive the borrowed structural value.
163        unsafe { AnyView::from_raw_ffi_any(value.raw()) }
164    }
165}
166
167impl StructuralView {
168    #[inline]
169    pub(crate) fn from_raw(raw: TVMFFIAny) -> Self {
170        Self(raw)
171    }
172
173    #[inline]
174    pub(crate) fn from_any(value: &Any) -> &Self {
175        // SAFETY: StructuralView is transparent over TVMFFIAny. The owning
176        // Any keeps its contents live for the lifetime of this shared borrow.
177        unsafe { &*std::ptr::from_ref(value.as_raw_ffi_any()).cast::<Self>() }
178    }
179
180    #[inline]
181    pub(crate) fn raw(&self) -> TVMFFIAny {
182        self.0
183    }
184
185    /// Copy this borrowed value into an owning [`Any`].
186    #[inline]
187    pub fn to_owned(&self) -> Any {
188        if let Some(owned) = try_to_owned_without_normalization(self.0) {
189            return owned;
190        }
191        Any::from(unsafe { AnyView::from_raw_ffi_any(self.0) })
192    }
193
194    /// Convert the value into an owned typed handle.
195    #[inline]
196    pub fn cast<R: crate::type_traits::AnyCompatible>(&self) -> Option<R> {
197        unsafe {
198            if R::check_any_strict(&self.0) {
199                Some(R::copy_from_any_view_after_check(&self.0))
200            } else {
201                None
202            }
203        }
204    }
205
206    /// Runtime type index stored in this value.
207    #[inline]
208    pub fn type_index(&self) -> i32 {
209        self.0.type_index
210    }
211
212    /// Borrow the value as node type `N` if it is an instance of that type.
213    #[inline]
214    pub fn as_node<N: ObjectCore>(&self) -> Option<&N> {
215        if self.0.type_index < TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
216            return None;
217        }
218        let base_type_index = N::type_index();
219        if self.0.type_index != base_type_index {
220            // A final type has no registered subtype, so a differing index can
221            // never match: reject with the integer compare alone, mirroring the
222            // `_type_final` fast path of C++ `IsObjectInstance`.
223            if N::TYPE_FINAL {
224                return None;
225            }
226            if !is_instance_at_depth(self.0.type_index, base_type_index, N::TYPE_DEPTH) {
227                return None;
228            }
229        }
230        Some(unsafe { &*(self.0.data_union.v_obj as *const N) })
231    }
232}
233
234/// Copy a borrowed FFI value when its owning representation is unchanged.
235///
236/// Raw string/byte views and `ObjectRValueRef` return `None` because they need
237/// the runtime's normalization or move logic. A null object pointer also
238/// returns `None` instead of constructing an invalid owning value.
239#[inline]
240pub(crate) fn try_to_owned_without_normalization(raw: TVMFFIAny) -> Option<Any> {
241    if is_plain_inline(raw.type_index) {
242        return Some(unsafe { Any::from_raw_ffi_any(raw) });
243    }
244    if raw.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin as i32 {
245        let object = unsafe { raw.data_union.v_obj };
246        if object.is_null() {
247            return None;
248        }
249        unsafe { object::unsafe_::inc_ref(object) };
250        return Some(unsafe { Any::from_raw_ffi_any(raw) });
251    }
252    None
253}
254
255pub(crate) use crate::any::is_plain_inline;
256
257#[inline]
258pub(crate) fn same_shallow(lhs: TVMFFIAny, rhs: TVMFFIAny) -> bool {
259    lhs.type_index == rhs.type_index
260        && lhs.small_str_len == rhs.small_str_len
261        && unsafe { lhs.data_union.v_uint64 == rhs.data_union.v_uint64 }
262}
263
264/// Subtype check with the base's inheritance depth supplied by the caller
265/// (`ObjectCore::TYPE_DEPTH`), so only the object's type info is fetched.
266#[inline]
267fn is_instance_at_depth(object_type_index: i32, base_type_index: i32, base_depth: i32) -> bool {
268    if object_type_index == base_type_index {
269        return true;
270    }
271    // Parent type indices are registered before their descendants. An object
272    // whose index precedes the target therefore cannot be its subtype.
273    if object_type_index < base_type_index {
274        return false;
275    }
276    unsafe {
277        let info = TVMFFIGetTypeInfo(object_type_index);
278        if info.is_null() {
279            return false;
280        }
281        if (*info).type_depth <= base_depth {
282            return false;
283        }
284        let ancestors = (*info).type_acenstors;
285        if ancestors.is_null() {
286            return false;
287        }
288        let ancestor = *ancestors.offset(base_depth as isize);
289        !ancestor.is_null() && (*ancestor).type_index == base_type_index
290    }
291}