diff --git a/Cargo.toml b/Cargo.toml index fd70bf52..cd022d6f 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,7 +29,7 @@ leb128 = "0.2.4" log = "0.4.8" rayon = { version = "1.1.0", optional = true } walrus-macro = { path = './crates/macro', version = '=0.17.0' } -wasmparser = "0.55.0" +wasmparser = "0.59.0" [features] parallel = ['rayon', 'id-arena/rayon'] diff --git a/crates/fuzz-utils/Cargo.toml b/crates/fuzz-utils/Cargo.toml index c95ead15..53cdd2a5 100644 --- a/crates/fuzz-utils/Cargo.toml +++ b/crates/fuzz-utils/Cargo.toml @@ -10,7 +10,7 @@ anyhow = "1.0" env_logger = "0.7.0" rand = { version = "0.7.0", features = ['small_rng'] } tempfile = "3.1.0" -wasmparser = "0.55" +wasmparser = "0.59" wat = "1.0" [dependencies.walrus] diff --git a/crates/fuzz-utils/src/lib.rs b/crates/fuzz-utils/src/lib.rs index 1938a031..0ea166c3 100755 --- a/crates/fuzz-utils/src/lib.rs +++ b/crates/fuzz-utils/src/lib.rs @@ -417,7 +417,7 @@ impl TestCaseGenerator for WasmOptTtf { // Only generate programs that wat2wasm can handle. if let Ok(bytes) = wat::parse_bytes(&wat) { - if wasmparser::validate(&bytes, None).is_ok() { + if wasmparser::validate(&bytes).is_ok() { return String::from_utf8(wat).unwrap(); } } diff --git a/crates/tests/tests/round_trip/anyref1.wat b/crates/tests/tests/round_trip/anyref1.wat index aee7c243..c9454a6f 100644 --- a/crates/tests/tests/round_trip/anyref1.wat +++ b/crates/tests/tests/round_trip/anyref1.wat @@ -1,6 +1,6 @@ (module - (import "x" "y" (func (param anyref))) - (func (export "a") (param anyref) + (import "x" "y" (func (param externref))) + (func (export "a") (param externref) local.get 0 call 0)) diff --git a/crates/tests/tests/round_trip/anyref2.wat b/crates/tests/tests/round_trip/anyref2.wat index d5bbace7..d873fde4 100644 --- a/crates/tests/tests/round_trip/anyref2.wat +++ b/crates/tests/tests/round_trip/anyref2.wat @@ -1,6 +1,6 @@ (module - (table 1 anyref) - (func (export "a") (param i32) (result anyref) + (table 1 externref) + (func (export "a") (param i32) (result externref) local.get 0 table.get 0)) diff --git a/crates/tests/tests/round_trip/anyref3.wat b/crates/tests/tests/round_trip/anyref3.wat index 4b39a694..322678d3 100644 --- a/crates/tests/tests/round_trip/anyref3.wat +++ b/crates/tests/tests/round_trip/anyref3.wat @@ -1,8 +1,8 @@ (module (type (func)) - (table 1 anyfunc) - (table 1 anyref) - (func (export "a") (param i32) (result anyref) + (table 1 funcref) + (table 1 externref) + (func (export "a") (param i32) (result externref) local.get 0 call_indirect (type 0) local.get 0 diff --git a/src/ir/mod.rs b/src/ir/mod.rs index 38a134e6..6fde9a76 100644 --- a/src/ir/mod.rs +++ b/src/ir/mod.rs @@ -527,12 +527,8 @@ pub enum Instr { ty: ValType, }, - /// `ref.is_null $ty` - RefIsNull { - /// The type of value we're testing for null - #[walrus(skip_visit)] - ty: ValType, - }, + /// `ref.is_null` + RefIsNull {}, /// `ref.func` RefFunc { @@ -946,14 +942,17 @@ pub enum UnaryOp { I8x16Neg, I8x16AnyTrue, I8x16AllTrue, + I8x16Bitmask, I16x8Abs, I16x8Neg, I16x8AnyTrue, I16x8AllTrue, + I16x8Bitmask, I32x4Abs, I32x4Neg, I32x4AnyTrue, I32x4AllTrue, + I32x4Bitmask, I64x2Neg, F32x4Abs, diff --git a/src/module/data.rs b/src/module/data.rs index 88a044ca..58acd1d6 100644 --- a/src/module/data.rs +++ b/src/module/data.rs @@ -185,20 +185,15 @@ impl Module { &mut self, section: wasmparser::DataSectionReader, ids: &IndicesToIds, - data_count: Option, ) -> Result<()> { log::debug!("parse data section"); - if let Some(count) = data_count { - if count != section.get_count() { - bail!("data count section mismatches actual data section"); - } - } + let preallocated = self.data.arena.len() > 0; for (i, segment) in section.into_iter().enumerate() { let segment = segment?; // If we had the `DataCount` section, then we already pre-allocated // a data segment. Otherwise, allocate one now. - let id = if data_count.is_some() { + let id = if preallocated { ids.get_data(i as u32)? } else { self.data.arena.alloc_with_id(|id| Data { diff --git a/src/module/exports.rs b/src/module/exports.rs index f4d129b5..6180ecbd 100644 --- a/src/module/exports.rs +++ b/src/module/exports.rs @@ -142,6 +142,9 @@ impl Module { Table => ExportItem::Table(ids.get_table(entry.index)?), Memory => ExportItem::Memory(ids.get_memory(entry.index)?), Global => ExportItem::Global(ids.get_global(entry.index)?), + Type | Module | Instance => { + unimplemented!("module linking not supported"); + } }; self.exports.arena.alloc_with_id(|id| Export { id, diff --git a/src/module/functions/local_function/emit.rs b/src/module/functions/local_function/emit.rs index d0a28cf0..acde6863 100644 --- a/src/module/functions/local_function/emit.rs +++ b/src/module/functions/local_function/emit.rs @@ -498,11 +498,13 @@ impl<'instr> Visitor<'instr> for Emit<'_, '_> { I8x16Neg => self.simd(0x61), I8x16AnyTrue => self.simd(0x62), I8x16AllTrue => self.simd(0x63), + I8x16Bitmask => self.simd(0x64), I16x8Abs => self.simd(0x80), I16x8Neg => self.simd(0x81), I16x8AnyTrue => self.simd(0x82), I16x8AllTrue => self.simd(0x83), + I16x8Bitmask => self.simd(0x84), I16x8WidenLowI8x16S => self.simd(0x87), I16x8WidenHighI8x16S => self.simd(0x88), I16x8WidenLowI8x16U => self.simd(0x89), @@ -512,6 +514,7 @@ impl<'instr> Visitor<'instr> for Emit<'_, '_> { I32x4Neg => self.simd(0xa1), I32x4AnyTrue => self.simd(0xa2), I32x4AllTrue => self.simd(0xa3), + I32x4Bitmask => self.simd(0xa4), I32x4WidenLowI16x8S => self.simd(0xa7), I32x4WidenHighI16x8S => self.simd(0xa8), I32x4WidenLowI16x8U => self.simd(0xa9), @@ -802,9 +805,8 @@ impl<'instr> Visitor<'instr> for Emit<'_, '_> { self.encoder.byte(0xd0); e.ty.emit(self.encoder); } - RefIsNull(e) => { + RefIsNull(_e) => { self.encoder.byte(0xd1); - e.ty.emit(self.encoder); } RefFunc(e) => { self.encoder.byte(0xd2); diff --git a/src/module/functions/local_function/mod.rs b/src/module/functions/local_function/mod.rs index 8be90604..f1d29343 100644 --- a/src/module/functions/local_function/mod.rs +++ b/src/module/functions/local_function/mod.rs @@ -1198,10 +1198,9 @@ fn validate_instruction<'context>( ctx.alloc_instr(RefNull { ty }, loc); ctx.push_operand(Some(ty)); } - Operator::RefIsNull { ty } => { - let ty = ValType::parse(&ty)?; - ctx.pop_operand_expected(Some(ty))?; - ctx.alloc_instr(RefIsNull { ty }, loc); + Operator::RefIsNull => { + // ctx.pop_operand_expected(Some(ty))?; + ctx.alloc_instr(RefIsNull {}, loc); ctx.push_operand(Some(I32)); } Operator::RefFunc { function_index } => { @@ -1462,6 +1461,10 @@ fn validate_instruction<'context>( Operator::I32x4MaxS => binop(ctx, V128, BinaryOp::I32x4MaxS)?, Operator::I32x4MaxU => binop(ctx, V128, BinaryOp::I32x4MaxU)?, + Operator::I8x16Bitmask => one_op(ctx, V128, I32, UnaryOp::I8x16Bitmask)?, + Operator::I16x8Bitmask => one_op(ctx, V128, I32, UnaryOp::I16x8Bitmask)?, + Operator::I32x4Bitmask => one_op(ctx, V128, I32, UnaryOp::I32x4Bitmask)?, + Operator::TableCopy { src_table, dst_table, @@ -1497,6 +1500,10 @@ fn validate_instruction<'context>( let elem = ctx.indices.get_element(segment)?; ctx.alloc_instr(ElemDrop { elem }, loc); } + + Operator::ReturnCall { .. } | Operator::ReturnCallIndirect { .. } => { + unimplemented!("not supported"); + } } Ok(()) } diff --git a/src/module/functions/mod.rs b/src/module/functions/mod.rs index 0d4ce55a..0433c404 100644 --- a/src/module/functions/mod.rs +++ b/src/module/functions/mod.rs @@ -12,8 +12,8 @@ use crate::parse::IndicesToIds; use crate::tombstone_arena::{Id, Tombstone, TombstoneArena}; use crate::ty::TypeId; use crate::ty::ValType; -use anyhow::bail; use std::cmp; +use wasmparser::{FuncValidator, FunctionBody, OperatorsReader}; #[cfg(feature = "parallel")] use rayon::prelude::*; @@ -323,25 +323,19 @@ impl Module { /// Add the locally defined functions in the wasm module to this instance. pub(crate) fn parse_local_functions( &mut self, - section: wasmparser::CodeSectionReader, - function_section_count: u32, + functions: Vec<(FunctionBody<'_>, FuncValidator, OperatorsReader<'_>)>, indices: &mut IndicesToIds, on_instr_pos: Option<&(dyn Fn(&usize) -> InstrLocId + Sync + Send + 'static)>, ) -> Result<()> { log::debug!("parse code section"); - let amt = section.get_count(); - if amt != function_section_count { - bail!("code and function sections must have same number of entries") - } - let num_imports = self.funcs.arena.len() - (amt as usize); + let num_imports = self.funcs.arena.len() - functions.len(); // First up serially create corresponding `LocalId` instances for all // functions as well as extract the operators parser for each function. // This is pretty tough to parallelize, but we can look into it later if // necessary and it's a bottleneck! - let mut bodies = Vec::with_capacity(amt as usize); - for (i, body) in section.into_iter().enumerate() { - let body = body?; + let mut bodies = Vec::with_capacity(functions.len()); + for (i, (body, _validator, ops)) in functions.into_iter().enumerate() { let index = (num_imports + i) as u32; let id = indices.get_func(index)?; let ty = match self.funcs.arena[id].kind { @@ -370,19 +364,7 @@ impl Module { let results = type_.results().to_vec(); self.types.add_entry_ty(&results); - // WebAssembly local indices are 32 bits, so it's a validation error to - // have more than 2^32 locals. Sure enough there's a spec test for this! - let mut total = 0u32; - for local in body.get_locals_reader()? { - let (count, _) = local?; - total = match total.checked_add(count) { - Some(n) => n, - None => bail!("can't have more than 2^32 locals"), - }; - } - - // Now that we know we have a reasonable amount of locals, put them in - // our map. + // Next up comes all the locals of the function. for local in body.get_locals_reader()? { let (count, ty) = local?; let ty = ValType::parse(&ty)?; @@ -396,8 +378,7 @@ impl Module { } } - let body = body.get_operators_reader()?; - bodies.push((id, body, args, ty)); + bodies.push((id, ops, args, ty)); } // Wasm modules can often have a lot of functions and this operation can diff --git a/src/module/imports.rs b/src/module/imports.rs index ccc2be3c..c3d9ff35 100644 --- a/src/module/imports.rs +++ b/src/module/imports.rs @@ -117,14 +117,18 @@ impl Module { match entry.ty { wasmparser::ImportSectionEntryType::Function(idx) => { let ty = ids.get_type(idx)?; - let id = self.add_import_func(entry.module, entry.field, ty); + let id = self.add_import_func( + entry.module, + entry.field.expect("module linking not supported"), + ty, + ); ids.push_func(id.0); } wasmparser::ImportSectionEntryType::Table(t) => { let ty = ValType::parse(&t.element_type)?; let id = self.add_import_table( entry.module, - entry.field, + entry.field.expect("module linking not supported"), t.limits.initial, t.limits.maximum, ty, @@ -134,7 +138,7 @@ impl Module { wasmparser::ImportSectionEntryType::Memory(m) => { let id = self.add_import_memory( entry.module, - entry.field, + entry.field.expect("module linking not supported"), m.shared, m.limits.initial, m.limits.maximum, @@ -144,12 +148,16 @@ impl Module { wasmparser::ImportSectionEntryType::Global(g) => { let id = self.add_import_global( entry.module, - entry.field, + entry.field.expect("module linking not supported"), ValType::parse(&g.content_type)?, g.mutable, ); ids.push_global(id.0); } + wasmparser::ImportSectionEntryType::Module(_) + | wasmparser::ImportSectionEntryType::Instance(_) => { + unimplemented!("module linking not implemented"); + } } } diff --git a/src/module/mod.rs b/src/module/mod.rs index 78501442..74b5b622 100644 --- a/src/module/mod.rs +++ b/src/module/mod.rs @@ -36,10 +36,11 @@ pub use crate::module::producers::ModuleProducers; pub use crate::module::tables::{ModuleTables, Table, TableId}; pub use crate::module::types::ModuleTypes; use crate::parse::IndicesToIds; -use anyhow::{bail, Context}; +use anyhow::Context; use std::fs; use std::mem; use std::path::Path; +use wasmparser::{Parser, Payload, Validator}; pub use self::config::ModuleConfig; @@ -112,111 +113,113 @@ impl Module { } fn parse(wasm: &[u8], config: &ModuleConfig) -> Result { - let mut parser = wasmparser::ModuleReader::new(wasm)?; - if parser.get_version() != 1 { - bail!("only support version 1 of wasm"); - } - let mut ret = Module::default(); ret.config = config.clone(); let mut indices = IndicesToIds::default(); - let mut function_section_size = None; - let mut data_count = None; - - while !parser.eof() { - let section = parser.read()?; - match section.code { - wasmparser::SectionCode::Data => { - let reader = section.get_data_section_reader()?; - ret.parse_data(reader, &mut indices, data_count) + let mut validator = Validator::new(); + validator.wasm_multi_value(true); + if !config.only_stable_features { + validator + .wasm_reference_types(true) + .wasm_multi_value(true) + .wasm_bulk_memory(true) + .wasm_simd(true) + .wasm_threads(true); + } + + let mut local_functions = Vec::new(); + + for payload in Parser::new(0).parse_all(wasm) { + match payload? { + Payload::Version { num, range } => { + validator.version(num, &range)?; + } + Payload::DataSection(s) => { + validator + .data_section(&s) .context("failed to parse data section")?; + ret.parse_data(s, &mut indices)?; } - wasmparser::SectionCode::Type => { - let reader = section.get_type_section_reader()?; - ret.parse_types(reader, &mut indices) + Payload::TypeSection(s) => { + validator + .type_section(&s) .context("failed to parse type section")?; + ret.parse_types(s, &mut indices)?; } - wasmparser::SectionCode::Import => { - let reader = section.get_import_section_reader()?; - ret.parse_imports(reader, &mut indices) + Payload::ImportSection(s) => { + validator + .import_section(&s) .context("failed to parse import section")?; + ret.parse_imports(s, &mut indices)?; } - wasmparser::SectionCode::Table => { - let reader = section.get_table_section_reader()?; - ret.parse_tables(reader, &mut indices) + Payload::TableSection(s) => { + validator + .table_section(&s) .context("failed to parse table section")?; + ret.parse_tables(s, &mut indices)?; } - wasmparser::SectionCode::Memory => { - let reader = section.get_memory_section_reader()?; - ret.parse_memories(reader, &mut indices) + Payload::MemorySection(s) => { + validator + .memory_section(&s) .context("failed to parse memory section")?; + ret.parse_memories(s, &mut indices)?; } - wasmparser::SectionCode::Global => { - let reader = section.get_global_section_reader()?; - ret.parse_globals(reader, &mut indices) + Payload::GlobalSection(s) => { + validator + .global_section(&s) .context("failed to parse global section")?; + ret.parse_globals(s, &mut indices)?; } - wasmparser::SectionCode::Export => { - let reader = section.get_export_section_reader()?; - ret.parse_exports(reader, &mut indices) + Payload::ExportSection(s) => { + validator + .export_section(&s) .context("failed to parse export section")?; + ret.parse_exports(s, &mut indices)?; } - wasmparser::SectionCode::Element => { - let reader = section.get_element_section_reader()?; - ret.parse_elements(reader, &mut indices) + Payload::ElementSection(s) => { + validator + .element_section(&s) .context("failed to parse element section")?; + ret.parse_elements(s, &mut indices)?; } - wasmparser::SectionCode::Start => { - let idx = section.get_start_section_content()?; - if ret.start.is_some() { - bail!("multiple start sections found"); - } - ret.start = Some(indices.get_func(idx)?); + Payload::StartSection { func, range, .. } => { + validator.start_section(func, &range)?; + ret.start = Some(indices.get_func(func)?); } - wasmparser::SectionCode::Function => { - let reader = section.get_function_section_reader()?; - function_section_size = Some(reader.get_count()); - ret.declare_local_functions(reader, &mut indices) + Payload::FunctionSection(s) => { + validator + .function_section(&s) .context("failed to parse function section")?; + ret.declare_local_functions(s, &mut indices)?; } - wasmparser::SectionCode::Code => { - let function_section_size = match function_section_size.take() { - Some(i) => i, - None => bail!("cannot have a code section without function section"), - }; - let reader = section.get_code_section_reader()?; - let on_instr_loc = config.on_instr_loc.as_ref().map(|f| f.as_ref()); - ret.parse_local_functions( - reader, - function_section_size, - &mut indices, - on_instr_loc, - ) - .context("failed to parse code section")?; - } - wasmparser::SectionCode::DataCount => { - let count = section.get_data_count_section_content()?; - data_count = Some(count); + Payload::DataCountSection { count, range } => { + validator.data_count_section(count, &range)?; ret.reserve_data(count, &mut indices); } - wasmparser::SectionCode::Custom { name, kind: _ } => { + Payload::CodeSectionStart { count, range, .. } => { + validator.code_section_start(count, &range)?; + } + Payload::CodeSectionEntry(body) => { + let (validator, ops) = validator.code_section_entry(&body)?; + local_functions.push((body, validator, ops)); + } + Payload::CustomSection { + name, + data, + data_offset, + } => { let result = match name { - "producers" => { - let reader = section.get_producers_section_reader()?; - ret.parse_producers_section(reader) - } - "name" => section - .get_name_section_reader() + "producers" => wasmparser::ProducersSectionReader::new(data, data_offset) + .map_err(anyhow::Error::from) + .and_then(|s| ret.parse_producers_section(s)), + "name" => wasmparser::NameSectionReader::new(data, data_offset) .map_err(anyhow::Error::from) .and_then(|r| ret.parse_name_section(r, &indices)), _ => { log::debug!("parsing custom section `{}`", name); - let mut reader = section.get_binary_reader(); - let len = reader.bytes_remaining(); - let payload = reader.read_bytes(len)?; ret.customs.add(RawCustomSection { name: name.to_string(), - data: payload.to_vec(), + data: data.to_vec(), }); continue; } @@ -225,12 +228,40 @@ impl Module { log::warn!("failed to parse `{}` custom section {}", name, e); } } + Payload::UnknownSection { id, range, .. } => { + validator.unknown_section(id, &range)?; + unreachable!() + } + + Payload::End => validator.end()?, + + // the module linking proposal is not implemented yet + Payload::AliasSection(s) => { + validator.alias_section(&s)?; + unreachable!() + } + Payload::InstanceSection(s) => { + validator.instance_section(&s)?; + unreachable!() + } + Payload::ModuleSection(s) => { + validator.module_section(&s)?; + unreachable!() + } + Payload::ModuleCodeSectionStart { count, range, .. } => { + validator.module_code_section_start(count, &range)?; + unreachable!() + } + Payload::ModuleCodeSectionEntry { .. } => unreachable!(), } } - if function_section_size.is_some() { - bail!("cannot define a function section without a code section"); - } + ret.parse_local_functions( + local_functions, + &mut indices, + config.on_instr_loc.as_ref().map(|f| f.as_ref()), + ) + .context("failed to parse code section")?; ret.producers .add_processed_by("walrus", env!("CARGO_PKG_VERSION")); @@ -240,7 +271,7 @@ impl Module { crate::passes::validate::run(&ret)?; } - if let Some(ref on_parse) = config.on_parse { + if let Some(on_parse) = &config.on_parse { on_parse(&mut ret, &indices)?; } diff --git a/src/module/types.rs b/src/module/types.rs index 14b00115..a44c4670 100644 --- a/src/module/types.rs +++ b/src/module/types.rs @@ -119,7 +119,10 @@ impl Module { ) -> Result<()> { log::debug!("parsing type section"); for ty in section { - let fun_ty = ty?; + let fun_ty = match ty? { + wasmparser::TypeDef::Func(ty) => ty, + _ => unimplemented!("module linking not supported"), + }; let id = self.types.arena.next_id(); let params = fun_ty .params