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}