Skip to main content

tvm_ffi/collections/
map.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//! Immutable [`Map`] container backed by the C++ `ffi.Map` object.
20//!
21//! The `MapObj` header mirrors C++ `MapBaseObj`, so [`Map::len`] reads `size`
22//! directly (no FFI). The hash-table storage itself is an implementation detail,
23//! so lookups and iteration are delegated to the global functions the C++ runtime
24//! registers (`ffi.MapGetItem`, `ffi.MapForwardIterFunctor`, ...): there is no
25//! Map-specific C ABI, and a Rust re-implementation would have to replicate the
26//! hashing (`AnyHash`/`AnyEqual`) and dense/small probing, just as the Python
27//! bindings also delegate.
28//!
29//! `Any` conversions follow C++ `MapTypeTraitsBase` (map_base.h): the strict
30//! check walks every entry, and a failed strict `try_from` falls back to
31//! converting the entries one by one into a new map.
32use std::fmt::Debug;
33use std::marker::PhantomData;
34use std::ops::Deref;
35
36use crate::any::TryFromTemp;
37use crate::derive::Object;
38use crate::function::Function;
39use crate::object::{Object, ObjectArc};
40use crate::{Any, AnyCompatible, AnyView, Error, ObjectRefCore, Result};
41use tvm_ffi_sys::TVMFFITypeIndex as TypeIndex;
42use tvm_ffi_sys::{TVMFFIAny, TVMFFIObject};
43
44/// Container object for [`Map`]. The header fields mirror C++ `MapBaseObj`
45/// (`include/tvm/ffi/container/map_base.h`) so [`Map::len`] can read `size`
46/// without an FFI call; the storage `data` points to stays opaque.
47///
48/// This is a partial mirror by design: `MapBaseObj` has one more trailing field
49/// after `slots_` (a `data_deleter_` function pointer), omitted here because this
50/// binding is read-only — it only reads fields up to `size`, which precede it,
51/// and never allocates a `MapObj` itself (the C++ runtime allocates the larger
52/// `Small`/`DenseMapBaseObj` subclass), so the trailing field is never touched.
53/// The layout also assumes `TVM_FFI_DEBUG_WITH_ABI_CHANGE` is off (the default):
54/// with that debug flag set, `MapBaseObj` gains a *leading* `state_marker` field
55/// that would shift every offset below.
56#[repr(C)]
57#[derive(Object)]
58#[type_key = "ffi.Map"]
59#[type_index(TypeIndex::kTVMFFIMap)]
60pub struct MapObj {
61    pub object: Object,
62    /// Pointer to the (opaque) key/value storage region (`MapBaseObj::data_`).
63    pub data: *mut core::ffi::c_void,
64    /// Number of entries (`MapBaseObj::size_`).
65    pub size: u64,
66    /// Number of hash slots; the MSB is a small-map tag (`MapBaseObj::slots_`).
67    pub slots: u64,
68}
69
70/// Immutable, reference-counted map from `K` to `V`, sharing its underlying
71/// `MapObj` with C++. Cloning is cheap (it bumps the refcount).
72#[repr(C)]
73pub struct Map<K, V> {
74    data: ObjectArc<MapObj>,
75    _marker: PhantomData<(K, V)>,
76}
77
78// Manual `Clone`: the data is the shared `ObjectArc`, so `Map<K, V>` is `Clone`
79// regardless of whether `K`/`V` are (they are only phantom markers).
80impl<K, V> Clone for Map<K, V> {
81    fn clone(&self) -> Self {
82        Self {
83            data: self.data.clone(),
84            _marker: PhantomData,
85        }
86    }
87}
88
89unsafe impl<K, V> ObjectRefCore for Map<K, V> {
90    type ContainerType = MapObj;
91
92    fn data(this: &Self) -> &ObjectArc<MapObj> {
93        &this.data
94    }
95
96    fn into_data(this: Self) -> ObjectArc<MapObj> {
97        this.data
98    }
99
100    fn from_data(data: ObjectArc<MapObj>) -> Self {
101        Self {
102            data,
103            _marker: PhantomData,
104        }
105    }
106}
107
108// A `Map<K, V>` is a counted handle to its `MapObj`, so it derefs to it (like
109// `ObjectArc` does): methods can read header fields as `self.size` instead of
110// `self.data.size`.
111impl<K, V> Deref for Map<K, V> {
112    type Target = MapObj;
113    #[inline]
114    fn deref(&self) -> &MapObj {
115        &self.data
116    }
117}
118
119impl<K, V> Map<K, V>
120where
121    K: AnyCompatible,
122    V: AnyCompatible,
123{
124    /// Creates a new, empty map (via an `ffi.Map()` call to the C++ runtime).
125    pub fn new() -> Self {
126        Self::from_pairs(&[]).expect("ffi.Map() failed to construct an empty map")
127    }
128
129    /// Builds a map by calling the C++ `ffi.Map` constructor with a flattened
130    /// `[k0, v0, k1, v1, ...]` argument list.
131    fn from_pairs(pairs: &[(K, V)]) -> Result<Self> {
132        let mut args: Vec<AnyView<'_>> = Vec::with_capacity(pairs.len() * 2);
133        for (k, v) in pairs {
134            args.push(AnyView::from(k));
135            args.push(AnyView::from(v));
136        }
137        let result = crate::cached_global_func!("ffi.Map").call_packed(&args)?;
138        Self::try_from(result)
139    }
140
141    /// Returns the number of entries in the map by reading the `MapObj` header
142    /// directly (no FFI call), like [`Array::len`](crate::Array).
143    pub fn len(&self) -> usize {
144        self.size as usize
145    }
146
147    /// Returns `true` if the map contains no entries.
148    pub fn is_empty(&self) -> bool {
149        self.len() == 0
150    }
151
152    /// Returns whether `key` is present, propagating any FFI error (e.g. a key
153    /// the C++ runtime cannot hash) rather than panicking. Backs both
154    /// [`Map::contains_key`] and [`Map::get`].
155    fn try_contains_key(&self, key: &K) -> Result<bool> {
156        let result = crate::cached_global_func!("ffi.MapCount")
157            .call_packed(&[AnyView::from(self), AnyView::from(key)])?;
158        Ok(i64::try_from(result)? != 0)
159    }
160
161    /// In debug builds, panics if `K` does not match the map's actual key type,
162    /// checked against one stored key. Compiles to nothing in release, so
163    /// [`Map::get`] / [`Map::contains_key`] keep their single-FFI-call fast path.
164    ///
165    /// A pure key lookup *hashes* the key rather than retrieving one, so a wrong
166    /// `K` simply fails to match and reads as "absent" — unlike [`Map::iter`],
167    /// which retrieves keys and surfaces the mismatch. This assertion catches
168    /// that misuse in dev/tests without taxing release lookups. Only meaningful
169    /// on a miss (a hit already proves `K` matched a stored key).
170    #[inline]
171    fn debug_assert_key_type(&self) {
172        #[cfg(debug_assertions)]
173        {
174            if !self.is_empty() {
175                let functor = self.iter_functor();
176                let first_key = functor
177                    .call_packed(&[AnyView::from(&0i64)])
178                    .expect("map iterator: reading current key failed");
179                assert!(
180                    first_key.try_as::<K>().is_some(),
181                    "Map lookup: key type `{}` does not match the map's stored key type",
182                    std::any::type_name::<K>(),
183                );
184            }
185        }
186    }
187
188    /// Returns `true` if `key` is present in the map.
189    ///
190    /// Panics if the lookup fails in the C++ runtime (e.g. `key` is not
191    /// hashable); use [`Map::get`] when such failures must be recovered from.
192    /// Passing a `key` whose *type* does not match the map's key type is
193    /// **undefined** (currently reads as `false`; see [`Map::get`]); debug builds
194    /// assert against it.
195    pub fn contains_key(&self, key: &K) -> bool {
196        let present = self
197            .try_contains_key(key)
198            .expect("ffi.MapCount call failed");
199        if !present {
200            self.debug_assert_key_type();
201        }
202        present
203    }
204
205    /// Looks up `key`, returning `Ok(None)` if absent, or `Err` if the lookup
206    /// fails in the C++ runtime (e.g. `key` is not hashable) or the stored value
207    /// cannot be converted to `V`. The map is immutable, so the lookup
208    /// (existence check then `ffi.MapGetItem`) cannot race with itself.
209    ///
210    /// Passing a `key` whose *type* does not match the map's key type is
211    /// **undefined**: the result is unspecified — it currently reads as absent
212    /// (`Ok(None)`) because a pure lookup hashes the key rather than retrieving
213    /// one, so the mismatch cannot be surfaced as an `Err` the way a value
214    /// mismatch (or [`Map::iter`], which retrieves keys) is. Debug builds assert
215    /// against this misuse.
216    pub fn get(&self, key: &K) -> Result<Option<V>> {
217        if !self.try_contains_key(key)? {
218            self.debug_assert_key_type();
219            return Ok(None);
220        }
221        let result = crate::cached_global_func!("ffi.MapGetItem")
222            .call_packed(&[AnyView::from(self), AnyView::from(key)])?;
223        let value = TryFromTemp::<V>::try_from(result).map(TryFromTemp::into_value)?;
224        Ok(Some(value))
225    }
226
227    /// Returns an iterator over the `(key, value)` pairs of the map.
228    pub fn iter(&self) -> MapItems<K, V> {
229        self.make_iter(|f| (iter_read::<K>(f, 0, "key"), iter_read::<V>(f, 1, "value")))
230    }
231
232    /// Returns an iterator over the keys of the map.
233    pub fn keys(&self) -> MapKeys<K> {
234        self.make_iter(|f| iter_read::<K>(f, 0, "key"))
235    }
236
237    /// Returns an iterator over the values of the map.
238    pub fn values(&self) -> MapValues<V> {
239        self.make_iter(|f| iter_read::<V>(f, 1, "value"))
240    }
241
242    /// Builds a [`MapIter`] whose `read` extracts each entry as `T`. The
243    /// forward-iteration functor is requested only for a non-empty map, so
244    /// iterating an empty map makes no FFI call and allocates no functor.
245    fn make_iter<T>(&self, read: fn(&Function) -> T) -> MapIter<T> {
246        let remaining = self.len();
247        MapIter {
248            functor: (remaining != 0).then(|| self.iter_functor()),
249            remaining,
250            _keepalive: self.data.clone(),
251            read,
252        }
253    }
254
255    /// Obtains a fresh stateful forward-iteration functor from the C++ runtime.
256    fn iter_functor(&self) -> Function {
257        let result = crate::cached_global_func!("ffi.MapForwardIterFunctor")
258            .call_packed(&[AnyView::from(self)])
259            .expect("ffi.MapForwardIterFunctor call failed");
260        Function::try_from(result).expect("ffi.MapForwardIterFunctor returned a non-function")
261    }
262
263    /// Reads all entries as raw `(Any, Any)` pairs without converting to
264    /// `K`/`V`, propagating FFI failures instead of panicking (unlike the
265    /// iterator API): this backs `check_any_strict` and
266    /// `try_cast_from_any_view`, which run during argument decoding where a
267    /// panic could unwind across the C ABI.
268    fn try_raw_entries(&self) -> Result<Vec<(Any, Any)>> {
269        let mut entries = Vec::with_capacity(self.len());
270        let mut remaining = self.len();
271        if remaining == 0 {
272            return Ok(entries);
273        }
274        let functor = crate::cached_global_func!("ffi.MapForwardIterFunctor")
275            .call_packed(&[AnyView::from(self)])
276            .and_then(Function::try_from)?;
277        loop {
278            let k = functor.call_packed(&[AnyView::from(&0i64)])?;
279            let v = functor.call_packed(&[AnyView::from(&1i64)])?;
280            entries.push((k, v));
281            remaining -= 1;
282            if remaining == 0 {
283                return Ok(entries);
284            }
285            functor.call_packed(&[AnyView::from(&2i64)])?;
286        }
287    }
288}
289
290impl<K, V> Default for Map<K, V>
291where
292    K: AnyCompatible,
293    V: AnyCompatible,
294{
295    fn default() -> Self {
296        Self::new()
297    }
298}
299
300impl<K, V> Debug for Map<K, V>
301where
302    K: AnyCompatible,
303    V: AnyCompatible,
304{
305    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
306        fn short(name: &str) -> &str {
307            name.split("::").last().unwrap_or(name)
308        }
309        write!(
310            f,
311            "Map<{}, {}>[{}]",
312            short(std::any::type_name::<K>()),
313            short(std::any::type_name::<V>()),
314            self.len()
315        )
316    }
317}
318
319impl<K, V> FromIterator<(K, V)> for Map<K, V>
320where
321    K: AnyCompatible,
322    V: AnyCompatible,
323{
324    /// Duplicate keys follow C++ `ffi.Map` semantics: a later pair overwrites an
325    /// earlier one, so the resulting map may be smaller than the iterator.
326    fn from_iter<I: IntoIterator<Item = (K, V)>>(iter: I) -> Self {
327        let pairs: Vec<(K, V)> = iter.into_iter().collect();
328        Self::from_pairs(&pairs).expect("ffi.Map() failed to construct a map")
329    }
330}
331
332// --- Iterators ---
333//
334// The functor from `ffi.MapForwardIterFunctor` does not keep the map alive, so
335// each iterator carries an `ObjectArc<MapObj>` keepalive. Functor command codes:
336// `0` = read key, `1` = read value, `2` = advance.
337//
338// These are `ExactSizeIterator`s, so a key/value not matching the declared
339// `K`/`V` panics rather than ending iteration early (which would drop entries).
340// Use [`Map::get`], which returns `Err` on a type mismatch, when the element
341// types are uncertain.
342
343/// Reads the functor's current key (`command` 0) or value (`command` 1) as `T`,
344/// panicking on a type mismatch (see the note above on `ExactSizeIterator`).
345fn iter_read<T: AnyCompatible>(functor: &Function, command: i64, kind: &str) -> T {
346    let any = functor
347        .call_packed(&[AnyView::from(&command)])
348        .expect("map iterator: reading current element failed");
349    TryFromTemp::<T>::try_from(any)
350        .map(TryFromTemp::into_value)
351        .unwrap_or_else(|_| panic!("map iterator: {kind} does not match the map's {kind} type"))
352}
353
354/// Consumes one entry: decrements `remaining` and advances the functor unless
355/// the map is now exhausted. Callers must guard `remaining > 0` (every `next`
356/// returns early when it hits 0), so the decrement can never underflow.
357fn iter_advance(functor: &Function, remaining: &mut usize) {
358    debug_assert!(
359        *remaining > 0,
360        "iter_advance called with no remaining entries"
361    );
362    *remaining -= 1;
363    if *remaining > 0 {
364        functor
365            .call_packed(&[AnyView::from(&2i64)])
366            .expect("map iterator: advancing failed");
367    }
368}
369
370/// Forward iterator over a [`Map`], yielding `T` per entry. The `read` fn pulls
371/// the current entry (key, value, or both) from the C++ functor, so each variant
372/// touches only the command(s) it needs — `keys()` never reads values, and vice
373/// versa. Created via the [`MapItems`] / [`MapKeys`] / [`MapValues`] aliases.
374pub struct MapIter<T> {
375    /// `None` for an empty map (no functor is requested); `Some` while at least
376    /// one entry remains to be yielded.
377    functor: Option<Function>,
378    remaining: usize,
379    _keepalive: ObjectArc<MapObj>,
380    read: fn(&Function) -> T,
381}
382
383impl<T> Iterator for MapIter<T> {
384    type Item = T;
385
386    fn next(&mut self) -> Option<T> {
387        if self.remaining == 0 {
388            return None;
389        }
390        // `remaining > 0` implies `functor` is `Some` (see `Map::make_iter`).
391        let functor = self
392            .functor
393            .as_ref()
394            .expect("non-empty map iterator has a functor");
395        let item = (self.read)(functor);
396        iter_advance(functor, &mut self.remaining);
397        Some(item)
398    }
399
400    fn size_hint(&self) -> (usize, Option<usize>) {
401        (self.remaining, Some(self.remaining))
402    }
403}
404
405impl<T> ExactSizeIterator for MapIter<T> {}
406
407/// Iterator over `(key, value)` pairs, created by [`Map::iter`].
408pub type MapItems<K, V> = MapIter<(K, V)>;
409/// Iterator over keys, created by [`Map::keys`].
410pub type MapKeys<K> = MapIter<K>;
411/// Iterator over values, created by [`Map::values`].
412pub type MapValues<V> = MapIter<V>;
413
414impl<K, V> IntoIterator for &Map<K, V>
415where
416    K: AnyCompatible,
417    V: AnyCompatible,
418{
419    type Item = (K, V);
420    type IntoIter = MapItems<K, V>;
421
422    fn into_iter(self) -> Self::IntoIter {
423        self.iter()
424    }
425}
426
427// --- Any Type System Conversions ---
428
429unsafe impl<K, V> AnyCompatible for Map<K, V>
430where
431    K: AnyCompatible,
432    V: AnyCompatible,
433{
434    fn type_str() -> String {
435        format!("Map<{}, {}>", K::type_str(), V::type_str())
436    }
437
438    unsafe fn check_any_strict(data: &TVMFFIAny) -> bool {
439        // Mirrors C++ `CheckAnyStrict` (map_base.h): every entry must strictly
440        // match `K`/`V`. An FFI failure reads as "no match" — this fn has no
441        // error channel and must not panic (see `try_raw_entries`).
442        if data.type_index != TypeIndex::kTVMFFIMap as i32 {
443            return false;
444        }
445        let map = Self::copy_from_any_view_after_check(data);
446        match map.try_raw_entries() {
447            Ok(entries) => entries
448                .iter()
449                .all(|(k, v)| k.try_as::<K>().is_some() && v.try_as::<V>().is_some()),
450            Err(_) => false,
451        }
452    }
453
454    unsafe fn copy_to_any_view(src: &Self, data: &mut TVMFFIAny) {
455        data.type_index = TypeIndex::kTVMFFIMap as i32;
456        data.data_union.v_obj = ObjectArc::as_raw(Self::data(src)) as *mut TVMFFIObject;
457        data.small_str_len = 0;
458    }
459
460    unsafe fn move_to_any(src: Self, data: &mut TVMFFIAny) {
461        data.type_index = TypeIndex::kTVMFFIMap as i32;
462        data.data_union.v_obj = ObjectArc::into_raw(Self::into_data(src)) as *mut TVMFFIObject;
463        data.small_str_len = 0;
464    }
465
466    unsafe fn copy_from_any_view_after_check(data: &TVMFFIAny) -> Self {
467        let ptr = data.data_union.v_obj as *const MapObj;
468        crate::object::unsafe_::inc_ref(ptr as *mut TVMFFIObject);
469        Self::from_data(ObjectArc::from_raw(ptr))
470    }
471
472    unsafe fn move_from_any_after_check(data: &mut TVMFFIAny) -> Self {
473        let ptr = data.data_union.v_obj as *const MapObj;
474        let obj = Self::from_data(ObjectArc::from_raw(ptr));
475        data.type_index = TypeIndex::kTVMFFINone as i32;
476        data.data_union.v_int64 = 0;
477        obj
478    }
479
480    unsafe fn try_cast_from_any_view(data: &TVMFFIAny) -> Result<Self, ()> {
481        if data.type_index != TypeIndex::kTVMFFIMap as i32 {
482            return Err(());
483        }
484
485        // Fast path: if all entries match strictly, we can just copy the reference.
486        if Self::check_any_strict(data) {
487            return Ok(Self::copy_from_any_view_after_check(data));
488        }
489
490        // Slow path: try to convert entry by entry into a new map, as C++
491        // `TryCastFromAnyView` does.
492        let src = Self::copy_from_any_view_after_check(data);
493        let mut pairs = Vec::with_capacity(src.len());
494        for (k, v) in src.try_raw_entries().map_err(|_| ())? {
495            let k = TryFromTemp::<K>::try_from(k).map_err(|_| ())?;
496            let v = TryFromTemp::<V>::try_from(v).map_err(|_| ())?;
497            pairs.push((TryFromTemp::into_value(k), TryFromTemp::into_value(v)));
498        }
499        Self::from_pairs(&pairs).map_err(|_| ())
500    }
501}
502
503impl<K, V> TryFrom<Any> for Map<K, V>
504where
505    K: AnyCompatible,
506    V: AnyCompatible,
507{
508    type Error = Error;
509
510    fn try_from(value: Any) -> Result<Self> {
511        let temp: TryFromTemp<Self> = TryFromTemp::try_from(value)?;
512        Ok(TryFromTemp::into_value(temp))
513    }
514}
515
516impl<'a, K, V> TryFrom<AnyView<'a>> for Map<K, V>
517where
518    K: AnyCompatible,
519    V: AnyCompatible,
520{
521    type Error = Error;
522
523    fn try_from(value: AnyView<'a>) -> Result<Self> {
524        let temp: TryFromTemp<Self> = TryFromTemp::try_from(value)?;
525        Ok(TryFromTemp::into_value(temp))
526    }
527}