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}