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
6 use crate::Engine;
7 use crate::error::OutOfMemory;
8 use crate::prelude::*;
9 use crate::sync::RwLock;
10 use crate::vm::GcRuntime;
11 use alloc::sync::Arc;
12 use core::cell::Cell;
13 use core::iter;
14 use 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 };
24 use wasmtime_core::slab::{Id as SlabId, Slab};
25 use wasmtime_environ::{
26 EngineOrModuleTypeIndex, EntityRef, GcLayout, ModuleInternedTypeIndex, ModuleTypes, TypeTrace,
27 Undo, VMSharedTypeIndex, WasmRecGroup, WasmSubType,
28 collections::TryCow,
29 iter_entity_range,
30 packed_option::{PackedOption, ReservedValue},
31 };
32
33 // ### Notes on the Lifetime Management of Types
34 //
35 // All defined types from all Wasm modules loaded into Wasmtime are interned
36 // into their engine's `TypeRegistry`.
37 //
38 // With Wasm MVP, managing type lifetimes within the registry was easy: we only
39 // cared about canonicalizing types so that `call_indirect` was fast and we
40 // didn't waste memory on many copies of the same function type definition.
41 // Function types could only take and return simple scalars (i32/f64/etc...) and
42 // there were no type-to-type references. We could simply deduplicate function
43 // types and reference count their entries in the registry.
44 //
45 // The typed function references and GC proposals change everything. The former
46 // introduced function types that take a reference to a function of another
47 // specific type. This is a type-to-type reference. The latter introduces struct
48 // and array types that can have references to other struct, array, and function
49 // types, as well as recursion groups that allow cyclic references between
50 // types. Now type canonicalization additionally enables fast type checks and
51 // downcasts *across* modules: so that two modules which define the same struct
52 // type, for example, can pass instances of that struct type to each other, and
53 // we can quickly check that those instances are in fact of the expected types.
54 //
55 // But how do we manage the lifetimes of types that can reference other types as
56 // Wasm modules are dynamically loaded and unloaded from the engine? These
57 // modules can define subsets of the same types and there can be cyclic type
58 // references. Dynamic lifetimes, sharing, and cycles is a classic combination
59 // of constraints that push a design towards a tracing garbage collector (or,
60 // equivalently, a reference-counting collector with a cycle collector).
61 //
62 // However, we can rely on the following properties:
63 //
64 // 1. The unit of type canonicalization is a whole recursion group.
65 //
66 // 2. Type-to-type reference cycles may only happen within a recursion group and
67 // therefore type-to-type references across recursion groups are acyclic.
68 //
69 // Therefore, our type registry makes the following design decisions:
70 //
71 // * We manage the lifetime of whole recursion groups, not individual
72 // types. That is, every type in the recursion group stays alive as long as
73 // any type in the recursion group is kept alive. This is effectively mandated
74 // by property (1) and the hash consing it implies.
75 //
76 // * We still use naive reference counting to manage the lifetimes of recursion
77 // groups. A type-to-type reference that crosses the boundary from recursion
78 // group A to recursion group B will increment B's reference count when A is
79 // first registered and decrement B's reference count when A is removed from
80 // the registry. Because of property (2) we don't need to worry about cycles,
81 // which are the classic weakness of reference counting.
82
83 /// Represents a collection of shared types.
84 ///
85 /// This is used to register shared types with a shared type registry.
86 ///
87 /// The collection will unregister any contained types with the registry
88 /// when dropped.
89 pub struct TypeCollection {
90 engine: Engine,
91 rec_groups: TryVec<RecGroupEntry>,
92 types: TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
93 trampolines: TrySecondaryMap<VMSharedTypeIndex, PackedOption<ModuleInternedTypeIndex>>,
94 }
95
96 impl Debug for TypeCollection {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result97 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
98 let TypeCollection {
99 engine: _,
100 rec_groups,
101 types,
102 trampolines,
103 } = self;
104 f.debug_struct("TypeCollection")
105 .field("rec_groups", rec_groups)
106 .field("types", types)
107 .field("trampolines", trampolines)
108 .finish_non_exhaustive()
109 }
110 }
111
112 impl Engine {
113 /// Registers the given types in this engine, re-canonicalizing them for
114 /// runtime usage.
115 #[must_use = "types are only registered as long as the `TypeCollection` is live"]
register_and_canonicalize_types<'a, I>( &self, module_types: &mut ModuleTypes, env_modules: I, ) -> Result<TypeCollection, OutOfMemory> where I: IntoIterator<Item = &'a mut wasmtime_environ::Module>, I::IntoIter: ExactSizeIterator,116 pub(crate) fn register_and_canonicalize_types<'a, I>(
117 &self,
118 module_types: &mut ModuleTypes,
119 env_modules: I,
120 ) -> Result<TypeCollection, OutOfMemory>
121 where
122 I: IntoIterator<Item = &'a mut wasmtime_environ::Module>,
123 I::IntoIter: ExactSizeIterator,
124 {
125 if cfg!(debug_assertions) {
126 module_types
127 .trace(&mut |idx| match idx {
128 EngineOrModuleTypeIndex::Module(_) => Ok(()),
129 EngineOrModuleTypeIndex::Engine(_) | EngineOrModuleTypeIndex::RecGroup(_) => {
130 Err(idx)
131 }
132 })
133 .expect("should only have module type indices");
134 }
135
136 let engine = self.clone();
137 let registry = engine.signatures();
138 let gc_runtime = engine.gc_runtime().map(|rt| &**rt);
139
140 // First, register these types in this engine's registry.
141 let (rec_groups, types) = registry
142 .0
143 .write()
144 .register_module_types(gc_runtime, module_types)?;
145
146 // Then build our map from each function type's engine index to the
147 // module-index of its trampoline. Trampoline functions are queried by
148 // module-index in a compiled module, and doing this engine-to-module
149 // resolution now means we don't need to do it on the function call hot
150 // path.
151 let mut trampolines = TrySecondaryMap::with_capacity(types.len())?;
152 for (module_ty, module_trampoline_ty) in module_types.trampoline_types() {
153 let shared_ty = types[module_ty];
154 let trampoline_shared_ty = registry.trampoline_type(shared_ty);
155 trampolines
156 .insert(trampoline_shared_ty, Some(module_trampoline_ty).into())
157 .expect("reserved space");
158 }
159
160 // Finally, to ensure that no matter which API from which layer
161 // (`wasmtime::runtime::vm` vs `wasmtime_environ`, etc...) we use to
162 // grab an entity's type, we will always end up with a type that has
163 // `VMSharedTypeIndex` rather than `ModuleInternedTypeIndex` type
164 // references, we canonicalize both the `ModuleTypes` and
165 // `wasmtime_environ::Module`s for runtime usage. All our type-of-X APIs
166 // ultimately use one of these two structures.
167 module_types.canonicalize_for_runtime_usage(&mut |idx| types[idx]);
168 for module in env_modules {
169 module.canonicalize_for_runtime_usage(&mut |idx| types[idx]);
170 }
171
172 Ok(TypeCollection {
173 engine,
174 rec_groups,
175 types,
176 trampolines,
177 })
178 }
179 }
180
181 impl TypeCollection {
182 /// Treats the type collection as a map from a module type index to
183 /// registered shared type indexes.
184 ///
185 /// This is used for looking up module shared type indexes during module
186 /// instantiation.
as_module_map(&self) -> &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>187 pub fn as_module_map(&self) -> &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex> {
188 &self.types
189 }
190
191 /// Gets the shared type index given a module type index.
192 #[inline]
shared_type(&self, index: ModuleInternedTypeIndex) -> Option<VMSharedTypeIndex>193 pub fn shared_type(&self, index: ModuleInternedTypeIndex) -> Option<VMSharedTypeIndex> {
194 let shared_ty = self.types.get(index).copied();
195 log::trace!("TypeCollection::shared_type({index:?}) -> {shared_ty:?}");
196 shared_ty
197 }
198
199 /// Get the module-level type index of the trampoline type for the given
200 /// engine-level function type, if any.
201 ///
202 /// This allows callers to look up the pre-compiled wasm-to-native
203 /// trampoline in this type collection's associated module.
204 ///
205 /// See the docs for `WasmFuncType::trampoline_type` for details on
206 /// trampoline types.
207 #[inline]
trampoline_type(&self, ty: VMSharedTypeIndex) -> Option<ModuleInternedTypeIndex>208 pub fn trampoline_type(&self, ty: VMSharedTypeIndex) -> Option<ModuleInternedTypeIndex> {
209 let trampoline_ty = self.trampolines[ty].expand();
210 log::trace!("TypeCollection::trampoline_type({ty:?}) -> {trampoline_ty:?}");
211 trampoline_ty
212 }
213 }
214
215 impl Drop for TypeCollection {
drop(&mut self)216 fn drop(&mut self) {
217 if !self.rec_groups.is_empty() {
218 self.engine
219 .signatures()
220 .0
221 .write()
222 .unregister_type_collection(self);
223 }
224 }
225 }
226
227 #[inline]
shared_type_index_to_slab_id(index: VMSharedTypeIndex) -> SlabId228 fn shared_type_index_to_slab_id(index: VMSharedTypeIndex) -> SlabId {
229 assert!(!index.is_reserved_value());
230 SlabId::from_raw(index.bits())
231 }
232
233 #[inline]
slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex234 fn slab_id_to_shared_type_index(id: SlabId) -> VMSharedTypeIndex {
235 let index = VMSharedTypeIndex::new(id.into_raw());
236 assert!(!index.is_reserved_value());
237 index
238 }
239
240 /// A Wasm type that has been registered in the engine's `TypeRegistry`.
241 ///
242 /// Prevents its associated type from being unregistered while it is alive.
243 ///
244 /// Automatically unregisters the type on drop. (Unless other `RegisteredTypes`
245 /// are keeping the type registered).
246 ///
247 /// Dereferences to its underlying `WasmSubType`.
248 pub struct RegisteredType {
249 engine: Engine,
250 entry: RecGroupEntry,
251 ty: Arc<WasmSubType>,
252 index: VMSharedTypeIndex,
253 layout: Option<GcLayout>,
254 }
255
256 impl Debug for RegisteredType {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result257 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
258 let RegisteredType {
259 engine: _,
260 entry: _,
261 ty,
262 index,
263 layout,
264 } = self;
265 f.debug_struct("RegisteredType")
266 .field("index", index)
267 .field("ty", ty)
268 .field("layout", layout)
269 .finish_non_exhaustive()
270 }
271 }
272
273 impl Clone for RegisteredType {
clone(&self) -> Self274 fn clone(&self) -> Self {
275 self.clone_panic_on_oom()
276 }
277 }
278
279 impl TryClone for RegisteredType {
try_clone(&self) -> Result<Self, OutOfMemory>280 fn try_clone(&self) -> Result<Self, OutOfMemory> {
281 self.engine.signatures().debug_assert_contains(self.index);
282 self.entry.incref("RegisteredType::try_clone");
283 Ok(RegisteredType {
284 engine: self.engine.clone(),
285 entry: self.entry.clone(),
286 ty: self.ty.clone(),
287 index: self.index,
288 layout: self.layout.clone(),
289 })
290 }
291 }
292
293 impl Drop for RegisteredType {
drop(&mut self)294 fn drop(&mut self) {
295 self.engine.signatures().debug_assert_contains(self.index);
296 if self.entry.decref("RegisteredType::drop") {
297 self.engine
298 .signatures()
299 .0
300 .write()
301 .unregister_entry(self.entry.clone());
302 }
303 }
304 }
305
306 impl core::ops::Deref for RegisteredType {
307 type Target = WasmSubType;
308
deref(&self) -> &Self::Target309 fn deref(&self) -> &Self::Target {
310 &self.ty
311 }
312 }
313
314 impl PartialEq for RegisteredType {
eq(&self, other: &Self) -> bool315 fn eq(&self, other: &Self) -> bool {
316 self.engine.signatures().debug_assert_contains(self.index);
317 other.engine.signatures().debug_assert_contains(other.index);
318
319 let eq = self.index == other.index && Engine::same(&self.engine, &other.engine);
320
321 if cfg!(debug_assertions) && eq {
322 // If they are the same, then their rec group entries and
323 // `WasmSubType`s had better also be the same.
324 assert!(Arc::ptr_eq(&self.entry.0, &other.entry.0));
325 assert_eq!(self.ty, other.ty);
326 }
327
328 eq
329 }
330 }
331
332 impl Eq for RegisteredType {}
333
334 impl Hash for RegisteredType {
hash<H: Hasher>(&self, state: &mut H)335 fn hash<H: Hasher>(&self, state: &mut H) {
336 self.engine.signatures().debug_assert_contains(self.index);
337 let ptr = Arc::as_ptr(&self.entry.0);
338 ptr.hash(state);
339 }
340 }
341
342 impl RegisteredType {
343 /// Constructs a new `RegisteredType`, registering the given type with the
344 /// engine's `TypeRegistry`.
new(engine: &Engine, ty: WasmSubType) -> Result<RegisteredType, OutOfMemory>345 pub fn new(engine: &Engine, ty: WasmSubType) -> Result<RegisteredType, OutOfMemory> {
346 let (entry, index, ty, layout) = {
347 log::trace!("RegisteredType::new({ty:?})");
348
349 let gc_runtime = engine.gc_runtime().map(|rt| &**rt);
350 let mut inner = engine.signatures().0.write();
351
352 // It shouldn't be possible for users to construct non-canonical
353 // types via the embedding API, and the only other types they can
354 // get are already-canonicalized types from modules, so we shouldn't
355 // ever get non-canonical types here. Furthermore, this is only
356 // called internally to Wasmtime, so we shouldn't ever have an
357 // engine mismatch; those should be caught earlier.
358 inner.assert_canonicalized_for_runtime_usage_in_this_registry(&ty);
359
360 let entry = inner.register_singleton_rec_group(gc_runtime, ty)?;
361
362 let index = entry.0.shared_type_indices[0];
363 let id = shared_type_index_to_slab_id(index);
364 let ty = inner.types[id].clone().unwrap();
365 let layout = inner.type_to_gc_layout.get(index).and_then(|l| l.clone());
366
367 (entry, index, ty, layout)
368 };
369
370 Ok(RegisteredType::from_parts(
371 engine.clone(),
372 entry,
373 index,
374 ty,
375 layout,
376 ))
377 }
378
379 /// Create an owning handle to the given index's associated type.
380 ///
381 /// This will prevent the associated type from being unregistered as long as
382 /// the returned `RegisteredType` is kept alive.
root(engine: &Engine, index: VMSharedTypeIndex) -> RegisteredType383 pub fn root(engine: &Engine, index: VMSharedTypeIndex) -> RegisteredType {
384 engine.signatures().debug_assert_contains(index);
385
386 let (entry, ty, layout) = {
387 let id = shared_type_index_to_slab_id(index);
388 let inner = engine.signatures().0.read();
389
390 let ty = inner.types[id].clone().unwrap();
391 let entry = inner.type_to_rec_group[index].clone().unwrap();
392 let layout = inner.type_to_gc_layout.get(index).and_then(|l| l.clone());
393
394 // NB: make sure to incref while the lock is held to prevent:
395 //
396 // * This thread: read locks registry, gets entry E, unlocks registry
397 // * Other thread: drops `RegisteredType` for entry E, decref
398 // reaches zero, write locks registry, unregisters entry
399 // * This thread: increfs entry, but it isn't in the registry anymore
400 entry.incref("RegisteredType::root");
401
402 (entry, ty, layout)
403 };
404
405 RegisteredType::from_parts(engine.clone(), entry, index, ty, layout)
406 }
407
408 /// Construct a new `RegisteredType`.
409 ///
410 /// It is the caller's responsibility to ensure that the entry's reference
411 /// count has already been incremented.
from_parts( engine: Engine, entry: RecGroupEntry, index: VMSharedTypeIndex, ty: Arc<WasmSubType>, layout: Option<GcLayout>, ) -> Self412 fn from_parts(
413 engine: Engine,
414 entry: RecGroupEntry,
415 index: VMSharedTypeIndex,
416 ty: Arc<WasmSubType>,
417 layout: Option<GcLayout>,
418 ) -> Self {
419 log::trace!(
420 "RegisteredType::from_parts({engine:?}, {entry:?}, {index:?}, {ty:?}, {layout:?})"
421 );
422 engine.signatures().debug_assert_contains(index);
423 debug_assert!(
424 entry.0.registrations.load(Acquire) != 0,
425 "entry should have a non-zero registration count"
426 );
427 RegisteredType {
428 engine,
429 entry,
430 ty,
431 index,
432 layout,
433 }
434 }
435
436 /// Get the engine whose registry this type is registered within.
engine(&self) -> &Engine437 pub fn engine(&self) -> &Engine {
438 &self.engine
439 }
440
441 /// Get this registered type's index.
index(&self) -> VMSharedTypeIndex442 pub fn index(&self) -> VMSharedTypeIndex {
443 self.index
444 }
445
446 /// Get this registered type's GC layout, if any.
447 ///
448 /// Only struct and array types have GC layouts; function types do not have
449 /// layouts.
450 #[cfg(feature = "gc")]
layout(&self) -> Option<&GcLayout>451 pub fn layout(&self) -> Option<&GcLayout> {
452 self.layout.as_ref()
453 }
454 }
455
456 /// An entry in the type registry.
457 ///
458 /// Implements `Borrow`, `Eq`, and `Hash` by forwarding to the underlying Wasm
459 /// rec group, so that this can be a hash consing key. (We can't use
460 /// `Arc<RecGroupEntryInner>` directly for this purpose because `Arc<T>` doesn't
461 /// implement `Borrow<U>` when `T: Borrow<U>`).
462 #[derive(Clone)]
463 struct RecGroupEntry(Arc<RecGroupEntryInner>);
464
465 impl TryClone for RecGroupEntry {
try_clone(&self) -> Result<Self, OutOfMemory>466 fn try_clone(&self) -> Result<Self, OutOfMemory> {
467 Ok(self.clone())
468 }
469 }
470
471 impl Debug for RecGroupEntry {
fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result472 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
473 struct FormatAsPtr<'a, P>(&'a P);
474 impl<P: fmt::Pointer> Debug for FormatAsPtr<'_, P> {
475 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
476 write!(f, "{:#p}", *self.0)
477 }
478 }
479
480 f.debug_tuple("RecGroupEntry")
481 .field(&FormatAsPtr(&self.0))
482 .finish()
483 }
484 }
485
486 struct RecGroupEntryInner {
487 /// The Wasm rec group, canonicalized for hash consing.
488 hash_consing_key: WasmRecGroup,
489
490 /// The shared type indices for each type in this rec group.
491 shared_type_indices: Box<[VMSharedTypeIndex]>,
492
493 /// The number of times that this entry has been registered in the
494 /// `TypeRegistryInner`.
495 ///
496 /// This is an atomic counter so that cloning a `RegisteredType`, and
497 /// temporarily keeping a type registered, doesn't require locking the full
498 /// registry.
499 registrations: AtomicUsize,
500
501 /// Whether this entry has already been unregistered from the
502 /// `TypeRegistryInner`.
503 ///
504 /// This flag exists to detect and avoid double-unregistration bugs that
505 /// could otherwise occur in rare cases. See the comments in
506 /// `TypeRegistryInner::unregister_type` for details.
507 unregistered: AtomicBool,
508 }
509
510 impl PartialEq for RecGroupEntry {
eq(&self, other: &Self) -> bool511 fn eq(&self, other: &Self) -> bool {
512 self.0.hash_consing_key == other.0.hash_consing_key
513 }
514 }
515
516 impl Eq for RecGroupEntry {}
517
518 impl Hash for RecGroupEntry {
hash<H: Hasher>(&self, state: &mut H)519 fn hash<H: Hasher>(&self, state: &mut H) {
520 self.0.hash_consing_key.hash(state);
521 }
522 }
523
524 impl Borrow<WasmRecGroup> for RecGroupEntry {
525 #[inline]
borrow(&self) -> &WasmRecGroup526 fn borrow(&self) -> &WasmRecGroup {
527 &self.0.hash_consing_key
528 }
529 }
530
531 impl RecGroupEntry {
532 /// Allocate a new, default `RecGroupEntryInner`.
533 ///
534 /// The resulting value should be initialized via `RecGroupEntry::init`.
new_inner() -> Result<Arc<RecGroupEntryInner>, OutOfMemory>535 fn new_inner() -> Result<Arc<RecGroupEntryInner>, OutOfMemory> {
536 try_new(RecGroupEntryInner {
537 hash_consing_key: Default::default(),
538 shared_type_indices: Default::default(),
539 registrations: AtomicUsize::new(0),
540 unregistered: AtomicBool::new(false),
541 })
542 }
543
544 /// Initialize a `Arc<RecGroupEntryInner>` (as created by
545 /// `RecGroupEntry::new_inner`) and wrap it into a `RecGroupEntry`.
init( mut inner: Arc<RecGroupEntryInner>, key: WasmRecGroup, shared_types: Box<[VMSharedTypeIndex]>, ) -> Self546 fn init(
547 mut inner: Arc<RecGroupEntryInner>,
548 key: WasmRecGroup,
549 shared_types: Box<[VMSharedTypeIndex]>,
550 ) -> Self {
551 debug_assert!(key.is_canonicalized_for_hash_consing());
552
553 let RecGroupEntryInner {
554 hash_consing_key,
555 shared_type_indices,
556 registrations: _,
557 unregistered: _,
558 } = Arc::get_mut(&mut inner).expect("must have the only handle to this inner entry");
559
560 debug_assert!(shared_type_indices.is_empty());
561 *shared_type_indices = shared_types;
562
563 debug_assert!(hash_consing_key.types.is_empty());
564 *hash_consing_key = key;
565
566 RecGroupEntry(inner)
567 }
568
569 /// Increment the registration count.
incref(&self, why: &str)570 fn incref(&self, why: &str) {
571 let old_count = self.0.registrations.fetch_add(1, AcqRel);
572 log::trace!("incref({self:?}) -> count {}: {why}", old_count + 1);
573 }
574
575 /// Decrement the registration count and return `true` if the registration
576 /// count reached zero and this entry should be removed from the registry.
577 #[must_use = "caller must remove entry from registry if `decref` returns `true`"]
decref(&self, why: &str) -> bool578 fn decref(&self, why: &str) -> bool {
579 let old_count = self.0.registrations.fetch_sub(1, AcqRel);
580 debug_assert_ne!(old_count, 0);
581 log::trace!("decref({self:?}) -> count {}: {why}", old_count - 1);
582 old_count == 1
583 }
584 }
585
586 #[derive(Debug, Default)]
587 struct TypeRegistryInner {
588 // A hash map from a canonicalized-for-hash-consing rec group to its
589 // `VMSharedTypeIndex`es.
590 //
591 // There is an entry in this map for every rec group we have already
592 // registered. Before registering new rec groups, we first check this map to
593 // see if we've already registered an identical rec group that we should
594 // reuse instead.
595 hash_consing_map: TryHashSet<RecGroupEntry>,
596
597 // A map from `VMSharedTypeIndex::bits()` to the type index's associated
598 // Wasm type.
599 //
600 // These types are always canonicalized for runtime usage.
601 //
602 // These are only `None` during the process of inserting a new rec group
603 // into the registry, where we need registered `VMSharedTypeIndex`es for
604 // forward type references within the rec group, but have not actually
605 // inserted all the types within the rec group yet.
606 types: Slab<Option<Arc<WasmSubType>>>,
607
608 // A map that lets you walk backwards from a `VMSharedTypeIndex` to its
609 // `RecGroupEntry`.
610 type_to_rec_group: TrySecondaryMap<VMSharedTypeIndex, Option<RecGroupEntry>>,
611
612 // A map from a registered type to its complete list of supertypes.
613 //
614 // The supertypes are ordered from super- to subtype, i.e. the immediate
615 // parent supertype is the last element and the least-upper-bound of all
616 // supertypes is the first element.
617 //
618 // Types without any supertypes are omitted from this map. This means that
619 // we never allocate any backing storage for this map when Wasm GC is not in
620 // use.
621 type_to_supertypes: TrySecondaryMap<VMSharedTypeIndex, Option<Box<[VMSharedTypeIndex]>>>,
622
623 // A map from each registered function type to its trampoline type.
624 //
625 // Note that when a function type is its own trampoline type, then we omit
626 // the entry in this map as a memory optimization. This means that if only
627 // core Wasm function types are ever used, then we will never allocate any
628 // backing storage for this map. As a nice bonus, this also avoids cycles (a
629 // function type referencing itself) that our naive reference counting
630 // doesn't play well with.
631 type_to_trampoline: TrySecondaryMap<VMSharedTypeIndex, PackedOption<VMSharedTypeIndex>>,
632
633 // A map from each registered GC type to its layout.
634 //
635 // Function types do not have an entry in this map. Similar to the
636 // `type_to_{supertypes,trampoline}` maps, we completely omit the `None`
637 // entries for these types as a memory optimization.
638 type_to_gc_layout: TrySecondaryMap<VMSharedTypeIndex, Option<GcLayout>>,
639
640 // An explicit stack of entries that we are in the middle of dropping. Used
641 // to avoid recursion when dropping a type that is holding the last
642 // reference to another type, etc...
643 drop_stack: TryVec<RecGroupEntry>,
644 }
645
646 impl TypeRegistryInner {
647 #[inline]
648 #[track_caller]
debug_assert_registered(&self, index: VMSharedTypeIndex)649 fn debug_assert_registered(&self, index: VMSharedTypeIndex) {
650 debug_assert!(
651 !index.is_reserved_value(),
652 "should have an actual VMSharedTypeIndex, not the reserved value"
653 );
654 debug_assert!(
655 self.types.contains(shared_type_index_to_slab_id(index)),
656 "registry's slab should contain {index:?}",
657 );
658 debug_assert!(
659 self.types[shared_type_index_to_slab_id(index)].is_some(),
660 "registry's slab should actually contain a type for {index:?}",
661 );
662 debug_assert!(
663 self.type_to_rec_group[index].is_some(),
664 "{index:?} should have an associated rec group entry"
665 );
666 }
667
668 #[inline]
669 #[track_caller]
debug_assert_all_registered(&self, entry: &RecGroupEntry)670 fn debug_assert_all_registered(&self, entry: &RecGroupEntry) {
671 if cfg!(debug_assertions) {
672 for &ty in &entry.0.shared_type_indices {
673 self.debug_assert_registered(ty);
674 }
675 }
676 }
677
register_module_types( &mut self, gc_runtime: Option<&dyn GcRuntime>, types: &ModuleTypes, ) -> Result< ( TryVec<RecGroupEntry>, TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>, ), OutOfMemory, >678 fn register_module_types(
679 &mut self,
680 gc_runtime: Option<&dyn GcRuntime>,
681 types: &ModuleTypes,
682 ) -> Result<
683 (
684 TryVec<RecGroupEntry>,
685 TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
686 ),
687 OutOfMemory,
688 > {
689 log::trace!("Start registering module types");
690
691 // The engine's type registry entries for these module types.
692 let mut entries = TryVec::with_capacity(types.rec_groups().len())?;
693
694 // The map from a module type index to an engine type index for these
695 // module types.
696 let mut map = TryPrimaryMap::<ModuleInternedTypeIndex, VMSharedTypeIndex>::with_capacity(
697 types.wasm_types().len(),
698 )?;
699
700 for (_rec_group_index, module_group) in types.rec_groups() {
701 let entry = self.register_rec_group(
702 gc_runtime,
703 &map,
704 module_group.clone(),
705 iter_entity_range(module_group.clone()).map(|ty| types[ty].try_clone()),
706 )?;
707
708 // Update the module-to-engine map with this rec group's
709 // newly-registered types.
710 for (module_ty, engine_ty) in
711 iter_entity_range(module_group).zip(entry.0.shared_type_indices.iter())
712 {
713 let module_ty2 = map.push(*engine_ty).expect("reserved capacity");
714 assert_eq!(module_ty, module_ty2);
715 }
716
717 entries.push(entry).expect("reserved capacity");
718 }
719
720 log::trace!("End registering module types");
721
722 Ok((entries, map))
723 }
724
725 /// Register a rec group in this registry.
726 ///
727 /// The rec group may be either module-level canonical (i.e. straight from
728 /// `wasmparser`) or engine-level canonicalized for runtime usage in this
729 /// registry. It may *not* be engine-level canonicalized for hash consing or
730 /// engine-level canonicalized for a different type registry instance.
731 ///
732 /// If this rec group is determined to be a duplicate of an
733 /// already-registered rec group, the existing rec group is reused.
734 ///
735 /// Parameters:
736 ///
737 /// * `map`: A map that we use to canonicalize inter-group type references
738 /// from module-canonical to engine-canonical indices. This must contain
739 /// entries for each inter-group type reference that this rec group
740 /// contains.
741 ///
742 /// * `range`: The range of (module-level) types defined by this rec
743 /// group. This is used to determine which type references inside this rec
744 /// group are inter- vs intra-group.
745 ///
746 /// * `types`: The types defined within this rec group. Must have the same
747 /// length as `range`.
748 ///
749 /// The returned entry will have already had its reference count incremented
750 /// on behalf of callers.
register_rec_group( &mut self, gc_runtime: Option<&dyn GcRuntime>, map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>, range: Range<ModuleInternedTypeIndex>, types: impl ExactSizeIterator<Item = Result<WasmSubType, OutOfMemory>>, ) -> Result<RecGroupEntry, OutOfMemory>751 fn register_rec_group(
752 &mut self,
753 gc_runtime: Option<&dyn GcRuntime>,
754 map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
755 range: Range<ModuleInternedTypeIndex>,
756 types: impl ExactSizeIterator<Item = Result<WasmSubType, OutOfMemory>>,
757 ) -> Result<RecGroupEntry, OutOfMemory> {
758 log::trace!("registering rec group of length {}", types.len());
759 debug_assert_eq!(iter_entity_range(range.clone()).len(), types.len());
760
761 // We need two different canonicalization of this rec group: one for
762 // hash-consing and another for runtime usage within this
763 // engine. However, we only need the latter if this is a new rec group
764 // that hasn't been registered before. Therefore, we only eagerly create
765 // the hash-consing canonicalized version, and while we lazily
766 // canonicalize for runtime usage in this engine, we must still eagerly
767 // clone and set aside the original, non-canonicalized types for that
768 // potential engine canonicalization eventuality.
769 let mut non_canon_types = TryVec::with_capacity(types.len())?;
770 let hash_consing_key = WasmRecGroup {
771 types: types
772 .zip(iter_entity_range(range.clone()))
773 .map(|(ty, module_index)| {
774 let mut ty = ty?;
775 non_canon_types
776 .push((module_index, ty.try_clone()?))
777 .expect("reserved capacity");
778 ty.canonicalize_for_hash_consing(range.clone(), &mut |idx| {
779 debug_assert!(idx < range.clone().start);
780 map[idx]
781 });
782 Ok(ty)
783 })
784 .try_collect::<Box<[_]>, OutOfMemory>()?,
785 };
786
787 // Any references in the hash-consing key to types outside of this rec
788 // group may only be to fully-registered types.
789 if cfg!(debug_assertions) {
790 hash_consing_key
791 .trace_engine_indices::<_, ()>(&mut |index| Ok(self.debug_assert_registered(index)))
792 .unwrap();
793 }
794
795 // If we've already registered this rec group before, reuse it.
796 if let Some(entry) = self.hash_consing_map.get(&hash_consing_key) {
797 log::trace!("hash-consing map hit: reusing {entry:?}");
798 assert_eq!(entry.0.unregistered.load(Acquire), false);
799 self.debug_assert_all_registered(entry);
800 entry.incref("hash-consing map hit");
801 Ok(entry.clone())
802 } else {
803 log::trace!("hash-consing map miss: making new registration");
804 self.register_new_rec_group(gc_runtime, map, range, hash_consing_key, non_canon_types)
805 }
806 }
807
808 /// Register a new rec group that is not already in this registry.
register_new_rec_group( &mut self, gc_runtime: Option<&(dyn GcRuntime + 'static)>, map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>, range: Range<ModuleInternedTypeIndex>, hash_consing_key: WasmRecGroup, mut non_canon_types: TryVec<(ModuleInternedTypeIndex, WasmSubType)>, ) -> Result<RecGroupEntry, OutOfMemory>809 fn register_new_rec_group(
810 &mut self,
811 gc_runtime: Option<&(dyn GcRuntime + 'static)>,
812 map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
813 range: Range<ModuleInternedTypeIndex>,
814 hash_consing_key: WasmRecGroup,
815 mut non_canon_types: TryVec<(ModuleInternedTypeIndex, WasmSubType)>,
816 ) -> Result<RecGroupEntry, OutOfMemory> {
817 debug_assert!(hash_consing_key.is_canonicalized_for_hash_consing());
818 debug_assert_eq!(self.hash_consing_map.contains(&hash_consing_key), false);
819
820 // XXX: Do not write new entries to `self` or its arenas in this block
821 // of code, as we will not rollback those changes on OOM.
822 let entry = {
823 // First, pre-allocate capacity in our arenas for this rec group, as
824 // much as possible.
825 let num_types = non_canon_types.len();
826 self.reserve_capacity_for_rec_group(num_types)?;
827 let mut shared_type_indices = TryVec::new();
828 shared_type_indices.reserve_exact(num_types)?;
829 let entry_inner = RecGroupEntry::new_inner()?;
830
831 // Assign a `VMSharedTypeIndex` to each type.
832 let shared_type_indices =
833 self.assign_shared_type_indices(&non_canon_types, shared_type_indices);
834
835 // Initialize the rec group entry, now that we have all its parts.
836 RecGroupEntry::init(entry_inner, hash_consing_key, shared_type_indices)
837 };
838
839 // XXX: From the creation of the `Undo` and afterwards, we can write to
840 // `self` and its arenas. We must, however, always ensure that the
841 // `Undo` fully rolls back the changes made below, so keep this in mind
842 // if adding/removing/changing the arenas in `self`.
843 let did_incref = Cell::new(false);
844 let entry2 = entry.clone();
845 let mut registry = Undo::new(self, |registry| {
846 registry.remove_entry_impl(&entry2, did_incref.get());
847 registry.drain_drop_stack();
848 });
849
850 registry.canonicalize_entry_types_for_runtime_usage(
851 map,
852 &entry,
853 non_canon_types.iter_mut().map(|(_, ty)| ty),
854 range.clone(),
855 );
856
857 // Inter-group edges: increment the referenced group's ref
858 // count, because these other rec groups shouldn't be dropped
859 // while this new rec group is still alive.
860 registry.incref_outgoing_edges(&entry);
861 did_incref.set(true);
862
863 registry.insert_entry_types(&entry, non_canon_types.into_iter().map(|(_, ty)| ty))?;
864 registry.insert_entry_rec_groups(&entry);
865 registry.insert_entry_supertypes(&entry)?;
866 registry.insert_entry_trampolines(gc_runtime, &entry)?;
867 registry.insert_entry_gc_layouts(gc_runtime, &entry)?;
868
869 let is_new_entry = registry.hash_consing_map.insert(entry.clone())?;
870 debug_assert!(is_new_entry);
871
872 // We successfully registered the rec group! Commit our changes,
873 // increment the rec group's registration count to one, and return it.
874 registry.debug_assert_all_registered(&entry);
875 Undo::commit(registry);
876 entry.incref("creation");
877 Ok(entry)
878 }
879
880 /// Implementation of removing a rec group entry.
881 ///
882 /// Shared by both unregistering a rec group and rolling back changes when
883 /// we OOM in `register_new_rec_group`.
884 ///
885 /// The order of removals should be the reverse of the insertions in
886 /// `register_new_rec_group`.
remove_entry_impl(&mut self, entry: &RecGroupEntry, should_decref: bool)887 fn remove_entry_impl(&mut self, entry: &RecGroupEntry, should_decref: bool) {
888 // All entries we are removing should *really* be ready for
889 // unregistration.
890 assert_eq!(entry.0.registrations.load(Acquire), 0);
891 assert_eq!(entry.0.unregistered.load(Acquire), false);
892
893 // We are taking responsibility for unregistering this entry, so prevent
894 // anyone else from attempting to do it again. See race (2) in the
895 // comment in `unregister_entry`.
896 entry.0.unregistered.store(true, Release);
897
898 // Perform the removals in the opposite order as the insertions in
899 // `register_new_rec_group`.
900 self.hash_consing_map.remove(&entry.0.hash_consing_key);
901 self.remove_entry_gc_layouts(&entry);
902 self.remove_entry_trampolines(&entry);
903 self.remove_entry_supertypes(&entry);
904 self.remove_entry_rec_groups(&entry);
905 self.remove_entry_types(&entry);
906 if should_decref {
907 self.decref_outgoing_edges(&entry);
908 }
909 }
910
911 /// Insert the rec group entry's types into the `self.types` arena.
insert_entry_types( &mut self, entry: &RecGroupEntry, sub_types: impl ExactSizeIterator<Item = WasmSubType>, ) -> Result<(), OutOfMemory>912 fn insert_entry_types(
913 &mut self,
914 entry: &RecGroupEntry,
915 sub_types: impl ExactSizeIterator<Item = WasmSubType>,
916 ) -> Result<(), OutOfMemory> {
917 debug_assert_eq!(entry.0.shared_type_indices.len(), sub_types.len());
918 for (ty_idx, sub_ty) in entry.0.shared_type_indices.iter().copied().zip(sub_types) {
919 // NB: Do not use
920 // `assert_canonicalized_for_runtime_usage_in_this_registry` because
921 // the types this type references may not be fully registered yet,
922 // as we are in the middle of a rec group's registration.
923 debug_assert!(sub_ty.is_canonicalized_for_runtime_usage());
924
925 let id = shared_type_index_to_slab_id(ty_idx);
926 debug_assert!(self.types.contains(id));
927 debug_assert!(self.types[id].is_none());
928 self.types[id] = Some(try_new(sub_ty)?);
929 }
930 Ok(())
931 }
932
933 /// Remove the rec group entry's types into the `self.types` arena.
remove_entry_types(&mut self, entry: &RecGroupEntry)934 fn remove_entry_types(&mut self, entry: &RecGroupEntry) {
935 for &ty in &entry.0.shared_type_indices {
936 let id = shared_type_index_to_slab_id(ty);
937 debug_assert!(self.types.contains(id));
938 self.types.dealloc(id);
939 }
940 }
941
942 /// Insert the rec group backlink for each type in this rec group.
insert_entry_rec_groups(&mut self, entry: &RecGroupEntry)943 fn insert_entry_rec_groups(&mut self, entry: &RecGroupEntry) {
944 debug_assert!(self.type_to_rec_group.capacity() >= self.types.len());
945 for &ty in &entry.0.shared_type_indices {
946 debug_assert!(self.type_to_rec_group[ty].is_none());
947 debug_assert!(ty.index() < self.type_to_rec_group.capacity());
948 self.type_to_rec_group
949 .insert(ty, Some(entry.clone()))
950 .expect("reserved capacity");
951 }
952 }
953
954 /// Remove the rec group's type's backlinks to the rec group.
remove_entry_rec_groups(&mut self, entry: &RecGroupEntry)955 fn remove_entry_rec_groups(&mut self, entry: &RecGroupEntry) {
956 for &ty in &entry.0.shared_type_indices {
957 debug_assert!(ty.index() < self.type_to_rec_group.capacity());
958 self.type_to_rec_group.remove(ty);
959 }
960 }
961
962 /// Insert the supertype information for this rec group's types.
insert_entry_supertypes(&mut self, entry: &RecGroupEntry) -> Result<(), OutOfMemory>963 fn insert_entry_supertypes(&mut self, entry: &RecGroupEntry) -> Result<(), OutOfMemory> {
964 for &ty in &entry.0.shared_type_indices {
965 let id = shared_type_index_to_slab_id(ty);
966 if let Some(supertype) = self.types[id].as_ref().unwrap().supertype {
967 debug_assert!(self.type_to_supertypes.capacity() <= self.types.capacity());
968 if self.type_to_supertypes.capacity() < self.types.capacity() {
969 log::trace!("type_to_supertypes.resize({})", self.types.capacity());
970 self.type_to_supertypes.resize(self.types.capacity())?;
971 }
972
973 let supertype = supertype.unwrap_engine_type_index();
974 let supers_supertypes = self.supertypes(supertype);
975 let supertypes = supers_supertypes
976 .iter()
977 .copied()
978 .chain(iter::once(supertype))
979 .try_collect()?;
980
981 self.type_to_supertypes
982 .insert(ty, Some(supertypes))
983 .expect("reserved capacity");
984 }
985 }
986 Ok(())
987 }
988
989 /// Remove the rec group's associated supertype information.
remove_entry_supertypes(&mut self, entry: &RecGroupEntry)990 fn remove_entry_supertypes(&mut self, entry: &RecGroupEntry) {
991 // We delay allocating space for this arena until we actually see a type
992 // with a supertype, so early exit if we never allocated any space.
993 if self.type_to_supertypes.capacity() == 0 {
994 return;
995 }
996
997 for &ty in &entry.0.shared_type_indices {
998 self.type_to_supertypes.remove(ty);
999 }
1000 }
1001
1002 /// Insert the trampoline information for this rec group's types.
insert_entry_trampolines( &mut self, gc_runtime: Option<&(dyn GcRuntime + 'static)>, entry: &RecGroupEntry, ) -> Result<(), OutOfMemory>1003 fn insert_entry_trampolines(
1004 &mut self,
1005 gc_runtime: Option<&(dyn GcRuntime + 'static)>,
1006 entry: &RecGroupEntry,
1007 ) -> Result<(), OutOfMemory> {
1008 for &ty_idx in &entry.0.shared_type_indices {
1009 let id = shared_type_index_to_slab_id(ty_idx);
1010 debug_assert!(self.types.contains(id));
1011 debug_assert!(self.types[id].is_some());
1012 let sub_ty = self.types[id].as_ref().unwrap();
1013
1014 let Some(func_ty) = sub_ty.as_func() else {
1015 continue;
1016 };
1017
1018 let trampoline_ty = match func_ty.trampoline_type()? {
1019 TryCow::Owned(ty) => ty,
1020 TryCow::Borrowed(ty) if !sub_ty.is_final || sub_ty.supertype.is_some() => {
1021 ty.try_clone()?
1022 }
1023 TryCow::Borrowed(_) => {
1024 // The function type is its own trampoline type. Leave its entry
1025 // in `type_to_trampoline` empty to signal this.
1026 debug_assert!(func_ty.is_trampoline_type());
1027 log::trace!("trampoline_type({ty_idx:?}) = {ty_idx:?}");
1028 continue;
1029 }
1030 };
1031
1032 debug_assert!(self.type_to_trampoline.capacity() <= self.types.capacity());
1033 if self.type_to_trampoline.capacity() < self.types.capacity() {
1034 log::trace!("type_to_trampoline.resize({})", self.types.capacity());
1035 self.type_to_trampoline.resize(self.types.capacity())?;
1036 }
1037
1038 let trampoline_sub_ty = WasmSubType {
1039 is_final: true,
1040 supertype: None,
1041 composite_type: wasmtime_environ::WasmCompositeType {
1042 shared: sub_ty.composite_type.shared,
1043 inner: wasmtime_environ::WasmCompositeInnerType::Func(trampoline_ty),
1044 },
1045 };
1046
1047 // This will recursively call into rec group registration, but at
1048 // most once since trampoline function types are their own
1049 // trampoline type.
1050 let trampoline_entry =
1051 self.register_singleton_rec_group(gc_runtime, trampoline_sub_ty)?;
1052
1053 assert_eq!(trampoline_entry.0.shared_type_indices.len(), 1);
1054 let trampoline_index = trampoline_entry.0.shared_type_indices[0];
1055 self.debug_assert_registered(trampoline_index);
1056 debug_assert_ne!(ty_idx, trampoline_index);
1057
1058 self.type_to_trampoline
1059 .insert(ty_idx, Some(trampoline_index).into())
1060 .expect("reserved capacity");
1061
1062 log::trace!("trampoline_type({ty_idx:?}) = {trampoline_index:?}");
1063 }
1064
1065 Ok(())
1066 }
1067
1068 /// Remove the rec group's associated trampoline information.
remove_entry_trampolines(&mut self, entry: &RecGroupEntry)1069 fn remove_entry_trampolines(&mut self, entry: &RecGroupEntry) {
1070 // We delay allocating space for this arena until we actually see a
1071 // function type that is not its own trampoline type, so early exit if
1072 // we never allocated any space.
1073 if self.type_to_trampoline.capacity() == 0 {
1074 return;
1075 }
1076
1077 for &ty in &entry.0.shared_type_indices {
1078 if let Some(tramp_ty) = self.type_to_trampoline.remove(ty).and_then(|x| x.expand()) {
1079 self.debug_assert_registered(tramp_ty);
1080 let tramp_entry = self.type_to_rec_group[tramp_ty].as_ref().unwrap();
1081 if tramp_entry.decref("dropping rec group's trampoline-type references") {
1082 self.push_to_drop_stack(tramp_entry.clone());
1083 }
1084 }
1085 }
1086
1087 self.drain_drop_stack();
1088 }
1089
1090 /// Insert the GC layout information for this rec group's types.
insert_entry_gc_layouts( &mut self, gc_runtime: Option<&(dyn GcRuntime + 'static)>, entry: &RecGroupEntry, ) -> Result<(), OutOfMemory>1091 fn insert_entry_gc_layouts(
1092 &mut self,
1093 gc_runtime: Option<&(dyn GcRuntime + 'static)>,
1094 entry: &RecGroupEntry,
1095 ) -> Result<(), OutOfMemory> {
1096 let Some(gc_runtime) = gc_runtime else {
1097 // If we don't have a GC runtime, then we won't have any GC types
1098 // and don't have to remember any GC layouts.
1099 debug_assert!(entry.0.shared_type_indices.iter().all(|ty| {
1100 let id = shared_type_index_to_slab_id(*ty);
1101 let sub_ty = self.types[id].as_ref().unwrap();
1102 assert!(!sub_ty.composite_type.shared);
1103 matches!(
1104 &sub_ty.composite_type.inner,
1105 wasmtime_environ::WasmCompositeInnerType::Func(_)
1106 )
1107 }));
1108 return Ok(());
1109 };
1110
1111 for &ty_idx in &entry.0.shared_type_indices {
1112 let id = shared_type_index_to_slab_id(ty_idx);
1113 let sub_ty = self.types[id].as_ref().unwrap();
1114 assert!(!sub_ty.composite_type.shared);
1115
1116 let gc_layout = match &sub_ty.composite_type.inner {
1117 wasmtime_environ::WasmCompositeInnerType::Func(_) => continue,
1118 wasmtime_environ::WasmCompositeInnerType::Array(a) => {
1119 gc_runtime.layouts().array_layout(a).into()
1120 }
1121 wasmtime_environ::WasmCompositeInnerType::Struct(s) => {
1122 try_new::<Arc<_>>(gc_runtime.layouts().struct_layout(s))?.into()
1123 }
1124 wasmtime_environ::WasmCompositeInnerType::Exn(e) => {
1125 try_new::<Arc<_>>(gc_runtime.layouts().exn_layout(e))?.into()
1126 }
1127 wasmtime_environ::WasmCompositeInnerType::Cont(_) => continue, // FIXME: #10248 stack switching support.
1128 };
1129
1130 debug_assert!(self.type_to_gc_layout.capacity() <= self.types.capacity());
1131 if self.type_to_gc_layout.capacity() < self.types.capacity() {
1132 log::trace!("type_to_gc_layout.resize({})", self.types.capacity());
1133 self.type_to_gc_layout.resize(self.types.capacity())?;
1134 }
1135
1136 self.type_to_gc_layout
1137 .insert(ty_idx, Some(gc_layout))
1138 .expect("reserved capacity");
1139 }
1140
1141 Ok(())
1142 }
1143
1144 /// Remove the rec group's associated GC layout information.
remove_entry_gc_layouts(&mut self, entry: &RecGroupEntry)1145 fn remove_entry_gc_layouts(&mut self, entry: &RecGroupEntry) {
1146 // We delay allocating space for this arena until we actually see a GC
1147 // type, so early exit if we never allocated any space.
1148 if self.type_to_gc_layout.capacity() == 0 {
1149 return;
1150 }
1151
1152 for ty in &entry.0.shared_type_indices {
1153 self.type_to_gc_layout.remove(*ty);
1154 }
1155 }
1156
1157 /// Assign a `VMSharedTypeIndex` to each type in a rec group.
assign_shared_type_indices( &mut self, non_canon_types: &[(ModuleInternedTypeIndex, WasmSubType)], mut shared_type_indices: TryVec<VMSharedTypeIndex>, ) -> Box<[VMSharedTypeIndex]>1158 fn assign_shared_type_indices(
1159 &mut self,
1160 non_canon_types: &[(ModuleInternedTypeIndex, WasmSubType)],
1161 mut shared_type_indices: TryVec<VMSharedTypeIndex>,
1162 ) -> Box<[VMSharedTypeIndex]> {
1163 debug_assert_eq!(non_canon_types.len(), shared_type_indices.capacity());
1164 debug_assert!(shared_type_indices.is_empty());
1165 debug_assert!(
1166 self.types.capacity() - self.types.len() >= non_canon_types.len(),
1167 "should have reserved capacity"
1168 );
1169 for (module_index, ty) in non_canon_types.iter() {
1170 let engine_index =
1171 slab_id_to_shared_type_index(self.types.alloc(None).expect("have capacity"));
1172 log::trace!("reserved {engine_index:?} for {module_index:?} = non-canonical {ty:?}");
1173 shared_type_indices
1174 .push(engine_index)
1175 .expect("reserved capacity");
1176 }
1177
1178 debug_assert_eq!(shared_type_indices.len(), shared_type_indices.capacity());
1179 shared_type_indices
1180 .into_boxed_slice()
1181 .expect("capacity should be exact")
1182 }
1183
1184 /// For each cross-rec group type reference inside `entry`, increment the
1185 /// referenced rec group's registration count.
incref_outgoing_edges(&mut self, entry: &RecGroupEntry)1186 fn incref_outgoing_edges(&mut self, entry: &RecGroupEntry) {
1187 let key = &entry.0.hash_consing_key;
1188 debug_assert!(key.is_canonicalized_for_hash_consing());
1189 key.trace_engine_indices::<_, ()>(&mut |index| {
1190 self.debug_assert_registered(index);
1191 let other_entry = self.type_to_rec_group[index].as_ref().unwrap();
1192 assert_eq!(other_entry.0.unregistered.load(Acquire), false);
1193 other_entry.incref("new rec group's type references");
1194 Ok(())
1195 })
1196 .unwrap();
1197 }
1198
1199 /// For each cross-rec group type reference inside `entry`, decrement the
1200 /// referenced rec group's registration count.
decref_outgoing_edges(&mut self, entry: &RecGroupEntry)1201 fn decref_outgoing_edges(&mut self, entry: &RecGroupEntry) {
1202 let key = &entry.0.hash_consing_key;
1203 debug_assert!(key.is_canonicalized_for_hash_consing());
1204 key.trace_engine_indices::<_, ()>(&mut |other_index| {
1205 self.debug_assert_registered(other_index);
1206 let other_entry = self.type_to_rec_group[other_index].as_ref().unwrap();
1207 assert_eq!(other_entry.0.unregistered.load(Acquire), false);
1208 if other_entry.decref("dropping rec group's type references") {
1209 self.push_to_drop_stack(other_entry.clone());
1210 }
1211 Ok(())
1212 })
1213 .unwrap();
1214 self.drain_drop_stack();
1215 }
1216
1217 /// Pre-allocate capacity for a rec group of size `num_types` in our various
1218 /// arenas.
reserve_capacity_for_rec_group(&mut self, num_types: usize) -> Result<(), OutOfMemory>1219 fn reserve_capacity_for_rec_group(&mut self, num_types: usize) -> Result<(), OutOfMemory> {
1220 log::trace!("Reserving capacity for rec group of {num_types} types");
1221
1222 // NB: Destructure to make sure we are considering every field in
1223 // `self`.
1224 let TypeRegistryInner {
1225 hash_consing_map,
1226 types,
1227 type_to_rec_group,
1228 drop_stack,
1229
1230 // We only insert entries into these `SecondaryMap`s if/when we
1231 // encounter GC types (which is rare, even when GC types are
1232 // enabled).
1233 type_to_supertypes: _,
1234 type_to_trampoline: _,
1235 type_to_gc_layout: _,
1236 } = self;
1237
1238 // This map contains rec groups, not types, so only reserve space for
1239 // one additional entry.
1240 log::trace!(" hash_consing_map.reserve(1)");
1241 hash_consing_map.reserve(1)?;
1242
1243 log::trace!(" types.reserve({num_types})");
1244 types.reserve(num_types)?;
1245 let types_capacity = types.capacity();
1246
1247 log::trace!(" type_to_rec_group.resize({types_capacity})");
1248 type_to_rec_group.resize(types_capacity)?;
1249
1250 log::trace!(" type_to_rec_group.reserve({types_capacity})");
1251 debug_assert!(drop_stack.is_empty());
1252 drop_stack.reserve(types_capacity)?;
1253
1254 Ok(())
1255 }
1256
1257 /// Canonicalize all the types inside a rec group for runtime usage within
1258 /// this registry.
canonicalize_entry_types_for_runtime_usage<'a>( &self, map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>, entry: &RecGroupEntry, sub_tys: impl ExactSizeIterator<Item = &'a mut WasmSubType>, range: Range<ModuleInternedTypeIndex>, )1259 fn canonicalize_entry_types_for_runtime_usage<'a>(
1260 &self,
1261 map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
1262 entry: &RecGroupEntry,
1263 sub_tys: impl ExactSizeIterator<Item = &'a mut WasmSubType>,
1264 range: Range<ModuleInternedTypeIndex>,
1265 ) {
1266 debug_assert_eq!(sub_tys.len(), entry.0.shared_type_indices.len());
1267 for (engine_index, ty) in entry.0.shared_type_indices.iter().copied().zip(sub_tys) {
1268 self.canonicalize_type_for_runtime_usage(map, &entry, engine_index, ty, range.clone());
1269 }
1270 }
1271
1272 /// Canonicalize one type for runtime usage within this registry.
canonicalize_type_for_runtime_usage( &self, map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>, entry: &RecGroupEntry, engine_index: VMSharedTypeIndex, ty: &mut WasmSubType, range: Range<ModuleInternedTypeIndex>, )1273 fn canonicalize_type_for_runtime_usage(
1274 &self,
1275 map: &TryPrimaryMap<ModuleInternedTypeIndex, VMSharedTypeIndex>,
1276 entry: &RecGroupEntry,
1277 engine_index: VMSharedTypeIndex,
1278 ty: &mut WasmSubType,
1279 range: Range<ModuleInternedTypeIndex>,
1280 ) {
1281 log::trace!("canonicalizing {engine_index:?} for runtime usage");
1282 ty.canonicalize_for_runtime_usage(&mut |module_index| {
1283 if module_index < range.start {
1284 let engine_index = map[module_index];
1285 log::trace!(" cross-group {module_index:?} becomes {engine_index:?}");
1286 self.debug_assert_registered(engine_index);
1287 engine_index
1288 } else {
1289 assert!(module_index < range.end);
1290 let rec_group_offset = module_index.as_u32() - range.start.as_u32();
1291 let rec_group_offset = usize::try_from(rec_group_offset).unwrap();
1292 let engine_index = entry.0.shared_type_indices[rec_group_offset];
1293 log::trace!(" intra-group {module_index:?} becomes {engine_index:?}");
1294 assert!(!engine_index.is_reserved_value());
1295 assert!(
1296 self.types
1297 .contains(shared_type_index_to_slab_id(engine_index))
1298 );
1299 engine_index
1300 }
1301 });
1302 }
1303
1304 /// Assert that the given type is canonicalized for runtime usage this
1305 /// registry, and that every type it references is also registered in this
1306 /// registry.
1307 #[track_caller]
assert_canonicalized_for_runtime_usage_in_this_registry(&self, ty: &WasmSubType)1308 fn assert_canonicalized_for_runtime_usage_in_this_registry(&self, ty: &WasmSubType) {
1309 ty.trace::<_, ()>(&mut |index| match index {
1310 EngineOrModuleTypeIndex::RecGroup(_) | EngineOrModuleTypeIndex::Module(_) => {
1311 panic!("not canonicalized for runtime usage: {ty:?}")
1312 }
1313 EngineOrModuleTypeIndex::Engine(idx) => {
1314 self.debug_assert_registered(idx);
1315 Ok(())
1316 }
1317 })
1318 .unwrap();
1319 }
1320
1321 /// Get the supertypes list for the given type.
1322 ///
1323 /// The supertypes are listed in super-to-sub order. `ty` itself is not
1324 /// included in the list.
supertypes(&self, ty: VMSharedTypeIndex) -> &[VMSharedTypeIndex]1325 fn supertypes(&self, ty: VMSharedTypeIndex) -> &[VMSharedTypeIndex] {
1326 self.type_to_supertypes
1327 .get(ty)
1328 .and_then(|s| s.as_deref())
1329 .unwrap_or(&[])
1330 }
1331
1332 /// Register a rec group consisting of a single type.
1333 ///
1334 /// The type must already be canonicalized for runtime usage in this
1335 /// registry.
1336 ///
1337 /// The returned entry will have already had its reference count incremented
1338 /// on behalf of callers.
register_singleton_rec_group( &mut self, gc_runtime: Option<&dyn GcRuntime>, ty: WasmSubType, ) -> Result<RecGroupEntry, OutOfMemory>1339 fn register_singleton_rec_group(
1340 &mut self,
1341 gc_runtime: Option<&dyn GcRuntime>,
1342 ty: WasmSubType,
1343 ) -> Result<RecGroupEntry, OutOfMemory> {
1344 self.assert_canonicalized_for_runtime_usage_in_this_registry(&ty);
1345
1346 // This type doesn't have any module-level type references, since it is
1347 // already canonicalized for runtime usage in this registry, so an empty
1348 // map suffices.
1349 let map = TryPrimaryMap::default();
1350
1351 // This must have `range.len() == 1`, even though we know this type
1352 // doesn't have any intra-group type references, to satisfy
1353 // `register_rec_group`'s preconditions.
1354 let range = ModuleInternedTypeIndex::from_bits(u32::MAX - 1)
1355 ..ModuleInternedTypeIndex::from_bits(u32::MAX);
1356
1357 self.register_rec_group(gc_runtime, &map, range, iter::once(Ok(ty)))
1358 }
1359
1360 /// Unregister all of a type collection's rec groups.
unregister_type_collection(&mut self, collection: &TypeCollection)1361 fn unregister_type_collection(&mut self, collection: &TypeCollection) {
1362 log::trace!("Begin unregistering `TypeCollection`");
1363 for entry in &collection.rec_groups {
1364 self.debug_assert_all_registered(entry);
1365 if entry.decref("TypeRegistryInner::unregister_type_collection") {
1366 self.unregister_entry(entry.clone());
1367 }
1368 }
1369 log::trace!("Finished unregistering `TypeCollection`");
1370 }
1371
1372 /// Remove a zero-refcount entry from the registry.
1373 ///
1374 /// This does *not* decrement the entry's registration count, it should
1375 /// instead be invoked only after a previous decrement operation observed
1376 /// zero remaining registrations.
unregister_entry(&mut self, entry: RecGroupEntry)1377 fn unregister_entry(&mut self, entry: RecGroupEntry) {
1378 log::trace!("Attempting to unregister {entry:?}");
1379 debug_assert!(self.drop_stack.is_empty());
1380
1381 // There are two races to guard against before we can unregister the
1382 // entry, even though it was on the drop stack:
1383 //
1384 // 1. Although an entry has to reach zero registrations before it is
1385 // enqueued in the drop stack, we need to double check whether the
1386 // entry is *still* at zero registrations. This is because someone
1387 // else can resurrect the entry in between when the
1388 // zero-registrations count was first observed and when we actually
1389 // acquire the lock to unregister it. In this example, we have
1390 // threads A and B, an existing rec group entry E, and a rec group
1391 // entry E' that is a duplicate of E:
1392 //
1393 // Thread A | Thread B
1394 // --------------------------------+-----------------------------
1395 // acquire(type registry lock) |
1396 // |
1397 // | decref(E) --> 0
1398 // |
1399 // | block_on(type registry lock)
1400 // |
1401 // register(E') == incref(E) --> 1 |
1402 // |
1403 // release(type registry lock) |
1404 // |
1405 // | acquire(type registry lock)
1406 // |
1407 // | unregister(E) !!!!!!
1408 //
1409 // If we aren't careful, we can unregister a type while it is still
1410 // in use!
1411 //
1412 // The fix in this case is that we skip unregistering the entry if
1413 // its reference count is non-zero, since that means it was
1414 // concurrently resurrected and is now in use again.
1415 //
1416 // 2. In a slightly more convoluted version of (1), where an entry is
1417 // resurrected but then dropped *again*, someone might attempt to
1418 // unregister an entry a second time:
1419 //
1420 // Thread A | Thread B
1421 // --------------------------------|-----------------------------
1422 // acquire(type registry lock) |
1423 // |
1424 // | decref(E) --> 0
1425 // |
1426 // | block_on(type registry lock)
1427 // |
1428 // register(E') == incref(E) --> 1 |
1429 // |
1430 // release(type registry lock) |
1431 // |
1432 // decref(E) --> 0 |
1433 // |
1434 // acquire(type registry lock) |
1435 // |
1436 // unregister(E) |
1437 // |
1438 // release(type registry lock) |
1439 // |
1440 // | acquire(type registry lock)
1441 // |
1442 // | unregister(E) !!!!!!
1443 //
1444 // If we aren't careful, we can unregister a type twice, which leads
1445 // to panics and registry corruption!
1446 //
1447 // To detect this scenario and avoid the double-unregistration bug,
1448 // we maintain an `unregistered` flag on entries. We set this flag
1449 // once an entry is unregistered and therefore, even if it is
1450 // enqueued in the drop stack multiple times, we only actually
1451 // unregister the entry the first time.
1452 //
1453 // A final note: we don't need to worry about any concurrent
1454 // modifications during the middle of this function's execution, only
1455 // between (a) when we first observed a zero-registrations count and
1456 // decided to unregister the type, and (b) when we acquired the type
1457 // registry's lock so that we could perform that unregistration. This is
1458 // because this method has exclusive access to `&mut self` -- that is,
1459 // we have a write lock on the whole type registry -- and therefore no
1460 // one else can create new references to this zero-registration entry
1461 // and bring it back to life (which would require finding it in
1462 // `self.hash_consing_map`, which no one else has access to, because we
1463 // now have an exclusive lock on `self`).
1464
1465 // Handle scenario (1) from above.
1466 let registrations = entry.0.registrations.load(Acquire);
1467 if registrations != 0 {
1468 log::trace!(
1469 " {entry:?} was concurrently resurrected and no longer has \
1470 zero registrations (registrations -> {registrations})",
1471 );
1472 assert_eq!(entry.0.unregistered.load(Acquire), false);
1473 return;
1474 }
1475
1476 // Handle scenario (2) from above.
1477 if entry.0.unregistered.load(Acquire) {
1478 log::trace!(
1479 " {entry:?} was concurrently resurrected, dropped again, \
1480 and already unregistered"
1481 );
1482 return;
1483 }
1484
1485 // Okay, we are really going to unregister this entry. Enqueue it on the
1486 // drop stack.
1487 debug_assert!(self.drop_stack.capacity() >= self.types.capacity());
1488 self.push_to_drop_stack(entry);
1489 self.drain_drop_stack();
1490 }
1491
1492 /// Enqueue an entry for dropping.
1493 ///
1494 /// Should always be followed by some call to `drain_drop_stack()`, although
1495 /// it is fine to associate many `push_to_drop_stack()` calls with one final
1496 /// `drain_drop_stack()` call.
push_to_drop_stack(&mut self, entry: RecGroupEntry)1497 fn push_to_drop_stack(&mut self, entry: RecGroupEntry) {
1498 log::trace!("Pushing entry to drop stack: {entry:?}");
1499 self.drop_stack
1500 .push(entry)
1501 .expect("always have space in `drop_stack` for all types");
1502 }
1503
1504 /// Keep unregistering entries until the drop stack is empty.
1505 ///
1506 /// This is logically a recursive process where if we unregister a type that
1507 /// was the only thing keeping another type alive, we then recursively
1508 /// unregister that other type as well. However, we use an explicit drop
1509 /// stack to avoid recursion and the potential stack overflows that
1510 /// recursion implies.
drain_drop_stack(&mut self)1511 fn drain_drop_stack(&mut self) {
1512 if self.drop_stack.is_empty() {
1513 return;
1514 }
1515 log::trace!("Draining drop stack");
1516 while let Some(entry) = self.drop_stack.pop() {
1517 log::trace!("Begin unregistering {entry:?}");
1518 self.debug_assert_all_registered(&entry);
1519 self.remove_entry_impl(&entry, true);
1520 log::trace!("End unregistering {entry:?}");
1521 }
1522 }
1523 }
1524
1525 // `TypeRegistryInner` implements `Drop` in debug builds to assert that
1526 // all types have been unregistered for the registry.
1527 #[cfg(debug_assertions)]
1528 impl Drop for TypeRegistryInner {
drop(&mut self)1529 fn drop(&mut self) {
1530 let TypeRegistryInner {
1531 hash_consing_map,
1532 types,
1533 type_to_rec_group,
1534 type_to_supertypes,
1535 type_to_trampoline,
1536 type_to_gc_layout,
1537 drop_stack,
1538 } = self;
1539 assert!(
1540 hash_consing_map.is_empty(),
1541 "type registry not empty: hash consing map is not empty: {hash_consing_map:#?}"
1542 );
1543 assert!(
1544 types.is_empty(),
1545 "type registry not empty: types slab is not empty: {types:#?}"
1546 );
1547 assert!(
1548 type_to_rec_group.is_empty() || type_to_rec_group.values().all(|x| x.is_none()),
1549 "type registry not empty: type-to-rec-group map is not empty: {type_to_rec_group:#?}"
1550 );
1551 assert!(
1552 type_to_supertypes.is_empty() || type_to_supertypes.values().all(|x| x.is_none()),
1553 "type registry not empty: type-to-supertypes map is not empty: {type_to_supertypes:#?}"
1554 );
1555 assert!(
1556 type_to_trampoline.is_empty() || type_to_trampoline.values().all(|x| x.is_none()),
1557 "type registry not empty: type-to-trampoline map is not empty: {type_to_trampoline:#?}"
1558 );
1559 assert!(
1560 type_to_gc_layout.is_empty() || type_to_gc_layout.values().all(|x| x.is_none()),
1561 "type registry not empty: type-to-gc-layout map is not empty: {type_to_gc_layout:#?}"
1562 );
1563 assert!(
1564 drop_stack.is_empty(),
1565 "type registry not empty: drop stack is not empty: {drop_stack:#?}"
1566 );
1567 }
1568 }
1569
1570 /// Implements a shared type registry.
1571 ///
1572 /// WebAssembly requires that the caller and callee types in an indirect
1573 /// call must match. To implement this efficiently, keep a registry of all
1574 /// types, shared by all instances, so that call sites can just do an
1575 /// index comparison.
1576 #[derive(Debug)]
1577 pub struct TypeRegistry(RwLock<TypeRegistryInner>);
1578
1579 impl TypeRegistry {
1580 /// Creates a new shared type registry.
new() -> Self1581 pub fn new() -> Self {
1582 Self(RwLock::new(TypeRegistryInner::default()))
1583 }
1584
1585 #[inline]
debug_assert_contains(&self, index: VMSharedTypeIndex)1586 pub fn debug_assert_contains(&self, index: VMSharedTypeIndex) {
1587 if cfg!(debug_assertions) {
1588 self.0.read().debug_assert_registered(index);
1589 }
1590 }
1591
1592 /// Looks up a function type from a shared type index.
1593 ///
1594 /// This does *NOT* prevent the type from being unregistered while you are
1595 /// still using the resulting value! Use the `RegisteredType::root`
1596 /// constructor if you need to ensure that property and you don't have some
1597 /// other mechanism already keeping the type registered.
borrow(&self, index: VMSharedTypeIndex) -> Option<Arc<WasmSubType>>1598 pub fn borrow(&self, index: VMSharedTypeIndex) -> Option<Arc<WasmSubType>> {
1599 let id = shared_type_index_to_slab_id(index);
1600 let inner = self.0.read();
1601 inner.types.get(id).and_then(|ty| ty.clone())
1602 }
1603
1604 /// Get the GC layout for the given index's type.
1605 ///
1606 /// Returns `None` for types that do not have GC layouts (i.e. function
1607 /// types).
layout(&self, index: VMSharedTypeIndex) -> Option<GcLayout>1608 pub fn layout(&self, index: VMSharedTypeIndex) -> Option<GcLayout> {
1609 let inner = self.0.read();
1610 inner.type_to_gc_layout.get(index).and_then(|l| l.clone())
1611 }
1612
1613 /// Get the trampoline type for the given function type index.
1614 ///
1615 /// Panics for non-function type indices.
trampoline_type(&self, index: VMSharedTypeIndex) -> VMSharedTypeIndex1616 pub fn trampoline_type(&self, index: VMSharedTypeIndex) -> VMSharedTypeIndex {
1617 let slab_id = shared_type_index_to_slab_id(index);
1618 let inner = self.0.read();
1619 inner.debug_assert_registered(index);
1620
1621 let ty = inner.types[slab_id].as_ref().unwrap();
1622 debug_assert!(
1623 ty.is_func(),
1624 "cannot get the trampoline type of a non-function type: {index:?} = {ty:?}"
1625 );
1626
1627 match inner.type_to_trampoline.get(index).and_then(|x| x.expand()) {
1628 Some(ty) => ty,
1629 None => {
1630 // The function type is its own trampoline type.
1631 index
1632 }
1633 }
1634 }
1635
1636 /// Is type `sub` a subtype of `sup`?
1637 #[inline]
is_subtype(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool1638 pub fn is_subtype(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
1639 if cfg!(debug_assertions) {
1640 self.0.read().debug_assert_registered(sub);
1641 self.0.read().debug_assert_registered(sup);
1642 }
1643
1644 if sub == sup {
1645 return true;
1646 }
1647
1648 self.is_subtype_slow(sub, sup)
1649 }
1650
is_subtype_slow(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool1651 fn is_subtype_slow(&self, sub: VMSharedTypeIndex, sup: VMSharedTypeIndex) -> bool {
1652 // Do the O(1) subtype checking trick:
1653 //
1654 // In a type system with single inheritance, the subtyping relationships
1655 // between all types form a set of trees. The root of each tree is a
1656 // type that has no supertype; each node's immediate children are the
1657 // types that directly subtype that node.
1658 //
1659 // For example, consider these types:
1660 //
1661 // class Base {}
1662 // class A subtypes Base {}
1663 // class B subtypes Base {}
1664 // class C subtypes A {}
1665 // class D subtypes A {}
1666 // class E subtypes C {}
1667 //
1668 // These types produce the following tree:
1669 //
1670 // Base
1671 // / \
1672 // A B
1673 // / \
1674 // C D
1675 // /
1676 // E
1677 //
1678 // Note the following properties:
1679 //
1680 // 1. If `sub` is a subtype of `sup` (either directly or transitively)
1681 // then `sup` *must* be on the path from `sub` up to the root of
1682 // `sub`'s tree.
1683 //
1684 // 2. Additionally, `sup` *must* be the `i`th node down from the root in
1685 // that path, where `i` is the length of the path from `sup` to its
1686 // tree's root.
1687 //
1688 // Therefore, if we have the path to the root for each type (we do) then
1689 // we can simply check if `sup` is at index `supertypes(sup).len()`
1690 // within `supertypes(sub)`.
1691 let inner = self.0.read();
1692 let sub_supertypes = inner.supertypes(sub);
1693 let sup_supertypes = inner.supertypes(sup);
1694 sub_supertypes.get(sup_supertypes.len()) == Some(&sup)
1695 }
1696 }
1697