Browse Source

wasmtime(gc): Add support for array types (#8481)

This commit adds support for defining array types from Wasm or the host, and
managing them inside the engine's types registry. It does not introduce support
for allocating or manipulating array values. That functionality will come in
future pull requests.
pull/8492/head
Nick Fitzgerald 6 months ago
committed by GitHub
parent
commit
dd70e31dc0
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      Cargo.lock
  2. 9
      crates/c-api/src/table.rs
  3. 111
      crates/cranelift/src/func_environ.rs
  4. 13
      crates/cranelift/src/gc/enabled.rs
  5. 20
      crates/cranelift/src/lib.rs
  6. 2
      crates/environ/src/compile/module_environ.rs
  7. 88
      crates/environ/src/compile/module_types.rs
  8. 1
      crates/environ/src/component/types_builder.rs
  9. 6
      crates/environ/src/module_types.rs
  10. 7
      crates/fuzzing/src/oracles/dummy.rs
  11. 54
      crates/runtime/src/instance.rs
  12. 14
      crates/runtime/src/instance/allocator.rs
  13. 12
      crates/runtime/src/table.rs
  14. 30
      crates/runtime/src/vmcontext.rs
  15. 215
      crates/types/src/lib.rs
  16. 8
      crates/wasmtime/Cargo.toml
  17. 33
      crates/wasmtime/src/compile.rs
  18. 18
      crates/wasmtime/src/runtime/coredump.rs
  19. 7
      crates/wasmtime/src/runtime/externals/global.rs
  20. 4
      crates/wasmtime/src/runtime/externals/table.rs
  21. 37
      crates/wasmtime/src/runtime/func/typed.rs
  22. 12
      crates/wasmtime/src/runtime/gc/enabled/anyref.rs
  23. 12
      crates/wasmtime/src/runtime/gc/enabled/externref.rs
  24. 6
      crates/wasmtime/src/runtime/gc/enabled/i31.rs
  25. 11
      crates/wasmtime/src/runtime/linker.rs
  26. 2
      crates/wasmtime/src/runtime/type_registry.rs
  27. 547
      crates/wasmtime/src/runtime/types.rs
  28. 59
      crates/wasmtime/src/runtime/types/matching.rs
  29. 2
      crates/wasmtime/src/runtime/v128.rs
  30. 18
      crates/wasmtime/src/runtime/values.rs
  31. 8
      tests/all/func.rs
  32. 1
      tests/all/main.rs
  33. 25
      tests/all/types.rs
  34. 2
      tests/misc_testsuite/component-model/modules.wast
  35. 20
      tests/misc_testsuite/gc/array-types.wast
  36. 4
      tests/misc_testsuite/linking-errors.wast

1
Cargo.lock

@ -3414,6 +3414,7 @@ dependencies = [
"serde",
"serde_derive",
"serde_json",
"smallvec",
"target-lexicon",
"tempfile",
"wasi-common",

9
crates/c-api/src/table.rs

@ -33,11 +33,12 @@ impl wasm_table_t {
}
fn option_wasm_ref_t_to_ref(r: Option<&wasm_ref_t>, table_ty: &TableType) -> Ref {
match (r.map(|r| r.r.clone()), table_ty.element().heap_type()) {
(None, HeapType::NoFunc | HeapType::Func | HeapType::ConcreteFunc(_)) => Ref::Func(None),
(None, HeapType::Extern) => Ref::Extern(None),
(None, HeapType::Any | HeapType::I31 | HeapType::None) => Ref::Any(None),
match (r.map(|r| r.r.clone()), table_ty.element().heap_type().top()) {
(Some(r), _) => r,
(None, HeapType::Func) => Ref::Func(None),
(None, HeapType::Extern) => Ref::Extern(None),
(None, HeapType::Any) => Ref::Any(None),
(None, ty) => unreachable!("not a top type: {ty:?}"),
}
}

111
crates/cranelift/src/func_environ.rs

@ -13,7 +13,7 @@ use cranelift_frontend::Variable;
use cranelift_wasm::{
EngineOrModuleTypeIndex, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap,
HeapData, HeapStyle, MemoryIndex, TableData, TableIndex, TableSize, TargetEnvironment,
TypeIndex, WasmHeapType, WasmResult,
TypeIndex, WasmHeapTopType, WasmHeapType, WasmResult,
};
use std::mem;
use wasmparser::Operator;
@ -1228,6 +1228,8 @@ impl<'a, 'func, 'module_env> Call<'a, 'func, 'module_env> {
| WasmHeapType::Extern
| WasmHeapType::Any
| WasmHeapType::I31
| WasmHeapType::Array
| WasmHeapType::ConcreteArray(_)
| WasmHeapType::None => {
unreachable!()
}
@ -1418,15 +1420,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
delta: ir::Value,
init_value: ir::Value,
) -> WasmResult<ir::Value> {
let grow = match self.module.table_plans[table_index].table.wasm_ty.heap_type {
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
self.builtin_functions.table_grow_func_ref(&mut pos.func)
}
ty @ WasmHeapType::Any
| ty @ WasmHeapType::I31
| ty @ WasmHeapType::None
| ty @ WasmHeapType::Extern => gc::gc_ref_table_grow_builtin(ty, self, &mut pos.func)?,
let ty = self.module.table_plans[table_index].table.wasm_ty.heap_type;
let grow = if ty.is_vmgcref_type() {
gc::gc_ref_table_grow_builtin(ty, self, &mut pos.func)?
} else {
debug_assert_eq!(ty.top(), WasmHeapTopType::Func);
self.builtin_functions.table_grow_func_ref(&mut pos.func)
};
let vmctx = self.vmctx_val(&mut pos);
@ -1448,44 +1447,43 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let plan = &self.module.table_plans[table_index];
self.ensure_table_exists(builder.func, table_index);
let table_data = self.tables[table_index].as_ref().unwrap();
match plan.table.wasm_ty.heap_type {
// GC-managed types.
WasmHeapType::Any | WasmHeapType::Extern | WasmHeapType::None => {
let heap_ty = plan.table.wasm_ty.heap_type;
match heap_ty.top() {
// `i31ref`s never need barriers, and therefore don't need to go
// through the GC compiler.
WasmHeapTopType::Any if heap_ty == WasmHeapType::I31 => {
let (src, flags) = table_data.prepare_table_addr(
builder,
index,
self.pointer_type(),
self.isa.flags().enable_table_access_spectre_mitigation(),
);
gc::gc_compiler(self).translate_read_gc_reference(
self,
builder,
plan.table.wasm_ty,
src,
flags,
)
gc::unbarriered_load_gc_ref(self, builder, WasmHeapType::I31, src, flags)
}
// `i31ref`s never need barriers, and therefore don't need to go
// through the GC compiler.
WasmHeapType::I31 => {
// GC-managed types.
WasmHeapTopType::Any | WasmHeapTopType::Extern => {
let (src, flags) = table_data.prepare_table_addr(
builder,
index,
self.pointer_type(),
self.isa.flags().enable_table_access_spectre_mitigation(),
);
gc::unbarriered_load_gc_ref(self, builder, WasmHeapType::I31, src, flags)
gc::gc_compiler(self).translate_read_gc_reference(
self,
builder,
plan.table.wasm_ty,
src,
flags,
)
}
// Function types.
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
match plan.style {
TableStyle::CallerChecksSignature => {
Ok(self.get_or_init_func_ref_table_elem(builder, table_index, index))
}
WasmHeapTopType::Func => match plan.style {
TableStyle::CallerChecksSignature => {
Ok(self.get_or_init_func_ref_table_elem(builder, table_index, index))
}
}
},
}
}
@ -1500,9 +1498,22 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
let plan = &self.module.table_plans[table_index];
self.ensure_table_exists(builder.func, table_index);
let table_data = self.tables[table_index].as_ref().unwrap();
match plan.table.wasm_ty.heap_type {
let heap_ty = plan.table.wasm_ty.heap_type;
match heap_ty.top() {
// `i31ref`s never need GC barriers, and therefore don't need to go
// through the GC compiler.
WasmHeapTopType::Any if heap_ty == WasmHeapType::I31 => {
let (addr, flags) = table_data.prepare_table_addr(
builder,
index,
self.pointer_type(),
self.isa.flags().enable_table_access_spectre_mitigation(),
);
gc::unbarriered_store_gc_ref(self, builder, WasmHeapType::I31, addr, value, flags)
}
// GC-managed types.
WasmHeapType::Any | WasmHeapType::Extern | WasmHeapType::None => {
WasmHeapTopType::Any | WasmHeapTopType::Extern => {
let (dst, flags) = table_data.prepare_table_addr(
builder,
index,
@ -1519,20 +1530,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
)
}
// `i31ref`s never need GC barriers, and therefore don't need to go
// through the GC compiler.
WasmHeapType::I31 => {
let (addr, flags) = table_data.prepare_table_addr(
builder,
index,
self.pointer_type(),
self.isa.flags().enable_table_access_spectre_mitigation(),
);
gc::unbarriered_store_gc_ref(self, builder, WasmHeapType::I31, addr, value, flags)
}
// Function types.
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
WasmHeapTopType::Func => {
match plan.style {
TableStyle::CallerChecksSignature => {
let (elem_addr, flags) = table_data.prepare_table_addr(
@ -1565,14 +1564,12 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
val: ir::Value,
len: ir::Value,
) -> WasmResult<()> {
let libcall = match self.module.table_plans[table_index].table.wasm_ty.heap_type {
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
self.builtin_functions.table_fill_func_ref(&mut pos.func)
}
ty @ WasmHeapType::Extern
| ty @ WasmHeapType::Any
| ty @ WasmHeapType::I31
| ty @ WasmHeapType::None => gc::gc_ref_table_fill_builtin(ty, self, &mut pos.func)?,
let ty = self.module.table_plans[table_index].table.wasm_ty.heap_type;
let libcall = if ty.is_vmgcref_type() {
gc::gc_ref_table_fill_builtin(ty, self, &mut pos.func)?
} else {
debug_assert_eq!(ty.top(), WasmHeapTopType::Func);
self.builtin_functions.table_fill_func_ref(&mut pos.func)
};
let vmctx = self.vmctx_val(&mut pos);
@ -1624,11 +1621,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m
mut pos: cranelift_codegen::cursor::FuncCursor,
ht: WasmHeapType,
) -> WasmResult<ir::Value> {
Ok(match ht {
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
pos.ins().iconst(self.pointer_type(), 0)
}
WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
Ok(match ht.top() {
WasmHeapTopType::Func => pos.ins().iconst(self.pointer_type(), 0),
WasmHeapTopType::Any | WasmHeapTopType::Extern => {
pos.ins().null(self.reference_type(ht))
}
})

13
crates/cranelift/src/gc/enabled.rs

@ -2,7 +2,9 @@ use super::GcCompiler;
use crate::func_environ::FuncEnvironment;
use cranelift_codegen::ir::{self, InstBuilder};
use cranelift_frontend::FunctionBuilder;
use cranelift_wasm::{TargetEnvironment, WasmHeapType, WasmRefType, WasmResult, WasmValType};
use cranelift_wasm::{
TargetEnvironment, WasmHeapTopType, WasmHeapType, WasmRefType, WasmResult, WasmValType,
};
use wasmtime_environ::{I31_DISCRIMINANT, NON_NULL_NON_I31_MASK};
/// Get the default GC compiler.
@ -171,12 +173,9 @@ impl FuncEnvironment<'_> {
) -> ir::Value {
assert!(ty.is_vmgcref_type_and_not_i31());
let might_be_i31 = match ty.heap_type {
WasmHeapType::Any => true,
WasmHeapType::Extern | WasmHeapType::None | WasmHeapType::ConcreteFunc(_) => false,
WasmHeapType::Func | WasmHeapType::NoFunc | WasmHeapType::I31 => {
unreachable!("we don't manage instances of these types with the GC")
}
let might_be_i31 = match ty.heap_type.top() {
WasmHeapTopType::Any => true,
WasmHeapTopType::Extern | WasmHeapTopType::Func => false,
};
let ptr_ty = self.pointer_type();

20
crates/cranelift/src/lib.rs

@ -11,7 +11,9 @@ use cranelift_codegen::{
settings, FinalizedMachReloc, FinalizedRelocTarget, MachTrap,
};
use cranelift_entity::PrimaryMap;
use cranelift_wasm::{DefinedFuncIndex, FuncIndex, WasmFuncType, WasmHeapType, WasmValType};
use cranelift_wasm::{
DefinedFuncIndex, FuncIndex, WasmFuncType, WasmHeapTopType, WasmHeapType, WasmValType,
};
use target_lexicon::Architecture;
use wasmtime_environ::{
@ -266,15 +268,13 @@ fn wasm_call_signature(
/// Returns the reference type to use for the provided wasm type.
fn reference_type(wasm_ht: WasmHeapType, pointer_type: ir::Type) -> ir::Type {
match wasm_ht {
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => pointer_type,
WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
match pointer_type {
ir::types::I32 => ir::types::R32,
ir::types::I64 => ir::types::R64,
_ => panic!("unsupported pointer type"),
}
}
match wasm_ht.top() {
WasmHeapTopType::Func => pointer_type,
WasmHeapTopType::Any | WasmHeapTopType::Extern => match pointer_type {
ir::types::I32 => ir::types::R32,
ir::types::I64 => ir::types::R64,
_ => panic!("unsupported pointer type"),
},
}
}

2
crates/environ/src/compile/module_environ.rs

@ -1195,6 +1195,8 @@ impl ModuleTranslation<'_> {
WasmHeapType::Extern
| WasmHeapType::Any
| WasmHeapType::I31
| WasmHeapType::Array
| WasmHeapType::ConcreteArray(_)
| WasmHeapType::None => break,
}

88
crates/environ/src/compile/module_types.rs

@ -1,10 +1,9 @@
use crate::{Module, ModuleTypes, TypeConvert, TypeIndex, WasmHeapType};
use cranelift_entity::EntityRef;
use crate::{EntityRef, Module, ModuleTypes, TypeConvert};
use std::{collections::HashMap, ops::Index};
use wasmparser::{UnpackedIndex, Validator, ValidatorId};
use wasmtime_types::{
EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, WasmResult,
WasmSubType,
EngineOrModuleTypeIndex, ModuleInternedRecGroupIndex, ModuleInternedTypeIndex, TypeIndex,
WasmCompositeType, WasmHeapType, WasmResult, WasmSubType,
};
/// A type marking the start of a recursion group's definition.
@ -105,7 +104,9 @@ impl ModuleTypesBuilder {
for id in validator_types.rec_group_elements(rec_group_id) {
let ty = &validator_types[id];
let wasm_ty = WasmparserTypeConverter::new(self, module).convert_sub_type(ty)?;
let wasm_ty = WasmparserTypeConverter::new(self, module)
.with_rec_group(validator_types, rec_group_id)
.convert_sub_type(ty)?;
self.wasm_sub_type_in_rec_group(id, wasm_ty);
}
@ -265,12 +266,31 @@ where
pub struct WasmparserTypeConverter<'a> {
types: &'a ModuleTypesBuilder,
module: &'a Module,
rec_group_context: Option<(
wasmparser::types::TypesRef<'a>,
wasmparser::types::RecGroupId,
)>,
}
impl<'a> WasmparserTypeConverter<'a> {
/// Construct a new type converter from `wasmparser` types to Wasmtime types.
pub fn new(types: &'a ModuleTypesBuilder, module: &'a Module) -> Self {
Self { types, module }
Self {
types,
module,
rec_group_context: None,
}
}
/// Configure this converter to be within the context of defining the
/// current rec group.
pub fn with_rec_group(
&mut self,
wasmparser_types: wasmparser::types::TypesRef<'a>,
rec_group: wasmparser::types::RecGroupId,
) -> &Self {
self.rec_group_context = Some((wasmparser_types, rec_group));
self
}
}
@ -278,14 +298,62 @@ impl TypeConvert for WasmparserTypeConverter<'_> {
fn lookup_heap_type(&self, index: UnpackedIndex) -> WasmHeapType {
match index {
UnpackedIndex::Id(id) => {
let signature = self.types.wasmparser_to_wasmtime[&id];
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(signature))
let interned = self.types.wasmparser_to_wasmtime[&id];
let index = EngineOrModuleTypeIndex::Module(interned);
// If this is a forward reference to a type in this type's rec
// group that we haven't converted yet, then we won't have an
// entry in `wasm_types` yet. In this case, fallback to a
// different means of determining whether this is a concrete
// array vs struct vs func reference. In this case, we can use
// the validator's type context.
if let Some(ty) = self.types.types.get(interned) {
match &ty.composite_type {
WasmCompositeType::Array(_) => WasmHeapType::ConcreteArray(index),
WasmCompositeType::Func(_) => WasmHeapType::ConcreteFunc(index),
}
} else if let Some((wasmparser_types, _)) = self.rec_group_context.as_ref() {
match &wasmparser_types[id].composite_type {
wasmparser::CompositeType::Array(_) => WasmHeapType::ConcreteArray(index),
wasmparser::CompositeType::Func(_) => WasmHeapType::ConcreteFunc(index),
wasmparser::CompositeType::Struct(_) => unreachable!(),
}
} else {
panic!("forward reference to type outside of rec group?")
}
}
UnpackedIndex::Module(module_index) => {
let module_index = TypeIndex::from_u32(module_index);
let interned_index = self.module.types[module_index];
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(interned_index))
let interned = self.module.types[module_index];
let index = EngineOrModuleTypeIndex::Module(interned);
// See comment above about `wasm_types` maybe not having the
// converted sub type yet. However in this case we don't have a
// `wasmparser::types::CoreTypeId` on hand, so we have to
// indirectly get one by looking it up inside the current rec
// group.
if let Some(ty) = self.types.types.get(interned) {
match &ty.composite_type {
WasmCompositeType::Array(_) => WasmHeapType::ConcreteArray(index),
WasmCompositeType::Func(_) => WasmHeapType::ConcreteFunc(index),
}
} else if let Some((parser_types, rec_group)) = self.rec_group_context.as_ref() {
let rec_group_index = interned.index() - self.types.types.len_types();
let id = parser_types
.rec_group_elements(*rec_group)
.nth(rec_group_index)
.unwrap();
match &parser_types[id].composite_type {
wasmparser::CompositeType::Array(_) => WasmHeapType::ConcreteArray(index),
wasmparser::CompositeType::Func(_) => WasmHeapType::ConcreteFunc(index),
wasmparser::CompositeType::Struct(_) => unreachable!(),
}
} else {
panic!("forward reference to type outside of rec group?")
}
}
UnpackedIndex::RecGroup(_) => unreachable!(),
}
}

1
crates/environ/src/component/types_builder.rs

@ -174,6 +174,7 @@ impl ComponentTypesBuilder {
self.module_types
.wasm_types()
.find(|(_, ty)| match &ty.composite_type {
wasmtime_types::WasmCompositeType::Array(_) => false,
wasmtime_types::WasmCompositeType::Func(sig) => {
sig.params().len() == 1
&& sig.returns().len() == 0

6
crates/environ/src/module_types.rs

@ -25,9 +25,9 @@ impl ModuleTypes {
self.wasm_types.iter()
}
/// Get the type at the specified index.
pub fn get(&self, ty: ModuleInternedTypeIndex) -> &WasmSubType {
&self.wasm_types[ty]
/// Get the type at the specified index, if it exists.
pub fn get(&self, ty: ModuleInternedTypeIndex) -> Option<&WasmSubType> {
self.wasm_types.get(ty)
}
/// Get an iterator over all recursion groups defined in this module and

7
crates/fuzzing/src/oracles/dummy.rs

@ -45,11 +45,12 @@ pub fn dummy_value(val_ty: ValType) -> Result<Val> {
ValType::F32 => Val::F32(0),
ValType::F64 => Val::F64(0),
ValType::V128 => Val::V128(0.into()),
ValType::Ref(r) => match r.heap_type() {
ValType::Ref(r) => match r.heap_type().top() {
_ if !r.is_nullable() => bail!("cannot construct a dummy value of type `{r}`"),
HeapType::Extern => Val::null_extern_ref(),
HeapType::NoFunc | HeapType::Func | HeapType::ConcreteFunc(_) => Val::null_func_ref(),
HeapType::Any | HeapType::I31 | HeapType::None => Val::null_any_ref(),
HeapType::Func => Val::null_func_ref(),
HeapType::Any => Val::null_any_ref(),
ty => unreachable!("not a top type: {ty:?}"),
},
})
}

54
crates/runtime/src/instance.rs

@ -25,12 +25,12 @@ use std::ptr::NonNull;
use std::sync::atomic::AtomicU64;
use std::sync::Arc;
use std::{mem, ptr};
use wasmtime_environ::WasmHeapType;
use wasmtime_environ::{
packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex,
DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex,
HostPtr, MemoryIndex, MemoryPlan, Module, ModuleInternedTypeIndex, PrimaryMap, TableIndex,
TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex, VMCONTEXT_MAGIC,
TableInitialValue, TableSegmentElements, Trap, VMOffsets, VMSharedTypeIndex, WasmHeapTopType,
VMCONTEXT_MAGIC,
};
#[cfg(feature = "wmemcheck")]
use wasmtime_wmemcheck::Wmemcheck;
@ -877,8 +877,13 @@ impl Instance {
.and_then(|s| s.get(..len))
.ok_or(Trap::TableOutOfBounds)?;
let mut context = ConstEvalContext::new(self, &module);
match module.table_plans[table_index].table.wasm_ty.heap_type {
WasmHeapType::Extern => table.init_gc_refs(
match module.table_plans[table_index]
.table
.wasm_ty
.heap_type
.top()
{
WasmHeapTopType::Extern => table.init_gc_refs(
dst,
exprs.iter().map(|expr| unsafe {
let raw = const_evaluator
@ -887,28 +892,25 @@ impl Instance {
VMGcRef::from_raw_u32(raw.get_externref())
}),
)?,
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => table
.init_gc_refs(
dst,
exprs.iter().map(|expr| unsafe {
let raw = const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid");
VMGcRef::from_raw_u32(raw.get_anyref())
}),
)?,
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
table.init_func(
dst,
exprs.iter().map(|expr| unsafe {
const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid")
.get_funcref()
.cast()
}),
)?
}
WasmHeapTopType::Any => table.init_gc_refs(
dst,
exprs.iter().map(|expr| unsafe {
let raw = const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid");
VMGcRef::from_raw_u32(raw.get_anyref())
}),
)?,
WasmHeapTopType::Func => table.init_func(
dst,
exprs.iter().map(|expr| unsafe {
const_evaluator
.eval(&mut context, expr)
.expect("const expr should be valid")
.get_funcref()
.cast()
}),
)?,
}
}
}

14
crates/runtime/src/instance/allocator.rs

@ -10,7 +10,7 @@ use std::{alloc, any::Any, mem, ptr, sync::Arc};
use wasmtime_environ::{
DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization,
MemoryInitializer, MemoryPlan, Module, PrimaryMap, TableInitialValue, TablePlan, TableSegment,
Trap, VMOffsets, WasmValType, WASM_PAGE_SIZE,
Trap, VMOffsets, WasmHeapTopType, WasmValType, WASM_PAGE_SIZE,
};
#[cfg(feature = "gc")]
@ -574,8 +574,8 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
};
let idx = module.table_index(table);
let table = unsafe { instance.get_defined_table(table).as_mut().unwrap() };
match module.table_plans[idx].table.wasm_ty.heap_type {
wasmtime_environ::WasmHeapType::Extern => {
match module.table_plans[idx].table.wasm_ty.heap_type.top() {
WasmHeapTopType::Extern => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_externref());
let gc_store = unsafe { (*instance.store()).gc_store() };
let items = (0..table.size())
@ -583,9 +583,7 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
table.init_gc_refs(0, items)?;
}
wasmtime_environ::WasmHeapType::Any
| wasmtime_environ::WasmHeapType::I31
| wasmtime_environ::WasmHeapType::None => {
WasmHeapTopType::Any => {
let gc_ref = VMGcRef::from_raw_u32(raw.get_anyref());
let gc_store = unsafe { (*instance.store()).gc_store() };
let items = (0..table.size())
@ -593,9 +591,7 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> {
table.init_gc_refs(0, items)?;
}
wasmtime_environ::WasmHeapType::Func
| wasmtime_environ::WasmHeapType::ConcreteFunc(_)
| wasmtime_environ::WasmHeapType::NoFunc => {
WasmHeapTopType::Func => {
let funcref = raw.get_funcref().cast::<VMFuncRef>();
let items = (0..table.size()).map(|_| funcref);
table.init_func(0, items)?;

12
crates/runtime/src/table.rs

@ -11,7 +11,7 @@ use sptr::Strict;
use std::ops::Range;
use std::ptr::{self, NonNull};
use wasmtime_environ::{
TablePlan, Trap, WasmHeapType, WasmRefType, FUNCREF_INIT_BIT, FUNCREF_MASK,
TablePlan, Trap, WasmHeapTopType, WasmRefType, FUNCREF_INIT_BIT, FUNCREF_MASK,
};
/// An element going into or coming out of a table.
@ -247,13 +247,9 @@ impl From<DynamicGcRefTable> for Table {
}
fn wasm_to_table_type(ty: WasmRefType) -> TableElementType {
match ty.heap_type {
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
TableElementType::Func
}
WasmHeapType::Extern | WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
TableElementType::GcRef
}
match ty.heap_type.top() {
WasmHeapTopType::Func => TableElementType::Func,
WasmHeapTopType::Any | WasmHeapTopType::Extern => TableElementType::GcRef,
}
}

30
crates/runtime/src/vmcontext.rs

@ -13,10 +13,10 @@ use std::ptr::{self, NonNull};
use std::sync::atomic::{AtomicUsize, Ordering};
use std::u32;
pub use vm_host_func_context::{VMArrayCallHostFuncContext, VMNativeCallHostFuncContext};
use wasmtime_environ::VMSharedTypeIndex;
use wasmtime_environ::WasmHeapType;
use wasmtime_environ::WasmValType;
use wasmtime_environ::{BuiltinFunctionIndex, DefinedMemoryIndex, Unsigned, VMCONTEXT_MAGIC};
use wasmtime_environ::{
BuiltinFunctionIndex, DefinedMemoryIndex, Unsigned, VMSharedTypeIndex, WasmHeapTopType,
WasmValType, VMCONTEXT_MAGIC,
};
/// A function pointer that exposes the array calling convention.
///
@ -449,16 +449,12 @@ impl VMGlobalDefinition {
WasmValType::F32 => *global.as_f32_bits_mut() = raw.get_f32(),
WasmValType::F64 => *global.as_f64_bits_mut() = raw.get_f64(),
WasmValType::V128 => *global.as_u128_mut() = raw.get_v128(),
WasmValType::Ref(r) => match r.heap_type {
WasmHeapType::Extern => {
WasmValType::Ref(r) => match r.heap_type.top() {
WasmHeapTopType::Extern => {
global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_externref()))
}
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => {
global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_anyref()))
}
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
*global.as_func_ref_mut() = raw.get_funcref().cast()
}
WasmHeapTopType::Any => global.init_gc_ref(VMGcRef::from_raw_u32(raw.get_anyref())),
WasmHeapTopType::Func => *global.as_func_ref_mut() = raw.get_funcref().cast(),
},
}
global
@ -476,18 +472,16 @@ impl VMGlobalDefinition {
WasmValType::F32 => ValRaw::f32(*self.as_f32_bits()),
WasmValType::F64 => ValRaw::f64(*self.as_f64_bits()),
WasmValType::V128 => ValRaw::v128(*self.as_u128()),
WasmValType::Ref(r) => match r.heap_type {
WasmHeapType::Extern => ValRaw::externref(
WasmValType::Ref(r) => match r.heap_type.top() {
WasmHeapTopType::Extern => ValRaw::externref(
self.as_gc_ref()
.map_or(0, |r| gc_store.clone_gc_ref(r).as_raw_u32()),
),
WasmHeapType::Any | WasmHeapType::I31 | WasmHeapType::None => ValRaw::anyref(
WasmHeapTopType::Any => ValRaw::anyref(
self.as_gc_ref()
.map_or(0, |r| gc_store.clone_gc_ref(r).as_raw_u32()),
),
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
ValRaw::funcref(self.as_func_ref().cast())
}
WasmHeapTopType::Func => ValRaw::funcref(self.as_func_ref().cast()),
},
}
}

215
crates/types/src/lib.rs

@ -192,6 +192,7 @@ impl TypeTrace for WasmValType {
impl WasmValType {
/// Is this a type that is represented as a `VMGcRef`?
#[inline]
pub fn is_vmgcref_type(&self) -> bool {
match self {
WasmValType::Ref(r) => r.is_vmgcref_type(),
@ -204,6 +205,7 @@ impl WasmValType {
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
#[inline]
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
match self {
WasmValType::Ref(r) => r.is_vmgcref_type_and_not_i31(),
@ -246,6 +248,7 @@ impl WasmRefType {
};
/// Is this a type that is represented as a `VMGcRef`?
#[inline]
pub fn is_vmgcref_type(&self) -> bool {
self.heap_type.is_vmgcref_type()
}
@ -255,6 +258,7 @@ impl WasmRefType {
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
#[inline]
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
self.heap_type.is_vmgcref_type_and_not_i31()
}
@ -375,14 +379,29 @@ impl EngineOrModuleTypeIndex {
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum WasmHeapType {
Extern,
Func,
ConcreteFunc(EngineOrModuleTypeIndex),
NoFunc,
Any,
I31,
Array,
ConcreteArray(EngineOrModuleTypeIndex),
None,
}
impl From<WasmHeapTopType> for WasmHeapType {
#[inline]
fn from(value: WasmHeapTopType) -> Self {
match value {
WasmHeapTopType::Extern => Self::Extern,
WasmHeapTopType::Any => Self::Any,
WasmHeapTopType::Func => Self::Func,
}
}
}
impl fmt::Display for WasmHeapType {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match self {
@ -392,6 +411,8 @@ impl fmt::Display for WasmHeapType {
Self::NoFunc => write!(f, "nofunc"),
Self::Any => write!(f, "any"),
Self::I31 => write!(f, "i31"),
Self::Array => write!(f, "array"),
Self::ConcreteArray(i) => write!(f, "array {i}"),
Self::None => write!(f, "none"),
}
}
@ -403,6 +424,7 @@ impl TypeTrace for WasmHeapType {
F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
{
match *self {
Self::ConcreteArray(i) => func(i),
Self::ConcreteFunc(i) => func(i),
_ => Ok(()),
}
@ -413,6 +435,7 @@ impl TypeTrace for WasmHeapType {
F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
{
match self {
Self::ConcreteArray(i) => func(i),
Self::ConcreteFunc(i) => func(i),
_ => Ok(()),
}
@ -421,14 +444,15 @@ impl TypeTrace for WasmHeapType {
impl WasmHeapType {
/// Is this a type that is represented as a `VMGcRef`?
#[inline]
pub fn is_vmgcref_type(&self) -> bool {
match self {
match self.top() {
// All `t <: (ref null any)` and `t <: (ref null extern)` are
// represented as `VMGcRef`s.
Self::Extern | Self::Any | Self::I31 | Self::None => true,
WasmHeapTopType::Any | WasmHeapTopType::Extern => true,
// All `t <: (ref null func)` are not.
Self::Func | Self::ConcreteFunc(_) | Self::NoFunc => false,
WasmHeapTopType::Func => false,
}
}
@ -437,9 +461,39 @@ impl WasmHeapType {
///
/// That is, is this a a type that actually refers to an object allocated in
/// a GC heap?
#[inline]
pub fn is_vmgcref_type_and_not_i31(&self) -> bool {
self.is_vmgcref_type() && *self != Self::I31
}
/// Get this type's top type.
#[inline]
pub fn top(&self) -> WasmHeapTopType {
match self {
WasmHeapType::Extern => WasmHeapTopType::Extern,
WasmHeapType::Func | WasmHeapType::ConcreteFunc(_) | WasmHeapType::NoFunc => {
WasmHeapTopType::Func
}
WasmHeapType::Any
| WasmHeapType::I31
| WasmHeapType::Array
| WasmHeapType::ConcreteArray(_)
| WasmHeapType::None => WasmHeapTopType::Any,
}
}
}
/// A top heap type.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum WasmHeapTopType {
/// The common supertype of all external references.
Extern,
/// The common supertype of all internal references.
Any,
/// The common supertype of all function references.
Func,
}
/// WebAssembly function type -- equivalent of `wasmparser`'s FuncType.
@ -523,12 +577,102 @@ impl WasmFuncType {
}
}
/// Represents storage types introduced in the GC spec for array and struct fields.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum WasmStorageType {
/// The storage type is i8.
I8,
/// The storage type is i16.
I16,
/// The storage type is a value type.
Val(WasmValType),
}
impl fmt::Display for WasmStorageType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
WasmStorageType::I8 => write!(f, "i8"),
WasmStorageType::I16 => write!(f, "i16"),
WasmStorageType::Val(v) => fmt::Display::fmt(v, f),
}
}
}
impl TypeTrace for WasmStorageType {
fn trace<F, E>(&self, func: &mut F) -> Result<(), E>
where
F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
{
match self {
WasmStorageType::I8 | WasmStorageType::I16 => Ok(()),
WasmStorageType::Val(v) => v.trace(func),
}
}
fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>
where
F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
{
match self {
WasmStorageType::I8 | WasmStorageType::I16 => Ok(()),
WasmStorageType::Val(v) => v.trace_mut(func),
}
}
}
/// The type of a struct field or array element.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct WasmFieldType {
/// The field's element type.
pub element_type: WasmStorageType,
/// Whether this field can be mutated or not.
pub mutable: bool,
}
impl TypeTrace for WasmFieldType {
fn trace<F, E>(&self, func: &mut F) -> Result<(), E>
where
F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
{
self.element_type.trace(func)
}
fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>
where
F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
{
self.element_type.trace_mut(func)
}
}
/// A concrete array type.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub struct WasmArrayType(pub WasmFieldType);
impl TypeTrace for WasmArrayType {
fn trace<F, E>(&self, func: &mut F) -> Result<(), E>
where
F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
{
self.0.trace(func)
}
fn trace_mut<F, E>(&mut self, func: &mut F) -> Result<(), E>
where
F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
{
self.0.trace_mut(func)
}
}
/// A function, array, or struct type.
#[derive(Debug, Clone, Eq, PartialEq, Hash, Serialize, Deserialize)]
pub enum WasmCompositeType {
Array(WasmArrayType),
Func(WasmFuncType),
//
// TODO: Array and struct types.
// TODO: struct types.
}
impl WasmCompositeType {
@ -541,6 +685,7 @@ impl WasmCompositeType {
pub fn as_func(&self) -> Option<&WasmFuncType> {
match self {
WasmCompositeType::Func(f) => Some(f),
_ => None,
}
}
@ -548,6 +693,24 @@ impl WasmCompositeType {
pub fn unwrap_func(&self) -> &WasmFuncType {
self.as_func().unwrap()
}
#[inline]
pub fn is_array(&self) -> bool {
matches!(self, Self::Array(_))
}
#[inline]
pub fn as_array(&self) -> Option<&WasmArrayType> {
match self {
WasmCompositeType::Array(f) => Some(f),
_ => None,
}
}
#[inline]
pub fn unwrap_array(&self) -> &WasmArrayType {
self.as_array().unwrap()
}
}
impl TypeTrace for WasmCompositeType {
@ -556,6 +719,7 @@ impl TypeTrace for WasmCompositeType {
F: FnMut(EngineOrModuleTypeIndex) -> Result<(), E>,
{
match self {
WasmCompositeType::Array(a) => a.trace(func),
WasmCompositeType::Func(f) => f.trace(func),
}
}
@ -565,6 +729,7 @@ impl TypeTrace for WasmCompositeType {
F: FnMut(&mut EngineOrModuleTypeIndex) -> Result<(), E>,
{
match self {
WasmCompositeType::Array(a) => a.trace_mut(func),
WasmCompositeType::Func(f) => f.trace_mut(func),
}
}
@ -594,6 +759,21 @@ impl WasmSubType {
pub fn unwrap_func(&self) -> &WasmFuncType {
self.composite_type.unwrap_func()
}
#[inline]
pub fn is_array(&self) -> bool {
self.composite_type.is_array()
}
#[inline]
pub fn as_array(&self) -> Option<&WasmArrayType> {
self.composite_type.as_array()
}
#[inline]
pub fn unwrap_array(&self) -> &WasmArrayType {
self.composite_type.unwrap_array()
}
}
impl TypeTrace for WasmSubType {
@ -1167,11 +1347,32 @@ pub trait TypeConvert {
wasmparser::CompositeType::Func(f) => {
Ok(WasmCompositeType::Func(self.convert_func_type(f)))
}
wasmparser::CompositeType::Array(_) => Err(wasm_unsupported!("wasm gc: array types")),
wasmparser::CompositeType::Array(a) => {
Ok(WasmCompositeType::Array(self.convert_array_type(a)))
}
wasmparser::CompositeType::Struct(_) => Err(wasm_unsupported!("wasm gc: struct types")),
}
}
fn convert_array_type(&self, ty: &wasmparser::ArrayType) -> WasmArrayType {
WasmArrayType(self.convert_field_type(&ty.0))
}
fn convert_field_type(&self, ty: &wasmparser::FieldType) -> WasmFieldType {
WasmFieldType {
element_type: self.convert_storage_type(&ty.element_type),
mutable: ty.mutable,
}
}
fn convert_storage_type(&self, ty: &wasmparser::StorageType) -> WasmStorageType {
match ty {
wasmparser::StorageType::I8 => WasmStorageType::I8,
wasmparser::StorageType::I16 => WasmStorageType::I16,
wasmparser::StorageType::Val(v) => WasmStorageType::Val(self.convert_valtype(*v)),
}
}
/// Converts a wasmparser function type to a wasmtime type
fn convert_func_type(&self, ty: &wasmparser::FuncType) -> WasmFuncType {
let params = ty
@ -1216,14 +1417,14 @@ pub trait TypeConvert {
wasmparser::HeapType::Concrete(i) => self.lookup_heap_type(i),
wasmparser::HeapType::Any => WasmHeapType::Any,
wasmparser::HeapType::I31 => WasmHeapType::I31,
wasmparser::HeapType::Array => WasmHeapType::Array,
wasmparser::HeapType::None => WasmHeapType::None,
wasmparser::HeapType::Exn
| wasmparser::HeapType::NoExn
| wasmparser::HeapType::NoExtern
| wasmparser::HeapType::Eq
| wasmparser::HeapType::Struct
| wasmparser::HeapType::Array => {
| wasmparser::HeapType::Struct => {
unimplemented!("unsupported heap type {ty:?}");
}
}

8
crates/wasmtime/Cargo.toml

@ -59,6 +59,7 @@ gimli = { workspace = true }
# debugging information.
addr2line = { version = "0.21.0", default-features = false, optional = true }
semver = { version = "1.0.17", optional = true }
smallvec = { workspace = true, optional = true }
[target.'cfg(target_os = "windows")'.dependencies.windows-sys]
workspace = true
@ -175,7 +176,12 @@ coredump = ["dep:wasm-encoder", "runtime", "wasmtime-runtime/coredump"]
debug-builtins = ["wasmtime-runtime?/debug-builtins"]
# Enable support for executing compiled Wasm modules.
runtime = ["dep:wasmtime-runtime", "dep:wasmtime-jit-icache-coherence", "dep:wasmtime-slab"]
runtime = [
"dep:wasmtime-runtime",
"dep:wasmtime-jit-icache-coherence",
"dep:wasmtime-slab",
"dep:smallvec",
]
# Enable support for garbage collection-related things.
#

33
crates/wasmtime/src/compile.rs

@ -80,7 +80,6 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
let compile_inputs = CompileInputs::for_module(&types, &translation, functions);
let unlinked_compile_outputs = compile_inputs.compile(engine)?;
let types = types.finish();
let (compiled_funcs, function_indices) = unlinked_compile_outputs.pre_link();
// Emplace all compiled functions into the object file with any other
@ -99,6 +98,7 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = function_indices.link_and_append_code(
&types,
object,
engine,
compiled_funcs,
@ -107,6 +107,7 @@ pub(crate) fn build_artifacts<T: FinishedObject>(
)?;
let info = compilation_artifacts.unwrap_as_module_info();
let types = types.finish();
object.serialize_info(&(&info, &types));
let result = T::finish_object(object)?;
@ -159,6 +160,7 @@ pub(crate) fn build_component_artifacts<'a, T: FinishedObject>(
engine.append_bti(&mut object);
let (mut object, compilation_artifacts) = function_indices.link_and_append_code(
types.module_types_builder(),
object,
engine,
compiled_funcs,
@ -493,19 +495,20 @@ impl<'a> CompileInputs<'a> {
}
for signature in sigs {
self.push_input(move |compiler| {
let wasm_func_ty = types[signature].unwrap_func();
let trampoline = compiler.compile_wasm_to_native_trampoline(wasm_func_ty)?;
Ok(CompileOutput {
key: CompileKey::wasm_to_native_trampoline(signature),
symbol: format!(
"signatures[{}]::wasm_to_native_trampoline",
signature.as_u32()
),
function: CompiledFunction::Function(trampoline),
info: None,
})
});
if let Some(wasm_func_ty) = types[signature].as_func() {
self.push_input(move |compiler| {
let trampoline = compiler.compile_wasm_to_native_trampoline(wasm_func_ty)?;
Ok(CompileOutput {
key: CompileKey::wasm_to_native_trampoline(signature),
symbol: format!(
"signatures[{}]::wasm_to_native_trampoline",
signature.as_u32()
),
function: CompiledFunction::Function(trampoline),
info: None,
})
});
}
}
}
@ -651,6 +654,7 @@ impl FunctionIndices {
/// them to the given ELF file.
fn link_and_append_code<'a>(
mut self,
types: &ModuleTypesBuilder,
mut obj: object::write::Object<'static>,
engine: &'a Engine,
compiled_funcs: Vec<(String, Box<dyn Any + Send>)>,
@ -839,6 +843,7 @@ impl FunctionIndices {
.types
.iter()
.map(|(_, ty)| *ty)
.filter(|idx| types[*idx].is_func())
.collect::<BTreeSet<_>>();
let wasm_to_native_trampolines = unique_and_sorted_sigs
.iter()

18
crates/wasmtime/src/runtime/coredump.rs

@ -176,19 +176,17 @@ impl WasmCoreDump {
// reference types. This lets us avoid needing to figure out
// what a concrete type reference's index is in the local
// core dump index space.
ValType::Ref(r) => match r.heap_type() {
ValType::Ref(r) => match r.heap_type().top() {
HeapType::Extern => wasm_encoder::ValType::EXTERNREF,
HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => {
wasm_encoder::ValType::FUNCREF
}
HeapType::Func => wasm_encoder::ValType::FUNCREF,
HeapType::Any | HeapType::I31 | HeapType::None => {
wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Any,
})
}
HeapType::Any => wasm_encoder::ValType::Ref(wasm_encoder::RefType {
nullable: true,
heap_type: wasm_encoder::HeapType::Any,
}),
ty => unreachable!("not a top type: {ty:?}"),
},
};
let init = match g.get(&mut store) {

7
crates/wasmtime/src/runtime/externals/global.rs

@ -133,13 +133,18 @@ impl Global {
.into(),
),
HeapType::Any | HeapType::I31 | HeapType::None => definition
HeapType::Any
| HeapType::I31
| HeapType::Array
| HeapType::ConcreteArray(_) => definition
.as_gc_ref()
.map(|r| {
let r = store.unwrap_gc_store_mut().clone_gc_ref(r);
AnyRef::from_cloned_gc_ref(&mut store, r)
})
.into(),
HeapType::None => Ref::Any(None),
};
debug_assert!(
ref_ty.is_nullable() || !reference.is_null(),

4
crates/wasmtime/src/runtime/externals/table.rs

@ -167,7 +167,7 @@ impl Table {
}
runtime::TableElement::GcRef(None) => {
match self._ty(&store).element().heap_type().top(store.engine()) {
match self._ty(&store).element().heap_type().top() {
HeapType::Any => Some(Ref::Any(None)),
HeapType::Extern => Some(Ref::Extern(None)),
HeapType::Func => {
@ -179,7 +179,7 @@ impl Table {
#[cfg_attr(not(feature = "gc"), allow(unreachable_code, unused_variables))]
runtime::TableElement::GcRef(Some(x)) => {
match self._ty(&store).element().heap_type().top(store.engine()) {
match self._ty(&store).element().heap_type().top() {
HeapType::Any => {
let x = AnyRef::from_cloned_gc_ref(&mut store, x);
Some(x.into())

37
crates/wasmtime/src/runtime/func/typed.rs

@ -299,8 +299,8 @@ pub unsafe trait WasmTy: Send {
(Some(expected_ref), Some(actual_ref)) if actual_ref.heap_type().is_concrete() => {
expected_ref
.heap_type()
.top(engine)
.ensure_matches(engine, &actual_ref.heap_type().top(engine))
.top()
.ensure_matches(engine, &actual_ref.heap_type().top())
}
_ => expected.ensure_matches(engine, &actual),
},
@ -327,7 +327,7 @@ pub unsafe trait WasmTy: Send {
&self,
store: &StoreOpaque,
nullable: bool,
actual: &FuncType,
actual: &HeapType,
) -> Result<()>;
// Is this an externref?
@ -391,7 +391,7 @@ macro_rules! integers {
true
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
@ -439,7 +439,7 @@ macro_rules! floats {
true
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
#[inline]
@ -486,7 +486,7 @@ unsafe impl WasmTy for NoFunc {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
match self._inner {}
}
@ -534,13 +534,13 @@ unsafe impl WasmTy for Option<NoFunc> {
&self,
_: &StoreOpaque,
nullable: bool,
func_ty: &FuncType,
ty: &HeapType,
) -> Result<()> {
if nullable {
// `(ref null nofunc) <: (ref null $f)` for all function types `$f`.
Ok(())
} else {
bail!("argument type mismatch: expected (ref {func_ty}), found null reference")
bail!("argument type mismatch: expected non-nullable (ref {ty}), found null reference")
}
}
@ -588,9 +588,10 @@ unsafe impl WasmTy for Func {
&self,
store: &StoreOpaque,
_nullable: bool,
actual: &FuncType,
expected: &HeapType,
) -> Result<()> {
self.ensure_matches_ty(store, actual)
let expected = expected.unwrap_concrete_func();
self.ensure_matches_ty(store, expected)
.context("argument type mismatch for reference to concrete type")
}
@ -643,15 +644,16 @@ unsafe impl WasmTy for Option<Func> {
&self,
store: &StoreOpaque,
nullable: bool,
func_ty: &FuncType,
expected: &HeapType,
) -> Result<()> {
if let Some(f) = self {
f.ensure_matches_ty(store, func_ty)
let expected = expected.unwrap_concrete_func();
f.ensure_matches_ty(store, expected)
.context("argument type mismatch for reference to concrete type")
} else if nullable {
Ok(())
} else {
bail!("argument type mismatch: expected (ref {func_ty}), found null reference")
bail!("argument type mismatch: expected non-nullable (ref {expected}), found null reference")
}
}
@ -808,10 +810,11 @@ macro_rules! impl_wasm_params {
}
if $t::valtype().is_ref() {
let p = _func_ty.param(_i).unwrap();
let r = p.unwrap_ref();
if let Some(c) = r.heap_type().as_concrete() {
$t.dynamic_concrete_type_check(_store, r.is_nullable(), c)?;
let param_ty = _func_ty.param(_i).unwrap();
let ref_ty = param_ty.unwrap_ref();
let heap_ty = ref_ty.heap_type();
if heap_ty.is_concrete() {
$t.dynamic_concrete_type_check(_store, ref_ty.is_nullable(), heap_ty)?;
}
}

12
crates/wasmtime/src/runtime/gc/enabled/anyref.rs

@ -2,8 +2,8 @@
use crate::{
store::{AutoAssertNoGc, StoreOpaque},
AsContext, AsContextMut, FuncType, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType,
Result, RootSet, Rooted, ValRaw, ValType, WasmTy, I31,
AsContext, AsContextMut, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType, Result,
RootSet, Rooted, ValRaw, ValType, WasmTy, I31,
};
use std::num::NonZeroU64;
use wasmtime_runtime::VMGcRef;
@ -259,7 +259,7 @@ unsafe impl WasmTy for Rooted<AnyRef> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -314,7 +314,7 @@ unsafe impl WasmTy for Option<Rooted<AnyRef>> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -366,7 +366,7 @@ unsafe impl WasmTy for ManuallyRooted<AnyRef> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -426,7 +426,7 @@ unsafe impl WasmTy for Option<ManuallyRooted<AnyRef>> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}

12
crates/wasmtime/src/runtime/gc/enabled/externref.rs

@ -2,8 +2,8 @@
use crate::{
store::{AutoAssertNoGc, StoreOpaque},
AsContextMut, FuncType, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted,
RefType, Result, RootSet, Rooted, StoreContext, StoreContextMut, ValRaw, ValType, WasmTy,
AsContextMut, GcHeapOutOfMemory, GcRefImpl, GcRootIndex, HeapType, ManuallyRooted, RefType,
Result, RootSet, Rooted, StoreContext, StoreContextMut, ValRaw, ValType, WasmTy,
};
use anyhow::Context;
use std::any::Any;
@ -431,7 +431,7 @@ unsafe impl WasmTy for Rooted<ExternRef> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -486,7 +486,7 @@ unsafe impl WasmTy for Option<Rooted<ExternRef>> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -538,7 +538,7 @@ unsafe impl WasmTy for ManuallyRooted<ExternRef> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}
@ -598,7 +598,7 @@ unsafe impl WasmTy for Option<ManuallyRooted<ExternRef>> {
}
#[inline]
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &FuncType) -> Result<()> {
fn dynamic_concrete_type_check(&self, _: &StoreOpaque, _: bool, _: &HeapType) -> Result<()> {
unreachable!()
}

6
crates/wasmtime/src/runtime/gc/enabled/i31.rs

@ -6,7 +6,7 @@
use crate::{
store::{AutoAssertNoGc, StoreOpaque},
FuncType, HeapType, RefType, Result, ValType, WasmTy,
HeapType, RefType, Result, ValType, WasmTy,
};
use wasmtime_runtime::{VMGcRef, ValRaw};
@ -237,7 +237,7 @@ unsafe impl WasmTy for I31 {
&self,
_store: &StoreOpaque,
_nullable: bool,
_actual: &FuncType,
_actual: &HeapType,
) -> Result<()> {
unreachable!()
}
@ -300,7 +300,7 @@ unsafe impl WasmTy for Option<I31> {
&self,
_store: &StoreOpaque,
_nullable: bool,
_actual: &FuncType,
_actual: &HeapType,
) -> Result<()> {
unreachable!()
}

11
crates/wasmtime/src/runtime/linker.rs

@ -343,14 +343,11 @@ impl<T> Linker<T> {
ValType::V128 => Val::V128(0_u128.into()),
ValType::Ref(r) => {
debug_assert!(r.is_nullable());
match r.heap_type() {
HeapType::Func
| HeapType::ConcreteFunc(_)
| HeapType::NoFunc => Val::null_func_ref(),
match r.heap_type().top() {
HeapType::Func => Val::null_func_ref(),
HeapType::Extern => Val::null_extern_ref(),
HeapType::Any | HeapType::I31 | HeapType::None => {
Val::null_any_ref()
}
HeapType::Any => Val::null_any_ref(),
ty => unreachable!("not a top type: {ty:?}"),
}
}
};

2
crates/wasmtime/src/runtime/type_registry.rs

@ -463,7 +463,7 @@ impl TypeRegistryInner {
let entry = self.register_rec_group(
&map,
module_group.clone(),
iter_entity_range(module_group.clone()).map(|ty| types.get(ty).clone()),
iter_entity_range(module_group.clone()).map(|ty| types[ty].clone()),
);
for (module_ty, engine_ty) in

547
crates/wasmtime/src/runtime/types.rs

@ -2,8 +2,8 @@ use anyhow::{bail, Result};
use std::fmt::{self, Display};
use wasmtime_environ::{
EngineOrModuleTypeIndex, EntityType, Global, Memory, ModuleTypes, Table, TypeTrace,
VMSharedTypeIndex, WasmCompositeType, WasmFuncType, WasmHeapType, WasmRefType, WasmSubType,
WasmValType,
VMSharedTypeIndex, WasmArrayType, WasmCompositeType, WasmFieldType, WasmFuncType, WasmHeapType,
WasmRefType, WasmStorageType, WasmSubType, WasmValType,
};
use crate::{type_registry::RegisteredType, Engine};
@ -363,6 +363,7 @@ impl RefType {
}
/// The heap type that this is a reference to.
#[inline]
pub fn heap_type(&self) -> &HeapType {
&self.heap_type
}
@ -424,7 +425,7 @@ impl RefType {
}
pub(crate) fn is_gc_heap_type(&self) -> bool {
self.heap_type().is_gc_heap_type()
self.heap_type().is_vmgcref_type_and_points_to_object()
}
}
@ -440,22 +441,22 @@ impl RefType {
/// between types, you can use the [`HeapType::eq`] method.
#[derive(Debug, Clone, Hash)]
pub enum HeapType {
/// The `extern` heap type represents external host data.
/// The abstract `extern` heap type represents external host data.
Extern,
/// The `func` heap type represents a reference to any kind of function.
/// The abstract `func` heap type represents a reference to any kind of
/// function.
///
/// This is the top type for the function references type hierarchy, and is
/// therefore a supertype of every function reference.
Func,
/// The concrete heap type represents a reference to a function of a
/// specific, concrete type.
/// A reference to a function of a specific, concrete type.
///
/// This is a subtype of `func` and a supertype of `nofunc`.
/// These are subtypes of `func` and supertypes of `nofunc`.
ConcreteFunc(FuncType),
/// The `nofunc` heap type represents the null function reference.
/// The abstract `nofunc` heap type represents the null function reference.
///
/// This is the bottom type for the function references type hierarchy, and
/// therefore `nofunc` is a subtype of all function reference types.
@ -469,8 +470,26 @@ pub enum HeapType {
Any,
/// The `i31` heap type represents unboxed 31-bit integers.
///
/// This is a subtype of `any` and a supertype of `none`.
I31,
/// The abstract `array` heap type represents a reference to any kind of array.
///
/// This is a subtype of `any` and a supertype of all concrete array types,
/// as well as a supertype of the abstract `none` heap type.
//
// TODO: add docs for subtype of `eq` once we add that heap type
Array,
/// A reference to an array of a specific, concrete type.
///
/// These are subtypes of the `array` heap type (therefore also a subtype of
/// `any`) and supertypes of the `none` heap type.
//
// TODO: add docs for subtype of `eq` once we add that heap type
ConcreteArray(ArrayType),
/// The abstract `none` heap type represents the null internal reference.
///
/// This is the bottom type for the internal type hierarchy, and therefore
@ -486,8 +505,10 @@ impl Display for HeapType {
HeapType::NoFunc => write!(f, "nofunc"),
HeapType::Any => write!(f, "any"),
HeapType::I31 => write!(f, "i31"),
HeapType::Array => write!(f, "array"),
HeapType::None => write!(f, "none"),
HeapType::ConcreteFunc(ty) => write!(f, "(concrete {:?})", ty.type_index()),
HeapType::ConcreteFunc(ty) => write!(f, "(concrete func {:?})", ty.type_index()),
HeapType::ConcreteArray(ty) => write!(f, "(concrete array {:?})", ty.type_index()),
}
}
}
@ -499,6 +520,13 @@ impl From<FuncType> for HeapType {
}
}
impl From<ArrayType> for HeapType {
#[inline]
fn from(a: ArrayType) -> Self {
HeapType::ConcreteArray(a)
}
}
impl HeapType {
/// Is this the abstract `extern` heap type?
pub fn is_extern(&self) -> bool {
@ -540,40 +568,76 @@ impl HeapType {
/// Is this a concrete, user-defined heap type?
///
/// Types that are not concrete, user-defined types are abstract types.
#[inline]
pub fn is_concrete(&self) -> bool {
matches!(self, HeapType::ConcreteFunc(_) | HeapType::ConcreteArray(_))
}
/// Is this a concrete, user-defined function type?
pub fn is_concrete_func(&self) -> bool {
matches!(self, HeapType::ConcreteFunc(_))
}
/// Get the underlying concrete, user-defined type, if any.
/// Get the underlying concrete, user-defined function type, if any.
///
/// Returns `None` for abstract types.
pub fn as_concrete(&self) -> Option<&FuncType> {
/// Returns `None` if this is not a concrete function type.
pub fn as_concrete_func(&self) -> Option<&FuncType> {
match self {
HeapType::ConcreteFunc(f) => Some(f),
_ => None,
}
}
/// Get the underlying concrete, user-defined type, panicking if this heap
/// type is not concrete.
pub fn unwrap_concrete(&self) -> &FuncType {
self.as_concrete()
.expect("HeapType::unwrap_concrete on non-concrete heap type")
/// Get the underlying concrete, user-defined type, panicking if this is not
/// a concrete function type.
pub fn unwrap_concrete_func(&self) -> &FuncType {
self.as_concrete_func().unwrap()
}
/// Is this a concrete, user-defined array type?
pub fn is_concrete_array(&self) -> bool {
matches!(self, HeapType::ConcreteArray(_))
}
/// Get the underlying concrete, user-defined array type, if any.
///
/// Returns `None` for if this is not a concrete array type.
pub fn as_concrete_array(&self) -> Option<&ArrayType> {
match self {
HeapType::ConcreteArray(f) => Some(f),
_ => None,
}
}
/// Get the underlying concrete, user-defined type, panicking if this is not
/// a concrete array type.
pub fn unwrap_concrete_array(&self) -> &ArrayType {
self.as_concrete_array().unwrap()
}
/// Get the top type of this heap type's type hierarchy.
///
/// The returned heap type is a supertype of all types in this heap type's
/// type hierarchy.
pub fn top(&self, engine: &Engine) -> HeapType {
// The engine isn't used yet, but will be once we support Wasm GC, so
// future-proof our API.
let _ = engine;
pub fn top(&self) -> HeapType {
match self {
HeapType::Func | HeapType::ConcreteFunc(_) | HeapType::NoFunc => HeapType::Func,
HeapType::Extern => HeapType::Extern,
HeapType::Any | HeapType::I31 | HeapType::None => HeapType::Any,
HeapType::Any
| HeapType::I31
| HeapType::Array
| HeapType::ConcreteArray(_)
| HeapType::None => HeapType::Any,
}
}
/// Is this the top type within its type hierarchy?
pub fn is_top(&self) -> bool {
match self {
HeapType::Any | HeapType::Extern | HeapType::Func => true,
_ => false,
}
}
@ -602,9 +666,23 @@ impl HeapType {
(HeapType::Func, HeapType::Func) => true,
(HeapType::Func, _) => false,
(HeapType::None, HeapType::None | HeapType::I31 | HeapType::Any) => true,
(
HeapType::None,
HeapType::None
| HeapType::ConcreteArray(_)
| HeapType::Array
| HeapType::I31
| HeapType::Any,
) => true,
(HeapType::None, _) => false,
(HeapType::ConcreteArray(_), HeapType::Array | HeapType::Any) => true,
(HeapType::ConcreteArray(a), HeapType::ConcreteArray(b)) => a.matches(b),
(HeapType::ConcreteArray(_), _) => false,
(HeapType::Array, HeapType::Array | HeapType::Any) => true,
(HeapType::Array, _) => false,
(HeapType::I31, HeapType::I31 | HeapType::Any) => true,
(HeapType::I31, _) => false,
@ -644,8 +722,10 @@ impl HeapType {
| HeapType::NoFunc
| HeapType::Any
| HeapType::I31
| HeapType::Array
| HeapType::None => true,
HeapType::ConcreteFunc(ty) => ty.comes_from_same_engine(engine),
HeapType::ConcreteArray(ty) => ty.comes_from_same_engine(engine),
}
}
@ -656,10 +736,14 @@ impl HeapType {
HeapType::NoFunc => WasmHeapType::NoFunc,
HeapType::Any => WasmHeapType::Any,
HeapType::I31 => WasmHeapType::I31,
HeapType::Array => WasmHeapType::Array,
HeapType::None => WasmHeapType::None,
HeapType::ConcreteFunc(f) => {
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(f.type_index()))
}
HeapType::ConcreteArray(a) => {
WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Engine(a.type_index()))
}
}
}
@ -670,43 +754,53 @@ impl HeapType {
WasmHeapType::NoFunc => HeapType::NoFunc,
WasmHeapType::Any => HeapType::Any,
WasmHeapType::I31 => HeapType::I31,
WasmHeapType::Array => HeapType::Array,
WasmHeapType::None => HeapType::None,
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Engine(idx)) => {
HeapType::ConcreteFunc(FuncType::from_shared_type_index(engine, *idx))
}
WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Engine(idx)) => {
HeapType::ConcreteArray(ArrayType::from_shared_type_index(engine, *idx))
}
WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::Module(_))
| WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::RecGroup(_)) => {
panic!("HeapType::from_wasm_type on non-canonical heap type")
| WasmHeapType::ConcreteFunc(EngineOrModuleTypeIndex::RecGroup(_))
| WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::Module(_))
| WasmHeapType::ConcreteArray(EngineOrModuleTypeIndex::RecGroup(_)) => {
panic!("HeapType::from_wasm_type on non-canonicalized-for-runtime-usage heap type")
}
}
}
pub(crate) fn is_gc_heap_type(&self) -> bool {
// All `t <: (ref null any)` and `t <: (ref null extern)` that are
// not `(ref null? i31)` are GC-managed references.
pub(crate) fn as_registered_type(&self) -> Option<&RegisteredType> {
match self {
// These types are managed by the GC.
HeapType::Extern | HeapType::Any => true,
// TODO: Once we support concrete struct and array types, we will
// need to inspect the payload to determine whether this is a
// GC-managed type or not.
Self::ConcreteFunc(_) => false,
// These are compatible with GC references, but don't actually point
// to GC objecs. It would generally be safe to return `true` here,
// but there is no need to.
HeapType::I31 => false,
// These are a subtype of GC-managed types, but are uninhabited, so
// can never actually point to a GC object. Again, we could return
// `true` here but there is no need.
HeapType::None => false,
// These types are not managed by the GC.
HeapType::Func | HeapType::NoFunc => false,
HeapType::ConcreteFunc(f) => Some(&f.registered_type),
HeapType::ConcreteArray(a) => Some(&a.registered_type),
HeapType::Extern
| HeapType::Func
| HeapType::NoFunc
| HeapType::Any
| HeapType::I31
| HeapType::Array
| HeapType::None => None,
}
}
#[inline]
pub(crate) fn is_vmgcref_type(&self) -> bool {
match self.top() {
Self::Any | Self::Extern => true,
Self::Func => false,
ty => unreachable!("not a top type: {ty:?}"),
}
}
/// Is this a `VMGcRef` type that is not i31 and is not an uninhabited
/// bottom type?
#[inline]
pub(crate) fn is_vmgcref_type_and_points_to_object(&self) -> bool {
self.is_vmgcref_type() && !matches!(self, HeapType::I31 | HeapType::NoFunc | HeapType::None)
}
}
// External Types
@ -807,6 +901,352 @@ impl From<TableType> for ExternType {
}
}
/// The storage type of a `struct` field or `array` element.
///
/// This is either a packed 8- or -16 bit integer, or else it is some unpacked
/// Wasm value type.
#[derive(Clone, Hash)]
pub enum StorageType {
/// `i8`, an 8-bit integer.
I8,
/// `i16`, a 16-bit integer.
I16,
/// A value type.
ValType(ValType),
}
impl From<ValType> for StorageType {
#[inline]
fn from(v: ValType) -> Self {
StorageType::ValType(v)
}
}
impl StorageType {
/// Is this an `i8`?
#[inline]
pub fn is_i8(&self) -> bool {
matches!(self, Self::I8)
}
/// Is this an `i16`?
#[inline]
pub fn is_i16(&self) -> bool {
matches!(self, Self::I16)
}
/// Is this a Wasm value type?
#[inline]
pub fn is_val_type(&self) -> bool {
matches!(self, Self::I16)
}
/// Get this storage type's underlying value type, if any.
///
/// Returns `None` if this storage type is not a value type.
#[inline]
pub fn as_val_type(&self) -> Option<&ValType> {
match self {
Self::ValType(v) => Some(v),
_ => None,
}
}
/// Get this storage type's underlying value type, panicking if it is not a
/// value type.
pub fn unwrap_val_type(&self) -> &ValType {
self.as_val_type().unwrap()
}
/// Does this field type match the other field type?
///
/// That is, is this field type a subtype of the other field type?
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn matches(&self, other: &Self) -> bool {
match (self, other) {
(StorageType::I8, StorageType::I8) => true,
(StorageType::I8, _) => false,
(StorageType::I16, StorageType::I16) => true,
(StorageType::I16, _) => false,
(StorageType::ValType(a), StorageType::ValType(b)) => a.matches(b),
(StorageType::ValType(_), _) => false,
}
}
/// Is field type `a` precisely equal to field type `b`?
///
/// Returns `false` even if `a` is a subtype of `b` or vice versa, if they
/// are not exactly the same field type.
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn from_wasm_storage_type(engine: &Engine, ty: &WasmStorageType) -> Self {
match ty {
WasmStorageType::I8 => Self::I8,
WasmStorageType::I16 => Self::I16,
WasmStorageType::Val(v) => ValType::from_wasm_type(engine, &v).into(),
}
}
pub(crate) fn to_wasm_storage_type(&self) -> WasmStorageType {
match self {
Self::I8 => WasmStorageType::I8,
Self::I16 => WasmStorageType::I16,
Self::ValType(v) => WasmStorageType::Val(v.to_wasm_type()),
}
}
}
/// The type of a `struct` field or an `array`'s elements.
///
/// This is a pair of both the field's storage type and its mutability
/// (i.e. whether the field can be updated or not).
#[derive(Clone, Hash)]
pub struct FieldType {
mutability: Mutability,
element_type: StorageType,
}
impl FieldType {
/// Construct a new field type from the given parts.
#[inline]
pub fn new(mutability: Mutability, element_type: StorageType) -> Self {
Self {
mutability,
element_type,
}
}
/// Get whether or not this field type is mutable.
#[inline]
pub fn mutability(&self) -> Mutability {
self.mutability
}
/// Get this field type's storage type.
#[inline]
pub fn element_type(&self) -> &StorageType {
&self.element_type
}
/// Does this field type match the other field type?
///
/// That is, is this field type a subtype of the other field type?
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn matches(&self, other: &Self) -> bool {
(other.mutability == Mutability::Var || self.mutability == Mutability::Const)
&& self.element_type.matches(&other.element_type)
}
/// Is field type `a` precisely equal to field type `b`?
///
/// Returns `false` even if `a` is a subtype of `b` or vice versa, if they
/// are not exactly the same field type.
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn eq(a: &Self, b: &Self) -> bool {
a.matches(b) && b.matches(a)
}
pub(crate) fn from_wasm_field_type(engine: &Engine, ty: &WasmFieldType) -> Self {
Self {
mutability: if ty.mutable {
Mutability::Var
} else {
Mutability::Const
},
element_type: StorageType::from_wasm_storage_type(engine, &ty.element_type),
}
}
pub(crate) fn to_wasm_field_type(&self) -> WasmFieldType {
WasmFieldType {
element_type: self.element_type.to_wasm_storage_type(),
mutable: matches!(self.mutability, Mutability::Var),
}
}
}
/// The type of a WebAssembly array.
///
/// WebAssembly arrays are dynamically-sized, but not resizable. They contain
/// either unpacked [`Val`][crate::Val]s or packed 8-/16-bit integers.
///
/// # Subtyping and Equality
///
/// `ArrayType` does not implement `Eq`, because reference types have a
/// subtyping relationship, and so 99.99% of the time you actually want to check
/// whether one type matches (i.e. is a subtype of) another type. You can use
/// the [`ArrayType::matches`] method to perform these types of checks. If,
/// however, you are in that 0.01% scenario where you need to check precise
/// equality between types, you can use the [`ArrayType::eq`] method.
//
// TODO: Once we have array values, update above docs with a reference to the
// future `Array::matches_ty` method
#[derive(Debug, Clone, Hash)]
pub struct ArrayType {
registered_type: RegisteredType,
}
impl ArrayType {
/// Construct a new `ArrayType` with the given field type's mutability and
/// storage type.
///
/// The result will be associated with the given engine, and attempts to use
/// it with other engines will panic (for example, checking whether it is a
/// subtype of another array type that is associated with a different
/// engine).
pub fn new(engine: &Engine, field_type: FieldType) -> Self {
// Same as in `FuncType::new`: we must prevent any `RegisteredType` in
// `field_type` from being reclaimed while constructing this array type.
let _registration = field_type
.element_type
.as_val_type()
.and_then(|v| v.as_ref())
.and_then(|r| r.heap_type().as_registered_type());
let wasm_ty = WasmArrayType(field_type.to_wasm_field_type());
Self::from_wasm_array_type(engine, wasm_ty)
}
/// Get the engine that this array type is associated with.
pub fn engine(&self) -> &Engine {
self.registered_type.engine()
}
/// Get this array's underlying field type.
///
/// The field type contains information about both this array type's
/// mutability and the storage type used for its elements.
pub fn field_type(&self) -> FieldType {
FieldType::from_wasm_field_type(self.engine(), &self.as_wasm_array_type().0)
}
/// Get this array type's mutability and whether its instances' elements can
/// be updated or not.
///
/// This is a convenience method providing a short-hand for
/// `my_array_type.field_type().mutability()`.
pub fn mutability(&self) -> Mutability {
if self.as_wasm_array_type().0.mutable {
Mutability::Var
} else {
Mutability::Const
}
}
/// Get the storage type used for this array type's elements.
///
/// This is a convenience method providing a short-hand for
/// `my_array_type.field_type().element_type()`.
pub fn element_type(&self) -> StorageType {
StorageType::from_wasm_storage_type(
self.engine(),
&self.registered_type.unwrap_array().0.element_type,
)
}
/// Does this array type match the other array type?
///
/// That is, is this function type a subtype of the other array type?
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn matches(&self, other: &ArrayType) -> bool {
assert!(self.comes_from_same_engine(other.engine()));
// Avoid matching on structure for subtyping checks when we have
// precisely the same type.
if self.type_index() == other.type_index() {
return true;
}
self.field_type().matches(&other.field_type())
}
/// Is array type `a` precisely equal to array type `b`?
///
/// Returns `false` even if `a` is a subtype of `b` or vice versa, if they
/// are not exactly the same array type.
///
/// # Panics
///
/// Panics if either type is associated with a different engine from the
/// other.
pub fn eq(a: &ArrayType, b: &ArrayType) -> bool {
assert!(a.comes_from_same_engine(b.engine()));
a.type_index() == b.type_index()
}
pub(crate) fn comes_from_same_engine(&self, engine: &Engine) -> bool {
Engine::same(self.registered_type.engine(), engine)
}
pub(crate) fn type_index(&self) -> VMSharedTypeIndex {
self.registered_type.index()
}
pub(crate) fn as_wasm_array_type(&self) -> &WasmArrayType {
self.registered_type.unwrap_array()
}
/// Construct a `ArrayType` from a `WasmArrayType`.
///
/// This method should only be used when something has already registered --
/// and is *keeping registered* -- any other concrete Wasm types referenced
/// by the given `WasmArrayType`.
///
/// For example, this method may be called to convert an array type from
/// within a Wasm module's `ModuleTypes` since the Wasm module itself is
/// holding a strong reference to all of its types, including any `(ref null
/// <index>)` types used as the element type for this array type.
pub(crate) fn from_wasm_array_type(engine: &Engine, ty: WasmArrayType) -> ArrayType {
let ty = RegisteredType::new(
engine,
WasmSubType {
// TODO:
//
// is_final: true,
// supertype: None,
composite_type: WasmCompositeType::Array(ty),
},
);
Self {
registered_type: ty,
}
}
pub(crate) fn from_shared_type_index(engine: &Engine, index: VMSharedTypeIndex) -> ArrayType {
let ty = RegisteredType::root(engine, index).expect(
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
assert!(ty.is_array());
Self {
registered_type: ty,
}
}
}
/// The type of a WebAssembly function.
///
/// WebAssembly functions can have 0 or more parameters and results.
@ -861,12 +1301,12 @@ impl FuncType {
// the only thing keeping a type in the registry, we don't want to
// unregister it when we convert the `ValType` into a `WasmValType` just
// before we register our new `WasmFuncType` that will reference it.
let mut registrations = vec![];
let mut registrations = smallvec::SmallVec::<[_; 4]>::new();
let mut to_wasm_type = |ty: ValType| {
if let Some(r) = ty.as_ref() {
if let Some(c) = r.heap_type().as_concrete() {
registrations.push(c.registered_type.clone());
if let Some(r) = r.heap_type().as_registered_type() {
registrations.push(r.clone());
}
}
ty.to_wasm_type()
@ -1025,6 +1465,7 @@ impl FuncType {
"VMSharedTypeIndex is not registered in the Engine! Wrong \
engine? Didn't root the index somewhere?",
);
assert!(ty.is_func());
Self {
registered_type: ty,
}

59
crates/wasmtime/src/runtime/types/matching.rs

@ -115,25 +115,47 @@ fn concrete_type_mismatch(
actual: &WasmSubType,
) -> anyhow::Error {
let render = |ty: &WasmSubType| match &ty.composite_type {
WasmCompositeType::Array(ty) => {
format!(
"(array {})",
if ty.0.mutable {
format!("(mut {})", ty.0.element_type)
} else {
ty.0.element_type.to_string()
}
)
}
WasmCompositeType::Func(ty) => {
let params = ty
.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
let returns = ty
.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(", ");
format!("`({}) -> ({})`", params, returns)
let params = if ty.params().is_empty() {
String::new()
} else {
format!(
" (param {})",
ty.params()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ")
)
};
let returns = if ty.returns().is_empty() {
String::new()
} else {
format!(
" (result {})",
ty.returns()
.iter()
.map(|s| s.to_string())
.collect::<Vec<_>>()
.join(" ")
)
};
format!("(func{params}{returns})")
}
};
anyhow!(
"{msg}: expected func of type {}, found func of type {}",
"{msg}: expected type `{}`, found type `{}`",
render(expected),
render(actual)
)
@ -205,6 +227,7 @@ fn match_heap(expected: WasmHeapType, actual: WasmHeapType, desc: &str) -> Resul
// TODO: Wasm GC introduces subtyping between function types, so it will
// no longer suffice to check whether canonicalized type IDs are equal.
(H::ConcreteFunc(actual), H::ConcreteFunc(expected)) => actual == expected,
(H::ConcreteArray(actual), H::ConcreteArray(expected)) => actual == expected,
(H::NoFunc, H::NoFunc) => true,
(_, H::NoFunc) => false,
@ -218,12 +241,18 @@ fn match_heap(expected: WasmHeapType, actual: WasmHeapType, desc: &str) -> Resul
(H::Extern, H::Extern) => true,
(_, H::Extern) => false,
(H::Any | H::I31 | H::None, H::Any) => true,
(H::Any | H::I31 | H::Array | H::ConcreteArray(_) | H::None, H::Any) => true,
(_, H::Any) => false,
(H::I31 | H::None, H::I31) => true,
(_, H::I31) => false,
(H::Array | H::ConcreteArray(_) | H::None, H::Array) => true,
(_, H::Array) => false,
(H::None, H::ConcreteArray(_)) => true,
(_, H::ConcreteArray(_)) => false,
(H::None, H::None) => true,
(_, H::None) => false,
};

2
crates/wasmtime/src/runtime/v128.rs

@ -99,7 +99,7 @@ unsafe impl WasmTy for V128 {
&self,
_: &StoreOpaque,
_: bool,
_: &crate::FuncType,
_: &crate::HeapType,
) -> anyhow::Result<()> {
unreachable!()
}

18
crates/wasmtime/src/runtime/values.rs

@ -79,10 +79,11 @@ macro_rules! accessors {
impl Val {
/// Returns the null reference for the given heap type.
pub fn null_ref(heap_type: HeapType) -> Val {
match heap_type {
HeapType::Func | HeapType::NoFunc | HeapType::ConcreteFunc(_) => Val::FuncRef(None),
match heap_type.top() {
HeapType::Func => Val::FuncRef(None),
HeapType::Extern => Val::ExternRef(None),
HeapType::Any | HeapType::I31 | HeapType::None => Val::AnyRef(None),
HeapType::Any => Val::AnyRef(None),
_ => unreachable!(),
}
}
@ -246,11 +247,18 @@ impl Val {
HeapType::Func | HeapType::ConcreteFunc(_) => {
Func::from_raw(store, raw.get_funcref()).into()
}
HeapType::NoFunc => Ref::Func(None),
HeapType::Extern => ExternRef::from_raw(store, raw.get_externref()).into(),
HeapType::Any | HeapType::I31 => {
HeapType::Any
| HeapType::I31
| HeapType::Array
| HeapType::ConcreteArray(_) => {
AnyRef::from_raw(store, raw.get_anyref()).into()
}
HeapType::None => Ref::Any(None),
};
assert!(
@ -838,7 +846,7 @@ impl Ref {
self.ensure_matches_ty(&store, &ty)
.context("type mismatch: value does not match table element type")?;
match (self, ty.heap_type().top(store.engine())) {
match (self, ty.heap_type().top()) {
(Ref::Func(None), HeapType::Func) => {
assert!(ty.is_nullable());
Ok(TableElement::FuncRef(ptr::null_mut()))

8
tests/all/func.rs

@ -1600,7 +1600,7 @@ fn typed_concrete_param() -> anyhow::Result<()> {
assert!(e.contains("argument type mismatch for reference to concrete type"));
assert!(e.contains(
"type mismatch: expected (type (func)), \
found (type (func (param (ref null (concrete VMSharedTypeIndex(0))))))"
found (type (func (param (ref null (concrete func VMSharedTypeIndex(0))))))"
));
// And dynamic checks also work with a non-nullable super type.
@ -1614,7 +1614,7 @@ fn typed_concrete_param() -> anyhow::Result<()> {
assert!(e.contains("argument type mismatch for reference to concrete type"));
assert!(e.contains(
"type mismatch: expected (type (func)), \
found (type (func (param (ref null (concrete VMSharedTypeIndex(0))))))"
found (type (func (param (ref null (concrete func VMSharedTypeIndex(0))))))"
));
// Calling `typed` with a type that is not a supertype nor a subtype fails
@ -1671,7 +1671,7 @@ fn typed_concrete_result() -> anyhow::Result<()> {
let e = format!("{e:?}");
assert!(e.contains("type mismatch with results"));
assert!(e.contains(
"type mismatch: expected (ref nofunc), found (ref (concrete VMSharedTypeIndex(0)))"
"type mismatch: expected (ref nofunc), found (ref (concrete func VMSharedTypeIndex(0)))"
));
// Nor some unrelated type that it is neither a subtype or supertype of.
@ -1679,7 +1679,7 @@ fn typed_concrete_result() -> anyhow::Result<()> {
let e = format!("{e:?}");
assert!(e.contains("type mismatch with results"));
assert!(e.contains(
"type mismatch: expected (ref extern), found (ref (concrete VMSharedTypeIndex(0)))"
"type mismatch: expected (ref extern), found (ref (concrete func VMSharedTypeIndex(0)))"
));
Ok(())

1
tests/all/main.rs

@ -36,6 +36,7 @@ mod store;
mod table;
mod threads;
mod traps;
mod types;
mod wait_notify;
mod wasi_testsuite;
mod wast;

25
tests/all/types.rs

@ -0,0 +1,25 @@
use wasmtime::*;
#[test]
fn basic_array_types() -> Result<()> {
let engine = Engine::default();
for mutability in [Mutability::Const, Mutability::Var] {
for storage_ty in [
StorageType::I8,
StorageType::I16,
StorageType::ValType(ValType::I32),
StorageType::ValType(RefType::new(true, FuncType::new(&engine, [], []).into()).into()),
] {
let field_ty = FieldType::new(mutability, storage_ty.clone());
assert_eq!(field_ty.mutability(), mutability);
assert!(StorageType::eq(field_ty.element_type(), &storage_ty));
let array_ty = ArrayType::new(&engine, field_ty.clone());
assert!(Engine::same(array_ty.engine(), &engine));
assert!(FieldType::eq(&array_ty.field_type(), &field_ty));
assert_eq!(array_ty.mutability(), mutability);
assert!(StorageType::eq(&array_ty.element_type(), &storage_ty));
}
}
Ok(())
}

2
tests/misc_testsuite/component-model/modules.wast

@ -91,7 +91,7 @@
))
))
)
"expected func of type `(i32) -> ()`, found func of type `() -> ()`")
"expected type `(func (param i32))`, found type `(func)`")
(assert_unlinkable
(component

20
tests/misc_testsuite/gc/array-types.wast

@ -0,0 +1,20 @@
(module
(type (array i8))
(type (array i16))
(type (array i32))
(type (array i64))
(type (array f32))
(type (array f64))
(type (array anyref))
(type (array (ref 0)))
(type (array (ref null 1)))
(type (array (mut i8)))
(type (array (mut i16)))
(type (array (mut i32)))
(type (array (mut i64)))
(type (array (mut i32)))
(type (array (mut i64)))
(type (array (mut anyref)))
(type (array (mut (ref 0))))
(type (array (mut (ref null i31))))
)

4
tests/misc_testsuite/linking-errors.wast

@ -45,8 +45,8 @@
;; errors on functions
(assert_unlinkable
(module (import "m" "f" (func (param i32))))
"expected func of type `(i32) -> ()`, found func of type `() -> ()`")
"expected type `(func (param i32))`, found type `(func)`")
(assert_unlinkable
(module (import "m" "f p1r2" (func (param i32 i32) (result f64))))
"expected func of type `(i32, i32) -> (f64)`, found func of type `(f32) -> (i32, i64)`")
"expected type `(func (param i32 i32) (result f64))`, found type `(func (param f32) (result i32 i64))`")

Loading…
Cancel
Save