diff --git a/build.rs b/build.rs index fef51a2e8941..88128fa88645 100644 --- a/build.rs +++ b/build.rs @@ -30,6 +30,7 @@ fn main() -> anyhow::Result<()> { test_directory_module(out, "tests/misc_testsuite/threads", strategy)?; test_directory_module(out, "tests/misc_testsuite/memory64", strategy)?; test_directory_module(out, "tests/misc_testsuite/component-model", strategy)?; + test_directory_module(out, "tests/misc_testsuite/function-references", strategy)?; Ok(()) })?; @@ -39,6 +40,11 @@ fn main() -> anyhow::Result<()> { // out. if spec_tests > 0 { test_directory_module(out, "tests/spec_testsuite/proposals/memory64", strategy)?; + test_directory_module( + out, + "tests/spec_testsuite/proposals/function-references", + strategy, + )?; test_directory_module( out, "tests/spec_testsuite/proposals/multi-memory", @@ -187,6 +193,30 @@ fn ignore(testsuite: &str, testname: &str, strategy: &str) -> bool { return true; } + // Tail calls are not yet implemented. + if testname.contains("return_call") { + return true; + } + + if testsuite == "function_references" { + // The following tests fail due to function references not yet + // being exposed in the public API. + if testname == "ref_null" || testname == "local_init" { + return true; + } + // This test fails due to incomplete support for the various + // table/elem syntactic sugar in wasm-tools/wast. + if testname == "br_table" { + return true; + } + // This test fails due to the current implementation of type + // canonicalisation being broken as a result of + // #[derive(hash)] on WasmHeapType. + if testname == "type_equivalence" { + return true; + } + } + match env::var("CARGO_CFG_TARGET_ARCH").unwrap().as_str() { "s390x" => { // FIXME: These tests fail under qemu due to a qemu bug. diff --git a/cranelift/codegen/src/ir/trapcode.rs b/cranelift/codegen/src/ir/trapcode.rs index 590c82a8b3df..411f2f2ba5d8 100644 --- a/cranelift/codegen/src/ir/trapcode.rs +++ b/cranelift/codegen/src/ir/trapcode.rs @@ -51,6 +51,9 @@ pub enum TrapCode { /// A user-defined trap code. User(u16), + + /// A null reference was encountered which was required to be non-null. + NullReference, } impl TrapCode { @@ -68,6 +71,7 @@ impl TrapCode { TrapCode::BadConversionToInteger, TrapCode::UnreachableCodeReached, TrapCode::Interrupt, + TrapCode::NullReference, ] } } @@ -88,6 +92,7 @@ impl Display for TrapCode { UnreachableCodeReached => "unreachable", Interrupt => "interrupt", User(x) => return write!(f, "user{}", x), + NullReference => "null_reference", }; f.write_str(identifier) } @@ -110,6 +115,7 @@ impl FromStr for TrapCode { "bad_toint" => Ok(BadConversionToInteger), "unreachable" => Ok(UnreachableCodeReached), "interrupt" => Ok(Interrupt), + "null_reference" => Ok(NullReference), _ if s.starts_with("user") => s[4..].parse().map(User).map_err(|_| ()), _ => Err(()), } diff --git a/cranelift/codegen/src/verifier/mod.rs b/cranelift/codegen/src/verifier/mod.rs index 4f1e8a3dfd9b..0927fe9f06f6 100644 --- a/cranelift/codegen/src/verifier/mod.rs +++ b/cranelift/codegen/src/verifier/mod.rs @@ -1175,7 +1175,10 @@ impl<'a> Verifier<'a> { errors.report(( inst, self.context(inst), - format!("has an invalid controlling type {}", ctrl_type), + format!( + "has an invalid controlling type {} (allowed set is {:?})", + ctrl_type, value_typeset + ), )); } diff --git a/cranelift/filetests/src/test_wasm/env.rs b/cranelift/filetests/src/test_wasm/env.rs index 316afcbcc6ab..e9e87c8d02b3 100644 --- a/cranelift/filetests/src/test_wasm/env.rs +++ b/cranelift/filetests/src/test_wasm/env.rs @@ -13,6 +13,7 @@ use cranelift_codegen::{ }; use cranelift_wasm::{ DummyEnvironment, FuncEnvironment, FuncIndex, ModuleEnvironment, TargetEnvironment, + TypeConvert, TypeIndex, WasmHeapType, }; pub struct ModuleEnv { @@ -235,6 +236,12 @@ impl<'data> ModuleEnvironment<'data> for ModuleEnv { } } +impl TypeConvert for ModuleEnv { + fn lookup_heap_type(&self, _index: TypeIndex) -> WasmHeapType { + todo!() + } +} + pub struct FuncEnv<'a> { pub inner: cranelift_wasm::DummyFuncEnvironment<'a>, pub config: TestConfig, @@ -261,6 +268,12 @@ impl<'a> FuncEnv<'a> { } } +impl TypeConvert for FuncEnv<'_> { + fn lookup_heap_type(&self, _index: TypeIndex) -> WasmHeapType { + todo!() + } +} + impl<'a> TargetEnvironment for FuncEnv<'a> { fn target_config(&self) -> TargetFrontendConfig { self.inner.target_config() @@ -622,4 +635,14 @@ impl<'a> FuncEnvironment for FuncEnv<'a> { fn is_x86(&self) -> bool { self.config.target.contains("x86_64") } + + fn translate_call_ref( + &mut self, + _builder: &mut cranelift_frontend::FunctionBuilder<'_>, + _ty: ir::SigRef, + _func: ir::Value, + _args: &[ir::Value], + ) -> cranelift_wasm::WasmResult { + unimplemented!() + } } diff --git a/cranelift/wasm/src/code_translator.rs b/cranelift/wasm/src/code_translator.rs index 030fe43c3b44..699da10aa145 100644 --- a/cranelift/wasm/src/code_translator.rs +++ b/cranelift/wasm/src/code_translator.rs @@ -81,7 +81,6 @@ use crate::translation_utils::{ }; use crate::wasm_unsupported; use crate::{FuncIndex, GlobalIndex, MemoryIndex, TableIndex, TypeIndex, WasmResult}; -use core::convert::TryInto; use core::{i32, u32}; use cranelift_codegen::ir::condcodes::{FloatCC, IntCC}; use cranelift_codegen::ir::immediates::Offset32; @@ -1152,7 +1151,8 @@ pub fn translate_operator( translate_fcmp(FloatCC::LessThanOrEqual, builder, state) } Operator::RefNull { hty } => { - state.push1(environ.translate_ref_null(builder.cursor(), (*hty).try_into()?)?) + let hty = environ.convert_heap_type(*hty); + state.push1(environ.translate_ref_null(builder.cursor(), hty)?) } Operator::RefIsNull => { let value = state.pop1(); @@ -2337,16 +2337,74 @@ pub fn translate_operator( state.push1(builder.ins().iadd(dot32, c)); } - Operator::CallRef { .. } - | Operator::ReturnCallRef { .. } - | Operator::BrOnNull { .. } - | Operator::BrOnNonNull { .. } - | Operator::RefAsNonNull => { + Operator::ReturnCallRef { type_index: _ } => { return Err(wasm_unsupported!( - "proposed function-references operator {:?}", + "proposed tail-call operator for function references {:?}", op )); } + Operator::BrOnNull { relative_depth } => { + let r = state.pop1(); + let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); + let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; + let else_block = builder.create_block(); + canonicalise_brif(builder, is_null, br_destination, inputs, else_block, &[]); + + builder.seal_block(else_block); // The only predecessor is the current block. + builder.switch_to_block(else_block); + state.push1(r); + } + Operator::BrOnNonNull { relative_depth } => { + // We write this a bit differently from the spec to avoid an extra + // block/branch and the typed accounting thereof. Instead of the + // spec's approach, it's described as such: + // Peek the value val from the stack. + // If val is ref.null ht, then: pop the value val from the stack. + // Else: Execute the instruction (br relative_depth). + let is_null = environ.translate_ref_is_null(builder.cursor(), state.peek1())?; + let (br_destination, inputs) = translate_br_if_args(*relative_depth, state); + let else_block = builder.create_block(); + canonicalise_brif(builder, is_null, else_block, &[], br_destination, inputs); + + // In the null case, pop the ref + state.pop1(); + + builder.seal_block(else_block); // The only predecessor is the current block. + + // The rest of the translation operates on our is null case, which is + // currently an empty block + builder.switch_to_block(else_block); + } + Operator::CallRef { type_index } => { + // Get function signature + // `index` is the index of the function's signature and `table_index` is the index of + // the table to search the function in. + let (sigref, num_args) = state.get_indirect_sig(builder.func, *type_index, environ)?; + let callee = state.pop1(); + + // Bitcast any vector arguments to their default type, I8X16, before calling. + let args = state.peekn_mut(num_args); + bitcast_wasm_params(environ, sigref, args, builder); + + let call = + environ.translate_call_ref(builder, sigref, callee, state.peekn(num_args))?; + + let inst_results = builder.inst_results(call); + debug_assert_eq!( + inst_results.len(), + builder.func.dfg.signatures[sigref].returns.len(), + "translate_call_ref results should match the call signature" + ); + state.popn(num_args); + state.pushn(inst_results); + } + Operator::RefAsNonNull => { + let r = state.pop1(); + let is_null = environ.translate_ref_is_null(builder.cursor(), r)?; + builder.ins().trapnz(is_null, ir::TrapCode::NullReference); + state.push1(r); + } + Operator::I31New | Operator::I31GetS | Operator::I31GetU => { unimplemented!("GC operators not yet implemented") } diff --git a/cranelift/wasm/src/environ/dummy.rs b/cranelift/wasm/src/environ/dummy.rs index b35bb533dadb..5da3fe76d19d 100644 --- a/cranelift/wasm/src/environ/dummy.rs +++ b/cranelift/wasm/src/environ/dummy.rs @@ -11,8 +11,8 @@ use crate::state::FuncTranslationState; use crate::WasmType; use crate::{ DataIndex, DefinedFuncIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap, - HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TypeIndex, WasmFuncType, - WasmResult, + HeapData, HeapStyle, Memory, MemoryIndex, Table, TableIndex, TypeConvert, TypeIndex, + WasmFuncType, WasmHeapType, WasmResult, }; use core::convert::TryFrom; use cranelift_codegen::cursor::FuncCursor; @@ -251,6 +251,12 @@ impl<'dummy_environment> DummyFuncEnvironment<'dummy_environment> { } } +impl<'dummy_environment> TypeConvert for DummyFuncEnvironment<'dummy_environment> { + fn lookup_heap_type(&self, _index: TypeIndex) -> WasmHeapType { + unimplemented!() + } +} + impl<'dummy_environment> TargetEnvironment for DummyFuncEnvironment<'dummy_environment> { fn target_config(&self) -> TargetFrontendConfig { self.mod_info.config @@ -279,7 +285,7 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => ir::types::R64, + WasmType::Ref(_) => ir::types::R64, }, }) } @@ -464,6 +470,16 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ Ok(pos.ins().Call(ir::Opcode::Call, INVALID, callee, args).0) } + fn translate_call_ref( + &mut self, + _builder: &mut FunctionBuilder, + _sig_ref: ir::SigRef, + _callee: ir::Value, + _call_args: &[ir::Value], + ) -> WasmResult { + todo!("Implement dummy translate_call_ref") + } + fn translate_memory_grow( &mut self, mut pos: FuncCursor, @@ -658,6 +674,12 @@ impl<'dummy_environment> FuncEnvironment for DummyFuncEnvironment<'dummy_environ } } +impl TypeConvert for DummyEnvironment { + fn lookup_heap_type(&self, _index: TypeIndex) -> WasmHeapType { + unimplemented!() + } +} + impl TargetEnvironment for DummyEnvironment { fn target_config(&self) -> TargetFrontendConfig { self.info.config @@ -683,7 +705,7 @@ impl<'data> ModuleEnvironment<'data> for DummyEnvironment { WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => reference_type, + WasmType::Ref(_) => reference_type, }) }; sig.params.extend(wasm.params().iter().map(&mut cvt)); diff --git a/cranelift/wasm/src/environ/spec.rs b/cranelift/wasm/src/environ/spec.rs index 595dba293d3e..baba57c6d7f1 100644 --- a/cranelift/wasm/src/environ/spec.rs +++ b/cranelift/wasm/src/environ/spec.rs @@ -9,8 +9,8 @@ use crate::state::FuncTranslationState; use crate::{ DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Heap, HeapData, Memory, - MemoryIndex, SignatureIndex, Table, TableIndex, Tag, TagIndex, TypeIndex, WasmError, - WasmFuncType, WasmResult, WasmType, + MemoryIndex, SignatureIndex, Table, TableIndex, Tag, TagIndex, TypeConvert, TypeIndex, + WasmError, WasmFuncType, WasmHeapType, WasmResult, }; use core::convert::From; use cranelift_codegen::cursor::FuncCursor; @@ -44,7 +44,7 @@ pub enum GlobalVariable { } /// Environment affecting the translation of a WebAssembly. -pub trait TargetEnvironment { +pub trait TargetEnvironment: TypeConvert { /// Get the information needed to produce Cranelift IR for the given target. fn target_config(&self) -> TargetFrontendConfig; @@ -70,7 +70,7 @@ pub trait TargetEnvironment { /// 32-bit architectures. If you override this, then you should also /// override `FuncEnvironment::{translate_ref_null, translate_ref_is_null}` /// as well. - fn reference_type(&self, ty: WasmType) -> ir::Type { + fn reference_type(&self, ty: WasmHeapType) -> ir::Type { let _ = ty; match self.pointer_type() { ir::types::I32 => ir::types::R32, @@ -208,6 +208,22 @@ pub trait FuncEnvironment: TargetEnvironment { Ok(pos.ins().call(callee, call_args)) } + /// Translate a `call_ref` WebAssembly instruction at `pos`. + /// + /// Insert instructions at `pos` for an indirect call to the + /// function `callee`. The `callee` value will have type `Ref`. + /// + /// The signature `sig_ref` was previously created by `make_indirect_sig()`. + /// + /// Return the call instruction whose results are the WebAssembly return values. + fn translate_call_ref( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult; + /// Translate a `memory.grow` WebAssembly instruction. /// /// The `index` provided identifies the linear memory to grow, and `heap` is the heap reference @@ -373,7 +389,11 @@ pub trait FuncEnvironment: TargetEnvironment { /// null sentinel is not a null reference type pointer for your type. If you /// override this method, then you should also override /// `translate_ref_is_null` as well. - fn translate_ref_null(&mut self, mut pos: FuncCursor, ty: WasmType) -> WasmResult { + fn translate_ref_null( + &mut self, + mut pos: FuncCursor, + ty: WasmHeapType, + ) -> WasmResult { let _ = ty; Ok(pos.ins().null(self.reference_type(ty))) } @@ -547,7 +567,7 @@ pub trait FuncEnvironment: TargetEnvironment { /// An object satisfying the `ModuleEnvironment` trait can be passed as argument to the /// [`translate_module`](fn.translate_module.html) function. These methods should not be called /// by the user, they are only for `cranelift-wasm` internal use. -pub trait ModuleEnvironment<'data> { +pub trait ModuleEnvironment<'data>: TypeConvert { /// Provides the number of types up front. By default this does nothing, but /// implementations can use this to preallocate memory if desired. fn reserve_types(&mut self, _num: u32) -> WasmResult<()> { diff --git a/cranelift/wasm/src/func_translator.rs b/cranelift/wasm/src/func_translator.rs index e4594abbc1b3..e99989010fe2 100644 --- a/cranelift/wasm/src/func_translator.rs +++ b/cranelift/wasm/src/func_translator.rs @@ -9,7 +9,6 @@ use crate::environ::FuncEnvironment; use crate::state::FuncTranslationState; use crate::translation_utils::get_vmctx_value_label; use crate::WasmResult; -use core::convert::TryInto; use cranelift_codegen::entity::EntityRef; use cranelift_codegen::ir::{self, Block, InstBuilder, ValueLabel}; use cranelift_codegen::timing; @@ -193,27 +192,49 @@ fn declare_locals( ) -> WasmResult<()> { // All locals are initialized to 0. use wasmparser::ValType::*; - let zeroval = match wasm_type { - I32 => builder.ins().iconst(ir::types::I32, 0), - I64 => builder.ins().iconst(ir::types::I64, 0), - F32 => builder.ins().f32const(ir::immediates::Ieee32::with_bits(0)), - F64 => builder.ins().f64const(ir::immediates::Ieee64::with_bits(0)), + let (ty, init) = match wasm_type { + I32 => ( + ir::types::I32, + Some(builder.ins().iconst(ir::types::I32, 0)), + ), + I64 => ( + ir::types::I64, + Some(builder.ins().iconst(ir::types::I64, 0)), + ), + F32 => ( + ir::types::F32, + Some(builder.ins().f32const(ir::immediates::Ieee32::with_bits(0))), + ), + F64 => ( + ir::types::F64, + Some(builder.ins().f64const(ir::immediates::Ieee64::with_bits(0))), + ), V128 => { let constant_handle = builder.func.dfg.constants.insert([0; 16].to_vec().into()); - builder.ins().vconst(ir::types::I8X16, constant_handle) + ( + ir::types::I8X16, + Some(builder.ins().vconst(ir::types::I8X16, constant_handle)), + ) } - Ref(t) => { - assert!(t.is_nullable()); - environ.translate_ref_null(builder.cursor(), t.heap_type().try_into()?)? + Ref(rt) => { + let hty = environ.convert_heap_type(rt.heap_type()); + let ty = environ.reference_type(hty); + let init = if rt.is_nullable() { + Some(environ.translate_ref_null(builder.cursor(), hty)?) + } else { + None + }; + (ty, init) } }; - let ty = builder.func.dfg.value_type(zeroval); for _ in 0..count { let local = Variable::new(*next_local); builder.declare_var(local, ty); - builder.def_var(local, zeroval); - builder.set_val_label(zeroval, ValueLabel::new(*next_local)); + if let Some(init) = init { + builder.def_var(local, init); + builder.set_val_label(init, ValueLabel::new(*next_local)); + } *next_local += 1; } Ok(()) diff --git a/cranelift/wasm/src/sections_translator.rs b/cranelift/wasm/src/sections_translator.rs index d0345903da2f..68705ad4a775 100644 --- a/cranelift/wasm/src/sections_translator.rs +++ b/cranelift/wasm/src/sections_translator.rs @@ -10,8 +10,8 @@ use crate::environ::ModuleEnvironment; use crate::wasm_unsupported; use crate::{ - DataIndex, ElemIndex, FuncIndex, Global, GlobalIndex, GlobalInit, Memory, MemoryIndex, Table, - TableIndex, Tag, TagIndex, TypeIndex, WasmError, WasmResult, + DataIndex, ElemIndex, FuncIndex, GlobalIndex, GlobalInit, Memory, MemoryIndex, TableIndex, Tag, + TagIndex, TypeIndex, WasmError, WasmResult, }; use cranelift_entity::packed_option::ReservedValue; use cranelift_entity::EntityRef; @@ -20,9 +20,9 @@ use std::vec::Vec; use wasmparser::{ self, Data, DataKind, DataSectionReader, Element, ElementItems, ElementKind, ElementSectionReader, Export, ExportSectionReader, ExternalKind, FunctionSectionReader, - GlobalSectionReader, GlobalType, ImportSectionReader, MemorySectionReader, MemoryType, - NameSectionReader, Naming, Operator, TableSectionReader, TableType, TagSectionReader, TagType, - Type, TypeRef, TypeSectionReader, + GlobalSectionReader, ImportSectionReader, MemorySectionReader, MemoryType, NameSectionReader, + Naming, Operator, TableSectionReader, TagSectionReader, TagType, Type, TypeRef, + TypeSectionReader, }; fn memory(ty: MemoryType) -> Memory { @@ -42,21 +42,6 @@ fn tag(e: TagType) -> Tag { } } -fn table(ty: TableType) -> WasmResult { - Ok(Table { - wasm_ty: ty.element_type.try_into()?, - minimum: ty.initial, - maximum: ty.maximum, - }) -} - -fn global(ty: GlobalType) -> WasmResult { - Ok(Global { - wasm_ty: ty.content_type.try_into()?, - mutability: ty.mutable, - }) -} - /// Parses the Type section of the wasm module. pub fn parse_type_section<'a>( types: TypeSectionReader<'a>, @@ -68,7 +53,8 @@ pub fn parse_type_section<'a>( for entry in types { match entry? { Type::Func(wasm_func_ty) => { - environ.declare_type_func(wasm_func_ty.clone().try_into()?)?; + let ty = environ.convert_func_type(&wasm_func_ty); + environ.declare_type_func(ty)?; } } } @@ -99,11 +85,11 @@ pub fn parse_import_section<'data>( environ.declare_tag_import(tag(e), import.module, import.name)?; } TypeRef::Global(ty) => { - let ty = global(ty)?; + let ty = environ.convert_global_type(&ty); environ.declare_global_import(ty, import.module, import.name)?; } TypeRef::Table(ty) => { - let ty = table(ty)?; + let ty = environ.convert_table_type(&ty); environ.declare_table_import(ty, import.module, import.name)?; } } @@ -142,7 +128,7 @@ pub fn parse_table_section( environ.reserve_tables(tables.count())?; for entry in tables { - let ty = table(entry?.ty)?; + let ty = environ.convert_table_type(&entry?.ty); environ.declare_table(ty)?; } @@ -211,7 +197,7 @@ pub fn parse_global_section( )); } }; - let ty = global(ty)?; + let ty = environ.convert_global_type(&ty); environ.declare_global(ty, initializer)?; } diff --git a/cranelift/wasm/src/translation_utils.rs b/cranelift/wasm/src/translation_utils.rs index 36176189e7f4..7d2208881cfa 100644 --- a/cranelift/wasm/src/translation_utils.rs +++ b/cranelift/wasm/src/translation_utils.rs @@ -1,7 +1,6 @@ //! Helper functions and structures for the translation. use crate::environ::TargetEnvironment; use crate::WasmResult; -use core::convert::TryInto; use core::u32; use cranelift_codegen::ir; use cranelift_frontend::FunctionBuilder; @@ -23,27 +22,18 @@ where return Ok(match ty { wasmparser::BlockType::Empty => { let params: &'static [wasmparser::ValType] = &[]; - let results: &'static [wasmparser::ValType] = &[]; + let results: std::vec::Vec = vec![]; ( itertools::Either::Left(params.iter().copied()), - itertools::Either::Left(results.iter().copied()), + itertools::Either::Left(results.into_iter()), ) } wasmparser::BlockType::Type(ty) => { let params: &'static [wasmparser::ValType] = &[]; - let results: &'static [wasmparser::ValType] = match ty { - wasmparser::ValType::I32 => &[wasmparser::ValType::I32], - wasmparser::ValType::I64 => &[wasmparser::ValType::I64], - wasmparser::ValType::F32 => &[wasmparser::ValType::F32], - wasmparser::ValType::F64 => &[wasmparser::ValType::F64], - wasmparser::ValType::V128 => &[wasmparser::ValType::V128], - wasmparser::ValType::EXTERNREF => &[wasmparser::ValType::EXTERNREF], - wasmparser::ValType::FUNCREF => &[wasmparser::ValType::FUNCREF], - wasmparser::ValType::Ref(_) => unimplemented!("function references proposal"), - }; + let results: std::vec::Vec = vec![ty.clone()]; ( itertools::Either::Left(params.iter().copied()), - itertools::Either::Left(results.iter().copied()), + itertools::Either::Left(results.into_iter()), ) } wasmparser::BlockType::FuncType(ty_index) => { @@ -80,8 +70,9 @@ pub fn block_with_params( wasmparser::ValType::F64 => { builder.append_block_param(block, ir::types::F64); } - wasmparser::ValType::Ref(ty) => { - builder.append_block_param(block, environ.reference_type(ty.try_into()?)); + wasmparser::ValType::Ref(rt) => { + let hty = environ.convert_heap_type(rt.heap_type()); + builder.append_block_param(block, environ.reference_type(hty)); } wasmparser::ValType::V128 => { builder.append_block_param(block, ir::types::I8X16); diff --git a/crates/cli-flags/src/lib.rs b/crates/cli-flags/src/lib.rs index 2306e8c31461..ba568f0d5f7d 100644 --- a/crates/cli-flags/src/lib.rs +++ b/crates/cli-flags/src/lib.rs @@ -43,6 +43,10 @@ pub const SUPPORTED_WASM_FEATURES: &[(&str, &str)] = &[ ("memory64", "enables support for 64-bit memories"), #[cfg(feature = "component-model")] ("component-model", "enables support for the component model"), + ( + "function-references", + "enables support for typed function references", + ), ]; pub const SUPPORTED_WASI_MODULES: &[(&str, &str)] = &[ @@ -366,6 +370,7 @@ impl CommonOptions { memory64, #[cfg(feature = "component-model")] component_model, + function_references, } = self.wasm_features.unwrap_or_default(); if let Some(enable) = simd { @@ -380,6 +385,9 @@ impl CommonOptions { if let Some(enable) = reference_types { config.wasm_reference_types(enable); } + if let Some(enable) = function_references { + config.wasm_function_references(enable); + } if let Some(enable) = multi_value { config.wasm_multi_value(enable); } @@ -432,6 +440,7 @@ pub struct WasmFeatures { pub memory64: Option, #[cfg(feature = "component-model")] pub component_model: Option, + pub function_references: Option, } fn parse_wasm_features(features: &str) -> Result { @@ -483,6 +492,7 @@ fn parse_wasm_features(features: &str) -> Result { memory64: all.or(values["memory64"]), #[cfg(feature = "component-model")] component_model: all.or(values["component-model"]), + function_references: all.or(values["function-references"]), }) } @@ -598,6 +608,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(true)); @@ -607,6 +618,7 @@ mod test { assert_eq!(threads, Some(true)); assert_eq!(multi_memory, Some(true)); assert_eq!(memory64, Some(true)); + assert_eq!(function_references, Some(true)); assert_eq!(relaxed_simd, Some(true)); Ok(()) @@ -625,6 +637,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(false)); @@ -634,6 +647,7 @@ mod test { assert_eq!(threads, Some(false)); assert_eq!(multi_memory, Some(false)); assert_eq!(memory64, Some(false)); + assert_eq!(function_references, Some(false)); assert_eq!(relaxed_simd, Some(false)); Ok(()) @@ -655,6 +669,7 @@ mod test { threads, multi_memory, memory64, + function_references, } = options.wasm_features.unwrap(); assert_eq!(reference_types, Some(false)); @@ -664,6 +679,7 @@ mod test { assert_eq!(threads, None); assert_eq!(multi_memory, Some(true)); assert_eq!(memory64, Some(true)); + assert_eq!(function_references, None); assert_eq!(relaxed_simd, None); Ok(()) diff --git a/crates/cranelift-shared/src/lib.rs b/crates/cranelift-shared/src/lib.rs index be29c5bec538..42a26352ca07 100644 --- a/crates/cranelift-shared/src/lib.rs +++ b/crates/cranelift-shared/src/lib.rs @@ -77,6 +77,7 @@ pub fn mach_trap_to_trap(trap: &MachTrap) -> Option { ir::TrapCode::UnreachableCodeReached => Trap::UnreachableCodeReached, ir::TrapCode::Interrupt => Trap::Interrupt, ir::TrapCode::User(ALWAYS_TRAP_CODE) => Trap::AlwaysTrapAdapter, + ir::TrapCode::NullReference => Trap::NullReference, // These do not get converted to wasmtime traps, since they // shouldn't ever be hit in theory. Instead of catching and handling diff --git a/crates/cranelift/src/debug/transform/simulate.rs b/crates/cranelift/src/debug/transform/simulate.rs index 7c64772fdadd..df06268d86cc 100644 --- a/crates/cranelift/src/debug/transform/simulate.rs +++ b/crates/cranelift/src/debug/transform/simulate.rs @@ -11,9 +11,8 @@ use gimli::{self, LineEncoding}; use std::collections::{HashMap, HashSet}; use std::path::PathBuf; use std::sync::atomic::{AtomicUsize, Ordering::SeqCst}; -use wasmparser::ValType as WasmType; use wasmtime_environ::{ - DebugInfoData, DefinedFuncIndex, EntityRef, FuncIndex, FunctionMetadata, WasmFileInfo, + DebugInfoData, DefinedFuncIndex, EntityRef, FuncIndex, FunctionMetadata, WasmFileInfo, WasmType, }; const PRODUCER_NAME: &str = "wasmtime"; diff --git a/crates/cranelift/src/func_environ.rs b/crates/cranelift/src/func_environ.rs index 8d4b9cbc0d90..ed6c4915a13a 100644 --- a/crates/cranelift/src/func_environ.rs +++ b/crates/cranelift/src/func_environ.rs @@ -10,14 +10,15 @@ use cranelift_frontend::FunctionBuilder; use cranelift_frontend::Variable; use cranelift_wasm::{ self, FuncIndex, FuncTranslationState, GlobalIndex, GlobalVariable, Heap, HeapData, HeapStyle, - MemoryIndex, TableIndex, TargetEnvironment, TypeIndex, WasmError, WasmResult, WasmType, + MemoryIndex, TableIndex, TargetEnvironment, TypeIndex, WasmHeapType, WasmRefType, WasmResult, + WasmType, }; use std::convert::TryFrom; use std::mem; use wasmparser::Operator; use wasmtime_environ::{ BuiltinFunctionIndex, MemoryPlan, MemoryStyle, Module, ModuleTranslation, ModuleTypes, PtrSize, - TableStyle, Tunables, VMOffsets, WASM_PAGE_SIZE, + TableStyle, Tunables, TypeConvert, VMOffsets, WASM_PAGE_SIZE, }; use wasmtime_environ::{FUNCREF_INIT_BIT, FUNCREF_MASK}; @@ -291,6 +292,52 @@ impl<'module_environment> FuncEnvironment<'module_environment> { (base, func_addr) } + /// This calls a function by reference without checking the signature. It + /// gets the function address, sets relevant flags, and passes the special + /// callee/caller vmctxs. It is used by both call_indirect (which checks the + /// signature) and call_ref (which doesn't). + fn call_function_unchecked( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult { + let pointer_type = self.pointer_type(); + + // Dereference callee pointer to get the function address. + let mem_flags = ir::MemFlags::trusted().with_readonly(); + let func_addr = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vm_func_ref_wasm_call()), + ); + + let mut real_call_args = Vec::with_capacity(call_args.len() + 2); + let caller_vmctx = builder + .func + .special_param(ArgumentPurpose::VMContext) + .unwrap(); + + // First append the callee vmctx address. + let vmctx = builder.ins().load( + pointer_type, + mem_flags, + callee, + i32::from(self.offsets.ptr.vm_func_ref_vmctx()), + ); + real_call_args.push(vmctx); + real_call_args.push(caller_vmctx); + + // Then append the regular call arguments. + real_call_args.extend_from_slice(call_args); + + Ok(builder + .ins() + .call_indirect(sig_ref, func_addr, &real_call_args)) + } + /// Generate code to increment or decrement the given `externref`'s /// reference count. /// @@ -813,12 +860,18 @@ impl<'module_environment> FuncEnvironment<'module_environment> { } } +impl TypeConvert for FuncEnvironment<'_> { + fn lookup_heap_type(&self, ty: TypeIndex) -> WasmHeapType { + self.module.lookup_heap_type(ty) + } +} + impl<'module_environment> TargetEnvironment for FuncEnvironment<'module_environment> { fn target_config(&self) -> TargetFrontendConfig { self.isa.frontend_config() } - fn reference_type(&self, ty: WasmType) -> ir::Type { + fn reference_type(&self, ty: WasmHeapType) -> ir::Type { crate::reference_type(ty, self.pointer_type()) } @@ -891,7 +944,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m }); let element_size = u64::from( - self.reference_type(self.module.table_plans[index].table.wasm_ty) + self.reference_type(self.module.table_plans[index].table.wasm_ty.heap_type) .bytes(), ); @@ -913,21 +966,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m init_value: ir::Value, ) -> WasmResult { let (func_idx, func_sig) = - match self.module.table_plans[table_index].table.wasm_ty { - WasmType::FuncRef => ( + match self.module.table_plans[table_index].table.wasm_ty.heap_type { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => ( BuiltinFunctionIndex::table_grow_func_ref(), self.builtin_function_signatures .table_grow_func_ref(&mut pos.func), ), - WasmType::ExternRef => ( + WasmHeapType::Extern => ( BuiltinFunctionIndex::table_grow_externref(), self.builtin_function_signatures .table_grow_externref(&mut pos.func), ), - _ => return Err(WasmError::Unsupported( - "`table.grow` with a table element type that is not `funcref` or `externref`" - .into(), - )), }; let (vmctx, func_addr) = self.translate_load_builtin_function_address(&mut pos, func_idx); @@ -952,13 +1001,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m let pointer_type = self.pointer_type(); let plan = &self.module.table_plans[table_index]; - match plan.table.wasm_ty { - WasmType::FuncRef => match plan.style { + match plan.table.wasm_ty.heap_type { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => match plan.style { TableStyle::CallerChecksSignature => { Ok(self.get_or_init_func_ref_table_elem(builder, table_index, table, index)) } }, - WasmType::ExternRef => { + WasmHeapType::Extern => { // Our read barrier for `externref` tables is roughly equivalent // to the following pseudocode: // @@ -979,7 +1028,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m // onto the stack are safely held alive by the // `VMExternRefActivationsTable`. - let reference_type = self.reference_type(WasmType::ExternRef); + let reference_type = self.reference_type(WasmHeapType::Extern); builder.ensure_inserted_block(); let continue_block = builder.create_block(); @@ -1074,10 +1123,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(elem) } - ty => Err(WasmError::Unsupported(format!( - "unsupported table type for `table.get` instruction: {:?}", - ty - ))), } } @@ -1090,10 +1135,9 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m index: ir::Value, ) -> WasmResult<()> { let pointer_type = self.pointer_type(); - let plan = &self.module.table_plans[table_index]; - match plan.table.wasm_ty { - WasmType::FuncRef => match plan.style { + match plan.table.wasm_ty.heap_type { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => match plan.style { TableStyle::CallerChecksSignature => { let table_entry_addr = builder.ins().table_addr(pointer_type, table, index, 0); // Set the "initialized bit". See doc-comment on @@ -1109,7 +1153,8 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(()) } }, - WasmType::ExternRef => { + + WasmHeapType::Extern => { // Our write barrier for `externref`s being copied out of the // stack and into a table is roughly equivalent to the following // pseudocode: @@ -1239,10 +1284,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(()) } - ty => Err(WasmError::Unsupported(format!( - "unsupported table type for `table.set` instruction: {:?}", - ty - ))), } } @@ -1255,21 +1296,17 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m len: ir::Value, ) -> WasmResult<()> { let (builtin_idx, builtin_sig) = - match self.module.table_plans[table_index].table.wasm_ty { - WasmType::FuncRef => ( + match self.module.table_plans[table_index].table.wasm_ty.heap_type { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => ( BuiltinFunctionIndex::table_fill_func_ref(), self.builtin_function_signatures .table_fill_func_ref(&mut pos.func), ), - WasmType::ExternRef => ( + WasmHeapType::Extern => ( BuiltinFunctionIndex::table_fill_externref(), self.builtin_function_signatures .table_fill_externref(&mut pos.func), ), - _ => return Err(WasmError::Unsupported( - "`table.fill` with a table element type that is not `funcref` or `externref`" - .into(), - )), }; let (vmctx, builtin_addr) = @@ -1288,16 +1325,13 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m fn translate_ref_null( &mut self, mut pos: cranelift_codegen::cursor::FuncCursor, - ty: WasmType, + ht: WasmHeapType, ) -> WasmResult { - Ok(match ty { - WasmType::FuncRef => pos.ins().iconst(self.pointer_type(), 0), - WasmType::ExternRef => pos.ins().null(self.reference_type(ty)), - _ => { - return Err(WasmError::Unsupported( - "`ref.null T` that is not a `funcref` or an `externref`".into(), - )); + Ok(match ht { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => { + pos.ins().iconst(self.pointer_type(), 0) } + WasmHeapType::Extern => pos.ins().null(self.reference_type(ht)), }) } @@ -1344,7 +1378,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::ExternRef, + WasmType::Ref(WasmRefType::EXTERNREF), "We only use GlobalVariable::Custom for externref" ); @@ -1372,7 +1406,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m ) -> WasmResult<()> { debug_assert_eq!( self.module.globals[index].wasm_ty, - WasmType::ExternRef, + WasmType::Ref(WasmRefType::EXTERNREF), "We only use GlobalVariable::Custom for externref" ); @@ -1510,21 +1544,37 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m func: &mut ir::Function, index: GlobalIndex, ) -> WasmResult { - // Although `ExternRef`s live at the same memory location as any other - // type of global at the same index would, getting or setting them - // requires ref counting barriers. Therefore, we need to use - // `GlobalVariable::Custom`, as that is the only kind of - // `GlobalVariable` for which `cranelift-wasm` supports custom access - // translation. - if self.module.globals[index].wasm_ty == WasmType::ExternRef { - return Ok(GlobalVariable::Custom); + let ty = self.module.globals[index].wasm_ty; + match ty { + // Although `ExternRef`s live at the same memory location as any + // other type of global at the same index would, getting or setting + // them requires ref counting barriers. Therefore, we need to use + // `GlobalVariable::Custom`, as that is the only kind of + // `GlobalVariable` for which `cranelift-wasm` supports custom + // access translation. + WasmType::Ref(WasmRefType { + heap_type: WasmHeapType::Extern, + .. + }) => return Ok(GlobalVariable::Custom), + + // Funcrefs are represented as pointers which survive for the + // entire lifetime of the `Store` so there's no need for barriers. + // This means that they can fall through to memory as well. + WasmType::Ref(WasmRefType { + heap_type: WasmHeapType::Func | WasmHeapType::TypedFunc(_), + .. + }) => {} + + // Value types all live in memory so let them fall through to a + // memory-based global. + WasmType::I32 | WasmType::I64 | WasmType::F32 | WasmType::F64 | WasmType::V128 => {} } let (gv, offset) = self.get_global_location(func, index); Ok(GlobalVariable::Memory { gv, offset: offset.into(), - ty: super::value_type(self.isa, self.module.globals[index].wasm_ty), + ty: super::value_type(self.isa, ty), }) } @@ -1593,15 +1643,6 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m .ins() .trapz(funcref_ptr, ir::TrapCode::IndirectCallToNull); - // Dereference the funcref pointer to get the function address. - let mem_flags = ir::MemFlags::trusted().with_readonly(); - let func_addr = builder.ins().load( - pointer_type, - mem_flags, - funcref_ptr, - i32::from(self.offsets.ptr.vm_func_ref_wasm_call()), - ); - // If necessary, check the signature. match self.module.table_plans[table_index].style { TableStyle::CallerChecksSignature => { @@ -1645,28 +1686,7 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m } } - let mut real_call_args = Vec::with_capacity(call_args.len() + 2); - let caller_vmctx = builder - .func - .special_param(ArgumentPurpose::VMContext) - .unwrap(); - - // First append the callee vmctx address. - let vmctx = builder.ins().load( - pointer_type, - mem_flags, - funcref_ptr, - i32::from(self.offsets.ptr.vm_func_ref_vmctx()), - ); - real_call_args.push(vmctx); - real_call_args.push(caller_vmctx); - - // Then append the regular call arguments. - real_call_args.extend_from_slice(call_args); - - Ok(builder - .ins() - .call_indirect(sig_ref, func_addr, &real_call_args)) + self.call_function_unchecked(builder, sig_ref, funcref_ptr, call_args) } fn translate_call( @@ -1721,6 +1741,26 @@ impl<'module_environment> cranelift_wasm::FuncEnvironment for FuncEnvironment<'m Ok(pos.ins().call_indirect(sig_ref, func_addr, &real_call_args)) } + fn translate_call_ref( + &mut self, + builder: &mut FunctionBuilder, + sig_ref: ir::SigRef, + callee: ir::Value, + call_args: &[ir::Value], + ) -> WasmResult { + // Check for whether the callee is null, and trap if so. + // + // FIXME: the wasm type system tracks enough information to know whether + // `callee` is a null reference or not. In some situations it can be + // statically known here that `callee` cannot be null in which case this + // null check can be elided. This requires feeding type information from + // wasmparser's validator into this function, however, which is not + // easily done at this time. + builder.ins().trapz(callee, ir::TrapCode::NullReference); + + self.call_function_unchecked(builder, sig_ref, callee, call_args) + } + fn translate_memory_grow( &mut self, mut pos: FuncCursor<'_>, diff --git a/crates/cranelift/src/lib.rs b/crates/cranelift/src/lib.rs index 487cb5446757..ec3642b3d75f 100644 --- a/crates/cranelift/src/lib.rs +++ b/crates/cranelift/src/lib.rs @@ -69,7 +69,7 @@ fn value_type(isa: &dyn TargetIsa, ty: WasmType) -> ir::types::Type { WasmType::F32 => ir::types::F32, WasmType::F64 => ir::types::F64, WasmType::V128 => ir::types::I8X16, - WasmType::FuncRef | WasmType::ExternRef => reference_type(ty, isa.pointer_type()), + WasmType::Ref(rt) => reference_type(rt.heap_type, isa.pointer_type()), } } @@ -164,14 +164,15 @@ fn wasm_call_signature(isa: &dyn TargetIsa, wasm_func_ty: &WasmFuncType) -> ir:: } /// Returns the reference type to use for the provided wasm type. -fn reference_type(wasm_ty: cranelift_wasm::WasmType, pointer_type: ir::Type) -> ir::Type { - match wasm_ty { - cranelift_wasm::WasmType::FuncRef => pointer_type, - cranelift_wasm::WasmType::ExternRef => match pointer_type { +fn reference_type(wasm_ht: cranelift_wasm::WasmHeapType, pointer_type: ir::Type) -> ir::Type { + match wasm_ht { + cranelift_wasm::WasmHeapType::Func | cranelift_wasm::WasmHeapType::TypedFunc(_) => { + pointer_type + } + cranelift_wasm::WasmHeapType::Extern => match pointer_type { ir::types::I32 => ir::types::R32, ir::types::I64 => ir::types::R64, _ => panic!("unsupported pointer type"), }, - _ => panic!("unsupported Wasm reference type"), } } diff --git a/crates/environ/src/component/translate.rs b/crates/environ/src/component/translate.rs index 5b91622703a1..5abd9e77755f 100644 --- a/crates/environ/src/component/translate.rs +++ b/crates/environ/src/component/translate.rs @@ -2,6 +2,7 @@ use crate::component::*; use crate::ScopeVec; use crate::{ EntityIndex, ModuleEnvironment, ModuleTranslation, PrimaryMap, SignatureIndex, Tunables, + TypeConvert, }; use anyhow::{bail, Result}; use indexmap::IndexMap; @@ -418,10 +419,8 @@ impl<'a, 'data> Translator<'a, 'data> { Some(ty) => ty, None => break, }; - let ty = self - .types - .module_types_builder() - .wasm_func_type(lowered_function_type.clone().try_into()?); + let ty = self.types.convert_func_type(lowered_function_type); + let ty = self.types.module_types_builder().wasm_func_type(ty); self.result.funcs.push(ty); } diff --git a/crates/environ/src/component/types.rs b/crates/environ/src/component/types.rs index 7668cdde3e4d..d3424256ee53 100644 --- a/crates/environ/src/component/types.rs +++ b/crates/environ/src/component/types.rs @@ -1,5 +1,8 @@ use crate::component::{MAX_FLAT_PARAMS, MAX_FLAT_RESULTS}; -use crate::{EntityType, Global, ModuleTypes, ModuleTypesBuilder, PrimaryMap, SignatureIndex}; +use crate::{ + EntityType, ModuleTypes, ModuleTypesBuilder, PrimaryMap, SignatureIndex, TypeConvert, + WasmHeapType, +}; use anyhow::{bail, Result}; use cranelift_entity::EntityRef; use indexmap::IndexMap; @@ -454,7 +457,8 @@ impl ComponentTypesBuilder { pub fn intern_core_type(&mut self, ty: &wasmparser::CoreType<'_>) -> Result { Ok(match ty { wasmparser::CoreType::Func(ty) => { - TypeDef::CoreFunc(self.module_types.wasm_func_type(ty.clone().try_into()?)) + let ty = self.convert_func_type(ty); + TypeDef::CoreFunc(self.module_types.wasm_func_type(ty)) } wasmparser::CoreType::Module(ty) => TypeDef::Module(self.module_type(ty)?), }) @@ -491,8 +495,8 @@ impl ComponentTypesBuilder { for item in ty { match item { wasmparser::ModuleTypeDeclaration::Type(wasmparser::Type::Func(f)) => { - let ty = - TypeDef::CoreFunc(self.module_types.wasm_func_type(f.clone().try_into()?)); + let f = self.convert_func_type(f); + let ty = TypeDef::CoreFunc(self.module_types.wasm_func_type(f)); self.push_core_typedef(ty); } wasmparser::ModuleTypeDeclaration::Export { name, ty } => { @@ -533,9 +537,9 @@ impl ComponentTypesBuilder { _ => unreachable!(), // not possible with valid components } } - wasmparser::TypeRef::Table(ty) => EntityType::Table(ty.clone().try_into()?), + wasmparser::TypeRef::Table(ty) => EntityType::Table(self.convert_table_type(ty)), wasmparser::TypeRef::Memory(ty) => EntityType::Memory(ty.clone().into()), - wasmparser::TypeRef::Global(ty) => EntityType::Global(Global::new(ty.clone())?), + wasmparser::TypeRef::Global(ty) => EntityType::Global(self.convert_global_type(ty)), wasmparser::TypeRef::Tag(_) => bail!("exceptions proposal not implemented"), }) } @@ -941,6 +945,15 @@ impl ComponentTypesBuilder { } } +impl TypeConvert for ComponentTypesBuilder { + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { + match self.type_scopes.last().unwrap().core[index] { + TypeDef::CoreFunc(i) => WasmHeapType::TypedFunc(i), + _ => unreachable!(), + } + } +} + // Forward the indexing impl to the internal `TypeTables` impl Index for ComponentTypesBuilder where diff --git a/crates/environ/src/module.rs b/crates/environ/src/module.rs index 6993b94ef7cf..565329df098b 100644 --- a/crates/environ/src/module.rs +++ b/crates/environ/src/module.rs @@ -1,6 +1,6 @@ //! Data structures for representing decoded wasm modules. -use crate::{ModuleTranslation, PrimaryMap, Tunables, WASM_PAGE_SIZE}; +use crate::{ModuleTranslation, PrimaryMap, Tunables, WasmHeapType, WASM_PAGE_SIZE}; use cranelift_entity::{packed_option::ReservedValue, EntityRef}; use indexmap::IndexMap; use serde::{Deserialize, Serialize}; @@ -401,45 +401,60 @@ impl ModuleTranslation<'_> { // OOMs or DoS on truly sparse tables. const MAX_FUNC_TABLE_SIZE: u32 = 1024 * 1024; - let segments = match &self.module.table_initialization { - TableInitialization::Segments { segments } => segments, - TableInitialization::FuncTable { .. } => { - // Already done! - return; - } - }; - - // Build the table arrays per-table. - let mut tables = PrimaryMap::with_capacity(self.module.table_plans.len()); - // Keep the "leftovers" for eager init. - let mut leftovers = vec![]; - - for segment in segments { - // Skip imported tables: we can't provide a preconstructed - // table for them, because their values depend on the - // imported table overlaid with whatever segments we have. - if self - .module - .defined_table_index(segment.table_index) - .is_none() - { - leftovers.push(segment.clone()); + // First convert any element-initialized tables to images of just that + // single function if the minimum size of the table allows doing so. + for ((_, init), (_, plan)) in self + .module + .table_initialization + .initial_values + .iter_mut() + .zip( + self.module + .table_plans + .iter() + .skip(self.module.num_imported_tables), + ) + { + let table_size = plan.table.minimum; + if table_size > MAX_FUNC_TABLE_SIZE { continue; } - - // If this is not a funcref table, then we can't support a - // pre-computed table of function indices. - if self.module.table_plans[segment.table_index].table.wasm_ty != WasmType::FuncRef { - leftovers.push(segment.clone()); - continue; + if let TableInitialValue::FuncRef(val) = *init { + *init = TableInitialValue::Null { + precomputed: vec![val; table_size as usize], + }; } + } + + let mut segments = mem::take(&mut self.module.table_initialization.segments) + .into_iter() + .peekable(); + + // The goal of this loop is to interpret a table segment and apply it + // "statically" to a local table. This will iterate over segments and + // apply them one-by-one to each table. + // + // If any segment can't be applied, however, then this loop exits and + // all remaining segments are placed back into the segment list. This is + // because segments are supposed to be initialized one-at-a-time which + // means that intermediate state is visible with respect to traps. If + // anything isn't statically known to not trap it's pessimistically + // assumed to trap meaning all further segment initializers must be + // applied manually at instantiation time. + while let Some(segment) = segments.peek() { + let defined_index = match self.module.defined_table_index(segment.table_index) { + Some(index) => index, + // Skip imported tables: we can't provide a preconstructed + // table for them, because their values depend on the + // imported table overlaid with whatever segments we have. + None => break, + }; // If the base of this segment is dynamic, then we can't // include it in the statically-built array of initial // contents. if segment.base.is_some() { - leftovers.push(segment.clone()); - continue; + break; } // Get the end of this segment. If out-of-bounds, or too @@ -447,34 +462,55 @@ impl ModuleTranslation<'_> { // segment. let top = match segment.offset.checked_add(segment.elements.len() as u32) { Some(top) => top, - None => { - leftovers.push(segment.clone()); - continue; - } + None => break, }; let table_size = self.module.table_plans[segment.table_index].table.minimum; if top > table_size || top > MAX_FUNC_TABLE_SIZE { - leftovers.push(segment.clone()); - continue; + break; } - // We can now incorporate this segment into the initializers array. - while tables.len() <= segment.table_index.index() { - tables.push(vec![]); - } - let elements = &mut tables[segment.table_index]; - if elements.is_empty() { - elements.resize(table_size as usize, FuncIndex::reserved_value()); + match self.module.table_plans[segment.table_index] + .table + .wasm_ty + .heap_type + { + WasmHeapType::Func | WasmHeapType::TypedFunc(_) => {} + // If this is not a funcref table, then we can't support a + // pre-computed table of function indices. Technically this + // initializer won't trap so we could continue processing + // segments, but that's left as a future optimization if + // necessary. + WasmHeapType::Extern => break, } - let dst = &mut elements[(segment.offset as usize)..(top as usize)]; + let precomputed = + match &mut self.module.table_initialization.initial_values[defined_index] { + TableInitialValue::Null { precomputed } => precomputed, + + // If this table is still listed as an initial value here + // then that means the initial size of the table doesn't + // support a precomputed function list, so skip this. + // Technically this won't trap so it's possible to process + // further initializers, but that's left as a future + // optimization. + TableInitialValue::FuncRef(_) => break, + }; + + // At this point we're committing to pre-initializing the table + // with the `segment` that's being iterated over. This segment is + // applied to the `precomputed` list for the table by ensuring + // it's large enough to hold the segment and then copying the + // segment into the precomputed list. + if precomputed.len() < top as usize { + precomputed.resize(top as usize, FuncIndex::reserved_value()); + } + let dst = &mut precomputed[(segment.offset as usize)..(top as usize)]; dst.copy_from_slice(&segment.elements[..]); - } - self.module.table_initialization = TableInitialization::FuncTable { - tables, - segments: leftovers, - }; + // advance the iterator to see the next segment + let _ = segments.next(); + } + self.module.table_initialization.segments = segments.collect(); } } @@ -681,9 +717,52 @@ impl TablePlan { } } +/// Table initialization data for all tables in the module. +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct TableInitialization { + /// Initial values for tables defined within the module itself. + /// + /// This contains the initial values and initializers for tables defined + /// within a wasm, so excluding imported tables. This initializer can + /// represent null-initialized tables, element-initialized tables (e.g. with + /// the function-references proposal), or precomputed images of table + /// initialization. For example table initializers to a table that are all + /// in-bounds will get removed from `segment` and moved into + /// `initial_values` here. + pub initial_values: PrimaryMap, + + /// Element segments present in the initial wasm module which are executed + /// at instantiation time. + /// + /// These element segments are iterated over during instantiation to apply + /// any segments that weren't already moved into `initial_values` above. + pub segments: Vec, +} + +/// Initial value for all elements in a table. +#[derive(Clone, Debug, Serialize, Deserialize)] +pub enum TableInitialValue { + /// Initialize each table element to null, optionally setting some elements + /// to non-null given the precomputed image. + Null { + /// A precomputed image of table initializers for this table. + /// + /// This image is constructed during `try_func_table_init` and + /// null-initialized elements are represented with + /// `FuncIndex::reserved_value()`. Note that this image is empty by + /// default and may not encompass the entire span of the table in which + /// case the elements are initialized to null. + precomputed: Vec, + }, + + /// Initialize each table element to the function reference given + /// by the `FuncIndex`. + FuncRef(FuncIndex), +} + /// A WebAssembly table initializer segment. #[derive(Clone, Debug, Serialize, Deserialize)] -pub struct TableInitializer { +pub struct TableSegment { /// The index of a table to initialize. pub table_index: TableIndex, /// Optionally, a global variable giving a base index. @@ -694,56 +773,6 @@ pub struct TableInitializer { pub elements: Box<[FuncIndex]>, } -/// Table initialization data for all tables in the module. -#[derive(Debug, Serialize, Deserialize)] -pub enum TableInitialization { - /// "Segment" mode: table initializer segments, possibly with - /// dynamic bases, possibly applying to an imported memory. - /// - /// Every kind of table initialization is supported by the - /// Segments mode. - Segments { - /// The segment initializers. All apply to the table for which - /// this TableInitialization is specified. - segments: Vec, - }, - - /// "FuncTable" mode: a single array per table, with a function - /// index or null per slot. This is only possible to provide for a - /// given table when it is defined by the module itself, and can - /// only include data from initializer segments that have - /// statically-knowable bases (i.e., not dependent on global - /// values). - /// - /// Any segments that are not compatible with this mode are held - /// in the `segments` array of "leftover segments", which are - /// still processed eagerly. - /// - /// This mode facilitates lazy initialization of the tables. It is - /// thus "nice to have", but not necessary for correctness. - FuncTable { - /// For each table, an array of function indices (or - /// FuncIndex::reserved_value(), meaning no initialized value, - /// hence null by default). Array elements correspond - /// one-to-one to table elements; i.e., `elements[i]` is the - /// initial value for `table[i]`. - tables: PrimaryMap>, - - /// Leftover segments that need to be processed eagerly on - /// instantiation. These either apply to an imported table (so - /// we can't pre-build a full image of the table from this - /// overlay) or have dynamically (at instantiation time) - /// determined bases. - segments: Vec, - }, -} - -impl Default for TableInitialization { - fn default() -> Self { - TableInitialization::Segments { segments: vec![] } - } -} - /// Different types that can appear in a module. /// /// Note that each of these variants are intended to index further into a @@ -1020,6 +1049,14 @@ impl Module { } } +impl TypeConvert for Module { + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { + match self.types[index] { + ModuleType::Function(i) => WasmHeapType::TypedFunc(i), + } + } +} + /// Type information about functions in a wasm module. #[derive(Debug, Serialize, Deserialize)] pub struct FunctionType { diff --git a/crates/environ/src/module_environ.rs b/crates/environ/src/module_environ.rs index 568bf632d0ff..06f8f158fd2d 100644 --- a/crates/environ/src/module_environ.rs +++ b/crates/environ/src/module_environ.rs @@ -1,16 +1,17 @@ use crate::module::{ FuncRefIndex, Initializer, MemoryInitialization, MemoryInitializer, MemoryPlan, Module, - ModuleType, TableInitializer, TablePlan, + ModuleType, TablePlan, TableSegment, }; use crate::{ - DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, Global, - GlobalIndex, GlobalInit, MemoryIndex, ModuleTypesBuilder, PrimaryMap, SignatureIndex, - TableIndex, TableInitialization, Tunables, TypeIndex, WasmError, WasmFuncType, WasmResult, + DataIndex, DefinedFuncIndex, ElemIndex, EntityIndex, EntityType, FuncIndex, GlobalIndex, + GlobalInit, MemoryIndex, ModuleTypesBuilder, PrimaryMap, SignatureIndex, TableIndex, + TableInitialValue, Tunables, TypeConvert, TypeIndex, WasmError, WasmFuncType, WasmHeapType, + WasmResult, WasmType, }; use cranelift_entity::packed_option::ReservedValue; use std::borrow::Cow; use std::collections::HashMap; -use std::convert::{TryFrom, TryInto}; +use std::convert::TryFrom; use std::path::PathBuf; use std::sync::Arc; use wasmparser::{ @@ -150,8 +151,8 @@ pub struct WasmFileInfo { #[derive(Debug)] #[allow(missing_docs)] pub struct FunctionMetadata { - pub params: Box<[wasmparser::ValType]>, - pub locals: Box<[(u32, wasmparser::ValType)]>, + pub params: Box<[WasmType]>, + pub locals: Box<[(u32, WasmType)]>, } impl<'a, 'data> ModuleEnvironment<'a, 'data> { @@ -239,7 +240,8 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { for ty in types { match ty? { Type::Func(wasm_func_ty) => { - self.declare_type_func(wasm_func_ty.try_into()?)?; + let ty = self.convert_func_type(&wasm_func_ty); + self.declare_type_func(ty)?; } } } @@ -267,11 +269,11 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { } TypeRef::Global(ty) => { self.result.module.num_imported_globals += 1; - EntityType::Global(Global::new(ty)?) + EntityType::Global(self.convert_global_type(&ty)) } TypeRef::Table(ty) => { self.result.module.num_imported_tables += 1; - EntityType::Table(ty.try_into()?) + EntityType::Table(self.convert_table_type(&ty)) } // doesn't get past validation @@ -301,9 +303,39 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { self.result.module.table_plans.reserve_exact(cnt); for entry in tables { - let table = entry?.ty.try_into()?; + let wasmparser::Table { ty, init } = entry?; + let table = self.convert_table_type(&ty); let plan = TablePlan::for_table(table, &self.tunables); self.result.module.table_plans.push(plan); + let init = match init { + wasmparser::TableInit::RefNull => TableInitialValue::Null { + precomputed: Vec::new(), + }, + wasmparser::TableInit::Expr(cexpr) => { + let mut init_expr_reader = cexpr.get_binary_reader(); + match init_expr_reader.read_operator()? { + Operator::RefNull { hty: _ } => TableInitialValue::Null { + precomputed: Vec::new(), + }, + Operator::RefFunc { function_index } => { + let index = FuncIndex::from_u32(function_index); + self.flag_func_escaped(index); + TableInitialValue::FuncRef(index) + } + s => { + return Err(WasmError::Unsupported(format!( + "unsupported init expr in table section: {:?}", + s + ))); + } + } + } + }; + self.result + .module + .table_initialization + .initial_values + .push(init); } } @@ -361,7 +393,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { ))); } }; - let ty = Global::new(ty)?; + let ty = self.convert_global_type(&ty); self.result.module.globals.push(ty); self.result.module.global_initializers.push(initializer); } @@ -472,17 +504,16 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { } }; - let table_segments = match &mut self.result.module.table_initialization - { - TableInitialization::Segments { segments } => segments, - TableInitialization::FuncTable { .. } => unreachable!(), - }; - table_segments.push(TableInitializer { - table_index, - base, - offset, - elements: elements.into(), - }); + self.result + .module + .table_initialization + .segments + .push(TableSegment { + table_index, + base, + offset, + elements: elements.into(), + }); } ElementKind::Passive => { @@ -518,7 +549,9 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { let sig = &self.types[sig_index]; let mut locals = Vec::new(); for pair in body.get_locals_reader()? { - locals.push(pair?); + let (cnt, ty) = pair?; + let ty = self.convert_valtype(ty); + locals.push((cnt, ty)); } self.result .debuginfo @@ -526,7 +559,7 @@ impl<'a, 'data> ModuleEnvironment<'a, 'data> { .funcs .push(FunctionMetadata { locals: locals.into_boxed_slice(), - params: sig.params().iter().cloned().map(|i| i.into()).collect(), + params: sig.params().into(), }); } body.allow_memarg64(self.validator.features().memory64); @@ -840,3 +873,9 @@ and for re-adding support for interface types you can see this issue: Ok(()) } } + +impl TypeConvert for ModuleEnvironment<'_, '_> { + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType { + self.result.module.lookup_heap_type(index) + } +} diff --git a/crates/environ/src/trap_encoding.rs b/crates/environ/src/trap_encoding.rs index 01b0c6dd9b1b..c65185abb9a5 100644 --- a/crates/environ/src/trap_encoding.rs +++ b/crates/environ/src/trap_encoding.rs @@ -87,7 +87,9 @@ pub enum Trap { /// Used to indicate that a trap was raised by atomic wait operations on non shared memory. AtomicWaitNonSharedMemory, - // + + /// Call to a null reference. + NullReference, // if adding a variant here be sure to update the `check!` macro below } @@ -110,6 +112,7 @@ impl fmt::Display for Trap { AlwaysTrapAdapter => "degenerate component adapter called", OutOfFuel => "all fuel consumed by WebAssembly", AtomicWaitNonSharedMemory => "atomic wait on non-shared memory", + NullReference => "null reference", }; write!(f, "wasm trap: {desc}") } @@ -224,6 +227,7 @@ pub fn lookup_trap_code(section: &[u8], offset: usize) -> Option { AlwaysTrapAdapter OutOfFuel AtomicWaitNonSharedMemory + NullReference } if cfg!(debug_assertions) { diff --git a/crates/runtime/src/cow.rs b/crates/runtime/src/cow.rs index 8d6ebb8c39b8..ccd34b828910 100644 --- a/crates/runtime/src/cow.rs +++ b/crates/runtime/src/cow.rs @@ -87,7 +87,7 @@ impl PartialEq for FdSource { use rustix::fd::AsRawFd; self.as_file().as_raw_fd() == other.as_file().as_raw_fd() } else { - drop(other); + let _ = other; match *self {} } } diff --git a/crates/runtime/src/instance.rs b/crates/runtime/src/instance.rs index bb63854d1246..4e24aec4dd4b 100644 --- a/crates/runtime/src/instance.rs +++ b/crates/runtime/src/instance.rs @@ -30,7 +30,7 @@ use wasmtime_environ::{ packed_option::ReservedValue, DataIndex, DefinedGlobalIndex, DefinedMemoryIndex, DefinedTableIndex, ElemIndex, EntityIndex, EntityRef, EntitySet, FuncIndex, GlobalIndex, GlobalInit, HostPtr, MemoryIndex, Module, PrimaryMap, SignatureIndex, TableIndex, - TableInitialization, Trap, VMOffsets, WasmType, VMCONTEXT_MAGIC, + TableInitialValue, Trap, VMOffsets, WasmHeapType, WasmRefType, WasmType, VMCONTEXT_MAGIC, }; mod allocator; @@ -963,37 +963,30 @@ impl Instance { break; } }; - if value.is_uninit() { - let module = self.module(); - let table_init = match &self.module().table_initialization { - // We unfortunately can't borrow `tables` outside the - // loop because we need to call `get_func_ref` (a `&mut` - // method) below; so unwrap it dynamically here. - TableInitialization::FuncTable { tables, .. } => tables, - _ => break, - } - .get(module.table_index(idx)); - - // The TableInitialization::FuncTable elements table may - // be smaller than the current size of the table: it - // always matches the initial table size, if present. We - // want to iterate up through the end of the accessed - // index range so that we set an "initialized null" even - // if there is no initializer. We do a checked `get()` on - // the initializer table below and unwrap to a null if - // we're past its end. - let func_index = - table_init.and_then(|indices| indices.get(i as usize).cloned()); - let func_ref = func_index - .and_then(|func_index| self.get_func_ref(func_index)) - .unwrap_or(std::ptr::null_mut()); - - let value = TableElement::FuncRef(func_ref); - - self.tables[idx] - .set(i, value) - .expect("Table type should match and index should be in-bounds"); + + if !value.is_uninit() { + continue; } + + // The table element `i` is uninitialized and is now being + // initialized. This must imply that a `precompiled` list of + // function indices is available for this table. The precompiled + // list is extracted and then it is consulted with `i` to + // determine the function that is going to be initialized. Note + // that `i` may be outside the limits of the static + // initialization so it's a fallible `get` instead of an index. + let module = self.module(); + let precomputed = match &module.table_initialization.initial_values[idx] { + TableInitialValue::Null { precomputed } => precomputed, + TableInitialValue::FuncRef(_) => unreachable!(), + }; + let func_index = precomputed.get(i as usize).cloned(); + let func_ref = func_index + .and_then(|func_index| self.get_func_ref(func_index)) + .unwrap_or(std::ptr::null_mut()); + self.tables[idx] + .set(i, TableElement::FuncRef(func_ref)) + .expect("Table type should match and index should be in-bounds"); } } @@ -1148,9 +1141,10 @@ impl Instance { // count as values move between globals, everything else is just // copy-able bits. match wasm_ty { - WasmType::ExternRef => { - *(*to).as_externref_mut() = from.as_externref().clone() - } + WasmType::Ref(WasmRefType { + heap_type: WasmHeapType::Extern, + .. + }) => *(*to).as_externref_mut() = from.as_externref().clone(), _ => ptr::copy_nonoverlapping(from, to, 1), } } @@ -1159,8 +1153,7 @@ impl Instance { } GlobalInit::RefNullConst => match wasm_ty { // `VMGlobalDefinition::new()` already zeroed out the bits - WasmType::FuncRef => {} - WasmType::ExternRef => {} + WasmType::Ref(WasmRefType { nullable: true, .. }) => {} ty => panic!("unsupported reference type for global: {:?}", ty), }, } @@ -1196,7 +1189,10 @@ impl Drop for Instance { }; match global.wasm_ty { // For now only externref globals need to get destroyed - WasmType::ExternRef => {} + WasmType::Ref(WasmRefType { + heap_type: WasmHeapType::Extern, + .. + }) => {} _ => continue, } unsafe { diff --git a/crates/runtime/src/instance/allocator.rs b/crates/runtime/src/instance/allocator.rs index 1f474ecadd5e..a7cbaeef7824 100644 --- a/crates/runtime/src/instance/allocator.rs +++ b/crates/runtime/src/instance/allocator.rs @@ -11,7 +11,7 @@ use std::ptr; use std::sync::Arc; use wasmtime_environ::{ DefinedMemoryIndex, DefinedTableIndex, HostPtr, InitMemory, MemoryInitialization, - MemoryInitializer, Module, PrimaryMap, TableInitialization, TableInitializer, Trap, VMOffsets, + MemoryInitializer, Module, PrimaryMap, TableInitialValue, TableSegment, Trap, VMOffsets, WasmType, WASM_PAGE_SIZE, }; @@ -200,7 +200,7 @@ pub unsafe trait InstanceAllocator { fn purge_module(&self, module: CompiledModuleId); } -fn get_table_init_start(init: &TableInitializer, instance: &mut Instance) -> Result { +fn get_table_init_start(init: &TableSegment, instance: &mut Instance) -> Result { match init.base { Some(base) => { let val = unsafe { *(*instance.defined_or_imported_global_ptr(base)).as_u32() }; @@ -214,23 +214,18 @@ fn get_table_init_start(init: &TableInitializer, instance: &mut Instance) -> Res } fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<()> { - match &module.table_initialization { - TableInitialization::FuncTable { segments, .. } - | TableInitialization::Segments { segments } => { - for segment in segments { - let table = unsafe { &*instance.get_table(segment.table_index) }; - let start = get_table_init_start(segment, instance)?; - let start = usize::try_from(start).unwrap(); - let end = start.checked_add(segment.elements.len()); - - match end { - Some(end) if end <= table.size() as usize => { - // Initializer is in bounds - } - _ => { - bail!("table out of bounds: elements segment does not fit") - } - } + for segment in module.table_initialization.segments.iter() { + let table = unsafe { &*instance.get_table(segment.table_index) }; + let start = get_table_init_start(segment, instance)?; + let start = usize::try_from(start).unwrap(); + let end = start.checked_add(segment.elements.len()); + + match end { + Some(end) if end <= table.size() as usize => { + // Initializer is in bounds + } + _ => { + bail!("table out of bounds: elements segment does not fit") } } } @@ -239,6 +234,19 @@ fn check_table_init_bounds(instance: &mut Instance, module: &Module) -> Result<( } fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> { + for (table, init) in module.table_initialization.initial_values.iter() { + match init { + // Tables are always initially null-initialized at this time + TableInitialValue::Null { precomputed: _ } => {} + + TableInitialValue::FuncRef(idx) => { + let funcref = instance.get_func_ref(*idx).unwrap(); + let table = unsafe { &mut *instance.get_defined_table(table) }; + table.init_func(funcref)?; + } + } + } + // Note: if the module's table initializer state is in // FuncTable mode, we will lazily initialize tables based on // any statically-precomputed image of FuncIndexes, but there @@ -246,20 +254,15 @@ fn initialize_tables(instance: &mut Instance, module: &Module) -> Result<()> { // incorporated. So we have a unified handler here that // iterates over all segments (Segments mode) or leftover // segments (FuncTable mode) to initialize. - match &module.table_initialization { - TableInitialization::FuncTable { segments, .. } - | TableInitialization::Segments { segments } => { - for segment in segments { - let start = get_table_init_start(segment, instance)?; - instance.table_init_segment( - segment.table_index, - &segment.elements, - start, - 0, - segment.elements.len() as u32, - )?; - } - } + for segment in module.table_initialization.segments.iter() { + let start = get_table_init_start(segment, instance)?; + instance.table_init_segment( + segment.table_index, + &segment.elements, + start, + 0, + segment.elements.len() as u32, + )?; } Ok(()) diff --git a/crates/runtime/src/table.rs b/crates/runtime/src/table.rs index ec394174e8ec..7fb5ffed7cd0 100644 --- a/crates/runtime/src/table.rs +++ b/crates/runtime/src/table.rs @@ -9,7 +9,9 @@ use sptr::Strict; use std::convert::{TryFrom, TryInto}; use std::ops::Range; use std::ptr::{self, NonNull}; -use wasmtime_environ::{TablePlan, Trap, WasmType, FUNCREF_INIT_BIT, FUNCREF_MASK}; +use wasmtime_environ::{ + TablePlan, Trap, WasmHeapType, WasmRefType, FUNCREF_INIT_BIT, FUNCREF_MASK, +}; /// An element going into or coming out of a table. /// @@ -27,7 +29,7 @@ pub enum TableElement { UninitFunc, } -#[derive(Copy, Clone, PartialEq, Eq)] +#[derive(Copy, Clone, PartialEq, Eq, Debug)] pub enum TableElementType { Func, Extern, @@ -172,11 +174,11 @@ pub enum Table { pub type TableValue = Option>; -fn wasm_to_table_type(ty: WasmType) -> Result { - match ty { - WasmType::FuncRef => Ok(TableElementType::Func), - WasmType::ExternRef => Ok(TableElementType::Extern), - ty => bail!("invalid table element type {:?}", ty), +fn wasm_to_table_type(ty: WasmRefType) -> Result { + match ty.heap_type { + WasmHeapType::Func => Ok(TableElementType::Func), + WasmHeapType::Extern => Ok(TableElementType::Extern), + WasmHeapType::TypedFunc(_) => Ok(TableElementType::Func), } } @@ -269,6 +271,17 @@ impl Table { } } + /// Initializes the contents of this table to the specified function + pub fn init_func(&mut self, init: *mut VMFuncRef) -> Result<(), Trap> { + assert!(self.element_type() == TableElementType::Func); + for slot in self.elements_mut().iter_mut() { + unsafe { + *slot = TableElement::FuncRef(init).into_table_value(); + } + } + Ok(()) + } + /// Fill `table[dst..]` with values from `items` /// /// Returns a trap error on out-of-bounds accesses. diff --git a/crates/types/src/lib.rs b/crates/types/src/lib.rs index c220ad06379c..68150ba639a3 100644 --- a/crates/types/src/lib.rs +++ b/crates/types/src/lib.rs @@ -4,9 +4,7 @@ pub use wasmparser; use cranelift_entity::entity_impl; - use serde::{Deserialize, Serialize}; -use std::convert::{TryFrom, TryInto}; use std::fmt; mod error; @@ -25,80 +23,81 @@ pub enum WasmType { F64, /// V128 type V128, - /// FuncRef type - FuncRef, - /// ExternRef type - ExternRef, + /// Reference type + Ref(WasmRefType), } -impl TryFrom for WasmType { - type Error = WasmError; - fn try_from(ty: wasmparser::ValType) -> Result { - use wasmparser::ValType::*; - match ty { - I32 => Ok(WasmType::I32), - I64 => Ok(WasmType::I64), - F32 => Ok(WasmType::F32), - F64 => Ok(WasmType::F64), - V128 => Ok(WasmType::V128), - Ref(r) => r.try_into(), +impl fmt::Display for WasmType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + WasmType::I32 => write!(f, "i32"), + WasmType::I64 => write!(f, "i64"), + WasmType::F32 => write!(f, "f32"), + WasmType::F64 => write!(f, "f64"), + WasmType::V128 => write!(f, "v128"), + WasmType::Ref(rt) => write!(f, "{rt}"), } } } -impl TryFrom for WasmType { - type Error = WasmError; - fn try_from(ty: wasmparser::RefType) -> Result { - match ty { - wasmparser::RefType::FUNCREF => Ok(WasmType::FuncRef), - wasmparser::RefType::EXTERNREF => Ok(WasmType::ExternRef), - _ => Err(WasmError::Unsupported( - "function references proposal".to_string(), - )), - } - } +/// WebAssembly reference type -- equivalent of `wasmparser`'s RefType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct WasmRefType { + pub nullable: bool, + pub heap_type: WasmHeapType, } -impl TryFrom for WasmType { - type Error = WasmError; - fn try_from(ty: wasmparser::HeapType) -> Result { - match ty { - wasmparser::HeapType::Func => Ok(WasmType::FuncRef), - wasmparser::HeapType::Extern => Ok(WasmType::ExternRef), - // NB: when the function-references proposal is implemented this - // entire `impl` should probably go away to remove the need for not - // only this `unsupported` but everything. - _ => Err(WasmError::Unsupported( - "function references proposal".to_string(), - )), - } - } +impl WasmRefType { + pub const EXTERNREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Extern, + }; + pub const FUNCREF: WasmRefType = WasmRefType { + nullable: true, + heap_type: WasmHeapType::Func, + }; } -impl From for wasmparser::ValType { - fn from(ty: WasmType) -> wasmparser::ValType { - match ty { - WasmType::I32 => wasmparser::ValType::I32, - WasmType::I64 => wasmparser::ValType::I64, - WasmType::F32 => wasmparser::ValType::F32, - WasmType::F64 => wasmparser::ValType::F64, - WasmType::V128 => wasmparser::ValType::V128, - WasmType::FuncRef => wasmparser::ValType::FUNCREF, - WasmType::ExternRef => wasmparser::ValType::EXTERNREF, +impl fmt::Display for WasmRefType { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match *self { + Self::FUNCREF => write!(f, "funcref"), + Self::EXTERNREF => write!(f, "externref"), + _ => { + if self.nullable { + write!(f, "(ref null {})", self.heap_type) + } else { + write!(f, "(ref {})", self.heap_type) + } + } } } } -impl fmt::Display for WasmType { +/// WebAssembly heap type -- equivalent of `wasmparser`'s HeapType +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum WasmHeapType { + Func, + Extern, + // FIXME: the `SignatureIndex` payload here is not suitable given all the + // contexts that this type is used within. For example the Engine in + // wasmtime hashes this index which is not appropriate because the index is + // not globally unique. + // + // This probably needs to become `WasmHeapType` where all of translation + // uses `WasmHeapType` and all of engine-level "stuff" uses + // `WasmHeapType`. This `` would need to be + // propagated to quite a few locations though so it's left for a future + // refactoring at this time. + TypedFunc(SignatureIndex), +} + +impl fmt::Display for WasmHeapType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - WasmType::I32 => write!(f, "i32"), - WasmType::I64 => write!(f, "i64"), - WasmType::F32 => write!(f, "f32"), - WasmType::F64 => write!(f, "f64"), - WasmType::V128 => write!(f, "v128"), - WasmType::ExternRef => write!(f, "externref"), - WasmType::FuncRef => write!(f, "funcref"), + Self::Func => write!(f, "func"), + Self::Extern => write!(f, "extern"), + Self::TypedFunc(i) => write!(f, "func_sig{}", i.as_u32()), } } } @@ -115,10 +114,19 @@ pub struct WasmFuncType { impl WasmFuncType { #[inline] pub fn new(params: Box<[WasmType]>, returns: Box<[WasmType]>) -> Self { - let externref_params_count = params.iter().filter(|p| **p == WasmType::ExternRef).count(); + let externref_params_count = params + .iter() + .filter(|p| match **p { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) + .count(); let externref_returns_count = returns .iter() - .filter(|r| **r == WasmType::ExternRef) + .filter(|r| match **r { + WasmType::Ref(rt) => rt.heap_type == WasmHeapType::Extern, + _ => false, + }) .count(); WasmFuncType { params, @@ -153,25 +161,6 @@ impl WasmFuncType { } } -impl TryFrom for WasmFuncType { - type Error = WasmError; - fn try_from(ty: wasmparser::FuncType) -> Result { - let params = ty - .params() - .iter() - .copied() - .map(WasmType::try_from) - .collect::>()?; - let returns = ty - .results() - .iter() - .copied() - .map(WasmType::try_from) - .collect::>()?; - Ok(Self::new(params, returns)) - } -} - /// Index type of a function (imported or defined) inside the WebAssembly module. #[derive(Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord, Debug, Serialize, Deserialize)] pub struct FuncIndex(u32); @@ -341,39 +330,17 @@ pub enum GlobalInit { RefFunc(FuncIndex), } -impl Global { - /// Creates a new `Global` type from wasmparser's representation. - pub fn new(ty: wasmparser::GlobalType) -> WasmResult { - Ok(Global { - wasm_ty: ty.content_type.try_into()?, - mutability: ty.mutable, - }) - } -} - /// WebAssembly table. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct Table { /// The table elements' Wasm type. - pub wasm_ty: WasmType, + pub wasm_ty: WasmRefType, /// The minimum number of elements in the table. pub minimum: u32, /// The maximum number of elements in the table. pub maximum: Option, } -impl TryFrom for Table { - type Error = WasmError; - - fn try_from(ty: wasmparser::TableType) -> WasmResult
{ - Ok(Table { - wasm_ty: ty.element_type.try_into()?, - minimum: ty.initial, - maximum: ty.maximum, - }) - } -} - /// WebAssembly linear memory. #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, Serialize, Deserialize)] pub struct Memory { @@ -414,3 +381,82 @@ impl From for Tag { } } } + +/// Helpers used to convert a `wasmparser` type to a type in this crate. +pub trait TypeConvert { + /// Converts a wasmparser table type into a wasmtime type + fn convert_global_type(&self, ty: &wasmparser::GlobalType) -> Global { + Global { + wasm_ty: self.convert_valtype(ty.content_type), + mutability: ty.mutable, + } + } + + /// Converts a wasmparser table type into a wasmtime type + fn convert_table_type(&self, ty: &wasmparser::TableType) -> Table { + Table { + wasm_ty: self.convert_ref_type(ty.element_type), + minimum: ty.initial, + maximum: ty.maximum, + } + } + + /// Converts a wasmparser function type to a wasmtime type + fn convert_func_type(&self, ty: &wasmparser::FuncType) -> WasmFuncType { + let params = ty + .params() + .iter() + .map(|t| self.convert_valtype(*t)) + .collect(); + let results = ty + .results() + .iter() + .map(|t| self.convert_valtype(*t)) + .collect(); + WasmFuncType::new(params, results) + } + + /// Converts a wasmparser value type to a wasmtime type + fn convert_valtype(&self, ty: wasmparser::ValType) -> WasmType { + match ty { + wasmparser::ValType::I32 => WasmType::I32, + wasmparser::ValType::I64 => WasmType::I64, + wasmparser::ValType::F32 => WasmType::F32, + wasmparser::ValType::F64 => WasmType::F64, + wasmparser::ValType::V128 => WasmType::V128, + wasmparser::ValType::Ref(t) => WasmType::Ref(self.convert_ref_type(t)), + } + } + + /// Converts a wasmparser reference type to a wasmtime type + fn convert_ref_type(&self, ty: wasmparser::RefType) -> WasmRefType { + WasmRefType { + nullable: ty.is_nullable(), + heap_type: self.convert_heap_type(ty.heap_type()), + } + } + + /// Converts a wasmparser heap type to a wasmtime type + fn convert_heap_type(&self, ty: wasmparser::HeapType) -> WasmHeapType { + match ty { + wasmparser::HeapType::Func => WasmHeapType::Func, + wasmparser::HeapType::Extern => WasmHeapType::Extern, + wasmparser::HeapType::TypedFunc(i) => self.lookup_heap_type(TypeIndex::from_u32(i)), + + wasmparser::HeapType::Any + | wasmparser::HeapType::None + | wasmparser::HeapType::NoExtern + | wasmparser::HeapType::NoFunc + | wasmparser::HeapType::Eq + | wasmparser::HeapType::Struct + | wasmparser::HeapType::Array + | wasmparser::HeapType::I31 => { + unimplemented!("unsupported heap type {ty:?}"); + } + } + } + + /// Converts the specified type index from a heap type into a canonicalized + /// heap type. + fn lookup_heap_type(&self, index: TypeIndex) -> WasmHeapType; +} diff --git a/crates/wasmtime/src/config.rs b/crates/wasmtime/src/config.rs index 4b3e3c4843c4..7001d6b02158 100644 --- a/crates/wasmtime/src/config.rs +++ b/crates/wasmtime/src/config.rs @@ -673,6 +673,22 @@ impl Config { self } + /// Configures whether the [WebAssembly function references proposal][proposal] + /// will be enabled for compilation. + /// + /// This feature gates non-nullable reference types, function reference + /// types, call_ref, ref.func, and non-nullable reference related instructions. + /// + /// Note that the function references proposal depends on the reference types proposal. + /// + /// This feature is `false` by default. + /// + /// [proposal]: https://github.com/WebAssembly/function-references + pub fn wasm_function_references(&mut self, enable: bool) -> &mut Self { + self.features.function_references = enable; + self + } + /// Configures whether the WebAssembly SIMD proposal will be /// enabled for compilation. /// @@ -1669,6 +1685,10 @@ impl fmt::Debug for Config { .field("parse_wasm_debuginfo", &self.tunables.parse_wasm_debuginfo) .field("wasm_threads", &self.features.threads) .field("wasm_reference_types", &self.features.reference_types) + .field( + "wasm_function_references", + &self.features.function_references, + ) .field("wasm_bulk_memory", &self.features.bulk_memory) .field("wasm_simd", &self.features.simd) .field("wasm_relaxed_simd", &self.features.relaxed_simd) diff --git a/crates/wasmtime/src/engine/serialization.rs b/crates/wasmtime/src/engine/serialization.rs index f3093bafcaee..5eeb421b711a 100644 --- a/crates/wasmtime/src/engine/serialization.rs +++ b/crates/wasmtime/src/engine/serialization.rs @@ -168,6 +168,7 @@ struct WasmFeatures { memory64: bool, relaxed_simd: bool, extended_const: bool, + function_references: bool, } impl Metadata { @@ -199,7 +200,6 @@ impl Metadata { assert!(!memory_control); assert!(!tail_call); - assert!(!function_references); assert!(!gc); Metadata { @@ -219,6 +219,7 @@ impl Metadata { memory64, relaxed_simd, extended_const, + function_references, }, } } @@ -389,6 +390,7 @@ impl Metadata { memory64, relaxed_simd, extended_const, + function_references, } = self.features; Self::check_bool( @@ -438,6 +440,11 @@ impl Metadata { other.relaxed_simd, "WebAssembly relaxed-simd support", )?; + Self::check_bool( + function_references, + other.function_references, + "WebAssembly function-references support", + )?; Ok(()) } diff --git a/crates/wasmtime/src/types.rs b/crates/wasmtime/src/types.rs index 3bcb28899051..b1d33709417a 100644 --- a/crates/wasmtime/src/types.rs +++ b/crates/wasmtime/src/types.rs @@ -1,5 +1,7 @@ use std::fmt; -use wasmtime_environ::{EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmType}; +use wasmtime_environ::{ + EntityType, Global, Memory, ModuleTypes, Table, WasmFuncType, WasmRefType, WasmType, +}; pub(crate) mod matching; @@ -19,7 +21,7 @@ pub enum Mutability { // Value Types /// A list of all possible value types in WebAssembly. -#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)] +#[derive(Debug, Clone, Hash, Eq, PartialEq)] pub enum ValType { // NB: the ordering here is intended to match the ordering in // `wasmtime_types::WasmType` to help improve codegen when converting. @@ -78,8 +80,8 @@ impl ValType { Self::F32 => WasmType::F32, Self::F64 => WasmType::F64, Self::V128 => WasmType::V128, - Self::FuncRef => WasmType::FuncRef, - Self::ExternRef => WasmType::ExternRef, + Self::FuncRef => WasmType::Ref(WasmRefType::FUNCREF), + Self::ExternRef => WasmType::Ref(WasmRefType::EXTERNREF), } } @@ -90,8 +92,17 @@ impl ValType { WasmType::F32 => Self::F32, WasmType::F64 => Self::F64, WasmType::V128 => Self::V128, - WasmType::FuncRef => Self::FuncRef, - WasmType::ExternRef => Self::ExternRef, + WasmType::Ref(WasmRefType::FUNCREF) => Self::FuncRef, + WasmType::Ref(WasmRefType::EXTERNREF) => Self::ExternRef, + // FIXME: exposing the full function-references (and beyond) + // proposals will require redesigning the embedder API for `ValType` + // and types in Wasmtime. That is a large undertaking which is + // deferred for later. The intention for now is that + // function-references types can't show up in the "public API" of a + // core wasm module but it can use everything internally still. + WasmType::Ref(_) => { + unimplemented!("typed function references are not exposed in the public API yet") + } } } } @@ -289,10 +300,20 @@ pub struct TableType { impl TableType { /// Creates a new table descriptor which will contain the specified /// `element` and have the `limits` applied to its length. + /// + /// # Panics + /// + /// Panics if the `element` type provided is not a reference type. pub fn new(element: ValType, min: u32, max: Option) -> TableType { TableType { ty: Table { - wasm_ty: element.to_wasm_type(), + // FIXME: the `ValType` API should be redesigned and the + // argument to this constructor should be `RefType`. + wasm_ty: match element { + ValType::FuncRef => WasmRefType::FUNCREF, + ValType::ExternRef => WasmRefType::EXTERNREF, + _ => panic!("Attempt to convert non-reference type to a reference type"), + }, minimum: min, maximum: max, }, @@ -301,7 +322,7 @@ impl TableType { /// Returns the element value type of this table. pub fn element(&self) -> ValType { - ValType::from_wasm_type(&self.ty.wasm_ty) + ValType::from_wasm_type(&WasmType::Ref(self.ty.wasm_ty)) } /// Returns minimum number of elements this table must have diff --git a/crates/wasmtime/src/types/matching.rs b/crates/wasmtime/src/types/matching.rs index 11b460fa796a..f0e24e1fe1b6 100644 --- a/crates/wasmtime/src/types/matching.rs +++ b/crates/wasmtime/src/types/matching.rs @@ -2,7 +2,8 @@ use crate::linker::DefinitionType; use crate::{signatures::SignatureCollection, Engine}; use anyhow::{anyhow, bail, Result}; use wasmtime_environ::{ - EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmType, + EntityType, Global, Memory, ModuleTypes, SignatureIndex, Table, WasmFuncType, WasmHeapType, + WasmRefType, WasmType, }; use wasmtime_runtime::VMSharedSignatureIndex; @@ -132,7 +133,14 @@ fn func_ty_mismatch(msg: &str, expected: &WasmFuncType, actual: &WasmFuncType) - } fn global_ty(expected: &Global, actual: &Global) -> Result<()> { - match_ty(expected.wasm_ty, actual.wasm_ty, "global")?; + // Subtyping is only sound on immutable global + // references. Therefore if either type is mutable we perform a + // strict equality check on the types. + if expected.mutability || actual.mutability { + equal_ty(expected.wasm_ty, actual.wasm_ty, "global")?; + } else { + match_ty(expected.wasm_ty, actual.wasm_ty, "global")?; + } match_bool( expected.mutability, actual.mutability, @@ -144,7 +152,11 @@ fn global_ty(expected: &Global, actual: &Global) -> Result<()> { } fn table_ty(expected: &Table, actual: &Table, actual_runtime_size: Option) -> Result<()> { - match_ty(expected.wasm_ty, actual.wasm_ty, "table")?; + equal_ty( + WasmType::Ref(expected.wasm_ty), + WasmType::Ref(actual.wasm_ty), + "table", + )?; match_limits( expected.minimum.into(), expected.maximum.map(|i| i.into()), @@ -180,7 +192,53 @@ fn memory_ty(expected: &Memory, actual: &Memory, actual_runtime_size: Option Result<()> { + let result = match (actual, expected) { + (WasmHeapType::TypedFunc(actual), WasmHeapType::TypedFunc(expected)) => { + // TODO(dhil): we need either canonicalised types or a context here. + actual == expected + } + (WasmHeapType::TypedFunc(_), WasmHeapType::Func) + | (WasmHeapType::Func, WasmHeapType::Func) + | (WasmHeapType::Extern, WasmHeapType::Extern) => true, + (WasmHeapType::Func, _) | (WasmHeapType::Extern, _) | (WasmHeapType::TypedFunc(_), _) => { + false + } + }; + if result { + Ok(()) + } else { + bail!( + "{} types incompatible: expected {0} of type `{}`, found {0} of type `{}`", + desc, + expected, + actual, + ) + } +} + +fn match_ref(expected: WasmRefType, actual: WasmRefType, desc: &str) -> Result<()> { + if actual.nullable == expected.nullable || expected.nullable { + return match_heap(expected.heap_type, actual.heap_type, desc); + } + bail!( + "{} types incompatible: expected {0} of type `{}`, found {0} of type `{}`", + desc, + expected, + actual, + ) +} + +// Checks whether actual is a subtype of expected, i.e. `actual <: expected` +// (note the parameters are given the other way around in code). fn match_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> { + match (actual, expected) { + (WasmType::Ref(actual), WasmType::Ref(expected)) => match_ref(expected, actual, desc), + (actual, expected) => equal_ty(expected, actual, desc), + } +} + +fn equal_ty(expected: WasmType, actual: WasmType, desc: &str) -> Result<()> { if expected == actual { return Ok(()); } diff --git a/crates/wast/src/core.rs b/crates/wast/src/core.rs index 46c1f8c95759..d057affed18e 100644 --- a/crates/wast/src/core.rs +++ b/crates/wast/src/core.rs @@ -80,11 +80,17 @@ pub fn match_val(actual: &Val, expected: &WastRetCore) -> Result<()> { bail!("expected non-null externref, found null") } } - (Val::FuncRef(x), WastRetCore::RefNull(_)) => { + (Val::FuncRef(actual), WastRetCore::RefNull(expected)) => match (actual, expected) { + (None, None) => Ok(()), + (None, Some(HeapType::Func)) => Ok(()), + (None, Some(_)) => bail!("expected null non-funcref, found null funcref"), + (Some(_), _) => bail!("expected null funcref, found non-null"), + }, + (Val::FuncRef(x), WastRetCore::RefFunc(_)) => { if x.is_none() { - Ok(()) + bail!("expected non-null funcref, found null"); } else { - bail!("expected null funcref, found non-null") + Ok(()) } } _ => bail!( diff --git a/crates/wast/src/wast.rs b/crates/wast/src/wast.rs index 545f207e8abf..32d4cc2d02ee 100644 --- a/crates/wast/src/wast.rs +++ b/crates/wast/src/wast.rs @@ -338,6 +338,8 @@ impl WastContext { // specifies which element is uninitialized, but our traps don't // shepherd that information out. || (expected.contains("uninitialized element 2") && actual.contains("uninitialized element")) + // function references call_ref + || (expected.contains("null function") && (actual.contains("uninitialized element") || actual.contains("null reference"))) { return Ok(()); } @@ -488,4 +490,5 @@ fn is_matching_assert_invalid_error_message(expected: &str, actual: &str) -> boo // the spec test suite asserts a different error message than we print // for this scenario || (expected == "unknown global" && actual.contains("global.get of locally defined global")) + || (expected == "immutable global" && actual.contains("global is immutable: cannot modify it with `global.set`")) } diff --git a/crates/winch/src/compiler.rs b/crates/winch/src/compiler.rs index a2da414bbf75..d57c412e98b5 100644 --- a/crates/winch/src/compiler.rs +++ b/crates/winch/src/compiler.rs @@ -54,10 +54,11 @@ impl wasmtime_environ::Compiler for Compiler { index: DefinedFuncIndex, data: FunctionBodyData<'_>, _tunables: &Tunables, - _types: &ModuleTypes, + types: &ModuleTypes, ) -> Result<(WasmFunctionInfo, Box), CompileError> { let index = translation.module.func_index(index); - let sig = translation.get_types().function_at(index.as_u32()).unwrap(); + let sig = translation.module.functions[index].signature; + let ty = &types[sig]; let FunctionBodyData { body, validator } = data; let start_srcloc = FilePos::new( body.get_binary_reader() @@ -68,7 +69,7 @@ impl wasmtime_environ::Compiler for Compiler { let mut validator = validator.into_validator(self.take_allocations()); let buffer = self .isa - .compile_function(&sig, &body, &translation, &mut validator) + .compile_function(ty, &body, &translation, &mut validator) .map_err(|e| CompileError::Codegen(format!("{e:?}"))); self.save_allocations(validator.into_allocations()); let buffer = buffer?; @@ -93,13 +94,9 @@ impl wasmtime_environ::Compiler for Compiler { let func_index = translation.module.func_index(index); let sig = translation.module.functions[func_index].signature; let ty = &types[sig]; - let wasm_ty = wasmparser::FuncType::new( - ty.params().iter().copied().map(Into::into), - ty.returns().iter().copied().map(Into::into), - ); let buffer = self .isa - .compile_trampoline(&wasm_ty, TrampolineKind::ArrayToWasm(func_index)) + .compile_trampoline(&ty, TrampolineKind::ArrayToWasm(func_index)) .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?; let compiled_function = CompiledFunction::new(buffer, CompiledFuncEnv {}, self.isa.function_alignment()); @@ -116,14 +113,10 @@ impl wasmtime_environ::Compiler for Compiler { let func_index = translation.module.func_index(index); let sig = translation.module.functions[func_index].signature; let ty = &types[sig]; - let wasm_ty = wasmparser::FuncType::new( - ty.params().iter().copied().map(Into::into), - ty.returns().iter().copied().map(Into::into), - ); let buffer = self .isa - .compile_trampoline(&wasm_ty, TrampolineKind::NativeToWasm(func_index)) + .compile_trampoline(ty, TrampolineKind::NativeToWasm(func_index)) .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?; let compiled_function = @@ -137,14 +130,9 @@ impl wasmtime_environ::Compiler for Compiler { _translation: &ModuleTranslation<'_>, wasm_func_ty: &wasmtime_environ::WasmFuncType, ) -> Result, CompileError> { - let wasm_ty = wasmparser::FuncType::new( - wasm_func_ty.params().iter().copied().map(Into::into), - wasm_func_ty.returns().iter().copied().map(Into::into), - ); - let buffer = self .isa - .compile_trampoline(&wasm_ty, TrampolineKind::WasmToNative) + .compile_trampoline(wasm_func_ty, TrampolineKind::WasmToNative) .map_err(|e| CompileError::Codegen(format!("{:?}", e)))?; let compiled_function = diff --git a/tests/all/externals.rs b/tests/all/externals.rs index 680b533198d1..eb256d3ba28d 100644 --- a/tests/all/externals.rs +++ b/tests/all/externals.rs @@ -18,12 +18,20 @@ fn bad_globals() { } #[test] -fn bad_tables() { - let mut store = Store::<()>::default(); +#[should_panic] +fn bad_tables_i32() { + // NOTE(dhil): The below test does not make sense after the + // implementation of the function-references proposal, since the + // type component of a TableType is now a reference type (I32 is + // not a reference type constructor). // i32 not supported yet - let ty = TableType::new(ValType::I32, 0, Some(1)); - assert!(Table::new(&mut store, ty.clone(), Val::I32(0)).is_err()); + TableType::new(ValType::I32, 0, Some(1)); +} + +#[test] +fn bad_tables() { + let mut store = Store::<()>::default(); // mismatched initializer let ty = TableType::new(ValType::FuncRef, 0, Some(1)); diff --git a/tests/all/wast.rs b/tests/all/wast.rs index 994d27269288..5d1d1d6d85ac 100644 --- a/tests/all/wast.rs +++ b/tests/all/wast.rs @@ -29,6 +29,7 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> let memory64 = feature_found(wast, "memory64"); let multi_memory = feature_found(wast, "multi-memory"); let threads = feature_found(wast, "threads"); + let function_references = feature_found(wast, "function-references"); let reference_types = !(threads && feature_found(wast, "proposals")); let relaxed_simd = feature_found(wast, "relaxed-simd"); let use_shared_memory = feature_found_src(&wast_bytes, "shared_memory") @@ -43,6 +44,7 @@ fn run_wast(wast: &str, strategy: Strategy, pooling: bool) -> anyhow::Result<()> cfg.wasm_multi_memory(multi_memory) .wasm_threads(threads) .wasm_memory64(memory64) + .wasm_function_references(function_references) .wasm_reference_types(reference_types) .wasm_relaxed_simd(relaxed_simd) .cranelift_debug_verifier(true); diff --git a/tests/misc_testsuite/function-references/table_fill.wast b/tests/misc_testsuite/function-references/table_fill.wast new file mode 100644 index 000000000000..ac20adf31a34 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_fill.wast @@ -0,0 +1,163 @@ +(module + (table $t 10 externref) + + (func (export "fill") (param $i i32) (param $r externref) (param $n i32) + (table.fill $t (local.get $i) (local.get $r) (local.get $n)) + ) + + (func (export "get") (param $i i32) (result externref) + (table.get $t (local.get $i)) + ) +) + +(assert_return (invoke "get" (i32.const 1)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 2)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 3)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 4)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 5)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 2) (ref.extern 1) (i32.const 3))) +(assert_return (invoke "get" (i32.const 1)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 2)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 5)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 4) (ref.extern 2) (i32.const 2))) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 6)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 4) (ref.extern 3) (i32.const 0))) +(assert_return (invoke "get" (i32.const 3)) (ref.extern 1)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 5)) (ref.extern 2)) + +(assert_return (invoke "fill" (i32.const 8) (ref.extern 4) (i32.const 2))) +(assert_return (invoke "get" (i32.const 7)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.extern 4)) + +(assert_return (invoke "fill" (i32.const 9) (ref.null extern) (i32.const 1))) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_return (invoke "fill" (i32.const 10) (ref.extern 5) (i32.const 0))) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_trap + (invoke "fill" (i32.const 8) (ref.extern 6) (i32.const 3)) + "out of bounds table access" +) +(assert_return (invoke "get" (i32.const 7)) (ref.null extern)) +(assert_return (invoke "get" (i32.const 8)) (ref.extern 4)) +(assert_return (invoke "get" (i32.const 9)) (ref.null extern)) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null extern) (i32.const 0)) + "out of bounds table access" +) + +(assert_trap + (invoke "fill" (i32.const 11) (ref.null extern) (i32.const 10)) + "out of bounds table access" +) + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-value-length-empty-vs-i32-i32 + (table.fill $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 + (table.fill $t (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-value-empty-vs + (table.fill $t (i32.const 1) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-length-empty-vs-i32 + (table.fill $t (i32.const 1) (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-index-f32-vs-i32 + (table.fill $t (f32.const 1) (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-value-vs-funcref (param $r externref) + (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func $type-funcref-vs-typed-func (param $r funcref) + (table.fill $t (i32.const 1) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-length-f32-vs-i32 + (table.fill $t (i32.const 1) (ref.null extern) (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 externref) + (table $t2 1 funcref) + (func $type-value-externref-vs-funcref-multi (param $r externref) + (table.fill $t2 (i32.const 0) (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-empty-vs-num (result i32) + (table.fill $t (i32.const 0) (ref.null extern) (i32.const 1)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_get.wast b/tests/misc_testsuite/function-references/table_get.wast new file mode 100644 index 000000000000..f4ab7b0151c2 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_get.wast @@ -0,0 +1,98 @@ +(module + (type $res-i32 (func (result i32))) + (table $t2 2 externref) + (table $t3 3 funcref) + (table $t4 (ref null $res-i32) (elem (ref.func $returns-five))) + (elem (table $t3) (i32.const 1) func $returns-five) + (func $returns-five (result i32) (i32.const 5)) + + (func (export "init") (param $r externref) + (table.set $t2 (i32.const 1) (local.get $r)) + (table.set $t3 (i32.const 2) (table.get $t3 (i32.const 1))) + ) + + (func (export "get-externref") (param $i i32) (result externref) + (table.get $t2 (local.get $i)) + ) + (func $f3 (export "get-funcref") (param $i i32) (result funcref) + (table.get $t3 (local.get $i)) + ) + (func $f4 (export "get-typed-func") (param $i i32) (result (ref $res-i32)) + (ref.as_non_null (table.get $t4 (local.get $i))) + ) + + (func (export "is_null-funcref") (param $i i32) (result i32) + (ref.is_null (call $f3 (local.get $i))) + ) + (func (export "get-typed-and-call") (param $i i32) (result i32) (call_ref $res-i32 (call $f4 (local.get $i)))) +) + +(invoke "init" (ref.extern 1)) + +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "get-externref" (i32.const 1)) (ref.extern 1)) + +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) +(assert_return (invoke "is_null-funcref" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "is_null-funcref" (i32.const 2)) (i32.const 0)) + +(assert_return (invoke "get-typed-and-call" (i32.const 0)) (i32.const 5)) + +(assert_trap (invoke "get-externref" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "get-funcref" (i32.const 3)) "out of bounds table access") +(assert_trap (invoke "get-typed-func" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "get-externref" (i32.const -1)) "out of bounds table access") +(assert_trap (invoke "get-funcref" (i32.const -1)) "out of bounds table access") +(assert_trap (invoke "get-typed-func" (i32.const -1)) "out of bounds table access") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 (result externref) + (table.get $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-f32-vs-i32 (result externref) + (table.get $t (f32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-externref-vs-empty + (table.get $t (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-externref-vs-funcref (result funcref) + (table.get $t (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 funcref) + (table $t2 1 externref) + (func $type-result-externref-vs-funcref-multi (result funcref) + (table.get $t2 (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_grow.wast b/tests/misc_testsuite/function-references/table_grow.wast new file mode 100644 index 000000000000..5dd9eefa1329 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_grow.wast @@ -0,0 +1,196 @@ +(module + (table $t 0 externref) + + (func (export "get") (param $i i32) (result externref) (table.get $t (local.get $i))) + (func (export "set") (param $i i32) (param $r externref) (table.set $t (local.get $i) (local.get $r))) + + (func (export "grow") (param $sz i32) (param $init externref) (result i32) + (table.grow $t (local.get $init) (local.get $sz)) + ) + (func (export "size") (result i32) (table.size $t)) +) + +(assert_return (invoke "size") (i32.const 0)) +(assert_trap (invoke "set" (i32.const 0) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 0)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 1) (ref.null extern)) (i32.const 0)) +(assert_return (invoke "size") (i32.const 1)) +(assert_return (invoke "get" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_trap (invoke "set" (i32.const 1) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 1)) "out of bounds table access") + +(assert_return (invoke "grow" (i32.const 4) (ref.extern 3)) (i32.const 1)) +(assert_return (invoke "size") (i32.const 5)) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_return (invoke "set" (i32.const 0) (ref.extern 2))) +(assert_return (invoke "get" (i32.const 0)) (ref.extern 2)) +(assert_return (invoke "get" (i32.const 1)) (ref.extern 3)) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 3)) +(assert_return (invoke "set" (i32.const 4) (ref.extern 4))) +(assert_return (invoke "get" (i32.const 4)) (ref.extern 4)) +(assert_trap (invoke "set" (i32.const 5) (ref.extern 2)) "out of bounds table access") +(assert_trap (invoke "get" (i32.const 5)) "out of bounds table access") + + +;; Reject growing to size outside i32 value range +(module + (table $t 0x10 funcref) + (elem declare func $f) + (func $f (export "grow") (result i32) + (table.grow $t (ref.func $f) (i32.const 0xffff_fff0)) + ) +) + +(assert_return (invoke "grow") (i32.const -1)) + + +(module + (table $t 0 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) + +(module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null $afunc) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 800)) (i32.const 3)) + +(module + (table $t 0 10 externref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null extern) (local.get 0)) + ) +) + +(assert_return (invoke "grow" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 0)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const 1)) +(assert_return (invoke "grow" (i32.const 2)) (i32.const 2)) +(assert_return (invoke "grow" (i32.const 6)) (i32.const 4)) +(assert_return (invoke "grow" (i32.const 0)) (i32.const 10)) +(assert_return (invoke "grow" (i32.const 1)) (i32.const -1)) +(assert_return (invoke "grow" (i32.const 0x10000)) (i32.const -1)) + + +(module + (table $t 10 funcref) + (func (export "grow") (param i32) (result i32) + (table.grow $t (ref.null func) (local.get 0)) + ) + (elem declare func 1) + (func (export "check-table-null") (param i32 i32) (result funcref) + (local funcref) + (local.set 2 (ref.func 1)) + (block + (loop + (local.set 2 (table.get $t (local.get 0))) + (br_if 1 (i32.eqz (ref.is_null (local.get 2)))) + (br_if 1 (i32.ge_u (local.get 0) (local.get 1))) + (local.set 0 (i32.add (local.get 0) (i32.const 1))) + (br_if 0 (i32.le_u (local.get 0) (local.get 1))) + ) + ) + (local.get 2) + ) +) + +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 9)) (ref.null func)) +(assert_return (invoke "grow" (i32.const 10)) (i32.const 10)) +(assert_return (invoke "check-table-null" (i32.const 0) (i32.const 19)) (ref.null func)) + + +;; Type errors + +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-size-empty-vs-i32-externref (result i32) + (table.grow $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-empty-vs-i32 (result i32) + (table.grow $t (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-init-empty-vs-externref (result i32) + (table.grow $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 externref) + (func $type-size-f32-vs-i32 (result i32) + (table.grow $t (ref.null extern) (f32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 0 funcref) + (func $type-init-externref-vs-funcref (param $r externref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (type $afunc (func)) + (table $t 0 (ref null $afunc)) + (func $type-init-funcref-vs-typed-func (param $r funcref) (result i32) + (table.grow $t (local.get $r) (i32.const 1)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-empty + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 1 externref) + (func $type-result-i32-vs-f32 (result f32) + (table.grow $t (ref.null extern) (i32.const 0)) + ) + ) + "type mismatch" +) diff --git a/tests/misc_testsuite/function-references/table_set.wast b/tests/misc_testsuite/function-references/table_set.wast new file mode 100644 index 000000000000..b9ad5cac4781 --- /dev/null +++ b/tests/misc_testsuite/function-references/table_set.wast @@ -0,0 +1,140 @@ +(module + (type $res-i32 (func (result i32))) + (table $t2 1 externref) + (table $t3 2 funcref) + (table $t4 1 (ref null $res-i32)) + (elem (table $t3) (i32.const 1) func $returns-five) + (func $returns-five (result i32) (i32.const 5)) + + (func (export "get-externref") (param $i i32) (result externref) + (table.get $t2 (local.get $i)) + ) + (func $f3 (export "get-funcref") (param $i i32) (result funcref) + (table.get $t3 (local.get $i)) + ) + (func $f4 (export "get-typed-func") (param $i i32) (result (ref null $res-i32)) + (table.get $t4 (local.get $i)) + ) + + (func (export "set-externref") (param $i i32) (param $r externref) + (table.set $t2 (local.get $i) (local.get $r)) + ) + (func (export "set-funcref") (param $i i32) (param $r funcref) + (table.set $t3 (local.get $i) (local.get $r)) + ) + (func (export "set-funcref-from") (param $i i32) (param $j i32) + (table.set $t3 (local.get $i) (table.get $t3 (local.get $j))) + ) + (func $f5 (export "set-typed-func") (param $i i32) (param $r (ref $res-i32)) + (table.set $t4 (local.get $i) (local.get $r)) + ) + + (func (export "is_null-funcref") (param $i i32) (result i32) + (ref.is_null (call $f3 (local.get $i))) + ) + (func (export "is_null-typed-func") (param $i i32) (result i32) + (ref.is_null (call $f4 (local.get $i))) + ) + (func (export "set-returns-five") (param $i i32) + (call $f5 (local.get $i) (ref.func $returns-five)) + ) + (func (export "get-typed-and-call") (param $i i32) (result i32) (call_ref $res-i32 (call $f4 (local.get $i)))) +) + +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) +(assert_return (invoke "set-externref" (i32.const 0) (ref.extern 1))) +(assert_return (invoke "get-externref" (i32.const 0)) (ref.extern 1)) +(assert_return (invoke "set-externref" (i32.const 0) (ref.null extern))) +(assert_return (invoke "get-externref" (i32.const 0)) (ref.null extern)) + +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) +(assert_return (invoke "set-funcref-from" (i32.const 0) (i32.const 1))) +(assert_return (invoke "is_null-funcref" (i32.const 0)) (i32.const 0)) +(assert_return (invoke "set-funcref" (i32.const 0) (ref.null func))) +(assert_return (invoke "get-funcref" (i32.const 0)) (ref.null func)) + +(assert_return (invoke "is_null-typed-func" (i32.const 0)) (i32.const 1)) +(invoke "set-returns-five" (i32.const 0)) +(assert_return (invoke "get-typed-and-call" (i32.const 0)) (i32.const 5)) + +(assert_trap (invoke "set-externref" (i32.const 2) (ref.null extern)) "out of bounds table access") +(assert_trap (invoke "set-funcref" (i32.const 3) (ref.null func)) "out of bounds table access") +(assert_trap (invoke "set-returns-five" (i32.const 2)) "out of bounds table access") +(assert_trap (invoke "set-externref" (i32.const -1) (ref.null extern)) "out of bounds table access") +(assert_trap (invoke "set-funcref" (i32.const -1) (ref.null func)) "out of bounds table access") +(assert_trap (invoke "set-returns-five" (i32.const -1)) "out of bounds table access") + +(assert_trap (invoke "set-externref" (i32.const 2) (ref.extern 0)) "out of bounds table access") +(assert_trap (invoke "set-funcref-from" (i32.const 3) (i32.const 1)) "out of bounds table access") +(assert_trap (invoke "set-externref" (i32.const -1) (ref.extern 0)) "out of bounds table access") +(assert_trap (invoke "set-funcref-from" (i32.const -1) (i32.const 1)) "out of bounds table access") + + +;; Type errors + +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-value-empty-vs-i32-externref + (table.set $t) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-index-empty-vs-i32 + (table.set $t (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-value-empty-vs-externref + (table.set $t (i32.const 1)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 externref) + (func $type-size-f32-vs-i32 + (table.set $t (f32.const 1) (ref.null extern)) + ) + ) + "type mismatch" +) +(assert_invalid + (module + (table $t 10 funcref) + (func $type-value-externref-vs-funcref (param $r externref) + (table.set $t (i32.const 1) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t1 1 externref) + (table $t2 1 funcref) + (func $type-value-externref-vs-funcref-multi (param $r externref) + (table.set $t2 (i32.const 0) (local.get $r)) + ) + ) + "type mismatch" +) + +(assert_invalid + (module + (table $t 10 externref) + (func $type-result-empty-vs-num (result i32) + (table.set $t (i32.const 0) (ref.null extern)) + ) + ) + "type mismatch" +) diff --git a/winch/codegen/src/abi/local.rs b/winch/codegen/src/abi/local.rs index 0a58b92a071c..d0aaa347ce8b 100644 --- a/winch/codegen/src/abi/local.rs +++ b/winch/codegen/src/abi/local.rs @@ -1,4 +1,5 @@ -use wasmparser::ValType; +use wasmtime_environ::WasmType; + /// Base register used to address the local slot. /// /// Slots for stack arguments are addressed from the frame pointer. @@ -19,7 +20,7 @@ pub(crate) struct LocalSlot { /// The offset of the local slot. pub offset: u32, /// The type contained by this local slot. - pub ty: ValType, + pub ty: WasmType, /// Base register associated to this local slot. base: Base, } @@ -27,7 +28,7 @@ pub(crate) struct LocalSlot { impl LocalSlot { /// Creates a local slot for a function defined local or /// for a spilled argument register. - pub fn new(ty: ValType, offset: u32) -> Self { + pub fn new(ty: WasmType, offset: u32) -> Self { Self { ty, offset, @@ -38,7 +39,7 @@ impl LocalSlot { /// Int32 shortcut for `new`. pub fn i32(offset: u32) -> Self { Self { - ty: ValType::I32, + ty: WasmType::I32, offset, base: Base::SP, } @@ -47,14 +48,14 @@ impl LocalSlot { /// Int64 shortcut for `new`. pub fn i64(offset: u32) -> Self { Self { - ty: ValType::I64, + ty: WasmType::I64, offset, base: Base::SP, } } /// Creates a local slot for a stack function argument. - pub fn stack_arg(ty: ValType, offset: u32) -> Self { + pub fn stack_arg(ty: WasmType, offset: u32) -> Self { Self { ty, offset, diff --git a/winch/codegen/src/abi/mod.rs b/winch/codegen/src/abi/mod.rs index f7f37b301fb8..0388c4b9be4b 100644 --- a/winch/codegen/src/abi/mod.rs +++ b/winch/codegen/src/abi/mod.rs @@ -46,7 +46,7 @@ use crate::isa::{reg::Reg, CallingConvention}; use smallvec::SmallVec; use std::ops::{Add, BitAnd, Not, Sub}; -use wasmparser::{FuncType, ValType}; +use wasmtime_environ::{WasmFuncType, WasmType}; pub(crate) mod local; pub(crate) use local::*; @@ -69,7 +69,7 @@ pub(crate) trait ABI { /// Construct the ABI-specific signature from a WebAssembly /// function type. - fn sig(wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig; + fn sig(wasm_sig: &WasmFuncType, call_conv: &CallingConvention) -> ABISig; /// Returns the number of bits in a word. fn word_bits() -> u32; @@ -103,14 +103,14 @@ pub(crate) enum ABIArg { /// A register argument. Reg { /// Type of the argument. - ty: ValType, + ty: WasmType, /// Register holding the argument. reg: Reg, }, /// A stack argument. Stack { /// The type of the argument. - ty: ValType, + ty: WasmType, /// Offset of the argument relative to the frame pointer. offset: u32, }, @@ -118,12 +118,12 @@ pub(crate) enum ABIArg { impl ABIArg { /// Allocate a new register abi arg. - pub fn reg(reg: Reg, ty: ValType) -> Self { + pub fn reg(reg: Reg, ty: WasmType) -> Self { Self::Reg { reg, ty } } /// Allocate a new stack abi arg. - pub fn stack_offset(offset: u32, ty: ValType) -> Self { + pub fn stack_offset(offset: u32, ty: WasmType) -> Self { Self::Stack { ty, offset } } @@ -144,7 +144,7 @@ impl ABIArg { } /// Get the type associated to this arg. - pub fn ty(&self) -> ValType { + pub fn ty(&self) -> WasmType { match *self { ABIArg::Reg { ty, .. } | ABIArg::Stack { ty, .. } => ty, } @@ -155,7 +155,7 @@ impl ABIArg { pub(crate) enum ABIResult { Reg { /// Type of the result. - ty: Option, + ty: Option, /// Register to hold the result. reg: Reg, }, @@ -163,7 +163,7 @@ pub(crate) enum ABIResult { impl ABIResult { /// Create a register ABI result. - pub fn reg(ty: Option, reg: Reg) -> Self { + pub fn reg(ty: Option, reg: Reg) -> Self { Self::Reg { ty, reg } } @@ -206,10 +206,10 @@ impl ABISig { } /// Returns the size in bytes of a given WebAssembly type. -pub(crate) fn ty_size(ty: &ValType) -> u32 { +pub(crate) fn ty_size(ty: &WasmType) -> u32 { match *ty { - ValType::I32 | ValType::F32 => 4, - ValType::I64 | ValType::F64 => 8, + WasmType::I32 | WasmType::F32 => 4, + WasmType::I64 | WasmType::F64 => 8, _ => panic!(), } } diff --git a/winch/codegen/src/codegen/env.rs b/winch/codegen/src/codegen/env.rs index 2dc6044d92a7..f1fe23fd240a 100644 --- a/winch/codegen/src/codegen/env.rs +++ b/winch/codegen/src/codegen/env.rs @@ -1,5 +1,6 @@ -use wasmparser::FuncType; -use wasmtime_environ::{FuncIndex, ModuleTranslation, PtrSize, VMOffsets}; +use wasmtime_environ::{ + FuncIndex, ModuleTranslation, PtrSize, TypeConvert, VMOffsets, WasmFuncType, +}; /// The function environment. /// @@ -28,10 +29,11 @@ impl<'a, P: PtrSize> FuncEnv<'a, P> { let ty = types .function_at(idx.as_u32()) .unwrap_or_else(|| panic!("function type at index: {}", idx.as_u32())); + let ty = self.translation.module.convert_func_type(ty); let import = self.translation.module.is_imported_function(idx); Callee { - ty: ty.clone(), + ty, import, index: idx, } @@ -42,7 +44,7 @@ impl<'a, P: PtrSize> FuncEnv<'a, P> { /// to emit function calls. pub struct Callee { /// The function type. - pub ty: FuncType, + pub ty: WasmFuncType, /// A flag to determine if the callee is imported. pub import: bool, /// The callee index in the WebAssembly function index space. diff --git a/winch/codegen/src/codegen/mod.rs b/winch/codegen/src/codegen/mod.rs index d414a89681ae..6a13f32cd16e 100644 --- a/winch/codegen/src/codegen/mod.rs +++ b/winch/codegen/src/codegen/mod.rs @@ -6,10 +6,8 @@ use crate::{ }; use anyhow::Result; use call::FnCall; -use wasmparser::{ - BinaryReader, FuncType, FuncValidator, ValType, ValidatorResources, VisitOperator, -}; -use wasmtime_environ::FuncIndex; +use wasmparser::{BinaryReader, FuncValidator, ValidatorResources, VisitOperator}; +use wasmtime_environ::{FuncIndex, WasmFuncType, WasmType}; mod context; pub(crate) use context::*; @@ -128,9 +126,9 @@ where let callee = self.env.callee_from_index(index); let (sig, callee_addr): (ABISig, Option<::Address>) = if callee.import { - let mut params = vec![ValType::I64, ValType::I64]; - params.extend_from_slice(&callee.ty.params()); - let sig = FuncType::new(params, callee.ty.results().to_owned()); + let mut params = vec![WasmType::I64, WasmType::I64]; + params.extend_from_slice(callee.ty.params()); + let sig = WasmFuncType::new(params.into(), callee.ty.returns().into()); let caller_vmctx = ::vmctx_reg(); let callee_vmctx = self.context.any_gpr(self.masm); @@ -196,8 +194,8 @@ where .expect("arg should be associated to a register"); match &ty { - ValType::I32 => self.masm.store(src.into(), addr, OperandSize::S32), - ValType::I64 => self.masm.store(src.into(), addr, OperandSize::S64), + WasmType::I32 => self.masm.store(src.into(), addr, OperandSize::S32), + WasmType::I64 => self.masm.store(src.into(), addr, OperandSize::S64), _ => panic!("Unsupported type {:?}", ty), } }); diff --git a/winch/codegen/src/frame/mod.rs b/winch/codegen/src/frame/mod.rs index 17dbd1164fae..2979162648e9 100644 --- a/winch/codegen/src/frame/mod.rs +++ b/winch/codegen/src/frame/mod.rs @@ -2,7 +2,8 @@ use crate::abi::{align_to, ty_size, ABIArg, ABISig, LocalSlot, ABI}; use anyhow::Result; use smallvec::SmallVec; use std::ops::Range; -use wasmparser::{BinaryReader, FuncValidator, ValType, ValidatorResources}; +use wasmparser::{BinaryReader, FuncValidator, ValidatorResources}; +use wasmtime_environ::{ModuleTranslation, TypeConvert}; // TODO: // SpiderMonkey's implementation uses 16; @@ -32,6 +33,7 @@ pub(crate) struct DefinedLocals { impl DefinedLocals { /// Compute the local slots for a Wasm function. pub fn new( + translation: &ModuleTranslation<'_>, reader: &mut BinaryReader<'_>, validator: &mut FuncValidator, ) -> Result { @@ -46,7 +48,7 @@ impl DefinedLocals { let ty = reader.read()?; validator.define_locals(position, count, ty)?; - let ty: ValType = ty.try_into()?; + let ty = translation.module.convert_valtype(ty); for _ in 0..count { let ty_size = ty_size(&ty); next_stack = align_to(next_stack, ty_size) + ty_size; diff --git a/winch/codegen/src/isa/aarch64/abi.rs b/winch/codegen/src/isa/aarch64/abi.rs index 8479aa9b4c4b..54e7291e1ba4 100644 --- a/winch/codegen/src/isa/aarch64/abi.rs +++ b/winch/codegen/src/isa/aarch64/abi.rs @@ -2,7 +2,7 @@ use super::regs; use crate::abi::{ABIArg, ABIResult, ABISig, ABI}; use crate::isa::{reg::Reg, CallingConvention}; use smallvec::SmallVec; -use wasmparser::{FuncType, ValType}; +use wasmtime_environ::{WasmFuncType, WasmType}; #[derive(Default)] pub(crate) struct Aarch64ABI; @@ -63,10 +63,10 @@ impl ABI for Aarch64ABI { 64 } - fn sig(wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig { + fn sig(wasm_sig: &WasmFuncType, call_conv: &CallingConvention) -> ABISig { assert!(call_conv.is_apple_aarch64() || call_conv.is_default()); - if wasm_sig.results().len() > 1 { + if wasm_sig.returns().len() > 1 { panic!("multi-value not supported"); } @@ -79,7 +79,7 @@ impl ABI for Aarch64ABI { .map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env)) .collect(); - let ty = wasm_sig.results().get(0).map(|e| e.clone()); + let ty = wasm_sig.returns().get(0).map(|e| e.clone()); // NOTE temporarily defaulting to x0; let reg = regs::xreg(0); let result = ABIResult::reg(ty, reg); @@ -110,14 +110,14 @@ impl ABI for Aarch64ABI { impl Aarch64ABI { fn to_abi_arg( - wasm_arg: &ValType, + wasm_arg: &WasmType, stack_offset: &mut u32, index_env: &mut RegIndexEnv, ) -> ABIArg { let (reg, ty) = match wasm_arg { - ty @ (ValType::I32 | ValType::I64) => (index_env.next_xreg().map(regs::xreg), ty), + ty @ (WasmType::I32 | WasmType::I64) => (index_env.next_xreg().map(regs::xreg), ty), - ty @ (ValType::F32 | ValType::F64) => (index_env.next_vreg().map(regs::vreg), ty), + ty @ (WasmType::F32 | WasmType::F64) => (index_env.next_vreg().map(regs::vreg), ty), ty => unreachable!("Unsupported argument type {:?}", ty), }; @@ -142,9 +142,9 @@ mod tests { isa::reg::Reg, isa::CallingConvention, }; - use wasmparser::{ - FuncType, - ValType::{self, *}, + use wasmtime_environ::{ + WasmFuncType, + WasmType::{self, *}, }; #[test] @@ -160,7 +160,10 @@ mod tests { #[test] fn xreg_abi_sig() { - let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32, I64], []); + let wasm_sig = WasmFuncType::new( + [I32, I64, I32, I64, I32, I32, I64, I32, I64].into(), + [].into(), + ); let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -178,7 +181,10 @@ mod tests { #[test] fn vreg_abi_sig() { - let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, F64, F32, F64, F32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -196,7 +202,10 @@ mod tests { #[test] fn mixed_abi_sig() { - let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = Aarch64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -212,7 +221,7 @@ mod tests { match_reg_arg(params.get(8).unwrap(), F64, regs::vreg(5)); } - fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) { + fn match_reg_arg(abi_arg: &ABIArg, expected_ty: WasmType, expected_reg: Reg) { match abi_arg { &ABIArg::Reg { reg, ty } => { assert_eq!(reg, expected_reg); @@ -222,7 +231,7 @@ mod tests { } } - fn match_stack_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_offset: u32) { + fn match_stack_arg(abi_arg: &ABIArg, expected_ty: WasmType, expected_offset: u32) { match abi_arg { &ABIArg::Stack { offset, ty } => { assert_eq!(offset, expected_offset); diff --git a/winch/codegen/src/isa/aarch64/mod.rs b/winch/codegen/src/isa/aarch64/mod.rs index 64b02a27508f..8d299eba0a93 100644 --- a/winch/codegen/src/isa/aarch64/mod.rs +++ b/winch/codegen/src/isa/aarch64/mod.rs @@ -16,8 +16,8 @@ use cranelift_codegen::{isa::aarch64::settings as aarch64_settings, Final, MachB use cranelift_codegen::{MachTextSectionBuilder, TextSectionBuilder}; use masm::MacroAssembler as Aarch64Masm; use target_lexicon::Triple; -use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; -use wasmtime_environ::ModuleTranslation; +use wasmparser::{FuncValidator, FunctionBody, ValidatorResources}; +use wasmtime_environ::{ModuleTranslation, WasmFuncType}; mod abi; mod address; @@ -84,7 +84,7 @@ impl TargetIsa for Aarch64 { fn compile_function( &self, - sig: &FuncType, + sig: &WasmFuncType, body: &FunctionBody, translation: &ModuleTranslation, validator: &mut FuncValidator, @@ -94,7 +94,7 @@ impl TargetIsa for Aarch64 { let stack = Stack::new(); let abi_sig = abi::Aarch64ABI::sig(sig, &CallingConvention::Default); - let defined_locals = DefinedLocals::new(&mut body, validator)?; + let defined_locals = DefinedLocals::new(translation, &mut body, validator)?; let frame = Frame::new::(&abi_sig, &defined_locals)?; // TODO: Add floating point bitmask let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), scratch()); @@ -119,7 +119,7 @@ impl TargetIsa for Aarch64 { fn compile_trampoline( &self, - _ty: &FuncType, + _ty: &WasmFuncType, _kind: TrampolineKind, ) -> Result> { todo!() diff --git a/winch/codegen/src/isa/mod.rs b/winch/codegen/src/isa/mod.rs index 9db772f10bb8..1943a5c2f6df 100644 --- a/winch/codegen/src/isa/mod.rs +++ b/winch/codegen/src/isa/mod.rs @@ -9,8 +9,8 @@ use std::{ fmt::{self, Debug, Display}, }; use target_lexicon::{Architecture, Triple}; -use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; -use wasmtime_environ::ModuleTranslation; +use wasmparser::{FuncValidator, FunctionBody, ValidatorResources}; +use wasmtime_environ::{ModuleTranslation, WasmFuncType}; #[cfg(feature = "x64")] pub(crate) mod x64; @@ -147,7 +147,7 @@ pub trait TargetIsa: Send + Sync { /// Compile a function. fn compile_function( &self, - sig: &FuncType, + sig: &WasmFuncType, body: &FunctionBody, translation: &ModuleTranslation, validator: &mut FuncValidator, @@ -192,7 +192,7 @@ pub trait TargetIsa: Send + Sync { /// depending on the `kind` paramter. fn compile_trampoline( &self, - ty: &FuncType, + ty: &WasmFuncType, kind: TrampolineKind, ) -> Result>; diff --git a/winch/codegen/src/isa/x64/abi.rs b/winch/codegen/src/isa/x64/abi.rs index bc4ddd6e6a89..bce4c758f222 100644 --- a/winch/codegen/src/isa/x64/abi.rs +++ b/winch/codegen/src/isa/x64/abi.rs @@ -4,7 +4,7 @@ use crate::{ isa::{reg::Reg, CallingConvention}, }; use smallvec::SmallVec; -use wasmparser::{FuncType, ValType}; +use wasmtime_environ::{WasmFuncType, WasmType}; /// Helper environment to track argument-register /// assignment in x64. @@ -96,10 +96,10 @@ impl ABI for X64ABI { 64 } - fn sig(wasm_sig: &FuncType, call_conv: &CallingConvention) -> ABISig { + fn sig(wasm_sig: &WasmFuncType, call_conv: &CallingConvention) -> ABISig { assert!(call_conv.is_fastcall() || call_conv.is_systemv() || call_conv.is_default()); - if wasm_sig.results().len() > 1 { + if wasm_sig.returns().len() > 1 { panic!("multi-value not supported"); } @@ -120,7 +120,7 @@ impl ABI for X64ABI { .map(|arg| Self::to_abi_arg(arg, &mut stack_offset, &mut index_env, is_fastcall)) .collect(); - let ty = wasm_sig.results().get(0).map(|e| e.clone()); + let ty = wasm_sig.returns().get(0).map(|e| e.clone()); // The `Default`, `WasmtimeFastcall` and `WasmtimeSystemV use `rax`. // NOTE This should be updated when supporting multi-value. let reg = regs::rax(); @@ -152,17 +152,17 @@ impl ABI for X64ABI { impl X64ABI { fn to_abi_arg( - wasm_arg: &ValType, + wasm_arg: &WasmType, stack_offset: &mut u32, index_env: &mut RegIndexEnv, fastcall: bool, ) -> ABIArg { let (reg, ty) = match wasm_arg { - ty @ (ValType::I32 | ValType::I64) => { + ty @ (WasmType::I32 | WasmType::I64) => { (Self::int_reg_for(index_env.next_gpr(), fastcall), ty) } - ty @ (ValType::F32 | ValType::F64) => { + ty @ (WasmType::F32 | WasmType::F64) => { (Self::float_reg_for(index_env.next_fpr(), fastcall), ty) } @@ -223,9 +223,9 @@ mod tests { isa::x64::regs, isa::CallingConvention, }; - use wasmparser::{ - FuncType, - ValType::{self, *}, + use wasmtime_environ::{ + WasmFuncType, + WasmType::{self, *}, }; #[test] @@ -250,7 +250,8 @@ mod tests { #[test] fn int_abi_sig() { - let wasm_sig = FuncType::new([I32, I64, I32, I64, I32, I32, I64, I32], []); + let wasm_sig = + WasmFuncType::new([I32, I64, I32, I64, I32, I32, I64, I32].into(), [].into()); let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -267,7 +268,10 @@ mod tests { #[test] fn float_abi_sig() { - let wasm_sig = FuncType::new([F32, F64, F32, F64, F32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, F64, F32, F64, F32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -285,7 +289,10 @@ mod tests { #[test] fn mixed_abi_sig() { - let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = X64ABI::sig(&wasm_sig, &CallingConvention::Default); let params = sig.params; @@ -303,7 +310,10 @@ mod tests { #[test] fn system_v_call_conv() { - let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WasmtimeSystemV); let params = sig.params; @@ -321,7 +331,10 @@ mod tests { #[test] fn fastcall_call_conv() { - let wasm_sig = FuncType::new([F32, I32, I64, F64, I32, F32, F64, F32, F64], []); + let wasm_sig = WasmFuncType::new( + [F32, I32, I64, F64, I32, F32, F64, F32, F64].into(), + [].into(), + ); let sig = X64ABI::sig(&wasm_sig, &CallingConvention::WasmtimeFastcall); let params = sig.params; @@ -334,7 +347,7 @@ mod tests { match_stack_arg(params.get(5).unwrap(), F32, 40); } - fn match_reg_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_reg: Reg) { + fn match_reg_arg(abi_arg: &ABIArg, expected_ty: WasmType, expected_reg: Reg) { match abi_arg { &ABIArg::Reg { reg, ty } => { assert_eq!(reg, expected_reg); @@ -344,7 +357,7 @@ mod tests { } } - fn match_stack_arg(abi_arg: &ABIArg, expected_ty: ValType, expected_offset: u32) { + fn match_stack_arg(abi_arg: &ABIArg, expected_ty: WasmType, expected_offset: u32) { match abi_arg { &ABIArg::Stack { offset, ty } => { assert_eq!(offset, expected_offset); diff --git a/winch/codegen/src/isa/x64/mod.rs b/winch/codegen/src/isa/x64/mod.rs index 3ce61f1b9dce..67199850efad 100644 --- a/winch/codegen/src/isa/x64/mod.rs +++ b/winch/codegen/src/isa/x64/mod.rs @@ -8,18 +8,18 @@ use crate::isa::{x64::masm::MacroAssembler as X64Masm, CallingConvention}; use crate::masm::MacroAssembler; use crate::regalloc::RegAlloc; use crate::stack::Stack; +use crate::trampoline::{Trampoline, TrampolineKind}; use crate::{ isa::{Builder, TargetIsa}, regset::RegSet, }; -use crate::{Trampoline, TrampolineKind}; use anyhow::Result; use cranelift_codegen::settings::{self, Flags}; use cranelift_codegen::{isa::x64::settings as x64_settings, Final, MachBufferFinalized}; use cranelift_codegen::{MachTextSectionBuilder, TextSectionBuilder}; use target_lexicon::Triple; -use wasmparser::{FuncType, FuncValidator, FunctionBody, ValidatorResources}; -use wasmtime_environ::ModuleTranslation; +use wasmparser::{FuncValidator, FunctionBody, ValidatorResources}; +use wasmtime_environ::{ModuleTranslation, WasmFuncType}; use self::regs::ALL_GPR; @@ -88,7 +88,7 @@ impl TargetIsa for X64 { fn compile_function( &self, - sig: &FuncType, + sig: &WasmFuncType, body: &FunctionBody, translation: &ModuleTranslation, validator: &mut FuncValidator, @@ -98,7 +98,7 @@ impl TargetIsa for X64 { let stack = Stack::new(); let abi_sig = abi::X64ABI::sig(sig, &CallingConvention::Default); - let defined_locals = DefinedLocals::new(&mut body, validator)?; + let defined_locals = DefinedLocals::new(translation, &mut body, validator)?; let frame = Frame::new::(&abi_sig, &defined_locals)?; // TODO Add in floating point bitmask let regalloc = RegAlloc::new(RegSet::new(ALL_GPR, 0), regs::scratch()); @@ -122,7 +122,7 @@ impl TargetIsa for X64 { fn compile_trampoline( &self, - ty: &FuncType, + ty: &WasmFuncType, kind: TrampolineKind, ) -> Result> { use TrampolineKind::*; diff --git a/winch/codegen/src/lib.rs b/winch/codegen/src/lib.rs index 2805de89c649..f68aa5ab3a2a 100644 --- a/winch/codegen/src/lib.rs +++ b/winch/codegen/src/lib.rs @@ -18,5 +18,4 @@ mod regset; mod stack; mod trampoline; pub use trampoline::TrampolineKind; -use trampoline::*; mod visitor; diff --git a/winch/codegen/src/trampoline.rs b/winch/codegen/src/trampoline.rs index 52cea8af8bfc..a276dbd75127 100644 --- a/winch/codegen/src/trampoline.rs +++ b/winch/codegen/src/trampoline.rs @@ -18,8 +18,7 @@ use crate::{ use anyhow::{anyhow, Result}; use smallvec::SmallVec; use std::mem; -use wasmparser::{FuncType, ValType}; -use wasmtime_environ::{FuncIndex, PtrSize}; +use wasmtime_environ::{FuncIndex, PtrSize, WasmFuncType, WasmType}; /// The supported trampoline kinds. /// See https://github.com/bytecodealliance/rfcs/blob/main/accepted/tail-calls.md#new-trampolines-and-vmcallercheckedanyfunc-changes @@ -81,10 +80,10 @@ where } /// Emit an array-to-wasm trampoline. - pub fn emit_array_to_wasm(&mut self, ty: &FuncType, callee_index: FuncIndex) -> Result<()> { - let native_ty = FuncType::new( - vec![ValType::I64, ValType::I64, ValType::I64, ValType::I64], - vec![], + pub fn emit_array_to_wasm(&mut self, ty: &WasmFuncType, callee_index: FuncIndex) -> Result<()> { + let native_ty = WasmFuncType::new( + [WasmType::I64, WasmType::I64, WasmType::I64, WasmType::I64].into(), + [].into(), ); let native_sig = self.native_sig(&native_ty); @@ -158,7 +157,11 @@ where } /// Emit a native-to-wasm trampoline. - pub fn emit_native_to_wasm(&mut self, ty: &FuncType, callee_index: FuncIndex) -> Result<()> { + pub fn emit_native_to_wasm( + &mut self, + ty: &WasmFuncType, + callee_index: FuncIndex, + ) -> Result<()> { let native_sig = self.native_sig(&ty); let wasm_sig = self.wasm_sig(&ty); let (vmctx, caller_vmctx) = Self::callee_and_caller_vmctx(&native_sig.params)?; @@ -203,11 +206,11 @@ where } /// Emit a wasm-to-native trampoline. - pub fn emit_wasm_to_native(&mut self, ty: &FuncType) -> Result<()> { + pub fn emit_wasm_to_native(&mut self, ty: &WasmFuncType) -> Result<()> { let mut params = Self::callee_and_caller_vmctx_types(); params.extend_from_slice(ty.params()); - let func_ty = FuncType::new(params, ty.results().to_owned()); + let func_ty = WasmFuncType::new(params.into(), ty.returns().into()); let wasm_sig = self.wasm_sig(&func_ty); let native_sig = self.native_sig(ty); @@ -323,21 +326,21 @@ where } /// Get the type of the caller and callee VM contexts. - fn callee_and_caller_vmctx_types() -> Vec { - vec![ValType::I64, ValType::I64] + fn callee_and_caller_vmctx_types() -> Vec { + vec![WasmType::I64, WasmType::I64] } /// Returns a signature using the system's calling convention. - fn native_sig(&self, ty: &FuncType) -> ABISig { + fn native_sig(&self, ty: &WasmFuncType) -> ABISig { let mut params = Self::callee_and_caller_vmctx_types(); params.extend_from_slice(ty.params()); - let native_type = FuncType::new(params, ty.results().to_owned()); + let native_type = WasmFuncType::new(params.into(), ty.returns().into()); ::sig(&native_type, self.call_conv) } /// Returns a signature using the Winch's default calling convention. - fn wasm_sig(&self, ty: &FuncType) -> ABISig { + fn wasm_sig(&self, ty: &WasmFuncType) -> ABISig { ::sig(ty, &CallingConvention::Default) } diff --git a/winch/codegen/src/visitor.rs b/winch/codegen/src/visitor.rs index c686f4ed5fb2..a5a02707d901 100644 --- a/winch/codegen/src/visitor.rs +++ b/winch/codegen/src/visitor.rs @@ -7,9 +7,8 @@ use crate::codegen::CodeGen; use crate::masm::{DivKind, MacroAssembler, OperandSize, RegImm, RemKind}; use crate::stack::Val; -use wasmparser::ValType; use wasmparser::VisitOperator; -use wasmtime_environ::FuncIndex; +use wasmtime_environ::{FuncIndex, WasmType}; /// A macro to define unsupported WebAssembly operators. /// @@ -178,7 +177,7 @@ where .get_local(index) .expect(&format!("valid local at slot = {}", index)); match slot.ty { - ValType::I32 | ValType::I64 => context.stack.push(Val::local(index)), + WasmType::I32 | WasmType::I64 => context.stack.push(Val::local(index)), _ => panic!("Unsupported type {:?} for local", slot.ty), } } @@ -206,11 +205,11 @@ where wasmparser::for_each_operator!(def_unsupported); } -impl From for OperandSize { - fn from(ty: ValType) -> OperandSize { +impl From for OperandSize { + fn from(ty: WasmType) -> OperandSize { match ty { - ValType::I32 => OperandSize::S32, - ValType::I64 => OperandSize::S64, + WasmType::I32 => OperandSize::S32, + WasmType::I64 => OperandSize::S64, ty => todo!("unsupported type {:?}", ty), } } diff --git a/winch/filetests/src/lib.rs b/winch/filetests/src/lib.rs index 54ea934d3fe3..4645870c1116 100644 --- a/winch/filetests/src/lib.rs +++ b/winch/filetests/src/lib.rs @@ -12,7 +12,7 @@ mod test { use wasmtime_environ::ModuleTranslation; use wasmtime_environ::{ wasmparser::{Parser as WasmParser, Validator}, - DefinedFuncIndex, FunctionBodyData, ModuleEnvironment, Tunables, + DefinedFuncIndex, FunctionBodyData, ModuleEnvironment, Tunables, TypeConvert, }; use winch_codegen::{lookup, TargetIsa}; use winch_test_macros::generate_file_tests; @@ -153,6 +153,7 @@ mod test { let sig = types .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); + let sig = translation.module.convert_func_type(&sig); let FunctionBodyData { body, validator } = f.1; let mut validator = validator.into_validator(Default::default()); diff --git a/winch/src/compile.rs b/winch/src/compile.rs index 143ca38f340f..154d537913e4 100644 --- a/winch/src/compile.rs +++ b/winch/src/compile.rs @@ -6,6 +6,7 @@ use target_lexicon::Triple; use wasmtime_environ::{ wasmparser::{Parser as WasmParser, Validator}, DefinedFuncIndex, FunctionBodyData, ModuleEnvironment, ModuleTranslation, Tunables, + TypeConvert, }; use winch_codegen::{lookup, TargetIsa}; use winch_filetests::disasm::disasm; @@ -55,6 +56,7 @@ fn compile( let sig = types .function_at(index.as_u32()) .expect(&format!("function type at index {:?}", index.as_u32())); + let sig = translation.module.convert_func_type(sig); let FunctionBodyData { body, validator } = f.1; let mut validator = validator.into_validator(Default::default()); let buffer = isa