wasmtime/runtime/
type_registry.rs

1//! Implement a registry of types: function, struct, and array definitions.
2//!
3//! Helps implement fast indirect call signature checking, reference type
4//! downcasting, and etc...
5
6use crate::hash_set::HashSet;
7use crate::prelude::*;
8use crate::sync::RwLock;
9use crate::vm::GcRuntime;
10use crate::Engine;
11use alloc::borrow::Cow;
12use alloc::sync::Arc;
13use core::iter;
14use core::{
15    borrow::Borrow,
16    fmt::{self, Debug},
17    hash::{Hash, Hasher},
18    ops::Range,
19    sync::atomic::{
20        AtomicBool, AtomicUsize,
21        Ordering::{AcqRel, Acquire, Release},
22    },
23};
24use wasmtime_environ::{
25    iter_entity_range,
26    packed_option::{PackedOption, ReservedValue},
27    EngineOrModuleTypeIndex, GcLayout, ModuleInternedTypeIndex, ModuleTypes, PrimaryMap,
28    SecondaryMap, TypeTrace, VMSharedTypeIndex, WasmRecGroup, WasmSubType,
29};
30use wasmtime_slab::{Id as SlabId, Slab};
31
32// ### Notes on the Lifetime Management of Types
33//
34// All defined types from all Wasm modules loaded into Wasmtime are interned
35// into their engine's `TypeRegistry`.
36//
37// With Wasm MVP, managing type lifetimes within the registry was easy: we only
38// cared about canonicalizing types so that `call_indirect` was fast and we
39// didn't waste memory on many copies of the same function type definition.
40// Function types could only take and return simple scalars (i32/f64/etc...) and
41// there were no type-to-type references. We could simply deduplicate function
42// types and reference count their entries in the registry.
43//
44// The typed function references and GC proposals change everything. The former
45// introduced function types that take a reference to a function of another
46// specific type. This is a type-to-type reference. The latter introduces struct
47// and array types that can have references to other struct, array, and function
48// types, as well as recursion groups that allow cyclic references between
49// types. Now type canonicalization additionally enables fast type checks and
50// downcasts *across* modules: so that two modules which define the same struct
51// type, for example, can pass instances of that struct type to each other, and
52// we can quickly check that those instances are in fact of the expected types.
53//
54// But how do we manage the lifetimes of types that can reference other types as
55// Wasm modules are dynamically loaded and unloaded from the engine? These
56// modules can define subsets of the same types and there can be cyclic type
57// references. Dynamic lifetimes, sharing, and cycles is a classic combination
58// of constraints that push a design towards a tracing garbage collector (or,
59// equivalently, a reference-counting collector with a cycle collector).
60//
61// However, we can rely on the following properties:
62//
63// 1. The unit of type canonicalization is a whole recursion group.
64//
65// 2. Type-to-type reference cycles may only happen within a recursion group and
66//    therefore type-to-type references across recursion groups are acyclic.
67//
68// Therefore, our type registry makes the following design decisions:
69//
70// * We manage the lifetime of whole recursion groups, not individual
71//   types. That is, every type in the recursion group stays alive as long as
72//   any type in the recursion group is kept alive. This is effectively mandated
73//   by property (1) and the hash consing it implies.
74//
75// * We still use naive reference counting to manage the lifetimes of recursion
76//   groups. A type-to-type reference that crosses the boundary from recursion
77//   group A to recursion group B will increment B's reference count when A is
78//   first registered and decrement B's reference count when A is removed from
79//   the registry. Because of property (2) we don't need to worry about cycles,
80//   which are the classic weakness of reference counting.
81
82/// Represents a collection of shared types.
83///
84/// This is used to register shared types with a shared type registry.
85///
86/// The collection will unregister any contained types with the registry
87/// when dropped.
88pub struct TypeCollection {
89    engine: Engine,
90    rec_groups: Vec<RecGroupEntry>,
91    types: PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
92    trampolines: SecondaryMap<VMSharedTypeIndex, PackedOption<ModuleInternedTypeIndex>>,
93}
94
95impl Debug for TypeCollection {
96    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97        let TypeCollection {
98            engine: _,
99            rec_groups,
100            types,
101            trampolines,
102        } = self;
103        f.debug_struct("TypeCollection")
104            .field("rec_groups", rec_groups)
105            .field("types", types)
106            .field("trampolines", trampolines)
107            .finish_non_exhaustive()
108    }
109}
110
111impl Engine {
112    /// Registers the given types in this engine, re-canonicalizing them for
113    /// runtime usage.
114    pub(crate) fn register_and_canonicalize_types<'a, I>(
115        &self,
116        module_types: &mut ModuleTypes,
117        env_modules: I,
118    ) -> TypeCollection
119    where
120        I: IntoIterator<Item = &'a mut wasmtime_environ::Module>,
121        I::IntoIter: ExactSizeIterator,
122    {
123        let engine = self.clone();
124        let registry = engine.signatures();
125        let gc_runtime = engine.gc_runtime().ok().map(|rt| &**rt);
126        let (rec_groups, types) = registry
127            .0
128            .write()
129            .register_module_types(gc_runtime, module_types);
130
131        // First, register the types in this engine's registry.
132        log::trace!("Begin building module's shared-to-module-trampoline-types map");
133        let mut trampolines = SecondaryMap::with_capacity(types.len());
134        for (module_ty, module_trampoline_ty) in module_types.trampoline_types() {
135            let shared_ty = types[module_ty];
136            let trampoline_shared_ty = registry.trampoline_type(shared_ty);
137            trampolines[trampoline_shared_ty] = Some(module_trampoline_ty).into();
138            log::trace!("--> shared_to_module_trampolines[{trampoline_shared_ty:?}] = {module_trampoline_ty:?}");
139        }
140        log::trace!("Done building module's shared-to-module-trampoline-types map");
141
142        // Second, re-canonicalize those types for runtime usage in this engine,
143        // replacing `ModuleInternedTypeIndex`es with the `VMSharedTypeIndex`es
144        // we just registered.
145        module_types.canonicalize_for_runtime_usage(&mut |idx| types[idx]);
146
147        // Third, re-canonicalize the types in our `wasmtime_environ::Module`s
148        // to point to the just-registered engine type indices.
149        for module in env_modules {
150            module.canonicalize_for_runtime_usage(&mut |idx| types[idx]);
151        }
152
153        TypeCollection {
154            engine,
155            rec_groups,
156            types,
157            trampolines,
158        }
159    }
160}
161
162impl TypeCollection {
163    /// Treats the type collection as a map from a module type index to
164    /// registered shared type indexes.
165    ///
166    /// This is used for looking up module shared type indexes during module
167    /// instantiation.
168    pub fn as_module_map(&self) -> &PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex> {
169        &self.types
170    }
171
172    /// Gets the shared type index given a module type index.
173    #[inline]
174    pub fn shared_type(&self, index: ModuleInternedTypeIndex) -> Option<VMSharedTypeIndex> {
175        let shared_ty = self.types.get(index).copied();
176        log::trace!("TypeCollection::shared_type({index:?}) -> {shared_ty:?}");
177        shared_ty
178    }
179
180    /// Get the module-level type index of the trampoline type for the given
181    /// engine-level function type, if any.
182    ///
183    /// This allows callers to look up the pre-compiled wasm-to-native
184    /// trampoline in this type collection's associated module.
185    ///
186    /// See the docs for `WasmFuncType::trampoline_type` for details on
187    /// trampoline types.
188    #[inline]
189    pub fn trampoline_type(&self, ty: VMSharedTypeIndex) -> Option<ModuleInternedTypeIndex> {
190        let trampoline_ty = self.trampolines[ty].expand();
191        log::trace!("TypeCollection::trampoline_type({ty:?}) -> {trampoline_ty:?}");
192        trampoline_ty
193    }
194}
195
196impl Drop for TypeCollection {
197    fn drop(&mut self) {
198        if !self.rec_groups.is_empty() {
199            self.engine
200                .signatures()
201                .0
202                .write()
203                .unregister_type_collection(self);
204        }
205    }
206}
207
208#[inline]
209fn shared_type_index_to_slab_id(index: VMSharedTypeIndex) -> SlabId {
210    assert!(!index.is_reserved_value());
211    SlabId::from_raw(index.bits())
212}
213
214#[inline]
215fn slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex {
216    let index = VMSharedTypeIndex::new(id.into_raw());
217    assert!(!index.is_reserved_value());
218    index
219}
220
221/// A Wasm type that has been registered in the engine's `TypeRegistry`.
222///
223/// Prevents its associated type from being unregistered while it is alive.
224///
225/// Automatically unregisters the type on drop. (Unless other `RegisteredTypes`
226/// are keeping the type registered).
227///
228/// Dereferences to its underlying `WasmSubType`.
229pub struct RegisteredType {
230    engine: Engine,
231    entry: RecGroupEntry,
232    ty: Arc<WasmSubType>,
233    index: VMSharedTypeIndex,
234    layout: Option<GcLayout>,
235}
236
237impl Debug for RegisteredType {
238    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
239        let RegisteredType {
240            engine: _,
241            entry: _,
242            ty,
243            index,
244            layout,
245        } = self;
246        f.debug_struct("RegisteredType")
247            .field("index", index)
248            .field("ty", ty)
249            .field("layout", layout)
250            .finish_non_exhaustive()
251    }
252}
253
254impl Clone for RegisteredType {
255    fn clone(&self) -> Self {
256        self.entry.incref("cloning RegisteredType");
257        RegisteredType {
258            engine: self.engine.clone(),
259            entry: self.entry.clone(),
260            ty: self.ty.clone(),
261            index: self.index,
262            layout: self.layout.clone(),
263        }
264    }
265}
266
267impl Drop for RegisteredType {
268    fn drop(&mut self) {
269        if self.entry.decref("dropping RegisteredType") {
270            self.engine
271                .signatures()
272                .0
273                .write()
274                .unregister_entry(self.entry.clone());
275        }
276    }
277}
278
279impl core::ops::Deref for RegisteredType {
280    type Target = WasmSubType;
281
282    fn deref(&self) -> &Self::Target {
283        &self.ty
284    }
285}
286
287impl PartialEq for RegisteredType {
288    fn eq(&self, other: &Self) -> bool {
289        let eq = self.index == other.index && Engine::same(&self.engine, &other.engine);
290
291        if cfg!(debug_assertions) && eq {
292            // If they are the same, then their rec group entries and
293            // `WasmSubType`s had better also be the same.
294            assert!(Arc::ptr_eq(&self.entry.0, &other.entry.0));
295            assert_eq!(self.ty, other.ty);
296        }
297
298        eq
299    }
300}
301
302impl Eq for RegisteredType {}
303
304impl Hash for RegisteredType {
305    fn hash<H: Hasher>(&self, state: &mut H) {
306        let ptr = Arc::as_ptr(&self.entry.0);
307        ptr.hash(state);
308    }
309}
310
311impl RegisteredType {
312    /// Constructs a new `RegisteredType`, registering the given type with the
313    /// engine's `TypeRegistry`.
314    pub fn new(engine: &Engine, ty: WasmSubType) -> RegisteredType {
315        let (entry, index, ty, layout) = {
316            log::trace!("RegisteredType::new({ty:?})");
317
318            let gc_runtime = engine.gc_runtime().ok().map(|rt| &**rt);
319            let mut inner = engine.signatures().0.write();
320
321            // It shouldn't be possible for users to construct non-canonical
322            // types via the embedding API, and the only other types they can
323            // get are already-canonicalized types from modules, so we shouldn't
324            // ever get non-canonical types here. Furthermore, this is only
325            // called internally to Wasmtime, so we shouldn't ever have an
326            // engine mismatch; those should be caught earlier.
327            inner.assert_canonicalized_for_runtime_usage_in_this_registry(&ty);
328
329            let entry = inner.register_singleton_rec_group(gc_runtime, ty);
330
331            let index = entry.0.shared_type_indices[0];
332            let id = shared_type_index_to_slab_id(index);
333            let ty = inner.types[id].clone();
334            let layout = inner.type_to_gc_layout.get(index).and_then(|l| l.clone());
335
336            (entry, index, ty, layout)
337        };
338
339        RegisteredType::from_parts(engine.clone(), entry, index, ty, layout)
340    }
341
342    /// Create an owning handle to the given index's associated type.
343    ///
344    /// This will prevent the associated type from being unregistered as long as
345    /// the returned `RegisteredType` is kept alive.
346    ///
347    /// Returns `None` if `index` is not registered in the given engine's
348    /// registry.
349    pub fn root(engine: &Engine, index: VMSharedTypeIndex) -> Option<RegisteredType> {
350        let (entry, ty, layout) = {
351            let id = shared_type_index_to_slab_id(index);
352            let inner = engine.signatures().0.read();
353
354            let ty = inner.types.get(id)?.clone();
355            let entry = inner.type_to_rec_group[index].clone().unwrap();
356            let layout = inner.type_to_gc_layout.get(index).and_then(|l| l.clone());
357
358            // NB: make sure to incref while the lock is held to prevent:
359            //
360            // * This thread: read locks registry, gets entry E, unlocks registry
361            // * Other thread: drops `RegisteredType` for entry E, decref
362            //   reaches zero, write locks registry, unregisters entry
363            // * This thread: increfs entry, but it isn't in the registry anymore
364            entry.incref("RegisteredType::root");
365
366            (entry, ty, layout)
367        };
368
369        Some(RegisteredType::from_parts(
370            engine.clone(),
371            entry,
372            index,
373            ty,
374            layout,
375        ))
376    }
377
378    /// Construct a new `RegisteredType`.
379    ///
380    /// It is the caller's responsibility to ensure that the entry's reference
381    /// count has already been incremented.
382    fn from_parts(
383        engine: Engine,
384        entry: RecGroupEntry,
385        index: VMSharedTypeIndex,
386        ty: Arc<WasmSubType>,
387        layout: Option<GcLayout>,
388    ) -> Self {
389        log::trace!(
390            "RegisteredType::from_parts({engine:?}, {entry:?}, {index:?}, {ty:?}, {layout:?})"
391        );
392        debug_assert!(entry.0.registrations.load(Acquire) != 0);
393        RegisteredType {
394            engine,
395            entry,
396            ty,
397            index,
398            layout,
399        }
400    }
401
402    /// Get the engine whose registry this type is registered within.
403    pub fn engine(&self) -> &Engine {
404        &self.engine
405    }
406
407    /// Get this registered type's index.
408    pub fn index(&self) -> VMSharedTypeIndex {
409        self.index
410    }
411
412    /// Get this registered type's GC layout, if any.
413    ///
414    /// Only struct and array types have GC layouts; function types do not have
415    /// layouts.
416    #[cfg(feature = "gc")]
417    pub fn layout(&self) -> Option<&GcLayout> {
418        self.layout.as_ref()
419    }
420}
421
422/// An entry in the type registry.
423///
424/// Implements `Borrow`, `Eq`, and `Hash` by forwarding to the underlying Wasm
425/// rec group, so that this can be a hash consing key. (We can't use
426/// `Arc<RecGroupEntryInner>` directly for this purpose because `Arc<T>` doesn't
427/// implement `Borrow<U>` when `T: Borrow<U>`).
428#[derive(Clone)]
429struct RecGroupEntry(Arc<RecGroupEntryInner>);
430
431impl Debug for RecGroupEntry {
432    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
433        struct Ptr<'a, P>(&'a P);
434        impl<P: fmt::Pointer> Debug for Ptr<'_, P> {
435            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
436                write!(f, "{:#p}", *self.0)
437            }
438        }
439
440        f.debug_struct("RecGroupEntry")
441            .field("ptr", &Ptr(&self.0))
442            .field("shared_type_indices", &self.0.shared_type_indices)
443            .field("hash_consing_key", &self.0.hash_consing_key)
444            .field("registrations", &self.0.registrations.load(Acquire))
445            .finish()
446    }
447}
448
449struct RecGroupEntryInner {
450    /// The Wasm rec group, canonicalized for hash consing.
451    hash_consing_key: WasmRecGroup,
452
453    /// The shared type indices for each type in this rec group.
454    shared_type_indices: Box<[VMSharedTypeIndex]>,
455
456    /// The number of times that this entry has been registered in the
457    /// `TypeRegistryInner`.
458    ///
459    /// This is an atomic counter so that cloning a `RegisteredType`, and
460    /// temporarily keeping a type registered, doesn't require locking the full
461    /// registry.
462    registrations: AtomicUsize,
463
464    /// Whether this entry has already been unregistered from the
465    /// `TypeRegistryInner`.
466    ///
467    /// This flag exists to detect and avoid double-unregistration bugs that
468    /// could otherwise occur in rare cases. See the comments in
469    /// `TypeRegistryInner::unregister_type` for details.
470    unregistered: AtomicBool,
471}
472
473impl PartialEq for RecGroupEntry {
474    fn eq(&self, other: &Self) -> bool {
475        self.0.hash_consing_key == other.0.hash_consing_key
476    }
477}
478
479impl Eq for RecGroupEntry {}
480
481impl Hash for RecGroupEntry {
482    fn hash<H: Hasher>(&self, state: &mut H) {
483        self.0.hash_consing_key.hash(state);
484    }
485}
486
487impl Borrow<WasmRecGroup> for RecGroupEntry {
488    fn borrow(&self) -> &WasmRecGroup {
489        &self.0.hash_consing_key
490    }
491}
492
493impl RecGroupEntry {
494    /// Increment the registration count.
495    fn incref(&self, why: &str) {
496        let old_count = self.0.registrations.fetch_add(1, AcqRel);
497        log::trace!(
498            "increment registration count for {self:?} (registrations -> {}): {why}",
499            old_count + 1
500        );
501    }
502
503    /// Decrement the registration count and return `true` if the registration
504    /// count reached zero and this entry should be removed from the registry.
505    #[must_use = "caller must remove entry from registry if `decref` returns `true`"]
506    fn decref(&self, why: &str) -> bool {
507        let old_count = self.0.registrations.fetch_sub(1, AcqRel);
508        debug_assert_ne!(old_count, 0);
509        log::trace!(
510            "decrement registration count for {self:?} (registrations -> {}): {why}",
511            old_count - 1
512        );
513        old_count == 1
514    }
515}
516
517#[derive(Debug, Default)]
518struct TypeRegistryInner {
519    // A hash map from a canonicalized-for-hash-consing rec group to its
520    // `VMSharedTypeIndex`es.
521    //
522    // There is an entry in this map for every rec group we have already
523    // registered. Before registering new rec groups, we first check this map to
524    // see if we've already registered an identical rec group that we should
525    // reuse instead.
526    hash_consing_map: HashSet<RecGroupEntry>,
527
528    // A map from `VMSharedTypeIndex::bits()` to the type index's associated
529    // Wasm type.
530    //
531    // These types are always canonicalized for runtime usage.
532    types: Slab<Arc<WasmSubType>>,
533
534    // A map that lets you walk backwards from a `VMSharedTypeIndex` to its
535    // `RecGroupEntry`.
536    type_to_rec_group: SecondaryMap<VMSharedTypeIndex, Option<RecGroupEntry>>,
537
538    // A map from a registered type to its complete list of supertypes.
539    //
540    // The supertypes are ordered from super- to subtype, i.e. the immediate
541    // parent supertype is the last element and the least-upper-bound of all
542    // supertypes is the first element.
543    //
544    // Types without any supertypes are omitted from this map. This means that
545    // we never allocate any backing storage for this map when Wasm GC is not in
546    // use.
547    type_to_supertypes: SecondaryMap<VMSharedTypeIndex, Option<Box<[VMSharedTypeIndex]>>>,
548
549    // A map from each registered function type to its trampoline type.
550    //
551    // Note that when a function type is its own trampoline type, then we omit
552    // the entry in this map as a memory optimization. This means that if only
553    // core Wasm function types are ever used, then we will never allocate any
554    // backing storage for this map. As a nice bonus, this also avoids cycles (a
555    // function type referencing itself) that our naive reference counting
556    // doesn't play well with.
557    type_to_trampoline: SecondaryMap<VMSharedTypeIndex, PackedOption<VMSharedTypeIndex>>,
558
559    // A map from each registered GC type to its layout.
560    //
561    // Function types do not have an entry in this map. Similar to the
562    // `type_to_{supertypes,trampoline}` maps, we completely omit the `None`
563    // entries for these types as a memory optimization.
564    type_to_gc_layout: SecondaryMap<VMSharedTypeIndex, Option<GcLayout>>,
565
566    // An explicit stack of entries that we are in the middle of dropping. Used
567    // to avoid recursion when dropping a type that is holding the last
568    // reference to another type, etc...
569    drop_stack: Vec<RecGroupEntry>,
570}
571
572impl TypeRegistryInner {
573    fn register_module_types(
574        &mut self,
575        gc_runtime: Option<&dyn GcRuntime>,
576        types: &ModuleTypes,
577    ) -> (
578        Vec<RecGroupEntry>,
579        PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
580    ) {
581        log::trace!("Start registering module types");
582
583        let mut entries = Vec::with_capacity(types.rec_groups().len());
584        let mut map = PrimaryMap::<ModuleInternedTypeIndex, VMSharedTypeIndex>::with_capacity(
585            types.wasm_types().len(),
586        );
587
588        for (_rec_group_index, module_group) in types.rec_groups() {
589            let entry = self.register_rec_group(
590                gc_runtime,
591                &map,
592                module_group.clone(),
593                iter_entity_range(module_group.clone()).map(|ty| types[ty].clone()),
594            );
595
596            for (module_ty, engine_ty) in
597                iter_entity_range(module_group).zip(entry.0.shared_type_indices.iter())
598            {
599                let module_ty2 = map.push(*engine_ty);
600                assert_eq!(module_ty, module_ty2);
601            }
602
603            entries.push(entry);
604        }
605
606        log::trace!("End registering module types");
607
608        (entries, map)
609    }
610
611    /// Register a rec group in this registry.
612    ///
613    /// The rec group may be either module-level canonical (i.e. straight from
614    /// `wasmparser`) or engine-level canonicalized for runtime usage in this
615    /// registry. It may *not* be engine-level canonicalized for hash consing or
616    /// engine-level canonicalized for a different type registry instance.
617    ///
618    /// If this rec group is determined to be a duplicate of an
619    /// already-registered rec group, the existing rec group is reused.
620    ///
621    /// Parameters:
622    ///
623    /// * `map`: A map that we use to canonicalize inter-group type references
624    ///   from module-canonical to engine-canonical indices. This must contain
625    ///   entries for each inter-group type reference that this rec group
626    ///   contains.
627    ///
628    /// * `range`: The range of (module-level) types defined by this rec
629    ///   group. This is used to determine which type references inside this rec
630    ///   group are inter- vs intra-group.
631    ///
632    /// * `types`: The types defined within this rec group. Must have the same
633    ///   length as `range`.
634    ///
635    /// The returned entry will have already had its reference count incremented
636    /// on behalf of callers.
637    fn register_rec_group(
638        &mut self,
639        gc_runtime: Option<&dyn GcRuntime>,
640        map: &PrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
641        range: Range<ModuleInternedTypeIndex>,
642        types: impl ExactSizeIterator<Item = WasmSubType>,
643    ) -> RecGroupEntry {
644        debug_assert_eq!(iter_entity_range(range.clone()).len(), types.len());
645
646        let mut non_canon_types = Vec::with_capacity(types.len());
647        let hash_consing_key = WasmRecGroup {
648            types: types
649                .zip(iter_entity_range(range.clone()))
650                .map(|(mut ty, module_index)| {
651                    non_canon_types.push((module_index, ty.clone()));
652                    ty.canonicalize_for_hash_consing(range.clone(), &mut |idx| {
653                        debug_assert!(idx < range.clone().start);
654                        map[idx]
655                    });
656                    ty
657                })
658                .collect::<Box<[_]>>(),
659        };
660
661        // If we've already registered this rec group before, reuse it.
662        if let Some(entry) = self.hash_consing_map.get(&hash_consing_key) {
663            assert_eq!(entry.0.unregistered.load(Acquire), false);
664            entry.incref(
665                "hash consed to already-registered type in `TypeRegistryInner::register_rec_group`",
666            );
667            return entry.clone();
668        }
669
670        // Inter-group edges: increment the referenced group's ref
671        // count, because these other rec groups shouldn't be dropped
672        // while this rec group is still alive.
673        hash_consing_key
674            .trace_engine_indices::<_, ()>(&mut |index| {
675                let other_entry = &self.type_to_rec_group[index].as_ref().unwrap();
676                assert_eq!(other_entry.0.unregistered.load(Acquire), false);
677                other_entry.incref(
678                    "new cross-group type reference to existing type in `register_rec_group`",
679                );
680                Ok(())
681            })
682            .unwrap();
683
684        // Register the individual types.
685        //
686        // Note that we can't update the reverse type-to-rec-group map until
687        // after we've constructed the `RecGroupEntry`, since that map needs to
688        // the fully-constructed entry for its values.
689        let module_rec_group_start = range.start;
690        let engine_rec_group_start = u32::try_from(self.types.len()).unwrap();
691        let shared_type_indices: Box<[_]> = non_canon_types
692            .into_iter()
693            .map(|(module_index, mut ty)| {
694                ty.canonicalize_for_runtime_usage(&mut |idx| {
695                    if idx < module_rec_group_start {
696                        map[idx]
697                    } else {
698                        let rec_group_offset = idx.as_u32() - module_rec_group_start.as_u32();
699                        let index =
700                            VMSharedTypeIndex::from_u32(engine_rec_group_start + rec_group_offset);
701                        assert!(!index.is_reserved_value());
702                        index
703                    }
704                });
705                self.insert_one_type_from_rec_group(gc_runtime, module_index, ty)
706            })
707            .collect();
708
709        debug_assert_eq!(
710            shared_type_indices.len(),
711            shared_type_indices
712                .iter()
713                .copied()
714                .inspect(|ty| assert!(!ty.is_reserved_value()))
715                .collect::<crate::hash_set::HashSet<_>>()
716                .len(),
717            "should not have any duplicate type indices",
718        );
719
720        let entry = RecGroupEntry(Arc::new(RecGroupEntryInner {
721            hash_consing_key,
722            shared_type_indices,
723            registrations: AtomicUsize::new(1),
724            unregistered: AtomicBool::new(false),
725        }));
726        log::trace!("create new entry {entry:?} (registrations -> 1)");
727
728        let is_new_entry = self.hash_consing_map.insert(entry.clone());
729        debug_assert!(is_new_entry);
730
731        // Now that we've constructed the entry, we can update the reverse
732        // type-to-rec-group map.
733        for ty in entry.0.shared_type_indices.iter().copied() {
734            debug_assert!(self.type_to_rec_group[ty].is_none());
735            self.type_to_rec_group[ty] = Some(entry.clone());
736        }
737
738        // Finally, make sure to register the trampoline type for each function
739        // type in the rec group.
740        for shared_type_index in entry.0.shared_type_indices.iter().copied() {
741            let slab_id = shared_type_index_to_slab_id(shared_type_index);
742            let sub_ty = &self.types[slab_id];
743            if let Some(f) = sub_ty.as_func() {
744                let trampoline = f.trampoline_type();
745                match &trampoline {
746                    Cow::Borrowed(_) if sub_ty.is_final && sub_ty.supertype.is_none() => {
747                        // The function type is its own trampoline type. Leave
748                        // its entry in `type_to_trampoline` empty to signal
749                        // this.
750                        log::trace!(
751                            "function type is its own trampoline type: \n\
752                             --> trampoline_type[{shared_type_index:?}] = {shared_type_index:?}\n\
753                             --> trampoline_type[{f}] = {f}"
754                        );
755                    }
756                    Cow::Borrowed(_) | Cow::Owned(_) => {
757                        // This will recursively call into rec group
758                        // registration, but at most once since trampoline
759                        // function types are their own trampoline type.
760                        let trampoline_entry = self.register_singleton_rec_group(
761                            gc_runtime,
762                            WasmSubType {
763                                is_final: true,
764                                supertype: None,
765                                composite_type: wasmtime_environ::WasmCompositeType {
766                                    shared: sub_ty.composite_type.shared,
767                                    inner: wasmtime_environ::WasmCompositeInnerType::Func(
768                                        trampoline.into_owned(),
769                                    ),
770                                },
771                            },
772                        );
773                        let trampoline_index = trampoline_entry.0.shared_type_indices[0];
774                        log::trace!(
775                            "Registering trampoline type:\n\
776                             --> trampoline_type[{shared_type_index:?}] = {trampoline_index:?}\n\
777                             --> trampoline_type[{f}] = {g}",
778                            f = {
779                                let slab_id = shared_type_index_to_slab_id(shared_type_index);
780                                self.types[slab_id].unwrap_func()
781                            },
782                            g = {
783                                let slab_id = shared_type_index_to_slab_id(trampoline_index);
784                                self.types[slab_id].unwrap_func()
785                            }
786                        );
787                        debug_assert_ne!(shared_type_index, trampoline_index);
788                        self.type_to_trampoline[shared_type_index] = Some(trampoline_index).into();
789                    }
790                }
791            }
792        }
793
794        entry
795    }
796
797    /// Is the given type canonicalized for runtime usage this registry?
798    fn assert_canonicalized_for_runtime_usage_in_this_registry(&self, ty: &WasmSubType) {
799        ty.trace::<_, ()>(&mut |index| match index {
800            EngineOrModuleTypeIndex::RecGroup(_) | EngineOrModuleTypeIndex::Module(_) => {
801                panic!("not canonicalized for runtime usage: {ty:?}")
802            }
803            EngineOrModuleTypeIndex::Engine(idx) => {
804                let id = shared_type_index_to_slab_id(idx);
805                assert!(
806                    self.types.contains(id),
807                    "canonicalized in a different engine? {ty:?}"
808                );
809                Ok(())
810            }
811        })
812        .unwrap();
813    }
814
815    /// Insert a new type as part of registering a new rec group.
816    ///
817    /// The type must be canonicalized for runtime usage in this registry and
818    /// its rec group must be a new one that we are currently registering, not
819    /// an already-registered rec group.
820    fn insert_one_type_from_rec_group(
821        &mut self,
822        gc_runtime: Option<&dyn GcRuntime>,
823        module_index: ModuleInternedTypeIndex,
824        ty: WasmSubType,
825    ) -> VMSharedTypeIndex {
826        // Despite being canonicalized for runtime usage, this type may still
827        // have forward references to other types in the rec group we haven't
828        // yet registered. Therefore, we can't use our usual
829        // `assert_canonicalized_for_runtime_usage_in_this_registry` helper here
830        // as that will see the forward references and think they must be
831        // references to types in other registries.
832        assert!(
833            ty.is_canonicalized_for_runtime_usage(),
834            "type is not canonicalized for runtime usage: {ty:?}"
835        );
836
837        assert!(!ty.composite_type.shared);
838        let gc_layout = match &ty.composite_type.inner {
839            wasmtime_environ::WasmCompositeInnerType::Func(_) => None,
840            wasmtime_environ::WasmCompositeInnerType::Array(a) => Some(
841                gc_runtime
842                    .expect("must have a GC runtime to register array types")
843                    .layouts()
844                    .array_layout(a)
845                    .into(),
846            ),
847            wasmtime_environ::WasmCompositeInnerType::Struct(s) => Some(
848                gc_runtime
849                    .expect("must have a GC runtime to register array types")
850                    .layouts()
851                    .struct_layout(s)
852                    .into(),
853            ),
854            wasmtime_environ::WasmCompositeInnerType::Cont(_) => todo!(), // FIXME: #10248 stack switching support.
855        };
856
857        // Add the type to our slab.
858        let id = self.types.alloc(Arc::new(ty));
859        let engine_index = slab_id_to_shared_type_index(id);
860        log::trace!(
861            "registered type {module_index:?} as {engine_index:?} = {:?}",
862            &self.types[id]
863        );
864
865        // Create the supertypes list for this type.
866        if let Some(supertype) = self.types[id].supertype {
867            let supertype = supertype.unwrap_engine_type_index();
868            let supers_supertypes = self.supertypes(supertype);
869            let mut supertypes = Vec::with_capacity(supers_supertypes.len() + 1);
870            supertypes.extend(
871                supers_supertypes
872                    .iter()
873                    .copied()
874                    .chain(iter::once(supertype)),
875            );
876            self.type_to_supertypes[engine_index] = Some(supertypes.into_boxed_slice());
877        }
878
879        // Only write the type-to-gc-layout entry if we have a GC layout, so
880        // that the map can avoid any heap allocation for backing storage in the
881        // case where Wasm GC is disabled.
882        if let Some(layout) = gc_layout {
883            self.type_to_gc_layout[engine_index] = Some(layout);
884        }
885
886        engine_index
887    }
888
889    /// Get the supertypes list for the given type.
890    ///
891    /// The supertypes are listed in super-to-sub order. `ty` itself is not
892    /// included in the list.
893    fn supertypes(&self, ty: VMSharedTypeIndex) -> &[VMSharedTypeIndex] {
894        self.type_to_supertypes
895            .get(ty)
896            .and_then(|s| s.as_deref())
897            .unwrap_or(&[])
898    }
899
900    /// Register a rec group consisting of a single type.
901    ///
902    /// The type must already be canonicalized for runtime usage in this
903    /// registry.
904    ///
905    /// The returned entry will have already had its reference count incremented
906    /// on behalf of callers.
907    fn register_singleton_rec_group(
908        &mut self,
909        gc_runtime: Option<&dyn GcRuntime>,
910        ty: WasmSubType,
911    ) -> RecGroupEntry {
912        self.assert_canonicalized_for_runtime_usage_in_this_registry(&ty);
913
914        // This type doesn't have any module-level type references, since it is
915        // already canonicalized for runtime usage in this registry, so an empty
916        // map suffices.
917        let map = PrimaryMap::default();
918
919        // This must have `range.len() == 1`, even though we know this type
920        // doesn't have any intra-group type references, to satisfy
921        // `register_rec_group`'s preconditions.
922        let range = ModuleInternedTypeIndex::from_bits(u32::MAX - 1)
923            ..ModuleInternedTypeIndex::from_bits(u32::MAX);
924
925        self.register_rec_group(gc_runtime, &map, range, iter::once(ty))
926    }
927
928    /// Unregister all of a type collection's rec groups.
929    fn unregister_type_collection(&mut self, collection: &TypeCollection) {
930        for entry in &collection.rec_groups {
931            if entry.decref("TypeRegistryInner::unregister_type_collection") {
932                self.unregister_entry(entry.clone());
933            }
934        }
935    }
936
937    /// Remove a zero-refcount entry from the registry.
938    ///
939    /// This does *not* decrement the entry's registration count, it should
940    /// instead be invoked only after a previous decrement operation observed
941    /// zero remaining registrations.
942    fn unregister_entry(&mut self, entry: RecGroupEntry) {
943        debug_assert!(self.drop_stack.is_empty());
944
945        // There are two races to guard against before we can unregister the
946        // entry, even though it was on the drop stack:
947        //
948        // 1. Although an entry has to reach zero registrations before it is
949        //    enqueued in the drop stack, we need to double check whether the
950        //    entry is *still* at zero registrations. This is because someone
951        //    else can resurrect the entry in between when the
952        //    zero-registrations count was first observed and when we actually
953        //    acquire the lock to unregister it. In this example, we have
954        //    threads A and B, an existing rec group entry E, and a rec group
955        //    entry E' that is a duplicate of E:
956        //
957        //    Thread A                        | Thread B
958        //    --------------------------------+-----------------------------
959        //    acquire(type registry lock)     |
960        //                                    |
961        //                                    | decref(E) --> 0
962        //                                    |
963        //                                    | block_on(type registry lock)
964        //                                    |
965        //    register(E') == incref(E) --> 1 |
966        //                                    |
967        //    release(type registry lock)     |
968        //                                    |
969        //                                    | acquire(type registry lock)
970        //                                    |
971        //                                    | unregister(E)         !!!!!!
972        //
973        //    If we aren't careful, we can unregister a type while it is still
974        //    in use!
975        //
976        //    The fix in this case is that we skip unregistering the entry if
977        //    its reference count is non-zero, since that means it was
978        //    concurrently resurrected and is now in use again.
979        //
980        // 2. In a slightly more convoluted version of (1), where an entry is
981        //    resurrected but then dropped *again*, someone might attempt to
982        //    unregister an entry a second time:
983        //
984        //    Thread A                        | Thread B
985        //    --------------------------------|-----------------------------
986        //    acquire(type registry lock)     |
987        //                                    |
988        //                                    | decref(E) --> 0
989        //                                    |
990        //                                    | block_on(type registry lock)
991        //                                    |
992        //    register(E') == incref(E) --> 1 |
993        //                                    |
994        //    release(type registry lock)     |
995        //                                    |
996        //    decref(E) --> 0                 |
997        //                                    |
998        //    acquire(type registry lock)     |
999        //                                    |
1000        //    unregister(E)                   |
1001        //                                    |
1002        //    release(type registry lock)     |
1003        //                                    |
1004        //                                    | acquire(type registry lock)
1005        //                                    |
1006        //                                    | unregister(E)         !!!!!!
1007        //
1008        //    If we aren't careful, we can unregister a type twice, which leads
1009        //    to panics and registry corruption!
1010        //
1011        //    To detect this scenario and avoid the double-unregistration bug,
1012        //    we maintain an `unregistered` flag on entries. We set this flag
1013        //    once an entry is unregistered and therefore, even if it is
1014        //    enqueued in the drop stack multiple times, we only actually
1015        //    unregister the entry the first time.
1016        //
1017        // A final note: we don't need to worry about any concurrent
1018        // modifications during the middle of this function's execution, only
1019        // between (a) when we first observed a zero-registrations count and
1020        // decided to unregister the type, and (b) when we acquired the type
1021        // registry's lock so that we could perform that unregistration. This is
1022        // because this method has exclusive access to `&mut self` -- that is,
1023        // we have a write lock on the whole type registry -- and therefore no
1024        // one else can create new references to this zero-registration entry
1025        // and bring it back to life (which would require finding it in
1026        // `self.hash_consing_map`, which no one else has access to, because we
1027        // now have an exclusive lock on `self`).
1028
1029        // Handle scenario (1) from above.
1030        let registrations = entry.0.registrations.load(Acquire);
1031        if registrations != 0 {
1032            log::trace!(
1033                "{entry:?} was concurrently resurrected and no longer has \
1034                 zero registrations (registrations -> {registrations})",
1035            );
1036            assert_eq!(entry.0.unregistered.load(Acquire), false);
1037            return;
1038        }
1039
1040        // Handle scenario (2) from above.
1041        if entry.0.unregistered.load(Acquire) {
1042            log::trace!(
1043                "{entry:?} was concurrently resurrected, dropped again, \
1044                 and already unregistered"
1045            );
1046            return;
1047        }
1048
1049        // Okay, we are really going to unregister this entry. Enqueue it on the
1050        // drop stack.
1051        self.drop_stack.push(entry);
1052
1053        // Keep unregistering entries until the drop stack is empty. This is
1054        // logically a recursive process where if we unregister a type that was
1055        // the only thing keeping another type alive, we then recursively
1056        // unregister that other type as well. However, we use this explicit
1057        // drop stack to avoid recursion and the potential stack overflows that
1058        // recursion implies.
1059        while let Some(entry) = self.drop_stack.pop() {
1060            log::trace!("Start unregistering {entry:?}");
1061
1062            // All entries on the drop stack should *really* be ready for
1063            // unregistration, since no one can resurrect entries once we've
1064            // locked the registry.
1065            assert_eq!(entry.0.registrations.load(Acquire), 0);
1066            assert_eq!(entry.0.unregistered.load(Acquire), false);
1067
1068            // We are taking responsibility for unregistering this entry, so
1069            // prevent anyone else from attempting to do it again.
1070            entry.0.unregistered.store(true, Release);
1071
1072            // Decrement any other types that this type was shallowly
1073            // (i.e. non-transitively) referencing and keeping alive. If this
1074            // was the last thing keeping them registered, its okay to
1075            // unregister them as well now.
1076            debug_assert!(entry.0.hash_consing_key.is_canonicalized_for_hash_consing());
1077            entry
1078                .0
1079                .hash_consing_key
1080                .trace_engine_indices::<_, ()>(&mut |other_index| {
1081                    let other_entry = self.type_to_rec_group[other_index].as_ref().unwrap();
1082                    if other_entry.decref(
1083                        "referenced by dropped entry in \
1084                         `TypeCollection::unregister_entry`",
1085                    ) {
1086                        self.drop_stack.push(other_entry.clone());
1087                    }
1088                    Ok(())
1089                })
1090                .unwrap();
1091
1092            // Remove the entry from the hash-consing map. If we register a
1093            // duplicate definition of this rec group again in the future, it
1094            // will be as if it is the first time it has ever been registered,
1095            // and it will be inserted into the hash-consing map again at that
1096            // time.
1097            self.hash_consing_map.remove(&entry);
1098
1099            // Similarly, remove the rec group's types from the registry, as
1100            // well as their entries from the reverse type-to-rec-group
1101            // map. Additionally, stop holding a strong reference from each
1102            // function type in the rec group to that function type's trampoline
1103            // type.
1104            debug_assert_eq!(
1105                entry.0.shared_type_indices.len(),
1106                entry
1107                    .0
1108                    .shared_type_indices
1109                    .iter()
1110                    .copied()
1111                    .inspect(|ty| assert!(!ty.is_reserved_value()))
1112                    .collect::<crate::hash_set::HashSet<_>>()
1113                    .len(),
1114                "should not have any duplicate type indices",
1115            );
1116            for ty in entry.0.shared_type_indices.iter().copied() {
1117                log::trace!("removing {ty:?} from registry");
1118
1119                let removed_entry = self.type_to_rec_group[ty].take();
1120                debug_assert_eq!(removed_entry.unwrap(), entry);
1121
1122                // Remove the associated trampoline type, if any.
1123                if let Some(trampoline_ty) =
1124                    self.type_to_trampoline.get(ty).and_then(|x| x.expand())
1125                {
1126                    self.type_to_trampoline[ty] = None.into();
1127                    let trampoline_entry = self.type_to_rec_group[trampoline_ty].as_ref().unwrap();
1128                    if trampoline_entry
1129                        .decref("removing reference from a function type to its trampoline type")
1130                    {
1131                        self.drop_stack.push(trampoline_entry.clone());
1132                    }
1133                }
1134
1135                // Remove the type's supertypes list, if any. Take care to guard
1136                // this assignment so that we don't accidentally force the
1137                // secondary map to allocate even when we never actually use
1138                // Wasm GC.
1139                if self.type_to_supertypes.get(ty).is_some() {
1140                    self.type_to_supertypes[ty] = None;
1141                }
1142
1143                // Same as above, but for the type's GC layout.
1144                if self.type_to_gc_layout.get(ty).is_some() {
1145                    self.type_to_gc_layout[ty] = None;
1146                }
1147
1148                let id = shared_type_index_to_slab_id(ty);
1149                self.types.dealloc(id);
1150            }
1151
1152            log::trace!("End unregistering {entry:?}");
1153        }
1154    }
1155}
1156
1157// `TypeRegistryInner` implements `Drop` in debug builds to assert that
1158// all types have been unregistered for the registry.
1159#[cfg(debug_assertions)]
1160impl Drop for TypeRegistryInner {
1161    fn drop(&mut self) {
1162        log::trace!("Dropping type registry: {self:#?}");
1163        let TypeRegistryInner {
1164            hash_consing_map,
1165            types,
1166            type_to_rec_group,
1167            type_to_supertypes,
1168            type_to_trampoline,
1169            type_to_gc_layout,
1170            drop_stack,
1171        } = self;
1172        assert!(
1173            hash_consing_map.is_empty(),
1174            "type registry not empty: hash consing map is not empty: {hash_consing_map:#?}"
1175        );
1176        assert!(
1177            types.is_empty(),
1178            "type registry not empty: types slab is not empty: {types:#?}"
1179        );
1180        assert!(
1181            type_to_rec_group.is_empty() || type_to_rec_group.values().all(|x| x.is_none()),
1182            "type registry not empty: type-to-rec-group map is not empty: {type_to_rec_group:#?}"
1183        );
1184        assert!(
1185            type_to_supertypes.is_empty() || type_to_supertypes.values().all(|x| x.is_none()),
1186            "type registry not empty: type-to-supertypes map is not empty: {type_to_supertypes:#?}"
1187        );
1188        assert!(
1189            type_to_trampoline.is_empty() || type_to_trampoline.values().all(|x| x.is_none()),
1190            "type registry not empty: type-to-trampoline map is not empty: {type_to_trampoline:#?}"
1191        );
1192        assert!(
1193            type_to_gc_layout.is_empty() || type_to_gc_layout.values().all(|x| x.is_none()),
1194            "type registry not empty: type-to-gc-layout map is not empty: {type_to_gc_layout:#?}"
1195        );
1196        assert!(
1197            drop_stack.is_empty(),
1198            "type registry not empty: drop stack is not empty: {drop_stack:#?}"
1199        );
1200    }
1201}
1202
1203/// Implements a shared type registry.
1204///
1205/// WebAssembly requires that the caller and callee types in an indirect
1206/// call must match. To implement this efficiently, keep a registry of all
1207/// types, shared by all instances, so that call sites can just do an
1208/// index comparison.
1209#[derive(Debug)]
1210pub struct TypeRegistry(RwLock<TypeRegistryInner>);
1211
1212impl TypeRegistry {
1213    /// Creates a new shared type registry.
1214    pub fn new() -> Self {
1215        Self(RwLock::new(TypeRegistryInner::default()))
1216    }
1217
1218    /// Looks up a function type from a shared type index.
1219    ///
1220    /// This does *NOT* prevent the type from being unregistered while you are
1221    /// still using the resulting value! Use the `RegisteredType::root`
1222    /// constructor if you need to ensure that property and you don't have some
1223    /// other mechanism already keeping the type registered.
1224    pub fn borrow(&self, index: VMSharedTypeIndex) -> Option<Arc<WasmSubType>> {
1225        let id = shared_type_index_to_slab_id(index);
1226        let inner = self.0.read();
1227        inner.types.get(id).cloned()
1228    }
1229
1230    /// Get the GC layout for the given index's type.
1231    ///
1232    /// Returns `None` for types that do not have GC layouts (i.e. function
1233    /// types).
1234    pub fn layout(&self, index: VMSharedTypeIndex) -> Option<GcLayout> {
1235        let inner = self.0.read();
1236        inner.type_to_gc_layout.get(index).and_then(|l| l.clone())
1237    }
1238
1239    /// Get the trampoline type for the given function type index.
1240    ///
1241    /// Panics for non-function type indices.
1242    pub fn trampoline_type(&self, index: VMSharedTypeIndex) -> VMSharedTypeIndex {
1243        let slab_id = shared_type_index_to_slab_id(index);
1244        let inner = self.0.read();
1245
1246        let ty = &inner.types[slab_id];
1247        debug_assert!(
1248            ty.is_func(),
1249            "cannot get the trampoline type of a non-function type: {index:?} = {ty:?}"
1250        );
1251
1252        let trampoline_ty = match inner.type_to_trampoline.get(index).and_then(|x| x.expand()) {
1253            Some(ty) => ty,
1254            None => {
1255                // The function type is its own trampoline type.
1256                index
1257            }
1258        };
1259        log::trace!("TypeRegistry::trampoline_type({index:?}) -> {trampoline_ty:?}");
1260        trampoline_ty
1261    }
1262
1263    /// Is type `sub` a subtype of `sup`?
1264    #[inline]
1265    pub fn is_subtype(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
1266        if sub == sup {
1267            return true;
1268        }
1269
1270        self.is_subtype_slow(sub, sup)
1271    }
1272
1273    fn is_subtype_slow(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
1274        // Do the O(1) subtype checking trick:
1275        //
1276        // In a type system with single inheritance, the subtyping relationships
1277        // between all types form a set of trees. The root of each tree is a
1278        // type that has no supertype; each node's immediate children are the
1279        // types that directly subtype that node.
1280        //
1281        // For example, consider these types:
1282        //
1283        //     class Base {}
1284        //     class A subtypes Base {}
1285        //     class B subtypes Base {}
1286        //     class C subtypes A {}
1287        //     class D subtypes A {}
1288        //     class E subtypes C {}
1289        //
1290        // These types produce the following tree:
1291        //
1292        //                Base
1293        //               /    \
1294        //              A      B
1295        //             / \
1296        //            C   D
1297        //           /
1298        //          E
1299        //
1300        // Note the following properties:
1301        //
1302        // 1. If `sub` is a subtype of `sup` (either directly or transitively)
1303        //    then `sup` *must* be on the path from `sub` up to the root of
1304        //    `sub`'s tree.
1305        //
1306        // 2. Additionally, `sup` *must* be the `i`th node down from the root in
1307        //    that path, where `i` is the length of the path from `sup` to its
1308        //    tree's root.
1309        //
1310        // Therefore, if we have the path to the root for each type (we do) then
1311        // we can simply check if `sup` is at index `supertypes(sup).len()`
1312        // within `supertypes(sub)`.
1313        let inner = self.0.read();
1314        let sub_supertypes = inner.supertypes(sub);
1315        let sup_supertypes = inner.supertypes(sup);
1316        sub_supertypes.get(sup_supertypes.len()) == Some(&sup)
1317    }
1318}