From c1aa81e3e38655bed7753d550f86ba981af2787a Mon Sep 17 00:00:00 2001 From: Alex Crichton Date: Thu, 18 Jun 2020 10:50:18 -0500 Subject: [PATCH] Initial implementation of module linking (#26) * Initial implementation of module linking This commit is the initial implementation of the module linking proposal in the three tooling crates of this repository. Unfortunately this is just one massive commit which isn't really able to be reviewed in a nuanced way. I wanted to bundle everything up though because implementing each of the features of the module linking proposal ended up having a pretty major impact on at least the text parser. The main focus of this commit is getting the text parser to a "pretty complete" point. I suspect there's still various odds and ends remaining, but I believe it's almost entirely up to date with the current state of the proposal. The `wasmprinter` crate should be in good shape but I'm pretty certian that `wasmparser` is still missing at least some pieces of validation. This is hoped to be a good unit of work to start from after which I can focus more on a complete implementation in `wasmparser` in terms of validation. From there I think we'll have a good suite of tests and can start moving on towards an implementation in an engine and/or implementation in toolchains. The largest changes here are to the text parser, and it's important to note that the text parser at least is ideally pretty low-impact in terms of risky changes. We've got a pretty good regression test suite, and the interface of the text parser is "given this string give me the binary wasm". Coupled with the fact that the text parser is 100% safe code I'm pretty certain that the only bad pieces which can arise are panics, which fuzzers in theory should find relatively quickly. * Tidy up `item_for` method with `Ns` Also replace a "wut" string with an actual namespace description. * FIx wasmpaser examples * Check in more `*.dump` files * Fix custom section annotations * Fix a doc comment in wasmparser * Address some review comments --- crates/wasmparser/examples/dump.rs | 2 +- crates/wasmparser/examples/simple.rs | 8 +- crates/wasmparser/src/binary_reader.rs | 92 +- crates/wasmparser/src/lib.rs | 11 + crates/wasmparser/src/limits.rs | 6 +- crates/wasmparser/src/module_resources.rs | 27 +- crates/wasmparser/src/operators_validator.rs | 166 +- crates/wasmparser/src/parser.rs | 142 +- crates/wasmparser/src/primitives.rs | 59 +- .../wasmparser/src/readers/alias_section.rs | 84 + .../wasmparser/src/readers/import_section.rs | 19 +- .../src/readers/instance_section.rs | 147 ++ crates/wasmparser/src/readers/mod.rs | 15 +- crates/wasmparser/src/readers/module.rs | 85 +- .../src/readers/module_code_section.rs | 93 + .../wasmparser/src/readers/module_section.rs | 55 + crates/wasmparser/src/readers/name_section.rs | 16 + crates/wasmparser/src/readers/type_section.rs | 23 +- crates/wasmparser/src/validator.rs | 595 ++++- crates/wasmprinter/src/lib.rs | 462 +++- crates/wast/src/ast/alias.rs | 43 + crates/wast/src/ast/custom.rs | 53 +- crates/wast/src/ast/event.rs | 4 +- crates/wast/src/ast/export.rs | 92 +- crates/wast/src/ast/expr.rs | 23 +- crates/wast/src/ast/func.rs | 23 +- crates/wast/src/ast/global.rs | 15 +- crates/wast/src/ast/import.rs | 193 +- crates/wast/src/ast/instance.rs | 69 + crates/wast/src/ast/memory.rs | 45 +- crates/wast/src/ast/mod.rs | 8 + crates/wast/src/ast/module.rs | 19 +- crates/wast/src/ast/nested_module.rs | 112 + crates/wast/src/ast/table.rs | 21 +- crates/wast/src/ast/token.rs | 80 +- crates/wast/src/ast/types.rs | 274 ++- crates/wast/src/binary.rs | 278 ++- crates/wast/src/parser.rs | 15 + .../src/resolve/deinline_import_export.rs | 274 +++ crates/wast/src/resolve/expand.rs | 853 +++++-- crates/wast/src/resolve/gensym.rs | 20 + crates/wast/src/resolve/mod.rs | 168 +- crates/wast/src/resolve/names.rs | 2153 +++++++++++++++-- crates/wast/src/resolve/tyexpand.rs | 132 - src/bin/wasm-validate-rs.rs | 6 + tests/dump.rs | 156 +- tests/dump/alias.wat | 9 + tests/dump/alias.wat.dump | 36 + tests/dump/alias2.wat | 28 + tests/dump/alias2.wat.dump | 85 + tests/dump/bundled.wat | 32 + tests/dump/bundled.wat.dump | 152 ++ tests/dump/export-all.wat | 6 + tests/dump/export-all.wat.dump | 18 + tests/dump/import-modules.wat | 9 + tests/dump/import-modules.wat.dump | 21 + tests/dump/instance-expand.wat | 7 + tests/dump/instance-expand.wat.dump | 11 + tests/dump/instance-type.wat | 7 + tests/dump/instance-type.wat.dump | 10 + tests/dump/instance-type2.wat | 6 + tests/dump/instance-type2.wat.dump | 10 + tests/dump/instantiate.wat | 21 + tests/dump/instantiate.wat.dump | 50 + tests/dump/instantiate2.wat | 4 + tests/dump/instantiate2.wat.dump | 15 + tests/dump/module-linking.wat | 28 + tests/dump/module-linking.wat.dump | 85 + tests/dump/module-types.wat | 11 + tests/dump/module-types.wat.dump | 21 + tests/dump/names.wat | 3 + tests/dump/names.wat.dump | 28 + tests/dump/nested-module.wat | 26 + tests/dump/nested-module.wat.dump | 90 + tests/dump/select.wat.dump | 2 +- tests/dump/simple.wat.dump | 6 +- tests/local/gc-ref.wat | 3 + tests/local/module-linking/alias.wast | 476 ++++ tests/local/module-linking/export-all.wast | 67 + tests/local/module-linking/import.wast | 31 + tests/local/module-linking/infer-types.wast | 60 + tests/local/module-linking/instance-type.wast | 200 ++ tests/local/module-linking/instantiate.wast | 251 ++ tests/local/module-linking/module-link.wast | 65 + .../local/module-linking/nested-modules.wast | 31 + tests/local/module-linking/single-import.wat | 10 + tests/local/module-linking/virtualize.wast | 80 + tests/roundtrip.rs | 24 +- 88 files changed, 8015 insertions(+), 1356 deletions(-) create mode 100644 crates/wasmparser/src/readers/alias_section.rs create mode 100644 crates/wasmparser/src/readers/instance_section.rs create mode 100644 crates/wasmparser/src/readers/module_code_section.rs create mode 100644 crates/wasmparser/src/readers/module_section.rs create mode 100644 crates/wast/src/ast/alias.rs create mode 100644 crates/wast/src/ast/instance.rs create mode 100644 crates/wast/src/ast/nested_module.rs create mode 100644 crates/wast/src/resolve/deinline_import_export.rs create mode 100644 crates/wast/src/resolve/gensym.rs delete mode 100644 crates/wast/src/resolve/tyexpand.rs create mode 100644 tests/dump/alias.wat create mode 100644 tests/dump/alias.wat.dump create mode 100644 tests/dump/alias2.wat create mode 100644 tests/dump/alias2.wat.dump create mode 100644 tests/dump/bundled.wat create mode 100644 tests/dump/bundled.wat.dump create mode 100644 tests/dump/export-all.wat create mode 100644 tests/dump/export-all.wat.dump create mode 100644 tests/dump/import-modules.wat create mode 100644 tests/dump/import-modules.wat.dump create mode 100644 tests/dump/instance-expand.wat create mode 100644 tests/dump/instance-expand.wat.dump create mode 100644 tests/dump/instance-type.wat create mode 100644 tests/dump/instance-type.wat.dump create mode 100644 tests/dump/instance-type2.wat create mode 100644 tests/dump/instance-type2.wat.dump create mode 100644 tests/dump/instantiate.wat create mode 100644 tests/dump/instantiate.wat.dump create mode 100644 tests/dump/instantiate2.wat create mode 100644 tests/dump/instantiate2.wat.dump create mode 100644 tests/dump/module-linking.wat create mode 100644 tests/dump/module-linking.wat.dump create mode 100644 tests/dump/module-types.wat create mode 100644 tests/dump/module-types.wat.dump create mode 100644 tests/dump/names.wat create mode 100644 tests/dump/names.wat.dump create mode 100644 tests/dump/nested-module.wat create mode 100644 tests/dump/nested-module.wat.dump create mode 100644 tests/local/module-linking/alias.wast create mode 100644 tests/local/module-linking/export-all.wast create mode 100644 tests/local/module-linking/import.wast create mode 100644 tests/local/module-linking/infer-types.wast create mode 100644 tests/local/module-linking/instance-type.wast create mode 100644 tests/local/module-linking/instantiate.wast create mode 100644 tests/local/module-linking/module-link.wast create mode 100644 tests/local/module-linking/nested-modules.wast create mode 100644 tests/local/module-linking/single-import.wat create mode 100644 tests/local/module-linking/virtualize.wast diff --git a/crates/wasmparser/examples/dump.rs b/crates/wasmparser/examples/dump.rs index d651e3c19f..af3b75cd9a 100644 --- a/crates/wasmparser/examples/dump.rs +++ b/crates/wasmparser/examples/dump.rs @@ -38,7 +38,7 @@ fn main() { ref ty, } => { println!( - "ImportSectionEntry {{ module: \"{}\", field: \"{}\", ty: {:?} }}", + "ImportSectionEntry {{ module: \"{}\", field: {:?}, ty: {:?} }}", module, field, ty ); } diff --git a/crates/wasmparser/examples/simple.rs b/crates/wasmparser/examples/simple.rs index 8843957fda..70dee2af99 100644 --- a/crates/wasmparser/examples/simple.rs +++ b/crates/wasmparser/examples/simple.rs @@ -29,9 +29,11 @@ fn main() { } => { println!(" Export {} {:?}", field, kind); } - ParserState::ImportSectionEntry { module, field, .. } => { - println!(" Import {}::{}", module, field) - } + ParserState::ImportSectionEntry { + module, + field: Some(field), + .. + } => println!(" Import {}::{}", module, field), ParserState::EndWasm => break, ParserState::Error(ref err) => panic!("Error: {:?}", err), _ => ( /* println!(" Other {:?}", state); */ ), diff --git a/crates/wasmparser/src/binary_reader.rs b/crates/wasmparser/src/binary_reader.rs index 1489146b26..2bcd35891d 100644 --- a/crates/wasmparser/src/binary_reader.rs +++ b/crates/wasmparser/src/binary_reader.rs @@ -18,16 +18,14 @@ use std::convert::TryInto; use std::str; use std::vec::Vec; -use crate::limits::{ - MAX_WASM_FUNCTION_LOCALS, MAX_WASM_FUNCTION_PARAMS, MAX_WASM_FUNCTION_RETURNS, - MAX_WASM_FUNCTION_SIZE, MAX_WASM_STRING_SIZE, -}; +use crate::limits::*; use crate::primitives::{ BinaryReaderError, BrTable, CustomSectionKind, ExternalKind, FuncType, GlobalType, Ieee32, Ieee64, LinkingType, MemoryImmediate, MemoryType, NameType, Operator, RelocType, ResizableLimits, Result, SIMDLaneIndex, SectionCode, TableType, Type, TypeOrFuncType, V128, }; +use crate::{ExportType, Import, ImportSectionEntryType, InstanceType, ModuleType}; const MAX_WASM_BR_TABLE_SIZE: usize = MAX_WASM_FUNCTION_SIZE; @@ -43,6 +41,7 @@ const WASM_MAGIC_NUMBER: &[u8; 4] = b"\0asm"; const WASM_EXPERIMENTAL_VERSION: u32 = 0xd; const WASM_SUPPORTED_VERSION: u32 = 0x1; +#[derive(Clone)] pub(crate) struct SectionHeader<'a> { pub code: SectionCode<'a>, pub payload_start: usize, @@ -235,6 +234,9 @@ impl<'a> BinaryReader<'a> { 1 => Ok(ExternalKind::Table), 2 => Ok(ExternalKind::Memory), 3 => Ok(ExternalKind::Global), + 5 => Ok(ExternalKind::Module), + 6 => Ok(ExternalKind::Instance), + 7 => Ok(ExternalKind::Type), _ => Err(BinaryReaderError::new( "Invalid external kind", self.original_position() - 1, @@ -243,13 +245,6 @@ impl<'a> BinaryReader<'a> { } pub(crate) fn read_func_type(&mut self) -> Result { - if self.read_type()? != Type::Func { - return Err(BinaryReaderError::new( - "type signature is not a func", - self.original_position() - 1, - )); - } - let params_len = self.read_var_u32()? as usize; if params_len > MAX_WASM_FUNCTION_PARAMS { return Err(BinaryReaderError::new( @@ -278,6 +273,77 @@ impl<'a> BinaryReader<'a> { }) } + pub(crate) fn read_module_type(&mut self) -> Result> { + let pos = self.original_position(); + let imports_len = self.read_var_u32()? as usize; + if imports_len > MAX_WASM_IMPORTS { + return Err(BinaryReaderError::new("imports size is out of bounds", pos)); + } + Ok(ModuleType { + imports: (0..imports_len) + .map(|_| self.read_import()) + .collect::>()?, + exports: self.read_export_types()?, + }) + } + + pub(crate) fn read_instance_type(&mut self) -> Result> { + Ok(InstanceType { + exports: self.read_export_types()?, + }) + } + + fn read_export_types(&mut self) -> Result]>> { + let pos = self.original_position(); + let exports_len = self.read_var_u32()? as usize; + if exports_len > MAX_WASM_EXPORTS { + return Err(BinaryReaderError::new("exports size is out of bound", pos)); + } + (0..exports_len).map(|_| self.read_export_type()).collect() + } + + pub(crate) fn read_import(&mut self) -> Result> { + let module = self.read_string()?; + + // For the `field`, figure out if we're the experimental encoding of + // single-level imports for the module linking proposal (a single-byte + // string which is 0xc0, which is invalid utf-8) or if we have a second + // level of import. + let mut clone = self.clone(); + let field = if clone.read_var_u32()? == 1 && clone.read_u8()? == 0xc0 { + *self = clone; + None + } else { + Some(self.read_string()?) + }; + + let ty = self.read_import_desc()?; + Ok(Import { module, field, ty }) + } + + pub(crate) fn read_export_type(&mut self) -> Result> { + let name = self.read_string()?; + let ty = self.read_import_desc()?; + Ok(ExportType { name, ty }) + } + + pub(crate) fn read_import_desc(&mut self) -> Result { + Ok(match self.read_external_kind()? { + ExternalKind::Function => ImportSectionEntryType::Function(self.read_var_u32()?), + ExternalKind::Table => ImportSectionEntryType::Table(self.read_table_type()?), + ExternalKind::Memory => ImportSectionEntryType::Memory(self.read_memory_type()?), + ExternalKind::Global => ImportSectionEntryType::Global(self.read_global_type()?), + ExternalKind::Module => ImportSectionEntryType::Module(self.read_var_u32()?), + ExternalKind::Instance => ImportSectionEntryType::Instance(self.read_var_u32()?), + ExternalKind::Type => { + return Err(BinaryReaderError::new( + "cannot import types", + self.original_position() - 1, + )) + } + }) + } + fn read_resizable_limits(&mut self, max_present: bool) -> Result { let initial = self.read_var_u32()?; let maximum = if max_present { @@ -362,6 +428,10 @@ impl<'a> BinaryReader<'a> { 10 => Ok(SectionCode::Code), 11 => Ok(SectionCode::Data), 12 => Ok(SectionCode::DataCount), + 100 => Ok(SectionCode::Module), + 101 => Ok(SectionCode::Instance), + 102 => Ok(SectionCode::Alias), + 103 => Ok(SectionCode::ModuleCode), _ => Err(BinaryReaderError::new("Invalid section code", offset)), } } diff --git a/crates/wasmparser/src/lib.rs b/crates/wasmparser/src/lib.rs index e581b932da..b40210873b 100644 --- a/crates/wasmparser/src/lib.rs +++ b/crates/wasmparser/src/lib.rs @@ -39,15 +39,18 @@ pub use crate::parser::WasmDecoder; pub use crate::primitives::BinaryReaderError; pub use crate::primitives::BrTable; pub use crate::primitives::CustomSectionKind; +pub use crate::primitives::ExportType; pub use crate::primitives::ExternalKind; pub use crate::primitives::FuncType; pub use crate::primitives::GlobalType; pub use crate::primitives::Ieee32; pub use crate::primitives::Ieee64; pub use crate::primitives::ImportSectionEntryType; +pub use crate::primitives::InstanceType; pub use crate::primitives::LinkingType; pub use crate::primitives::MemoryImmediate; pub use crate::primitives::MemoryType; +pub use crate::primitives::ModuleType; pub use crate::primitives::NameType; pub use crate::primitives::Naming; pub use crate::primitives::Operator; @@ -57,6 +60,7 @@ pub use crate::primitives::Result; pub use crate::primitives::SectionCode; pub use crate::primitives::TableType; pub use crate::primitives::Type; +pub use crate::primitives::TypeDef; pub use crate::primitives::TypeOrFuncType; pub use crate::primitives::V128; @@ -72,11 +76,15 @@ pub use crate::module_resources::WasmMemoryType; pub use crate::module_resources::WasmModuleResources; pub use crate::module_resources::WasmTableType; pub use crate::module_resources::WasmType; +pub use crate::module_resources::WasmTypeDef; pub(crate) use crate::module_resources::{wasm_func_type_inputs, wasm_func_type_outputs}; pub use crate::operators_validator::OperatorValidatorConfig; +pub use crate::readers::Alias; +pub use crate::readers::AliasSectionReader; +pub use crate::readers::AliasedInstance; pub use crate::readers::CodeSectionReader; pub use crate::readers::CustomSectionContent; pub use crate::readers::Data; @@ -97,10 +105,13 @@ pub use crate::readers::GlobalSectionReader; pub use crate::readers::Import; pub use crate::readers::ImportSectionReader; pub use crate::readers::InitExpr; +pub use crate::readers::InstanceSectionReader; pub use crate::readers::LinkingSectionReader; pub use crate::readers::LocalsReader; pub use crate::readers::MemorySectionReader; +pub use crate::readers::ModuleCodeSectionReader; pub use crate::readers::ModuleReader; +pub use crate::readers::ModuleSectionReader; pub use crate::readers::Name; pub use crate::readers::NameSectionReader; pub use crate::readers::NamingReader; diff --git a/crates/wasmparser/src/limits.rs b/crates/wasmparser/src/limits.rs index 38594a6362..e0307d14f6 100644 --- a/crates/wasmparser/src/limits.rs +++ b/crates/wasmparser/src/limits.rs @@ -17,8 +17,8 @@ // The limits are agreed upon with other engines for consistency. pub const MAX_WASM_TYPES: usize = 1_000_000; pub const MAX_WASM_FUNCTIONS: usize = 1_000_000; -pub const _MAX_WASM_IMPORTS: usize = 100_000; -pub const _MAX_WASM_EXPORTS: usize = 100_000; +pub const MAX_WASM_IMPORTS: usize = 100_000; +pub const MAX_WASM_EXPORTS: usize = 100_000; pub const MAX_WASM_GLOBALS: usize = 1_000_000; pub const _MAX_WASM_DATA_SEGMENTS: usize = 100_000; pub const MAX_WASM_MEMORY_PAGES: usize = 65536; @@ -32,3 +32,5 @@ pub const _MAX_WASM_TABLE_SIZE: usize = 10_000_000; pub const MAX_WASM_TABLE_ENTRIES: usize = 10_000_000; pub const MAX_WASM_TABLES: usize = 1; pub const MAX_WASM_MEMORIES: usize = 1; +pub const MAX_WASM_MODULES: usize = 1_000; +pub const MAX_WASM_INSTANCES: usize = 1_000; diff --git a/crates/wasmparser/src/module_resources.rs b/crates/wasmparser/src/module_resources.rs index d75eaae338..5f83835a73 100644 --- a/crates/wasmparser/src/module_resources.rs +++ b/crates/wasmparser/src/module_resources.rs @@ -13,6 +13,8 @@ * limitations under the License. */ +use crate::{FuncType, TypeDef}; + /// Types that qualify as Wasm types for validation purposes. /// /// Must be comparable with `wasmparser` given Wasm types and @@ -26,6 +28,12 @@ pub trait WasmType: PartialEq + PartialEq + Eq { fn to_parser_type(&self) -> crate::Type; } +pub trait WasmTypeDef { + type FuncType: WasmFuncType; + + fn as_func(&self) -> Option<&Self::FuncType>; +} + /// Types that qualify as Wasm function types for validation purposes. pub trait WasmFuncType { /// A type that is comparable with Wasm types. @@ -277,7 +285,7 @@ pub trait WasmGlobalType { /// the need of an additional parsing or validation step or copying data around. pub trait WasmModuleResources { /// The function type used for validation. - type FuncType: WasmFuncType; + type TypeDef: WasmTypeDef; /// The table type used for validation. type TableType: WasmTableType; /// The memory type used for validation. @@ -286,7 +294,7 @@ pub trait WasmModuleResources { type GlobalType: WasmGlobalType; /// Returns the type at given index. - fn type_at(&self, at: u32) -> Option<&Self::FuncType>; + fn type_at(&self, at: u32) -> Option<&Self::TypeDef>; /// Returns the table at given index if any. fn table_at(&self, at: u32) -> Option<&Self::TableType>; /// Returns the linear memory at given index. @@ -311,12 +319,12 @@ impl WasmModuleResources for &'_ T where T: ?Sized + WasmModuleResources, { - type FuncType = T::FuncType; + type TypeDef = T::TypeDef; type TableType = T::TableType; type MemoryType = T::MemoryType; type GlobalType = T::GlobalType; - fn type_at(&self, at: u32) -> Option<&Self::FuncType> { + fn type_at(&self, at: u32) -> Option<&Self::TypeDef> { T::type_at(self, at) } fn table_at(&self, at: u32) -> Option<&Self::TableType> { @@ -352,6 +360,17 @@ impl WasmType for crate::Type { } } +impl<'a> WasmTypeDef for TypeDef<'a> { + type FuncType = FuncType; + + fn as_func(&self) -> Option<&Self::FuncType> { + match self { + TypeDef::Func(f) => Some(f), + _ => None, + } + } +} + impl WasmFuncType for crate::FuncType { type Type = crate::Type; diff --git a/crates/wasmparser/src/operators_validator.rs b/crates/wasmparser/src/operators_validator.rs index 214bf91930..ad16a6926a 100644 --- a/crates/wasmparser/src/operators_validator.rs +++ b/crates/wasmparser/src/operators_validator.rs @@ -18,7 +18,7 @@ use std::cmp::min; use crate::primitives::{MemoryImmediate, Operator, SIMDLaneIndex, Type, TypeOrFuncType}; use crate::{ wasm_func_type_inputs, wasm_func_type_outputs, BinaryReaderError, WasmFuncType, WasmGlobalType, - WasmMemoryType, WasmModuleResources, WasmTableType, WasmType, + WasmModuleResources, WasmTableType, WasmType, WasmTypeDef, }; #[derive(Debug)] @@ -113,28 +113,17 @@ impl FuncState { } Ok(()) } - fn push_block( + fn push_block( &mut self, ty: TypeOrFuncType, block_type: BlockType, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, ) -> OperatorValidatorResult<()> { let (start_types, return_types) = match ty { TypeOrFuncType::Type(Type::EmptyBlockType) => (vec![], vec![]), TypeOrFuncType::Type(ty) => (vec![], vec![ty]), TypeOrFuncType::FuncType(idx) => { - let ty = resources - .type_at(idx) - // Note: This was an out-of-bounds memory access before - // the change to return `Option` at `type_at`. So - // I assumed that invalid indices at this point are - // bugs. - .expect("function type index is out of bounds"); + let ty = func_type_at(&resources, idx)?; ( wasm_func_type_inputs(ty) .map(WasmType::to_parser_type) @@ -328,6 +317,7 @@ pub struct OperatorValidatorConfig { pub enable_bulk_memory: bool, pub enable_multi_value: bool, pub enable_tail_call: bool, + pub enable_module_linking: bool, #[cfg(feature = "deterministic")] pub deterministic_only: bool, @@ -341,6 +331,7 @@ pub(crate) const DEFAULT_OPERATOR_VALIDATOR_CONFIG: OperatorValidatorConfig = enable_bulk_memory: false, enable_multi_value: true, enable_tail_call: false, + enable_module_linking: false, #[cfg(feature = "deterministic")] deterministic_only: true, @@ -541,9 +532,7 @@ impl OperatorValidator { ); } }; - let ty = resources - .type_at(type_index) - .expect("function type index is out of bounds"); + let ty = func_type_at(&resources, type_index)?; self.check_operands(wasm_func_type_inputs(ty).map(WasmType::to_parser_type))?; self.func_state.change_frame_with_types( ty.len_inputs(), @@ -563,26 +552,18 @@ impl OperatorValidator { "unknown table: table index out of bounds", )); } - match resources.type_at(index) { - None => { - return Err(OperatorValidatorError::new( - "unknown type: type index out of bounds", - )) - } - Some(ty) => { - let types = { - let mut types = Vec::with_capacity(ty.len_inputs() + 1); - types.extend(wasm_func_type_inputs(ty).map(WasmType::to_parser_type)); - types.push(Type::I32); - types - }; - self.check_operands(types.into_iter())?; - self.func_state.change_frame_with_types( - ty.len_inputs() + 1, - wasm_func_type_outputs(ty).map(WasmType::to_parser_type), - )?; - } - } + let ty = func_type_at(&resources, index)?; + let types = { + let mut types = Vec::with_capacity(ty.len_inputs() + 1); + types.extend(wasm_func_type_inputs(ty).map(WasmType::to_parser_type)); + types.push(Type::I32); + types + }; + self.check_operands(types.into_iter())?; + self.func_state.change_frame_with_types( + ty.len_inputs() + 1, + wasm_func_type_outputs(ty).map(WasmType::to_parser_type), + )?; Ok(()) } @@ -674,20 +655,10 @@ impl OperatorValidator { Ok(()) } - fn check_memory_index< - F: WasmFuncType, - T: WasmTableType, - M: WasmMemoryType, - G: WasmGlobalType, - >( + fn check_memory_index( &self, memory_index: u32, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, ) -> OperatorValidatorResult<()> { if resources.memory_at(memory_index).is_none() { bail_op_err!("unknown memory {}", memory_index); @@ -695,16 +666,11 @@ impl OperatorValidator { Ok(()) } - fn check_memarg( + fn check_memarg( &self, memarg: MemoryImmediate, max_align: u32, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, ) -> OperatorValidatorResult<()> { self.check_memory_index(0, resources)?; let align = memarg.flags; @@ -766,20 +732,10 @@ impl OperatorValidator { Ok(()) } - fn check_shared_memarg_wo_align< - F: WasmFuncType, - T: WasmTableType, - M: WasmMemoryType, - G: WasmGlobalType, - >( + fn check_shared_memarg_wo_align( &self, _: MemoryImmediate, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, ) -> OperatorValidatorResult<()> { self.check_memory_index(0, resources)?; Ok(()) @@ -792,15 +748,10 @@ impl OperatorValidator { Ok(()) } - fn check_block_type( + fn check_block_type( &self, ty: TypeOrFuncType, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, ) -> OperatorValidatorResult<()> { match ty { TypeOrFuncType::Type(Type::EmptyBlockType) @@ -812,9 +763,9 @@ impl OperatorValidator { self.check_reference_types_enabled() } TypeOrFuncType::Type(Type::V128) => self.check_simd_enabled(), - TypeOrFuncType::FuncType(idx) => match resources.type_at(idx) { - None => Err(OperatorValidatorError::new("type index out of bounds")), - Some(ty) if !self.config.enable_multi_value => { + TypeOrFuncType::FuncType(idx) => { + let ty = func_type_at(&resources, idx)?; + if !self.config.enable_multi_value { if ty.len_outputs() > 1 { return Err(OperatorValidatorError::new( "blocks, loops, and ifs may only return at most one \ @@ -827,38 +778,21 @@ impl OperatorValidator { when multi-value is not enabled", )); } - Ok(()) } - Some(_) => Ok(()), - }, + Ok(()) + } _ => Err(OperatorValidatorError::new("invalid block return type")), } } - fn check_block_params< - F: WasmFuncType, - T: WasmTableType, - M: WasmMemoryType, - G: WasmGlobalType, - >( + fn check_block_params( &self, ty: TypeOrFuncType, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, skip: usize, ) -> OperatorValidatorResult<()> { if let TypeOrFuncType::FuncType(idx) = ty { - let func_ty = resources - .type_at(idx) - // Note: This was an out-of-bounds memory access before - // the change to return `Option` at `type_at`. So - // I assumed that invalid indices at this point are - // bugs. - .expect("function type index is out of bounds"); + let func_ty = func_type_at(&resources, idx)?; let len = func_ty.len_inputs(); self.check_frame_size(len + skip)?; for (i, ty) in wasm_func_type_inputs(func_ty).enumerate() { @@ -908,20 +842,10 @@ impl OperatorValidator { Ok(Some(ty)) } - pub(crate) fn process_operator< - F: WasmFuncType, - T: WasmTableType, - M: WasmMemoryType, - G: WasmGlobalType, - >( + pub(crate) fn process_operator( &mut self, operator: &Operator, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: &impl WasmModuleResources, ) -> OperatorValidatorResult { if self.func_state.end_function { return Err(OperatorValidatorError::new("unexpected operator")); @@ -2099,3 +2023,19 @@ impl OperatorValidator { Ok(()) } } + +fn func_type_at( + resources: &T, + at: u32, +) -> OperatorValidatorResult<&::FuncType> { + let ty = match resources.type_at(at) { + Some(ty) => ty, + None => { + return Err(OperatorValidatorError::new( + "unknown type: type index out of bounds", + )) + } + }; + ty.as_func() + .ok_or_else(|| OperatorValidatorError::new("type index not a function type")) +} diff --git a/crates/wasmparser/src/parser.rs b/crates/wasmparser/src/parser.rs index f8ef88d9a0..2263bf22ba 100644 --- a/crates/wasmparser/src/parser.rs +++ b/crates/wasmparser/src/parser.rs @@ -22,19 +22,12 @@ use crate::limits::{ }; use crate::primitives::{ - BinaryReaderError, CustomSectionKind, ExternalKind, FuncType, GlobalType, - ImportSectionEntryType, LinkingType, MemoryType, Naming, Operator, RelocType, Result, - SectionCode, TableType, Type, + BinaryReaderError, CustomSectionKind, ExternalKind, GlobalType, ImportSectionEntryType, + LinkingType, MemoryType, Naming, Operator, RelocType, Result, SectionCode, TableType, Type, + TypeDef, }; -use crate::readers::{ - CodeSectionReader, Data, DataKind, DataSectionReader, Element, ElementItem, ElementItems, - ElementKind, ElementSectionReader, Export, ExportSectionReader, FunctionBody, - FunctionSectionReader, Global, GlobalSectionReader, Import, ImportSectionReader, - LinkingSectionReader, MemorySectionReader, ModuleReader, Name, NameSectionReader, NamingReader, - OperatorsReader, Reloc, RelocSectionReader, Section, SectionReader, TableSectionReader, - TypeSectionReader, -}; +use crate::readers::*; use crate::binary_reader::{BinaryReader, Range}; @@ -85,10 +78,10 @@ pub enum ParserState<'a> { ReadingSectionRawData, SectionRawData(&'a [u8]), - TypeSectionEntry(FuncType), + TypeSectionEntry(TypeDef<'a>), ImportSectionEntry { module: &'a str, - field: &'a str, + field: Option<&'a str>, ty: ImportSectionEntryType, }, FunctionSectionEntry(u32), @@ -140,6 +133,19 @@ pub enum ParserState<'a> { LinkingSectionEntry(LinkingType), SourceMappingURL(&'a str), + + ModuleSectionEntry(u32), + AliasSectionEntry(Alias), + BeginInstantiate { + module: u32, + count: u32, + }, + InstantiateParameter { + kind: ExternalKind, + index: u32, + }, + EndInstantiate, + InlineModule(ModuleCode<'a>), } #[derive(Debug, Copy, Clone)] @@ -183,6 +189,10 @@ enum ParserSectionReader<'a> { NameSectionReader(NameSectionReader<'a>), LinkingSectionReader(LinkingSectionReader<'a>), RelocSectionReader(RelocSectionReader<'a>), + ModuleSectionReader(ModuleSectionReader<'a>), + AliasSectionReader(AliasSectionReader<'a>), + InstanceSectionReader(InstanceSectionReader<'a>), + ModuleCodeSectionReader(ModuleCodeSectionReader<'a>), } macro_rules! section_reader { @@ -221,6 +231,7 @@ pub struct Parser<'a> { current_data_segment: Option<&'a [u8]>, binary_reader: Option>, operators_reader: Option>, + instance_args: Option>, section_entries_left: u32, } @@ -247,6 +258,7 @@ impl<'a> Parser<'a> { current_data_segment: None, binary_reader: None, operators_reader: None, + instance_args: None, section_entries_left: 0, } } @@ -681,6 +693,63 @@ impl<'a> Parser<'a> { Ok(()) } + fn read_module_entry(&mut self) -> Result<()> { + if self.section_entries_left == 0 { + return self.check_section_end(); + } + let module_ty = section_reader!(self, ModuleSectionReader).read()?; + self.state = ParserState::ModuleSectionEntry(module_ty); + self.section_entries_left -= 1; + Ok(()) + } + + fn read_alias_entry(&mut self) -> Result<()> { + if self.section_entries_left == 0 { + return self.check_section_end(); + } + let alias_ty = section_reader!(self, AliasSectionReader).read()?; + self.state = ParserState::AliasSectionEntry(alias_ty); + self.section_entries_left -= 1; + Ok(()) + } + + fn read_instance_entry(&mut self) -> Result<()> { + if self.section_entries_left == 0 { + return self.check_section_end(); + } + let instance = section_reader!(self, InstanceSectionReader).read()?; + let args = instance.args()?; + self.state = ParserState::BeginInstantiate { + module: instance.module(), + count: args.get_count(), + }; + self.instance_args = Some(args); + self.section_entries_left -= 1; + Ok(()) + } + + fn read_instantiate_field(&mut self) -> Result<()> { + let instance = self.instance_args.as_mut().unwrap(); + if instance.eof() { + self.instance_args = None; + self.state = ParserState::EndInstantiate; + } else { + let (kind, index) = self.instance_args.as_mut().unwrap().read()?; + self.state = ParserState::InstantiateParameter { kind, index }; + } + Ok(()) + } + + fn read_module_code_entry(&mut self) -> Result<()> { + if self.section_entries_left == 0 { + return self.check_section_end(); + } + let module = section_reader!(self, ModuleCodeSectionReader).read()?; + self.state = ParserState::InlineModule(module); + self.section_entries_left -= 1; + Ok(()) + } + fn read_section_body(&mut self) -> Result<()> { match self.state { ParserState::BeginSection { @@ -775,6 +844,38 @@ impl<'a> Parser<'a> { .get_data_count_section_content()?; self.state = ParserState::DataCountSectionEntry(func_index); } + ParserState::BeginSection { + code: SectionCode::Module, + .. + } => { + start_section_reader!(self, ModuleSectionReader, get_module_section_reader); + self.read_module_entry()?; + } + ParserState::BeginSection { + code: SectionCode::Alias, + .. + } => { + start_section_reader!(self, AliasSectionReader, get_alias_section_reader); + self.read_alias_entry()?; + } + ParserState::BeginSection { + code: SectionCode::Instance, + .. + } => { + start_section_reader!(self, InstanceSectionReader, get_instance_section_reader); + self.read_instance_entry()?; + } + ParserState::BeginSection { + code: SectionCode::ModuleCode, + .. + } => { + start_section_reader!( + self, + ModuleCodeSectionReader, + get_module_code_section_reader + ); + self.read_module_code_entry()?; + } ParserState::BeginSection { code: SectionCode::Custom { .. }, .. @@ -849,6 +950,10 @@ impl<'a> Parser<'a> { ParserSectionReader::TypeSectionReader(ref reader) => reader.ensure_end()?, ParserSectionReader::LinkingSectionReader(ref reader) => reader.ensure_end()?, ParserSectionReader::RelocSectionReader(ref reader) => reader.ensure_end()?, + ParserSectionReader::ModuleSectionReader(ref reader) => reader.ensure_end()?, + ParserSectionReader::ModuleCodeSectionReader(ref reader) => reader.ensure_end()?, + ParserSectionReader::InstanceSectionReader(ref reader) => reader.ensure_end()?, + ParserSectionReader::AliasSectionReader(ref reader) => reader.ensure_end()?, _ => unreachable!(), } self.position_to_section_end() @@ -984,6 +1089,13 @@ impl<'a> Parser<'a> { ParserState::ReadingSectionRawData | ParserState::SectionRawData(_) => { self.read_section_body_bytes()? } + ParserState::ModuleSectionEntry(_) => self.read_module_entry()?, + ParserState::AliasSectionEntry(_) => self.read_alias_entry()?, + ParserState::BeginInstantiate { .. } | ParserState::InstantiateParameter { .. } => { + self.read_instantiate_field()? + } + ParserState::EndInstantiate => self.read_instance_entry()?, + ParserState::InlineModule(_) => self.read_module_code_entry()?, } Ok(()) } @@ -1085,7 +1197,7 @@ impl<'a> WasmDecoder<'a> for Parser<'a> { /// # 0x80, 0x80, 0x0, 0x0, 0xa, 0x91, 0x80, 0x80, 0x80, 0x0, /// # 0x2, 0x83, 0x80, 0x80, 0x80, 0x0, 0x0, 0x1, 0xb, 0x83, /// # 0x80, 0x80, 0x80, 0x0, 0x0, 0x0, 0xb]; - /// use wasmparser::{WasmDecoder, Parser, ParserState}; + /// use wasmparser::{WasmDecoder, Parser, ParserState, TypeDef}; /// let mut parser = Parser::new(data); /// let mut types = Vec::new(); /// let mut function_types = Vec::new(); @@ -1094,7 +1206,7 @@ impl<'a> WasmDecoder<'a> for Parser<'a> { /// match parser.read() { /// ParserState::Error(_) | /// ParserState::EndWasm => break, - /// ParserState::TypeSectionEntry(ty) => { + /// ParserState::TypeSectionEntry(TypeDef::Func(ty)) => { /// types.push(ty.clone()); /// } /// ParserState::FunctionSectionEntry(id) => { diff --git a/crates/wasmparser/src/primitives.rs b/crates/wasmparser/src/primitives.rs index 995825348f..08fa1fb7e0 100644 --- a/crates/wasmparser/src/primitives.rs +++ b/crates/wasmparser/src/primitives.rs @@ -83,18 +83,22 @@ pub enum SectionCode<'a> { name: &'a str, kind: CustomSectionKind, }, - Type, // Function signature declarations - Import, // Import declarations - Function, // Function declarations - Table, // Indirect function table and other tables - Memory, // Memory attributes - Global, // Global declarations - Export, // Exports - Start, // Start function declaration - Element, // Elements section - Code, // Function bodies (code) - Data, // Data segments - DataCount, // Count of passive data segments + Type, // Function signature declarations + Alias, // Aliased indices from nested/parent modules + Import, // Import declarations + Module, // Module declarations + Instance, // Instance definitions + Function, // Function declarations + Table, // Indirect function table and other tables + Memory, // Memory attributes + Global, // Global declarations + Export, // Exports + Start, // Start function declaration + Element, // Elements section + ModuleCode, // Module definitions + Code, // Function bodies (code) + Data, // Data segments + DataCount, // Count of passive data segments } /// Types as defined [here]. @@ -135,6 +139,16 @@ pub enum ExternalKind { Table, Memory, Global, + Type, + Module, + Instance, +} + +#[derive(Debug, Clone)] +pub enum TypeDef<'a> { + Func(FuncType), + Instance(InstanceType<'a>), + Module(ModuleType<'a>), } #[derive(Debug, Clone, Eq, PartialEq, Hash)] @@ -143,6 +157,23 @@ pub struct FuncType { pub returns: Box<[Type]>, } +#[derive(Debug, Clone)] +pub struct InstanceType<'a> { + pub exports: Box<[ExportType<'a>]>, +} + +#[derive(Debug, Clone)] +pub struct ModuleType<'a> { + pub imports: Box<[crate::Import<'a>]>, + pub exports: Box<[ExportType<'a>]>, +} + +#[derive(Debug, Clone)] +pub struct ExportType<'a> { + pub name: &'a str, + pub ty: ImportSectionEntryType, +} + #[derive(Debug, Copy, Clone)] pub struct ResizableLimits { pub initial: u32, @@ -161,7 +192,7 @@ pub struct MemoryType { pub shared: bool, } -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct GlobalType { pub content_type: Type, pub mutable: bool, @@ -173,6 +204,8 @@ pub enum ImportSectionEntryType { Table(TableType), Memory(MemoryType), Global(GlobalType), + Module(u32), + Instance(u32), } #[derive(Debug, Copy, Clone)] diff --git a/crates/wasmparser/src/readers/alias_section.rs b/crates/wasmparser/src/readers/alias_section.rs new file mode 100644 index 0000000000..f98d2f1a05 --- /dev/null +++ b/crates/wasmparser/src/readers/alias_section.rs @@ -0,0 +1,84 @@ +use crate::{ + BinaryReader, BinaryReaderError, ExternalKind, Result, SectionIteratorLimited, SectionReader, + SectionWithLimitedItems, +}; + +pub struct AliasSectionReader<'a> { + reader: BinaryReader<'a>, + count: u32, +} + +#[derive(Debug)] +pub struct Alias { + pub instance: AliasedInstance, + pub kind: ExternalKind, + pub index: u32, +} + +#[derive(Debug)] +pub enum AliasedInstance { + Parent, + Child(u32), +} + +impl<'a> AliasSectionReader<'a> { + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let count = reader.read_var_u32()?; + Ok(AliasSectionReader { reader, count }) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn get_count(&self) -> u32 { + self.count + } + + pub fn read(&mut self) -> Result { + Ok(Alias { + instance: match self.reader.read_u8()? { + 0x00 => AliasedInstance::Child(self.reader.read_var_u32()?), + 0x01 => AliasedInstance::Parent, + _ => { + return Err(BinaryReaderError::new( + "invalid byte in alias", + self.original_position() - 1, + )) + } + }, + kind: self.reader.read_external_kind()?, + index: self.reader.read_var_u32()?, + }) + } +} + +impl<'a> SectionReader for AliasSectionReader<'a> { + type Item = Alias; + + fn read(&mut self) -> Result { + AliasSectionReader::read(self) + } + fn eof(&self) -> bool { + self.reader.eof() + } + fn original_position(&self) -> usize { + AliasSectionReader::original_position(self) + } +} + +impl<'a> SectionWithLimitedItems for AliasSectionReader<'a> { + fn get_count(&self) -> u32 { + AliasSectionReader::get_count(self) + } +} + +impl<'a> IntoIterator for AliasSectionReader<'a> { + type Item = Result; + type IntoIter = SectionIteratorLimited>; + + fn into_iter(self) -> Self::IntoIter { + SectionIteratorLimited::new(self) + } +} diff --git a/crates/wasmparser/src/readers/import_section.rs b/crates/wasmparser/src/readers/import_section.rs index 92fd5f5e99..68e1ff58c0 100644 --- a/crates/wasmparser/src/readers/import_section.rs +++ b/crates/wasmparser/src/readers/import_section.rs @@ -13,15 +13,15 @@ * limitations under the License. */ -use super::{ - BinaryReader, ExternalKind, ImportSectionEntryType, Result, SectionIteratorLimited, - SectionReader, SectionWithLimitedItems, +use crate::{ + BinaryReader, ImportSectionEntryType, Result, SectionIteratorLimited, SectionReader, + SectionWithLimitedItems, }; #[derive(Debug, Copy, Clone)] pub struct Import<'a> { pub module: &'a str, - pub field: &'a str, + pub field: Option<&'a str>, pub ty: ImportSectionEntryType, } @@ -67,16 +67,7 @@ impl<'a> ImportSectionReader<'a> { where 'a: 'b, { - let module = self.reader.read_string()?; - let field = self.reader.read_string()?; - let kind = self.reader.read_external_kind()?; - let ty = match kind { - ExternalKind::Function => ImportSectionEntryType::Function(self.reader.read_var_u32()?), - ExternalKind::Table => ImportSectionEntryType::Table(self.reader.read_table_type()?), - ExternalKind::Memory => ImportSectionEntryType::Memory(self.reader.read_memory_type()?), - ExternalKind::Global => ImportSectionEntryType::Global(self.reader.read_global_type()?), - }; - Ok(Import { module, field, ty }) + self.reader.read_import() } } diff --git a/crates/wasmparser/src/readers/instance_section.rs b/crates/wasmparser/src/readers/instance_section.rs new file mode 100644 index 0000000000..c7ac301c54 --- /dev/null +++ b/crates/wasmparser/src/readers/instance_section.rs @@ -0,0 +1,147 @@ +use crate::{ + BinaryReader, ExternalKind, Result, SectionIteratorLimited, SectionReader, + SectionWithLimitedItems, +}; + +pub struct InstanceSectionReader<'a> { + reader: BinaryReader<'a>, + count: u32, +} + +impl<'a> InstanceSectionReader<'a> { + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let count = reader.read_var_u32()?; + Ok(InstanceSectionReader { reader, count }) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn get_count(&self) -> u32 { + self.count + } + + pub fn read(&mut self) -> Result> { + let instance = Instance::new( + &self.reader.buffer[self.reader.position..], + self.original_position(), + )?; + self.reader.skip_var_32()?; + let count = self.reader.read_var_u32()?; + for _ in 0..count { + self.reader.skip_bytes(1)?; + self.reader.skip_var_32()?; + } + Ok(instance) + } +} + +impl<'a> SectionReader for InstanceSectionReader<'a> { + type Item = Instance<'a>; + + fn read(&mut self) -> Result { + InstanceSectionReader::read(self) + } + fn eof(&self) -> bool { + self.reader.eof() + } + fn original_position(&self) -> usize { + InstanceSectionReader::original_position(self) + } +} + +impl<'a> SectionWithLimitedItems for InstanceSectionReader<'a> { + fn get_count(&self) -> u32 { + InstanceSectionReader::get_count(self) + } +} + +impl<'a> IntoIterator for InstanceSectionReader<'a> { + type Item = Result>; + type IntoIter = SectionIteratorLimited>; + + fn into_iter(self) -> Self::IntoIter { + SectionIteratorLimited::new(self) + } +} + +pub struct Instance<'a> { + reader: BinaryReader<'a>, + module: u32, +} + +impl<'a> Instance<'a> { + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let module = reader.read_var_u32()?; + Ok(Instance { module, reader }) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn module(&self) -> u32 { + self.module + } + + pub fn args(&self) -> Result> { + let mut reader = self.reader.clone(); + let count = reader.read_var_u32()?; + Ok(InstanceArgsReader { + count, + remaining: count, + reader, + }) + } +} + +pub struct InstanceArgsReader<'a> { + reader: BinaryReader<'a>, + count: u32, + remaining: u32, +} + +impl<'a> InstanceArgsReader<'a> { + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn read(&mut self) -> Result<(ExternalKind, u32)> { + let kind = self.reader.read_external_kind()?; + let index = self.reader.read_var_u32()?; + self.remaining -= 1; + Ok((kind, index)) + } +} + +impl<'a> SectionReader for InstanceArgsReader<'a> { + type Item = (ExternalKind, u32); + + fn read(&mut self) -> Result { + InstanceArgsReader::read(self) + } + fn eof(&self) -> bool { + self.remaining == 0 + } + fn original_position(&self) -> usize { + InstanceArgsReader::original_position(self) + } +} + +impl<'a> SectionWithLimitedItems for InstanceArgsReader<'a> { + fn get_count(&self) -> u32 { + self.count + } +} + +impl<'a> IntoIterator for InstanceArgsReader<'a> { + type Item = Result<(ExternalKind, u32)>; + type IntoIter = SectionIteratorLimited>; + + fn into_iter(self) -> Self::IntoIter { + SectionIteratorLimited::new(self) + } +} diff --git a/crates/wasmparser/src/readers/mod.rs b/crates/wasmparser/src/readers/mod.rs index 6eca50d9ff..78d9e0c5be 100644 --- a/crates/wasmparser/src/readers/mod.rs +++ b/crates/wasmparser/src/readers/mod.rs @@ -14,9 +14,8 @@ */ use super::{ - BinaryReader, BinaryReaderError, CustomSectionKind, ExternalKind, FuncType, GlobalType, - ImportSectionEntryType, LinkingType, MemoryType, NameType, Naming, Operator, Range, RelocType, - Result, SectionCode, TableType, Type, + BinaryReader, BinaryReaderError, CustomSectionKind, ExternalKind, GlobalType, LinkingType, + MemoryType, NameType, Naming, Operator, Range, RelocType, Result, SectionCode, TableType, Type, }; use super::SectionHeader; @@ -56,6 +55,7 @@ pub use self::section_reader::SectionIteratorLimited; pub use self::section_reader::SectionReader; pub use self::section_reader::SectionWithLimitedItems; +pub use self::name_section::FunctionLocalReader; pub use self::name_section::FunctionName; pub use self::name_section::LocalName; pub use self::name_section::ModuleName; @@ -77,6 +77,12 @@ use self::sourcemappingurl_section::read_sourcemappingurl_section_content; pub use self::operators::OperatorsReader; +pub use self::alias_section::*; +pub use self::instance_section::*; +pub use self::module_code_section::*; +pub use self::module_section::*; + +mod alias_section; mod code_section; mod data_count_section; mod data_section; @@ -86,9 +92,12 @@ mod function_section; mod global_section; mod import_section; mod init_expr; +mod instance_section; mod linking_section; mod memory_section; mod module; +mod module_code_section; +mod module_section; mod name_section; mod operators; mod producers_section; diff --git a/crates/wasmparser/src/readers/module.rs b/crates/wasmparser/src/readers/module.rs index dd6499a443..6e8418a7c3 100644 --- a/crates/wasmparser/src/readers/module.rs +++ b/crates/wasmparser/src/readers/module.rs @@ -13,15 +13,18 @@ * limitations under the License. */ +use std::fmt; + use super::{ BinaryReader, BinaryReaderError, CustomSectionKind, Range, Result, SectionCode, SectionHeader, }; use super::{ read_data_count_section_content, read_sourcemappingurl_section_content, - read_start_section_content, CodeSectionReader, DataSectionReader, ElementSectionReader, - ExportSectionReader, FunctionSectionReader, GlobalSectionReader, ImportSectionReader, - LinkingSectionReader, MemorySectionReader, NameSectionReader, ProducersSectionReader, + read_start_section_content, AliasSectionReader, CodeSectionReader, DataSectionReader, + ElementSectionReader, ExportSectionReader, FunctionSectionReader, GlobalSectionReader, + ImportSectionReader, InstanceSectionReader, LinkingSectionReader, MemorySectionReader, + ModuleCodeSectionReader, ModuleSectionReader, NameSectionReader, ProducersSectionReader, RelocSectionReader, TableSectionReader, TypeSectionReader, }; @@ -232,6 +235,46 @@ impl<'a> Section<'a> { } } + pub fn get_module_section_reader<'b>(&self) -> Result> + where + 'a: 'b, + { + match self.code { + SectionCode::Module => ModuleSectionReader::new(self.data, self.offset), + _ => panic!("Invalid state for get_module_section_reader"), + } + } + + pub fn get_alias_section_reader<'b>(&self) -> Result> + where + 'a: 'b, + { + match self.code { + SectionCode::Alias => AliasSectionReader::new(self.data, self.offset), + _ => panic!("Invalid state for get_alias_section_reader"), + } + } + + pub fn get_instance_section_reader<'b>(&self) -> Result> + where + 'a: 'b, + { + match self.code { + SectionCode::Instance => InstanceSectionReader::new(self.data, self.offset), + _ => panic!("Invalid state for get_instance_section_reader"), + } + } + + pub fn get_module_code_section_reader<'b>(&self) -> Result> + where + 'a: 'b, + { + match self.code { + SectionCode::ModuleCode => ModuleCodeSectionReader::new(self.data, self.offset), + _ => panic!("Invalid state for get_module_code_section_reader"), + } + } + pub fn get_binary_reader<'b>(&self) -> BinaryReader<'b> where 'a: 'b, @@ -239,6 +282,10 @@ impl<'a> Section<'a> { BinaryReader::new_with_offset(self.data, self.offset) } + pub fn content_raw(&self) -> &'a [u8] { + self.data + } + pub fn range(&self) -> Range { Range { start: self.offset, @@ -262,6 +309,12 @@ impl<'a> Section<'a> { SectionCode::Table => SectionContent::Table(self.get_table_section_reader()?), SectionCode::Element => SectionContent::Element(self.get_element_section_reader()?), SectionCode::Start => SectionContent::Start(self.get_start_section_content()?), + SectionCode::Module => SectionContent::Module(self.get_module_section_reader()?), + SectionCode::Alias => SectionContent::Alias(self.get_alias_section_reader()?), + SectionCode::Instance => SectionContent::Instance(self.get_instance_section_reader()?), + SectionCode::ModuleCode => { + SectionContent::ModuleCode(self.get_module_code_section_reader()?) + } SectionCode::DataCount => { SectionContent::DataCount(self.get_data_count_section_content()?) } @@ -323,6 +376,10 @@ pub enum SectionContent<'a> { binary: BinaryReader<'a>, content: Option>, }, + Module(ModuleSectionReader<'a>), + Alias(AliasSectionReader<'a>), + Instance(InstanceSectionReader<'a>), + ModuleCode(ModuleCodeSectionReader<'a>), } pub enum CustomSectionContent<'a> { @@ -334,6 +391,7 @@ pub enum CustomSectionContent<'a> { } /// Reads top-level WebAssembly file structure: header and sections. +#[derive(Clone)] pub struct ModuleReader<'a> { reader: BinaryReader<'a>, version: u32, @@ -342,7 +400,11 @@ pub struct ModuleReader<'a> { impl<'a> ModuleReader<'a> { pub fn new(data: &[u8]) -> Result { - let mut reader = BinaryReader::new(data); + ModuleReader::new_with_offset(data, 0) + } + + pub(crate) fn new_with_offset(data: &[u8], offset: usize) -> Result { + let mut reader = BinaryReader::new_with_offset(data, offset); let version = reader.read_file_header()?; Ok(ModuleReader { reader, @@ -362,6 +424,10 @@ impl<'a> ModuleReader<'a> { } } + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + pub fn eof(&self) -> bool { self.read_ahead.is_none() && self.reader.eof() } @@ -412,11 +478,12 @@ impl<'a> ModuleReader<'a> { }; let payload_end = payload_start + payload_len; self.verify_section_end(payload_end)?; + let offset = self.reader.original_position(); let body_start = self.reader.position; self.reader.skip_to(payload_end); Ok(Section { code, - offset: body_start, + offset, data: &self.reader.buffer[body_start..payload_end], }) } @@ -481,6 +548,14 @@ impl<'a> IntoIterator for ModuleReader<'a> { } } +impl<'a> fmt::Debug for ModuleReader<'a> { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("ModuleReader") + .field("version", &self.version) + .finish() + } +} + pub struct ModuleIterator<'a> { reader: ModuleReader<'a>, err: bool, diff --git a/crates/wasmparser/src/readers/module_code_section.rs b/crates/wasmparser/src/readers/module_code_section.rs new file mode 100644 index 0000000000..cb13b6b959 --- /dev/null +++ b/crates/wasmparser/src/readers/module_code_section.rs @@ -0,0 +1,93 @@ +use crate::{ + BinaryReader, BinaryReaderError, ModuleReader, Result, SectionIteratorLimited, SectionReader, + SectionWithLimitedItems, +}; + +pub struct ModuleCodeSectionReader<'a> { + reader: BinaryReader<'a>, + count: u32, +} + +#[derive(Debug)] +pub struct ModuleCode<'a> { + reader: BinaryReader<'a>, +} + +impl<'a> ModuleCodeSectionReader<'a> { + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let count = reader.read_var_u32()?; + Ok(ModuleCodeSectionReader { reader, count }) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn get_count(&self) -> u32 { + self.count + } + + fn verify_module_end(&self, end: usize) -> Result<()> { + if self.reader.buffer.len() < end { + return Err(BinaryReaderError::new( + "module body extends past end of the module code section", + self.reader.original_offset + self.reader.buffer.len(), + )); + } + Ok(()) + } + + pub fn read(&mut self) -> Result> { + let size = self.reader.read_var_u32()? as usize; + let module_start = self.reader.position; + let module_end = module_start + size; + self.verify_module_end(module_end)?; + self.reader.skip_to(module_end); + Ok(ModuleCode { + reader: BinaryReader::new_with_offset( + &self.reader.buffer[module_start..module_end], + self.reader.original_offset + module_start, + ), + }) + } +} + +impl<'a> SectionReader for ModuleCodeSectionReader<'a> { + type Item = ModuleCode<'a>; + + fn read(&mut self) -> Result { + ModuleCodeSectionReader::read(self) + } + fn eof(&self) -> bool { + self.reader.eof() + } + fn original_position(&self) -> usize { + ModuleCodeSectionReader::original_position(self) + } +} + +impl<'a> SectionWithLimitedItems for ModuleCodeSectionReader<'a> { + fn get_count(&self) -> u32 { + ModuleCodeSectionReader::get_count(self) + } +} + +impl<'a> IntoIterator for ModuleCodeSectionReader<'a> { + type Item = Result>; + type IntoIter = SectionIteratorLimited>; + + fn into_iter(self) -> Self::IntoIter { + SectionIteratorLimited::new(self) + } +} + +impl<'a> ModuleCode<'a> { + pub fn module(&self) -> Result> { + ModuleReader::new_with_offset(self.reader.buffer, self.reader.original_position()) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } +} diff --git a/crates/wasmparser/src/readers/module_section.rs b/crates/wasmparser/src/readers/module_section.rs new file mode 100644 index 0000000000..e17f30f4dd --- /dev/null +++ b/crates/wasmparser/src/readers/module_section.rs @@ -0,0 +1,55 @@ +use super::{BinaryReader, Result, SectionIteratorLimited, SectionReader, SectionWithLimitedItems}; + +pub struct ModuleSectionReader<'a> { + reader: BinaryReader<'a>, + count: u32, +} + +impl<'a> ModuleSectionReader<'a> { + pub fn new(data: &'a [u8], offset: usize) -> Result> { + let mut reader = BinaryReader::new_with_offset(data, offset); + let count = reader.read_var_u32()?; + Ok(ModuleSectionReader { reader, count }) + } + + pub fn original_position(&self) -> usize { + self.reader.original_position() + } + + pub fn get_count(&self) -> u32 { + self.count + } + + pub fn read(&mut self) -> Result { + self.reader.read_var_u32() + } +} + +impl<'a> SectionReader for ModuleSectionReader<'a> { + type Item = u32; + + fn read(&mut self) -> Result { + ModuleSectionReader::read(self) + } + fn eof(&self) -> bool { + self.reader.eof() + } + fn original_position(&self) -> usize { + ModuleSectionReader::original_position(self) + } +} + +impl<'a> SectionWithLimitedItems for ModuleSectionReader<'a> { + fn get_count(&self) -> u32 { + ModuleSectionReader::get_count(self) + } +} + +impl<'a> IntoIterator for ModuleSectionReader<'a> { + type Item = Result; + type IntoIter = SectionIteratorLimited>; + + fn into_iter(self) -> Self::IntoIter { + SectionIteratorLimited::new(self) + } +} diff --git a/crates/wasmparser/src/readers/name_section.rs b/crates/wasmparser/src/readers/name_section.rs index 9ecaff9930..9f7a6fc536 100644 --- a/crates/wasmparser/src/readers/name_section.rs +++ b/crates/wasmparser/src/readers/name_section.rs @@ -31,6 +31,10 @@ impl<'a> ModuleName<'a> { let mut reader = BinaryReader::new_with_offset(self.data, self.offset); reader.read_string() } + + pub fn original_position(&self) -> usize { + self.offset + } } pub struct NamingReader<'a> { @@ -85,6 +89,10 @@ impl<'a> FunctionName<'a> { { NamingReader::new(self.data, self.offset) } + + pub fn original_position(&self) -> usize { + self.offset + } } #[derive(Debug, Copy, Clone)] @@ -101,6 +109,10 @@ impl<'a> FunctionLocalName<'a> { { NamingReader::new(self.data, self.offset) } + + pub fn original_position(&self) -> usize { + self.offset + } } pub struct FunctionLocalReader<'a> { @@ -152,6 +164,10 @@ impl<'a> LocalName<'a> { { FunctionLocalReader::new(self.data, self.offset) } + + pub fn original_position(&self) -> usize { + self.offset + } } #[derive(Debug, Copy, Clone)] diff --git a/crates/wasmparser/src/readers/type_section.rs b/crates/wasmparser/src/readers/type_section.rs index 51fd6eea36..1fa288b21b 100644 --- a/crates/wasmparser/src/readers/type_section.rs +++ b/crates/wasmparser/src/readers/type_section.rs @@ -13,8 +13,9 @@ * limitations under the License. */ -use super::{ - BinaryReader, FuncType, Result, SectionIteratorLimited, SectionReader, SectionWithLimitedItems, +use crate::{ + BinaryReader, BinaryReaderError, Result, SectionIteratorLimited, SectionReader, + SectionWithLimitedItems, TypeDef, }; pub struct TypeSectionReader<'a> { @@ -53,13 +54,23 @@ impl<'a> TypeSectionReader<'a> { /// println!("Type {:?}", ty); /// } /// ``` - pub fn read(&mut self) -> Result { - self.reader.read_func_type() + pub fn read(&mut self) -> Result> { + Ok(match self.reader.read_u8()? { + 0x60 => TypeDef::Func(self.reader.read_func_type()?), + 0x61 => TypeDef::Module(self.reader.read_module_type()?), + 0x62 => TypeDef::Instance(self.reader.read_instance_type()?), + _ => { + return Err(BinaryReaderError::new( + "invalid leading byte in type definition", + self.original_position() - 1, + )) + } + }) } } impl<'a> SectionReader for TypeSectionReader<'a> { - type Item = FuncType; + type Item = TypeDef<'a>; fn read(&mut self) -> Result { TypeSectionReader::read(self) } @@ -78,7 +89,7 @@ impl<'a> SectionWithLimitedItems for TypeSectionReader<'a> { } impl<'a> IntoIterator for TypeSectionReader<'a> { - type Item = Result; + type Item = Result>; type IntoIter = SectionIteratorLimited>; /// Implements iterator over the type section. diff --git a/crates/wasmparser/src/validator.rs b/crates/wasmparser/src/validator.rs index 5c525d7f2c..94a6f6f890 100644 --- a/crates/wasmparser/src/validator.rs +++ b/crates/wasmparser/src/validator.rs @@ -13,20 +13,18 @@ * limitations under the License. */ -use std::collections::HashSet; +use std::collections::{HashMap, HashSet}; use std::result; use std::str; -use crate::limits::{ - MAX_WASM_FUNCTIONS, MAX_WASM_FUNCTION_LOCALS, MAX_WASM_GLOBALS, MAX_WASM_MEMORIES, - MAX_WASM_MEMORY_PAGES, MAX_WASM_TABLES, MAX_WASM_TYPES, -}; +use crate::limits::*; use crate::binary_reader::BinaryReader; use crate::primitives::{ - BinaryReaderError, ExternalKind, FuncType, GlobalType, ImportSectionEntryType, MemoryType, - Operator, ResizableLimits, Result, SectionCode, TableType, Type, + BinaryReaderError, ExportType, ExternalKind, FuncType, GlobalType, ImportSectionEntryType, + InstanceType, MemoryType, ModuleType, Operator, ResizableLimits, Result, SectionCode, + TableType, Type, TypeDef, }; use crate::operators_validator::{ @@ -34,8 +32,8 @@ use crate::operators_validator::{ OperatorValidatorError, DEFAULT_OPERATOR_VALIDATOR_CONFIG, }; use crate::parser::{Parser, ParserInput, ParserState, WasmDecoder}; -use crate::{ElemSectionEntryTable, ElementItem}; -use crate::{WasmFuncType, WasmGlobalType, WasmMemoryType, WasmModuleResources, WasmTableType}; +use crate::{AliasedInstance, WasmModuleResources}; +use crate::{ElemSectionEntryTable, ElementItem, WasmTypeDef}; use crate::readers::FunctionBody; @@ -53,6 +51,7 @@ enum SectionOrderState { Initial, Type, Import, + ModuleLinkingHeader, Function, Table, Memory, @@ -61,13 +60,22 @@ enum SectionOrderState { Start, Element, DataCount, + ModuleCode, Code, Data, } impl SectionOrderState { - pub fn from_section_code(code: &SectionCode) -> Option { + pub fn from_section_code( + code: &SectionCode, + config: &ValidatingParserConfig, + ) -> Option { match *code { + SectionCode::Type | SectionCode::Import + if config.operator_config.enable_module_linking => + { + Some(SectionOrderState::ModuleLinkingHeader) + } SectionCode::Type => Some(SectionOrderState::Type), SectionCode::Import => Some(SectionOrderState::Import), SectionCode::Function => Some(SectionOrderState::Function), @@ -80,7 +88,11 @@ impl SectionOrderState { SectionCode::Code => Some(SectionOrderState::Code), SectionCode::Data => Some(SectionOrderState::Data), SectionCode::DataCount => Some(SectionOrderState::DataCount), - _ => None, + SectionCode::Alias => Some(SectionOrderState::ModuleLinkingHeader), + SectionCode::Module => Some(SectionOrderState::ModuleLinkingHeader), + SectionCode::Instance => Some(SectionOrderState::ModuleLinkingHeader), + SectionCode::ModuleCode => Some(SectionOrderState::ModuleCode), + SectionCode::Custom { .. } => None, } } } @@ -94,24 +106,31 @@ const DEFAULT_VALIDATING_PARSER_CONFIG: ValidatingParserConfig = ValidatingParse operator_config: DEFAULT_OPERATOR_VALIDATOR_CONFIG, }; -struct ValidatingParserResources { - types: Vec, +struct ValidatingParserResources<'a> { + types: Vec>, tables: Vec, memories: Vec, globals: Vec, element_types: Vec, data_count: Option, func_type_indices: Vec, + module_type_indices: Vec, + instance_type_indices: Vec, function_references: HashSet, } -impl<'a> WasmModuleResources for ValidatingParserResources { - type FuncType = crate::FuncType; +enum InstanceDef { + Imported { type_idx: u32 }, + Instantiated { module_idx: u32 }, +} + +impl<'a> WasmModuleResources for ValidatingParserResources<'a> { + type TypeDef = crate::TypeDef<'a>; type TableType = crate::TableType; type MemoryType = crate::MemoryType; type GlobalType = crate::GlobalType; - fn type_at(&self, at: u32) -> Option<&Self::FuncType> { + fn type_at(&self, at: u32) -> Option<&Self::TypeDef> { self.types.get(at as usize) } @@ -153,13 +172,14 @@ pub struct ValidatingParser<'a> { validation_error: Option>, read_position: Option, section_order_state: SectionOrderState, - resources: ValidatingParserResources, + resources: ValidatingParserResources<'a>, current_func_index: u32, - func_imports_count: u32, + func_nonlocal_count: u32, init_expression_state: Option, data_found: u32, exported_names: HashSet, current_operator_validator: Option, + module_instantiation: Option<(u32, usize)>, config: ValidatingParserConfig, } @@ -178,26 +198,22 @@ impl<'a> ValidatingParser<'a> { element_types: Vec::new(), data_count: None, func_type_indices: Vec::new(), + instance_type_indices: Vec::new(), + module_type_indices: Vec::new(), function_references: HashSet::new(), }, current_func_index: 0, - func_imports_count: 0, + func_nonlocal_count: 0, current_operator_validator: None, init_expression_state: None, data_found: 0, exported_names: HashSet::new(), + module_instantiation: None, config: config.unwrap_or(DEFAULT_VALIDATING_PARSER_CONFIG), } } - pub fn get_resources( - &self, - ) -> &dyn WasmModuleResources< - FuncType = crate::FuncType, - TableType = crate::TableType, - MemoryType = crate::MemoryType, - GlobalType = crate::GlobalType, - > { + pub fn get_resources<'b>(&'b self) -> impl WasmModuleResources + 'b { &self.resources } @@ -251,6 +267,37 @@ impl<'a> ValidatingParser<'a> { } } + fn check_module_type(&self, ty: &ModuleType<'a>) -> ValidatorResult<'a, ()> { + if !self.config.operator_config.enable_module_linking { + return self.create_error("module linking proposal not enabled"); + } + for i in ty.imports.iter() { + self.check_import_entry(&i.ty)?; + } + let mut names = HashSet::new(); + for e in ty.exports.iter() { + if !names.insert(e.name) { + return self.create_error("duplicate export name"); + } + self.check_import_entry(&e.ty)?; + } + Ok(()) + } + + fn check_instance_type(&self, ty: &InstanceType<'a>) -> ValidatorResult<'a, ()> { + if !self.config.operator_config.enable_module_linking { + return self.create_error("module linking proposal not enabled"); + } + let mut names = HashSet::new(); + for e in ty.exports.iter() { + if !names.insert(e.name) { + return self.create_error("duplicate export name"); + } + self.check_import_entry(&e.ty)?; + } + Ok(()) + } + fn check_table_type(&self, table_type: &TableType) -> ValidatorResult<'a, ()> { match table_type.element_type { Type::FuncRef => {} @@ -294,13 +341,12 @@ impl<'a> ValidatingParser<'a> { if self.resources.func_type_indices.len() >= MAX_WASM_FUNCTIONS { return self.create_error("functions count out of bounds"); } - if type_index as usize >= self.resources.types.len() { - return self.create_error("unknown type: type index out of bounds"); - } + self.func_type_at(type_index)?; Ok(()) } ImportSectionEntryType::Table(ref table_type) => { if !self.config.operator_config.enable_reference_types + && !self.config.operator_config.enable_module_linking && self.resources.tables.len() >= MAX_WASM_TABLES { return self.create_error("multiple tables: tables count must be at most 1"); @@ -308,17 +354,33 @@ impl<'a> ValidatingParser<'a> { self.check_table_type(table_type) } ImportSectionEntryType::Memory(ref memory_type) => { - if self.resources.memories.len() >= MAX_WASM_MEMORIES { + if !self.config.operator_config.enable_module_linking + && self.resources.memories.len() >= MAX_WASM_MEMORIES + { return self.create_error("multiple memories: memory count must be at most 1"); } self.check_memory_type(memory_type) } ImportSectionEntryType::Global(global_type) => { if self.resources.globals.len() >= MAX_WASM_GLOBALS { - return self.create_error("functions count out of bounds"); + return self.create_error("globals count out of bounds"); } self.check_global_type(global_type) } + ImportSectionEntryType::Module(type_index) => { + if self.resources.module_type_indices.len() >= MAX_WASM_MODULES { + return self.create_error("modules count out of bounds"); + } + self.module_type_at(type_index)?; + Ok(()) + } + ImportSectionEntryType::Instance(type_index) => { + if self.resources.instance_type_indices.len() >= MAX_WASM_INSTANCES { + return self.create_error("instance count out of bounds"); + } + self.instance_type_at(type_index)?; + Ok(()) + } } } @@ -383,41 +445,83 @@ impl<'a> ValidatingParser<'a> { if self.exported_names.contains(field) { return self.create_error("duplicate export name"); } - match kind { - ExternalKind::Function => { - if index as usize >= self.resources.func_type_indices.len() { - return self - .create_error("unknown function: exported function index out of bounds"); - } - self.resources.function_references.insert(index); - } - ExternalKind::Table => { - if index as usize >= self.resources.tables.len() { - return self.create_error("unknown table: exported table index out of bounds"); - } - } - ExternalKind::Memory => { - if index as usize >= self.resources.memories.len() { - return self - .create_error("unknown memory: exported memory index out of bounds"); - } - } - ExternalKind::Global => { - if index as usize >= self.resources.globals.len() { - return self - .create_error("unknown global: exported global index out of bounds"); - } - } + if let ExternalKind::Type = kind { + return self.create_error("cannot export types"); + } + self.check_external_kind("exported", kind, index)?; + Ok(()) + } + + fn check_external_kind( + &mut self, + desc: &str, + kind: ExternalKind, + index: u32, + ) -> ValidatorResult<'a, ()> { + let (ty, total) = match kind { + ExternalKind::Function => ("function", self.resources.func_type_indices.len()), + ExternalKind::Table => ("table", self.resources.tables.len()), + ExternalKind::Memory => ("memory", self.resources.memories.len()), + ExternalKind::Global => ("global", self.resources.globals.len()), + ExternalKind::Module => ("module", self.resources.module_type_indices.len()), + ExternalKind::Instance => ("instance", self.resources.instance_type_indices.len()), + ExternalKind::Type => return self.create_error("cannot export types"), }; + if index as usize >= total { + return self.create_error(&format!( + "unknown {0}: {1} {0} index out of bounds", + ty, desc + )); + } + if let ExternalKind::Function = kind { + self.resources.function_references.insert(index); + } Ok(()) } + fn type_at<'me>(&'me self, type_index: u32) -> ValidatorResult<'a, &'me TypeDef<'a>> { + match self.resources.types.get(type_index as usize) { + Some(ty) => Ok(ty), + None => self.create_error("unknown type: type index out of bounds"), + } + } + + fn func_type_at<'me>(&'me self, type_index: u32) -> ValidatorResult<'a, &'me FuncType> { + match self.type_at(type_index)? { + TypeDef::Func(f) => Ok(f), + _ => self.create_error("type index is not a function"), + } + } + + fn module_type_at<'me>(&'me self, type_index: u32) -> ValidatorResult<'a, &'me ModuleType<'a>> { + if !self.config.operator_config.enable_module_linking { + return self.create_error("module linking proposal not enabled"); + } + match self.type_at(type_index)? { + TypeDef::Module(m) => Ok(m), + _ => self.create_error("type index is not a module"), + } + } + + fn instance_type_at<'me>( + &'me self, + type_index: u32, + ) -> ValidatorResult<'a, &'me InstanceType<'a>> { + if !self.config.operator_config.enable_module_linking { + return self.create_error("module linking proposal not enabled"); + } + match self.type_at(type_index)? { + TypeDef::Instance(i) => Ok(i), + _ => self.create_error("type index is not an instance"), + } + } + fn check_start(&self, func_index: u32) -> ValidatorResult<'a, ()> { if func_index as usize >= self.resources.func_type_indices.len() { return self.create_error("unknown function: start function index out of bounds"); } let type_index = self.resources.func_type_indices[func_index as usize]; - let ty = &self.resources.types[type_index as usize]; + let ty = self.func_type_at(type_index)?; if !ty.params.is_empty() || !ty.returns.is_empty() { return self.create_error("invlid start function type"); } @@ -425,22 +529,32 @@ impl<'a> ValidatingParser<'a> { } fn process_begin_section(&self, code: &SectionCode) -> ValidatorResult<'a, SectionOrderState> { - let order_state = SectionOrderState::from_section_code(code); + use SectionOrderState::*; + + let state = SectionOrderState::from_section_code(code, &self.config); + let state = match state { + Some(state) => state, + None => return Ok(self.section_order_state), + }; Ok(match self.section_order_state { - SectionOrderState::Initial => match order_state { - Some(section) => section, - _ => SectionOrderState::Initial, - }, - previous => { - if let Some(order_state_unwraped) = order_state { - if previous >= order_state_unwraped { - return self.create_error("section out of order"); - } - order_state_unwraped - } else { - previous - } + // Did we just start? In that case move to our newly-found state. + Initial => state, + + // If our previous state comes before our current state, nothing to + // worry about, just advance ourselves. + previous if previous < state => state, + + // In the module linking proposal we can see this state multiple + // times in a row. + ModuleLinkingHeader + if state == ModuleLinkingHeader + && self.config.operator_config.enable_module_linking => + { + ModuleLinkingHeader } + + // otherwise the sections are out of order + _ => return self.create_error("section out of order"), }) } @@ -459,14 +573,18 @@ impl<'a> ValidatingParser<'a> { self.section_order_state = check.ok().unwrap(); } } - ParserState::TypeSectionEntry(ref func_type) => { - let check = self.check_func_type(func_type); + ParserState::TypeSectionEntry(ref def) => { + let check = match def { + TypeDef::Func(ty) => self.check_func_type(ty), + TypeDef::Instance(ty) => self.check_instance_type(ty), + TypeDef::Module(ty) => self.check_module_type(ty), + }; if check.is_err() { self.validation_error = check.err(); } else if self.resources.types.len() > MAX_WASM_TYPES { self.set_validation_error("types count is out of bounds"); } else { - self.resources.types.push(func_type.clone()); + self.resources.types.push(def.clone()); } } ParserState::ImportSectionEntry { ref ty, .. } => { @@ -476,7 +594,7 @@ impl<'a> ValidatingParser<'a> { } else { match *ty { ImportSectionEntryType::Function(type_index) => { - self.func_imports_count += 1; + self.func_nonlocal_count += 1; self.resources.func_type_indices.push(type_index); } ImportSectionEntryType::Table(ref table_type) => { @@ -488,6 +606,16 @@ impl<'a> ValidatingParser<'a> { ImportSectionEntryType::Global(ref global_type) => { self.resources.globals.push(global_type.clone()); } + ImportSectionEntryType::Instance(type_index) => { + self.resources + .instance_type_indices + .push(InstanceDef::Imported { + type_idx: type_index, + }); + } + ImportSectionEntryType::Module(type_index) => { + self.resources.module_type_indices.push(type_index); + } } } } @@ -502,6 +630,7 @@ impl<'a> ValidatingParser<'a> { } ParserState::TableSectionEntry(ref table_type) => { if !self.config.operator_config.enable_reference_types + && !self.config.operator_config.enable_module_linking && self.resources.tables.len() >= MAX_WASM_TABLES { self.set_validation_error("multiple tables: tables count must be at most 1"); @@ -511,7 +640,9 @@ impl<'a> ValidatingParser<'a> { } } ParserState::MemorySectionEntry(ref memory_type) => { - if self.resources.memories.len() >= MAX_WASM_MEMORIES { + if !self.config.operator_config.enable_module_linking + && self.resources.memories.len() >= MAX_WASM_MEMORIES + { self.set_validation_error( "multiple memories: memories count must be at most 1", ); @@ -618,15 +749,16 @@ impl<'a> ValidatingParser<'a> { } } ParserState::BeginFunctionBody { .. } => { - let index = (self.current_func_index + self.func_imports_count) as usize; + let index = (self.current_func_index + self.func_nonlocal_count) as usize; if index as usize >= self.resources.func_type_indices.len() { self.set_validation_error("func type is not defined"); } } ParserState::FunctionBodyLocals { ref locals } => { - let index = (self.current_func_index + self.func_imports_count) as usize; - let func_type = - &self.resources.types[self.resources.func_type_indices[index] as usize]; + let index = (self.current_func_index + self.func_nonlocal_count) as usize; + let func_type = self + .func_type_at(self.resources.func_type_indices[index]) + .unwrap(); let operator_config = self.config.operator_config; match OperatorValidator::new(func_type, locals, operator_config) { Ok(validator) => self.current_operator_validator = Some(validator), @@ -679,7 +811,7 @@ impl<'a> ValidatingParser<'a> { } ParserState::EndWasm => { if self.resources.func_type_indices.len() - != self.current_func_index as usize + self.func_imports_count as usize + != self.current_func_index as usize + self.func_nonlocal_count as usize { self.set_validation_error( "function and code section have inconsistent lengths", @@ -691,6 +823,68 @@ impl<'a> ValidatingParser<'a> { } } } + + ParserState::ModuleSectionEntry(type_index) => { + if !self.config.operator_config.enable_module_linking { + self.set_validation_error("module linking proposal not enabled"); + } else if self.resources.module_type_indices.len() >= MAX_WASM_MODULES { + self.set_validation_error("modules count out of bounds"); + } else { + match self.module_type_at(type_index) { + Ok(_) => self.resources.module_type_indices.push(type_index), + Err(e) => self.validation_error = Some(e), + } + } + } + ParserState::BeginInstantiate { module, count } => { + if !self.config.operator_config.enable_module_linking { + self.set_validation_error("module linking proposal not enabled"); + } else if module as usize >= self.resources.module_type_indices.len() { + self.set_validation_error("module is not defined"); + } else if self.resources.instance_type_indices.len() >= MAX_WASM_INSTANCES { + self.set_validation_error("instance count out of bounds"); + } else { + self.resources + .instance_type_indices + .push(InstanceDef::Instantiated { module_idx: module }); + let module_ty = self.resources.module_type_indices[module as usize]; + if count as usize != self.module_type_at(module_ty).unwrap().imports.len() { + self.set_validation_error("wrong number of imports provided"); + } else { + self.module_instantiation = Some((module_ty, 0)); + } + } + } + ParserState::InstantiateParameter { kind, index } => { + let (module_ty_idx, import_idx) = self.module_instantiation.take().unwrap(); + let module_ty = self.module_type_at(module_ty_idx).unwrap(); + let ty = module_ty.imports[import_idx].ty.clone(); + match self.check_instantiate_field(&ty, kind, index) { + Ok(()) => { + self.module_instantiation = Some((module_ty_idx, import_idx + 1)); + } + Err(e) => self.validation_error = Some(e), + } + } + ParserState::EndInstantiate => { + let (module_ty, import_idx) = self.module_instantiation.take().unwrap(); + let module_ty = self.module_type_at(module_ty).unwrap(); + if import_idx != module_ty.imports.len() { + self.set_validation_error("not enough imports provided"); + } + } + ParserState::AliasSectionEntry(ref alias) => match alias.instance { + AliasedInstance::Parent => { + self.set_validation_error("parent instances not supported"); + } + AliasedInstance::Child(instance_idx) => { + let (kind, index) = (alias.kind, alias.index); + match self.check_alias_entry(instance_idx, kind, index) { + Ok(()) => {} + Err(e) => self.validation_error = Some(e), + } + } + }, _ => (), }; } @@ -708,9 +902,10 @@ impl<'a> ValidatingParser<'a> { self.read(); let operator_validator = match *self.last_state() { ParserState::FunctionBodyLocals { ref locals } => { - let index = (self.current_func_index + self.func_imports_count) as usize; - let func_type = - &self.resources.types[self.resources.func_type_indices[index] as usize]; + let index = (self.current_func_index + self.func_nonlocal_count) as usize; + let func_type = self + .func_type_at(self.resources.func_type_indices[index]) + .unwrap(); let operator_config = self.config.operator_config; OperatorValidator::new(func_type, locals, operator_config) .map_err(|e| ParserState::Error(e.set_offset(self.read_position.unwrap())))? @@ -728,6 +923,202 @@ impl<'a> ValidatingParser<'a> { pub fn current_position(&self) -> usize { self.parser.current_position() } + + fn check_instantiate_field( + &mut self, + expected: &ImportSectionEntryType, + kind: ExternalKind, + index: u32, + ) -> ValidatorResult<'a, ()> { + self.check_external_kind("referenced", kind, index)?; + let actual = match kind { + ExternalKind::Function => { + let actual_type = self.resources.func_type_indices[index as usize]; + ImportSectionEntryType::Function(actual_type) + } + ExternalKind::Table => { + ImportSectionEntryType::Table(self.resources.tables[index as usize]) + } + ExternalKind::Memory => { + ImportSectionEntryType::Memory(self.resources.memories[index as usize]) + } + ExternalKind::Global => { + ImportSectionEntryType::Global(self.resources.globals[index as usize]) + } + ExternalKind::Module => { + let actual_type = self.resources.module_type_indices[index as usize]; + ImportSectionEntryType::Module(actual_type) + } + ExternalKind::Instance => match self.resources.instance_type_indices[index as usize] { + InstanceDef::Imported { type_idx } => ImportSectionEntryType::Instance(type_idx), + InstanceDef::Instantiated { module_idx } => { + let expected = match expected { + ImportSectionEntryType::Instance(idx) => idx, + _ => return self.create_error("wrong kind of item used for instantiate"), + }; + let expected = self.instance_type_at(*expected)?; + let actual_ty = self.resources.module_type_indices[module_idx as usize]; + let actual = self.module_type_at(actual_ty)?; + return self.check_export_sets_match(&expected.exports, &actual.exports); + } + }, + ExternalKind::Type => return self.create_error("cannot export types"), + }; + self.check_imports_match(expected, &actual) + } + + // Note that this function is basically implementing + // https://webassembly.github.io/spec/core/exec/modules.html#import-matching + fn check_imports_match( + &self, + expected: &ImportSectionEntryType, + actual: &ImportSectionEntryType, + ) -> ValidatorResult<'a, ()> { + let limits_match = |expected: &ResizableLimits, actual: &ResizableLimits| { + actual.initial >= expected.initial + && match expected.maximum { + Some(expected_max) => match actual.maximum { + Some(actual_max) => actual_max <= expected_max, + None => false, + }, + None => true, + } + }; + match (expected, actual) { + ( + ImportSectionEntryType::Function(expected), + ImportSectionEntryType::Function(actual), + ) => { + let expected = self.func_type_at(*expected)?; + let actual = self.func_type_at(*actual)?; + if actual == expected { + return Ok(()); + } + self.create_error("function provided for instantiation has wrong type") + } + (ImportSectionEntryType::Table(expected), ImportSectionEntryType::Table(actual)) => { + if expected.element_type == actual.element_type + && limits_match(&expected.limits, &actual.limits) + { + return Ok(()); + } + self.create_error("table provided for instantiation has wrong type") + } + (ImportSectionEntryType::Memory(expected), ImportSectionEntryType::Memory(actual)) => { + if limits_match(&expected.limits, &actual.limits) + && expected.shared == actual.shared + { + return Ok(()); + } + self.create_error("memory provided for instantiation has wrong type") + } + (ImportSectionEntryType::Global(expected), ImportSectionEntryType::Global(actual)) => { + if expected == actual { + return Ok(()); + } + self.create_error("global provided for instantiation has wrong type") + } + ( + ImportSectionEntryType::Instance(expected), + ImportSectionEntryType::Instance(actual), + ) => { + let expected = self.instance_type_at(*expected)?; + let actual = self.instance_type_at(*actual)?; + self.check_export_sets_match(&expected.exports, &actual.exports)?; + Ok(()) + } + (ImportSectionEntryType::Module(expected), ImportSectionEntryType::Module(actual)) => { + let expected = self.module_type_at(*expected)?; + let actual = self.module_type_at(*actual)?; + if expected.imports.len() != actual.imports.len() { + return self.create_error("mismatched number of module imports"); + } + for (expected, actual) in expected.imports.iter().zip(actual.imports.iter()) { + self.check_imports_match(&expected.ty, &actual.ty)?; + } + self.check_export_sets_match(&expected.exports, &actual.exports)?; + Ok(()) + } + _ => self.create_error("wrong kind of item used for instantiate"), + } + } + + fn check_export_sets_match( + &self, + expected: &[ExportType<'_>], + actual: &[ExportType<'_>], + ) -> ValidatorResult<'a, ()> { + let name_to_idx = actual + .iter() + .enumerate() + .map(|(i, e)| (e.name, i)) + .collect::>(); + for expected in expected { + let idx = match name_to_idx.get(expected.name) { + Some(i) => *i, + None => return self.create_error(&format!("no export named `{}`", expected.name)), + }; + self.check_imports_match(&expected.ty, &actual[idx].ty)?; + } + Ok(()) + } + + fn check_alias_entry( + &mut self, + instance_idx: u32, + kind: ExternalKind, + export_idx: u32, + ) -> ValidatorResult<'a, ()> { + let ty = match self + .resources + .instance_type_indices + .get(instance_idx as usize) + { + Some(ty) => ty, + None => { + return self.create_error("unknown instance: aliased instance index out of bounds"); + } + }; + let exports = match ty { + InstanceDef::Imported { type_idx } => &self.instance_type_at(*type_idx)?.exports, + InstanceDef::Instantiated { module_idx } => { + let ty = self.resources.module_type_indices[*module_idx as usize]; + &self.module_type_at(ty)?.exports + } + }; + let export = match exports.get(export_idx as usize) { + Some(e) => e, + None => { + return self.create_error("aliased export index out of bounds"); + } + }; + match (export.ty, kind) { + (ImportSectionEntryType::Function(ty), ExternalKind::Function) => { + self.func_nonlocal_count += 1; + self.resources.func_type_indices.push(ty); + } + (ImportSectionEntryType::Table(ty), ExternalKind::Table) => { + self.resources.tables.push(ty); + } + (ImportSectionEntryType::Memory(ty), ExternalKind::Memory) => { + self.resources.memories.push(ty); + } + (ImportSectionEntryType::Global(ty), ExternalKind::Global) => { + self.resources.globals.push(ty); + } + (ImportSectionEntryType::Instance(ty), ExternalKind::Instance) => { + self.resources + .instance_type_indices + .push(InstanceDef::Imported { type_idx: ty }); + } + (ImportSectionEntryType::Module(ty), ExternalKind::Module) => { + self.resources.module_type_indices.push(ty); + } + _ => return self.create_error("alias kind mismatch with export kind"), + } + + Ok(()) + } } impl<'a> WasmDecoder<'a> for ValidatingParser<'a> { @@ -851,20 +1242,12 @@ impl<'b> ValidatingOperatorParser<'b> { /// } /// } /// ``` - pub fn next<'c, F: WasmFuncType, T: WasmTableType, M: WasmMemoryType, G: WasmGlobalType>( - &mut self, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, - ) -> Result> + pub fn next<'c>(&mut self, resources: impl WasmModuleResources) -> Result> where 'b: 'c, { let op = self.reader.read_operator()?; - match self.operator_validator.process_operator(&op, resources) { + match self.operator_validator.process_operator(&op, &resources) { Err(err) => { let offset = self.func_body_offset + self.reader.current_position(); return Err(err.set_offset(offset)); @@ -886,21 +1269,11 @@ impl<'b> ValidatingOperatorParser<'b> { /// Test whether the given buffer contains a valid WebAssembly function. /// The resources parameter contains all needed data to validate the operators. -pub fn validate_function_body< - F: WasmFuncType, - T: WasmTableType, - M: WasmMemoryType, - G: WasmGlobalType, ->( +pub fn validate_function_body( bytes: &[u8], offset: usize, func_index: u32, - resources: &dyn WasmModuleResources< - FuncType = F, - TableType = T, - MemoryType = M, - GlobalType = G, - >, + resources: impl WasmModuleResources, operator_config: Option, ) -> Result<()> { let operator_config = operator_config.unwrap_or(DEFAULT_OPERATOR_VALIDATOR_CONFIG); @@ -940,7 +1313,9 @@ pub fn validate_function_body< // Note: This was an out-of-bounds access before the change to return `Option` // so I assumed it is considered a bug to access a non-existing function // id here and went with panicking instead of returning a proper error. - .expect("the function type indexof the validated function itself is out of bounds"); + .expect("the function type indexof the validated function itself is out of bounds") + .as_func() + .unwrap(); let mut operator_validator = OperatorValidator::new(func_type, &locals, operator_config) .map_err(|e| e.set_offset(offset))?; let mut eof_found = false; @@ -948,7 +1323,7 @@ pub fn validate_function_body< for item in operators_reader.into_iter_with_offsets() { let (ref op, offset) = item?; match operator_validator - .process_operator(op, resources) + .process_operator(op, &resources) .map_err(|e| e.set_offset(offset))? { FunctionEnd::Yes => { @@ -987,7 +1362,7 @@ pub fn validate(bytes: &[u8], config: Option) -> Result< let operator_config = config.map(|c| c.operator_config); for (i, range) in func_ranges.into_iter().enumerate() { let function_body = range.slice(bytes); - let function_index = i as u32 + parser.func_imports_count; + let function_index = i as u32 + parser.func_nonlocal_count; validate_function_body( function_body, range.start, diff --git a/crates/wasmprinter/src/lib.rs b/crates/wasmprinter/src/lib.rs index 5d404c2d7b..8eae4b1b26 100644 --- a/crates/wasmprinter/src/lib.rs +++ b/crates/wasmprinter/src/lib.rs @@ -40,12 +40,19 @@ pub fn print_bytes(wasm: impl AsRef<[u8]>) -> Result { pub struct Printer { printers: HashMap Result<()>>>, result: String, + state: ModuleState, + nesting: u32, +} + +#[derive(Default)] +struct ModuleState { func: u32, + module: u32, + instance: u32, memory: u32, global: u32, table: u32, - nesting: u32, - functypes: Vec, + types: Vec>, names: HashMap, local_names: HashMap>, module_name: Option, @@ -91,24 +98,28 @@ impl Printer { /// This function takes an entire `wasm` binary blob and will print it to /// the WebAssembly Text Format and return the result as a `String`. pub fn print(&mut self, wasm: &[u8]) -> Result { - // Reset internal state which will get configured throughout printing. - self.func = 0; - self.memory = 0; - self.global = 0; - self.table = 0; - self.functypes.truncate(0); - self.local_names.drain(); - self.module_name = None; + let parser = wasmparser::ModuleReader::new(wasm)?; + self.start_group("module"); + self.print_contents(parser, "")?; + self.end_group(); + Ok(mem::take(&mut self.result)) + } + fn print_contents(&mut self, mut parser: ModuleReader, module_ty: &str) -> Result<()> { // First up try to find the `name` subsection which we'll use to print // pretty names everywhere. Also look for the `code` section so we can // print out functions as soon as we hit the function section. - let mut parser = wasmparser::ModuleReader::new(wasm)?; let mut code = None; - while !parser.eof() { - let section = parser.read()?; + let mut module_code = None; + let mut pre_parser = parser.clone(); + let prev = mem::take(&mut self.state); + while !pre_parser.eof() { + let section = pre_parser.read()?; match section.code { SectionCode::Code => code = Some(section.get_code_section_reader()?), + SectionCode::ModuleCode => { + module_code = Some(section.get_module_code_section_reader()?) + } SectionCode::Custom { name: "name", .. } => { self.register_names(section.get_name_section_reader()?)?; } @@ -117,21 +128,21 @@ impl Printer { } // ... and here we go, time to print all the sections! - self.result.push_str("(module"); - if let Some(name) = &self.module_name { + if let Some(name) = &self.state.module_name { self.result.push_str(" $"); self.result.push_str(name); } - let mut parser = wasmparser::ModuleReader::new(wasm)?; - let mut printers = mem::replace(&mut self.printers, HashMap::new()); + self.result.push_str(module_ty); while !parser.eof() { let section = parser.read()?; match section.code { SectionCode::Custom { name, .. } => { let range = section.get_binary_reader().range(); + let mut printers = mem::replace(&mut self.printers, HashMap::new()); if let Some(printer) = printers.get_mut(name) { - printer(self, range.start, &wasm[range.start..range.end])?; + printer(self, range.start, section.content_raw())?; } + self.printers = printers; } SectionCode::Type => { self.print_types(section.get_type_section_reader()?)?; @@ -179,22 +190,52 @@ impl Printer { SectionCode::DataCount => { // not part of the text format } + SectionCode::Alias => { + self.print_aliases(section.get_alias_section_reader()?)?; + } + SectionCode::Module => { + let reader = section.get_module_section_reader()?; + if reader.get_count() == 0 { + continue; + } + let module_code = match module_code.as_mut() { + Some(f) => f, + None => bail!("found function section without code section"), + }; + self.print_module_code(module_code, reader)?; + } + SectionCode::Instance => { + self.print_instances(section.get_instance_section_reader()?)?; + } + SectionCode::ModuleCode => { + // printed in `Module` section + } } } + self.state = prev; + Ok(()) + } + + fn start_group(&mut self, name: &str) { + self.result.push_str("("); + self.result.push_str(name); + self.nesting += 1; + } + + fn end_group(&mut self) { self.result.push_str(")"); - self.printers = printers; - Ok(mem::replace(&mut self.result, String::new())) + self.nesting -= 1; } fn register_names(&mut self, names: NameSectionReader<'_>) -> Result<()> { for section in names { match section? { - Name::Module(n) => self.module_name = Some(n.get_name()?.to_string()), + Name::Module(n) => self.state.module_name = Some(n.get_name()?.to_string()), Name::Function(n) => { let mut map = n.get_map()?; for _ in 0..map.get_count() { let name = map.read()?; - self.names.insert(name.index, name.name.to_string()); + self.state.names.insert(name.index, name.name.to_string()); } } Name::Local(n) => { @@ -207,7 +248,9 @@ impl Printer { let name = map.read()?; local_map.insert(name.index, name.name.to_string()); } - self.local_names.insert(local_name.func_index, local_map); + self.state + .local_names + .insert(local_name.func_index, local_map); } } } @@ -216,19 +259,57 @@ impl Printer { } fn print_types(&mut self, parser: TypeSectionReader<'_>) -> Result<()> { - for (i, ty) in parser.into_iter().enumerate() { - let ty = ty?; - write!(self.result, "\n (type (;{};) (func", i)?; - self.print_functype(&ty, None)?; - self.result.push_str("))"); - self.functypes.push(ty); + for ty in parser { + self.newline(); + self.start_group("type"); + write!(self.result, " (;{};) ", self.state.types.len())?; + let ty = match ty? { + TypeDef::Func(ty) => { + self.start_group("func"); + self.print_functype(&ty, None)?; + Some(ty) + } + TypeDef::Module(ty) => { + self.newline(); + self.start_group("module"); + for import in ty.imports.iter() { + self.print_import(import, false)?; + } + for export in ty.exports.iter() { + self.newline(); + self.start_group("export "); + self.print_str(export.name)?; + self.result.push_str(" "); + self.print_import_ty(&export.ty, false)?; + self.end_group(); + } + None + } + TypeDef::Instance(ty) => { + self.newline(); + self.start_group("instance"); + for export in ty.exports.iter() { + self.newline(); + self.start_group("export "); + self.print_str(export.name)?; + self.result.push_str(" "); + self.print_import_ty(&export.ty, false)?; + self.end_group(); + } + None + } + }; + self.end_group(); // inner type + self.end_group(); // `type` itself + self.state.types.push(ty); } Ok(()) } fn print_functype_idx(&mut self, idx: u32, names_for: Option) -> Result { - let ty = match self.functypes.get(idx as usize) { - Some(ty) => ty.clone(), + let ty = match self.state.types.get(idx as usize) { + Some(Some(ty)) => ty.clone(), + Some(None) => bail!("function type index `{}` is not a function", idx), None => bail!("function type index `{}` out of bounds", idx), }; self.print_functype(&ty, names_for) @@ -242,7 +323,7 @@ impl Printer { // we need to be careful to terminate previous param blocks and open // a new one if that's the case with a named parameter. for (i, param) in ty.params.iter().enumerate() { - let local_names = &self.local_names; + let local_names = &self.state.local_names; let name = names_for .and_then(|n| local_names.get(&n)) .and_then(|n| n.get(&(i as u32))); @@ -286,49 +367,90 @@ impl Printer { } fn print_imports(&mut self, parser: ImportSectionReader<'_>) -> Result<()> { - use ImportSectionEntryType::*; for import in parser { let import = import?; - self.result.push_str("\n (import "); - self.print_str(import.module)?; - self.result.push_str(" "); - self.print_str(import.field)?; - self.result.push_str(" ("); + self.print_import(&import, true)?; match import.ty { - Function(f) => { - self.result.push_str("func "); - match self.names.get(&self.func) { - Some(name) => write!(self.result, "${}", name)?, - None => write!(self.result, "(;{};)", self.func)?, + ImportSectionEntryType::Function(_) => self.state.func += 1, + ImportSectionEntryType::Module(_) => self.state.module += 1, + ImportSectionEntryType::Instance(_) => self.state.instance += 1, + ImportSectionEntryType::Table(_) => self.state.table += 1, + ImportSectionEntryType::Memory(_) => self.state.memory += 1, + ImportSectionEntryType::Global(_) => self.state.global += 1, + } + } + Ok(()) + } + + fn print_import(&mut self, import: &Import<'_>, index: bool) -> Result<()> { + self.newline(); + self.start_group("import "); + self.print_str(import.module)?; + if let Some(field) = import.field { + self.result.push_str(" "); + self.print_str(field)?; + } + self.result.push_str(" "); + self.print_import_ty(&import.ty, index)?; + self.end_group(); + Ok(()) + } + + fn print_import_ty(&mut self, ty: &ImportSectionEntryType, index: bool) -> Result<()> { + use ImportSectionEntryType::*; + match ty { + Function(f) => { + self.start_group("func"); + if index { + match self.state.names.get(&self.state.func) { + Some(name) => write!(self.result, " ${}", name)?, + None => write!(self.result, " (;{};)", self.state.func)?, } - write!(self.result, " (type {})", f)?; - self.func += 1; } - Table(f) => self.print_table_type(&f)?, - Memory(f) => self.print_memory_type(&f)?, - Global(f) => self.print_global_type(&f)?, + write!(self.result, " (type {})", f)?; } - self.result.push_str("))"); + Module(f) => { + self.start_group("module "); + if index { + write!(self.result, "(;{};) ", self.state.module)?; + } + write!(self.result, "(type {})", f)?; + } + Instance(f) => { + self.start_group("instance "); + if index { + write!(self.result, "(;{};) ", self.state.instance)?; + } + write!(self.result, "(type {})", f)?; + } + Table(f) => self.print_table_type(&f, index)?, + Memory(f) => self.print_memory_type(&f, index)?, + Global(f) => self.print_global_type(&f, index)?, } + self.end_group(); Ok(()) } - fn print_table_type(&mut self, ty: &TableType) -> Result<()> { - write!(self.result, "table (;{};) ", self.table)?; + fn print_table_type(&mut self, ty: &TableType, index: bool) -> Result<()> { + self.start_group("table "); + if index { + write!(self.result, "(;{};) ", self.state.table)?; + } self.print_limits(&ty.limits)?; self.result.push_str(" "); self.print_valtype(ty.element_type)?; - self.table += 1; Ok(()) } - fn print_memory_type(&mut self, ty: &MemoryType) -> Result<()> { - write!(self.result, "memory (;{};) ", self.memory)?; + fn print_memory_type(&mut self, ty: &MemoryType, index: bool) -> Result<()> { + self.start_group("memory "); + if index { + write!(self.result, "(;{};) ", self.state.memory)?; + } self.print_limits(&ty.limits)?; if ty.shared { self.result.push_str(" shared"); } - self.memory += 1; Ok(()) } @@ -340,8 +462,11 @@ impl Printer { Ok(()) } - fn print_global_type(&mut self, ty: &GlobalType) -> Result<()> { - write!(self.result, "global (;{};) ", self.global)?; + fn print_global_type(&mut self, ty: &GlobalType, index: bool) -> Result<()> { + self.start_group("global "); + if index { + write!(self.result, "(;{};) ", self.state.global)?; + } if ty.mutable { self.result.push_str("(mut "); self.print_valtype(ty.content_type)?; @@ -349,16 +474,16 @@ impl Printer { } else { self.print_valtype(ty.content_type)?; } - self.global += 1; Ok(()) } fn print_tables(&mut self, parser: TableSectionReader<'_>) -> Result<()> { for table in parser { let table = table?; - self.result.push_str("\n ("); - self.print_table_type(&table)?; - self.result.push_str(")"); + self.newline(); + self.print_table_type(&table, true)?; + self.end_group(); + self.state.table += 1; } Ok(()) } @@ -366,9 +491,10 @@ impl Printer { fn print_memories(&mut self, parser: MemorySectionReader<'_>) -> Result<()> { for memory in parser { let memory = memory?; - self.result.push_str("\n ("); - self.print_memory_type(&memory)?; - self.result.push_str(")"); + self.newline(); + self.print_memory_type(&memory, true)?; + self.end_group(); + self.state.memory += 1; } Ok(()) } @@ -376,11 +502,12 @@ impl Printer { fn print_globals(&mut self, parser: GlobalSectionReader<'_>) -> Result<()> { for global in parser { let global = global?; - self.result.push_str("\n ("); - self.print_global_type(&global.ty)?; + self.newline(); + self.print_global_type(&global.ty, true)?; self.result.push_str(" "); self.print_init_expr(&global.init_expr)?; - self.result.push_str(")"); + self.end_group(); + self.state.global += 1; } Ok(()) } @@ -393,13 +520,14 @@ impl Printer { for body in code { let body = body?; let ty = funcs.read()?; - self.result.push_str("\n (func "); - match self.names.get(&self.func) { + self.newline(); + self.start_group("func "); + match self.state.names.get(&self.state.func) { Some(name) => write!(self.result, "${}", name)?, - None => write!(self.result, "(;{};)", self.func)?, + None => write!(self.result, "(;{};)", self.state.func)?, } write!(self.result, " (type {})", ty)?; - let params = self.print_functype_idx(ty, Some(self.func))?; + let params = self.print_functype_idx(ty, Some(self.state.func))?; let mut first = true; let mut local_idx = 0; @@ -412,8 +540,9 @@ impl Printer { first = false; } let name = self + .state .local_names - .get(&self.func) + .get(&self.state.func) .and_then(|m| m.get(&(params + local_idx))); locals.start_local(name, &mut self.result); self.print_valtype(ty)?; @@ -423,55 +552,58 @@ impl Printer { } locals.finish(&mut self.result); - self.nesting = 1; + let nesting_start = self.nesting; for operator in body.get_operators_reader()? { let operator = operator?; match operator { Operator::Else => self.nesting -= 1, - Operator::End if self.nesting == 1 => continue, + Operator::End if self.nesting == nesting_start => continue, Operator::End => self.nesting -= 1, _ => {} } - self.result.push_str("\n "); - for _ in 0..self.nesting { - self.result.push_str(" "); - } - self.print_operator(&operator)?; + self.newline(); + self.print_operator(&operator, nesting_start)?; } - self.result.push_str(")"); + self.end_group(); - self.func += 1; + self.state.func += 1; } Ok(()) } - fn print_operator(&mut self, op: &Operator<'_>) -> Result<()> { + fn newline(&mut self) { + self.result.push_str("\n"); + for _ in 0..self.nesting { + self.result.push_str(" "); + } + } + + fn print_operator(&mut self, op: &Operator<'_>, nesting_start: u32) -> Result<()> { use Operator::*; - let nesting = self.nesting; - let label = - |relative: u32| match nesting.checked_sub(relative).and_then(|s| s.checked_sub(1)) { - Some(i) => format!("@{}", i), - None => format!(" INVALID "), - }; + let cur_label = self.nesting - nesting_start + 1; + let label = |relative: u32| match cur_label.checked_sub(relative + 1) { + Some(i) => format!("@{}", i), + None => format!(" INVALID "), + }; match op { Nop => self.result.push_str("nop"), Unreachable => self.result.push_str("unreachable"), Block { ty } => { self.result.push_str("block"); self.print_blockty(ty)?; - write!(self.result, " ;; label = @{}", self.nesting)?; + write!(self.result, " ;; label = @{}", cur_label)?; self.nesting += 1; } Loop { ty } => { self.result.push_str("loop"); self.print_blockty(ty)?; - write!(self.result, " ;; label = @{}", self.nesting)?; + write!(self.result, " ;; label = @{}", cur_label)?; self.nesting += 1; } If { ty } => { self.result.push_str("if"); self.print_blockty(ty)?; - write!(self.result, " ;; label = @{}", self.nesting)?; + write!(self.result, " ;; label = @{}", cur_label)?; self.nesting += 1; } Else => { @@ -538,15 +670,15 @@ impl Printer { } LocalGet { local_index } => { self.result.push_str("local.get "); - self.print_local_idx(self.func, *local_index)?; + self.print_local_idx(self.state.func, *local_index)?; } LocalSet { local_index } => { self.result.push_str("local.set "); - self.print_local_idx(self.func, *local_index)?; + self.print_local_idx(self.state.func, *local_index)?; } LocalTee { local_index } => { self.result.push_str("local.tee "); - self.print_local_idx(self.func, *local_index)?; + self.print_local_idx(self.state.func, *local_index)?; } GlobalGet { global_index } => { @@ -1135,9 +1267,11 @@ impl Printer { fn print_exports(&mut self, data: ExportSectionReader) -> Result<()> { for export in data { let export = export?; - self.result.push_str("\n (export "); + self.newline(); + self.start_group("export "); self.print_str(export.field)?; - self.result.push_str(" ("); + self.result.push_str(" "); + self.start_group(""); match export.kind { ExternalKind::Function => { self.result.push_str("func "); @@ -1146,18 +1280,40 @@ impl Printer { ExternalKind::Table => write!(self.result, "table {}", export.index)?, ExternalKind::Global => write!(self.result, "global {}", export.index)?, ExternalKind::Memory => write!(self.result, "memory {}", export.index)?, + ExternalKind::Module => write!(self.result, "module {}", export.index)?, + ExternalKind::Instance => write!(self.result, "instance {}", export.index)?, + ExternalKind::Type => write!(self.result, "type {}", export.index)?, } - self.result.push_str("))"); + self.end_group(); // field + self.end_group(); // export } return Ok(()); } + fn print_external(&mut self, kind: ExternalKind, index: u32) -> Result<()> { + self.result.push_str("("); + match kind { + ExternalKind::Function => { + self.result.push_str("func "); + self.print_func_idx(index)?; + } + ExternalKind::Table => write!(self.result, "table {}", index)?, + ExternalKind::Global => write!(self.result, "global {}", index)?, + ExternalKind::Memory => write!(self.result, "memory {}", index)?, + ExternalKind::Module => write!(self.result, "module {}", index)?, + ExternalKind::Instance => write!(self.result, "instance {}", index)?, + ExternalKind::Type => write!(self.result, "type {}", index)?, + } + self.result.push_str(")"); + Ok(()) + } + /// Prints a function index specified, using the identifier for the function /// from the `name` section if it was present. /// /// This will either print `$foo` or `idx` as a raw integer. pub fn print_func_idx(&mut self, idx: u32) -> Result<()> { - match self.names.get(&idx) { + match self.state.names.get(&idx) { Some(name) => write!(self.result, "${}", name)?, None => write!(self.result, "{}", idx)?, } @@ -1165,7 +1321,7 @@ impl Printer { } fn print_local_idx(&mut self, func: u32, idx: u32) -> Result<()> { - match self.local_names.get(&func).and_then(|f| f.get(&idx)) { + match self.state.local_names.get(&func).and_then(|f| f.get(&idx)) { Some(name) => write!(self.result, "${}", name)?, None => write!(self.result, "{}", idx)?, } @@ -1243,9 +1399,109 @@ impl Printer { Ok(()) } + fn print_instances(&mut self, instances: InstanceSectionReader) -> Result<()> { + for (i, instance) in instances.into_iter().enumerate() { + let instance = instance?; + self.newline(); + self.start_group("instance"); + write!(self.result, " (;{};)", i)?; + self.newline(); + self.start_group("instantiate"); + write!(self.result, " {}", instance.module())?; + for export in instance.args()? { + let (kind, index) = export?; + self.newline(); + self.print_external(kind, index)?; + } + self.end_group(); // instantiate + self.end_group(); // instance + } + Ok(()) + } + + fn print_aliases(&mut self, aliases: AliasSectionReader) -> Result<()> { + for alias in aliases { + let alias = alias?; + self.newline(); + self.start_group("alias"); + match alias.kind { + ExternalKind::Function => { + match self.state.names.get(&self.state.func) { + Some(name) => write!(self.result, " ${}", name)?, + None => write!(self.result, " (;{};)", self.state.func)?, + } + self.state.func += 1; + } + ExternalKind::Table => { + write!(self.result, " (;{};)", self.state.table)?; + self.state.table += 1; + } + ExternalKind::Memory => { + write!(self.result, " (;{};)", self.state.memory)?; + self.state.memory += 1; + } + ExternalKind::Global => { + write!(self.result, " (;{};)", self.state.global)?; + self.state.global += 1; + } + ExternalKind::Instance => { + write!(self.result, " (;{};)", self.state.instance)?; + self.state.instance += 1; + } + ExternalKind::Module => { + write!(self.result, " (;{};)", self.state.module)?; + self.state.module += 1; + } + ExternalKind::Type => self.state.types.push(None), + } + self.result.push_str(" "); + match alias.instance { + AliasedInstance::Parent => self.result.push_str("parent"), + AliasedInstance::Child(i) => { + self.start_group("instance"); + write!(self.result, " {}", i)?; + self.end_group(); + } + } + self.result.push_str(" "); + match alias.kind { + ExternalKind::Function => self.start_group("func"), + ExternalKind::Table => self.start_group("table"), + ExternalKind::Memory => self.start_group("memory"), + ExternalKind::Global => self.start_group("global"), + ExternalKind::Instance => self.start_group("instance"), + ExternalKind::Module => self.start_group("module"), + ExternalKind::Type => self.start_group("type"), + } + write!(self.result, " {}", alias.index)?; + self.end_group(); + self.end_group(); + } + Ok(()) + } + + fn print_module_code( + &mut self, + module_code: &mut ModuleCodeSectionReader<'_>, + tys: ModuleSectionReader<'_>, + ) -> Result<()> { + for ty in tys { + let ty = ty?; + let module = module_code.read()?; + self.newline(); + self.start_group("module"); + self.print_contents( + module.module()?, + &format!(" (;{};) (type {})", self.state.module, ty), + )?; + self.end_group(); + self.state.module += 1; + } + Ok(()) + } + fn print_init_expr(&mut self, expr: &InitExpr) -> Result<()> { - self.result.push_str("("); - self.nesting = 1; + self.start_group(""); for (i, op) in expr.get_operators_reader().into_iter().enumerate() { match op? { Operator::End => {} @@ -1253,11 +1509,11 @@ impl Printer { if i > 0 { self.result.push_str(" "); } - self.print_operator(&other)? + self.print_operator(&other, self.nesting)? } } } - self.result.push_str(")"); + self.end_group(); Ok(()) } diff --git a/crates/wast/src/ast/alias.rs b/crates/wast/src/ast/alias.rs new file mode 100644 index 0000000000..fe98b86fcf --- /dev/null +++ b/crates/wast/src/ast/alias.rs @@ -0,0 +1,43 @@ +use crate::ast::{self, kw}; +use crate::parser::{Parse, Parser, Result}; + +/// An `alias` statement used to juggle indices with nested modules. +#[derive(Debug)] +pub struct Alias<'a> { + /// Where this `alias` was defined. + pub span: ast::Span, + /// An identifier that this alias is resolved with (optionally) for name + /// resolution. + pub id: Option>, + /// An optional name for this alias stored in the custom `name` section. + pub name: Option>, + /// The instance that we're aliasing. If `None` then we're aliasing the + /// parent instance. + pub instance: Option>, + /// The item in the parent instance that we're aliasing. + pub kind: ast::ExportKind<'a>, +} + +impl<'a> Parse<'a> for Alias<'a> { + fn parse(parser: Parser<'a>) -> Result { + let span = parser.parse::()?.0; + let id = parser.parse()?; + let name = parser.parse()?; + let instance = if parser.parse::>()?.is_some() { + None + } else { + Some(parser.parens(|p| { + p.parse::()?; + p.parse() + })?) + }; + + Ok(Alias { + span, + id, + name, + instance, + kind: parser.parens(|p| p.parse())?, + }) + } +} diff --git a/crates/wast/src/ast/custom.rs b/crates/wast/src/ast/custom.rs index 568614af56..4915b4ca55 100644 --- a/crates/wast/src/ast/custom.rs +++ b/crates/wast/src/ast/custom.rs @@ -1,4 +1,4 @@ -use crate::ast::{self, kw, annotation}; +use crate::ast::{self, annotation, kw}; use crate::parser::{Parse, Parser, Result}; /// A wasm custom section within a module. @@ -36,6 +36,9 @@ pub enum CustomPlace { pub enum CustomPlaceAnchor { Type, Import, + Module, + Instance, + Alias, Func, Table, Memory, @@ -43,6 +46,7 @@ pub enum CustomPlaceAnchor { Export, Start, Elem, + ModuleCode, Code, Data, Event, @@ -61,7 +65,12 @@ impl<'a> Parse<'a> for Custom<'a> { while !parser.is_empty() { data.push(parser.parse()?); } - Ok(Custom { span, name, place, data }) + Ok(Custom { + span, + name, + place, + data, + }) } } @@ -93,51 +102,67 @@ impl<'a> Parse<'a> for CustomPlaceAnchor { fn parse(parser: Parser<'a>) -> Result { if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Type) + return Ok(CustomPlaceAnchor::Type); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Import) + return Ok(CustomPlaceAnchor::Import); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Func) + return Ok(CustomPlaceAnchor::Func); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Table) + return Ok(CustomPlaceAnchor::Table); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Memory) + return Ok(CustomPlaceAnchor::Memory); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Global) + return Ok(CustomPlaceAnchor::Global); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Export) + return Ok(CustomPlaceAnchor::Export); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Start) + return Ok(CustomPlaceAnchor::Start); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Elem) + return Ok(CustomPlaceAnchor::Elem); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Code) + return Ok(CustomPlaceAnchor::Code); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Data) + return Ok(CustomPlaceAnchor::Data); } if parser.peek::() { parser.parse::()?; - return Ok(CustomPlaceAnchor::Event) + return Ok(CustomPlaceAnchor::Event); + } + if parser.peek::() { + parser.parse::()?; + return Ok(CustomPlaceAnchor::Instance); + } + if parser.peek::() { + parser.parse::()?; + return Ok(CustomPlaceAnchor::Module); + } + if parser.peek::() { + parser.parse::()?; + return Ok(CustomPlaceAnchor::ModuleCode); + } + if parser.peek::() { + parser.parse::()?; + return Ok(CustomPlaceAnchor::Alias); } Err(parser.error("expected a valid section name")) diff --git a/crates/wast/src/ast/event.rs b/crates/wast/src/ast/event.rs index 2d4402394e..0746afc4c2 100644 --- a/crates/wast/src/ast/event.rs +++ b/crates/wast/src/ast/event.rs @@ -15,11 +15,11 @@ pub struct Event<'a> { } /// Listing of various types of events that can be defined in a wasm module. -#[derive(Debug)] +#[derive(Clone, Debug)] pub enum EventType<'a> { /// An exception event, where the payload is the type signature of the event /// (constructor parameters, etc). - Exception(ast::TypeUse<'a>), + Exception(ast::TypeUse<'a, ast::FunctionType<'a>>), } impl<'a> Parse<'a> for Event<'a> { diff --git a/crates/wast/src/ast/export.rs b/crates/wast/src/ast/export.rs index d3e0b32036..f96607d0d3 100644 --- a/crates/wast/src/ast/export.rs +++ b/crates/wast/src/ast/export.rs @@ -1,9 +1,11 @@ use crate::ast::{self, kw}; -use crate::parser::{Parse, Parser, Result}; +use crate::parser::{Cursor, Parse, Parser, Peek, Result}; /// A entry in a WebAssembly module's export section. #[derive(Debug)] pub struct Export<'a> { + /// Where this export was defined. + pub span: ast::Span, /// The name of this export from the module. pub name: &'a str, /// What's being exported from the module. @@ -12,7 +14,7 @@ pub struct Export<'a> { /// Different kinds of elements that can be exported from a WebAssembly module, /// contained in an [`Export`]. -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(missing_docs)] pub enum ExportKind<'a> { Func(ast::Index<'a>), @@ -20,34 +22,50 @@ pub enum ExportKind<'a> { Memory(ast::Index<'a>), Global(ast::Index<'a>), Event(ast::Index<'a>), + Module(ast::Index<'a>), + Instance(ast::Index<'a>), + Type(ast::Index<'a>), } impl<'a> Parse<'a> for Export<'a> { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; + let span = parser.parse::()?.0; let name = parser.parse()?; - let kind = parser.parens(|parser| { - let mut l = parser.lookahead1(); - if l.peek::() { - parser.parse::()?; - Ok(ExportKind::Func(parser.parse()?)) - } else if l.peek::() { - parser.parse::()?; - Ok(ExportKind::Table(parser.parse()?)) - } else if l.peek::() { - parser.parse::()?; - Ok(ExportKind::Memory(parser.parse()?)) - } else if l.peek::() { - parser.parse::()?; - Ok(ExportKind::Global(parser.parse()?)) - } else if l.peek::() { - parser.parse::()?; - Ok(ExportKind::Event(parser.parse()?)) - } else { - Err(l.error()) - } - })?; - Ok(Export { name, kind }) + let kind = parser.parens(|p| p.parse())?; + Ok(Export { span, name, kind }) + } +} + +impl<'a> Parse<'a> for ExportKind<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut l = parser.lookahead1(); + if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Func(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Table(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Memory(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Global(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Event(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Module(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Instance(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(ExportKind::Type(parser.parse()?)) + } else { + Err(l.error()) + } } } @@ -62,7 +80,7 @@ pub struct InlineExport<'a> { impl<'a> Parse<'a> for InlineExport<'a> { fn parse(parser: Parser<'a>) -> Result { let mut names = Vec::new(); - while parser.peek2::() { + while parser.peek::() { names.push(parser.parens(|p| { p.parse::()?; p.parse::<&str>() @@ -71,3 +89,25 @@ impl<'a> Parse<'a> for InlineExport<'a> { Ok(InlineExport { names }) } } + +impl Peek for InlineExport<'_> { + fn peek(cursor: Cursor<'_>) -> bool { + let cursor = match cursor.lparen() { + Some(cursor) => cursor, + None => return false, + }; + let cursor = match cursor.keyword() { + Some(("export", cursor)) => cursor, + _ => return false, + }; + let cursor = match cursor.string() { + Some((_, cursor)) => cursor, + None => return false, + }; + cursor.rparen().is_some() + } + + fn display() -> &'static str { + "inline export" + } +} diff --git a/crates/wast/src/ast/expr.rs b/crates/wast/src/ast/expr.rs index a5568eeb22..6eed0a4b53 100644 --- a/crates/wast/src/ast/expr.rs +++ b/crates/wast/src/ast/expr.rs @@ -897,14 +897,16 @@ instructions! { #[allow(missing_docs)] pub struct BlockType<'a> { pub label: Option>, - pub ty: ast::TypeUse<'a>, + pub ty: ast::TypeUse<'a, ast::FunctionType<'a>>, } impl<'a> Parse<'a> for BlockType<'a> { fn parse(parser: Parser<'a>) -> Result { Ok(BlockType { label: parser.parse()?, - ty: ast::TypeUse::parse_no_names(parser)?, + ty: parser + .parse::>>()? + .into(), }) } } @@ -991,13 +993,14 @@ pub struct CallIndirect<'a> { /// The table that this call is going to be indexing. pub table: ast::Index<'a>, /// Type type signature that this `call_indirect` instruction is using. - pub ty: ast::TypeUse<'a>, + pub ty: ast::TypeUse<'a, ast::FunctionType<'a>>, } impl<'a> Parse<'a> for CallIndirect<'a> { fn parse(parser: Parser<'a>) -> Result { + let prev_span = parser.prev_span(); let mut table: Option<_> = parser.parse()?; - let ty = ast::TypeUse::parse_no_names(parser)?; + let ty = parser.parse::>>()?; // Turns out the official test suite at this time thinks table // identifiers comes first but wabt's test suites asserts differently // putting them second. Let's just handle both. @@ -1005,8 +1008,8 @@ impl<'a> Parse<'a> for CallIndirect<'a> { table = parser.parse()?; } Ok(CallIndirect { - table: table.unwrap_or(ast::Index::Num(0)), - ty, + table: table.unwrap_or(ast::Index::Num(0, prev_span)), + ty: ty.into(), }) } } @@ -1022,10 +1025,11 @@ pub struct TableInit<'a> { impl<'a> Parse<'a> for TableInit<'a> { fn parse(parser: Parser<'a>) -> Result { + let prev_span = parser.prev_span(); let table_or_elem = parser.parse()?; let (table, elem) = match parser.parse()? { Some(elem) => (table_or_elem, elem), - None => (ast::Index::Num(0), table_or_elem), + None => (ast::Index::Num(0, prev_span), table_or_elem), }; Ok(TableInit { table, elem }) } @@ -1045,7 +1049,8 @@ impl<'a> Parse<'a> for TableCopy<'a> { let (dst, src) = if let Some(dst) = parser.parse()? { (dst, parser.parse()?) } else { - (ast::Index::Num(0), ast::Index::Num(0)) + let span = parser.prev_span(); + (ast::Index::Num(0, span), ast::Index::Num(0, span)) }; Ok(TableCopy { dst, src }) } @@ -1063,7 +1068,7 @@ impl<'a> Parse<'a> for TableArg<'a> { let dst = if let Some(dst) = parser.parse()? { dst } else { - ast::Index::Num(0) + ast::Index::Num(0, parser.prev_span()) }; Ok(TableArg { dst }) } diff --git a/crates/wast/src/ast/func.rs b/crates/wast/src/ast/func.rs index cf03627ccb..e54d2aee6e 100644 --- a/crates/wast/src/ast/func.rs +++ b/crates/wast/src/ast/func.rs @@ -20,7 +20,7 @@ pub struct Func<'a> { /// function. pub kind: FuncKind<'a>, /// The type that this function will have. - pub ty: ast::TypeUse<'a>, + pub ty: ast::TypeUse<'a, ast::FunctionType<'a>>, } /// Possible ways to define a function in the text format. @@ -31,19 +31,18 @@ pub enum FuncKind<'a> { /// ```text /// (func (type 3) (import "foo" "bar")) /// ``` - Import { - /// The module that this function is imported from - module: &'a str, - /// The module field name this function is imported from - field: &'a str, - }, + Import(ast::InlineImport<'a>), /// Almost all functions, those defined inline in a wasm module. Inline { /// The list of locals, if any, for this function. Each local has an /// optional identifier for name resolution and name for the custom /// `name` section associated with it. - locals: Vec<(Option>, Option>, ast::ValType<'a>)>, + locals: Vec<( + Option>, + Option>, + ast::ValType<'a>, + )>, /// The instructions of the function. expression: ast::Expression<'a>, @@ -57,12 +56,8 @@ impl<'a> Parse<'a> for Func<'a> { let name = parser.parse()?; let exports = parser.parse()?; - let (ty, kind) = if parser.peek2::() { - let (module, field) = parser.parens(|p| { - p.parse::()?; - Ok((p.parse()?, p.parse()?)) - })?; - (parser.parse()?, FuncKind::Import { module, field }) + let (ty, kind) = if let Some(import) = parser.parse()? { + (parser.parse()?, FuncKind::Import(import)) } else { let ty = parser.parse()?; let mut locals = Vec::new(); diff --git a/crates/wast/src/ast/global.rs b/crates/wast/src/ast/global.rs index acdc2532d1..78ca637488 100644 --- a/crates/wast/src/ast/global.rs +++ b/crates/wast/src/ast/global.rs @@ -25,12 +25,7 @@ pub enum GlobalKind<'a> { /// ```text /// (global i32 (import "foo" "bar")) /// ``` - Import { - /// The module that this function is imported from - module: &'a str, - /// The module field name this function is imported from - field: &'a str, - }, + Import(ast::InlineImport<'a>), /// A global defined inline in the module itself Inline(ast::Expression<'a>), @@ -42,12 +37,8 @@ impl<'a> Parse<'a> for Global<'a> { let id = parser.parse()?; let exports = parser.parse()?; - let (ty, kind) = if parser.peek2::() { - let (module, field) = parser.parens(|p| { - p.parse::()?; - Ok((p.parse()?, p.parse()?)) - })?; - (parser.parse()?, GlobalKind::Import { module, field }) + let (ty, kind) = if let Some(import) = parser.parse()? { + (parser.parse()?, GlobalKind::Import(import)) } else { (parser.parse()?, GlobalKind::Inline(parser.parse()?)) }; diff --git a/crates/wast/src/ast/import.rs b/crates/wast/src/ast/import.rs index 774ec679b1..2a57086448 100644 --- a/crates/wast/src/ast/import.rs +++ b/crates/wast/src/ast/import.rs @@ -1,73 +1,176 @@ use crate::ast::{self, kw}; -use crate::parser::{Parse, Parser, Result}; +use crate::parser::{Cursor, Parse, Parser, Peek, Result}; /// An `import` statement and entry in a WebAssembly module. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Import<'a> { /// Where this `import` was defined pub span: ast::Span, /// The module that this statement is importing from pub module: &'a str, /// The name of the field in the module this statement imports from. - pub field: &'a str, + pub field: Option<&'a str>, + /// The item that's being imported. + pub item: ItemSig<'a>, +} + +impl<'a> Parse<'a> for Import<'a> { + fn parse(parser: Parser<'a>) -> Result { + let span = parser.parse::()?.0; + let module = parser.parse()?; + let field = parser.parse()?; + let item = parser.parens(|p| p.parse())?; + Ok(Import { + span, + module, + field, + item, + }) + } +} + +#[derive(Debug, Clone)] +#[allow(missing_docs)] +pub struct ItemSig<'a> { + /// Where this item is defined in the source. + pub span: ast::Span, /// An optional identifier used during name resolution to refer to this item /// from the rest of the module. pub id: Option>, /// An optional name which, for functions, will be stored in the /// custom `name` section. pub name: Option>, - /// What kind of item is being imported. - pub kind: ImportKind<'a>, + /// What kind of item this is. + pub kind: ItemKind<'a>, } -/// All possible types of items that can be imported into a wasm module. -#[derive(Debug)] +#[derive(Debug, Clone)] #[allow(missing_docs)] -pub enum ImportKind<'a> { - Func(ast::TypeUse<'a>), +pub enum ItemKind<'a> { + Func(ast::TypeUse<'a, ast::FunctionType<'a>>), Table(ast::TableType), Memory(ast::MemoryType), Global(ast::GlobalType<'a>), Event(ast::EventType<'a>), + Module(ast::TypeUse<'a, ast::ModuleType<'a>>), + Instance(ast::TypeUse<'a, ast::InstanceType<'a>>), } -impl<'a> Parse<'a> for Import<'a> { +impl<'a> Parse<'a> for ItemSig<'a> { fn parse(parser: Parser<'a>) -> Result { - let span = parser.parse::()?.0; - let module = parser.parse()?; - let field = parser.parse()?; - let (id, name, kind) = parser.parens(|parser| { - let mut l = parser.lookahead1(); - if l.peek::() { - parser.parse::()?; - Ok(( - parser.parse()?, - parser.parse()?, - ImportKind::Func(parser.parse()?), - )) - } else if l.peek::() { - parser.parse::()?; - Ok((parser.parse()?, None, ImportKind::Table(parser.parse()?))) - } else if l.peek::() { - parser.parse::()?; - Ok((parser.parse()?, None, ImportKind::Memory(parser.parse()?))) - } else if l.peek::() { - parser.parse::()?; - Ok((parser.parse()?, None, ImportKind::Global(parser.parse()?))) - } else if l.peek::() { - parser.parse::()?; - Ok((parser.parse()?, None, ImportKind::Event(parser.parse()?))) - } else { - Err(l.error()) - } - })?; - Ok(Import { - span, - module, - field, - id, - kind, - name, + let mut l = parser.lookahead1(); + if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: parser.parse()?, + kind: ItemKind::Func(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Table(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Memory(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Global(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Event(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Module(parser.parse()?), + }) + } else if l.peek::() { + let span = parser.parse::()?.0; + Ok(ItemSig { + span, + id: parser.parse()?, + name: None, + kind: ItemKind::Instance(parser.parse()?), + }) + } else { + Err(l.error()) + } + } +} + +/// A listing of a inline `(import "foo")` statement. +/// +/// Note that when parsing this type it is somewhat unconventional that it +/// parses its own surrounding parentheses. This is typically an optional type, +/// so it's so far been a bit nicer to have the optionality handled through +/// `Peek` rather than `Option`. +#[derive(Debug, Copy, Clone)] +#[allow(missing_docs)] +pub struct InlineImport<'a> { + pub module: &'a str, + pub field: Option<&'a str>, +} + +impl<'a> Parse<'a> for InlineImport<'a> { + fn parse(parser: Parser<'a>) -> Result { + parser.parens(|p| { + p.parse::()?; + Ok(InlineImport { + module: p.parse()?, + field: p.parse()?, + }) }) } } + +impl Peek for InlineImport<'_> { + fn peek(cursor: Cursor<'_>) -> bool { + let cursor = match cursor.lparen() { + Some(cursor) => cursor, + None => return false, + }; + let cursor = match cursor.keyword() { + Some(("import", cursor)) => cursor, + _ => return false, + }; + let cursor = match cursor.string() { + Some((_, cursor)) => cursor, + None => return false, + }; + + // optional field + let cursor = match cursor.string() { + Some((_, cursor)) => cursor, + None => cursor, + }; + + cursor.rparen().is_some() + } + + fn display() -> &'static str { + "inline import" + } +} diff --git a/crates/wast/src/ast/instance.rs b/crates/wast/src/ast/instance.rs new file mode 100644 index 0000000000..03fb9b8444 --- /dev/null +++ b/crates/wast/src/ast/instance.rs @@ -0,0 +1,69 @@ +use crate::ast::{self, kw}; +use crate::parser::{Parse, Parser, Result}; + +/// A nested WebAssembly instance to be created as part of a module. +#[derive(Debug)] +pub struct Instance<'a> { + /// Where this `instance` was defined. + pub span: ast::Span, + /// An identifier that this instance is resolved with (optionally) for name + /// resolution. + pub id: Option>, + /// If present, inline export annotations which indicate names this + /// definition should be exported under. + pub exports: ast::InlineExport<'a>, + /// What kind of instance this is, be it an inline-defined or imported one. + pub kind: InstanceKind<'a>, +} + +/// Possible ways to define a instance in the text format. +#[derive(Debug)] +pub enum InstanceKind<'a> { + /// An instance which is actually defined as an import, such as: + Import { + /// Where we're importing from + import: ast::InlineImport<'a>, + /// The type that this instance will have. + ty: ast::TypeUse<'a, ast::InstanceType<'a>>, + }, + + /// Instances whose instantiation is defined inline. + Inline { + /// Module that we're instantiating + module: ast::Index<'a>, + /// Items used to instantiate the instance + items: Vec>, + }, +} + +impl<'a> Parse<'a> for Instance<'a> { + fn parse(parser: Parser<'a>) -> Result { + let span = parser.parse::()?.0; + let id = parser.parse()?; + let exports = parser.parse()?; + + let kind = if let Some(import) = parser.parse()? { + InstanceKind::Import { + import, + ty: parser.parse()?, + } + } else { + parser.parens(|p| { + p.parse::()?; + let module = p.parse()?; + let mut items = Vec::new(); + while !p.is_empty() { + items.push(p.parens(|p| p.parse())?); + } + Ok(InstanceKind::Inline { module, items }) + })? + }; + + Ok(Instance { + span, + id, + exports, + kind, + }) + } +} diff --git a/crates/wast/src/ast/memory.rs b/crates/wast/src/ast/memory.rs index 39c367f0bf..93b772bf58 100644 --- a/crates/wast/src/ast/memory.rs +++ b/crates/wast/src/ast/memory.rs @@ -21,8 +21,7 @@ pub enum MemoryKind<'a> { /// This memory is actually an inlined import definition. #[allow(missing_docs)] Import { - module: &'a str, - field: &'a str, + import: ast::InlineImport<'a>, ty: ast::MemoryType, }, @@ -41,39 +40,25 @@ impl<'a> Parse<'a> for Memory<'a> { // Afterwards figure out which style this is, either: // - // * `(data ...)` // * `(import "a" "b") limits` + // * `(data ...)` // * `limits` let mut l = parser.lookahead1(); - let kind = if l.peek::() { - enum Which<'a, T> { - Inline(Vec), - Import(&'a str, &'a str), + let kind = if let Some(import) = parser.parse()? { + MemoryKind::Import { + import, + ty: parser.parse()?, } - let result = parser.parens(|parser| { - let mut l = parser.lookahead1(); - if l.peek::() { - parser.parse::()?; - let mut data = Vec::new(); - while !parser.is_empty() { - data.push(parser.parse()?); - } - Ok(Which::Inline(data)) - } else if l.peek::() { - parser.parse::()?; - Ok(Which::Import(parser.parse()?, parser.parse()?)) - } else { - Err(l.error()) + } else if l.peek::() { + let data = parser.parens(|parser| { + parser.parse::()?; + let mut data = Vec::new(); + while !parser.is_empty() { + data.push(parser.parse()?); } + Ok(data) })?; - match result { - Which::Inline(data) => MemoryKind::Inline(data), - Which::Import(module, field) => MemoryKind::Import { - module, - field, - ty: parser.parse()?, - }, - } + MemoryKind::Inline(data) } else if l.peek::() { MemoryKind::Normal(parser.parse()?) } else { @@ -156,7 +141,7 @@ impl<'a> Parse<'a> for Data<'a> { parser.parse() })?; DataKind::Active { - memory: memory.unwrap_or(ast::Index::Num(0)), + memory: memory.unwrap_or(ast::Index::Num(0, span)), offset, } }; diff --git a/crates/wast/src/ast/mod.rs b/crates/wast/src/ast/mod.rs index 07cd6dfb59..2766d20ea2 100644 --- a/crates/wast/src/ast/mod.rs +++ b/crates/wast/src/ast/mod.rs @@ -301,6 +301,7 @@ reexport! { #[cfg(feature = "wasm-module")] reexport! { + mod alias; mod assert_expr; mod custom; mod event; @@ -309,8 +310,10 @@ reexport! { mod func; mod global; mod import; + mod instance; mod memory; mod module; + mod nested_module; mod table; mod types; mod wast; @@ -319,6 +322,7 @@ reexport! { /// Common keyword used to parse WebAssembly text files. pub mod kw { custom_keyword!(after); + custom_keyword!(alias); custom_keyword!(any); custom_keyword!(anyfunc); custom_keyword!(anyref); @@ -373,12 +377,15 @@ pub mod kw { custom_keyword!(i8); custom_keyword!(i8x16); custom_keyword!(import); + custom_keyword!(instance); + custom_keyword!(instantiate); custom_keyword!(invoke); custom_keyword!(item); custom_keyword!(last); custom_keyword!(local); custom_keyword!(memory); custom_keyword!(module); + custom_keyword!(modulecode); custom_keyword!(nan_arithmetic = "nan:arithmetic"); custom_keyword!(nan_canonical = "nan:canonical"); custom_keyword!(null); @@ -387,6 +394,7 @@ pub mod kw { custom_keyword!(opt); custom_keyword!(optref); custom_keyword!(param); + custom_keyword!(parent); custom_keyword!(passive); custom_keyword!(quote); custom_keyword!(r#else = "else"); diff --git a/crates/wast/src/ast/module.rs b/crates/wast/src/ast/module.rs index 10c84889a3..e923f01c5c 100644 --- a/crates/wast/src/ast/module.rs +++ b/crates/wast/src/ast/module.rs @@ -1,4 +1,4 @@ -use crate::ast::{self, kw, annotation}; +use crate::ast::{self, annotation, kw}; use crate::parser::{Parse, Parser, Result}; pub use crate::resolve::Names; @@ -166,11 +166,15 @@ pub enum ModuleField<'a> { Memory(ast::Memory<'a>), Global(ast::Global<'a>), Export(ast::Export<'a>), + ExportAll(ast::Span, ast::Id<'a>), Start(ast::Index<'a>), Elem(ast::Elem<'a>), Data(ast::Data<'a>), Event(ast::Event<'a>), Custom(ast::Custom<'a>), + Instance(ast::Instance<'a>), + NestedModule(ast::NestedModule<'a>), + Alias(ast::Alias<'a>), } impl<'a> ModuleField<'a> { @@ -204,6 +208,10 @@ impl<'a> Parse<'a> for ModuleField<'a> { return Ok(ModuleField::Global(parser.parse()?)); } if parser.peek::() { + if parser.peek2::() { + let span = parser.parse::()?.0; + return Ok(ModuleField::ExportAll(span, parser.parse()?)); + } return Ok(ModuleField::Export(parser.parse()?)); } if parser.peek::() { @@ -222,6 +230,15 @@ impl<'a> Parse<'a> for ModuleField<'a> { if parser.peek::() { return Ok(ModuleField::Custom(parser.parse()?)); } + if parser.peek::() { + return Ok(ModuleField::Instance(parser.parse()?)); + } + if parser.peek::() { + return Ok(ModuleField::NestedModule(parser.parse()?)); + } + if parser.peek::() { + return Ok(ModuleField::Alias(parser.parse()?)); + } Err(parser.error("expected valid module field")) } } diff --git a/crates/wast/src/ast/nested_module.rs b/crates/wast/src/ast/nested_module.rs new file mode 100644 index 0000000000..f9385aedad --- /dev/null +++ b/crates/wast/src/ast/nested_module.rs @@ -0,0 +1,112 @@ +use crate::ast::{self, kw}; +use crate::parser::{Cursor, Parse, Parser, Peek, Result}; + +/// A nested WebAssembly nested module to be created as part of a module. +#[derive(Debug)] +pub struct NestedModule<'a> { + /// Where this `nested module` was defined. + pub span: ast::Span, + /// An identifier that this nested module is resolved with (optionally) for name + /// resolution. + pub id: Option>, + /// An optional name for this module stored in the custom `name` section. + pub name: Option>, + /// If present, inline export annotations which indicate names this + /// definition should be exported under. + pub exports: ast::InlineExport<'a>, + /// What kind of nested module this is, be it an inline-defined or imported one. + pub kind: NestedModuleKind<'a>, +} + +/// Possible ways to define a nested module in the text format. +#[derive(Debug)] +pub enum NestedModuleKind<'a> { + /// An nested module which is actually defined as an import, such as: + Import { + /// Where this nested module is imported from + import: ast::InlineImport<'a>, + /// The type that this nested module will have. + ty: ast::TypeUse<'a, ast::ModuleType<'a>>, + }, + + /// Nested modules whose instantiation is defined inline. + Inline { + /// Optional type reference + ty: Option>, + /// Fields in the nested module. + fields: Vec>, + }, +} + +impl<'a> Parse<'a> for NestedModule<'a> { + fn parse(parser: Parser<'a>) -> Result { + let span = parser.parse::()?.0; + let id = parser.parse()?; + let name = parser.parse()?; + let exports = parser.parse()?; + + let kind = if let Some(import) = parser.parse()? { + NestedModuleKind::Import { + import, + ty: parser.parse()?, + } + } else { + let ty = if parser.peek::() { + Some(parser.parens(|p| { + p.parse::()?; + p.parse() + })?) + } else { + None + }; + let mut fields = Vec::new(); + while !parser.is_empty() { + fields.push(parser.parens(|p| p.parse())?); + } + NestedModuleKind::Inline { ty, fields } + }; + + Ok(NestedModule { + span, + id, + name, + exports, + kind, + }) + } +} + +// Note that this is a custom implementation to get multi-token lookahead to +// figure out how to parse `(type ...` when it's in an inline module. If this is +// `(type $x)` or `(type 0)` then it's an inline type annotation, otherwise it's +// probably a typedef like `(type $x (func))` or something like that. We only +// want to parse the two-token variant right now. +struct InlineType; + +impl Peek for InlineType { + fn peek(cursor: Cursor<'_>) -> bool { + let cursor = match cursor.lparen() { + Some(cursor) => cursor, + None => return false, + }; + let cursor = match cursor.keyword() { + Some(("type", cursor)) => cursor, + _ => return false, + }; + + // optional identifier + let cursor = match cursor.id() { + Some((_, cursor)) => cursor, + None => match cursor.integer() { + Some((_, cursor)) => cursor, + None => return false, + }, + }; + + cursor.rparen().is_some() + } + + fn display() -> &'static str { + "inline type" + } +} diff --git a/crates/wast/src/ast/table.rs b/crates/wast/src/ast/table.rs index eee4b24dbf..932905bffc 100644 --- a/crates/wast/src/ast/table.rs +++ b/crates/wast/src/ast/table.rs @@ -21,8 +21,7 @@ pub enum TableKind<'a> { /// This table is actually an inlined import definition. #[allow(missing_docs)] Import { - module: &'a str, - field: &'a str, + import: ast::InlineImport<'a>, ty: ast::TableType, }, @@ -65,14 +64,9 @@ impl<'a> Parse<'a> for Table<'a> { TableKind::Inline { elem, payload } } else if l.peek::() { TableKind::Normal(parser.parse()?) - } else if l.peek::() { - let (module, field) = parser.parens(|p| { - p.parse::()?; - Ok((p.parse()?, p.parse()?)) - })?; + } else if let Some(import) = parser.parse()? { TableKind::Import { - module, - field, + import, ty: parser.parse()?, } } else { @@ -149,7 +143,7 @@ impl<'a> Parse<'a> for Elem<'a> { p.parse() })?) } else if parser.peek::() { - Some(ast::Index::Num(parser.parse()?)) + Some(parser.parse()?) } else { None }; @@ -160,7 +154,7 @@ impl<'a> Parse<'a> for Elem<'a> { parser.parse() })?; ElemKind::Active { - table: table.unwrap_or(ast::Index::Num(0)), + table: table.unwrap_or(ast::Index::Num(0, span)), offset, } } else if parser.peek::() { @@ -221,7 +215,10 @@ impl<'a> ElemPayload<'a> { } } -fn parse_ref_func<'a>(parser: Parser<'a>, ty: ast::TableElemType) -> Result>> { +fn parse_ref_func<'a>( + parser: Parser<'a>, + ty: ast::TableElemType, +) -> Result>> { let mut l = parser.lookahead1(); if l.peek::() { parser.parse::()?; diff --git a/crates/wast/src/ast/token.rs b/crates/wast/src/ast/token.rs index 1247956ada..8ba13a3107 100644 --- a/crates/wast/src/ast/token.rs +++ b/crates/wast/src/ast/token.rs @@ -44,10 +44,23 @@ impl Span { #[derive(Copy, Clone)] pub struct Id<'a> { name: &'a str, + gen: u32, span: Span, } impl<'a> Id<'a> { + fn new(name: &'a str, span: Span) -> Id<'a> { + Id { name, gen: 0, span } + } + + pub(crate) fn gensym(span: Span, gen: u32) -> Id<'a> { + Id { + name: "gensym", + gen, + span, + } + } + /// Returns the underlying name of this identifier. /// /// The name returned does not contain the leading `$`. @@ -59,17 +72,22 @@ impl<'a> Id<'a> { pub fn span(&self) -> Span { self.span } + + pub(crate) fn is_gensym(&self) -> bool { + self.gen != 0 + } } impl<'a> Hash for Id<'a> { fn hash(&self, hasher: &mut H) { self.name.hash(hasher); + self.gen.hash(hasher); } } impl<'a> PartialEq for Id<'a> { fn eq(&self, other: &Id<'a>) -> bool { - self.name == other.name + self.name == other.name && self.gen == other.gen } } @@ -79,13 +97,7 @@ impl<'a> Parse<'a> for Id<'a> { fn parse(parser: Parser<'a>) -> Result { parser.step(|c| { if let Some((name, rest)) = c.id() { - return Ok(( - Id { - name, - span: c.cur_span(), - }, - rest, - )); + return Ok((Id::new(name, c.cur_span()), rest)); } Err(c.error("expected an identifier")) }) @@ -116,23 +128,34 @@ impl Peek for Id<'_> { /// /// The emission phase of a module will ensure that `Index::Id` is never used /// and switch them all to `Index::Num`. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +#[derive(Copy, Clone, Debug)] pub enum Index<'a> { /// A numerical index that this references. The index space this is /// referencing is implicit based on where this [`Index`] is stored. - Num(u32), + Num(u32, Span), /// A human-readable identifier this references. Like `Num`, the namespace /// this references is based on where this is stored. Id(Id<'a>), } +impl Index<'_> { + /// Returns the source location where this `Index` was defined. + pub fn span(&self) -> Span { + match self { + Index::Num(_, span) => *span, + Index::Id(id) => id.span(), + } + } +} + impl<'a> Parse<'a> for Index<'a> { fn parse(parser: Parser<'a>) -> Result { let mut l = parser.lookahead1(); if l.peek::() { Ok(Index::Id(parser.parse()?)) } else if l.peek::() { - Ok(Index::Num(parser.parse()?)) + let (val, span) = parser.parse()?; + Ok(Index::Num(val, span)) } else { Err(l.error()) } @@ -149,6 +172,33 @@ impl Peek for Index<'_> { } } +impl PartialEq for Index<'_> { + fn eq(&self, other: &Index<'_>) -> bool { + match (self, other) { + (Index::Num(a, _), Index::Num(b, _)) => a == b, + (Index::Id(a), Index::Id(b)) => a == b, + _ => false, + } + } +} + +impl Eq for Index<'_> {} + +impl Hash for Index<'_> { + fn hash(&self, hasher: &mut H) { + match self { + Index::Num(a, _) => { + 0u8.hash(hasher); + a.hash(hasher); + } + Index::Id(a) => { + 1u8.hash(hasher); + a.hash(hasher); + } + } + } +} + /// An `@name` annotation in source, currently of the form `@name "foo"` #[derive(Copy, Clone, PartialEq, Debug)] pub struct NameAnnotation<'a> { @@ -178,6 +228,12 @@ impl<'a> Parse<'a> for Option> { macro_rules! integers { ($($i:ident($u:ident))*) => ($( impl<'a> Parse<'a> for $i { + fn parse(parser: Parser<'a>) -> Result { + Ok(parser.parse::<($i, Span)>()?.0) + } + } + + impl<'a> Parse<'a> for ($i, Span) { fn parse(parser: Parser<'a>) -> Result { parser.step(|c| { if let Some((i, rest)) = c.integer() { @@ -187,7 +243,7 @@ macro_rules! integers { $u::from_str_radix(s, base).map(|i| i as $i) }); return match val { - Ok(n) => Ok((n, rest)), + Ok(n) => Ok(((n, c.cur_span()), rest)), Err(_) => Err(c.error(concat!( "invalid ", stringify!($i), diff --git a/crates/wast/src/ast/types.rs b/crates/wast/src/ast/types.rs index 797792e6ad..a988c0be56 100644 --- a/crates/wast/src/ast/types.rs +++ b/crates/wast/src/ast/types.rs @@ -153,7 +153,7 @@ impl<'a> Parse<'a> for RefType<'a> { } /// Type for a `global` in a wasm module -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct GlobalType<'a> { /// The element type of this `global` pub ty: ValType<'a>, @@ -181,7 +181,7 @@ impl<'a> Parse<'a> for GlobalType<'a> { } /// List of different kinds of table types we can have. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum TableElemType { /// An element for a table that is a list of functions. Funcref, @@ -233,7 +233,7 @@ impl Peek for TableElemType { } /// Min/max limits used for tables/memories. -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct Limits { /// The minimum number of units for this type. pub min: u32, @@ -254,7 +254,7 @@ impl<'a> Parse<'a> for Limits { } /// Configuration for a table of a wasm mdoule -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct TableType { /// Limits on the element sizes of this table pub limits: Limits, @@ -272,7 +272,7 @@ impl<'a> Parse<'a> for TableType { } /// Configuration for a memory of a wasm module -#[derive(Copy, Clone, Debug)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub struct MemoryType { /// Limits on the page sizes of this memory pub limits: Limits, @@ -289,11 +289,15 @@ impl<'a> Parse<'a> for MemoryType { } /// A function type with parameters and results. -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct FunctionType<'a> { /// The parameters of a function, optionally each having an identifier for /// name resolution and a name for the custom `name` section. - pub params: Vec<(Option>, Option>, ValType<'a>)>, + pub params: Vec<( + Option>, + Option>, + ValType<'a>, + )>, /// The results types of a function. pub results: Vec>, } @@ -342,7 +346,6 @@ impl<'a> FunctionType<'a> { impl<'a> Parse<'a> for FunctionType<'a> { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; let mut ret = FunctionType { params: Vec::new(), results: Vec::new(), @@ -352,6 +355,54 @@ impl<'a> Parse<'a> for FunctionType<'a> { } } +impl<'a> Peek for FunctionType<'a> { + fn peek(cursor: Cursor<'_>) -> bool { + if let Some(next) = cursor.lparen() { + match next.keyword() { + Some(("param", _)) | Some(("result", _)) => return true, + _ => {} + } + } + + false + } + + fn display() -> &'static str { + "function type" + } +} + +/// A function type with parameters and results. +#[derive(Clone, Debug, Default)] +pub struct FunctionTypeNoNames<'a>(pub FunctionType<'a>); + +impl<'a> Parse<'a> for FunctionTypeNoNames<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut ret = FunctionType { + params: Vec::new(), + results: Vec::new(), + }; + ret.finish_parse(false, parser)?; + Ok(FunctionTypeNoNames(ret)) + } +} + +impl<'a> Peek for FunctionTypeNoNames<'a> { + fn peek(cursor: Cursor<'_>) -> bool { + FunctionType::peek(cursor) + } + + fn display() -> &'static str { + FunctionType::display() + } +} + +impl<'a> From> for FunctionType<'a> { + fn from(ty: FunctionTypeNoNames<'a>) -> FunctionType<'a> { + ty.0 + } +} + /// A struct type with fields. #[derive(Clone, Debug)] pub struct StructType<'a> { @@ -361,10 +412,7 @@ pub struct StructType<'a> { impl<'a> Parse<'a> for StructType<'a> { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; - let mut ret = StructType { - fields: Vec::new(), - }; + let mut ret = StructType { fields: Vec::new() }; while !parser.is_empty() { let field = if parser.peek2::() { parser.parens(|parser| { @@ -393,11 +441,7 @@ pub struct StructField<'a> { impl<'a> StructField<'a> { fn parse(parser: Parser<'a>, with_id: bool) -> Result { - let id = if with_id { - parser.parse()? - } else { - None - }; + let id = if with_id { parser.parse()? } else { None }; let (ty, mutable) = if parser.peek2::() { let ty = parser.parens(|parser| { parser.parse::()?; @@ -407,11 +451,7 @@ impl<'a> StructField<'a> { } else { (parser.parse::>()?, false) }; - Ok(StructField { - id, - mutable, - ty, - }) + Ok(StructField { id, mutable, ty }) } } @@ -426,7 +466,6 @@ pub struct ArrayType<'a> { impl<'a> Parse<'a> for ArrayType<'a> { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; let (ty, mutable) = if parser.peek2::() { let ty = parser.parens(|parser| { parser.parse::()?; @@ -436,13 +475,119 @@ impl<'a> Parse<'a> for ArrayType<'a> { } else { (parser.parse::>()?, false) }; - Ok(ArrayType { - mutable, - ty + Ok(ArrayType { mutable, ty }) + } +} + +/// A type for a nested module +#[derive(Clone, Debug, Default)] +pub struct ModuleType<'a> { + /// The imports that are expected for this module type. + pub imports: Vec>, + /// The exports that this module type is expected to have. + pub exports: Vec>, + /// Instances within this module which are entirely exported. + pub instance_exports: Vec<(ast::Span, ast::Id<'a>)>, +} + +impl<'a> Parse<'a> for ModuleType<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut imports = Vec::new(); + while parser.peek2::() { + imports.push(parser.parens(|p| p.parse())?); + } + let mut exports = Vec::new(); + let mut instance_exports = Vec::new(); + while parser.peek2::() { + parser.parens(|p| { + if p.peek2::() { + let span = p.parse::()?.0; + instance_exports.push((span, p.parse()?)); + } else { + exports.push(p.parse()?); + } + Ok(()) + })?; + } + Ok(ModuleType { + imports, + exports, + instance_exports, }) } } +impl<'a> Peek for ModuleType<'a> { + fn peek(cursor: Cursor<'_>) -> bool { + if let Some(next) = cursor.lparen() { + match next.keyword() { + Some(("import", _)) | Some(("export", _)) => return true, + _ => {} + } + } + + false + } + + fn display() -> &'static str { + "module type" + } +} + +/// A type for a nested instance +#[derive(Clone, Debug, Default)] +pub struct InstanceType<'a> { + /// The exported types from this instance + pub exports: Vec>, +} + +impl<'a> Parse<'a> for InstanceType<'a> { + fn parse(parser: Parser<'a>) -> Result { + let mut exports = Vec::new(); + while !parser.is_empty() { + exports.push(parser.parens(|p| p.parse())?); + } + Ok(InstanceType { exports }) + } +} + +impl<'a> Peek for InstanceType<'a> { + fn peek(cursor: Cursor<'_>) -> bool { + if let Some(next) = cursor.lparen() { + match next.keyword() { + Some(("export", _)) => return true, + _ => {} + } + } + + false + } + + fn display() -> &'static str { + "instance type" + } +} + +/// The type of an exported item from a module or instance. +#[derive(Debug, Clone)] +pub struct ExportType<'a> { + /// Where this export was defined. + pub span: ast::Span, + /// The name of this export. + pub name: &'a str, + /// The signature of the item that's exported. + pub item: ast::ItemSig<'a>, +} + +impl<'a> Parse<'a> for ExportType<'a> { + fn parse(parser: Parser<'a>) -> Result { + let span = parser.parse::()?.0; + let name = parser.parse()?; + let item = parser.parens(|p| p.parse())?; + Ok(ExportType { span, name, item }) + } +} + /// A definition of a type. #[derive(Debug)] pub enum TypeDef<'a> { @@ -452,11 +597,17 @@ pub enum TypeDef<'a> { Struct(StructType<'a>), /// An array type definition. Array(ArrayType<'a>), + /// A module type definition. + Module(ModuleType<'a>), + /// An instance type definition. + Instance(InstanceType<'a>), } /// A type declaration in a module #[derive(Debug)] pub struct Type<'a> { + /// Where this type was defined. + pub span: ast::Span, /// An optional identifer to refer to this `type` by as part of name /// resolution. pub id: Option>, @@ -466,77 +617,74 @@ pub struct Type<'a> { impl<'a> Parse<'a> for Type<'a> { fn parse(parser: Parser<'a>) -> Result { - parser.parse::()?; + let span = parser.parse::()?.0; let id = parser.parse()?; let def = parser.parens(|parser| { let mut l = parser.lookahead1(); if l.peek::() { + parser.parse::()?; Ok(TypeDef::Func(parser.parse()?)) } else if l.peek::() { + parser.parse::()?; Ok(TypeDef::Struct(parser.parse()?)) } else if l.peek::() { + parser.parse::()?; Ok(TypeDef::Array(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(TypeDef::Module(parser.parse()?)) + } else if l.peek::() { + parser.parse::()?; + Ok(TypeDef::Instance(parser.parse()?)) } else { Err(l.error()) } })?; - Ok(Type { id, def }) + Ok(Type { span, id, def }) } } /// A reference to a type defined in this module. -/// -/// This is a pretty tricky type used in a lot of places and is somewhat subtly -/// handled as well. In general `(type)` or `(param)` annotations are parsed as -/// this. #[derive(Clone, Debug)] -pub struct TypeUse<'a> { - /// The span of the index specifier, if it was found - pub index_span: Option, +pub struct TypeUse<'a, T> { /// The type that we're referencing, if it was present. pub index: Option>, - /// The inline function type defined. If nothing was defined inline this is - /// empty. - pub func_ty: ast::FunctionType<'a>, + /// The inline type, if present. + pub inline: Option, } -impl<'a> TypeUse<'a> { - /// Parse a `TypeUse`, but don't allow any names of `param` tokens. - pub fn parse_no_names(parser: Parser<'a>) -> Result { - TypeUse::parse_allow_names(parser, false) +impl<'a, T> TypeUse<'a, T> { + /// Constructs a new instance of `TypeUse` without an inline definition but + /// with an index specified. + pub fn new_with_index(index: ast::Index<'a>) -> TypeUse<'a, T> { + TypeUse { + index: Some(index), + inline: None, + } } +} - fn parse_allow_names(parser: Parser<'a>, allow_names: bool) -> Result { +impl<'a, T: Peek + Parse<'a>> Parse<'a> for TypeUse<'a, T> { + fn parse(parser: Parser<'a>) -> Result { let index = if parser.peek2::() { Some(parser.parens(|parser| { parser.parse::()?; - Ok((parser.cur_span(), parser.parse()?)) + Ok(parser.parse()?) })?) } else { None }; - let (index_span, index) = match index { - Some((a, b)) => (Some(a), Some(b)), - None => (None, None), - }; - let mut func_ty = FunctionType { - params: Vec::new(), - results: Vec::new(), - }; - if parser.peek2::() || parser.peek2::() { - func_ty.finish_parse(allow_names, parser)?; - } + let inline = parser.parse()?; - Ok(TypeUse { - index, - index_span, - func_ty, - }) + Ok(TypeUse { index, inline }) } } -impl<'a> Parse<'a> for TypeUse<'a> { - fn parse(parser: Parser<'a>) -> Result { - TypeUse::parse_allow_names(parser, true) +impl<'a> From>> for TypeUse<'a, FunctionType<'a>> { + fn from(src: TypeUse<'a, FunctionTypeNoNames<'a>>) -> TypeUse<'a, FunctionType<'a>> { + TypeUse { + index: src.index, + inline: src.inline.map(|x| x.into()), + } } } diff --git a/crates/wast/src/binary.rs b/crates/wast/src/binary.rs index 4d4c5b8b3a..a8b83a05dd 100644 --- a/crates/wast/src/binary.rs +++ b/crates/wast/src/binary.rs @@ -1,16 +1,20 @@ use crate::ast::*; pub fn encode(module: &Module<'_>) -> Vec { + match &module.kind { + ModuleKind::Text(fields) => encode_fields(&module.id, &module.name, fields), + ModuleKind::Binary(bytes) => bytes.iter().flat_map(|b| b.iter().cloned()).collect(), + } +} + +fn encode_fields( + module_id: &Option>, + module_name: &Option>, + fields: &[ModuleField<'_>], +) -> Vec { use crate::ast::CustomPlace::*; use crate::ast::CustomPlaceAnchor::*; - let fields = match &module.kind { - ModuleKind::Text(fields) => fields, - ModuleKind::Binary(bytes) => { - return bytes.iter().flat_map(|b| b.iter().cloned()).collect(); - } - }; - let mut types = Vec::new(); let mut imports = Vec::new(); let mut funcs = Vec::new(); @@ -23,6 +27,9 @@ pub fn encode(module: &Module<'_>) -> Vec { let mut data = Vec::new(); let mut events = Vec::new(); let mut customs = Vec::new(); + let mut instances = Vec::new(); + let mut modules = Vec::new(); + let mut aliases = Vec::new(); for field in fields { match field { ModuleField::Type(i) => types.push(i), @@ -32,11 +39,15 @@ pub fn encode(module: &Module<'_>) -> Vec { ModuleField::Memory(i) => memories.push(i), ModuleField::Global(i) => globals.push(i), ModuleField::Export(i) => exports.push(i), + ModuleField::ExportAll(..) => panic!("should not be present for encoding"), ModuleField::Start(i) => start.push(i), ModuleField::Elem(i) => elem.push(i), ModuleField::Data(i) => data.push(i), ModuleField::Event(i) => events.push(i), ModuleField::Custom(i) => customs.push(i), + ModuleField::Instance(i) => instances.push(i), + ModuleField::NestedModule(i) => modules.push(i), + ModuleField::Alias(a) => aliases.push(a), } } @@ -49,8 +60,62 @@ pub fn encode(module: &Module<'_>) -> Vec { e.wasm.extend(b"\x01\0\0\0"); e.custom_sections(BeforeFirst); - e.section_list(1, Type, &types); - e.section_list(2, Import, &imports); + + // let moduletys = modules + // .iter() + // .map(|m| match &m.kind { + // NestedModuleKind::Inline { ty, .. } => ty.as_ref().expect("type should be filled in"), + // _ => panic!("only inline modules should be present now"), + // }) + // .collect::>(); + // e.section_list(100, Module, &moduletys); + + let mut items = fields + .iter() + .filter(|i| match i { + ModuleField::Alias(_) + | ModuleField::Type(_) + | ModuleField::Import(_) + | ModuleField::NestedModule(_) + | ModuleField::Instance(_) => true, + _ => false, + }) + .peekable(); + + // A special path is used for now to handle non-module-linking modules to + // work around WebAssembly/annotations#11 + if aliases.len() == 0 && modules.len() == 0 && instances.len() == 0 { + e.section_list(1, Type, &types); + e.section_list(2, Import, &imports); + } else { + while let Some(field) = items.next() { + macro_rules! list { + ($code:expr, $name:ident) => { + list!($code, $name, $name, |f| f) + }; + ($code:expr, $field:ident, $custom:ident, |$f:ident| $e:expr) => { + if let ModuleField::$field($f) = field { + let mut list = vec![$e]; + while let Some(ModuleField::$field($f)) = items.peek() { + list.push($e); + items.next(); + } + e.section_list($code, $custom, &list); + } + }; + } + list!(1, Type); + list!(2, Import); + list!(100, NestedModule, Module, |m| match &m.kind { + NestedModuleKind::Inline { ty, .. } => + ty.as_ref().expect("type should be filled in"), + _ => panic!("only inline modules should be present now"), + }); + list!(101, Instance); + list!(102, Alias); + } + } + let functys = funcs.iter().map(|f| &f.ty).collect::>(); e.section_list(3, Func, &functys); e.section_list(4, Table, &tables); @@ -67,10 +132,11 @@ pub fn encode(module: &Module<'_>) -> Vec { if contains_bulk_memory(&funcs) { e.section(12, &data.len()); } + e.section_list(103, ModuleCode, &modules); e.section_list(10, Code, &funcs); e.section_list(11, Data, &data); - let names = find_names(module, fields); + let names = find_names(module_id, module_name, fields); if !names.is_empty() { e.section(0, &("name", names)); } @@ -208,6 +274,27 @@ impl Encode for ArrayType<'_> { } } +impl Encode for ModuleType<'_> { + fn encode(&self, e: &mut Vec) { + self.imports.encode(e); + self.exports.encode(e); + assert!(self.instance_exports.is_empty()); + } +} + +impl Encode for InstanceType<'_> { + fn encode(&self, e: &mut Vec) { + self.exports.encode(e); + } +} + +impl Encode for ExportType<'_> { + fn encode(&self, e: &mut Vec) { + self.name.encode(e); + self.item.encode(e); + } +} + impl Encode for Type<'_> { fn encode(&self, e: &mut Vec) { match &self.def { @@ -223,6 +310,14 @@ impl Encode for Type<'_> { e.push(0x5e); array.encode(e) } + TypeDef::Module(module) => { + e.push(0x61); + module.encode(e) + } + TypeDef::Instance(instance) => { + e.push(0x62); + instance.encode(e) + } } } } @@ -284,33 +379,53 @@ impl<'a> Encode for RefType<'a> { impl Encode for Import<'_> { fn encode(&self, e: &mut Vec) { self.module.encode(e); - self.field.encode(e); + match self.field { + Some(s) => s.encode(e), + None => { + e.push(0x01); + e.push(0xc0); + } + } + self.item.encode(e); + } +} + +impl Encode for ItemSig<'_> { + fn encode(&self, e: &mut Vec) { match &self.kind { - ImportKind::Func(f) => { + ItemKind::Func(f) => { e.push(0x00); f.encode(e); } - ImportKind::Table(f) => { + ItemKind::Table(f) => { e.push(0x01); f.encode(e); } - ImportKind::Memory(f) => { + ItemKind::Memory(f) => { e.push(0x02); f.encode(e); } - ImportKind::Global(f) => { + ItemKind::Global(f) => { e.push(0x03); f.encode(e); } - ImportKind::Event(f) => { + ItemKind::Event(f) => { e.push(0x04); f.encode(e); } + ItemKind::Module(m) => { + e.push(0x05); + m.encode(e); + } + ItemKind::Instance(i) => { + e.push(0x06); + i.encode(e); + } } } } -impl Encode for TypeUse<'_> { +impl Encode for TypeUse<'_, T> { fn encode(&self, e: &mut Vec) { self.index .as_ref() @@ -322,8 +437,8 @@ impl Encode for TypeUse<'_> { impl Encode for Index<'_> { fn encode(&self, e: &mut Vec) { match self { - Index::Num(n) => n.encode(e), - Index::Id(n) => panic!("unresolved index in emission: {}", n.name()), + Index::Num(n, _) => n.encode(e), + Index::Id(n) => panic!("unresolved index in emission: {:?}", n), } } } @@ -419,7 +534,13 @@ impl Encode for Global<'_> { impl Encode for Export<'_> { fn encode(&self, e: &mut Vec) { self.name.encode(e); - match &self.kind { + self.kind.encode(e); + } +} + +impl Encode for ExportKind<'_> { + fn encode(&self, e: &mut Vec) { + match self { ExportKind::Func(f) => { e.push(0x00); f.encode(e); @@ -440,6 +561,18 @@ impl Encode for Export<'_> { e.push(0x04); f.encode(e); } + ExportKind::Module(f) => { + e.push(0x05); + f.encode(e); + } + ExportKind::Instance(f) => { + e.push(0x06); + f.encode(e); + } + ExportKind::Type(f) => { + e.push(0x07); + f.encode(e); + } } } } @@ -462,7 +595,7 @@ impl Encode for Elem<'_> { match (&self.kind, &to_encode) { ( ElemKind::Active { - table: Index::Num(0), + table: Index::Num(0, _), offset, }, ElemPayload::Indices(_), @@ -482,7 +615,7 @@ impl Encode for Elem<'_> { } ( ElemKind::Active { - table: Index::Num(0), + table: Index::Num(0, _), offset, }, ElemPayload::Exprs { @@ -548,7 +681,7 @@ impl Encode for Data<'_> { match &self.kind { DataKind::Passive => e.push(0x01), DataKind::Active { memory, offset } => { - if *memory == Index::Num(0) { + if let Index::Num(0, _) = memory { e.push(0x00); } else { e.push(0x02); @@ -603,14 +736,19 @@ impl Encode for Expression<'_> { impl Encode for BlockType<'_> { fn encode(&self, e: &mut Vec) { // block types using an index are encoded as an sleb, not a uleb - if let Some(Index::Num(n)) = &self.ty.index { + if let Some(Index::Num(n, _)) = &self.ty.index { return i64::from(*n).encode(e); } - if self.ty.func_ty.params.is_empty() && self.ty.func_ty.results.is_empty() { + let ty = self + .ty + .inline + .as_ref() + .expect("function type not filled in"); + if ty.params.is_empty() && ty.results.is_empty() { return e.push(0x40); } - if self.ty.func_ty.params.is_empty() && self.ty.func_ty.results.len() == 1 { - return self.ty.func_ty.results[0].encode(e); + if ty.params.is_empty() && ty.results.len() == 1 { + return ty.results[0].encode(e); } panic!("multi-value block types should have an index"); } @@ -688,9 +826,19 @@ struct Names<'a> { locals: Vec<(u32, Vec<(u32, &'a str)>)>, } -fn find_names<'a>(module: &Module<'a>, fields: &[ModuleField<'a>]) -> Names<'a> { +fn find_names<'a>( + module_id: &Option>, + module_name: &Option>, + fields: &[ModuleField<'a>], +) -> Names<'a> { fn get_name<'a>(id: &Option>, name: &Option>) -> Option<&'a str> { - name.as_ref().map(|n| n.name).or(id.map(|id| id.name())) + name.as_ref().map(|n| n.name).or(id.and_then(|id| { + if id.is_gensym() { + None + } else { + Some(id.name()) + } + })) } let mut funcs = Vec::new(); @@ -699,12 +847,12 @@ fn find_names<'a>(module: &Module<'a>, fields: &[ModuleField<'a>]) -> Names<'a> for field in fields { match field { ModuleField::Import(i) => { - match i.kind { - ImportKind::Func(_) => {} + match i.item.kind { + ItemKind::Func(_) => {} _ => continue, } - if let Some(name) = get_name(&i.id, &i.name) { + if let Some(name) = get_name(&i.item.id, &i.item.name) { funcs.push((idx, name)); } @@ -716,11 +864,18 @@ fn find_names<'a>(module: &Module<'a>, fields: &[ModuleField<'a>]) -> Names<'a> } let mut local_names = Vec::new(); let mut local_idx = 0; - for (id, name, _) in f.ty.func_ty.params.iter() { - if let Some(name) = get_name(id, name) { - local_names.push((local_idx, name)); + + // Consult the inline type listed for local names of parameters. + // This is specifically preserved during the name resolution + // pass, but only for functions, so here we can look at the + // original source's names. + if let Some(ty) = &f.ty.inline { + for (id, name, _) in ty.params.iter() { + if let Some(name) = get_name(id, name) { + local_names.push((local_idx, name)); + } + local_idx += 1; } - local_idx += 1; } if let FuncKind::Inline { locals, .. } = &f.kind { for (id, name, _) in locals { @@ -735,12 +890,23 @@ fn find_names<'a>(module: &Module<'a>, fields: &[ModuleField<'a>]) -> Names<'a> } idx += 1; } + ModuleField::Alias(Alias { + id, + name, + kind: ExportKind::Func(_), + .. + }) => { + if let Some(name) = get_name(id, name) { + funcs.push((idx, name)); + } + idx += 1; + } _ => {} } } Names { - module: get_name(&module.id, &module.name), + module: get_name(module_id, module_name), funcs, locals, } @@ -779,6 +945,7 @@ impl Encode for Names<'_> { impl Encode for Id<'_> { fn encode(&self, dst: &mut Vec) { + assert!(!self.is_gensym()); self.name().encode(dst); } } @@ -852,3 +1019,40 @@ impl Encode for StructNarrow<'_> { self.to.encode(e); } } + +impl Encode for NestedModule<'_> { + fn encode(&self, e: &mut Vec) { + let fields = match &self.kind { + NestedModuleKind::Inline { fields, .. } => fields, + _ => panic!("should only have inline modules in emission"), + }; + + encode_fields(&self.id, &self.name, fields).encode(e); + } +} + +impl Encode for Instance<'_> { + fn encode(&self, e: &mut Vec) { + assert!(self.exports.names.is_empty()); + let (module, items) = match &self.kind { + InstanceKind::Inline { module, items } => (module, items), + _ => panic!("should only have inline instances in emission"), + }; + + module.encode(e); + items.encode(e); + } +} + +impl Encode for Alias<'_> { + fn encode(&self, e: &mut Vec) { + match self.instance { + Some(instance) => { + e.push(0x00); + instance.encode(e); + } + None => e.push(0x01), + } + self.kind.encode(e); + } +} diff --git a/crates/wast/src/parser.rs b/crates/wast/src/parser.rs index 0af1783744..07cc465e19 100644 --- a/crates/wast/src/parser.rs +++ b/crates/wast/src/parser.rs @@ -709,6 +709,11 @@ impl<'a> Parser<'a> { self.cursor().cur_span() } + /// Returns the span of the previous token + pub fn prev_span(&self) -> Span { + self.cursor().prev_span().unwrap_or(Span::from_offset(0)) + } + /// Registers a new known annotation with this parser to allow parsing /// annotations with this name. /// @@ -873,6 +878,16 @@ impl<'a> Cursor<'a> { Span { offset } } + /// Returns the span of the previous `Token` token. + /// + /// Does not take into account whitespace or comments. + pub(crate) fn prev_span(&self) -> Option { + let (token, _) = self.parser.buf.tokens.get(self.cur.checked_sub(1)?)?; + Some(Span { + offset: self.parser.buf.input_pos(token.src()), + }) + } + /// Same as [`Parser::error`], but works with the current token in this /// [`Cursor`] instead. pub fn error(&self, msg: impl fmt::Display) -> Error { diff --git a/crates/wast/src/resolve/deinline_import_export.rs b/crates/wast/src/resolve/deinline_import_export.rs new file mode 100644 index 0000000000..1089fcdf42 --- /dev/null +++ b/crates/wast/src/resolve/deinline_import_export.rs @@ -0,0 +1,274 @@ +use crate::ast::*; +use crate::resolve::gensym; +use std::mem; + +pub fn run(fields: &mut Vec) { + let mut cur = 0; + let mut to_append = Vec::new(); + while cur < fields.len() { + let item = &mut fields[cur]; + match item { + ModuleField::Func(f) => { + for name in f.exports.names.drain(..) { + let id = gensym::fill(f.span, &mut f.id); + to_append.push(ModuleField::Export(Export { + span: f.span, + name, + kind: ExportKind::Func(Index::Id(id)), + })); + } + match f.kind { + FuncKind::Import(import) => { + *item = ModuleField::Import(Import { + span: f.span, + module: import.module, + field: import.field, + item: ItemSig { + span: f.span, + id: f.id, + name: f.name, + kind: ItemKind::Func(f.ty.clone()), + }, + }); + } + FuncKind::Inline { .. } => {} + } + } + + ModuleField::Memory(m) => { + for name in m.exports.names.drain(..) { + let id = gensym::fill(m.span, &mut m.id); + to_append.push(ModuleField::Export(Export { + span: m.span, + name, + kind: ExportKind::Memory(Index::Id(id)), + })); + } + match m.kind { + MemoryKind::Import { import, ty } => { + *item = ModuleField::Import(Import { + span: m.span, + module: import.module, + field: import.field, + item: ItemSig { + span: m.span, + id: m.id, + name: None, + kind: ItemKind::Memory(ty), + }, + }); + } + // If data is defined inline insert an explicit `data` module + // field here instead, switching this to a `Normal` memory. + MemoryKind::Inline(ref data) => { + let len = data.iter().map(|l| l.len()).sum::() as u32; + let pages = (len + page_size() - 1) / page_size(); + let kind = MemoryKind::Normal(MemoryType { + limits: Limits { + min: pages, + max: Some(pages), + }, + shared: false, + }); + let data = match mem::replace(&mut m.kind, kind) { + MemoryKind::Inline(data) => data, + _ => unreachable!(), + }; + let id = gensym::fill(m.span, &mut m.id); + to_append.push(ModuleField::Data(Data { + span: m.span, + id: None, + kind: DataKind::Active { + memory: Index::Id(id), + offset: Expression { + instrs: vec![Instruction::I32Const(0)], + }, + }, + data, + })); + } + + MemoryKind::Normal(_) => {} + } + } + + ModuleField::Table(t) => { + for name in t.exports.names.drain(..) { + let id = gensym::fill(t.span, &mut t.id); + to_append.push(ModuleField::Export(Export { + span: t.span, + name, + kind: ExportKind::Table(Index::Id(id)), + })); + } + match &mut t.kind { + TableKind::Import { import, ty } => { + *item = ModuleField::Import(Import { + span: t.span, + module: import.module, + field: import.field, + item: ItemSig { + span: t.span, + id: t.id, + name: None, + kind: ItemKind::Table(*ty), + }, + }); + } + // If data is defined inline insert an explicit `data` module + // field here instead, switching this to a `Normal` memory. + TableKind::Inline { payload, elem } => { + let len = match payload { + ElemPayload::Indices(v) => v.len(), + ElemPayload::Exprs { exprs, .. } => exprs.len(), + }; + let kind = TableKind::Normal(TableType { + limits: Limits { + min: len as u32, + max: Some(len as u32), + }, + elem: *elem, + }); + let payload = match mem::replace(&mut t.kind, kind) { + TableKind::Inline { payload, .. } => payload, + _ => unreachable!(), + }; + let id = gensym::fill(t.span, &mut t.id); + to_append.push(ModuleField::Elem(Elem { + span: t.span, + id: None, + kind: ElemKind::Active { + table: Index::Id(id), + offset: Expression { + instrs: vec![Instruction::I32Const(0)], + }, + }, + payload, + })); + } + + TableKind::Normal(_) => {} + } + } + + ModuleField::Global(g) => { + for name in g.exports.names.drain(..) { + let id = gensym::fill(g.span, &mut g.id); + to_append.push(ModuleField::Export(Export { + span: g.span, + name, + kind: ExportKind::Global(Index::Id(id)), + })); + } + match g.kind { + GlobalKind::Import(import) => { + *item = ModuleField::Import(Import { + span: g.span, + module: import.module, + field: import.field, + item: ItemSig { + span: g.span, + id: g.id, + name: None, + kind: ItemKind::Global(g.ty), + }, + }); + } + GlobalKind::Inline { .. } => {} + } + } + + ModuleField::Event(e) => { + for name in e.exports.names.drain(..) { + let id = gensym::fill(e.span, &mut e.id); + to_append.push(ModuleField::Export(Export { + span: e.span, + name, + kind: ExportKind::Event(Index::Id(id)), + })); + } + } + + ModuleField::Instance(i) => { + for name in i.exports.names.drain(..) { + let id = gensym::fill(i.span, &mut i.id); + to_append.push(ModuleField::Export(Export { + span: i.span, + name, + kind: ExportKind::Instance(Index::Id(id)), + })); + } + match &mut i.kind { + InstanceKind::Import { import, ty } => { + *item = ModuleField::Import(Import { + span: i.span, + module: import.module, + field: import.field, + item: ItemSig { + span: i.span, + id: i.id, + name: None, + kind: ItemKind::Instance(mem::replace( + ty, + TypeUse::new_with_index(Index::Num(0, Span::from_offset(0))), + )), + }, + }); + } + InstanceKind::Inline { .. } => {} + } + } + + ModuleField::NestedModule(m) => { + for name in m.exports.names.drain(..) { + let id = gensym::fill(m.span, &mut m.id); + to_append.push(ModuleField::Export(Export { + span: m.span, + name, + kind: ExportKind::Module(Index::Id(id)), + })); + } + match &mut m.kind { + NestedModuleKind::Import { import, ty } => { + *item = ModuleField::Import(Import { + span: m.span, + module: import.module, + field: import.field, + item: ItemSig { + span: m.span, + id: m.id, + name: m.name, + kind: ItemKind::Module(mem::replace( + ty, + TypeUse::new_with_index(Index::Num(0, Span::from_offset(0))), + )), + }, + }); + } + NestedModuleKind::Inline { fields, .. } => { + run(fields); + } + }; + } + + ModuleField::ExportAll(..) + | ModuleField::Import(_) + | ModuleField::Type(_) + | ModuleField::Export(_) + | ModuleField::Alias(_) + | ModuleField::Start(_) + | ModuleField::Elem(_) + | ModuleField::Data(_) + | ModuleField::Custom(_) => {} + } + + fields.splice(cur..cur, to_append.drain(..)); + cur += 1; + } + + assert!(to_append.is_empty()); + + fn page_size() -> u32 { + 1 << 16 + } +} diff --git a/crates/wast/src/resolve/expand.rs b/crates/wast/src/resolve/expand.rs index 9eb81691e8..7709c4a47f 100644 --- a/crates/wast/src/resolve/expand.rs +++ b/crates/wast/src/resolve/expand.rs @@ -1,18 +1,74 @@ use crate::ast::*; -use std::mem; +use crate::resolve::gensym; +use crate::resolve::Ns; +use crate::Error; +use std::collections::{HashMap, HashSet}; + +/// Runs an expansion process on the fields provided to elaborate and expand +/// features from the module-linking proposal. Namely this handles> +/// +/// * Injection of child `alias` directives to fill in `$a.$b` identifiers +/// * Injection of parent `alias` directives for names that are likely to be +/// resolved from the parent. +/// * Expansion of export-all annotations in modules definitions and types. +/// +/// Note that this pass runs before name resolution so no indices are worked +/// with as part of this pass. Instead everything internally works off +/// identifiers and new items have generated identifiers assigned to them. +pub fn run(fields: &mut Vec) -> Result<(), Error> { + Expander::default().process(fields, None) +} #[derive(Default)] -pub struct Expander<'a> { +struct Expander<'a> { to_append: Vec>, - funcs: u32, - memories: u32, - tables: u32, - globals: u32, - events: u32, + + /// The set of all names reference in a module. Each missing name has an + /// associated namespace with it as well for which kind of item it's + /// referring to. + /// + /// This map is built as part of `record_missing_name`. + all_missing: HashSet<(Id<'a>, Ns)>, + + /// This is the set of all names defined in this module. Each name is + /// defined in a particular namespace as well. + /// + /// This map is built as part of `record_missing_name`. + defined_names: HashSet<(Id<'a>, Ns)>, + + /// This is the set of all missing names which might be filled in as + /// `$a.$b` names automatically. The first level of this hash map is the + /// instance name it would come from (e.g. "a") and the second level contains + /// the item name and namespace that would be used (e.g. "b" and Ns::Func). + /// + /// The final value in the map is the full `$a.$b` identifier used to + /// synthesize as the name of `alias` annotations. + /// + /// This map is built as part of `record_missing_name`. + missing_names: HashMap<&'a str, HashMap<(&'a str, Ns), Id<'a>>>, + + /// This map contains information about the exports of a module, used to + /// fill in export-all annotations. This is only recorded for inline modules + /// which have a name associated with them. + module_exports: HashMap, ExportInfo<'a>>, + + /// This map contains info about the exports of locally defined instances. + /// Each locally defined instance, with a name, is recorded here about how + /// we can learn about its export information. + instance_exports: HashMap, InstanceExports<'a>>, + + /// This map is similar to the above, but for instance types rather than + /// instances themselves. + instance_type_exports: HashMap, Vec>>, } -fn page_size() -> u32 { - 1 << 16 +enum InstanceExports<'a> { + /// This instance was an import and we have its export information available + /// inline from the definiton. + Inline(ExportInfo<'a>), + /// This instance is the instantiation of the module listed here. The + /// `module_exports` map will need to be consulted for export information. + Module(Id<'a>), } impl<'a> Expander<'a> { @@ -20,255 +76,634 @@ impl<'a> Expander<'a> { /// /// This method will handle the `to_append` field of this `Expander`, /// appending items after processing a `ModuleField` as necessary. - pub fn process( + fn process( + &mut self, + fields: &mut Vec>, + parent: Option<&Expander<'a>>, + ) -> Result<(), Error> { + // The first step here is to record information about defined/missing + // names in this module. At the same time we also scrape information + // about defined modules to learn about the exports of inline modules. + for field in fields.iter_mut() { + self.record_missing_name(field); + self.discover_module_names(field); + } + + // Next up we can start injecting alias annotations from our parent. Any + // name defined in our parent but not defined locally is inherited with + // an `Alias` annotation pointing to the parent. + if let Some(parent) = parent { + let mut names_to_add = self + .all_missing + .iter() + // skip anything defined in our own module ... + .filter(|key| !self.defined_names.contains(key)) + // ... and also skip anything that's not defined in the parent + // module + .filter(|key| parent.defined_names.contains(key)) + .collect::>(); + + // Ensure we have deterministic output by inserting aliases in a + // known order. + names_to_add.sort_by_key(|e| e.0.name()); + + for key in names_to_add { + // Inject an `(alias (parent ...))` annotation now that we know + // we're referencing an item from the parent module. + // + // For now we insert this at the front since these items may be + // referred to by later items (like types and modules), and + // inserting them at the front ensure that they're defined early + // in the index space. + fields.insert( + 0, + ModuleField::Alias(Alias { + id: Some(key.0), + instance: None, + name: None, + span: key.0.span(), + kind: key.1.to_export_kind(Index::Id(key.0)), + }), + ); + + // Now that we've defined a new name in our module we want to + // copy over all the metadata learned about that item into our + // module as well, so when the name is used in our module we + // still retain all metadata about it. + self.defined_names.insert(*key); + match key.1 { + Ns::Module => { + if let Some(info) = parent.module_exports.get(&key.0) { + self.module_exports.insert(key.0, info.clone()); + } + } + Ns::Type => { + if let Some(list) = parent.instance_type_exports.get(&key.0) { + self.instance_type_exports.insert(key.0, list.clone()); + } + } + // When copying over information about instances we just + // interpret everything as information listed inline to + // avoid the go-through-the-module-map indirection. + Ns::Instance => { + let info = match parent.instance_exports.get(&key.0) { + Some(InstanceExports::Inline(info)) => info, + Some(InstanceExports::Module(module_id)) => { + &parent.module_exports[module_id] + } + None => continue, + }; + self.instance_exports + .insert(key.0, InstanceExports::Inline(info.clone())); + } + _ => {} + } + } + } + + // After we've dealt with inherited identifiers from our parent we can + // start injecting aliases after local `instance` definitions based on + // referenced identifiers elsewhere in this module. + self.iter_and_expand(fields, |me, field| { + me.inject_aliases(field); + Ok(true) + })?; + assert!(self.to_append.is_empty()); + + // And last for our own module we can expand all the export-all + // annotations since we have all information about nested + // modules/instances at this point. + self.iter_and_expand(fields, |me, field| me.expand_export_all(field))?; + assert!(self.to_append.is_empty()); + + // And as a final processing step we recurse into our nested modules and + // process all of them. + for field in fields.iter_mut() { + if let ModuleField::NestedModule(m) = field { + if let NestedModuleKind::Inline { fields, .. } = &mut m.kind { + Expander::default().process(fields, Some(self))?; + } + } + } + Ok(()) + } + + fn discover_module_names(&mut self, field: &ModuleField<'a>) { + let (id, info) = match field { + ModuleField::Import(Import { + item: + ItemSig { + id: Some(id), + kind: + ItemKind::Module(TypeUse { + inline: Some(ty), .. + }), + .. + }, + .. + }) => (id, ExportInfo::from_exports(&ty.exports)), + + ModuleField::NestedModule(NestedModule { + id: Some(id), + kind: NestedModuleKind::Inline { fields, .. }, + .. + }) => { + // This is a bit of a cop-out at this point, but we don't really + // have full name resolution info available at this point. As a + // result we just consider `(export "" (func $foo))` as a + // candidate that `$foo` is available from this module for + // referencing. If you do, however, `(export "" (func 0))` and + // the 0th function is `$foo`, we won't realize that. + let mut info = ExportInfo::default(); + for field in fields { + let export = match field { + ModuleField::Export(e) => e, + _ => continue, + }; + let (idx, ns) = Ns::from_export(&export.kind); + if let Index::Id(id) = idx { + info.wat_name_to_export_idx + .insert((id.name(), ns), info.export_names.len() as u32); + } + info.export_names.push((export.name, ns)); + } + (id, info) + } + + _ => return, + }; + self.module_exports.insert(*id, info); + } + + /// Processes each element of `fields` individually and then inserts all + /// items in `self.to_append` after that field. + fn iter_and_expand( &mut self, fields: &mut Vec>, - mut f: impl FnMut(&mut Self, &mut ModuleField<'a>), - ) { + f: impl Fn(&mut Self, &mut ModuleField<'a>) -> Result, + ) -> Result<(), Error> { + assert!(self.to_append.is_empty()); let mut cur = 0; while cur < fields.len() { - f(self, &mut fields[cur]); + let keep = f(self, &mut fields[cur])?; + if keep { + cur += 1; + } else { + fields.remove(cur); + } for new in self.to_append.drain(..) { fields.insert(cur, new); cur += 1; } - cur += 1; } + Ok(()) } - /// Inverts inline `import` descriptions into actual `import` statements. - /// - /// In doing so this also takes care of inline `export` statements, if any, - /// since we'll be removing the corresponding `ModuleField` item and - /// replacing it in-place. - /// - /// To work right, `Import` field items must be visited first. - /// - /// This will replace `item` in-place with an `Import` directive if an - /// inline import is found, and after this pass has been run no more inline - /// import directives should be present. - pub fn deinline_import(&mut self, item: &mut ModuleField<'a>) { - match item { + fn record_missing_name(&mut self, field: &ModuleField<'a>) { + match field { + ModuleField::Type(t) => { + self.record_defined(&t.id, Ns::Type); + match &t.def { + TypeDef::Module(m) => m.record_missing(self), + TypeDef::Instance(i) => { + i.record_missing(self); + if let Some(name) = t.id { + self.instance_type_exports.insert(name, i.exports.clone()); + } + } + TypeDef::Func(f) => f.record_missing(self), + TypeDef::Array(_) | TypeDef::Struct(_) => {} + } + } + + ModuleField::Import(i) => { + self.record_defined(&i.item.id, Ns::from_item(&i.item)); + self.record_item_sig(&i.item); + } + ModuleField::Func(f) => { - let (module, field) = match f.kind { - FuncKind::Import { module, field } => (module, field), - _ => return, - }; - for name in f.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Func(Index::Num(self.funcs)), - })); + self.record_defined(&f.id, Ns::Func); + self.record_type_use(&f.ty); + if let FuncKind::Inline { expression, .. } = &f.kind { + self.record_expr(expression); } - *item = ModuleField::Import(Import { - span: f.span, - module, - field, - id: f.id, - name: f.name, - kind: ImportKind::Func(f.ty.clone()), - }); - self.funcs += 1; } ModuleField::Memory(m) => { - let (module, field, ty) = match m.kind { - MemoryKind::Import { module, field, ty } => (module, field, ty), - _ => return, - }; - for name in m.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Memory(Index::Num(self.memories)), - })); - } - *item = ModuleField::Import(Import { - span: m.span, - module, - field, - id: m.id, - name: None, - kind: ImportKind::Memory(ty), - }); - self.memories += 1; + self.record_defined(&m.id, Ns::Memory); } ModuleField::Table(t) => { - let (module, field, ty) = match t.kind { - TableKind::Import { module, field, ty } => (module, field, ty), - _ => return, - }; - for name in t.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Table(Index::Num(self.tables)), - })); - } - *item = ModuleField::Import(Import { - span: t.span, - module, - field, - id: t.id, - name: None, - kind: ImportKind::Table(ty), - }); - self.tables += 1; + self.record_defined(&t.id, Ns::Table); } ModuleField::Global(g) => { - let (module, field) = match g.kind { - GlobalKind::Import { module, field } => (module, field), - _ => return, - }; - for name in g.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Global(Index::Num(self.globals)), - })); + self.record_defined(&g.id, Ns::Global); + if let GlobalKind::Inline(e) = &g.kind { + self.record_expr(e); } - *item = ModuleField::Import(Import { - span: g.span, - module, - field, - id: g.id, - name: None, - kind: ImportKind::Global(g.ty), - }); - self.globals += 1; } - ModuleField::Import(i) => match i.kind { - ImportKind::Func(_) => self.funcs += 1, - ImportKind::Memory(_) => self.memories += 1, - ImportKind::Table(_) => self.tables += 1, - ImportKind::Global(_) => self.globals += 1, - ImportKind::Event(_) => self.events += 1, - }, - - _ => {} - } - } + ModuleField::Event(e) => { + self.record_defined(&e.id, Ns::Event); + match &e.ty { + EventType::Exception(ty) => self.record_type_use(ty), + } + } - /// Extracts all inline `export` annotations and creates - /// `ModuleField::Export` items. - /// - /// Note that this executes after the `deinline_import` pass to ensure - /// indices all line up right. - pub fn deinline_export(&mut self, item: &mut ModuleField<'a>) { - match item { - ModuleField::Func(f) => { - for name in f.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Func(Index::Num(self.funcs)), - })); + ModuleField::Instance(i) => { + self.record_defined(&i.id, Ns::Instance); + if let InstanceKind::Inline { items, module, .. } = &i.kind { + self.record_missing(module, Ns::Module); + for item in items { + let (idx, ns) = Ns::from_export(item); + self.record_missing(&idx, ns); + } } - self.funcs += 1; } - ModuleField::Memory(m) => { - for name in m.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Memory(Index::Num(self.memories)), - })); + ModuleField::Start(id) => self.record_missing(id, Ns::Func), + + ModuleField::Export(e) => { + let (idx, ns) = Ns::from_export(&e.kind); + self.record_missing(&idx, ns); + } + + ModuleField::Elem(e) => { + match &e.kind { + ElemKind::Active { table, offset } => { + self.record_missing(table, Ns::Table); + self.record_expr(offset); + } + ElemKind::Passive { .. } | ElemKind::Declared { .. } => {} + } + match &e.payload { + ElemPayload::Indices(elems) => { + for idx in elems { + self.record_missing(idx, Ns::Func); + } + } + ElemPayload::Exprs { exprs, .. } => { + for funcref in exprs { + if let Some(idx) = funcref { + self.record_missing(idx, Ns::Func); + } + } + } } + } - // If data is defined inline insert an explicit `data` module - // field here instead, switching this to a `Normal` memory. - if let MemoryKind::Inline(data) = &mut m.kind { - let len = data.iter().map(|l| l.len()).sum::() as u32; - let pages = (len + page_size() - 1) / page_size(); - let kind = MemoryKind::Normal(MemoryType { - limits: Limits { - min: pages, - max: Some(pages), - }, - shared: false, - }); - let data = match mem::replace(&mut m.kind, kind) { - MemoryKind::Inline(data) => data, - _ => unreachable!(), - }; - self.to_append.push(ModuleField::Data(Data { - span: m.span, - id: None, - kind: DataKind::Active { - memory: Index::Num(self.memories), - offset: Expression { - instrs: vec![Instruction::I32Const(0)], - }, - }, - data, - })); + ModuleField::Data(d) => { + if let DataKind::Active { memory, offset } = &d.kind { + self.record_missing(memory, Ns::Memory); + self.record_expr(offset); } + } - self.memories += 1; + ModuleField::Alias(a) => { + let (_idx, ns) = Ns::from_export(&a.kind); + self.record_defined(&a.id, ns); } - ModuleField::Table(t) => { - for name in t.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Table(Index::Num(self.tables)), - })); - } + ModuleField::NestedModule(m) => { + self.record_defined(&m.id, Ns::Module); + } - // If data is defined inline insert an explicit `data` module - // field here instead, switching this to a `Normal` memory. - if let TableKind::Inline { payload, elem } = &mut t.kind { - let len = match payload { - ElemPayload::Indices(v) => v.len(), - ElemPayload::Exprs { exprs, .. } => exprs.len(), - }; - let kind = TableKind::Normal(TableType { - limits: Limits { - min: len as u32, - max: Some(len as u32), - }, - elem: *elem, - }); - let payload = match mem::replace(&mut t.kind, kind) { - TableKind::Inline { payload, .. } => payload, - _ => unreachable!(), - }; - self.to_append.push(ModuleField::Elem(Elem { - span: t.span, - id: None, - kind: ElemKind::Active { - table: Index::Num(self.tables), - offset: Expression { - instrs: vec![Instruction::I32Const(0)], - }, - }, - payload, - })); + ModuleField::ExportAll(_span, instance) => { + self.record_missing(&Index::Id(*instance), Ns::Instance); + } + + ModuleField::Custom(_) => {} + } + } + fn record_type_use(&mut self, item: &TypeUse<'a, impl TypeReference<'a>>) { + if let Some(idx) = &item.index { + self.record_missing(idx, Ns::Type); + } + if let Some(inline) = &item.inline { + inline.record_missing(self); + } + } + + fn record_item_sig(&mut self, sig: &ItemSig<'a>) { + match &sig.kind { + ItemKind::Func(f) | ItemKind::Event(EventType::Exception(f)) => self.record_type_use(f), + ItemKind::Module(m) => self.record_type_use(m), + ItemKind::Instance(i) => self.record_type_use(i), + ItemKind::Table(_) | ItemKind::Memory(_) | ItemKind::Global(_) => {} + } + } + + fn record_expr(&mut self, expr: &Expression<'a>) { + use Instruction::*; + for instr in expr.instrs.iter() { + match instr { + Block(t) | Loop(t) | If(t) | Try(t) => { + self.record_type_use(&t.ty); + } + FuncBind(f) | Call(f) | ReturnCall(f) | RefFunc(f) => { + self.record_missing(f, Ns::Func) } + CallIndirect(f) | ReturnCallIndirect(f) => { + self.record_missing(&f.table, Ns::Table); + self.record_type_use(&f.ty); + } + GlobalGet(g) | GlobalSet(g) => self.record_missing(g, Ns::Global), + TableFill(g) | TableSize(g) | TableGrow(g) | TableGet(g) | TableSet(g) => { + self.record_missing(&g.dst, Ns::Table) + } + TableInit(t) => self.record_missing(&t.table, Ns::Table), + TableCopy(t) => { + self.record_missing(&t.src, Ns::Table); + self.record_missing(&t.dst, Ns::Table); + } + _ => {} + } + } + } - self.tables += 1; + fn record_defined(&mut self, name: &Option>, kind: Ns) { + if let Some(id) = name { + self.defined_names.insert((*id, kind)); + } + } + + fn record_missing(&mut self, name: &Index<'a>, kind: Ns) { + let id = match name { + Index::Num(..) => return, + Index::Id(id) => id, + }; + self.all_missing.insert((*id, kind)); + let (instance, field) = match self.split_name(id) { + Some(p) => p, + None => return, + }; + self.missing_names + .entry(instance) + .or_insert(HashMap::new()) + .insert((field, kind), *id); + } + + /// Splits an identifier into two halves if it looks like `$a.$b`. Returns + /// `("a", "b")` in that case. + fn split_name(&self, id: &Id<'a>) -> Option<(&'a str, &'a str)> { + let name = id.name(); + let i = name.find('.')?; + let (instance, field) = (&name[..i], &name[i + 1..]); + if field.starts_with("$") { + Some((instance, &field[1..])) + } else { + None + } + } + + fn inject_aliases(&mut self, field: &ModuleField<'a>) { + let defined_names = &self.defined_names; + let (id, span, export_info) = match field { + // For imported instances, only those with a name and an inline type + // listed are candidates for expansion with `$a.$b`. + ModuleField::Import(Import { + item: + ItemSig { + id: Some(id), + span, + kind: + ItemKind::Instance(TypeUse { + inline: Some(ty), .. + }), + .. + }, + .. + }) => { + let map = self.instance_exports.entry(*id).or_insert_with(|| { + InstanceExports::Inline(ExportInfo::from_exports(&ty.exports)) + }); + ( + id, + span, + match map { + InstanceExports::Inline(i) => &*i, + // If this instance name was already seen earlier we just + // skip expansion for now and let name resolution generate + // an error about it. + InstanceExports::Module(_) => return, + }, + ) } - ModuleField::Global(g) => { - for name in g.exports.names.drain(..) { + // Of locally defined instances, only those which have a name and + // instantiate a named module are candidates for expansion with + // `$a.$b`. This isn't the greatest of situations right now but it + // should be good enough. + ModuleField::Instance(Instance { + span, + id: Some(id), + kind: + InstanceKind::Inline { + module: Index::Id(module_id), + .. + }, + .. + }) => match self.module_exports.get(module_id) { + Some(map) => { + self.instance_exports + .insert(*id, InstanceExports::Module(*module_id)); + (id, span, map) + } + // If this named module doesn't exist let name resolution later + // on generate an error about that. + None => return, + }, + _ => return, + }; + + // Load the list of names missing for our instance's name. Note that + // there may be no names missing, so we can just skip that. + let missing = match self.missing_names.get(id.name()) { + Some(missing) => missing, + None => return, + }; + let mut to_add = missing + .iter() + // Skip names defined locally in our module already... + .filter(|((_, ns), full_name)| !defined_names.contains(&(**full_name, *ns))) + // ... and otherwise only work with names actually defined in the + // instance. + .filter_map(|(key, full_name)| { + export_info + .wat_name_to_export_idx + .get(key) + .map(move |idx| (&key.1, idx, full_name)) + }) + .collect::>(); + + // Sort the list of aliases to add to ensure that we have deterministic + // output. + to_add.sort_by_key(|e| e.1); + + for (ns, index, full_name) in to_add { + let export_index = Index::Num(*index, *span); + self.defined_names.insert((*full_name, *ns)); + self.to_append.push(ModuleField::Alias(Alias { + id: Some(*full_name), + instance: Some(Index::Id(*id)), + name: None, + span: *span, + kind: ns.to_export_kind(export_index), + })); + } + } + + fn expand_export_all(&mut self, field: &mut ModuleField<'a>) -> Result { + match field { + ModuleField::Type(t) => { + match &mut t.def { + TypeDef::Module(m) => m.expand_export_all(self)?, + TypeDef::Instance(i) => i.expand_export_all(self)?, + TypeDef::Func(f) => f.expand_export_all(self)?, + TypeDef::Array(_) | TypeDef::Struct(_) => {} + } + Ok(true) + } + ModuleField::Import(t) => { + self.expand_export_all_item_sig(&mut t.item)?; + Ok(true) + } + ModuleField::ExportAll(span, instance) => { + let info = match self.instance_exports.get(instance) { + Some(InstanceExports::Inline(i)) => i, + Some(InstanceExports::Module(i)) => &self.module_exports[i], + None => { + return Err(Error::new( + instance.span(), + format!("failed to find instance to inline exports"), + )) + } + }; + for (i, (name, ns)) in info.export_names.iter().enumerate() { + let index = Index::Num(i as u32, *span); + let alias_id = gensym::gen(*span); + self.to_append.push(ModuleField::Alias(Alias { + id: Some(alias_id), + instance: Some(Index::Id(*instance)), + name: None, + span: *span, + kind: ns.to_export_kind(index), + })); self.to_append.push(ModuleField::Export(Export { + span: *span, name, - kind: ExportKind::Global(Index::Num(self.globals)), + kind: ns.to_export_kind(Index::Id(alias_id)), })); } - self.globals += 1; + Ok(false) } + _ => Ok(true), + } + } - ModuleField::Event(e) => { - for name in e.exports.names.drain(..) { - self.to_append.push(ModuleField::Export(Export { - name, - kind: ExportKind::Event(Index::Num(self.events)), - })); + fn expand_export_all_item_sig(&mut self, item: &mut ItemSig<'a>) -> Result<(), Error> { + match &mut item.kind { + ItemKind::Module(t) => self.expand_export_all_type_use(t), + ItemKind::Instance(t) => self.expand_export_all_type_use(t), + ItemKind::Func(t) | ItemKind::Event(EventType::Exception(t)) => { + self.expand_export_all_type_use(t) + } + ItemKind::Memory(_) | ItemKind::Table(_) | ItemKind::Global(_) => Ok(()), + } + } + + fn expand_export_all_type_use( + &mut self, + item: &mut TypeUse<'a, impl TypeReference<'a>>, + ) -> Result<(), Error> { + if let Some(t) = &mut item.inline { + t.expand_export_all(self)?; + } + Ok(()) + } +} + +#[derive(Default, Clone)] +struct ExportInfo<'a> { + wat_name_to_export_idx: HashMap<(&'a str, Ns), u32>, + export_names: Vec<(&'a str, Ns)>, +} + +impl<'a> ExportInfo<'a> { + fn from_exports(exports: &[ExportType<'a>]) -> ExportInfo<'a> { + let mut wat_name_to_export_idx = HashMap::new(); + let mut export_names = Vec::new(); + for (i, export) in exports.iter().enumerate() { + let ns = Ns::from_item(&export.item); + if let Some(id) = export.item.id { + wat_name_to_export_idx.insert((id.name(), ns), i as u32); + } + export_names.push((export.name, ns)); + } + ExportInfo { + wat_name_to_export_idx, + export_names, + } + } +} + +trait TypeReference<'a> { + fn record_missing(&self, cx: &mut Expander<'a>); + fn expand_export_all(&mut self, cx: &mut Expander<'a>) -> Result<(), Error>; +} + +impl<'a> TypeReference<'a> for FunctionType<'a> { + fn record_missing(&self, _cx: &mut Expander<'a>) {} + fn expand_export_all(&mut self, _cx: &mut Expander<'a>) -> Result<(), Error> { + Ok(()) + } +} + +impl<'a> TypeReference<'a> for ModuleType<'a> { + fn record_missing(&self, cx: &mut Expander<'a>) { + for i in self.imports.iter() { + cx.record_item_sig(&i.item); + } + for e in self.exports.iter() { + cx.record_item_sig(&e.item); + } + for (_, name) in self.instance_exports.iter() { + cx.record_missing(&Index::Id(*name), Ns::Type); + } + } + fn expand_export_all(&mut self, cx: &mut Expander<'a>) -> Result<(), Error> { + for (_span, instance_ty_name) in self.instance_exports.drain(..) { + match cx.instance_type_exports.get(&instance_ty_name) { + Some(exports) => self.exports.extend(exports.iter().cloned()), + None => { + return Err(Error::new( + instance_ty_name.span(), + format!("failed to find instance type to inline exports from"), + )) } - self.events += 1; } + } + for i in self.imports.iter_mut() { + cx.expand_export_all_item_sig(&mut i.item)?; + } + for e in self.exports.iter_mut() { + cx.expand_export_all_item_sig(&mut e.item)?; + } + Ok(()) + } +} - _ => {} +impl<'a> TypeReference<'a> for InstanceType<'a> { + fn record_missing(&self, cx: &mut Expander<'a>) { + for e in self.exports.iter() { + cx.record_item_sig(&e.item); + } + } + fn expand_export_all(&mut self, cx: &mut Expander<'a>) -> Result<(), Error> { + for e in self.exports.iter_mut() { + cx.expand_export_all_item_sig(&mut e.item)?; } + Ok(()) } } diff --git a/crates/wast/src/resolve/gensym.rs b/crates/wast/src/resolve/gensym.rs new file mode 100644 index 0000000000..5f6d94133a --- /dev/null +++ b/crates/wast/src/resolve/gensym.rs @@ -0,0 +1,20 @@ +use crate::ast::{Id, Span}; +use std::cell::Cell; + +thread_local!(static NEXT: Cell = Cell::new(0)); + +pub fn reset() { + NEXT.with(|c| c.set(0)); +} + +pub fn gen(span: Span) -> Id<'static> { + NEXT.with(|next| { + let gen = next.get() + 1; + next.set(gen); + Id::gensym(span, gen) + }) +} + +pub fn fill<'a>(span: Span, slot: &mut Option>) -> Id<'a> { + *slot.get_or_insert_with(|| gen(span)) +} diff --git a/crates/wast/src/resolve/mod.rs b/crates/wast/src/resolve/mod.rs index aa04ee5fa8..4501990484 100644 --- a/crates/wast/src/resolve/mod.rs +++ b/crates/wast/src/resolve/mod.rs @@ -1,9 +1,88 @@ use crate::ast::*; use crate::Error; +mod deinline_import_export; mod expand; +mod gensym; mod names; -mod tyexpand; + +#[derive(PartialEq, Eq, Hash, Copy, Clone, Debug)] +pub enum Ns { + Func, + Table, + Global, + Memory, + Module, + Instance, + Event, + Type, +} + +impl Ns { + fn from_item(item: &ItemSig<'_>) -> Ns { + match item.kind { + ItemKind::Func(_) => Ns::Func, + ItemKind::Table(_) => Ns::Table, + ItemKind::Memory(_) => Ns::Memory, + ItemKind::Global(_) => Ns::Global, + ItemKind::Instance(_) => Ns::Instance, + ItemKind::Module(_) => Ns::Module, + ItemKind::Event(_) => Ns::Event, + } + } + + fn from_export<'a>(kind: &ExportKind<'a>) -> (Index<'a>, Ns) { + match *kind { + ExportKind::Func(f) => (f, Ns::Func), + ExportKind::Table(f) => (f, Ns::Table), + ExportKind::Global(f) => (f, Ns::Global), + ExportKind::Memory(f) => (f, Ns::Memory), + ExportKind::Instance(f) => (f, Ns::Instance), + ExportKind::Module(f) => (f, Ns::Module), + ExportKind::Event(f) => (f, Ns::Event), + ExportKind::Type(f) => (f, Ns::Type), + } + } + + fn from_export_mut<'a, 'b>(kind: &'b mut ExportKind<'a>) -> (&'b mut Index<'a>, Ns) { + match kind { + ExportKind::Func(f) => (f, Ns::Func), + ExportKind::Table(f) => (f, Ns::Table), + ExportKind::Global(f) => (f, Ns::Global), + ExportKind::Memory(f) => (f, Ns::Memory), + ExportKind::Instance(f) => (f, Ns::Instance), + ExportKind::Module(f) => (f, Ns::Module), + ExportKind::Event(f) => (f, Ns::Event), + ExportKind::Type(f) => (f, Ns::Type), + } + } + + fn to_export_kind<'a>(&self, index: Index<'a>) -> ExportKind<'a> { + match self { + Ns::Func => ExportKind::Func(index), + Ns::Table => ExportKind::Table(index), + Ns::Global => ExportKind::Global(index), + Ns::Memory => ExportKind::Memory(index), + Ns::Module => ExportKind::Module(index), + Ns::Instance => ExportKind::Instance(index), + Ns::Event => ExportKind::Event(index), + Ns::Type => ExportKind::Type(index), + } + } + + fn desc(&self) -> &'static str { + match self { + Ns::Func => "func", + Ns::Table => "table", + Ns::Global => "global", + Ns::Memory => "memory", + Ns::Module => "module", + Ns::Instance => "instance", + Ns::Event => "event", + Ns::Type => "type", + } + } +} pub fn resolve<'a>(module: &mut Module<'a>) -> Result, Error> { let fields = match &mut module.kind { @@ -11,24 +90,19 @@ pub fn resolve<'a>(module: &mut Module<'a>) -> Result, Error> { _ => return Ok(Default::default()), }; - // First up, let's de-inline import/export annotations since this'll - // restructure the module and affect how we count indices in future passes - // since function definitions turn into imports. - // - // The first pass switches all inline imports to explicit `Import` items. - // This pass also counts all `Import` items per-type to start building up - // the index space so we know the corresponding index for each item. - // - // In a second pass we then remove all inline `export` annotations, again - // counting indices as we go along to ensure we always know the index for - // what we're exporting. + // Ensure that each resolution of a module is deterministic in the names + // that it generates by resetting our thread-local symbol generator. + gensym::reset(); + + // First up, de-inline import/export annotations. // - // The final step is then taking all of the injected `export` fields and - // actually pushing them onto our list of fields. - let mut expander = expand::Expander::default(); - expander.process(fields, expand::Expander::deinline_import); - expander.process(fields, expand::Expander::deinline_export); + // This ensures we only have to deal with inline definitions and to + // calculate exports we only have to look for a particular kind of module + // field. + deinline_import_export::run(fields); + // With a canonical form of imports make sure that imports are all listed + // first. for i in 1..fields.len() { let span = match &fields[i] { ModuleField::Import(i) => i.span, @@ -44,55 +118,17 @@ pub fn resolve<'a>(module: &mut Module<'a>) -> Result, Error> { return Err(Error::new(span, format!("import after {}", name))); } - // For the second pass we resolve all inline type annotations. This will, in - // the order that we see them, append to the list of types. Note that types - // are indexed so we're careful to always insert new types just before the - // field that we're looking at. - // - // It's not strictly required that we `move_types_first` here but it gets - // our indexing to exactly match wabt's which our test suite is asserting. - let mut cur = 0; - let mut expander = tyexpand::Expander::default(); - move_types_first(fields); - while cur < fields.len() { - expander.expand(&mut fields[cur]); - for new in expander.to_prepend.drain(..) { - fields.insert(cur, new); - cur += 1; - } - cur += 1; - } + // Next inject any `alias` declarations and expand `(export $foo)` + // annotations. These are all part of the module-linking proposal and we try + // to resolve their injection here to keep index spaces stable for later. + expand::run(fields)?; // Perform name resolution over all `Index` items to resolve them all to // indices instead of symbolic names. - // - // For this operation we do need to make sure that imports are sorted first - // because otherwise we'll be calculating indices in the wrong order. - move_imports_first(fields); - let mut resolver = names::Resolver::default(); - for field in fields.iter_mut() { - resolver.register(field)?; - } - for field in fields.iter_mut() { - resolver.resolve(field)?; - } + let resolver = names::resolve(fields)?; Ok(Names { resolver }) } -fn move_imports_first(fields: &mut [ModuleField<'_>]) { - fields.sort_by_key(|f| match f { - ModuleField::Import(_) => false, - _ => true, - }); -} - -fn move_types_first(fields: &mut [ModuleField<'_>]) { - fields.sort_by_key(|f| match f { - ModuleField::Type(_) => false, - _ => true, - }); -} - /// Representation of the results of name resolution for a module. /// /// This structure is returned from the @@ -110,7 +146,8 @@ impl<'a> Names<'a> { /// looked up in the function namespace and converted to a `Num`. If the /// `Id` is not defined then an error will be returned. pub fn resolve_func(&self, idx: &mut Index<'a>) -> Result<(), Error> { - self.resolver.resolve_idx(idx, names::Ns::Func) + self.resolver.module().resolve(idx, Ns::Func)?; + Ok(()) } /// Resolves `idx` within the memory namespace. @@ -119,7 +156,8 @@ impl<'a> Names<'a> { /// looked up in the memory namespace and converted to a `Num`. If the /// `Id` is not defined then an error will be returned. pub fn resolve_memory(&self, idx: &mut Index<'a>) -> Result<(), Error> { - self.resolver.resolve_idx(idx, names::Ns::Memory) + self.resolver.module().resolve(idx, Ns::Memory)?; + Ok(()) } /// Resolves `idx` within the table namespace. @@ -128,7 +166,8 @@ impl<'a> Names<'a> { /// looked up in the table namespace and converted to a `Num`. If the /// `Id` is not defined then an error will be returned. pub fn resolve_table(&self, idx: &mut Index<'a>) -> Result<(), Error> { - self.resolver.resolve_idx(idx, names::Ns::Table) + self.resolver.module().resolve(idx, Ns::Table)?; + Ok(()) } /// Resolves `idx` within the global namespace. @@ -137,6 +176,7 @@ impl<'a> Names<'a> { /// looked up in the global namespace and converted to a `Num`. If the /// `Id` is not defined then an error will be returned. pub fn resolve_global(&self, idx: &mut Index<'a>) -> Result<(), Error> { - self.resolver.resolve_idx(idx, names::Ns::Global) + self.resolver.module().resolve(idx, Ns::Global)?; + Ok(()) } } diff --git a/crates/wast/src/resolve/names.rs b/crates/wast/src/resolve/names.rs index 6c025f9279..dbda62fc26 100644 --- a/crates/wast/src/resolve/names.rs +++ b/crates/wast/src/resolve/names.rs @@ -1,126 +1,1301 @@ use crate::ast::*; +use crate::resolve::gensym; +use crate::resolve::Ns; use crate::Error; use std::collections::HashMap; +use std::mem; -#[derive(Copy, Clone)] -pub enum Ns { - Data, - Elem, - Event, - Func, - Global, - Memory, - Table, - Type, - Field, -} - -impl Ns { - fn desc(&self) -> &'static str { - match self { - Ns::Data => "data", - Ns::Elem => "elem", - Ns::Event => "event", - Ns::Func => "func", - Ns::Global => "global", - Ns::Memory => "memory", - Ns::Table => "table", - Ns::Type => "type", - Ns::Field => "field", - } - } +pub fn resolve<'a>(fields: &mut Vec>) -> Result, Error> { + let mut resolver = Resolver::default(); + resolver.process(None, fields)?; + Ok(resolver) } +/// Context structure used to perform name resolution. #[derive(Default)] pub struct Resolver<'a> { - ns: [Namespace<'a>; 9], - func_tys: HashMap>, + modules: Vec>, + /// Index of the module currently being worked on. + cur: usize, } #[derive(Default)] -struct Namespace<'a> { - names: HashMap, u32>, - count: u32, +pub struct Module<'a> { + /// The index of this module in `resolver.modules`. + id: usize, + + /// The parent index of this module, if present. + parent: Option, + + /// Whether or not names have been registered in the namespaces below, used + /// in `key_to_idx` when we're adding new type fields. + registered: bool, + + // Namespaces within each module. Note that each namespace carries with it + // information about the signature of the item in that namespace. The + // signature is later used to synthesize the type of a module and inject + // type annotations if necessary. + funcs: Namespace<'a, Index<'a>>, + globals: Namespace<'a, GlobalType<'a>>, + tables: Namespace<'a, TableType>, + memories: Namespace<'a, MemoryType>, + types: Namespace<'a, TypeInfo<'a>>, + events: Namespace<'a, Index<'a>>, + modules: Namespace<'a, ModuleInfo<'a>>, + instances: Namespace<'a, InstanceDef<'a>>, + datas: Namespace<'a, ()>, + elems: Namespace<'a, ()>, + fields: Namespace<'a, ()>, + + /// Information about the exports of this module. + exports: ExportNamespace<'a>, + /// List of exported items in this module, along with what kind of item is + /// exported. + exported_ids: Vec>, + + // Maps used to "intern" types. These maps are populated as type annotations + // are seen and inline type annotations use previously defined ones if + // there's a match. + func_type_to_idx: HashMap, Index<'a>>, + instance_type_to_idx: HashMap, Index<'a>>, + module_type_to_idx: HashMap, Index<'a>>, + + /// Fields, during processing, which should be prepended to the + /// currently-being-processed field. This should always be empty after + /// processing is complete. + to_prepend: Vec>, +} + +enum InstanceDef<'a> { + /// An instance definition that was imported. + Import { + /// An index pointing to the type of the instance. + type_idx: Index<'a>, + /// Information about the exports of the instance. + exports: ExportNamespace<'a>, + }, + /// An instance definition that was the instantiation of a module. + Instantiate { + /// The module that is instantiated to create this instance + module_idx: Index<'a>, + }, +} + +enum ModuleInfo<'a> { + /// An imported module with inline information about the exports. + Imported { + /// Information about the exports of this module, inferred from the type. + exports: ExportNamespace<'a>, + /// The type of the imported module. + type_idx: Index<'a>, + }, + Nested { + /// The index in `resolver.modules` of where this module resides. It + /// will need to be consulted for export information. + idx: usize, + /// The type of the module. + type_idx: Index<'a>, + }, } impl<'a> Resolver<'a> { - pub fn register(&mut self, item: &ModuleField<'a>) -> Result<(), Error> { - let mut register = - |ns: Ns, name: Option>| self.ns_mut(ns).register(name, ns.desc()).map(|_| ()); + fn process( + &mut self, + parent: Option, + fields: &mut Vec>, + ) -> Result<(), Error> { + // First up create a new `Module` structure and add it to our list of + // modules. Afterwards configure some internal fields about it as well. + assert_eq!(self.modules.len(), self.cur); + self.modules.push(Module::default()); + let module = &mut self.modules[self.cur]; + module.id = self.cur; + module.parent = parent; + + // The first thing we do is expand `type` declarations and their + // specified-inline types. This will expand inline instance/module types + // and add more type declarations as necessary. This happens early on + // since it's pretty easy to do and it also will add more types to the + // index space, which we'll want to stabilize soon. + self.iter_and_prepend( + fields, + |me, field| Ok(me.modules[me.cur].expand_type(field)), + )?; + + // FIXME: this is a bad hack and is something we should not do. This is + // not forwards-compatible with type imports since we're reordering the + // text file. + // + // The purpose of this is: + // + // * In `me.expand(...)` below we'll be creating more types. This is + // for `TypeUse` on items other than type declarations (e.g. inline + // functions) as well as inline module types. Especially for inline + // modules we don't know how many types we'll be manufacturing for + // each item. + // + // * The injected types come *after* we've numbered the existing types + // in the module. This means that they need to come last in the index + // space. Somehow the binary emission needs to be coordinated with + // all this where the emitted types get emitted last. + // + // * However, according to the current module linking spec, modules + // can be interleaved with the type section. This means that we can't + // rely on types being declared before the module section. The module + // section can only refer to previously defined types as well. + // + // Practically there is no purpose to interleaving the type and module + // section today. As a result we can safely sort all types to the front. + // This, however, can break the roundtrip binary-text-binary for + // strictly-speaking compliant modules with the module linking spec. + // Anyway, this is a bummer, should figure out a better thing in the + // future. + // + // I've tried to open discussion about this at + // WebAssembly/module-linking#8 + fields.sort_by_key(|field| match field { + ModuleField::Type(_) + | ModuleField::Alias(Alias { + kind: ExportKind::Type(_), + .. + }) => 0, + _ => 1, + }); + + // Number everything in the module, recording what names correspond to + // what indices. + let module = &mut self.modules[self.cur]; + for field in fields.iter_mut() { + module.register(field)?; + } + module.registered = true; + + // After we've got our index spaces we then need to expand inline type + // annotations on everything other than `type` definitions. + // + // This is the key location we start recursively processing nested + // modules as well. Along the way of expansion we record the signature + // of all items. This way nested modules have access to all names + // defined in parent modules, but they only have access to type + // signatures of previously defined items. + self.iter_and_prepend(fields, |me, field| me.expand(field))?; + + // This is the same as the comment above, only we're doing it now after + // the full expansion process since all types should now be present in + // the module. + fields.sort_by_key(|field| match field { + ModuleField::Type(_) + | ModuleField::Alias(Alias { + kind: ExportKind::Type(_), + .. + }) => 0, + _ => 1, + }); + + // And finally the last step is to replace all our `Index::Id` instances + // with `Index::Num` in the AST. This does not recurse into nested + // modules because that was handled in `expand` above. + let module = &mut self.modules[self.cur]; + for field in fields.iter_mut() { + module.resolve_field(field)?; + } + Ok(()) + } + + pub fn module(&self) -> &Module<'a> { + &self.modules[self.cur] + } + + fn iter_and_prepend( + &mut self, + fields: &mut Vec>, + cb: impl Fn(&mut Self, &mut ModuleField<'a>) -> Result<(), Error>, + ) -> Result<(), Error> { + let module = &self.modules[self.cur]; + assert!(module.to_prepend.is_empty()); + let mut cur = 0; + // The name on the tin of this method is that we'll be *prepending* + // items in the `to_prepend` list. The only items we're prepending are + // types, however. In the module-linking proposal we really need to + // prepend types because the previous item (like the definition of a + // module) might refer back to the type and the type must precede the + // reference. If we don't have nested modules, however, it's fine to + // throw everything at the end. + // + // WABT throws everything at the end (expansion of inline type + // annotations from functions/etc), and we want to be binary-compatible + // with that. With all of that in mind we only prepend if some + // conditions are true: + // + // * If names have *not* been registered, then we're expanding types. If + // a type is expanded then it's an instance/module type, so we must + // prepend. + // + // * If `self.cur` is not zero, then we're in a nested module, so we're + // in the module linking proposal. + // + // * If we have any instances/modules then we're part of the module + // linking proposal, so we prepend. + // + // Basically if it looks like we're an "MVP-compatible" module we throw + // types at the end. Otherwise if it looks like module-linking is being + // used we prepend items. This will likely change as other tools + // implement module linking. + let actually_prepend = !module.registered + || self.cur != 0 + || module.instances.count > 0 + || module.modules.count > 0; + while cur < fields.len() { + cb(self, &mut fields[cur])?; + if actually_prepend { + for item in self.modules[self.cur].to_prepend.drain(..) { + fields.insert(cur, item); + cur += 1; + } + } + cur += 1; + } + if !actually_prepend { + fields.extend(self.modules[self.cur].to_prepend.drain(..)); + } + assert!(self.modules[self.cur].to_prepend.is_empty()); + Ok(()) + } + + fn expand(&mut self, item: &mut ModuleField<'a>) -> Result<(), Error> { + let module = &mut self.modules[self.cur]; match item { - ModuleField::Import(i) => match i.kind { - ImportKind::Func(_) => register(Ns::Func, i.id), - ImportKind::Memory(_) => register(Ns::Memory, i.id), - ImportKind::Table(_) => register(Ns::Table, i.id), - ImportKind::Global(_) => register(Ns::Global, i.id), - ImportKind::Event(_) => register(Ns::Event, i.id), + // expansion generally handled above, we're just handling the + // signature of each item here + ModuleField::Type(ty) => { + let info = match &ty.def { + TypeDef::Func(f) => TypeInfo::Func(f.key()), + TypeDef::Instance(i) => TypeInfo::Instance { + info: Module::from_exports(ty.span, self.cur, &i.exports)?, + key: i.key(), + }, + TypeDef::Module(m) => TypeInfo::Module { + info: Module::from_exports(ty.span, self.cur, &m.exports)?, + key: m.key(), + }, + TypeDef::Array(_) | TypeDef::Struct(_) => TypeInfo::Other, + }; + module.types.push_item(self.cur, info); + } + + ModuleField::Import(i) => { + module.expand_item_sig(&mut i.item, false); + + // Record each item's type information in the index space + // arrays. + match &mut i.item.kind { + ItemKind::Func(t) => module.funcs.push_item(self.cur, t.index.unwrap()), + ItemKind::Event(EventType::Exception(t)) => { + module.events.push_item(self.cur, t.index.unwrap()) + } + ItemKind::Instance(t) => { + let exports = match t.inline.take() { + Some(i) => ExportNamespace::from_exports(&i.exports)?, + None => ExportNamespace::default(), + }; + module.instances.push_item( + self.cur, + InstanceDef::Import { + type_idx: t.index.unwrap(), + exports, + }, + ); + } + ItemKind::Module(m) => { + let exports = match m.inline.take() { + Some(m) => ExportNamespace::from_exports(&m.exports)?, + None => ExportNamespace::default(), + }; + module.modules.push_item( + self.cur, + ModuleInfo::Imported { + type_idx: m.index.unwrap(), + exports, + }, + ); + } + ItemKind::Global(g) => module.globals.push_item(self.cur, g.clone()), + ItemKind::Table(t) => module.tables.push_item(self.cur, t.clone()), + ItemKind::Memory(m) => module.memories.push_item(self.cur, m.clone()), + } + } + ModuleField::Func(f) => { + let idx = module.expand_type_use(&mut f.ty); + module.funcs.push_item(self.cur, idx); + if let FuncKind::Inline { expression, .. } = &mut f.kind { + module.expand_expression(expression); + } + } + ModuleField::Global(g) => { + module.globals.push_item(self.cur, g.ty); + if let GlobalKind::Inline(expr) = &mut g.kind { + module.expand_expression(expr); + } + } + ModuleField::Data(d) => { + if let DataKind::Active { offset, .. } = &mut d.kind { + module.expand_expression(offset); + } + } + ModuleField::Elem(e) => { + if let ElemKind::Active { offset, .. } = &mut e.kind { + module.expand_expression(offset); + } + } + ModuleField::Event(e) => match &mut e.ty { + EventType::Exception(ty) => { + let idx = module.expand_type_use(ty); + module.events.push_item(self.cur, idx); + } }, - ModuleField::Global(i) => register(Ns::Global, i.id), - ModuleField::Memory(i) => register(Ns::Memory, i.id), - ModuleField::Func(i) => register(Ns::Func, i.id), - ModuleField::Table(i) => register(Ns::Table, i.id), + ModuleField::Instance(i) => { + if let InstanceKind::Inline { + module: module_idx, .. + } = i.kind + { + module + .instances + .push_item(self.cur, InstanceDef::Instantiate { module_idx }); + } + } + ModuleField::NestedModule(m) => { + let (fields, ty) = match &mut m.kind { + NestedModuleKind::Inline { fields, ty } => (fields, ty), + NestedModuleKind::Import { .. } => panic!("should only be inline"), + }; + + // And this is where the magic happens for recursive resolution + // of modules. Here we switch `self.cur` to a new module that + // will be injected (the length of the array since a new element + // is pushed). We then recurse and process, in its entirety, the + // nested module. + // + // Our nested module will have access to all names defined in + // our module, but it will only have access to item signatures + // of previously defined items. + // + // After this is all done if the inline type annotation is + // missing then we can infer the type of the inline module and + // inject the type here. + let prev = mem::replace(&mut self.cur, self.modules.len()); + self.process(Some(prev), fields)?; + let child = mem::replace(&mut self.cur, prev); + if ty.is_none() { + *ty = Some(self.infer_module_ty(m.span, child, fields)?); + } + self.modules[self.cur].modules.push_item( + self.cur, + ModuleInfo::Nested { + type_idx: ty.unwrap(), + idx: child, + }, + ); + } + ModuleField::Table(t) => { + let ty = match t.kind { + TableKind::Normal(ty) => ty, + _ => panic!("tables should be normalized by now"), + }; + module.tables.push_item(self.cur, ty.clone()); + } + ModuleField::Memory(t) => { + let ty = match t.kind { + MemoryKind::Normal(ty) => ty, + _ => panic!("memories should be normalized by now"), + }; + module.memories.push_item(self.cur, ty.clone()); + } + ModuleField::Alias(a) => self.expand_alias(a)?, + ModuleField::Start(_) | ModuleField::Export(_) | ModuleField::Custom(_) => {} + ModuleField::ExportAll(..) => unreachable!(), + } + return Ok(()); + } + + fn expand_alias(&mut self, a: &mut Alias<'a>) -> Result<(), Error> { + // This is a pretty awful method. I'd like to preemptively apologize for + // how much of a mess this is. Not only is what this doing inherently + // complicated but it's also complicated by working around + // Rust-the-language as well. + // + // The general idea is that we're taking a look at where this alias is + // coming from (nested instance or parent) and we manufacture an + // appropriate closure which, when invoked, creates a `SigAlias` + // appropriate for this signature. + // + // Cleanups are very welcome to this function if you've got an idea, I + // don't think anyone will be wed to it in its current form. + // + // As some more information on this method, though, the goal of this + // method is to expand the `alias` directive and fill in all the names + // here. Note that we do this eagerly because we want to fill in the + // type signature of the item this alias is introducing. We don't want + // to fill in the *full* type signature, though, just that it was an + // alias. The full signature may not yet be available in some valid + // situations, so we record the bare minimum necessary that an 'alias' + // was used, and then later on if that alias attempts to get followed + // (e.g. in `item_for`) we'll do the checks to make sure everything + // lines up. + let gen1; + let gen2; + let gen3; + let gen4; + let gen_alias: &dyn Fn(&mut Index<'a>, Ns) -> Result, Error> = + match &mut a.instance { + Some(i) => { + self.modules[self.cur].resolve(i, Ns::Instance)?; + let (def, m) = self.instance_for(self.cur, i)?; + match def { + InstanceDef::Import { type_idx, exports } => { + gen1 = move |idx: &mut Index<'a>, ns: Ns| { + Ok(SigAlias::TypeExport { + module: m, + ty: *type_idx, + export_idx: exports.resolve(idx, ns)?, + }) + }; + &gen1 + } + InstanceDef::Instantiate { module_idx } => { + let (info, m) = self.module_for(m, module_idx)?; + match info { + ModuleInfo::Imported { type_idx, exports } => { + gen2 = move |idx: &mut Index<'a>, ns: Ns| { + Ok(SigAlias::TypeExport { + module: m, + ty: *type_idx, + export_idx: exports.resolve(idx, ns)?, + }) + }; + &gen2 + } + ModuleInfo::Nested { + idx: module_idx, .. + } => { + let module = &self.modules[*module_idx]; + gen3 = move |idx: &mut Index<'a>, ns: Ns| { + Ok(SigAlias::ModuleExport { + module: *module_idx, + export_idx: module.exports.resolve(idx, ns)?, + }) + }; + &gen3 + } + } + } + } + } + None => { + let module_idx = match self.modules[self.cur].parent { + Some(p) => p, + None => { + return Err(Error::new( + a.span, + "cannot use alias parent directives in root module".to_string(), + )) + } + }; + let module = &self.modules[module_idx]; + gen4 = move |idx: &mut Index<'a>, ns: Ns| { + module.resolve(idx, ns)?; + Ok(SigAlias::Item { + module: module_idx, + idx: *idx, + }) + }; + &gen4 + } + }; + match &mut a.kind { + ExportKind::Func(idx) => { + let item = gen_alias(idx, Ns::Func)?; + self.modules[self.cur].funcs.push(Sig::Alias(item)); + } + ExportKind::Table(idx) => { + let item = gen_alias(idx, Ns::Table)?; + self.modules[self.cur].tables.push(Sig::Alias(item)); + } + ExportKind::Global(idx) => { + let item = gen_alias(idx, Ns::Global)?; + self.modules[self.cur].globals.push(Sig::Alias(item)); + } + ExportKind::Memory(idx) => { + let item = gen_alias(idx, Ns::Memory)?; + self.modules[self.cur].memories.push(Sig::Alias(item)); + } + ExportKind::Event(idx) => { + let item = gen_alias(idx, Ns::Event)?; + self.modules[self.cur].events.push(Sig::Alias(item)); + } + ExportKind::Type(idx) => { + let item = gen_alias(idx, Ns::Type)?; + self.modules[self.cur].types.push(Sig::Alias(item)); + } + ExportKind::Instance(idx) => { + let item = gen_alias(idx, Ns::Instance)?; + self.modules[self.cur].instances.push(Sig::Alias(item)); + } + ExportKind::Module(idx) => { + let item = gen_alias(idx, Ns::Module)?; + self.modules[self.cur].modules.push(Sig::Alias(item)); + } + } + Ok(()) + } + + /// Creates a local type definition that matches the signature of the + /// `child` module, which is defined by `fields`. + /// + /// The goal here is that we're injecting type definitions into the current + /// module to create a `module` type that matches the module provided, and + /// then we'll use that new type as the type of the module itself. + fn infer_module_ty( + &mut self, + span: Span, + child: usize, + fields: &[ModuleField<'a>], + ) -> Result, Error> { + let mut imports = Vec::new(); + let mut exports = Vec::new(); + + for field in fields { + match field { + // For each import it already has the type signature listed + // inline, so we use that and then copy out the signature from + // the module to our module. + ModuleField::Import(i) => { + let item = match &i.item.kind { + ItemKind::Func(f) => Item::Func(f.index.unwrap()), + ItemKind::Module(f) => Item::Module(f.index.unwrap()), + ItemKind::Instance(f) => Item::Instance(f.index.unwrap()), + ItemKind::Event(EventType::Exception(f)) => Item::Event(f.index.unwrap()), + ItemKind::Table(t) => Item::Table(t.clone()), + ItemKind::Memory(t) => Item::Memory(t.clone()), + ItemKind::Global(t) => Item::Global(t.clone()), + }; + let item = self.copy_item_from_module(i.span, child, &item)?; + imports.push((i.module, i.field, item)); + } + + // Exports are a little more complicated, we have to use the + // `*_for` family of functions to follow the trail of + // definitions to figure out the signature of the item referred + // to by the export. + ModuleField::Export(e) => { + let (module, item) = match &e.kind { + ExportKind::Func(i) => { + let (type_idx, m) = self.func_for(child, i)?; + (m, Item::Func(*type_idx)) + } + ExportKind::Event(i) => { + let (type_idx, m) = self.event_for(child, i)?; + (m, Item::Event(*type_idx)) + } + ExportKind::Table(i) => { + let (table_ty, m) = self.table_for(child, i)?; + (m, Item::Table(table_ty.clone())) + } + ExportKind::Memory(i) => { + let (memory_ty, m) = self.memory_for(child, i)?; + (m, Item::Memory(memory_ty.clone())) + } + ExportKind::Global(i) => { + let (global_ty, m) = self.global_for(child, i)?; + (m, Item::Global(global_ty.clone())) + } + ExportKind::Module(i) => { + let (info, m) = self.module_for(child, i)?; + let type_idx = match info { + ModuleInfo::Imported { type_idx, .. } => *type_idx, + ModuleInfo::Nested { type_idx, .. } => *type_idx, + }; + (m, Item::Module(type_idx)) + } + ExportKind::Instance(i) => { + let (def, m) = self.instance_for(child, i)?; + match def { + InstanceDef::Import { type_idx, .. } => { + (m, Item::Instance(*type_idx)) + } + InstanceDef::Instantiate { module_idx } => { + let (info, m) = self.module_for(m, module_idx)?; + let type_idx = match info { + ModuleInfo::Imported { type_idx, .. } => *type_idx, + ModuleInfo::Nested { type_idx, .. } => *type_idx, + }; + let item = + self.copy_type_from_module(e.span, m, &type_idx, true)?; + exports.push((e.name, item)); + continue; + } + } + } + ExportKind::Type(_) => { + return Err(Error::new( + span, + "exported types not supported yet".to_string(), + )) + } + }; + let item = self.copy_item_from_module(e.span, module, &item)?; + exports.push((e.name, item)); + } + _ => {} + } + } + let key = (imports, exports); + Ok(self.modules[self.cur].key_to_idx(span, key)) + } + + /// Copies an item signature from a child module into the current module. + /// This will recursively do so for referenced types and value types and + /// such. + fn copy_item_from_module( + &mut self, + span: Span, + child: usize, + item: &Item<'a>, + ) -> Result, Error> { + Ok(match item { + // These items have everything specified inline, nothing to recurse + // with, so we just clone it. + Item::Table(_) | Item::Memory(_) => item.clone(), + // Items with indirect indices means the contents of the index need + // to be fully copied into our module. + Item::Func(idx) | Item::Event(idx) | Item::Module(idx) | Item::Instance(idx) => { + self.copy_type_from_module(span, child, idx, false)? + } + // Globals just need to copy over the value type and otherwise + // contain most information inline. + Item::Global(ty) => Item::Global(GlobalType { + ty: self.copy_valtype_from_module(span, child, ty.ty)?, + mutable: ty.mutable, + }), + }) + } + + /// Copies a type index from one module into the current module. + /// + /// This method will chase the actual definition of `type_idx` within + /// `child` and recursively copy all contents into `self.cur`. + fn copy_type_from_module( + &mut self, + span: Span, + child: usize, + type_idx: &Index<'a>, + switch_module_to_instance: bool, + ) -> Result, Error> { + let (ty, child) = self.type_for(child, type_idx)?; + match ty { + TypeInfo::Func(key) => { + let key = key.clone(); + let my_key = ( + key.0 + .iter() + .map(|ty| self.copy_valtype_from_module(span, child, *ty)) + .collect::, Error>>()?, + key.1 + .iter() + .map(|ty| self.copy_valtype_from_module(span, child, *ty)) + .collect::, Error>>()?, + ); + Ok(Item::Func(self.modules[self.cur].key_to_idx(span, my_key))) + } + + TypeInfo::Instance { key, .. } => { + let key = key.clone(); + let my_key = key + .iter() + .map(|(name, item)| { + self.copy_item_from_module(span, child, item) + .map(|x| (*name, x)) + }) + .collect::, Error>>()?; + Ok(Item::Instance( + self.modules[self.cur].key_to_idx(span, my_key), + )) + } + + TypeInfo::Module { key, .. } => { + let key = key.clone(); + let exports = key + .1 + .iter() + .map(|(name, item)| { + self.copy_item_from_module(span, child, item) + .map(|x| (*name, x)) + }) + .collect::, Error>>()?; + if switch_module_to_instance { + return Ok(Item::Instance( + self.modules[self.cur].key_to_idx(span, exports), + )); + } + let imports = key + .0 + .iter() + .map(|(module, field, item)| { + self.copy_item_from_module(span, child, item) + .map(|x| (*module, *field, x)) + }) + .collect::, Error>>()?; + Ok(Item::Module( + self.modules[self.cur].key_to_idx(span, (imports, exports)), + )) + } + + TypeInfo::Other => Err(Error::new( + span, + format!("cannot copy reference types between modules right now"), + )), + } + } + + fn copy_valtype_from_module( + &mut self, + span: Span, + _child: usize, + ty: ValType<'a>, + ) -> Result, Error> { + match ty { + // It's not clear to me at this time what to do about these types. + // What to do here sort of depends on the GC proposal. This method + // is only invoked with the module-linking proposal, which means + // that we have two proposals interacting, and that's always weird + // territory anywya. + ValType::Ref(RefType::Type(_)) + | ValType::Ref(RefType::OptType(_)) + | ValType::Rtt(_) => Err(Error::new( + span, + format!("cannot copy reference types between modules right now"), + )), + + ValType::I32 + | ValType::I64 + | ValType::F32 + | ValType::F64 + | ValType::V128 + | ValType::I8 + | ValType::I16 + | ValType::Ref(RefType::Extern) + | ValType::Ref(RefType::Func) + | ValType::Ref(RefType::Exn) + | ValType::Ref(RefType::Eq) + | ValType::Ref(RefType::I31) => Ok(ty), + } + } + + // Below here is a suite of `*_for` family of functions. These all delegate + // to `item_for` below, which is a crux of the module-linking proposal. Each + // individual method is specialized for a particular namespace, but they all + // look the same. For comments on the meat of what's happening here see the + // `item_for` method below. + + fn type_for( + &self, + module_idx: usize, + idx: &Index<'a>, + ) -> Result<(&TypeInfo<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Type, &|m| &m.types) + } + + fn global_for( + &self, + module_idx: usize, + idx: &Index<'a>, + ) -> Result<(&GlobalType<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Global, &|m| &m.globals) + } + + fn table_for(&self, module_idx: usize, idx: &Index<'a>) -> Result<(&TableType, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Table, &|m| &m.tables) + } + + fn module_for( + &self, + module_idx: usize, + idx: &Index<'a>, + ) -> Result<(&ModuleInfo<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Module, &|m| &m.modules) + } + + fn instance_for( + &self, + module_idx: usize, + idx: &Index<'a>, + ) -> Result<(&InstanceDef<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Instance, &|m| { + &m.instances + }) + } + + fn func_for(&self, module_idx: usize, idx: &Index<'a>) -> Result<(&Index<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Func, &|m| &m.funcs) + } + + fn event_for(&self, module_idx: usize, idx: &Index<'a>) -> Result<(&Index<'a>, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Event, &|m| &m.events) + } + + fn memory_for( + &self, + module_idx: usize, + idx: &Index<'a>, + ) -> Result<(&MemoryType, usize), Error> { + self.item_for(&self.modules[module_idx], idx, Ns::Memory, &|m| &m.memories) + } + + /// A gnarly method that resolves an `idx` within the `module` provided to + /// the actual signature of that item. + /// + /// The goal of this method is to recursively follow, if necessary, chains + /// of `alias` annotations to figure out the type signature of an item. This + /// is required to determine the signature of an inline `module` when a type + /// for it has otherwise not been listed. + /// + /// Note that on success this method returns both the type signature and the + /// index of the module this item was defined in. The index returned may be + /// different than the index of `module` due to alias annotations. + fn item_for<'b, T>( + &'b self, + module: &'b Module<'a>, + idx: &Index<'a>, + ns: Ns, + get_ns: &impl for<'c> Fn(&'c Module<'a>) -> &'c Namespace<'a, T>, + ) -> Result<(&'b T, usize), Error> { + let i = get_ns(module).resolve(&mut idx.clone(), ns.desc())?; + match get_ns(module).items.get(i as usize) { + // This can arise if you do something like `(export "" (func + // 10000))`, in which case we can't infer the type signature of that + // module, so we generate an error. + None => Err(Error::new( + idx.span(), + format!("reference to {} is out of bounds", ns.desc()), + )), + + // This happens when a module refers to something that's defined + // after the module's definition. For example + // + // (module + // (module (export "" (func $f))) ;; refer to later item + // (func $f) ;; item defined too late + // ) + // + // It's not entirely clear how the upstream spec wants to eventually + // handle these cases, but for now re return this error. + Some(Sig::Unknown) => Err(Error::new( + idx.span(), + format!("reference to {} before item is defined", ns.desc()), + )), + + // This item was defined in the module and we know its signature! + // Return as such. + Some(Sig::Defined { item, module }) => Ok((item, *module)), + + // This item is aliasing the export of another module. To figure out + // its type signature we need to recursie into the `module` listed + // here. + Some(Sig::Alias(SigAlias::ModuleExport { + module: module_idx, + export_idx, + })) => { + let module = &self.modules[*module_idx]; + // Look up the `export_idx`th export, then try to extract an + // `Index` from that `ExportKind`, assuming it points to the + // right kind of item. + let kind = module + .exported_ids + .get(*export_idx as usize) + .ok_or_else(|| { + Error::new( + idx.span(), + format!("aliased from an export that does not exist"), + ) + })?; + let (idx, ns2) = Ns::from_export(kind); + if ns != ns2 { + return Err(Error::new( + idx.span(), + format!("alias points to export of wrong kind of item"), + )); + } + + // Once we have our desired index within `module`, recurse! + self.item_for(module, &idx, ns, get_ns) + } + + // This item is aliasing the export of a type. This type could be an + // imported instance or an imported module. (in the latter case the + // alias was referring to an instance instantiated from an imported + // module). In either case we chase the definition of `ty`, get its + // export information, and go from there. + Some(Sig::Alias(SigAlias::TypeExport { + module, + ty, + export_idx, + })) => { + let (info, _m) = match self.type_for(*module, ty)? { + (TypeInfo::Instance { info, .. }, m) => (info, m), + (TypeInfo::Module { info, .. }, m) => (info, m), + _ => { + return Err(Error::new( + ty.span(), + format!( + "aliased from an instance/module that is listed with the wrong type" + ), + )) + } + }; + let kind = info.exported_ids.get(*export_idx as usize).ok_or_else(|| { + Error::new( + idx.span(), + format!("aliased from an export that does not exist"), + ) + })?; + let (idx, ns2) = Ns::from_export(kind); + if ns != ns2 { + return Err(Error::new( + idx.span(), + format!("alias points to export of wrong kind of item"), + )); + } + self.item_for(info, &idx, ns, get_ns) + } + + // In this final case we're aliasing an item defined in a child + // module, so we recurse with that! + Some(Sig::Alias(SigAlias::Item { + module: module_idx, + idx, + })) => self.item_for(&self.modules[*module_idx], &idx, ns, get_ns), + } + } +} + +impl<'a> Module<'a> { + /// This is a helper method to create a synthetic `Module` representing the + /// export information of a module type defined (or instance type). This + /// is used to resolve metadata for aliases which bottom out in imported + /// types of some kind. + fn from_exports( + span: Span, + parent: usize, + exports: &[ExportType<'a>], + ) -> Result, Error> { + let mut ret = Module::default(); + ret.exports = ExportNamespace::from_exports(exports)?; + for export in exports { + // Note that the `.unwrap()`s below should be ok because `TypeUse` + // annotations should all be expanded by this point. + let kind = match &export.item.kind { + ItemKind::Func(ty) => { + let idx = push(parent, span, &mut ret.funcs, ty.index.unwrap()); + ExportKind::Func(idx) + } + ItemKind::Event(EventType::Exception(ty)) => { + let idx = push(parent, span, &mut ret.events, ty.index.unwrap()); + ExportKind::Event(idx) + } + ItemKind::Global(ty) => { + let idx = push(parent, span, &mut ret.globals, ty.clone()); + ExportKind::Global(idx) + } + ItemKind::Memory(ty) => { + let idx = push(parent, span, &mut ret.memories, ty.clone()); + ExportKind::Memory(idx) + } + ItemKind::Table(ty) => { + let idx = push(parent, span, &mut ret.tables, ty.clone()); + ExportKind::Table(idx) + } + ItemKind::Instance(ty) => { + let idx = push( + parent, + span, + &mut ret.instances, + InstanceDef::Import { + type_idx: ty.index.unwrap(), + exports: ExportNamespace::default(), + }, + ); + ExportKind::Instance(idx) + } + ItemKind::Module(ty) => { + let idx = push( + parent, + span, + &mut ret.modules, + ModuleInfo::Imported { + type_idx: ty.index.unwrap(), + exports: ExportNamespace::default(), + }, + ); + ExportKind::Module(idx) + } + }; + ret.exported_ids.push(kind); + } + return Ok(ret); + + fn push<'a, T>(parent: usize, span: Span, ns: &mut Namespace<'a, T>, item: T) -> Index<'a> { + let ret = Index::Num(ns.alloc(), span); + ns.push_item(parent, item); + return ret; + } + } + + fn expand_type(&mut self, item: &mut ModuleField<'a>) { + let ty = match item { + ModuleField::Type(t) => t, + _ => return, + }; + let id = gensym::fill(ty.span, &mut ty.id); + match &mut ty.def { + TypeDef::Func(f) => { + f.key().insert(self, Index::Id(id)); + } + TypeDef::Instance(i) => { + i.expand(self); + i.key().insert(self, Index::Id(id)); + } + TypeDef::Module(m) => { + m.expand(self); + m.key().insert(self, Index::Id(id)); + } + TypeDef::Array(_) | TypeDef::Struct(_) => {} + } + } + + fn register(&mut self, item: &ModuleField<'a>) -> Result<(), Error> { + match item { + ModuleField::Import(i) => match &i.item.kind { + ItemKind::Func(_) => self.funcs.register(i.item.id, "func")?, + ItemKind::Memory(_) => self.memories.register(i.item.id, "memory")?, + ItemKind::Table(_) => self.tables.register(i.item.id, "table")?, + ItemKind::Global(_) => self.globals.register(i.item.id, "global")?, + ItemKind::Event(_) => self.events.register(i.item.id, "event")?, + ItemKind::Module(_) => self.modules.register(i.item.id, "module")?, + ItemKind::Instance(_) => self.instances.register(i.item.id, "instance")?, + }, + ModuleField::Global(i) => self.globals.register(i.id, "global")?, + ModuleField::Memory(i) => self.memories.register(i.id, "memory")?, + ModuleField::Func(i) => self.funcs.register(i.id, "func")?, + ModuleField::Table(i) => self.tables.register(i.id, "table")?, + ModuleField::NestedModule(m) => self.modules.register(m.id, "module")?, + ModuleField::Instance(i) => self.instances.register(i.id, "instance")?, + ModuleField::Type(i) => { - let index = self.ns_mut(Ns::Type).register(i.id, Ns::Type.desc())?; match &i.def { - TypeDef::Func(func) => { - // Store a copy of this function type for resolving - // type uses later. We can't resolve this copy yet, so - // we will take care of that when resolving the type - // use. - self.func_tys.insert(index, func.clone()); - } + // For GC structure types we need to be sure to populate the + // field namespace here as well. + // + // The field namespace is global, but the resolved indices + // are relative to the struct they are defined in TypeDef::Struct(r#struct) => { for (i, field) in r#struct.fields.iter().enumerate() { if let Some(id) = field.id { - // The field namespace is global, but the - // resolved indices are relative to the struct - // they are defined in - self.ns_mut(Ns::Field).register_specific( - id, - i as u32, - Ns::Field.desc(), - )?; + self.fields.register_specific(id, i as u32, "field")?; } } } - TypeDef::Array(_) => {} + + TypeDef::Instance(_) + | TypeDef::Array(_) + | TypeDef::Func(_) + | TypeDef::Module(_) => {} } - Ok(()) + self.types.register(i.id, "type")? } - ModuleField::Elem(e) => register(Ns::Elem, e.id), - ModuleField::Data(d) => register(Ns::Data, d.id), - ModuleField::Event(e) => register(Ns::Event, e.id), - ModuleField::Start(_) | ModuleField::Export(_) | ModuleField::Custom(_) => Ok(()), + ModuleField::Elem(e) => self.elems.register(e.id, "elem")?, + ModuleField::Data(d) => self.datas.register(d.id, "data")?, + ModuleField::Event(e) => self.events.register(e.id, "event")?, + ModuleField::Alias(e) => match e.kind { + ExportKind::Func(_) => self.funcs.register(e.id, "func")?, + ExportKind::Table(_) => self.tables.register(e.id, "table")?, + ExportKind::Memory(_) => self.memories.register(e.id, "memory")?, + ExportKind::Global(_) => self.globals.register(e.id, "global")?, + ExportKind::Instance(_) => self.instances.register(e.id, "instance")?, + ExportKind::Module(_) => self.modules.register(e.id, "module")?, + ExportKind::Event(_) => self.events.register(e.id, "event")?, + ExportKind::Type(_) => self.types.register(e.id, "type")?, + }, + ModuleField::Export(e) => { + let (idx, ns) = Ns::from_export(&e.kind); + self.exports.push( + ns, + match idx { + Index::Id(id) => Some(id), + Index::Num(..) => None, + }, + )?; + self.exported_ids.push(e.kind.clone()); + return Ok(()); + } + ModuleField::Start(_) | ModuleField::Custom(_) => return Ok(()), + + // should not be present in AST by this point + ModuleField::ExportAll(..) => unreachable!(), + }; + + Ok(()) + } + + fn expand_item_sig(&mut self, item: &mut ItemSig<'a>, take_inline: bool) { + match &mut item.kind { + ItemKind::Func(t) | ItemKind::Event(EventType::Exception(t)) => { + self.expand_type_use(t); + } + ItemKind::Instance(t) => { + self.expand_type_use(t); + if take_inline { + t.inline.take(); + } + } + ItemKind::Module(m) => { + self.expand_type_use(m); + if take_inline { + m.inline.take(); + } + } + ItemKind::Global(_) | ItemKind::Table(_) | ItemKind::Memory(_) => {} } } - fn ns_mut(&mut self, ns: Ns) -> &mut Namespace<'a> { - &mut self.ns[ns as usize] + fn expand_expression(&mut self, expr: &mut Expression<'a>) { + for instr in expr.instrs.iter_mut() { + self.expand_instr(instr); + } } - fn ns(&self, ns: Ns) -> &Namespace<'a> { - &self.ns[ns as usize] + fn expand_instr(&mut self, instr: &mut Instruction<'a>) { + match instr { + Instruction::Block(bt) + | Instruction::If(bt) + | Instruction::Loop(bt) + | Instruction::Try(bt) => { + // No expansion necessary, a type reference is already here. + // We'll verify that it's the same as the inline type, if any, + // later. + if bt.ty.index.is_some() { + return; + } + + match &bt.ty.inline { + // Only actually expand `TypeUse` with an index which appends a + // type if it looks like we need one. This way if the + // multi-value proposal isn't enabled and/or used we won't + // encode it. + Some(inline) => { + if inline.params.len() == 0 && inline.results.len() <= 1 { + return; + } + } + + // If we didn't have either an index or an inline type + // listed then assume our block has no inputs/outputs, so + // fill in the inline type here. + // + // Do not fall through to expanding the `TypeUse` because + // this doesn't force an empty function type to go into the + // type section. + None => { + bt.ty.inline = Some(FunctionType::default()); + return; + } + } + self.expand_type_use(&mut bt.ty); + } + Instruction::CallIndirect(c) | Instruction::ReturnCallIndirect(c) => { + self.expand_type_use(&mut c.ty); + } + _ => {} + } + } + + fn expand_type_use(&mut self, item: &mut TypeUse<'a, T>) -> Index<'a> + where + T: TypeReference<'a>, + { + if let Some(idx) = &item.index { + return idx.clone(); + } + let key = match item.inline.as_mut() { + Some(ty) => { + ty.expand(self); + ty.key() + } + None => T::default().key(), + }; + let span = Span::from_offset(0); // FIXME: don't manufacture + let idx = self.key_to_idx(span, key); + item.index = Some(idx); + return idx; + } + + fn key_to_idx(&mut self, span: Span, key: impl TypeKey<'a>) -> Index<'a> { + // First see if this `key` already exists in the type definitions we've + // seen so far... + if let Some(idx) = key.lookup(self) { + return idx; + } + + // ... and failing that we insert a new type definition. + let id = gensym::gen(span); + self.to_prepend.push(ModuleField::Type(Type { + span, + id: Some(id), + def: key.to_def(span), + })); + let idx = Index::Id(id); + key.insert(self, idx); + + // If we're a late call to `key_to_idx` and name registration has + // already happened then we are appending to the index space, so + // register the new type's information here as well. + if self.registered { + let idx = self.types.register(Some(id), "type").unwrap(); + self.types.items[idx as usize] = Sig::Defined { + item: key.into_info(span, self.id), + module: self.id, + }; + } + + return idx; } - pub fn resolve(&self, field: &mut ModuleField<'a>) -> Result<(), Error> { + fn resolve_field(&self, field: &mut ModuleField<'a>) -> Result<(), Error> { match field { ModuleField::Import(i) => { - match &mut i.kind { - ImportKind::Func(t) | ImportKind::Event(EventType::Exception(t)) => { - self.resolve_type_use(i.span, t)?; - } - ImportKind::Global(t) => { - self.resolve_valtype(&mut t.ty)?; + self.resolve_item_sig(&mut i.item)?; + Ok(()) + } + + ModuleField::Type(ty) => { + match &mut ty.def { + TypeDef::Func(func) => func.resolve(self)?, + TypeDef::Struct(struct_) => { + for field in &mut struct_.fields { + self.resolve_valtype(&mut field.ty)?; + } } - _ => {} + TypeDef::Array(array) => self.resolve_valtype(&mut array.ty)?, + TypeDef::Module(m) => m.resolve(self)?, + TypeDef::Instance(i) => i.resolve(self)?, } Ok(()) } - ModuleField::Type(t) => self.resolve_type(t), - ModuleField::Func(f) => { - self.resolve_type_use(f.span, &mut f.ty)?; + let (idx, inline) = self.resolve_type_use(&mut f.ty)?; + let n = match idx { + Index::Num(n, _) => *n, + Index::Id(_) => panic!("expected `Num`"), + }; if let FuncKind::Inline { locals, expression } = &mut f.kind { // Resolve (ref T) in locals for (_, _, valtype) in locals.iter_mut() { @@ -129,11 +1304,19 @@ impl<'a> Resolver<'a> { // Build a resolver with local namespace for the function // body - let mut resolver = ExprResolver::new(self, f.span); + let mut resolver = ExprResolver::new(self); // Parameters come first in the local namespace... - for (id, _, _) in f.ty.func_ty.params.iter() { - resolver.locals.register(*id, "local")?; + if let Some(inline) = &inline { + for (id, _, _) in inline.params.iter() { + resolver.locals.register(*id, "local")?; + } + } else if let Some(TypeInfo::Func((params, _))) = + self.types.items.get(n as usize).and_then(|i| i.item()) + { + for _ in 0..params.len() { + resolver.locals.register(None, "local")?; + } } // .. followed by locals themselves @@ -143,6 +1326,10 @@ impl<'a> Resolver<'a> { // and then we can resolve the expression! resolver.resolve(expression)?; + + // specifically save the original `sig`, if it was present, + // because that's what we're using for local names. + f.ty.inline = inline; } Ok(()) } @@ -150,21 +1337,21 @@ impl<'a> Resolver<'a> { ModuleField::Elem(e) => { match &mut e.kind { ElemKind::Active { table, offset } => { - self.resolve_idx(table, Ns::Table)?; - self.resolve_expr(e.span, offset)?; + self.resolve(table, Ns::Table)?; + self.resolve_expr(offset)?; } ElemKind::Passive { .. } | ElemKind::Declared { .. } => {} } match &mut e.payload { ElemPayload::Indices(elems) => { for idx in elems { - self.resolve_idx(idx, Ns::Func)?; + self.resolve(idx, Ns::Func)?; } } ElemPayload::Exprs { exprs, .. } => { for funcref in exprs { if let Some(idx) = funcref { - self.resolve_idx(idx, Ns::Func)?; + self.resolve(idx, Ns::Func)?; } } } @@ -174,34 +1361,63 @@ impl<'a> Resolver<'a> { ModuleField::Data(d) => { if let DataKind::Active { memory, offset } = &mut d.kind { - self.resolve_idx(memory, Ns::Memory)?; - self.resolve_expr(d.span, offset)?; + self.resolve(memory, Ns::Memory)?; + self.resolve_expr(offset)?; } Ok(()) } ModuleField::Start(i) => { - self.resolve_idx(i, Ns::Func)?; + self.resolve(i, Ns::Func)?; Ok(()) } - ModuleField::Export(e) => match &mut e.kind { - ExportKind::Func(f) => self.resolve_idx(f, Ns::Func), - ExportKind::Memory(f) => self.resolve_idx(f, Ns::Memory), - ExportKind::Global(f) => self.resolve_idx(f, Ns::Global), - ExportKind::Table(f) => self.resolve_idx(f, Ns::Table), - ExportKind::Event(f) => self.resolve_idx(f, Ns::Event), - }, + ModuleField::Export(e) => { + let (idx, ns) = Ns::from_export_mut(&mut e.kind); + self.resolve(idx, ns)?; + Ok(()) + } + + ModuleField::ExportAll(..) => unreachable!(), ModuleField::Global(g) => { self.resolve_valtype(&mut g.ty.ty)?; if let GlobalKind::Inline(expr) = &mut g.kind { - self.resolve_expr(g.span, expr)?; + self.resolve_expr(expr)?; } Ok(()) } - ModuleField::Event(e) => self.resolve_event_type(e.span, &mut e.ty), + ModuleField::Event(e) => { + match &mut e.ty { + EventType::Exception(ty) => { + self.resolve_type_use(ty)?; + } + } + Ok(()) + } + + ModuleField::Instance(i) => { + if let InstanceKind::Inline { module, items } = &mut i.kind { + self.resolve(module, Ns::Module)?; + for item in items { + let (idx, ns) = Ns::from_export_mut(item); + self.resolve(idx, ns)?; + } + } + Ok(()) + } + + ModuleField::NestedModule(i) => { + if let NestedModuleKind::Inline { ty, .. } = &mut i.kind { + let ty = ty.as_mut().expect("type index should be filled in"); + self.resolve(ty, Ns::Type)?; + } + Ok(()) + } + + // Everything about aliases is handled in `expand` above + ModuleField::Alias(_) => Ok(()), ModuleField::Table(_) | ModuleField::Memory(_) | ModuleField::Custom(_) => Ok(()), } @@ -211,9 +1427,7 @@ impl<'a> Resolver<'a> { match ty { ValType::Ref(ty) => self.resolve_reftype(ty)?, ValType::Rtt(i) => { - self.ns(Ns::Type) - .resolve(i) - .map_err(|id| self.resolve_error(id, "type"))?; + self.resolve(i, Ns::Type)?; } _ => {} } @@ -223,116 +1437,103 @@ impl<'a> Resolver<'a> { fn resolve_reftype(&self, ty: &mut RefType<'a>) -> Result<(), Error> { match ty { RefType::Type(i) | RefType::OptType(i) => { - self.ns(Ns::Type) - .resolve(i) - .map_err(|id| self.resolve_error(id, "type"))?; + self.resolve(i, Ns::Type)?; } - _ => {} + RefType::Eq | RefType::Func | RefType::Extern | RefType::Exn | RefType::I31 => {} } Ok(()) } - fn resolve_type(&self, ty: &mut Type<'a>) -> Result<(), Error> { - match &mut ty.def { - TypeDef::Func(func) => self.resolve_function_type(func), - TypeDef::Struct(r#struct) => self.resolve_struct_type(r#struct), - TypeDef::Array(array) => self.resolve_array_type(array), - } - } - - fn resolve_function_type(&self, func: &mut FunctionType<'a>) -> Result<(), Error> { - for param in &mut func.params { - self.resolve_valtype(&mut param.2)?; - } - for result in &mut func.results { - self.resolve_valtype(result)?; - } - Ok(()) - } - - fn resolve_struct_type(&self, r#struct: &mut StructType<'a>) -> Result<(), Error> { - for field in &mut r#struct.fields { - self.resolve_valtype(&mut field.ty)?; + fn resolve_item_sig(&self, item: &mut ItemSig<'a>) -> Result<(), Error> { + match &mut item.kind { + ItemKind::Func(t) | ItemKind::Event(EventType::Exception(t)) => { + self.resolve_type_use(t)?; + } + ItemKind::Global(t) => self.resolve_valtype(&mut t.ty)?, + ItemKind::Instance(t) => { + self.resolve_type_use(t)?; + } + ItemKind::Module(m) => { + self.resolve_type_use(m)?; + } + ItemKind::Table(_) | ItemKind::Memory(_) => {} } Ok(()) } - fn resolve_array_type(&self, array: &mut ArrayType<'a>) -> Result<(), Error> { - self.resolve_valtype(&mut array.ty)?; - Ok(()) - } - - fn resolve_type_use(&self, span: Span, ty: &mut TypeUse<'a>) -> Result { - assert!(ty.index.is_some()); - let idx = self - .ns(Ns::Type) - .resolve(ty.index.as_mut().unwrap()) - .map_err(|id| self.resolve_error(id, "type"))?; + fn resolve_type_use<'b, T>( + &self, + ty: &'b mut TypeUse<'a, T>, + ) -> Result<(&'b Index<'a>, Option), Error> + where + T: TypeReference<'a>, + { + let idx = ty.index.as_mut().unwrap(); + self.resolve(idx, Ns::Type)?; // If the type was listed inline *and* it was specified via a type index // we need to assert they're the same. - let expected = match self.func_tys.get(&(idx as u32)) { - Some(ty) => ty, - None => return Ok(idx), - }; - if ty.func_ty.params.len() > 0 || ty.func_ty.results.len() > 0 { - // The function type for this type use hasn't been resolved, - // but neither has the expected function type, so this comparison - // is valid. - let params_not_equal = expected.params.iter().map(|t| t.2).ne(ty - .func_ty - .params - .iter() - .map(|t| t.2)); - if params_not_equal || expected.results != ty.func_ty.results { - let span = ty.index_span.unwrap_or(span); - return Err(Error::new( - span, - format!("inline function type doesn't match type reference"), - )); - } - } else { - ty.func_ty.params = expected.params.clone(); - ty.func_ty.results = expected.results.clone(); + // + // Note that we resolve the type first to transform all names to + // indices to ensure that all the indices line up. + if let Some(inline) = &mut ty.inline { + inline.resolve(self)?; + inline.check_matches(idx, self)?; } - // Resolve the (ref T) value types in the final function type - self.resolve_function_type(&mut ty.func_ty)?; - - Ok(idx) + Ok((idx, ty.inline.take())) } - fn resolve_expr(&self, span: Span, expr: &mut Expression<'a>) -> Result<(), Error> { - ExprResolver::new(self, span).resolve(expr) + fn resolve_expr(&self, expr: &mut Expression<'a>) -> Result<(), Error> { + ExprResolver::new(self).resolve(expr) } - fn resolve_event_type(&self, span: Span, ty: &mut EventType<'a>) -> Result<(), Error> { - match ty { - EventType::Exception(ty) => { - self.resolve_type_use(span, ty)?; - } + pub fn resolve(&self, idx: &mut Index<'a>, ns: Ns) -> Result { + match ns { + Ns::Func => self.funcs.resolve(idx, "func"), + Ns::Table => self.tables.resolve(idx, "table"), + Ns::Global => self.globals.resolve(idx, "global"), + Ns::Memory => self.memories.resolve(idx, "memory"), + Ns::Instance => self.instances.resolve(idx, "instance"), + Ns::Module => self.modules.resolve(idx, "module"), + Ns::Event => self.events.resolve(idx, "event"), + Ns::Type => self.types.resolve(idx, "type"), } - Ok(()) } +} - pub fn resolve_idx(&self, idx: &mut Index<'a>, ns: Ns) -> Result<(), Error> { - match self.ns(ns).resolve(idx) { - Ok(_n) => Ok(()), - Err(id) => Err(self.resolve_error(id, ns.desc())), - } - } +pub struct Namespace<'a, T> { + names: HashMap, u32>, + count: u32, + next_item: usize, + items: Vec>, +} - fn resolve_error(&self, id: Id<'a>, ns: &str) -> Error { - Error::new( - id.span(), - format!("failed to find {} named `${}`", ns, id.name()), - ) - } +enum Sig<'a, T> { + Defined { item: T, module: usize }, + Alias(SigAlias<'a>), + Unknown, } -impl<'a> Namespace<'a> { +enum SigAlias<'a> { + TypeExport { + module: usize, + ty: Index<'a>, + export_idx: u32, + }, + ModuleExport { + module: usize, + export_idx: u32, + }, + Item { + module: usize, + idx: Index<'a>, + }, +} + +impl<'a, T> Namespace<'a, T> { fn register(&mut self, name: Option>, desc: &str) -> Result { - let index = self.count; + let index = self.alloc(); if let Some(name) = name { if let Some(_prev) = self.names.insert(name, index) { // FIXME: temporarily allow duplicately-named data and element @@ -363,10 +1564,16 @@ impl<'a> Namespace<'a> { } } } - self.count += 1; Ok(index) } + fn alloc(&mut self) -> u32 { + let index = self.count; + self.items.push(Sig::Unknown); + self.count += 1; + return index; + } + fn register_specific(&mut self, name: Id<'a>, index: u32, desc: &str) -> Result<(), Error> { if let Some(_prev) = self.names.insert(name, index) { return Err(Error::new( @@ -377,33 +1584,72 @@ impl<'a> Namespace<'a> { Ok(()) } - fn resolve(&self, idx: &mut Index<'a>) -> Result> { + fn resolve(&self, idx: &mut Index<'a>, desc: &str) -> Result { let id = match idx { - Index::Num(n) => return Ok(*n), + Index::Num(n, _) => return Ok(*n), Index::Id(id) => id, }; if let Some(&n) = self.names.get(id) { - *idx = Index::Num(n); + *idx = Index::Num(n, id.span()); return Ok(n); } - Err(*id) + Err(resolve_error(*id, desc)) + } + + fn push_item(&mut self, module: usize, item: T) { + self.push(Sig::Defined { item, module }); + } + + fn push(&mut self, item: Sig<'a, T>) { + match self.items[self.next_item] { + Sig::Unknown => {} + _ => panic!("item already known"), + } + self.items[self.next_item] = item; + self.next_item += 1; + } +} + +impl Sig<'_, T> { + fn item(&self) -> Option<&T> { + match self { + Sig::Defined { item, .. } => Some(item), + _ => None, + } + } +} + +fn resolve_error(id: Id<'_>, ns: &str) -> Error { + assert!(!id.is_gensym()); + Error::new( + id.span(), + format!("failed to find {} named `${}`", ns, id.name()), + ) +} + +impl<'a, T> Default for Namespace<'a, T> { + fn default() -> Namespace<'a, T> { + Namespace { + names: HashMap::new(), + count: 0, + next_item: 0, + items: Vec::new(), + } } } struct ExprResolver<'a, 'b> { - resolver: &'b Resolver<'a>, - locals: Namespace<'a>, + module: &'b Module<'a>, + locals: Namespace<'a, ()>, labels: Vec>>, - span: Span, } impl<'a, 'b> ExprResolver<'a, 'b> { - fn new(resolver: &'b Resolver<'a>, span: Span) -> ExprResolver<'a, 'b> { + fn new(module: &'b Module<'a>) -> ExprResolver<'a, 'b> { ExprResolver { - resolver, + module, locals: Default::default(), labels: Vec::new(), - span, } } @@ -418,41 +1664,50 @@ impl<'a, 'b> ExprResolver<'a, 'b> { use crate::ast::Instruction::*; match instr { - MemoryInit(i) => self.resolver.resolve_idx(&mut i.data, Ns::Data), - DataDrop(i) => self.resolver.resolve_idx(i, Ns::Data), + MemoryInit(i) => { + self.module.datas.resolve(&mut i.data, "data")?; + } + DataDrop(i) => { + self.module.datas.resolve(i, "data")?; + } TableInit(i) => { - self.resolver.resolve_idx(&mut i.elem, Ns::Elem)?; - self.resolver.resolve_idx(&mut i.table, Ns::Table) + self.module.elems.resolve(&mut i.elem, "elem")?; + self.module.resolve(&mut i.table, Ns::Table)?; + } + ElemDrop(i) => { + self.module.elems.resolve(i, "elem")?; } - ElemDrop(i) => self.resolver.resolve_idx(i, Ns::Elem), TableCopy(i) => { - self.resolver.resolve_idx(&mut i.dst, Ns::Table)?; - self.resolver.resolve_idx(&mut i.src, Ns::Table) + self.module.resolve(&mut i.dst, Ns::Table)?; + self.module.resolve(&mut i.src, Ns::Table)?; } TableFill(i) | TableSet(i) | TableGet(i) | TableSize(i) | TableGrow(i) => { - self.resolver.resolve_idx(&mut i.dst, Ns::Table) + self.module.resolve(&mut i.dst, Ns::Table)?; } - GlobalSet(i) | GlobalGet(i) => self.resolver.resolve_idx(i, Ns::Global), + GlobalSet(i) | GlobalGet(i) => { + self.module.resolve(i, Ns::Global)?; + } - LocalSet(i) | LocalGet(i) | LocalTee(i) => self - .locals - .resolve(i) - .map(|_| ()) - .map_err(|id| self.resolver.resolve_error(id, "local")), + LocalSet(i) | LocalGet(i) | LocalTee(i) => { + self.locals.resolve(i, "local")?; + } - Call(i) | RefFunc(i) | ReturnCall(i) => self.resolver.resolve_idx(i, Ns::Func), + Call(i) | RefFunc(i) | ReturnCall(i) => { + self.module.resolve(i, Ns::Func)?; + } CallIndirect(c) | ReturnCallIndirect(c) => { - self.resolver.resolve_idx(&mut c.table, Ns::Table)?; - self.resolver.resolve_type_use(self.span, &mut c.ty)?; - Ok(()) + self.module.resolve(&mut c.table, Ns::Table)?; + self.module.resolve_type_use(&mut c.ty)?; } - FuncBind(i) => self.resolver.resolve_idx(i, Ns::Type), + FuncBind(i) => { + self.module.resolve(i, Ns::Type)?; + } Block(bt) | If(bt) | Loop(bt) | Try(bt) => { self.labels.push(bt.label); @@ -483,22 +1738,34 @@ impl<'a, 'b> ExprResolver<'a, 'b> { // out `params` and `results` if we can, otherwise no work // happens. if bt.ty.index.is_some() { - let ty = self.resolver.resolve_type_use(self.span, &mut bt.ty)?; - let ty = match self.resolver.func_tys.get(&(ty as u32)) { - Some(ty) => ty, - None => return Ok(()), + let (ty, _) = self.module.resolve_type_use(&mut bt.ty)?; + let n = match ty { + Index::Num(n, _) => *n, + Index::Id(_) => panic!("expected `Num`"), + }; + let ty = match self + .module + .types + .items + .get(n as usize) + .and_then(|s| s.item()) + { + Some(TypeInfo::Func(ty)) => ty, + _ => return Ok(()), }; - if ty.params.len() == 0 && ty.results.len() <= 1 { - bt.ty.func_ty.params.truncate(0); - bt.ty.func_ty.results = ty.results.clone(); + if ty.0.len() == 0 && ty.1.len() <= 1 { + let mut inline = FunctionType::default(); + inline.results = ty.1.clone(); + bt.ty.inline = Some(inline); bt.ty.index = None; } } - // Resolve (ref T) params and results - self.resolver.resolve_function_type(&mut bt.ty.func_ty)?; - - Ok(()) + // If the inline annotation persists to this point then resolve + // all of its inline value types. + if let Some(inline) = &mut bt.ty.inline { + inline.resolve(self.module)?; + } } // On `End` instructions we pop a label from the stack, and for both @@ -527,51 +1794,58 @@ impl<'a, 'b> ExprResolver<'a, 'b> { )); } - Br(i) | BrIf(i) | BrOnCast(i) | BrOnNull(i) => self.resolve_label(i), + Br(i) | BrIf(i) | BrOnCast(i) | BrOnNull(i) => { + self.resolve_label(i)?; + } BrTable(i) => { for label in i.labels.iter_mut() { self.resolve_label(label)?; } - self.resolve_label(&mut i.default) + self.resolve_label(&mut i.default)?; } - Throw(i) => self.resolver.resolve_idx(i, Ns::Event), + Throw(i) => { + self.module.resolve(i, Ns::Event)?; + } BrOnExn(b) => { self.resolve_label(&mut b.label)?; - self.resolver.resolve_idx(&mut b.exn, Ns::Event) + self.module.resolve(&mut b.exn, Ns::Event)?; } Select(s) => { if let Some(list) = &mut s.tys { for ty in list { - self.resolver.resolve_valtype(ty)?; + self.module.resolve_valtype(ty)?; } } - Ok(()) } StructNew(i) | StructNewSub(i) | StructNewDefault(i) | ArrayNew(i) | ArrayNewSub(i) | ArrayNewDefault(i) | ArrayGet(i) | ArrayGetS(i) | ArrayGetU(i) | ArraySet(i) - | ArrayLen(i) | RTTGet(i) | RTTSub(i) => self.resolver.resolve_idx(i, Ns::Type), + | ArrayLen(i) | RTTGet(i) | RTTSub(i) => { + self.module.resolve(i, Ns::Type)?; + } + StructSet(s) | StructGet(s) | StructGetS(s) | StructGetU(s) => { - self.resolver.resolve_idx(&mut s.r#struct, Ns::Type)?; - self.resolver.resolve_idx(&mut s.field, Ns::Field) + self.module.resolve(&mut s.r#struct, Ns::Type)?; + self.module.fields.resolve(&mut s.field, "field")?; } StructNarrow(s) => { - self.resolver.resolve_valtype(&mut s.from)?; - self.resolver.resolve_valtype(&mut s.to) + self.module.resolve_valtype(&mut s.from)?; + self.module.resolve_valtype(&mut s.to)?; } - RefNull(ty) => self.resolver.resolve_reftype(ty), + RefNull(ty) => self.module.resolve_reftype(ty)?, - _ => Ok(()), + _ => {} } + Ok(()) } fn resolve_label(&self, label: &mut Index<'a>) -> Result<(), Error> { let id = match label { - Index::Num(_) => return Ok(()), + Index::Num(..) => return Ok(()), Index::Id(id) => *id, }; let idx = self @@ -583,10 +1857,397 @@ impl<'a, 'b> ExprResolver<'a, 'b> { .find(|(_, l)| *l == id); match idx { Some((idx, _)) => { - *label = Index::Num(idx as u32); + *label = Index::Num(idx as u32, id.span()); Ok(()) } - None => Err(self.resolver.resolve_error(id, "label")), + None => Err(resolve_error(id, "label")), + } + } +} + +enum TypeInfo<'a> { + Func(FuncKey<'a>), + Module { + key: ModuleKey<'a>, + info: Module<'a>, + }, + Instance { + key: InstanceKey<'a>, + info: Module<'a>, + }, + Other, +} + +trait TypeReference<'a>: Default { + type Key: TypeKey<'a>; + fn key(&self) -> Self::Key; + fn expand(&mut self, cx: &mut Module<'a>); + fn check_matches(&mut self, idx: &Index<'a>, cx: &Module<'a>) -> Result<(), Error>; + fn resolve(&mut self, cx: &Module<'a>) -> Result<(), Error>; +} + +trait TypeKey<'a> { + fn lookup(&self, cx: &Module<'a>) -> Option>; + fn to_def(&self, span: Span) -> TypeDef<'a>; + fn insert(&self, cx: &mut Module<'a>, id: Index<'a>); + fn into_info(self, span: Span, cur: usize) -> TypeInfo<'a>; +} + +type FuncKey<'a> = (Vec>, Vec>); + +impl<'a> TypeReference<'a> for FunctionType<'a> { + type Key = FuncKey<'a>; + + fn key(&self) -> Self::Key { + let params = self.params.iter().map(|p| p.2).collect::>(); + let results = self.results.clone(); + (params, results) + } + + fn expand(&mut self, _cx: &mut Module<'a>) {} + + fn check_matches(&mut self, idx: &Index<'a>, cx: &Module<'a>) -> Result<(), Error> { + let n = match idx { + Index::Num(n, _) => *n, + Index::Id(_) => panic!("expected `Num`"), + }; + let (params, results) = match cx.types.items.get(n as usize).and_then(|i| i.item()) { + Some(TypeInfo::Func(ty)) => ty, + _ => return Ok(()), + }; + + // Here we need to check that the inline type listed (ourselves) matches + // what was listed in the module itself (the `params` and `results` + // above). The listed values in `types` are not resolved yet, although + // we should be resolved. In any case we do name resolution + // opportunistically here to see if the values are equal. + + let types_not_equal = |a: &ValType, b: &ValType| { + let mut a = a.clone(); + let mut b = b.clone(); + drop(cx.resolve_valtype(&mut a)); + drop(cx.resolve_valtype(&mut b)); + a != b + }; + + let not_equal = params.len() != self.params.len() + || results.len() != self.results.len() + || params + .iter() + .zip(self.params.iter()) + .any(|(a, (_, _, b))| types_not_equal(a, b)) + || results + .iter() + .zip(self.results.iter()) + .any(|(a, b)| types_not_equal(a, b)); + if not_equal { + return Err(Error::new( + idx.span(), + format!("inline function type doesn't match type reference"), + )); + } + + Ok(()) + } + + fn resolve(&mut self, cx: &Module<'a>) -> Result<(), Error> { + // Resolve the (ref T) value types in the final function type + for param in &mut self.params { + cx.resolve_valtype(&mut param.2)?; + } + for result in &mut self.results { + cx.resolve_valtype(result)?; + } + Ok(()) + } +} + +impl<'a> TypeKey<'a> for FuncKey<'a> { + fn lookup(&self, cx: &Module<'a>) -> Option> { + cx.func_type_to_idx.get(self).cloned() + } + + fn to_def(&self, _span: Span) -> TypeDef<'a> { + TypeDef::Func(FunctionType { + params: self.0.iter().map(|t| (None, None, *t)).collect(), + results: self.1.clone(), + }) + } + + fn insert(&self, cx: &mut Module<'a>, idx: Index<'a>) { + cx.func_type_to_idx.entry(self.clone()).or_insert(idx); + } + + fn into_info(self, _span: Span, _cur: usize) -> TypeInfo<'a> { + TypeInfo::Func(self) + } +} + +// A list of the exports of a module as well as the signature they export. +type InstanceKey<'a> = Vec<(&'a str, Item<'a>)>; + +impl<'a> TypeReference<'a> for InstanceType<'a> { + type Key = InstanceKey<'a>; + + fn key(&self) -> Self::Key { + self.exports + .iter() + .map(|export| (export.name, Item::new(&export.item))) + .collect() + } + + fn expand(&mut self, cx: &mut Module<'a>) { + for export in self.exports.iter_mut() { + cx.expand_item_sig(&mut export.item, true); + } + } + + fn check_matches(&mut self, idx: &Index<'a>, cx: &Module<'a>) -> Result<(), Error> { + drop(cx); + Err(Error::new( + idx.span(), + format!("cannot specify instance type as a reference and inline"), + )) + } + + fn resolve(&mut self, cx: &Module<'a>) -> Result<(), Error> { + for export in self.exports.iter_mut() { + cx.resolve_item_sig(&mut export.item)?; + } + Ok(()) + } +} + +impl<'a> TypeKey<'a> for InstanceKey<'a> { + fn lookup(&self, cx: &Module<'a>) -> Option> { + cx.instance_type_to_idx.get(self).cloned() + } + + fn to_def(&self, span: Span) -> TypeDef<'a> { + let exports = self + .iter() + .map(|(name, item)| ExportType { + span, + name, + item: item.to_sig(span), + }) + .collect(); + TypeDef::Instance(InstanceType { exports }) + } + + fn insert(&self, cx: &mut Module<'a>, idx: Index<'a>) { + cx.instance_type_to_idx.entry(self.clone()).or_insert(idx); + } + + fn into_info(self, span: Span, cur: usize) -> TypeInfo<'a> { + let exports = self + .iter() + .map(|(name, item)| ExportType { + span, + name, + item: item.to_sig(span), + }) + .collect::>(); + TypeInfo::Instance { + key: self, + // if exports information fails to get generated for duplicate + // information we just don't make any names available for + // resolution to have errors generated elsewhere. + info: Module::from_exports(span, cur, &exports).unwrap_or_default(), + } + } +} + +// The first element of this pair is the list of imports in the module, and the +// second element is the list of exports. +type ModuleKey<'a> = ( + Vec<(&'a str, Option<&'a str>, Item<'a>)>, + Vec<(&'a str, Item<'a>)>, +); + +impl<'a> TypeReference<'a> for ModuleType<'a> { + type Key = ModuleKey<'a>; + + fn key(&self) -> Self::Key { + assert!(self.instance_exports.is_empty()); + let imports = self + .imports + .iter() + .map(|import| (import.module, import.field, Item::new(&import.item))) + .collect(); + let exports = self + .exports + .iter() + .map(|export| (export.name, Item::new(&export.item))) + .collect(); + (imports, exports) + } + + fn expand(&mut self, cx: &mut Module<'a>) { + assert!(self.instance_exports.is_empty()); + for export in self.exports.iter_mut() { + cx.expand_item_sig(&mut export.item, true); + } + for import in self.imports.iter_mut() { + cx.expand_item_sig(&mut import.item, true); + } + } + + fn check_matches(&mut self, idx: &Index<'a>, cx: &Module<'a>) -> Result<(), Error> { + drop(cx); + Err(Error::new( + idx.span(), + format!("cannot specify module type as a reference and inline"), + )) + } + + fn resolve(&mut self, cx: &Module<'a>) -> Result<(), Error> { + assert!(self.instance_exports.is_empty()); + for i in self.imports.iter_mut() { + cx.resolve_item_sig(&mut i.item)?; + } + for e in self.exports.iter_mut() { + cx.resolve_item_sig(&mut e.item)?; + } + Ok(()) + } +} + +impl<'a> TypeKey<'a> for ModuleKey<'a> { + fn lookup(&self, cx: &Module<'a>) -> Option> { + cx.module_type_to_idx.get(self).cloned() + } + + fn to_def(&self, span: Span) -> TypeDef<'a> { + let imports = self + .0 + .iter() + .map(|(module, field, item)| Import { + span, + module, + field: *field, + item: item.to_sig(span), + }) + .collect(); + let exports = self + .1 + .iter() + .map(|(name, item)| ExportType { + span, + name, + item: item.to_sig(span), + }) + .collect(); + TypeDef::Module(ModuleType { + imports, + exports, + instance_exports: Vec::new(), + }) + } + + fn insert(&self, cx: &mut Module<'a>, idx: Index<'a>) { + cx.module_type_to_idx.entry(self.clone()).or_insert(idx); + } + + fn into_info(self, span: Span, cur: usize) -> TypeInfo<'a> { + let exports = self + .1 + .iter() + .map(|(name, item)| ExportType { + span, + name, + item: item.to_sig(span), + }) + .collect::>(); + TypeInfo::Module { + key: self, + // see above comment for why `unwrap_or_default` is used here. + info: Module::from_exports(span, cur, &exports).unwrap_or_default(), + } + } +} + +// A lookalike to `ItemKind` except without all non-relevant information for +// hashing. This is used as a hash key for instance/module type lookup. +#[derive(Clone, PartialEq, Eq, Hash)] +enum Item<'a> { + Func(Index<'a>), + Table(TableType), + Memory(MemoryType), + Global(GlobalType<'a>), + Event(Index<'a>), + Module(Index<'a>), + Instance(Index<'a>), +} + +impl<'a> Item<'a> { + fn new(item: &ItemSig<'a>) -> Item<'a> { + match &item.kind { + ItemKind::Func(f) => Item::Func(f.index.expect("should have index")), + ItemKind::Instance(f) => Item::Instance(f.index.expect("should have index")), + ItemKind::Module(f) => Item::Module(f.index.expect("should have index")), + ItemKind::Event(EventType::Exception(f)) => { + Item::Event(f.index.expect("should have index")) + } + ItemKind::Table(t) => Item::Table(t.clone()), + ItemKind::Memory(t) => Item::Memory(t.clone()), + ItemKind::Global(t) => Item::Global(t.clone()), + } + } + + fn to_sig(&self, span: Span) -> ItemSig<'a> { + let kind = match self { + Item::Func(index) => ItemKind::Func(TypeUse::new_with_index(*index)), + Item::Event(index) => { + ItemKind::Event(EventType::Exception(TypeUse::new_with_index(*index))) + } + Item::Instance(index) => ItemKind::Instance(TypeUse::new_with_index(*index)), + Item::Module(index) => ItemKind::Module(TypeUse::new_with_index(*index)), + Item::Table(t) => ItemKind::Table(t.clone()), + Item::Memory(t) => ItemKind::Memory(t.clone()), + Item::Global(t) => ItemKind::Global(t.clone()), + }; + ItemSig { + span, + id: None, + name: None, + kind, + } + } +} + +#[derive(Default)] +struct ExportNamespace<'a> { + names: HashMap<(Ns, Id<'a>), u32>, + num: u32, +} + +impl<'a> ExportNamespace<'a> { + fn from_exports(exports: &[ExportType<'a>]) -> Result, Error> { + let mut ret = ExportNamespace::default(); + for export in exports { + ret.push(Ns::from_item(&export.item), export.item.id)?; + } + Ok(ret) + } + + fn push(&mut self, ns: Ns, id: Option>) -> Result<(), Error> { + if let Some(id) = id { + self.names.insert((ns, id), self.num); + } + self.num += 1; + Ok(()) + } + + fn resolve(&self, idx: &mut Index<'a>, ns: Ns) -> Result { + let id = match idx { + Index::Num(n, _) => return Ok(*n), + Index::Id(id) => id, + }; + if let Some(&n) = self.names.get(&(ns, *id)) { + *idx = Index::Num(n, id.span()); + return Ok(n); } + Err(resolve_error(*id, ns.desc())) } } diff --git a/crates/wast/src/resolve/tyexpand.rs b/crates/wast/src/resolve/tyexpand.rs deleted file mode 100644 index 30bdf2e8f4..0000000000 --- a/crates/wast/src/resolve/tyexpand.rs +++ /dev/null @@ -1,132 +0,0 @@ -use crate::ast::*; -use std::collections::HashMap; - -#[derive(Default)] -pub struct Expander<'a> { - pub to_prepend: Vec>, - func_types: HashMap<(Vec>, Vec>), u32>, - ntypes: u32, -} - -impl<'a> Expander<'a> { - pub fn expand(&mut self, item: &mut ModuleField<'a>) { - match item { - ModuleField::Type(t) => self.register_type(t), - ModuleField::Import(i) => self.expand_import(i), - ModuleField::Func(f) => self.expand_func(f), - ModuleField::Global(g) => self.expand_global(g), - ModuleField::Data(d) => self.expand_data(d), - ModuleField::Elem(e) => self.expand_elem(e), - ModuleField::Event(e) => self.expand_event(e), - _ => {} - } - } - - fn register_type(&mut self, ty: &Type<'a>) { - if let TypeDef::Func(func) = &ty.def { - let key = self.key(func); - if !self.func_types.contains_key(&key) { - self.func_types.insert(key, self.ntypes); - } - } - self.ntypes += 1; - } - - fn expand_import(&mut self, import: &mut Import<'a>) { - match &mut import.kind { - ImportKind::Func(t) | ImportKind::Event(EventType::Exception(t)) => { - self.expand_type_use(t) - } - _ => {} - } - } - - fn expand_func(&mut self, func: &mut Func<'a>) { - self.expand_type_use(&mut func.ty); - if let FuncKind::Inline { expression, .. } = &mut func.kind { - self.expand_expression(expression); - } - } - - fn expand_global(&mut self, global: &mut Global<'a>) { - if let GlobalKind::Inline(expr) = &mut global.kind { - self.expand_expression(expr); - } - } - - fn expand_elem(&mut self, elem: &mut Elem<'a>) { - if let ElemKind::Active { offset, .. } = &mut elem.kind { - self.expand_expression(offset); - } - } - - fn expand_event(&mut self, event: &mut Event<'a>) { - match &mut event.ty { - EventType::Exception(ty) => self.expand_type_use(ty), - } - } - - fn expand_data(&mut self, data: &mut Data<'a>) { - if let DataKind::Active { offset, .. } = &mut data.kind { - self.expand_expression(offset); - } - } - - fn expand_expression(&mut self, expr: &mut Expression<'a>) { - for instr in expr.instrs.iter_mut() { - self.expand_instr(instr); - } - } - - fn expand_instr(&mut self, instr: &mut Instruction<'a>) { - match instr { - Instruction::Block(bt) - | Instruction::If(bt) - | Instruction::Loop(bt) - | Instruction::Try(bt) => { - // Only actually expand `TypeUse` with an index which appends a - // type if it looks like we need one. This way if the - // multi-value proposal isn't enabled and/or used we won't - // encode it. - if bt.ty.func_ty.params.len() == 0 && bt.ty.func_ty.results.len() <= 1 { - return; - } - self.expand_type_use(&mut bt.ty) - } - Instruction::CallIndirect(c) | Instruction::ReturnCallIndirect(c) => { - self.expand_type_use(&mut c.ty) - } - _ => {} - } - } - - fn expand_type_use(&mut self, item: &mut TypeUse<'a>) { - if item.index.is_some() { - return; - } - let key = self.key(&item.func_ty); - item.index = Some(Index::Num(match self.func_types.get(&key) { - Some(i) => *i, - None => self.prepend(key), - })); - } - - fn key(&self, ty: &FunctionType<'a>) -> (Vec>, Vec>) { - let params = ty.params.iter().map(|p| p.2).collect::>(); - let results = ty.results.clone(); - (params, results) - } - - fn prepend(&mut self, key: (Vec>, Vec>)) -> u32 { - self.to_prepend.push(ModuleField::Type(Type { - id: None, - def: TypeDef::Func(FunctionType { - params: key.0.iter().map(|t| (None, None, *t)).collect(), - results: key.1.clone(), - }), - })); - self.func_types.insert(key, self.ntypes); - self.ntypes += 1; - return self.ntypes - 1; - } -} diff --git a/src/bin/wasm-validate-rs.rs b/src/bin/wasm-validate-rs.rs index 7a75034807..a894239242 100644 --- a/src/bin/wasm-validate-rs.rs +++ b/src/bin/wasm-validate-rs.rs @@ -19,6 +19,11 @@ fn main() -> Result<()> { ); opts.optflag("", "enable-multi-value", "Enable wasm multi-value feature"); opts.optflag("", "enable-tail-call", "Enable wasm tail-call feature"); + opts.optflag( + "", + "enable-module-linking", + "Enable wasm module-linking feature", + ); #[cfg(feature = "deterministic")] opts.optflag( "", @@ -47,6 +52,7 @@ fn main() -> Result<()> { enable_bulk_memory: matches.opt_present("enable-bulk-memory"), enable_multi_value: matches.opt_present("enable-multi-value"), enable_tail_call: matches.opt_present("enable-tail-call"), + enable_module_linking: matches.opt_present("enable-module-linking"), #[cfg(feature = "deterministic")] deterministic_only: matches.opt_present("deterministic-only"), }, diff --git a/tests/dump.rs b/tests/dump.rs index 0e922ce32f..635800dd10 100644 --- a/tests/dump.rs +++ b/tests/dump.rs @@ -120,6 +120,7 @@ struct Dump<'a> { cur: usize, state: String, dst: String, + nesting: u32, } const NBYTES: usize = 4; @@ -129,20 +130,30 @@ impl<'a> Dump<'a> { Dump { bytes, cur: 0, + nesting: 0, state: String::new(), dst: String::new(), } } fn run(&mut self) -> Result<()> { - let mut parser = ModuleReader::new(self.bytes)?; + self.print_module(ModuleReader::new(self.bytes)?)?; + assert_eq!(self.cur, self.bytes.len()); + Ok(()) + } + + fn print_module(&mut self, mut parser: ModuleReader<'a>) -> Result<()> { + self.nesting += 1; write!(self.state, "version {}", parser.get_version())?; - self.print(parser.current_position())?; + self.print(parser.original_position())?; let mut funcs = 0; let mut globals = 0; let mut tables = 0; let mut memories = 0; + let mut modules = 0; + let mut instances = 0; + let mut types = 0; while !parser.eof() { let section = parser.read()?; @@ -151,7 +162,8 @@ impl<'a> Dump<'a> { match section.code { SectionCode::Type => { self.print_iter(section.get_type_section_reader()?, |me, end, i| { - write!(me.state, "type {:?}", i)?; + write!(me.state, "[type {}] {:?}", types, i)?; + types += 1; me.print(end) })? } @@ -175,6 +187,14 @@ impl<'a> Dump<'a> { write!(me.state, "[global {}]", globals)?; globals += 1; } + ImportSectionEntryType::Instance(_) => { + write!(me.state, "[instance {}]", instances)?; + instances += 1; + } + ImportSectionEntryType::Module(_) => { + write!(me.state, "[module {}]", modules)?; + modules += 1; + } } write!(me.state, " {:?}", i)?; me.print(end) @@ -216,6 +236,48 @@ impl<'a> Dump<'a> { me.print_ops(i.init_expr.get_operators_reader()) })? } + SectionCode::Alias => { + self.print_iter(section.get_alias_section_reader()?, |me, end, i| { + write!(me.state, "[alias] {:?}", i)?; + match i.kind { + ExternalKind::Function => funcs += 1, + ExternalKind::Global => globals += 1, + ExternalKind::Module => modules += 1, + ExternalKind::Table => tables += 1, + ExternalKind::Instance => instances += 1, + ExternalKind::Memory => memories += 1, + ExternalKind::Type => types += 1, + } + me.print(end) + })? + } + SectionCode::Module => { + let mut cnt = 0; + self.print_iter(section.get_module_section_reader()?, |me, end, i| { + write!(me.state, "[module {}] type {:?}", cnt + modules, i)?; + cnt += 1; + me.print(end) + })? + } + SectionCode::Instance => { + self.print_iter(section.get_instance_section_reader()?, |me, _end, i| { + write!(me.state, "[instance {}] module:{}", instances, i.module())?; + me.print(i.original_position())?; + instances += 1; + me.print_iter(i.args()?, |me, end, (kind, index)| { + write!(me.state, "[instantiate arg] {:?} {}", kind, index)?; + me.print(end) + }) + })? + } + SectionCode::ModuleCode => { + self.print_iter(section.get_module_code_section_reader()?, |me, _end, i| { + write!(me.state, "inline module")?; + me.print(i.original_position())?; + modules += 1; + me.print_module(i.module()?) + })? + } SectionCode::Start => { let start = section.get_start_section_content()?; write!(self.state, "start function {}", start)?; @@ -306,22 +368,72 @@ impl<'a> Dump<'a> { })? } - SectionCode::Custom { .. } => { - write!(self.dst, "0x{:04x} |", self.cur)?; - for _ in 0..NBYTES { - write!(self.dst, "---")?; + SectionCode::Custom { kind, name: _ } => match kind { + CustomSectionKind::Name => { + let mut iter = section.get_name_section_reader()?; + while !iter.eof() { + self.print_custom_name_section(iter.read()?, iter.original_position())?; + } } - write!( - self.dst, - "-| ... {} bytes of data\n", - section.get_binary_reader().bytes_remaining() - )?; - self.cur = section.range().end; - } + _ => { + write!(self.dst, "0x{:04x} |", self.cur)?; + for _ in 0..NBYTES { + write!(self.dst, "---")?; + } + write!( + self.dst, + "-| ... {} bytes of data\n", + section.get_binary_reader().bytes_remaining() + )?; + self.cur = section.range().end; + } + }, } } - assert_eq!(self.cur, self.bytes.len()); + self.nesting -= 1; + Ok(()) + } + + fn print_custom_name_section(&mut self, name: Name<'_>, end: usize) -> Result<()> { + match name { + Name::Module(n) => { + write!(self.state, "module name")?; + self.print(n.original_position())?; + write!(self.state, "{:?}", n.get_name()?)?; + self.print(end)?; + } + Name::Function(n) => { + write!(self.state, "function names")?; + self.print(n.original_position())?; + let mut map = n.get_map()?; + write!(self.state, "{} count", map.get_count())?; + self.print(map.original_position())?; + for _ in 0..map.get_count() { + write!(self.state, "{:?}", map.read()?)?; + self.print(map.original_position())?; + } + } + Name::Local(n) => { + write!(self.state, "local names")?; + self.print(n.original_position())?; + let mut function_map = n.get_function_local_reader()?; + write!(self.state, "{} count", function_map.get_count())?; + self.print(function_map.original_position())?; + for _ in 0..function_map.get_count() { + let function_names = function_map.read()?; + write!(self.state, "function {} locals", function_names.func_index)?; + self.print(function_names.original_position())?; + let mut map = function_names.get_map()?; + write!(self.state, "{} count", map.get_count())?; + self.print(map.original_position())?; + for _ in 0..map.get_count() { + write!(self.state, "{:?}", map.read()?)?; + self.print(map.original_position())?; + } + } + } + } Ok(()) } @@ -357,11 +469,23 @@ impl<'a> Dump<'a> { } fn print(&mut self, end: usize) -> Result<()> { - assert!(self.cur < end); + assert!( + self.cur < end, + "{:#x} >= {:#x}\n{}", + self.cur, + end, + self.dst + ); let bytes = &self.bytes[self.cur..end]; + for _ in 0..self.nesting - 1 { + write!(self.dst, " ")?; + } write!(self.dst, "0x{:04x} |", self.cur)?; for (i, chunk) in bytes.chunks(NBYTES).enumerate() { if i > 0 { + for _ in 0..self.nesting - 1 { + write!(self.dst, " ")?; + } self.dst.push_str(" |"); } for j in 0..NBYTES { diff --git a/tests/dump/alias.wat b/tests/dump/alias.wat new file mode 100644 index 0000000000..28a6194505 --- /dev/null +++ b/tests/dump/alias.wat @@ -0,0 +1,9 @@ +(module + (import "i" (instance $i + (export "f1" (func $f1)) + (export "f2" (func $f2 (param i32))) + )) + (func (export "run") + call $i.$f1 + ) +) diff --git a/tests/dump/alias.wat.dump b/tests/dump/alias.wat.dump new file mode 100644 index 0000000000..65203918d4 --- /dev/null +++ b/tests/dump/alias.wat.dump @@ -0,0 +1,36 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 14 | section Type +0x000a | 03 | 3 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 60 01 7f 00 | [type 1] Func(FuncType { params: [I32], returns: [] }) +0x0012 | 62 02 02 66 | [type 2] Instance(InstanceType { exports: [ExportType { name: "f1", ty: Function(0) }, ExportType { name: "f2", ty: Function(1) }] }) + | 31 00 00 02 + | 66 32 00 01 +0x001e | 02 07 | section Import +0x0020 | 01 | 1 count +0x0021 | 01 69 01 c0 | import [instance 0] Import { module: "i", field: None, ty: Instance(2) } + | 06 02 +0x0027 | 66 05 | section Alias +0x0029 | 01 | 1 count +0x002a | 00 00 00 00 | [alias] Alias { instance: Child(0), kind: Function, index: 0 } +0x002e | 03 02 | section Function +0x0030 | 01 | 1 count +0x0031 | 00 | [func 1] type 0 +0x0032 | 07 07 | section Export +0x0034 | 01 | 1 count +0x0035 | 03 72 75 6e | export Export { field: "run", kind: Function, index: 1 } + | 00 01 +0x003b | 0a 06 | section Code +0x003d | 01 | 1 count +============== func 1 ==================== +0x003e | 04 | size of function +0x003f | 00 | 0 local blocks +0x0040 | 10 00 | Call { function_index: 0 } +0x0042 | 0b | End +0x0043 | 00 0f 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x004a | 01 08 | function names +0x004c | 01 | 1 count +0x004d | 00 05 69 2e | Naming { index: 0, name: "i.$f1" } + | 24 66 31 diff --git a/tests/dump/alias2.wat b/tests/dump/alias2.wat new file mode 100644 index 0000000000..ff3609c612 --- /dev/null +++ b/tests/dump/alias2.wat @@ -0,0 +1,28 @@ +(module + (import "" (instance $i + (export "1" (func $func)) + (export "2" (memory $memory 1)) + (export "3" (table $table 1 funcref)) + (export "4" (global $global i32)) + (export "5" (module $module)) + (export "6" (instance $instance)) + )) + + (module $m + (import "" (func)) + (import "" (memory 1)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (module)) + (import "" (instance)) + ) + + (instance (instantiate $m + (func $i.$func) + (memory $i.$memory) + (global $i.$global) + (table $i.$table) + (module $i.$module) + (instance $i.$instance) + )) +) diff --git a/tests/dump/alias2.wat.dump b/tests/dump/alias2.wat.dump new file mode 100644 index 0000000000..f074c04991 --- /dev/null +++ b/tests/dump/alias2.wat.dump @@ -0,0 +1,85 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 4c | section Type +0x000a | 05 | 5 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 61 00 00 | [type 1] Module(ModuleType { imports: [], exports: [] }) +0x0011 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) +0x0013 | 62 06 01 31 | [type 3] Instance(InstanceType { exports: [ExportType { name: "1", ty: Function(0) }, ExportType { name: "2", ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) }, ExportType { name: "3", ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) }, ExportType { name: "4", ty: Global(GlobalType { content_type: I32, mutable: false }) }, ExportType { name: "5", ty: Module(1) }, ExportType { name: "6", ty: Instance(2) }] }) + | 00 00 01 32 + | 02 00 01 01 + | 33 01 70 00 + | 01 01 34 03 + | 7f 00 01 35 + | 05 01 01 36 + | 06 02 +0x0031 | 61 06 00 01 | [type 4] Module(ModuleType { imports: [Import { module: "", field: None, ty: Function(0) }, Import { module: "", field: None, ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) }, Import { module: "", field: None, ty: Global(GlobalType { content_type: I32, mutable: false }) }, Import { module: "", field: None, ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) }, Import { module: "", field: None, ty: Module(1) }, Import { module: "", field: None, ty: Instance(2) }], exports: [] }) + | c0 00 00 00 + | 01 c0 02 00 + | 01 00 01 c0 + | 03 7f 00 00 + | 01 c0 01 70 + | 00 01 00 01 + | c0 05 01 00 + | 01 c0 06 02 + | 00 +0x0056 | 02 06 | section Import +0x0058 | 01 | 1 count +0x0059 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(3) } + | 03 +0x005e | 66 19 | section Alias +0x0060 | 06 | 6 count +0x0061 | 00 00 00 00 | [alias] Alias { instance: Child(0), kind: Function, index: 0 } +0x0065 | 00 00 02 01 | [alias] Alias { instance: Child(0), kind: Memory, index: 1 } +0x0069 | 00 00 01 02 | [alias] Alias { instance: Child(0), kind: Table, index: 2 } +0x006d | 00 00 03 03 | [alias] Alias { instance: Child(0), kind: Global, index: 3 } +0x0071 | 00 00 05 04 | [alias] Alias { instance: Child(0), kind: Module, index: 4 } +0x0075 | 00 00 06 05 | [alias] Alias { instance: Child(0), kind: Instance, index: 5 } +0x0079 | 64 02 | section Module +0x007b | 01 | 1 count +0x007c | 04 | [module 1] type 4 +0x007d | 65 0f | section Instance +0x007f | 01 | 1 count +0x0080 | 01 | [instance 2] module:1 +0x0081 | 06 | 6 count +0x0082 | 00 00 | [instantiate arg] Function 0 +0x0084 | 02 00 | [instantiate arg] Memory 0 +0x0086 | 03 00 | [instantiate arg] Global 0 +0x0088 | 01 00 | [instantiate arg] Table 0 +0x008a | 05 00 | [instantiate arg] Module 0 +0x008c | 06 01 | [instantiate arg] Instance 1 +0x008e | 67 45 | section ModuleCode +0x0090 | 01 | 1 count +0x0091 | 43 | inline module + 0x0092 | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x009a | 01 09 | section Type + 0x009c | 03 | 3 count + 0x009d | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) + 0x00a0 | 61 00 00 | [type 1] Module(ModuleType { imports: [], exports: [] }) + 0x00a3 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) + 0x00a5 | 02 23 | section Import + 0x00a7 | 06 | 6 count + 0x00a8 | 00 01 c0 00 | import [func 0] Import { module: "", field: None, ty: Function(0) } + | 00 + 0x00ad | 00 01 c0 02 | import [memory 0] Import { module: "", field: None, ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) } + | 00 01 + 0x00b3 | 00 01 c0 03 | import [global 0] Import { module: "", field: None, ty: Global(GlobalType { content_type: I32, mutable: false }) } + | 7f 00 + 0x00b9 | 00 01 c0 01 | import [table 0] Import { module: "", field: None, ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) } + | 70 00 01 + 0x00c0 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(1) } + | 01 + 0x00c5 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(2) } + | 02 + 0x00ca | 00 09 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 + 0x00d1 | 00 02 | module name + 0x00d3 | 01 6d | "m" +0x00d5 | 00 11 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x00dc | 01 0a | function names +0x00de | 01 | 1 count +0x00df | 00 07 69 2e | Naming { index: 0, name: "i.$func" } + | 24 66 75 6e + | 63 diff --git a/tests/dump/bundled.wat b/tests/dump/bundled.wat new file mode 100644 index 0000000000..b70cda8e5b --- /dev/null +++ b/tests/dump/bundled.wat @@ -0,0 +1,32 @@ +(module + (type $WasiFile (instance + (export "read" (func $read (param i32 i32 i32) (result i32))) + (export "write" (func $write (param i32 i32 i32) (result i32))) + )) + (import "wasi_file" (instance $real-wasi (type $WasiFile))) + + (module $CHILD + (import "wasi_file" (instance $wasi-file (type $WasiFile))) + (func $play (export "play") + ;; TODO: implement this + ;;call $wasi-file.$read + ) + ) + + + (module $VIRTUALIZE + (import "wasi_file" (instance $wasi-file (type $WasiFile))) + (func (export "read") (param i32 i32 i32) (result i32) + i32.const 0 + ) + (func (export "write") (param i32 i32 i32) (result i32) + i32.const 0 + ) + ) + + (instance $virt-wasi (instantiate $VIRTUALIZE (instance $real-wasi))) + (instance $child (instantiate $CHILD (instance $virt-wasi))) + (func (export "work") + call $child.$play + ) +) diff --git a/tests/dump/bundled.wat.dump b/tests/dump/bundled.wat.dump new file mode 100644 index 0000000000..c6807009b6 --- /dev/null +++ b/tests/dump/bundled.wat.dump @@ -0,0 +1,152 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 54 | section Type +0x000a | 05 | 5 count +0x000b | 60 03 7f 7f | [type 0] Func(FuncType { params: [I32, I32, I32], returns: [I32] }) + | 7f 01 7f +0x0012 | 62 02 04 72 | [type 1] Instance(InstanceType { exports: [ExportType { name: "read", ty: Function(0) }, ExportType { name: "write", ty: Function(0) }] }) + | 65 61 64 00 + | 00 05 77 72 + | 69 74 65 00 + | 00 +0x0023 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) +0x0026 | 61 01 09 77 | [type 3] Module(ModuleType { imports: [Import { module: "wasi_file", field: None, ty: Instance(1) }], exports: [ExportType { name: "play", ty: Function(2) }] }) + | 61 73 69 5f + | 66 69 6c 65 + | 01 c0 06 01 + | 01 04 70 6c + | 61 79 00 02 +0x003e | 61 01 09 77 | [type 4] Module(ModuleType { imports: [Import { module: "wasi_file", field: None, ty: Instance(1) }], exports: [ExportType { name: "read", ty: Function(0) }, ExportType { name: "write", ty: Function(0) }] }) + | 61 73 69 5f + | 66 69 6c 65 + | 01 c0 06 01 + | 02 04 72 65 + | 61 64 00 00 + | 05 77 72 69 + | 74 65 00 00 +0x005e | 02 0f | section Import +0x0060 | 01 | 1 count +0x0061 | 09 77 61 73 | import [instance 0] Import { module: "wasi_file", field: None, ty: Instance(1) } + | 69 5f 66 69 + | 6c 65 01 c0 + | 06 01 +0x006f | 64 03 | section Module +0x0071 | 02 | 2 count +0x0072 | 03 | [module 0] type 3 +0x0073 | 04 | [module 1] type 4 +0x0074 | 65 09 | section Instance +0x0076 | 02 | 2 count +0x0077 | 01 | [instance 1] module:1 +0x0078 | 01 | 1 count +0x0079 | 06 00 | [instantiate arg] Instance 0 +0x007b | 00 | [instance 2] module:0 +0x007c | 01 | 1 count +0x007d | 06 01 | [instantiate arg] Instance 1 +0x007f | 66 05 | section Alias +0x0081 | 01 | 1 count +0x0082 | 00 02 00 00 | [alias] Alias { instance: Child(2), kind: Function, index: 0 } +0x0086 | 03 02 | section Function +0x0088 | 01 | 1 count +0x0089 | 02 | [func 1] type 2 +0x008a | 07 08 | section Export +0x008c | 01 | 1 count +0x008d | 04 77 6f 72 | export Export { field: "work", kind: Function, index: 1 } + | 6b 00 01 +0x0094 | 67 b5 01 | section ModuleCode +0x0097 | 02 | 2 count +0x0098 | 51 | inline module + 0x0099 | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x00a1 | 66 04 | section Alias + 0x00a3 | 01 | 1 count + 0x00a4 | 01 07 01 | [alias] Alias { instance: Parent, kind: Type, index: 1 } + 0x00a7 | 01 04 | section Type + 0x00a9 | 01 | 1 count + 0x00aa | 60 00 00 | [type 1] Func(FuncType { params: [], returns: [] }) + 0x00ad | 02 0f | section Import + 0x00af | 01 | 1 count + 0x00b0 | 09 77 61 73 | import [instance 0] Import { module: "wasi_file", field: None, ty: Instance(0) } + | 69 5f 66 69 + | 6c 65 01 c0 + | 06 00 + 0x00be | 03 02 | section Function + 0x00c0 | 01 | 1 count + 0x00c1 | 01 | [func 0] type 1 + 0x00c2 | 07 08 | section Export + 0x00c4 | 01 | 1 count + 0x00c5 | 04 70 6c 61 | export Export { field: "play", kind: Function, index: 0 } + | 79 00 00 + 0x00cc | 0a 04 | section Code + 0x00ce | 01 | 1 count +============== func 0 ==================== + 0x00cf | 02 | size of function + 0x00d0 | 00 | 0 local blocks + 0x00d1 | 0b | End + 0x00d2 | 00 16 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 + 0x00d9 | 00 06 | module name + 0x00db | 05 43 48 49 | "CHILD" + | 4c 44 + 0x00e1 | 01 07 | function names + 0x00e3 | 01 | 1 count + 0x00e4 | 00 04 70 6c | Naming { index: 0, name: "play" } + | 61 79 +0x00ea | 61 | inline module + 0x00eb | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x00f3 | 66 04 | section Alias + 0x00f5 | 01 | 1 count + 0x00f6 | 01 07 01 | [alias] Alias { instance: Parent, kind: Type, index: 1 } + 0x00f9 | 01 08 | section Type + 0x00fb | 01 | 1 count + 0x00fc | 60 03 7f 7f | [type 1] Func(FuncType { params: [I32, I32, I32], returns: [I32] }) + | 7f 01 7f + 0x0103 | 02 0f | section Import + 0x0105 | 01 | 1 count + 0x0106 | 09 77 61 73 | import [instance 0] Import { module: "wasi_file", field: None, ty: Instance(0) } + | 69 5f 66 69 + | 6c 65 01 c0 + | 06 00 + 0x0114 | 03 03 | section Function + 0x0116 | 02 | 2 count + 0x0117 | 01 | [func 0] type 1 + 0x0118 | 01 | [func 1] type 1 + 0x0119 | 07 10 | section Export + 0x011b | 02 | 2 count + 0x011c | 04 72 65 61 | export Export { field: "read", kind: Function, index: 0 } + | 64 00 00 + 0x0123 | 05 77 72 69 | export Export { field: "write", kind: Function, index: 1 } + | 74 65 00 01 + 0x012b | 0a 0b | section Code + 0x012d | 02 | 2 count +============== func 0 ==================== + 0x012e | 04 | size of function + 0x012f | 00 | 0 local blocks + 0x0130 | 41 00 | I32Const { value: 0 } + 0x0132 | 0b | End +============== func 1 ==================== + 0x0133 | 04 | size of function + 0x0134 | 00 | 0 local blocks + 0x0135 | 41 00 | I32Const { value: 0 } + 0x0137 | 0b | End + 0x0138 | 00 12 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 + 0x013f | 00 0b | module name + 0x0141 | 0a 56 49 52 | "VIRTUALIZE" + | 54 55 41 4c + | 49 5a 45 +0x014c | 0a 06 | section Code +0x014e | 01 | 1 count +============== func 1 ==================== +0x014f | 04 | size of function +0x0150 | 00 | 0 local blocks +0x0151 | 10 00 | Call { function_index: 0 } +0x0153 | 0b | End +0x0154 | 00 15 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x015b | 01 0e | function names +0x015d | 01 | 1 count +0x015e | 00 0b 63 68 | Naming { index: 0, name: "child.$play" } + | 69 6c 64 2e + | 24 70 6c 61 + | 79 diff --git a/tests/dump/export-all.wat b/tests/dump/export-all.wat new file mode 100644 index 0000000000..ca49eaeddf --- /dev/null +++ b/tests/dump/export-all.wat @@ -0,0 +1,6 @@ +(module + (import "i" (instance $i + (export "f1" (func)) + )) + (export $i) +) diff --git a/tests/dump/export-all.wat.dump b/tests/dump/export-all.wat.dump new file mode 100644 index 0000000000..fbaa4198c5 --- /dev/null +++ b/tests/dump/export-all.wat.dump @@ -0,0 +1,18 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 0b | section Type +0x000a | 02 | 2 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 62 01 02 66 | [type 1] Instance(InstanceType { exports: [ExportType { name: "f1", ty: Function(0) }] }) + | 31 00 00 +0x0015 | 02 07 | section Import +0x0017 | 01 | 1 count +0x0018 | 01 69 01 c0 | import [instance 0] Import { module: "i", field: None, ty: Instance(1) } + | 06 01 +0x001e | 66 05 | section Alias +0x0020 | 01 | 1 count +0x0021 | 00 00 00 00 | [alias] Alias { instance: Child(0), kind: Function, index: 0 } +0x0025 | 07 06 | section Export +0x0027 | 01 | 1 count +0x0028 | 02 66 31 00 | export Export { field: "f1", kind: Function, index: 0 } + | 00 diff --git a/tests/dump/import-modules.wat b/tests/dump/import-modules.wat new file mode 100644 index 0000000000..23f7221128 --- /dev/null +++ b/tests/dump/import-modules.wat @@ -0,0 +1,9 @@ +(module + (import "" (module $m + (import "" (module)) + )) + (import "" (module $i + (import "" (func)) + )) + (instance $i (instantiate $m (module $i))) +) diff --git a/tests/dump/import-modules.wat.dump b/tests/dump/import-modules.wat.dump new file mode 100644 index 0000000000..d050b97e41 --- /dev/null +++ b/tests/dump/import-modules.wat.dump @@ -0,0 +1,21 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 17 | section Type +0x000a | 04 | 4 count +0x000b | 61 00 00 | [type 0] Module(ModuleType { imports: [], exports: [] }) +0x000e | 61 01 00 01 | [type 1] Module(ModuleType { imports: [Import { module: "", field: None, ty: Module(0) }], exports: [] }) + | c0 05 00 00 +0x0016 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) +0x0019 | 61 01 00 01 | [type 3] Module(ModuleType { imports: [Import { module: "", field: None, ty: Function(2) }], exports: [] }) + | c0 00 02 00 +0x0021 | 02 0b | section Import +0x0023 | 02 | 2 count +0x0024 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(1) } + | 01 +0x0029 | 00 01 c0 05 | import [module 1] Import { module: "", field: None, ty: Module(3) } + | 03 +0x002e | 65 05 | section Instance +0x0030 | 01 | 1 count +0x0031 | 00 | [instance 0] module:0 +0x0032 | 01 | 1 count +0x0033 | 05 01 | [instantiate arg] Module 1 diff --git a/tests/dump/instance-expand.wat b/tests/dump/instance-expand.wat new file mode 100644 index 0000000000..e2375e0e3c --- /dev/null +++ b/tests/dump/instance-expand.wat @@ -0,0 +1,7 @@ +(module + (type $i (instance + (export "" (func)) + )) + + (import "" (instance (type $i))) +) diff --git a/tests/dump/instance-expand.wat.dump b/tests/dump/instance-expand.wat.dump new file mode 100644 index 0000000000..ec2af38792 --- /dev/null +++ b/tests/dump/instance-expand.wat.dump @@ -0,0 +1,11 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 09 | section Type +0x000a | 02 | 2 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 62 01 00 00 | [type 1] Instance(InstanceType { exports: [ExportType { name: "", ty: Function(0) }] }) + | 00 +0x0013 | 02 06 | section Import +0x0015 | 01 | 1 count +0x0016 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(1) } + | 01 diff --git a/tests/dump/instance-type.wat b/tests/dump/instance-type.wat new file mode 100644 index 0000000000..a6775e72ed --- /dev/null +++ b/tests/dump/instance-type.wat @@ -0,0 +1,7 @@ +(module + (type (instance + (export "" (func)) + )) + + (type (instance (export "" (instance)))) +) diff --git a/tests/dump/instance-type.wat.dump b/tests/dump/instance-type.wat.dump new file mode 100644 index 0000000000..feaae1b894 --- /dev/null +++ b/tests/dump/instance-type.wat.dump @@ -0,0 +1,10 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 10 | section Type +0x000a | 04 | 4 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 62 01 00 00 | [type 1] Instance(InstanceType { exports: [ExportType { name: "", ty: Function(0) }] }) + | 00 +0x0013 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) +0x0015 | 62 01 00 06 | [type 3] Instance(InstanceType { exports: [ExportType { name: "", ty: Instance(2) }] }) + | 02 diff --git a/tests/dump/instance-type2.wat b/tests/dump/instance-type2.wat new file mode 100644 index 0000000000..1fc7f5d5ab --- /dev/null +++ b/tests/dump/instance-type2.wat @@ -0,0 +1,6 @@ +(module + (type (instance)) + (type (instance (export "" (instance (type 0))))) + (type $x (instance)) + (type (instance (export "" (instance (type $x))))) +) diff --git a/tests/dump/instance-type2.wat.dump b/tests/dump/instance-type2.wat.dump new file mode 100644 index 0000000000..fe48f01d96 --- /dev/null +++ b/tests/dump/instance-type2.wat.dump @@ -0,0 +1,10 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 0f | section Type +0x000a | 04 | 4 count +0x000b | 62 00 | [type 0] Instance(InstanceType { exports: [] }) +0x000d | 62 01 00 06 | [type 1] Instance(InstanceType { exports: [ExportType { name: "", ty: Instance(0) }] }) + | 00 +0x0012 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) +0x0014 | 62 01 00 06 | [type 3] Instance(InstanceType { exports: [ExportType { name: "", ty: Instance(2) }] }) + | 02 diff --git a/tests/dump/instantiate.wat b/tests/dump/instantiate.wat new file mode 100644 index 0000000000..648c4d7d6f --- /dev/null +++ b/tests/dump/instantiate.wat @@ -0,0 +1,21 @@ + +(module + (import "" (module $m)) + (import "" (module $m2)) + (import "" (instance $b)) + (func $f) + (global $g i32 (i32.const 0)) + (memory $mem 1) + (table $table 1 funcref) + + (instance $a + (instantiate $m + (module $m2) + (func $f) + (global $g) + (instance $b) + (memory $mem) + (table $table) + ) + ) +) diff --git a/tests/dump/instantiate.wat.dump b/tests/dump/instantiate.wat.dump new file mode 100644 index 0000000000..e4f9c2fea8 --- /dev/null +++ b/tests/dump/instantiate.wat.dump @@ -0,0 +1,50 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 09 | section Type +0x000a | 03 | 3 count +0x000b | 61 00 00 | [type 0] Module(ModuleType { imports: [], exports: [] }) +0x000e | 62 00 | [type 1] Instance(InstanceType { exports: [] }) +0x0010 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) +0x0013 | 02 10 | section Import +0x0015 | 03 | 3 count +0x0016 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(0) } + | 00 +0x001b | 00 01 c0 05 | import [module 1] Import { module: "", field: None, ty: Module(0) } + | 00 +0x0020 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(1) } + | 01 +0x0025 | 65 0f | section Instance +0x0027 | 01 | 1 count +0x0028 | 00 | [instance 1] module:0 +0x0029 | 06 | 6 count +0x002a | 05 01 | [instantiate arg] Module 1 +0x002c | 00 00 | [instantiate arg] Function 0 +0x002e | 03 00 | [instantiate arg] Global 0 +0x0030 | 06 00 | [instantiate arg] Instance 0 +0x0032 | 02 00 | [instantiate arg] Memory 0 +0x0034 | 01 00 | [instantiate arg] Table 0 +0x0036 | 03 02 | section Function +0x0038 | 01 | 1 count +0x0039 | 02 | [func 0] type 2 +0x003a | 04 04 | section Table +0x003c | 01 | 1 count +0x003d | 70 00 01 | [table 0] TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } } +0x0040 | 05 03 | section Memory +0x0042 | 01 | 1 count +0x0043 | 00 01 | [memory 0] MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false } +0x0045 | 06 06 | section Global +0x0047 | 01 | 1 count +0x0048 | 7f 00 | [global 0] GlobalType { content_type: I32, mutable: false } +0x004a | 41 00 | I32Const { value: 0 } +0x004c | 0b | End +0x004d | 0a 04 | section Code +0x004f | 01 | 1 count +============== func 0 ==================== +0x0050 | 02 | size of function +0x0051 | 00 | 0 local blocks +0x0052 | 0b | End +0x0053 | 00 0b 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x005a | 01 04 | function names +0x005c | 01 | 1 count +0x005d | 00 01 66 | Naming { index: 0, name: "f" } diff --git a/tests/dump/instantiate2.wat b/tests/dump/instantiate2.wat new file mode 100644 index 0000000000..35b298739b --- /dev/null +++ b/tests/dump/instantiate2.wat @@ -0,0 +1,4 @@ +(module + (import "" (module $m (import "" (func)))) + (instance (instantiate $m)) +) diff --git a/tests/dump/instantiate2.wat.dump b/tests/dump/instantiate2.wat.dump new file mode 100644 index 0000000000..5dea98297f --- /dev/null +++ b/tests/dump/instantiate2.wat.dump @@ -0,0 +1,15 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 0c | section Type +0x000a | 02 | 2 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 61 01 00 01 | [type 1] Module(ModuleType { imports: [Import { module: "", field: None, ty: Function(0) }], exports: [] }) + | c0 00 00 00 +0x0016 | 02 06 | section Import +0x0018 | 01 | 1 count +0x0019 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(1) } + | 01 +0x001e | 65 03 | section Instance +0x0020 | 01 | 1 count +0x0021 | 00 | [instance 0] module:0 +0x0022 | 00 | 0 count diff --git a/tests/dump/module-linking.wat b/tests/dump/module-linking.wat new file mode 100644 index 0000000000..ff3609c612 --- /dev/null +++ b/tests/dump/module-linking.wat @@ -0,0 +1,28 @@ +(module + (import "" (instance $i + (export "1" (func $func)) + (export "2" (memory $memory 1)) + (export "3" (table $table 1 funcref)) + (export "4" (global $global i32)) + (export "5" (module $module)) + (export "6" (instance $instance)) + )) + + (module $m + (import "" (func)) + (import "" (memory 1)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (module)) + (import "" (instance)) + ) + + (instance (instantiate $m + (func $i.$func) + (memory $i.$memory) + (global $i.$global) + (table $i.$table) + (module $i.$module) + (instance $i.$instance) + )) +) diff --git a/tests/dump/module-linking.wat.dump b/tests/dump/module-linking.wat.dump new file mode 100644 index 0000000000..f074c04991 --- /dev/null +++ b/tests/dump/module-linking.wat.dump @@ -0,0 +1,85 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 4c | section Type +0x000a | 05 | 5 count +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) +0x000e | 61 00 00 | [type 1] Module(ModuleType { imports: [], exports: [] }) +0x0011 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) +0x0013 | 62 06 01 31 | [type 3] Instance(InstanceType { exports: [ExportType { name: "1", ty: Function(0) }, ExportType { name: "2", ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) }, ExportType { name: "3", ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) }, ExportType { name: "4", ty: Global(GlobalType { content_type: I32, mutable: false }) }, ExportType { name: "5", ty: Module(1) }, ExportType { name: "6", ty: Instance(2) }] }) + | 00 00 01 32 + | 02 00 01 01 + | 33 01 70 00 + | 01 01 34 03 + | 7f 00 01 35 + | 05 01 01 36 + | 06 02 +0x0031 | 61 06 00 01 | [type 4] Module(ModuleType { imports: [Import { module: "", field: None, ty: Function(0) }, Import { module: "", field: None, ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) }, Import { module: "", field: None, ty: Global(GlobalType { content_type: I32, mutable: false }) }, Import { module: "", field: None, ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) }, Import { module: "", field: None, ty: Module(1) }, Import { module: "", field: None, ty: Instance(2) }], exports: [] }) + | c0 00 00 00 + | 01 c0 02 00 + | 01 00 01 c0 + | 03 7f 00 00 + | 01 c0 01 70 + | 00 01 00 01 + | c0 05 01 00 + | 01 c0 06 02 + | 00 +0x0056 | 02 06 | section Import +0x0058 | 01 | 1 count +0x0059 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(3) } + | 03 +0x005e | 66 19 | section Alias +0x0060 | 06 | 6 count +0x0061 | 00 00 00 00 | [alias] Alias { instance: Child(0), kind: Function, index: 0 } +0x0065 | 00 00 02 01 | [alias] Alias { instance: Child(0), kind: Memory, index: 1 } +0x0069 | 00 00 01 02 | [alias] Alias { instance: Child(0), kind: Table, index: 2 } +0x006d | 00 00 03 03 | [alias] Alias { instance: Child(0), kind: Global, index: 3 } +0x0071 | 00 00 05 04 | [alias] Alias { instance: Child(0), kind: Module, index: 4 } +0x0075 | 00 00 06 05 | [alias] Alias { instance: Child(0), kind: Instance, index: 5 } +0x0079 | 64 02 | section Module +0x007b | 01 | 1 count +0x007c | 04 | [module 1] type 4 +0x007d | 65 0f | section Instance +0x007f | 01 | 1 count +0x0080 | 01 | [instance 2] module:1 +0x0081 | 06 | 6 count +0x0082 | 00 00 | [instantiate arg] Function 0 +0x0084 | 02 00 | [instantiate arg] Memory 0 +0x0086 | 03 00 | [instantiate arg] Global 0 +0x0088 | 01 00 | [instantiate arg] Table 0 +0x008a | 05 00 | [instantiate arg] Module 0 +0x008c | 06 01 | [instantiate arg] Instance 1 +0x008e | 67 45 | section ModuleCode +0x0090 | 01 | 1 count +0x0091 | 43 | inline module + 0x0092 | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x009a | 01 09 | section Type + 0x009c | 03 | 3 count + 0x009d | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) + 0x00a0 | 61 00 00 | [type 1] Module(ModuleType { imports: [], exports: [] }) + 0x00a3 | 62 00 | [type 2] Instance(InstanceType { exports: [] }) + 0x00a5 | 02 23 | section Import + 0x00a7 | 06 | 6 count + 0x00a8 | 00 01 c0 00 | import [func 0] Import { module: "", field: None, ty: Function(0) } + | 00 + 0x00ad | 00 01 c0 02 | import [memory 0] Import { module: "", field: None, ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) } + | 00 01 + 0x00b3 | 00 01 c0 03 | import [global 0] Import { module: "", field: None, ty: Global(GlobalType { content_type: I32, mutable: false }) } + | 7f 00 + 0x00b9 | 00 01 c0 01 | import [table 0] Import { module: "", field: None, ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) } + | 70 00 01 + 0x00c0 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(1) } + | 01 + 0x00c5 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(2) } + | 02 + 0x00ca | 00 09 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 + 0x00d1 | 00 02 | module name + 0x00d3 | 01 6d | "m" +0x00d5 | 00 11 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x00dc | 01 0a | function names +0x00de | 01 | 1 count +0x00df | 00 07 69 2e | Naming { index: 0, name: "i.$func" } + | 24 66 75 6e + | 63 diff --git a/tests/dump/module-types.wat b/tests/dump/module-types.wat new file mode 100644 index 0000000000..887f6aaa1b --- /dev/null +++ b/tests/dump/module-types.wat @@ -0,0 +1,11 @@ +(module + (import "" (module $m + (import "" (module)) + (import "" (func)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (instance)) + (import "" (memory 1)) + )) + (type $empty (instance)) +) diff --git a/tests/dump/module-types.wat.dump b/tests/dump/module-types.wat.dump new file mode 100644 index 0000000000..95d621e035 --- /dev/null +++ b/tests/dump/module-types.wat.dump @@ -0,0 +1,21 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 2e | section Type +0x000a | 04 | 4 count +0x000b | 62 00 | [type 0] Instance(InstanceType { exports: [] }) +0x000d | 61 00 00 | [type 1] Module(ModuleType { imports: [], exports: [] }) +0x0010 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) +0x0013 | 61 06 00 01 | [type 3] Module(ModuleType { imports: [Import { module: "", field: None, ty: Module(1) }, Import { module: "", field: None, ty: Function(2) }, Import { module: "", field: None, ty: Global(GlobalType { content_type: I32, mutable: false }) }, Import { module: "", field: None, ty: Table(TableType { element_type: FuncRef, limits: ResizableLimits { initial: 1, maximum: None } }) }, Import { module: "", field: None, ty: Instance(0) }, Import { module: "", field: None, ty: Memory(MemoryType { limits: ResizableLimits { initial: 1, maximum: None }, shared: false }) }], exports: [] }) + | c0 05 01 00 + | 01 c0 00 02 + | 00 01 c0 03 + | 7f 00 00 01 + | c0 01 70 00 + | 01 00 01 c0 + | 06 00 00 01 + | c0 02 00 01 + | 00 +0x0038 | 02 06 | section Import +0x003a | 01 | 1 count +0x003b | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(3) } + | 03 diff --git a/tests/dump/names.wat b/tests/dump/names.wat new file mode 100644 index 0000000000..64fc40dd5f --- /dev/null +++ b/tests/dump/names.wat @@ -0,0 +1,3 @@ +(module $foo + (func $f (param $x i32) + (local $y f64))) diff --git a/tests/dump/names.wat.dump b/tests/dump/names.wat.dump new file mode 100644 index 0000000000..9078a20487 --- /dev/null +++ b/tests/dump/names.wat.dump @@ -0,0 +1,28 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 05 | section Type +0x000a | 01 | 1 count +0x000b | 60 01 7f 00 | [type 0] Func(FuncType { params: [I32], returns: [] }) +0x000f | 03 02 | section Function +0x0011 | 01 | 1 count +0x0012 | 00 | [func 0] type 0 +0x0013 | 0a 06 | section Code +0x0015 | 01 | 1 count +============== func 0 ==================== +0x0016 | 04 | size of function +0x0017 | 01 | 1 local blocks +0x0018 | 01 7c | 1 locals of type F64 +0x001a | 0b | End +0x001b | 00 1c 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 +0x0022 | 00 04 | module name +0x0024 | 03 66 6f 6f | "foo" +0x0028 | 01 04 | function names +0x002a | 01 | 1 count +0x002b | 00 01 66 | Naming { index: 0, name: "f" } +0x002e | 02 09 | local names +0x0030 | 01 | 1 count +0x0031 | 00 | function 0 locals +0x0032 | 02 | 2 count +0x0033 | 00 01 78 | Naming { index: 0, name: "x" } +0x0036 | 01 01 79 | Naming { index: 1, name: "y" } diff --git a/tests/dump/nested-module.wat b/tests/dump/nested-module.wat new file mode 100644 index 0000000000..88c45d5cdf --- /dev/null +++ b/tests/dump/nested-module.wat @@ -0,0 +1,26 @@ + +(module + (module (import "")) + + (module) + (module) + + (type $f (module)) + (module (type $f)) + + (module (export "x")) + + (module + (module) + ) + + (module + (module $m) + (import "" (func (param i32))) + (export "a" (module $m)) + + (instance (export "b") (import "") + (export "b" (func)) + ) + ) +) diff --git a/tests/dump/nested-module.wat.dump b/tests/dump/nested-module.wat.dump new file mode 100644 index 0000000000..2311ab6e54 --- /dev/null +++ b/tests/dump/nested-module.wat.dump @@ -0,0 +1,90 @@ +0x0000 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0008 | 01 26 | section Type +0x000a | 05 | 5 count +0x000b | 61 00 00 | [type 0] Module(ModuleType { imports: [], exports: [] }) +0x000e | 60 01 7f 00 | [type 1] Func(FuncType { params: [I32], returns: [] }) +0x0012 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) +0x0015 | 62 01 01 62 | [type 3] Instance(InstanceType { exports: [ExportType { name: "b", ty: Function(2) }] }) + | 00 02 +0x001b | 61 02 00 01 | [type 4] Module(ModuleType { imports: [Import { module: "", field: None, ty: Function(1) }, Import { module: "", field: None, ty: Instance(3) }], exports: [ExportType { name: "a", ty: Module(0) }, ExportType { name: "b", ty: Instance(3) }] }) + | c0 00 01 00 + | 01 c0 06 03 + | 02 01 61 05 + | 00 01 62 06 + | 03 +0x0030 | 02 06 | section Import +0x0032 | 01 | 1 count +0x0033 | 00 01 c0 05 | import [module 0] Import { module: "", field: None, ty: Module(0) } + | 00 +0x0038 | 64 07 | section Module +0x003a | 06 | 6 count +0x003b | 00 | [module 1] type 0 +0x003c | 00 | [module 2] type 0 +0x003d | 00 | [module 3] type 0 +0x003e | 00 | [module 4] type 0 +0x003f | 00 | [module 5] type 0 +0x0040 | 04 | [module 6] type 4 +0x0041 | 07 05 | section Export +0x0043 | 01 | 1 count +0x0044 | 01 78 05 04 | export Export { field: "x", kind: Module, index: 4 } +0x0048 | 67 93 01 | section ModuleCode +0x004b | 06 | 6 count +0x004c | 08 | inline module + 0x004d | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0055 | 08 | inline module + 0x0056 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x005e | 08 | inline module + 0x005f | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0067 | 08 | inline module + 0x0068 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x0070 | 1e | inline module + 0x0071 | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x0079 | 01 04 | section Type + 0x007b | 01 | 1 count + 0x007c | 61 00 00 | [type 0] Module(ModuleType { imports: [], exports: [] }) + 0x007f | 64 02 | section Module + 0x0081 | 01 | 1 count + 0x0082 | 00 | [module 0] type 0 + 0x0083 | 67 0a | section ModuleCode + 0x0085 | 01 | 1 count + 0x0086 | 08 | inline module + 0x0087 | 00 61 73 6d | version 1 + | 01 00 00 00 +0x008f | 4e | inline module + 0x0090 | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x0098 | 01 11 | section Type + 0x009a | 04 | 4 count + 0x009b | 61 00 00 | [type 0] Module(ModuleType { imports: [], exports: [] }) + 0x009e | 60 01 7f 00 | [type 1] Func(FuncType { params: [I32], returns: [] }) + 0x00a2 | 60 00 00 | [type 2] Func(FuncType { params: [], returns: [] }) + 0x00a5 | 62 01 01 62 | [type 3] Instance(InstanceType { exports: [ExportType { name: "b", ty: Function(2) }] }) + | 00 02 + 0x00ab | 64 02 | section Module + 0x00ad | 01 | 1 count + 0x00ae | 00 | [module 0] type 0 + 0x00af | 02 0b | section Import + 0x00b1 | 02 | 2 count + 0x00b2 | 00 01 c0 00 | import [func 0] Import { module: "", field: None, ty: Function(1) } + | 01 + 0x00b7 | 00 01 c0 06 | import [instance 0] Import { module: "", field: None, ty: Instance(3) } + | 03 + 0x00bc | 07 09 | section Export + 0x00be | 02 | 2 count + 0x00bf | 01 61 05 00 | export Export { field: "a", kind: Module, index: 0 } + 0x00c3 | 01 62 06 00 | export Export { field: "b", kind: Instance, index: 0 } + 0x00c7 | 67 15 | section ModuleCode + 0x00c9 | 01 | 1 count + 0x00ca | 13 | inline module + 0x00cb | 00 61 73 6d | version 1 + | 01 00 00 00 + 0x00d3 | 00 09 04 6e | section Custom { name: "name", kind: Name } + | 61 6d 65 + 0x00da | 00 02 | module name + 0x00dc | 01 6d | "m" diff --git a/tests/dump/select.wat.dump b/tests/dump/select.wat.dump index b8ddf91101..dad3d1174c 100644 --- a/tests/dump/select.wat.dump +++ b/tests/dump/select.wat.dump @@ -2,7 +2,7 @@ | 01 00 00 00 0x0008 | 01 04 | section Type 0x000a | 01 | 1 count -0x000b | 60 00 00 | type FuncType { params: [], returns: [] } +0x000b | 60 00 00 | [type 0] Func(FuncType { params: [], returns: [] }) 0x000e | 03 02 | section Function 0x0010 | 01 | 1 count 0x0011 | 00 | [func 0] type 0 diff --git a/tests/dump/simple.wat.dump b/tests/dump/simple.wat.dump index 9d1080a1af..ea15bb3de8 100644 --- a/tests/dump/simple.wat.dump +++ b/tests/dump/simple.wat.dump @@ -2,11 +2,11 @@ | 01 00 00 00 0x0008 | 01 08 | section Type 0x000a | 02 | 2 count -0x000b | 60 01 7f 00 | type FuncType { params: [I32], returns: [] } -0x000f | 60 00 00 | type FuncType { params: [], returns: [] } +0x000b | 60 01 7f 00 | [type 0] Func(FuncType { params: [I32], returns: [] }) +0x000f | 60 00 00 | [type 1] Func(FuncType { params: [], returns: [] }) 0x0012 | 02 07 | section Import 0x0014 | 01 | 1 count -0x0015 | 01 6d 01 6e | import [func 0] Import { module: "m", field: "n", ty: Function(0) } +0x0015 | 01 6d 01 6e | import [func 0] Import { module: "m", field: Some("n"), ty: Function(0) } | 00 00 0x001b | 03 04 | section Function 0x001d | 03 | 3 count diff --git a/tests/local/gc-ref.wat b/tests/local/gc-ref.wat index d459404e39..234f79d260 100644 --- a/tests/local/gc-ref.wat +++ b/tests/local/gc-ref.wat @@ -11,6 +11,9 @@ (global (ref $b)) (func (param (ref $a))) + (func (param (ref 0))) + (func (type $f1) (param (ref 0))) + (func (type 2) (param (ref 0))) (func (result (ref $a))) (func (local (ref $a))) diff --git a/tests/local/module-linking/alias.wast b/tests/local/module-linking/alias.wast new file mode 100644 index 0000000000..49dfe631fe --- /dev/null +++ b/tests/local/module-linking/alias.wast @@ -0,0 +1,476 @@ +(module + (import "i" (instance $i + (export "f1" (func $f1)) + (export "f2" (func $f2 (param i32))) + )) + (func (export "run") + call $i.$f1 + ) +) +(assert_malformed + (module quote + " (type $t (instance" + " (export \"f1\" (func $f1))" + " ))" + " (import \"i\" (instance $i (type $t)))" + " (func (export \"run\")" + " call $i.$f1" + " )" + ) + "failed to find func named `$i.$f1`") + +(module + (import "i" (module $m + (export "f1" (func $f1)) + (export "f2" (func $f2 (param i32))) + )) + (instance $i (instantiate $m)) + (func (export "run") + call $i.$f1 + ) +) +(assert_malformed + (module quote + "(type $t (module " + " (export \"f1\" (func $f1))" + "))" + "(import \"i\" (module $m (type $t)))" + "(instance $i (instantiate $m))" + "(func (export \"run\")" + " call $i.$f1" + ")" + ) + "failed to find func named `$i.$f1`") + +(module + (module $m + (func $f1 (export "f1")) + (func $f2 (export "f2") (param i32)) + ) + (instance $i (instantiate $m)) + (func (export "run") + call $i.$f1 + ) +) + + +(module + (import "libc" (instance $libc + (export "memory" (memory $mem 1)) + (export "table" (table $tbl 0 funcref)) + )) + (alias (instance $libc) (memory $mem)) + (alias (instance $libc) (table $tbl)) +) + +(module + (import "libc" (instance $libc + (export "memory" (memory $mem 1)) + (export "table" (table $tbl 0 funcref)) + (export "global" (global $glbl i32)) + )) + + (func $table_get + i32.const 0 + table.get $libc.$tbl + drop) + + (func $global_get + global.get $libc.$glbl + drop) +) + +;; make sure auto-expanded aliases can't shadow already-defined names +(module + (import "" (instance $i + (export "1" (func $func (param i32))) + (export "2" (func $memory)) + (export "3" (func $table)) + (export "4" (func $global)) + (export "5" (func $module)) + (export "6" (func $instance)) + )) + (import "" (instance $i2 + (export "1" (func $func (param i32))) + (export "2" (func $memory)) + (export "3" (func $table)) + (export "4" (func $global)) + (export "5" (func $module)) + (export "6" (func $instance)) + )) + + (import "" (instance $other + (export "1" (func $func)) + (export "2" (memory $memory 1)) + (export "3" (global $global i32)) + (export "4" (table $table 1 funcref)) + (export "5" (module $module)) + (export "6" (instance $instance)) + )) + + (func $i.$func (import "")) + (memory $i.$memory (import "") 1) + (global $i.$global (import "") i32) + (table $i.$table (import "") 1 funcref) + (module $i.$module (import "")) + (instance $i.$instance (import "")) + + (alias $i2.$func (instance $other) (func $func)) + (alias $i2.$global (instance $other) (global $global)) + (alias $i2.$table (instance $other) (table $table)) + (alias $i2.$memory (instance $other) (memory $memory)) + (alias $i2.$instance (instance $other) (instance $instance)) + (alias $i2.$module (instance $other) (module $module)) + + (module $m + (import "" (func)) + (import "" (memory 1)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (module)) + (import "" (instance)) + ) + + (instance (instantiate $m + (func $i.$func) + (memory $i.$memory) + (global $i.$global) + (table $i.$table) + (module $i.$module) + (instance $i.$instance) + )) + (instance (instantiate $m + (func $i2.$func) + (memory $i2.$memory) + (global $i2.$global) + (table $i2.$table) + (module $i2.$module) + (instance $i2.$instance) + )) +) + +;; auto-expansion should visit everywhere +(module + (import "" (instance $i + (export "" (global $global i32)) + )) + + (func + global.get $i.$global + drop) +) +(module + (import "" (instance $i + (export "" (global $global (mut i32))) + )) + + (func + i32.const 0 + global.set $i.$global) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + table.get $i.$table + drop) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + ref.null func + table.set $i.$table) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + call_indirect $i.$table) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + table.size $i.$table + drop) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + ref.null func + i32.const 0 + table.grow $i.$table + drop) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + ref.null func + i32.const 0 + table.fill $i.$table) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + i32.const 0 + i32.const 0 + table.init $i.$table 0) + (elem func 0) +) +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + + (func + i32.const 0 + i32.const 0 + i32.const 0 + table.copy $i.$table $i.$table) +) + +(module + (import "" (instance $i + (export "" (func $func)) + )) + (func + return_call $i.$func) +) + +(module + (import "" (instance $i + (export "" (table $table 1 funcref)) + )) + (func + i32.const 0 + return_call_indirect $i.$table) +) + +(module + (import "" (instance $i + (export "" (func $func)) + )) + (global funcref (ref.func $i.$func)) +) +(module + (import "" (instance $i + (export "" (func $func)) + )) + (start $i.$func) +) +(module + (import "" (instance $i + (export "a" (table $table 1 funcref)) + (export "b" (func $func)) + )) + (elem (table $i.$table) (i32.const 0) funcref (ref.func $i.$func)) +) +(module + (import "" (instance $i + (export "a" (table $table 1 funcref)) + (export "b" (func $func)) + )) + (elem (table $i.$table) (i32.const 0) func $i.$func) +) +(module + (import "" (instance $i + (export "" (memory $memory 1)) + )) + (data (memory $i.$memory) (i32.const 0) "") +) + +(module + (import "" (instance $i + (export "1" (func $func)) + (export "2" (memory $memory 1)) + (export "3" (table $table 1 funcref)) + (export "4" (global $global i32)) + (export "5" (module $module)) + (export "6" (instance $instance)) + )) + (export "1" (func $i.$func)) + (export "2" (memory $i.$memory)) + (export "3" (table $i.$table)) + (export "4" (global $i.$global)) + (export "5" (module $i.$module)) + (export "6" (instance $i.$instance)) +) + +(module + (import "" (instance $i + (export "1" (func $func)) + (export "2" (memory $memory 1)) + (export "3" (table $table 1 funcref)) + (export "4" (global $global i32)) + (export "5" (module $module)) + (export "6" (instance $instance)) + )) + + (module $m + (import "" (func)) + (import "" (memory 1)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (module)) + (import "" (instance)) + ) + + (instance (instantiate $m + (func $i.$func) + (memory $i.$memory) + (global $i.$global) + (table $i.$table) + (module $i.$module) + (instance $i.$instance) + )) +) + +(module + (module $m + (func $f (export "")) + ) + + (instance $i (instantiate $m)) + + (func + call $i.$f) +) + +(module + (import "" (instance $i (export "a" (func)))) + + (import "" (module $m + (import "" (module (export "a" (func)))) + )) + + (module $local + (export $i)) + + (instance (instantiate $m (module $local))) +) + +(assert_malformed + (module quote + "(func)" + "(module" + " (export \"\" (func $f))" + ")" + "(func $f)" + ) + "reference to func before item is defined") + +(assert_malformed + (module quote + "(func)" + "(module" + " (import \"\" (instance))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "aliased from an export that does not exist") + +(assert_malformed + (module quote + "(func)" + "(module" + " (import \"\" (instance (export \"\" (global i32))))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "alias points to export of wrong kind of item") + +(assert_malformed + (module quote + "(func)" + "(module" + " (import \"\" (module))" + " (instance (instantiate 0))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "aliased from an export that does not exist") + +(assert_malformed + (module quote + "(func)" + "(module" + " (import \"\" (module (export \"\" (global i32))))" + " (instance (instantiate 0))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "alias points to export of wrong kind of item") + +(assert_malformed + (module quote + "(func)" + "(module" + " (module)" + " (instance (instantiate 0))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "aliased from an export that does not exist") + +(assert_malformed + (module quote + "(func)" + "(module" + " (module (global (export \"\") i32 (i32.const 0)))" + " (instance (instantiate 0))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "alias points to export of wrong kind of item") + +(assert_malformed + (module quote + "(func)" + "(module" + " (type (func))" + " (import \"\" (instance (type 0)))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "aliased from an instance/module that is listed with the wrong type") + +(assert_malformed + (module quote + "(func)" + "(module" + " (instance (instantiate 100))" + " (alias (instance 0) (func 0))" + " (export \"\" (func 0))" + ")" + ) + "reference to module is out of bounds") diff --git a/tests/local/module-linking/export-all.wast b/tests/local/module-linking/export-all.wast new file mode 100644 index 0000000000..40dfb1d351 --- /dev/null +++ b/tests/local/module-linking/export-all.wast @@ -0,0 +1,67 @@ +(module + (import "" (instance $f1_instance (export "f1" (func)))) + (import "" (module $f1_module (export "f1" (func)))) + + (module $require_f1 + (import "" (instance + (export "f1" (func)) + )) + ) + + (instance (instantiate $require_f1 (instance $f1_instance))) + + (module $a + (import "i" (instance $i + (export "f1" (func)) + )) + (export $i) + ) + (instance $a (instantiate $a (instance $f1_instance))) + (instance (instantiate $require_f1 (instance $a))) + + (module $b + (import "i" (module $m + (export "f1" (func)) + )) + (instance $i (instantiate $m)) + (export $i) + ) + (instance $b (instantiate $b (module $f1_module))) + (instance (instantiate $require_f1 (instance $b))) + + (module $c + (module $m + (func (export "f1")) + ) + (instance $i (instantiate $m)) + (export $i) + ) + (instance $c (instantiate $c)) + (instance (instantiate $require_f1 (instance $c))) +) + +(module + (type $i (instance (export "a" (func)))) + (type $m (module (export $i))) + + (import "" (module $imported (type $m))) + (import "" (module $to_instantiate + (import "" (module (export "a" (func)))) + )) + + (instance (instantiate $to_instantiate (module $imported))) +) + +(module + (type $i (instance (export "a" (func)))) + (module + (type $m (module (export $i))) + + (import "" (module $imported (type $m))) + (import "" (module $to_instantiate + (import "" (module (export "a" (func)))) + )) + + (instance (instantiate $to_instantiate (module $imported))) + ) +) diff --git a/tests/local/module-linking/import.wast b/tests/local/module-linking/import.wast new file mode 100644 index 0000000000..282028d72d --- /dev/null +++ b/tests/local/module-linking/import.wast @@ -0,0 +1,31 @@ +(module + (import "" (func)) + (import "" (instance)) + + (import "" (instance + (export "" (func)) + )) + (import "" (module + (import "" (module)) + (export "" (func)) + )) +) + +(assert_invalid + (module + (type (func)) + (import "" (instance (type 0))) + ) + "type index is not an instance") +(assert_invalid + (module + (type (func)) + (import "" (module (type 0))) + ) + "type index is not a module") +(assert_invalid + (module + (type (module)) + (import "" (func (type 0))) + ) + "type index is not a function") diff --git a/tests/local/module-linking/infer-types.wast b/tests/local/module-linking/infer-types.wast new file mode 100644 index 0000000000..78a27ca3ae --- /dev/null +++ b/tests/local/module-linking/infer-types.wast @@ -0,0 +1,60 @@ +(module + (module + (import "" (func $f)) + (export "1" (func $f)) + + (import "" (global $g i32)) + (export "2" (global $g)) + + (import "" (table $t 1 funcref)) + (export "3" (table $t)) + + (import "" (memory $m 1)) + (export "4" (memory $m)) + + (import "" (instance $i)) + (export "5" (instance $i)) + + (import "" (module $m)) + (export "6" (module $m)) + ) +) + +(module + (module $m + (import "" (module $m2)) + + (func $f) + (export "1" (func $f)) + + (global $g i32 (i32.const 0)) + (export "2" (global $g)) + + (table $t 1 funcref) + (export "3" (table $t)) + + (memory $m 1) + (export "4" (memory $m)) + + (module $m) + (export "5" (module $m)) + + (instance $i (instantiate $m2)) + (export "6" (instance $i)) + + (instance $i2 (instantiate $m)) + (export "7" (instance $i2)) + ) + + (module + (module $empty) + (instance $i (instantiate $m (module $m))) + (export "1" (func $i.$f)) + (export "2" (global $i.$g)) + (export "3" (table $i.$t)) + (export "4" (memory $i.$m)) + (export "5" (module $i.$m)) + (export "6" (instance $i.$i)) + (export "7" (instance $i.$i2)) + ) +) diff --git a/tests/local/module-linking/instance-type.wast b/tests/local/module-linking/instance-type.wast new file mode 100644 index 0000000000..c7e3c025ff --- /dev/null +++ b/tests/local/module-linking/instance-type.wast @@ -0,0 +1,200 @@ +;; instances +(module + (type (instance)) + + (type $foo (func)) + + (type (func (result i32))) + + (type (instance + ;; functions + (export "a" (func)) + (export "b" (func $foo)) + (export "c" (func (@name "bar"))) + (export "d" (func $foo (@name "bar"))) + (export "e" (func (type 2))) + (export "f" (func (param i32))) + (export "g" (func (param i32) (result i32 i64))) + (export "h" (func (type 2) (result i32))) + + ;; globals + (export "i" (global i32)) + (export "j" (global $foo i32)) + (export "k" (global (mut i32))) + + ;; tables + (export "l" (table 1 funcref)) + (export "m" (table $foo 1 funcref)) + + ;; memory + (export "n" (memory 1)) + (export "o" (memory $foo 1)) + (export "p" (memory 1 2)) + (export "q" (memory 1 2 shared)) + )) +) + +;; expand inline types +(module + (type (instance (export "" (instance)))) +) + +;; reference outer types +(module + (type (instance)) + (type (instance (export "" (instance (type 0))))) + (type $x (instance)) + (type (instance (export "" (instance (type $x))))) +) + +;; recursive +(module + (type $functype (func)) + + (type (instance (export "" (instance + (export "a" (func)) + (export "b" (func (type 0))) + (export "c" (func (param i32))) + (export "d" (func (type $functype))) + + ;; globals + (export "e" (global i32)) + (export "f" (global (mut i32))) + + ;; tables + (export "g" (table 1 funcref)) + + ;; memory + (export "h" (memory 1)) + (export "i" (memory 1 2)) + (export "j" (memory 1 2 shared)) + + ;; instances + (export "k" (instance)) + )))) +) + +;; modules +(module + (type (module)) + + (type $foo (module)) + + (type $empty (func)) + (type $i (instance)) + + (type (module + (import "" "" (func)) + (import "" "" (func (type $empty))) + (import "" "" (func (param i32))) + (import "" "" (func (param i32) (result i32))) + + (import "" "" (global i32)) + (import "" "" (memory 1)) + (import "" "" (table 1 funcref)) + + (import "" "" (instance)) + (import "" "" (instance (type $i))) + (import "" "" (instance + (export "a" (func)) + (export "b" (func (type $empty))) + (export "c" (func (param i32))) + )) + + (import "" "" (module)) + (import "" "" (module + (import "" "" (func (type $empty))) + (import "" "" (func (param i32))) + (export "a" (func (type $empty))) + (export "b" (func (param i32))) + (export "c" (module)) + )) + + (export "a" (func)) + (export "b" (global i32)) + (export "c" (memory 1)) + (export "d" (table 1 funcref)) + + (export "e" (func (type $empty))) + (export "f" (func (param i32))) + + (export "g" (instance + (export "a" (func)) + (export "b" (func (type $empty))) + (export "c" (func (param i32))) + )) + + (export "h" (module + (import "" "" (func (type $empty))) + (import "" "" (func (param i32))) + (export "a" (func (type $empty))) + (export "b" (func (param i32))) + (export "c" (module)) + )) + )) +) + +(assert_invalid + (module + (type (instance + (export "" (func)) + (export "" (func))))) + "duplicate export name") + +(assert_invalid + (module + (type (func)) + (type (instance + (export "" (instance (type 0))) + ))) + "type index is not an instance") + +(assert_invalid + (module + (type (module)) + (type (instance + (export "" (instance (type 0))) + ))) + "type index is not an instance") + +(assert_invalid + (module + (type (func)) + (type (instance + (export "" (module (type 0))) + ))) + "type index is not a module") + +(assert_invalid + (module + (type (instance)) + (type (instance + (export "" (module (type 0))) + ))) + "type index is not a module") + +(assert_invalid + (module + (type (module)) + (type (instance + (export "" (func (type 0))) + ))) + "type index is not a function") + +(assert_invalid + (module + (type (instance)) + (type (instance + (export "" (func (type 0))) + ))) + "type index is not a function") + +(assert_invalid + (module + (type (instance)) + (type (instance + (export "" (instance + (export "" (func (type 0))) + )) + ))) + "type index is not a function") diff --git a/tests/local/module-linking/instantiate.wast b/tests/local/module-linking/instantiate.wast new file mode 100644 index 0000000000..f0043c3494 --- /dev/null +++ b/tests/local/module-linking/instantiate.wast @@ -0,0 +1,251 @@ +(module + (import "" (module $m)) + (instance $a (instantiate $m)) +) + +(module + (import "" (func)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (instance)) + (import "" (memory 1)) + (import "" (module $empty)) + + (import "" (module $m + (import "" (module)) + (import "" (func)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (instance)) + (import "" (memory 1)) + )) + + (instance $a (instantiate $m + (module $empty) + (func 0) + (global 0) + (table 0) + (instance 0) + (memory 0) + )) +) + +(module + (import "" (module $m + (import "" (module)) + (import "" (func)) + (import "" (global i32)) + (import "" (table 1 funcref)) + (import "" (instance)) + (import "" (memory 1)) + )) + (import "" (module $m2)) + (import "" (instance $b)) + (func $f (import "")) + (global $g (import "") i32) + (memory $mem (import "") 1) + (table $table (import "") 1 funcref) + + (instance $a + (instantiate $m + (module $m2) + (func $f) + (global $g) + (table $table) + (instance $b) + (memory $mem) + ) + ) + + ;; inline exports/imports + (instance $c (export "i") (instantiate $m2)) + (type $empty (instance)) + (instance $d (import "i") (type $empty)) + (instance (import "i")) + (instance (import "i") + (export "x" (func))) + (instance (export "a") (export "b") (instantiate $m2)) + (instance (export "c") (export "d") (import "x")) +) + +(assert_invalid + (module + (instance (instantiate 0)) + ) + "module is not defined") +(assert_invalid + (module + (import "" (module)) + (instance (instantiate 1)) + ) + "module is not defined") + +(assert_invalid + (module + (import "" (func $f)) + (import "" (module $m)) + (instance (instantiate $m (func $f))) + ) + "wrong number of imports provided") +(assert_invalid + (module + (import "" (module $m (import "" (func)))) + (instance (instantiate $m)) + ) + "wrong number of imports provided") + + +(assert_invalid + (module + (import "" (module $m + (import "" (func)) + )) + (import "" (global i32)) + (instance $i (instantiate $m (global 0))) + ) + "wrong kind of item used for instantiate") +(assert_invalid + (module + (import "" (module $m + (import "" (func)) + )) + (import "" (func (result i32))) + (instance $i (instantiate $m (func 0))) + ) + "function provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (func)) + )) + (import "" (func (param i32))) + (instance $i (instantiate $m (func 0))) + ) + "function provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (global i32)) + )) + (import "" (global i64)) + (instance $i (instantiate $m (global 0))) + ) + "global provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (table 1 externref)) + )) + (import "" (table 2 funcref)) + (instance $i (instantiate $m (table 0))) + ) + "table provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (table 1 2 funcref)) + )) + (import "" (table 2 funcref)) + (instance $i (instantiate $m (table 0))) + ) + "table provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (table 2 2 funcref)) + )) + (import "" (table 1 funcref)) + (instance $i (instantiate $m (table 0))) + ) + "table provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (table 2 2 funcref)) + )) + (import "" (table 2 3 funcref)) + (instance $i (instantiate $m (table 0))) + ) + "table provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (memory 1 2 shared)) + )) + (import "" (memory 1)) + (instance $i (instantiate $m (memory 0))) + ) + "memory provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (memory 1)) + )) + (import "" (memory 0)) + (instance $i (instantiate $m (memory 0))) + ) + "memory provided for instantiation has wrong type") +(assert_invalid + (module + (import "" (module $m + (import "" (module + (import "" (func)) + )) + )) + (import "" (module $i + (import "" (global i32)) + )) + (instance $i (instantiate $m (module $i))) + ) + "wrong kind of item used for instantiate") +(assert_invalid + (module + (import "" (module $m + (import "" (module)) + )) + (import "" (module $i + (import "" (global i32)) + )) + (instance $i (instantiate $m (module $i))) + ) + "mismatched number of module imports") +(assert_invalid + (module + (import "" (module $m + (import "" (module + (import "" (global i32)) + (import "" (func)) + )) + )) + (import "" (module $i + (import "" (global i32)) + )) + (instance $i (instantiate $m (module $i))) + ) + "mismatched number of module imports") + +;; export subsets +(module + (import "" (module $m + (import "" (module + (export "" (func)) + )) + )) + (import "" (module $i + (export "" (func)) + (export "a" (func)) + )) + (instance $i (instantiate $m (module $i))) +) +(module + (import "" (module $m + (import "" (instance + (export "" (func)) + )) + )) + (import "" (instance $i + (export "" (func)) + (export "a" (func)) + )) + (instance (instantiate $m (instance $i))) +) diff --git a/tests/local/module-linking/module-link.wast b/tests/local/module-linking/module-link.wast new file mode 100644 index 0000000000..fa9b8e201e --- /dev/null +++ b/tests/local/module-linking/module-link.wast @@ -0,0 +1,65 @@ +(module + (type $Wasi (instance)) + (module $B) + (module $B_wrap + (import "wasi" (instance $wasi (type $Wasi))) + (instance $b (instantiate $B)) + (export $b) + ) +) +(module + (type $Wasi (instance)) + (import "wasi" (instance $wasi (type $Wasi))) + + (module $A + (type $Wasi (instance)) + (import "wasi" (instance (type $Wasi))) + (func (export "a")) + ) + + (module $B + (type $Wasi (instance)) + (import "wasi" (instance $wasi (type $Wasi))) + (import "A:1.x" (module $A + (import "wasi" (instance (type $Wasi))) + (export "a" (func)) + )) + (instance $a (instantiate $A (instance $wasi))) + (func (export "b")) + ) + (module $B_wrap + (import "wasi" (instance $wasi (type $Wasi))) + (instance $b (instantiate $B (instance $wasi) (module $A))) + (export $b) + ) + + (module $C + (type $Wasi (instance)) + (import "wasi" (instance $wasi (type $Wasi))) + (import "B:1.x" (module $B + (import "wasi" (instance $wasi (type $Wasi))) + (export "b" (func)) + )) + (instance $b (instantiate $B (instance $wasi))) + (func (export "c")) + ) + (module $C_wrap + (import "wasi" (instance $wasi (type $Wasi))) + (instance $c (instantiate $C (instance $wasi) (module $B_wrap))) + (export $c) + ) + + (module $D + (type $Wasi (instance)) + (import "wasi" (instance $wasi (type $Wasi))) + (import "C:1.x" (module $C + (import "wasi" (instance $wasi (type $Wasi))) + (export "c" (func)) + )) + (instance $c (instantiate $C (instance $wasi))) + (func (export "d")) + ) + + (instance $d (instantiate $D (instance $wasi) (module $C_wrap))) + (export $d) +) diff --git a/tests/local/module-linking/nested-modules.wast b/tests/local/module-linking/nested-modules.wast new file mode 100644 index 0000000000..96abb6fb29 --- /dev/null +++ b/tests/local/module-linking/nested-modules.wast @@ -0,0 +1,31 @@ +(module + (module (import "")) + + (module) + (module) + + (type $f (module)) + (module (type $f)) + + (module (export "x")) + + (module + (module) + ) + + (module + (module $m) + (import "" (func (param i32))) + (export "a" (module $m)) + + (instance (export "b") (import "") + (export "b" (func)) + ) + ) +) + +;; does the `import` use the type annotation specified later? +(module + (import "" (module)) + (type (module)) +) diff --git a/tests/local/module-linking/single-import.wat b/tests/local/module-linking/single-import.wat new file mode 100644 index 0000000000..7da4134c45 --- /dev/null +++ b/tests/local/module-linking/single-import.wat @@ -0,0 +1,10 @@ +(module + (import "" (func)) + (import "" (module + (import "" (func)) + )) + (func (import "")) + (global (import "") i32) + (table (import "") 1 funcref) + (memory (import "") 1) +) diff --git a/tests/local/module-linking/virtualize.wast b/tests/local/module-linking/virtualize.wast new file mode 100644 index 0000000000..fdd21c39db --- /dev/null +++ b/tests/local/module-linking/virtualize.wast @@ -0,0 +1,80 @@ +(module $child + (import "wasi_file" (instance $wasi-file + (export "read" (func $read (param i32 i32 i32) (result i32))) + (export "write" (func $write (param i32 i32 i32) (result i32))) + )) + (func $play (export "play") + i32.const 0 + i32.const 0 + i32.const 0 + call $wasi-file.$read + drop + ) +) + + +(module $virtualize + (import "wasi_file" (instance $wasi-file + (export "read" (func $read (param i32 i32 i32) (result i32))) + (export "write" (func $write (param i32 i32 i32) (result i32))) + )) + (export $wasi-file) +) + + +;; parent.wat +(module + (type $WasiFile (instance + (export "read" (func $read (param i32 i32 i32) (result i32))) + (export "write" (func $write (param i32 i32 i32) (result i32))) + )) + (import "wasi_file" (instance $real-wasi (type $WasiFile))) + (import "./virtualize.wasm" (module $VIRTUALIZE + (import "wasi_file" (instance (type $WasiFile))) + (export $WasiFile) + )) + (import "./child.wasm" (module $CHILD + (import "wasi_file" (instance (type $WasiFile))) + (export "play" (func $play)) + )) + (instance $virt-wasi (instantiate $VIRTUALIZE (instance $real-wasi))) + (instance $child (instantiate $CHILD (instance $virt-wasi))) + (func (export "work") + call $child.$play + ) +) + + +;; bundled.wat +(module + (type $WasiFile (instance + (export "read" (func $read (param i32 i32 i32) (result i32))) + (export "write" (func $write (param i32 i32 i32) (result i32))) + )) + (import "wasi_file" (instance $real-wasi (type $WasiFile))) + + (module $CHILD + (import "wasi_file" (instance $wasi-file (type $WasiFile))) + (alias $wasi-file.$read (instance $wasi-file) (func 0)) + (func $play (export "play") + call $wasi-file.$read + ) + ) + + + (module $VIRTUALIZE + (import "wasi_file" (instance $wasi-file (type $WasiFile))) + (func (export "read") (param i32 i32 i32) (result i32) + i32.const 0 + ) + (func (export "write") (param i32 i32 i32) (result i32) + i32.const 0 + ) + ) + + (instance $virt-wasi (instantiate $VIRTUALIZE (instance $real-wasi))) + (instance $child (instantiate $CHILD (instance $virt-wasi))) + (func (export "work") + call $child.$play + ) +) diff --git a/tests/roundtrip.rs b/tests/roundtrip.rs index 8d8f929364..6c9901ef50 100644 --- a/tests/roundtrip.rs +++ b/tests/roundtrip.rs @@ -225,8 +225,10 @@ impl TestState { && !test.ends_with("dump/import.txt") // uses exceptions && !test.ends_with("parse/all-features.txt") + && !test.iter().any(|t| t == "module-linking") { - self.test_wasm(test, &binary, true)?; + self.test_wasm(test, &binary, true) + .context("failed testing the binary output of `wat`")?; } Ok(()) } @@ -247,6 +249,9 @@ impl TestState { // FIXME(WebAssembly/wabt#1447) && !test.ends_with("bulk-memory-operations/binary.wast") && !test.ends_with("reference-types/binary.wast") + + // not implemented in wabt + && !test.iter().any(|t| t == "module-linking") { if let Some(expected) = self.wasm2wat(contents)? { self.string_compare(&string, &expected)?; @@ -256,9 +261,11 @@ impl TestState { // If we can, convert the string back to bytes and assert it has the // same binary representation. if test_roundtrip { - let binary2 = wat::parse_str(&string)?; + let binary2 = + wat::parse_str(&string).context("failed to parse `wat` from `wasmprinter`")?; self.bump_ntests(); - self.binary_compare(&binary2, contents, false)?; + self.binary_compare(&binary2, contents, false) + .context("failed to compare original `wat` with roundtrip `wat`")?; } Ok(()) @@ -314,7 +321,11 @@ impl TestState { return Ok(()); } let mut s = format!("{} test failures in {}:", errors.len(), test.display()); - for error in errors { + for mut error in errors { + if let Some(err) = error.downcast_mut::() { + err.set_path(test); + err.set_text(contents); + } s.push_str("\n\n\t--------------------------------\n\n\t"); s.push_str(&format!("{:?}", error).replace("\n", "\n\t")); } @@ -349,7 +360,8 @@ impl TestState { // with wabt which does further parsing. ModuleKind::Binary(_) => false, }; - self.test_wasm(test, &actual, test_roundtrip)?; + self.test_wasm(test, &actual, test_roundtrip) + .context("failed testing wasm binary produced by `wast`")?; } WastDirective::QuoteModule { source, span: _ } => { @@ -645,6 +657,7 @@ impl TestState { enable_bulk_memory: true, enable_multi_value: true, enable_tail_call: true, + enable_module_linking: true, }, }; for part in test.iter().filter_map(|t| t.to_str()) { @@ -655,6 +668,7 @@ impl TestState { config.operator_config.enable_simd = false; config.operator_config.enable_bulk_memory = false; config.operator_config.enable_tail_call = false; + config.operator_config.enable_module_linking = false; } "threads" => config.operator_config.enable_threads = true, "simd" => config.operator_config.enable_simd = true,