Skip to main content

wasmtime_environ/compile/
module_types.rs

1use crate::{
2    EngineOrModuleTypeIndex, EntityRef, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex,
3    ModuleTypes, PanicOnOom as _, TypeConvert, TypeIndex, WasmArrayType, WasmCompositeInnerType,
4    WasmCompositeType, WasmExnType, WasmFieldType, WasmFuncType, WasmHeapType, WasmResult,
5    WasmStorageType, WasmStructType, WasmSubType,
6    collections::{TryClone as _, TryCow},
7    wasm_unsupported,
8};
9use std::{
10    collections::{HashMap, hash_map::Entry},
11    ops::Index,
12};
13use wasmparser::{UnpackedIndex, Validator, ValidatorId};
14
15/// A type marking the start of a recursion group's definition.
16///
17/// This is initialized by `ModuleTypesBuilder::start_rec_group` and then
18/// finished in `ModuleTypes::end_rec_group` after all of the types in the rec
19/// group have been defined.
20struct RecGroupStart {
21    rec_group_index: ModuleInternedRecGroupIndex,
22    start: ModuleInternedTypeIndex,
23    end: ModuleInternedTypeIndex,
24}
25
26/// A builder for [`ModuleTypes`].
27pub struct ModuleTypesBuilder {
28    /// The ID of the validator that this builder is configured for. Using a
29    /// different validator, or multiple validators, with this builder would
30    /// result in silliness because our `wasmparser::types::*Id`s are only
31    /// unique within the context of a particular validator. Getting this wrong
32    /// could result in generating calls to functions of the wrong type, for
33    /// example. So therefore we always assert that a builder instances is only
34    /// ever paired with a particular validator context.
35    validator_id: ValidatorId,
36
37    /// The canonicalized and deduplicated set of types we are building.
38    types: ModuleTypes,
39
40    /// The set of trampoline-compatible function types we have already added to
41    /// `self.types`. We do this additional level of deduping, on top of what
42    /// `wasmparser` already does, so we can quickly and easily get the
43    /// trampoline type for a given function type if we've already interned one.
44    trampoline_types: HashMap<WasmFuncType, ModuleInternedTypeIndex>,
45
46    /// An interning map for exception types corresponding to function
47    /// types used by tags. Tags are nominal, but the underlying
48    /// Wasmtime types describe only the object layout and so are
49    /// structural.
50    exception_types: HashMap<ModuleInternedTypeIndex, ModuleInternedTypeIndex>,
51
52    /// A map from already-interned `wasmparser` types to their corresponding
53    /// Wasmtime type.
54    wasmparser_to_wasmtime: HashMap<wasmparser::types::CoreTypeId, ModuleInternedTypeIndex>,
55
56    /// The set of recursion groups we have already seen and interned.
57    already_seen: HashMap<wasmparser::types::RecGroupId, ModuleInternedRecGroupIndex>,
58
59    /// If we are in the middle of defining a recursion group, this is the
60    /// metadata about the recursion group we started defining.
61    defining_rec_group: Option<RecGroupStart>,
62}
63
64impl ModuleTypesBuilder {
65    /// Construct a new `ModuleTypesBuilder` using the given validator.
66    pub fn new(validator: &Validator) -> Self {
67        Self {
68            validator_id: validator.id(),
69            types: ModuleTypes::default(),
70            trampoline_types: HashMap::default(),
71            exception_types: HashMap::default(),
72            wasmparser_to_wasmtime: HashMap::default(),
73            already_seen: HashMap::default(),
74            defining_rec_group: None,
75        }
76    }
77
78    /// Reserves space for `amt` more type signatures.
79    pub fn reserve_wasm_signatures(&mut self, amt: usize) {
80        self.types.reserve(amt);
81        self.wasmparser_to_wasmtime.reserve(amt);
82        self.already_seen.reserve(amt);
83    }
84
85    /// Get the id of the validator that this builder is configured for.
86    pub fn validator_id(&self) -> ValidatorId {
87        self.validator_id
88    }
89
90    /// Intern a recursion group and all of its types into this builder.
91    ///
92    /// If the recursion group has already been interned, then it is reused.
93    ///
94    /// Panics if given types from a different validator than the one that this
95    /// builder is associated with.
96    pub fn intern_rec_group(
97        &mut self,
98        validator_types: wasmparser::types::TypesRef<'_>,
99        rec_group_id: wasmparser::types::RecGroupId,
100    ) -> WasmResult<ModuleInternedRecGroupIndex> {
101        assert_eq!(validator_types.id(), self.validator_id);
102
103        if let Some(interned) = self.already_seen.get(&rec_group_id) {
104            return Ok(*interned);
105        }
106
107        self.define_new_rec_group(validator_types, rec_group_id)
108    }
109
110    /// Define a new recursion group that we haven't already interned.
111    fn define_new_rec_group(
112        &mut self,
113        validator_types: wasmparser::types::TypesRef<'_>,
114        rec_group_id: wasmparser::types::RecGroupId,
115    ) -> WasmResult<ModuleInternedRecGroupIndex> {
116        assert_eq!(validator_types.id(), self.validator_id);
117
118        self.start_rec_group(
119            validator_types,
120            validator_types.rec_group_elements(rec_group_id),
121        );
122
123        for id in validator_types.rec_group_elements(rec_group_id) {
124            let ty = &validator_types[id];
125            let wasm_ty = WasmparserTypeConverter::new(self, |_| {
126                unreachable!("no need to lookup indexes; we already have core type IDs")
127            })
128            .with_rec_group(validator_types, rec_group_id)
129            .convert_sub_type(ty)?;
130            self.wasm_sub_type_in_rec_group(id, wasm_ty);
131        }
132
133        let rec_group_index = self.end_rec_group(rec_group_id);
134
135        // Iterate over all the types we just defined and make sure that every
136        // function type has an associated trampoline type. This needs to happen
137        // *after* we finish defining the rec group because we may need to
138        // intern new function types, which would conflict with the contiguous
139        // range of type indices we pre-reserved for the rec group elements.
140        for ty in self.rec_group_elements(rec_group_index) {
141            if self.types[ty].is_func() {
142                let trampoline = self.intern_trampoline_type(ty);
143                self.types.set_trampoline_type(ty, trampoline);
144            }
145        }
146
147        Ok(rec_group_index)
148    }
149
150    /// Get or create the trampoline function type for the given function
151    /// type. Returns the interned type index of the trampoline function type.
152    fn intern_trampoline_type(
153        &mut self,
154        for_func_ty: ModuleInternedTypeIndex,
155    ) -> ModuleInternedTypeIndex {
156        let sub_ty = &self.types[for_func_ty];
157        let trampoline = sub_ty.unwrap_func().trampoline_type().panic_on_oom();
158
159        if let Some(idx) = self.trampoline_types.get(&trampoline) {
160            // We've already interned this trampoline type; reuse it.
161            *idx
162        } else {
163            // We have not already interned this trampoline type.
164            match trampoline {
165                // The trampoline type is the same as the original function
166                // type. We can reuse the definition and its index, but still
167                // need to intern the type into our `trampoline_types` map so we
168                // can reuse it in the future.
169                TryCow::Borrowed(f) => {
170                    self.trampoline_types
171                        .insert(f.clone_panic_on_oom(), for_func_ty);
172                    for_func_ty
173                }
174                // The trampoline type is different from the original function
175                // type. Define the trampoline type and then intern it in
176                // `trampoline_types` so we can reuse it in the future.
177                TryCow::Owned(f) => {
178                    let idx = self.types.push(WasmSubType {
179                        is_final: true,
180                        supertype: None,
181                        composite_type: WasmCompositeType {
182                            inner: WasmCompositeInnerType::Func(f.clone_panic_on_oom()),
183                            shared: sub_ty.composite_type.shared,
184                        },
185                    });
186
187                    // The trampoline type is its own trampoline type.
188                    self.types.set_trampoline_type(idx, idx);
189
190                    let next = self.types.next_ty();
191                    self.types.push_rec_group(idx..next);
192                    self.trampoline_types.insert(f, idx);
193                    idx
194                }
195            }
196        }
197    }
198
199    /// Start defining a recursion group.
200    fn start_rec_group(
201        &mut self,
202        validator_types: wasmparser::types::TypesRef<'_>,
203        elems: impl ExactSizeIterator<Item = wasmparser::types::CoreTypeId>,
204    ) {
205        log::trace!("Starting rec group of length {}", elems.len());
206
207        assert!(self.defining_rec_group.is_none());
208        assert_eq!(validator_types.id(), self.validator_id);
209
210        // Eagerly define the reverse map's entries for this rec group's types
211        // so that we can use them when converting `wasmparser` types to our
212        // types.
213        let len = elems.len();
214        for (i, wasmparser_id) in elems.enumerate() {
215            let interned = ModuleInternedTypeIndex::new(self.types.len_types() + i);
216            log::trace!(
217                "Reserving {interned:?} for {wasmparser_id:?} = {:?}",
218                validator_types[wasmparser_id]
219            );
220
221            let old_entry = self.wasmparser_to_wasmtime.insert(wasmparser_id, interned);
222            debug_assert_eq!(
223                old_entry, None,
224                "should not have already inserted {wasmparser_id:?}"
225            );
226        }
227
228        self.defining_rec_group = Some(RecGroupStart {
229            rec_group_index: self.types.next_rec_group(),
230            start: self.types.next_ty(),
231            end: ModuleInternedTypeIndex::new(self.types.len_types() + len),
232        });
233    }
234
235    /// Finish defining a recursion group.
236    fn end_rec_group(
237        &mut self,
238        rec_group_id: wasmparser::types::RecGroupId,
239    ) -> ModuleInternedRecGroupIndex {
240        let RecGroupStart {
241            rec_group_index,
242            start,
243            end,
244        } = self
245            .defining_rec_group
246            .take()
247            .expect("should be defining a rec group");
248
249        log::trace!("Ending rec group {start:?}..{end:?}");
250
251        debug_assert!(start.index() < self.types.len_types());
252        debug_assert_eq!(
253            end,
254            self.types.next_ty(),
255            "should have defined the number of types declared in `start_rec_group`"
256        );
257
258        let idx = self.types.push_rec_group(start..end);
259        debug_assert_eq!(idx, rec_group_index);
260
261        self.already_seen.insert(rec_group_id, rec_group_index);
262        rec_group_index
263    }
264
265    /// Intern a type into this builder and get its Wasmtime index.
266    ///
267    /// This will intern not only the single given type, but the type's entire
268    /// rec group. This helper method is provided as a convenience so that
269    /// callers don't have to get the type's rec group, intern the rec group,
270    /// and then look up the Wasmtime index for the original type themselves.
271    pub fn intern_type(
272        &mut self,
273        validator_types: wasmparser::types::TypesRef<'_>,
274        id: wasmparser::types::CoreTypeId,
275    ) -> WasmResult<ModuleInternedTypeIndex> {
276        assert!(self.defining_rec_group.is_none());
277        assert_eq!(validator_types.id(), self.validator_id);
278
279        let rec_group_id = validator_types.rec_group_id_of(id);
280        debug_assert!(
281            validator_types
282                .rec_group_elements(rec_group_id)
283                .any(|e| e == id)
284        );
285
286        let interned_rec_group = self.intern_rec_group(validator_types, rec_group_id)?;
287
288        let interned_type = self.wasmparser_to_wasmtime[&id];
289        debug_assert!(
290            self.rec_group_elements(interned_rec_group)
291                .any(|e| e == interned_type)
292        );
293
294        Ok(interned_type)
295    }
296
297    /// Define a new Wasm type while we are defining a rec group.
298    fn wasm_sub_type_in_rec_group(
299        &mut self,
300        id: wasmparser::types::CoreTypeId,
301        ty: WasmSubType,
302    ) -> ModuleInternedTypeIndex {
303        assert!(
304            self.defining_rec_group.is_some(),
305            "must be defining a rec group to define new types"
306        );
307
308        let module_interned_index = self.types.push(ty);
309        debug_assert_eq!(
310            self.wasmparser_to_wasmtime.get(&id),
311            Some(&module_interned_index),
312            "should have reserved the right module-interned index for this wasmparser type already"
313        );
314
315        module_interned_index
316    }
317
318    /// Define a new exception type when we see a function type used
319    /// in a tag.
320    ///
321    /// The returned `ModuleInternedTypeIndex` gives us a Wasmtime
322    /// type which corresponds to the exception object layout, but
323    /// note that these types do not exist in the Wasm spec: at the
324    /// Wasm level, only function types exist (and tags and exception
325    /// instructions reference them). For implementation reasons, we
326    /// need a separate type to describe the exception object layout,
327    /// and this registers and provides that type.
328    pub fn define_exception_type_for_tag(
329        &mut self,
330        for_func_ty: ModuleInternedTypeIndex,
331    ) -> ModuleInternedTypeIndex {
332        match self.exception_types.entry(for_func_ty) {
333            Entry::Occupied(o) => *o.get(),
334            Entry::Vacant(v) => {
335                let fields = self.types[for_func_ty]
336                    .unwrap_func()
337                    .params()
338                    .iter()
339                    .map(|valtype| WasmFieldType {
340                        element_type: WasmStorageType::Val(*valtype),
341                        mutable: false,
342                    })
343                    .collect();
344                let idx = self.types.push(WasmSubType {
345                    is_final: true,
346                    supertype: None,
347                    composite_type: WasmCompositeType {
348                        inner: WasmCompositeInnerType::Exn(WasmExnType {
349                            func_ty: EngineOrModuleTypeIndex::Module(for_func_ty),
350                            fields,
351                        }),
352                        shared: false,
353                    },
354                });
355                let next = self.types.next_ty();
356                self.types.push_rec_group(idx..next);
357                *v.insert(idx)
358            }
359        }
360    }
361
362    /// Returns the result [`ModuleTypes`] of this builder.
363    pub fn finish(self) -> ModuleTypes {
364        self.types
365    }
366
367    /// Get the elements within an already-defined rec group.
368    pub fn rec_group_elements(
369        &self,
370        rec_group: ModuleInternedRecGroupIndex,
371    ) -> impl ExactSizeIterator<Item = ModuleInternedTypeIndex> + use<> {
372        self.types.rec_group_elements(rec_group)
373    }
374
375    /// Returns an iterator over all the unique wasm types defined thus far
376    /// within this builder.
377    pub fn wasm_types(&self) -> impl Iterator<Item = (ModuleInternedTypeIndex, &WasmSubType)> {
378        self.types.wasm_types()
379    }
380
381    /// Get an iterator over all function types and their associated trampoline
382    /// type.
383    pub fn trampoline_types(
384        &self,
385    ) -> impl Iterator<Item = (ModuleInternedTypeIndex, ModuleInternedTypeIndex)> + '_ {
386        self.types.trampoline_types()
387    }
388
389    /// Get the associated trampoline type for the given function type.
390    pub fn trampoline_type(&self, ty: ModuleInternedTypeIndex) -> ModuleInternedTypeIndex {
391        self.types.trampoline_type(ty)
392    }
393
394    /// Get and unwrap a [`WasmStructType`] for the given struct index.
395    ///
396    /// # Panics
397    ///
398    /// Panics if the unwrapped type is not a struct.
399    ///
400    /// # Errors
401    ///
402    /// For now, fails with an unsupported error if the type is shared.
403    pub fn unwrap_struct(&self, ty: ModuleInternedTypeIndex) -> WasmResult<&WasmStructType> {
404        let composite_type = &self.types[ty].composite_type;
405        if composite_type.shared {
406            return Err(wasm_unsupported!("shared structs are not yet implemented"));
407        }
408        Ok(composite_type.inner.unwrap_struct())
409    }
410
411    /// Get and unwrap a [`WasmArrayType`] for the given array index.
412    ///
413    /// # Panics
414    ///
415    /// Panics if the unwrapped type is not an array.
416    ///
417    /// # Errors
418    ///
419    /// For now, fails with an unsupported error if the type is shared.
420    pub fn unwrap_array(&self, interned_ty: ModuleInternedTypeIndex) -> WasmResult<&WasmArrayType> {
421        let composite_type = &self.types[interned_ty].composite_type;
422        if composite_type.shared {
423            return Err(wasm_unsupported!("shared arrays are not yet implemented"));
424        }
425        Ok(composite_type.inner.unwrap_array())
426    }
427
428    /// Get and unwrap a [`WasmExnType`] for the given exception-type index.
429    ///
430    /// # Panics
431    ///
432    /// Panics if the unwrapped type is not an exception type.
433    ///
434    /// # Errors
435    ///
436    /// For now, fails with an unsupported error if the type is shared.
437    pub fn unwrap_exn(&self, interned_ty: ModuleInternedTypeIndex) -> WasmResult<&WasmExnType> {
438        let composite_type = &self.types[interned_ty].composite_type;
439        if composite_type.shared {
440            return Err(wasm_unsupported!(
441                "shared exceptions are not yet implemented"
442            ));
443        }
444        Ok(composite_type.inner.unwrap_exn())
445    }
446
447    /// Get and unwrap a [`WasmFuncType`] for the given function-type index.
448    ///
449    /// # Panics
450    ///
451    /// Panics if the unwrapped type is not a function type.
452    ///
453    /// # Errors
454    ///
455    /// For now, fails with an unsupported error if the type is shared.
456    pub fn unwrap_func(&self, interned_ty: ModuleInternedTypeIndex) -> WasmResult<&WasmFuncType> {
457        let composite_type = &self.types[interned_ty].composite_type;
458        if composite_type.shared {
459            return Err(wasm_unsupported!(
460                "shared functions are not yet implemented"
461            ));
462        }
463        Ok(composite_type.inner.unwrap_func())
464    }
465}
466
467// Forward the indexing impl to the internal `ModuleTypes`
468impl<T> Index<T> for ModuleTypesBuilder
469where
470    ModuleTypes: Index<T>,
471{
472    type Output = <ModuleTypes as Index<T>>::Output;
473
474    fn index(&self, sig: T) -> &Self::Output {
475        &self.types[sig]
476    }
477}
478
479/// A convert from `wasmparser` types to Wasmtime types.
480pub struct WasmparserTypeConverter<'a, F> {
481    types: &'a ModuleTypesBuilder,
482    lookup_type_idx: F,
483    rec_group_context: Option<(
484        wasmparser::types::TypesRef<'a>,
485        wasmparser::types::RecGroupId,
486    )>,
487}
488
489impl<'a, F> WasmparserTypeConverter<'a, F> {
490    /// Construct a new type converter from `wasmparser` types to Wasmtime types.
491    pub fn new(types: &'a ModuleTypesBuilder, lookup_type_idx: F) -> Self {
492        Self {
493            types,
494            lookup_type_idx,
495            rec_group_context: None,
496        }
497    }
498
499    /// Configure this converter to be within the context of defining the
500    /// current rec group.
501    pub fn with_rec_group(
502        &mut self,
503        wasmparser_types: wasmparser::types::TypesRef<'a>,
504        rec_group: wasmparser::types::RecGroupId,
505    ) -> &Self {
506        self.rec_group_context = Some((wasmparser_types, rec_group));
507        self
508    }
509}
510
511impl<F> TypeConvert for WasmparserTypeConverter<'_, F>
512where
513    F: Fn(TypeIndex) -> ModuleInternedTypeIndex,
514{
515    fn lookup_heap_type(&self, index: UnpackedIndex) -> WasmHeapType {
516        match index {
517            UnpackedIndex::Id(id) => {
518                let interned = self.types.wasmparser_to_wasmtime[&id];
519                let index = EngineOrModuleTypeIndex::Module(interned);
520
521                // If this is a forward reference to a type in this type's rec
522                // group that we haven't converted yet, then we won't have an
523                // entry in `wasm_types` yet. In this case, fallback to a
524                // different means of determining whether this is a concrete
525                // array vs struct vs func reference. In this case, we can use
526                // the validator's type context.
527                if let Some(ty) = self.types.types.get(interned) {
528                    assert!(!ty.composite_type.shared);
529                    match &ty.composite_type.inner {
530                        WasmCompositeInnerType::Array(_) => WasmHeapType::ConcreteArray(index),
531                        WasmCompositeInnerType::Func(_) => WasmHeapType::ConcreteFunc(index),
532                        WasmCompositeInnerType::Struct(_) => WasmHeapType::ConcreteStruct(index),
533                        WasmCompositeInnerType::Cont(_) => WasmHeapType::ConcreteCont(index),
534                        WasmCompositeInnerType::Exn(_) => WasmHeapType::ConcreteExn(index),
535                    }
536                } else if let Some((wasmparser_types, _)) = self.rec_group_context.as_ref() {
537                    let wasmparser_ty = &wasmparser_types[id].composite_type;
538                    assert!(!wasmparser_ty.shared);
539                    match &wasmparser_ty.inner {
540                        wasmparser::CompositeInnerType::Array(_) => {
541                            WasmHeapType::ConcreteArray(index)
542                        }
543                        wasmparser::CompositeInnerType::Func(_) => {
544                            WasmHeapType::ConcreteFunc(index)
545                        }
546                        wasmparser::CompositeInnerType::Struct(_) => {
547                            WasmHeapType::ConcreteStruct(index)
548                        }
549                        wasmparser::CompositeInnerType::Cont(_) => {
550                            WasmHeapType::ConcreteCont(index)
551                        }
552                    }
553                } else {
554                    panic!("forward reference to type outside of rec group?")
555                }
556            }
557
558            UnpackedIndex::Module(module_index) => {
559                let module_index = TypeIndex::from_u32(module_index);
560                let interned = (self.lookup_type_idx)(module_index);
561                let index = EngineOrModuleTypeIndex::Module(interned);
562
563                // See comment above about `wasm_types` maybe not having the
564                // converted sub type yet. However in this case we don't have a
565                // `wasmparser::types::CoreTypeId` on hand, so we have to
566                // indirectly get one by looking it up inside the current rec
567                // group.
568                if let Some(ty) = self.types.types.get(interned) {
569                    assert!(!ty.composite_type.shared);
570                    match &ty.composite_type.inner {
571                        WasmCompositeInnerType::Array(_) => WasmHeapType::ConcreteArray(index),
572                        WasmCompositeInnerType::Func(_) => WasmHeapType::ConcreteFunc(index),
573                        WasmCompositeInnerType::Struct(_) => WasmHeapType::ConcreteStruct(index),
574                        WasmCompositeInnerType::Cont(_) => WasmHeapType::ConcreteCont(index),
575                        WasmCompositeInnerType::Exn(_) => WasmHeapType::ConcreteExn(index),
576                    }
577                } else if let Some((parser_types, rec_group)) = self.rec_group_context.as_ref() {
578                    let rec_group_index = interned.index() - self.types.types.len_types();
579                    let id = parser_types
580                        .rec_group_elements(*rec_group)
581                        .nth(rec_group_index)
582                        .unwrap();
583                    let wasmparser_ty = &parser_types[id].composite_type;
584                    assert!(!wasmparser_ty.shared);
585                    match &wasmparser_ty.inner {
586                        wasmparser::CompositeInnerType::Array(_) => {
587                            WasmHeapType::ConcreteArray(index)
588                        }
589                        wasmparser::CompositeInnerType::Func(_) => {
590                            WasmHeapType::ConcreteFunc(index)
591                        }
592                        wasmparser::CompositeInnerType::Struct(_) => {
593                            WasmHeapType::ConcreteStruct(index)
594                        }
595                        wasmparser::CompositeInnerType::Cont(_) => {
596                            WasmHeapType::ConcreteCont(index)
597                        }
598                    }
599                } else {
600                    panic!("forward reference to type outside of rec group?")
601                }
602            }
603
604            UnpackedIndex::RecGroup(_) => unreachable!(),
605        }
606    }
607
608    fn lookup_type_index(&self, index: wasmparser::UnpackedIndex) -> EngineOrModuleTypeIndex {
609        match index {
610            UnpackedIndex::Id(id) => {
611                let interned = self.types.wasmparser_to_wasmtime[&id];
612                EngineOrModuleTypeIndex::Module(interned)
613            }
614            UnpackedIndex::Module(module_index) => {
615                let module_index = TypeIndex::from_u32(module_index);
616                let interned = (self.lookup_type_idx)(module_index);
617                EngineOrModuleTypeIndex::Module(interned)
618            }
619            UnpackedIndex::RecGroup(_) => unreachable!(),
620        }
621    }
622}