From 786152d4ac0751cb130e7a138c5d47394927965a Mon Sep 17 00:00:00 2001 From: Henry Date: Sun, 5 Apr 2026 18:39:48 +0200 Subject: chore: refactor optimizer/add more superinstructions Signed-off-by: Henry --- crates/parser/src/lib.rs | 25 +- crates/parser/src/module.rs | 20 +- crates/parser/src/optimize.rs | 362 ++++++++++ crates/parser/src/visit.rs | 760 +++++++-------------- crates/tinywasm/src/interpreter/executor.rs | 96 ++- .../tinywasm/src/interpreter/stack/value_stack.rs | 18 + crates/tinywasm/src/interpreter/values.rs | 9 + crates/types/src/instructions.rs | 7 + 8 files changed, 748 insertions(+), 549 deletions(-) create mode 100644 crates/parser/src/optimize.rs (limited to 'crates') diff --git a/crates/parser/src/lib.rs b/crates/parser/src/lib.rs index 364209c..2d46857 100644 --- a/crates/parser/src/lib.rs +++ b/crates/parser/src/lib.rs @@ -32,6 +32,7 @@ pub(crate) mod log { mod conversion; mod error; mod module; +mod optimize; mod visit; pub use error::*; use module::ModuleReader; @@ -41,14 +42,18 @@ pub use tinywasm_types::TinyWasmModule; /// Parser optimization and lowering options. #[non_exhaustive] -#[derive(Debug, Clone, Default)] +#[derive(Debug, Clone)] pub struct ParserOptions { - // /// Enable control-flow graph cleanup rewrites. - // pub cfg_cleanup: bool, - // /// Enable dead-code pruning. - // pub dce: bool, - // /// Enable return-call rewrites when safe. - // pub tailcall_rewrite: bool, + /// Enable post-lowering DCE pass. + /// Should be enabled by default, since the parser performs some optimizations that can result in dead code. + /// Disabling this may result in larger modules, but faster parsing time. + pub dce: bool, +} + +impl Default for ParserOptions { + fn default() -> Self { + Self { dce: true } + } } /// A WebAssembly parser @@ -133,7 +138,7 @@ impl Parser { return Err(ParseError::EndNotReached); } - reader.into_module() + reader.into_module(&self.options) } #[cfg(feature = "std")] @@ -173,7 +178,7 @@ impl Parser { reader.process_payload(payload, &mut validator)?; buffer.drain(..consumed); if eof || reader.end_reached { - return reader.into_module(); + return reader.into_module(&self.options); } } }; @@ -185,6 +190,6 @@ impl TryFrom for TinyWasmModule { type Error = ParseError; fn try_from(reader: ModuleReader) -> Result { - reader.into_module() + reader.into_module(&ParserOptions::default()) } } diff --git a/crates/parser/src/module.rs b/crates/parser/src/module.rs index 27fcb1b..fdc02ab 100644 --- a/crates/parser/src/module.rs +++ b/crates/parser/src/module.rs @@ -1,7 +1,6 @@ use crate::log::debug; -use crate::{ParseError, Result, conversion}; +use crate::{ParseError, ParserOptions, Result, conversion, optimize}; use alloc::string::ToString; -use alloc::sync::Arc; use alloc::{format, vec::Vec}; use tinywasm_types::{ ArcSlice, Data, Element, Export, FuncType, Global, Import, ImportKind, Instruction, MemoryType, TableType, @@ -9,7 +8,7 @@ use tinywasm_types::{ }; use wasmparser::{FuncValidatorAllocations, Payload, Validator}; -pub(crate) type Code = (Arc<[Instruction]>, WasmFunctionData, ValueCounts); +pub(crate) type Code = (Vec, WasmFunctionData, ValueCounts); #[derive(Default)] pub(crate) struct ModuleReader { @@ -31,16 +30,6 @@ pub(crate) struct ModuleReader { } impl ModuleReader { - fn apply_instruction_rewrites(instructions: &mut [Instruction], self_func_addr: u32) { - for instr in instructions.iter_mut() { - if matches!(instr, Instruction::Call(addr) if *addr == self_func_addr) { - *instr = Instruction::CallSelf; - } else if matches!(instr, Instruction::ReturnCall(addr) if *addr == self_func_addr) { - *instr = Instruction::ReturnCallSelf; - } - } - } - pub(crate) fn new() -> Self { Self::default() } @@ -190,7 +179,7 @@ impl ModuleReader { Ok(()) } - pub(crate) fn into_module(self) -> Result { + pub(crate) fn into_module(self, options: &ParserOptions) -> Result { if !self.end_reached { return Err(ParseError::EndNotReached); } @@ -217,8 +206,7 @@ impl ModuleReader { cref: u16::try_from(locals.cref).unwrap_or_else(|_| unreachable!("local count exceeds u16")), }; let self_func_addr = imported_func_count + func_idx as u32; - let mut instructions = instructions.to_vec(); - Self::apply_instruction_rewrites(&mut instructions, self_func_addr); + let instructions = optimize::optimize_instructions(instructions, self_func_addr, options); WasmFunction { instructions: ArcSlice::from(instructions), data, locals, params, ty } }) diff --git a/crates/parser/src/optimize.rs b/crates/parser/src/optimize.rs new file mode 100644 index 0000000..887ea04 --- /dev/null +++ b/crates/parser/src/optimize.rs @@ -0,0 +1,362 @@ +use crate::ParserOptions; +use alloc::vec::Vec; +use tinywasm_types::Instruction; + +pub(crate) fn optimize_instructions( + mut instructions: Vec, + self_func_addr: u32, + options: &ParserOptions, +) -> Vec { + rewrite(&mut instructions, self_func_addr); + if options.dce { + dce(&mut instructions); + } + instructions +} + +fn rewrite(instructions: &mut [Instruction], self_func_addr: u32) { + for read in 0..instructions.len() { + match instructions[read] { + Instruction::LocalCopy32(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy64(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopy128(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::LocalCopyRef(a, b) if a == b => instructions[read] = Instruction::Nop, + Instruction::Call(addr) if addr == self_func_addr => instructions[read] = Instruction::CallSelf, + Instruction::ReturnCall(addr) if addr == self_func_addr => instructions[read] = Instruction::ReturnCallSelf, + Instruction::I32Add => { + if read > 1 + && let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I32Const(c) if read > 1 => { + if let Instruction::LocalGet32(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet32(local); + instructions[read] = Instruction::I32AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Add => { + if read > 1 + && let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddLocals(a, b); + } + + if read > 0 { + match instructions[read - 1] { + Instruction::I64Const(c) if read > 1 => { + if let Instruction::LocalGet64(local) = instructions[read - 2] { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::LocalGet64(local); + instructions[read] = Instruction::I64AddConst(c); + } else { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64AddConst(c); + } + _ => {} + } + } + } + Instruction::I64Rotl => { + if read > 1 + && let (Instruction::I64Xor, Instruction::I64Const(c)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConst(c); + } + } + Instruction::I32Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::I64Store(memarg) => { + if read > 1 + && let (Instruction::LocalGet32(addr_local), Instruction::LocalGet64(value_local)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64StoreLocalLocal(memarg, addr_local, value_local); + } + } + Instruction::MemoryFill(mem) => { + if read > 1 + && let (Instruction::I32Const(val), Instruction::I32Const(size)) = + (instructions[read - 2], instructions[read - 1]) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::MemoryFillImm(mem, val as u8, size); + } + } + + Instruction::LocalGet32(dst) => { + if read > 0 + && let Instruction::LocalSet32(src) = instructions[read - 1] + && src == dst + { + instructions[read - 1] = Instruction::LocalTee32(src); + instructions[read] = Instruction::Nop; + } + } + Instruction::LocalGet64(dst) + if read > 0 + && let Instruction::LocalSet64(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTee64(src); + instructions[read] = Instruction::Nop; + } + Instruction::LocalGet128(dst) + if read > 0 + && let Instruction::LocalSet128(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTee128(src); + instructions[read] = Instruction::Nop; + } + Instruction::LocalGetRef(dst) + if read > 0 + && let Instruction::LocalSetRef(src) = instructions[read - 1] + && src == dst => + { + instructions[read - 1] = Instruction::LocalTeeRef(src); + instructions[read] = Instruction::Nop; + } + + Instruction::LocalSet32(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet32(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy32(src, dst) }; + } + Instruction::I32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst32(dst, c); + } + Instruction::F32Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst32(dst, i32::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 { + match (instructions[read - 2], instructions[read - 1]) { + (Instruction::LocalGet32(src), Instruction::I32AddConst(c)) if src == dst => { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst32(dst, c); + } + (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) => { + if let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalSet(memarg, addr, dst); + } + } + _ => {} + } + } + } + Instruction::LocalSet64(dst) => { + if read > 0 { + match instructions[read - 1] { + Instruction::LocalGet64(src) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + if src == dst { Instruction::Nop } else { Instruction::LocalCopy64(src, dst) }; + } + Instruction::I64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalSetConst64(dst, c); + } + Instruction::F64Const(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = + Instruction::LocalSetConst64(dst, i64::from_ne_bytes(c.to_bits().to_ne_bytes())); + } + _ => {} + } + } + + if read > 1 + && let (Instruction::LocalGet64(src), Instruction::I64AddConst(c)) = + (instructions[read - 2], instructions[read - 1]) + && src == dst + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::LocalAddConst64(dst, c); + } + } + Instruction::LocalSet128(dst) + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopy128(src, dst) }; + } + Instruction::LocalSetRef(dst) + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::Nop; + instructions[read] = if src == dst { Instruction::Nop } else { Instruction::LocalCopyRef(src, dst) }; + } + + Instruction::LocalTee32(dst) => { + if read > 0 + && let Instruction::LocalGet32(src) = instructions[read - 1] + && src == dst + { + instructions[read] = Instruction::Nop; + } + + if read > 1 + && let (Instruction::LocalGet32(addr), Instruction::I32Load(memarg)) = + (instructions[read - 2], instructions[read - 1]) + && let (Ok(addr), Ok(dst)) = (u8::try_from(addr), u8::try_from(dst)) + { + instructions[read - 2] = Instruction::Nop; + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I32LoadLocalTee(memarg, addr, dst); + } + } + Instruction::LocalTee64(dst) if read > 0 => match instructions[read - 1] { + Instruction::LocalGet64(src) if src == dst => { + instructions[read] = Instruction::Nop; + } + Instruction::I64XorRotlConst(c) => { + instructions[read - 1] = Instruction::Nop; + instructions[read] = Instruction::I64XorRotlConstTee(c, dst); + } + _ => {} + }, + Instruction::LocalTee128(dst) + if read > 0 + && let Instruction::LocalGet128(src) = instructions[read - 1] + && src == dst => + { + instructions[read] = Instruction::Nop; + } + Instruction::LocalTeeRef(dst) + if read > 0 + && let Instruction::LocalGetRef(src) = instructions[read - 1] + && src == dst => + { + instructions[read] = Instruction::Nop; + } + + Instruction::Drop32 + if read > 0 + && let Instruction::LocalTee32(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet32(local); + instructions[read] = Instruction::Nop; + } + Instruction::Drop64 + if read > 0 + && let Instruction::LocalTee64(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet64(local); + instructions[read] = Instruction::Nop; + } + Instruction::Drop128 + if read > 0 + && let Instruction::LocalTee128(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSet128(local); + instructions[read] = Instruction::Nop; + } + Instruction::DropRef + if read > 0 + && let Instruction::LocalTeeRef(local) = instructions[read - 1] => + { + instructions[read - 1] = Instruction::LocalSetRef(local); + instructions[read] = Instruction::Nop; + } + _ => {} + } + } +} + +fn dce(instructions: &mut Vec) { + let old_len = instructions.len(); + if old_len == 0 { + return; + } + + let mut removed_before = Vec::with_capacity(old_len + 1); + removed_before.push(0u32); + instructions.iter().for_each(|instr| { + let removed = removed_before.last().copied().unwrap_or(0) + u32::from(matches!(instr, Instruction::Nop)); + removed_before.push(removed); + }); + + let removed_total = removed_before[old_len]; + if removed_total == 0 { + return; + } + + let compacted_len = old_len as u32 - removed_total; + instructions.retain_mut(|instr| { + let ip = match instr { + Instruction::Jump(ip) + | Instruction::JumpIfZero(ip) + | Instruction::JumpIfNonZero(ip) + | Instruction::BranchTableTarget(ip) + | Instruction::BranchTable(ip, _) => ip, + _ => return !matches!(instr, Instruction::Nop), + }; + + let old_target = *ip as usize; + if old_target > old_len { + return !matches!(instr, Instruction::Nop); + } + + *ip -= removed_before[old_target]; + debug_assert!(*ip < compacted_len, "remapped jump target points past end of function"); + !matches!(instr, Instruction::Nop) + }); +} diff --git a/crates/parser/src/visit.rs b/crates/parser/src/visit.rs index f263dcc..09e3fae 100644 --- a/crates/parser/src/visit.rs +++ b/crates/parser/src/visit.rs @@ -61,7 +61,7 @@ pub(crate) fn process_operators_and_validate( validator: FuncValidator, body: FunctionBody<'_>, local_addr_map: Vec, -) -> Result<(alloc::sync::Arc<[Instruction]>, WasmFunctionData, FuncValidatorAllocations)> { +) -> Result<(Vec, WasmFunctionData, FuncValidatorAllocations)> { let mut reader = body.get_operators_reader()?; let remaining = reader.get_binary_reader().bytes_remaining(); let mut builder = FunctionBuilder::new(remaining, validator, local_addr_map); @@ -76,7 +76,7 @@ pub(crate) fn process_operators_and_validate( } Ok(( - alloc::sync::Arc::from(builder.instructions), + builder.instructions, WasmFunctionData { v128_constants: builder.v128_constants.into_boxed_slice() }, builder.validator.into_allocations(), )) @@ -141,217 +141,6 @@ pub(crate) struct FunctionBuilder { errors: Vec, } -impl FunctionBuilder { - pub(crate) fn validator_visitor( - &mut self, - offset: usize, - ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { - self.validator.simd_visitor(offset) - } - - pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { - Self { - validator, - local_addr_map, - instructions: Vec::with_capacity(instr_capacity), - v128_constants: Vec::new(), - ctx_stack: Vec::with_capacity(256), - errors: Vec::new(), - } - } - - fn stack_base_at_frame(&self, depth: usize) -> StackBase { - let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; - let mut base = StackBase::default(); - for i in 0..frame.height { - let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; - if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { - match ty { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, - wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, - wasmparser::ValType::V128 => base.s128 += 1, - wasmparser::ValType::Ref(_) => base.sref += 1, - } - } - } - - base - } - - fn unsupported(&mut self, name: &str) { - self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); - } - - fn is_unreachable(&self) -> bool { - self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) - } - - fn get_ctx_idx(&self, depth: u32) -> Option { - let len = self.ctx_stack.len(); - let idx = len.checked_sub(depth as usize + 1)?; - Some(idx) - } - - fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { - let fits_u8 = base.s32 <= u8::MAX as u16 - && c32 <= u8::MAX as u16 - && base.s64 <= u8::MAX as u16 - && c64 <= u8::MAX as u16 - && base.s128 <= u8::MAX as u16 - && c128 <= u8::MAX as u16 - && base.sref <= u8::MAX as u16 - && cref <= u8::MAX as u16; - - if fits_u8 { - self.instructions.push(Instruction::DropKeepSmall { - base32: base.s32 as u8, - keep32: c32 as u8, - base64: base.s64 as u8, - keep64: c64 as u8, - base128: base.s128 as u8, - keep128: c128 as u8, - base_ref: base.sref as u8, - keep_ref: cref as u8, - }); - } else { - self.instructions.push(Instruction::DropKeep32(base.s32, c32)); - self.instructions.push(Instruction::DropKeep64(base.s64, c64)); - self.instructions.push(Instruction::DropKeep128(base.s128, c128)); - self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); - } - } - - fn patch_jump(&mut self, jump_ip: usize, target: usize) { - if let Instruction::Jump(ip) = &mut self.instructions[jump_ip] { - *ip = target as u32; - } - } - - fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { - if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { - *ip = target as u32; - } - } - - fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { - let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); - for ty in label_types { - match ty { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, - wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, - wasmparser::ValType::V128 => c128 += 1, - wasmparser::ValType::Ref(_) => cref += 1, - } - } - - (c32, c64, c128, cref) - } - - fn emit_dropkeep_to_label(&mut self, label_depth: u32) { - if self.is_unreachable() { - return; - } - - let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { - return; - }; - - let base = self.stack_base_at_frame(label_depth as usize); - let label_types: Vec<_> = self.label_types_for_frame(frame); - let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); - - self.emit_dropkeep(base, c32, c64, c128, cref); - } - - fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { - let ty = &frame.block_type; - match ty { - wasmparser::BlockType::Empty => Vec::new(), - wasmparser::BlockType::Type(ty) => match frame.kind { - FrameKind::Loop => Vec::new(), - _ => vec![*ty], - }, - wasmparser::BlockType::FuncType(idx) => { - let sub_type = self.validator.resources().sub_type_at(*idx); - let func_ty = match sub_type { - Some(st) => st.composite_type.unwrap_func(), - None => return Vec::new(), - }; - match frame.kind { - FrameKind::Loop => func_ty.params().to_vec(), - _ => func_ty.results().to_vec(), - } - } - } - } - - fn emit_branch_jump_or_return(&mut self, depth: u32) { - if let Some(ctx_idx) = self.get_ctx_idx(depth) { - let jump_ip = self.instructions.len(); - self.instructions.push(Instruction::Jump(0)); - self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); - } else { - self.instructions.push(Instruction::Return); - } - } - - fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { - let pad_start = self.instructions.len(); - let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; - let Some(frame) = frame else { - let ip = self.instructions.len(); - self.instructions.push(Instruction::Return); - return (pad_start, ip, true); - }; - - let base = self.stack_base_at_frame(depth as usize); - let label_types: Vec<_> = self.label_types_for_frame(frame); - let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); - self.emit_dropkeep(base, c32, c64, c128, cref); - - let jump_ip = self.instructions.len(); - self.instructions.push(Instruction::Jump(0)); - (pad_start, jump_ip, false) - } - - fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { - let Some(frame) = self.validator.get_control_frame(depth as usize) else { - self.instructions[jump_ip] = Instruction::Return; - return; - }; - let Some(ctx_idx) = self.get_ctx_idx(depth) else { - self.instructions[jump_ip] = Instruction::Return; - return; - }; - - match frame.kind { - FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), - _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), - } - } - - fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { - match ctx.kind { - BlockKind::Block | BlockKind::Loop => { - let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; - for jump_ip in ctx.branch_jumps { - self.patch_jump(jump_ip, target); - } - } - BlockKind::If => { - if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { - if !ctx.has_else { - self.patch_jump_if_zero(cond_jump_ip, end_ip); - } - for &jump_ip in branch_jumps { - self.patch_jump(jump_ip, end_ip); - } - } - } - } - } -} - macro_rules! impl_visit_operator { ($(@$proposal:ident $op:ident $({ $($arg:ident: $argty:ty),* })? => $visit:ident ($($ann:tt)*))*) => { $(impl_visit_operator!(@@$proposal $op $({ $($arg: $argty),* })? => $visit ($($ann:tt)*));)* @@ -383,12 +172,12 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild } define_mem_operands! { - visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_i64_store(I64Store), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32) + visit_i32_load(I32Load), visit_i64_load(I64Load), visit_f32_load(F32Load), visit_f64_load(F64Load), visit_i32_load8_s(I32Load8S), visit_i32_load8_u(I32Load8U), visit_i32_load16_s(I32Load16S), visit_i32_load16_u(I32Load16U), visit_i64_load8_s(I64Load8S), visit_i64_load8_u(I64Load8U), visit_i64_load16_s(I64Load16S), visit_i64_load16_u(I64Load16U), visit_i64_load32_s(I64Load32S), visit_i64_load32_u(I64Load32U), visit_f32_store(F32Store), visit_f64_store(F64Store), visit_i32_store8(I32Store8), visit_i32_store16(I32Store16), visit_i64_store8(I64Store8), visit_i64_store16(I64Store16), visit_i64_store32(I64Store32), visit_i32_store(I32Store), visit_i64_store(I64Store) } define_operands! { // basic instructions - visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), + visit_global_get(GlobalGet, u32), visit_i32_const(I32Const, i32), visit_i64_const(I64Const, i64), visit_call(Call, u32), visit_return_call(ReturnCall, u32), visit_memory_size(MemorySize, u32), visit_memory_grow(MemoryGrow, u32), visit_unreachable(Unreachable), visit_nop(Nop), visit_i32_eqz(I32Eqz), visit_i32_eq(I32Eq), visit_i32_ne(I32Ne), visit_i32_lt_s(I32LtS), visit_i32_lt_u(I32LtU), visit_i32_gt_s(I32GtS), visit_i32_gt_u(I32GtU), visit_i32_le_s(I32LeS), visit_i32_le_u(I32LeU), visit_i32_ge_s(I32GeS), visit_i32_ge_u(I32GeU), visit_i64_eqz(I64Eqz), visit_i64_eq(I64Eq), visit_i64_ne(I64Ne), visit_i64_lt_s(I64LtS), visit_i64_lt_u(I64LtU), visit_i64_gt_s(I64GtS), visit_i64_gt_u(I64GtU), visit_i64_le_s(I64LeS), visit_i64_le_u(I64LeU), visit_i64_ge_s(I64GeS), visit_i64_ge_u(I64GeU), visit_f32_eq(F32Eq), visit_f32_ne(F32Ne), visit_f32_lt(F32Lt), visit_f32_gt(F32Gt), visit_f32_le(F32Le), visit_f32_ge(F32Ge), visit_f64_eq(F64Eq), visit_f64_ne(F64Ne), visit_f64_lt(F64Lt), visit_f64_gt(F64Gt), visit_f64_le(F64Le), visit_f64_ge(F64Ge), visit_i32_clz(I32Clz), visit_i32_ctz(I32Ctz), visit_i32_popcnt(I32Popcnt), visit_i32_sub(I32Sub), visit_i32_mul(I32Mul), visit_i32_div_s(I32DivS), visit_i32_div_u(I32DivU), visit_i32_rem_s(I32RemS), visit_i32_rem_u(I32RemU), visit_i32_and(I32And), visit_i32_or(I32Or), visit_i32_xor(I32Xor), visit_i32_shl(I32Shl), visit_i32_shr_s(I32ShrS), visit_i32_shr_u(I32ShrU), visit_i32_rotl(I32Rotl), visit_i32_rotr(I32Rotr), visit_i64_clz(I64Clz), visit_i64_ctz(I64Ctz), visit_i64_popcnt(I64Popcnt), visit_i64_sub(I64Sub), visit_i64_mul(I64Mul), visit_i64_div_s(I64DivS), visit_i64_div_u(I64DivU), visit_i64_rem_s(I64RemS), visit_i64_rem_u(I64RemU), visit_i64_and(I64And), visit_i64_or(I64Or), visit_i64_xor(I64Xor), visit_i64_shl(I64Shl), visit_i64_shr_s(I64ShrS), visit_i64_shr_u(I64ShrU), visit_i64_rotr(I64Rotr), visit_f32_abs(F32Abs), visit_f32_neg(F32Neg), visit_f32_ceil(F32Ceil), visit_f32_floor(F32Floor), visit_f32_trunc(F32Trunc), visit_f32_nearest(F32Nearest), visit_f32_sqrt(F32Sqrt), visit_f32_add(F32Add), visit_f32_sub(F32Sub), visit_f32_mul(F32Mul), visit_f32_div(F32Div), visit_f32_min(F32Min), visit_f32_max(F32Max), visit_f32_copysign(F32Copysign), visit_f64_abs(F64Abs), visit_f64_neg(F64Neg), visit_f64_ceil(F64Ceil), visit_f64_floor(F64Floor), visit_f64_trunc(F64Trunc), visit_f64_nearest(F64Nearest), visit_f64_sqrt(F64Sqrt), visit_f64_add(F64Add), visit_f64_sub(F64Sub), visit_f64_mul(F64Mul), visit_f64_div(F64Div), visit_f64_min(F64Min), visit_f64_max(F64Max), visit_f64_copysign(F64Copysign), visit_i32_wrap_i64(I32WrapI64), visit_i32_trunc_f32_s(I32TruncF32S), visit_i32_trunc_f32_u(I32TruncF32U), visit_i32_trunc_f64_s(I32TruncF64S), visit_i32_trunc_f64_u(I32TruncF64U), visit_i64_extend_i32_s(I64ExtendI32S), visit_i64_extend_i32_u(I64ExtendI32U), visit_i64_trunc_f32_s(I64TruncF32S), visit_i64_trunc_f32_u(I64TruncF32U), visit_i64_trunc_f64_s(I64TruncF64S), visit_i64_trunc_f64_u(I64TruncF64U), visit_f32_convert_i32_s(F32ConvertI32S), visit_f32_convert_i32_u(F32ConvertI32U), visit_f32_convert_i64_s(F32ConvertI64S), visit_f32_convert_i64_u(F32ConvertI64U), visit_f32_demote_f64(F32DemoteF64), visit_f64_convert_i32_s(F64ConvertI32S), visit_f64_convert_i32_u(F64ConvertI32U), visit_f64_convert_i64_s(F64ConvertI64S), visit_f64_convert_i64_u(F64ConvertI64U), visit_f64_promote_f32(F64PromoteF32), visit_i32_add(I32Add), visit_i64_add(I64Add), visit_i64_rotl(I64Rotl), // sign_extension visit_i32_extend8_s(I32Extend8S), visit_i32_extend16_s(I32Extend16S), visit_i64_extend8_s(I64Extend8S), visit_i64_extend16_s(I64Extend16S), visit_i64_extend32_s(I64Extend32S), @@ -400,88 +189,35 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild visit_ref_func(RefFunc, u32), visit_table_fill(TableFill, u32), visit_table_get(TableGet, u32), visit_table_set(TableSet, u32), visit_table_grow(TableGrow, u32), visit_table_size(TableSize, u32), // Bulk Memory - visit_memory_init(MemoryInit, u32, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), + visit_memory_init(MemoryInit, u32, u32), visit_memory_fill(MemoryFill, u32), visit_memory_copy(MemoryCopy, u32, u32), visit_table_init(TableInit, u32, u32), visit_data_drop(DataDrop, u32), visit_elem_drop(ElemDrop, u32), // Wide Arithmetic visit_i64_add128(I64Add128), visit_i64_sub128(I64Sub128), visit_i64_mul_wide_s(I64MulWideS), visit_i64_mul_wide_u(I64MulWideU) } fn visit_global_set(&mut self, global_index: u32) -> Self::Output { - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::F32 => Instruction::GlobalSet32(global_index), wasmparser::ValType::I64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::F64 => Instruction::GlobalSet64(global_index), wasmparser::ValType::V128 => Instruction::GlobalSet128(global_index), wasmparser::ValType::Ref(_) => Instruction::GlobalSetRef(global_index), - }), - _ => { - { - self.visit_unreachable(); - }; - } + }) } } - fn visit_i32_store(&mut self, memarg: wasmparser::MemArg) -> Self::Output { - let memarg = MemoryArg::new(memarg.offset, memarg.memory); - let len = self.instructions.len(); - if len >= 2 { - let addr = self.instructions[len - 2]; - let value = self.instructions[len - 1]; - if let (Instruction::LocalGet32(addr_local), Instruction::LocalGet32(value_local)) = (addr, value) { - let (Ok(addr_local), Ok(value_local)) = (u8::try_from(addr_local), u8::try_from(value_local)) else { - self.instructions.push(Instruction::I32Store(memarg)); - return; - }; - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32StoreLocalLocal(memarg, addr_local, value_local)); - return; - } - } - - self.instructions.push(Instruction::I32Store(memarg)); - } - fn visit_drop(&mut self) -> Self::Output { - match self.instructions.last().copied() { - Some(Instruction::LocalTee32(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet32(local)); - return; - } - Some(Instruction::LocalTee64(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet64(local)); - return; - } - Some(Instruction::LocalTee128(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSet128(local)); - return; - } - Some(Instruction::LocalTeeRef(local)) => { - self.instructions.pop(); - self.instructions.push(Instruction::LocalSetRef(local)); - return; - } - _ => {} - } - - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::Drop32, wasmparser::ValType::F32 => Instruction::Drop32, wasmparser::ValType::I64 => Instruction::Drop64, wasmparser::ValType::F64 => Instruction::Drop64, wasmparser::ValType::V128 => Instruction::Drop128, wasmparser::ValType::Ref(_) => Instruction::DropRef, - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -496,90 +232,6 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild self.instructions.push(Instruction::Return); } - fn visit_i32_add(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::LocalGet32(a), Instruction::LocalGet32(b)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32AddLocals(a, b)); - return; - } - } - - if let Some(Instruction::I32Const(c)) = self.instructions.last().copied() { - self.instructions.pop(); - self.instructions.push(Instruction::I32AddConst(c)); - return; - } - - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I32Const(c), Instruction::LocalGet32(local)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::LocalGet32(local)); - self.instructions.push(Instruction::I32AddConst(c)); - return; - } - } - - self.instructions.push(Instruction::I32Add); - } - - fn visit_i64_add(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::LocalGet64(a), Instruction::LocalGet64(b)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I64AddLocals(a, b)); - return; - } - } - - if let Some(Instruction::I64Const(c)) = self.instructions.last().copied() { - self.instructions.pop(); - self.instructions.push(Instruction::I64AddConst(c)); - return; - } - - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I64Const(c), Instruction::LocalGet64(local)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::LocalGet64(local)); - self.instructions.push(Instruction::I64AddConst(c)); - return; - } - } - - self.instructions.push(Instruction::I64Add); - } - - fn visit_i64_rotl(&mut self) -> Self::Output { - let len = self.instructions.len(); - if len >= 2 { - let lhs = self.instructions[len - 2]; - let rhs = self.instructions[len - 1]; - if let (Instruction::I64Xor, Instruction::I64Const(c)) = (lhs, rhs) { - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I64XorRotlConst(c)); - return; - } - } - - self.instructions.push(Instruction::I64Rotl); - } - fn visit_local_get(&mut self, idx: u32) -> Self::Output { let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { self.errors.push(crate::ParseError::UnsupportedOperator( @@ -588,103 +240,41 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild return; }; - match self.validator.get_local_type(idx) { - Some(t) => match t { + if let Some(t) = self.validator.get_local_type(idx) { + match t { wasmparser::ValType::I32 | wasmparser::ValType::F32 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet32(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee32(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet32(resolved_idx)); } wasmparser::ValType::I64 | wasmparser::ValType::F64 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet64(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee64(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet64(resolved_idx)); } wasmparser::ValType::V128 => { - if matches!(self.instructions.last(), Some(Instruction::LocalSet128(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTee128(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGet128(resolved_idx)); } wasmparser::ValType::Ref(_) => { - if matches!(self.instructions.last(), Some(Instruction::LocalSetRef(local)) if *local == resolved_idx) - { - self.instructions.pop(); - self.instructions.push(Instruction::LocalTeeRef(resolved_idx)); - return; - } self.instructions.push(Instruction::LocalGetRef(resolved_idx)); } - }, - _ => { - self.visit_unreachable(); } } } - fn visit_local_set(&mut self, idx: u32) -> Self::Output { - let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { - self.errors.push(crate::ParseError::UnsupportedOperator( - "Local index is too large, tinywasm does not support local indexes that large".to_string(), - )); - return; - }; - - if let Some( - Instruction::LocalGet32(from) - | Instruction::LocalGet64(from) - | Instruction::LocalGet128(from) - | Instruction::LocalGetRef(from), - ) = self.instructions.last() - { - let from = *from; - self.instructions.pop(); - - if from == resolved_idx { - // local.set x (local.get x) is a no-op; drop it. - return; - } - - // validation will ensure that the last instruction is the correct local.get - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { - wasmparser::ValType::I32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::F32 => Instruction::LocalCopy32(from, resolved_idx), - wasmparser::ValType::I64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::F64 => Instruction::LocalCopy64(from, resolved_idx), - wasmparser::ValType::V128 => Instruction::LocalCopy128(from, resolved_idx), - wasmparser::ValType::Ref(_) => Instruction::LocalCopyRef(from, resolved_idx), - }), - _ => { - self.visit_unreachable(); - } - } + fn visit_local_set(&mut self, idx: u32) -> Self::Output { + let Ok(resolved_idx) = self.local_addr_map[idx as usize].try_into() else { + self.errors.push(crate::ParseError::UnsupportedOperator( + "Local index is too large, tinywasm does not support local indexes that large".to_string(), + )); return; - } + }; - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + if let Some(Some(t)) = self.validator.get_operand_type(0) { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalSet32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalSet64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalSet128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalSetRef(resolved_idx), - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -697,79 +287,14 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild }; if let Some(Some(t)) = self.validator.get_operand_type(0) { - match t { - wasmparser::ValType::I32 | wasmparser::ValType::F32 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet32(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::I64 | wasmparser::ValType::F64 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet64(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::V128 => { - if matches!(self.instructions.last(), Some(Instruction::LocalGet128(local)) if *local == resolved_idx) - { - return; - } - } - wasmparser::ValType::Ref(_) => { - if matches!(self.instructions.last(), Some(Instruction::LocalGetRef(local)) if *local == resolved_idx) - { - return; - } - } - } - } - - if let Some(Instruction::I64XorRotlConst(c)) = self.instructions.last().copied() { - match self.validator.get_operand_type(0) { - Some(Some(wasmparser::ValType::I64)) | Some(Some(wasmparser::ValType::F64)) => { - self.instructions.pop(); - self.instructions.push(Instruction::I64XorRotlConstTee(c, resolved_idx)); - return; - } - _ => {} - } - } - - let len = self.instructions.len(); - if len >= 2 { - let addr = self.instructions[len - 2]; - let load = self.instructions[len - 1]; - if let (Instruction::LocalGet32(addr_local), Instruction::I32Load(memarg)) = (addr, load) { - match self.validator.get_operand_type(0) { - Some(Some(wasmparser::ValType::I32)) | Some(Some(wasmparser::ValType::F32)) => { - let (Ok(addr_local), Ok(resolved_idx)) = (u8::try_from(addr_local), u8::try_from(resolved_idx)) - else { - self.instructions.push(Instruction::LocalTee32(resolved_idx)); - return; - }; - self.instructions.pop(); - self.instructions.pop(); - self.instructions.push(Instruction::I32LoadLocalTee(memarg, addr_local, resolved_idx)); - return; - } - _ => {} - } - } - } - - match self.validator.get_operand_type(0) { - Some(Some(t)) => self.instructions.push(match t { + self.instructions.push(match t { wasmparser::ValType::I32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::F32 => Instruction::LocalTee32(resolved_idx), wasmparser::ValType::I64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::F64 => Instruction::LocalTee64(resolved_idx), wasmparser::ValType::V128 => Instruction::LocalTee128(resolved_idx), wasmparser::ValType::Ref(_) => Instruction::LocalTeeRef(resolved_idx), - }), - _ => { - self.visit_unreachable(); - } + }) } } @@ -838,7 +363,18 @@ impl<'a, R: WasmModuleResources> wasmparser::VisitOperator<'a> for FunctionBuild fn visit_br_if(&mut self, depth: u32) -> Self::Output { let cond_jump_ip = self.instructions.len(); self.instructions.push(Instruction::JumpIfZero(0)); + + let branch_side_start = self.instructions.len(); self.emit_dropkeep_to_label(depth); + + if self.instructions.len() == branch_side_start + && let Some(ctx_idx) = self.get_ctx_idx(depth) + { + self.instructions[cond_jump_ip] = Instruction::JumpIfNonZero(0); + self.ctx_stack[ctx_idx].branch_jumps.push(cond_jump_ip); + return; + } + self.emit_branch_jump_or_return(depth); self.patch_jump_if_zero(cond_jump_ip, self.instructions.len()); } @@ -1043,3 +579,229 @@ impl wasmparser::VisitSimdOperator<'_> for FunctionBuild self.v128_constants.push(value.i128()); } } + +impl FunctionBuilder { + pub(crate) fn validator_visitor( + &mut self, + offset: usize, + ) -> impl VisitOperator<'_, Output = Result<(), wasmparser::BinaryReaderError>> + VisitSimdOperator<'_> { + self.validator.simd_visitor(offset) + } + + pub(crate) fn new(instr_capacity: usize, validator: FuncValidator, local_addr_map: Vec) -> Self { + Self { + validator, + local_addr_map, + instructions: Vec::with_capacity(instr_capacity), + v128_constants: Vec::new(), + ctx_stack: Vec::with_capacity(256), + errors: Vec::new(), + } + } + + fn stack_base_at_frame(&self, depth: usize) -> StackBase { + let Some(frame) = self.validator.get_control_frame(depth) else { return StackBase::default() }; + let mut base = StackBase::default(); + for i in 0..frame.height { + let depth_from_top = self.validator.operand_stack_height() as usize - 1 - i; + if let Some(Some(ty)) = self.validator.get_operand_type(depth_from_top) { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => base.s32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => base.s64 += 1, + wasmparser::ValType::V128 => base.s128 += 1, + wasmparser::ValType::Ref(_) => base.sref += 1, + } + } + } + + base + } + + fn unsupported(&mut self, name: &str) { + self.errors.push(crate::ParseError::UnsupportedOperator(name.to_string())); + } + + fn is_unreachable(&self) -> bool { + self.validator.get_control_frame(0).is_none_or(|f| f.unreachable) + } + + fn get_ctx_idx(&self, depth: u32) -> Option { + let len = self.ctx_stack.len(); + let idx = len.checked_sub(depth as usize + 1)?; + Some(idx) + } + + fn emit_dropkeep(&mut self, base: StackBase, c32: u16, c64: u16, c128: u16, cref: u16) { + if base.s32 == 0 + && c32 == 0 + && base.s64 == 0 + && c64 == 0 + && base.s128 == 0 + && c128 == 0 + && base.sref == 0 + && cref == 0 + { + return; + } + + let fits_u8 = base.s32 <= u8::MAX as u16 + && c32 <= u8::MAX as u16 + && base.s64 <= u8::MAX as u16 + && c64 <= u8::MAX as u16 + && base.s128 <= u8::MAX as u16 + && c128 <= u8::MAX as u16 + && base.sref <= u8::MAX as u16 + && cref <= u8::MAX as u16; + + if fits_u8 { + self.instructions.push(Instruction::DropKeepSmall { + base32: base.s32 as u8, + keep32: c32 as u8, + base64: base.s64 as u8, + keep64: c64 as u8, + base128: base.s128 as u8, + keep128: c128 as u8, + base_ref: base.sref as u8, + keep_ref: cref as u8, + }); + } else { + self.instructions.push(Instruction::DropKeep32(base.s32, c32)); + self.instructions.push(Instruction::DropKeep64(base.s64, c64)); + self.instructions.push(Instruction::DropKeep128(base.s128, c128)); + self.instructions.push(Instruction::DropKeepRef(base.sref, cref)); + } + } + + fn patch_jump(&mut self, jump_ip: usize, target: usize) { + match &mut self.instructions[jump_ip] { + Instruction::Jump(ip) | Instruction::JumpIfNonZero(ip) => { + *ip = target as u32; + } + _ => {} + } + } + + fn patch_jump_if_zero(&mut self, jump_ip: usize, target: usize) { + if let Instruction::JumpIfZero(ip) = &mut self.instructions[jump_ip] { + *ip = target as u32; + } + } + + fn label_keep_counts(label_types: &[wasmparser::ValType]) -> (u16, u16, u16, u16) { + let (mut c32, mut c64, mut c128, mut cref) = (0, 0, 0, 0); + for ty in label_types { + match ty { + wasmparser::ValType::I32 | wasmparser::ValType::F32 => c32 += 1, + wasmparser::ValType::I64 | wasmparser::ValType::F64 => c64 += 1, + wasmparser::ValType::V128 => c128 += 1, + wasmparser::ValType::Ref(_) => cref += 1, + } + } + + (c32, c64, c128, cref) + } + + fn emit_dropkeep_to_label(&mut self, label_depth: u32) { + if self.is_unreachable() { + return; + } + + let Some(frame) = self.validator.get_control_frame(label_depth as usize) else { + return; + }; + + let base = self.stack_base_at_frame(label_depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + + self.emit_dropkeep(base, c32, c64, c128, cref); + } + + fn label_types_for_frame(&self, frame: &wasmparser::Frame) -> Vec { + let ty = &frame.block_type; + match ty { + wasmparser::BlockType::Empty => Vec::new(), + wasmparser::BlockType::Type(ty) => match frame.kind { + FrameKind::Loop => Vec::new(), + _ => vec![*ty], + }, + wasmparser::BlockType::FuncType(idx) => { + let sub_type = self.validator.resources().sub_type_at(*idx); + let func_ty = match sub_type { + Some(st) => st.composite_type.unwrap_func(), + None => return Vec::new(), + }; + match frame.kind { + FrameKind::Loop => func_ty.params().to_vec(), + _ => func_ty.results().to_vec(), + } + } + } + } + + fn emit_branch_jump_or_return(&mut self, depth: u32) { + if let Some(ctx_idx) = self.get_ctx_idx(depth) { + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip); + } else { + self.instructions.push(Instruction::Return); + } + } + + fn emit_br_table_pad(&mut self, depth: u32) -> (usize, usize, bool) { + let pad_start = self.instructions.len(); + let frame = if self.is_unreachable() { None } else { self.validator.get_control_frame(depth as usize) }; + let Some(frame) = frame else { + let ip = self.instructions.len(); + self.instructions.push(Instruction::Return); + return (pad_start, ip, true); + }; + + let base = self.stack_base_at_frame(depth as usize); + let label_types: Vec<_> = self.label_types_for_frame(frame); + let (c32, c64, c128, cref) = Self::label_keep_counts(&label_types); + self.emit_dropkeep(base, c32, c64, c128, cref); + + let jump_ip = self.instructions.len(); + self.instructions.push(Instruction::Jump(0)); + (pad_start, jump_ip, false) + } + + fn patch_branch_jump_or_return(&mut self, depth: u32, jump_ip: usize) { + let Some(frame) = self.validator.get_control_frame(depth as usize) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + let Some(ctx_idx) = self.get_ctx_idx(depth) else { + self.instructions[jump_ip] = Instruction::Return; + return; + }; + + match frame.kind { + FrameKind::Loop => self.patch_jump(jump_ip, self.ctx_stack[ctx_idx].start_ip), + _ => self.ctx_stack[ctx_idx].branch_jumps.push(jump_ip), + } + } + + fn patch_end_jumps(&mut self, ctx: LoweringCtx, end_ip: usize) { + match ctx.kind { + BlockKind::Block | BlockKind::Loop => { + let target = if matches!(ctx.kind, BlockKind::Loop) { ctx.start_ip } else { end_ip }; + for jump_ip in ctx.branch_jumps { + self.patch_jump(jump_ip, target); + } + } + BlockKind::If => { + if let Some((&cond_jump_ip, branch_jumps)) = ctx.branch_jumps.split_first() { + if !ctx.has_else { + self.patch_jump_if_zero(cond_jump_ip, end_ip); + } + for &jump_ip in branch_jumps { + self.patch_jump(jump_ip, end_ip); + } + } + } + } + } +} diff --git a/crates/tinywasm/src/interpreter/executor.rs b/crates/tinywasm/src/interpreter/executor.rs index 54ab12a..0091013 100644 --- a/crates/tinywasm/src/interpreter/executor.rs +++ b/crates/tinywasm/src/interpreter/executor.rs @@ -119,6 +119,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { ReturnCallIndirect(ty, table) => { self.exec_call_indirect::(*ty, *table)?; continue; } Jump(ip) => { self.exec_jump(*ip); continue; } JumpIfZero(ip) => if self.exec_jump_if_zero(*ip) { continue; }, + JumpIfNonZero(ip) => if self.exec_jump_if_non_zero(*ip) { continue; }, DropKeepSmall { base32, keep32, base64, keep64, base128, keep128, base_ref, keep_ref } => { let b32 = self.cf.stack_base().s32 + *base32 as u32; let k32 = *keep32 as usize; @@ -172,30 +173,14 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { I64AddLocals(a, b) => self.store.stack.values.push(self.store.stack.values.local_get::(&self.cf, *a).wrapping_add(self.store.stack.values.local_get::(&self.cf, *b)))?, I32AddConst(c) => stack_op!(unary i32, |v| v.wrapping_add(*c)), I64AddConst(c) => stack_op!(unary i64, |v| v.wrapping_add(*c)), - I32StoreLocalLocal(m, addr_local, value_local) => { - let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(m.mem_addr())); - let addr_local = u16::from(*addr_local); - let value_local = u16::from(*value_local); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); - let value = self.store.stack.values.local_get::(&self.cf, value_local).to_mem_bytes(); - mem.store((m.offset() + addr) as usize, value.len(), &value)?; - } - I32LoadLocalTee(m, addr_local, dst_local) => { - let mem = self.store.state.get_mem(self.module.resolve_mem_addr(m.mem_addr())); - let addr_local = u16::from(*addr_local); - let dst_local = u16::from(*dst_local); - let addr = u64::from(self.store.stack.values.local_get::(&self.cf, addr_local)); - let Some(Ok(addr)) = m.offset().checked_add(addr).map(|a| a.try_into()) else { - return Err(Error::Trap(Trap::MemoryOutOfBounds { - offset: addr as usize, - len: 4, - max: 0, - })); - }; - let value = mem.load_as::<4, i32>(addr)?; - self.store.stack.values.local_set(&self.cf, dst_local, value); - self.store.stack.values.push(value)?; - } + LocalAddConst32(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u32)), + LocalAddConst64(local_index, c) => self.store.stack.values.local_update::(&self.cf, *local_index, |local| *local = local.wrapping_add(*c as u64)), + LocalSetConst32(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + LocalSetConst64(local_index, c) => self.store.stack.values.local_set::(&self.cf, *local_index, *c), + I32StoreLocalLocal(m, addr_local, value_local) => self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I64StoreLocalLocal(m, addr_local, value_local) =>self.exec_store_local_local::(*m, *addr_local, *value_local, |v| v)?, + I32LoadLocalTee(m, addr_local, dst_local) => self.exec_i32_load_local_tee(*m, *addr_local, *dst_local)?, + I32LoadLocalSet(m, addr_local, dst_local) => self.exec_i32_load_local_set(*m, *addr_local, *dst_local)?, I64XorRotlConst(c) => stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)), I64XorRotlConstTee(c, local_index) => { stack_op!(binary i64, |lhs, rhs| (lhs ^ rhs).rotate_left(*c as u32)); @@ -323,6 +308,7 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { // Bulk memory operations MemoryCopy(from, to) => self.exec_memory_copy(*from, *to)?, MemoryFill(addr) => self.exec_memory_fill(*addr)?, + MemoryFillImm(addr, val, size) => self.exec_memory_fill_imm(*addr, *val, *size)?, MemoryInit(data_idx, mem_idx) => self.exec_memory_init(*data_idx, *mem_idx)?, DataDrop(data_index) => self.store.state.get_data_mut(self.module.resolve_data_addr(*data_index)).drop(), ElemDrop(elem_index) => self.store.state.get_elem_mut(self.module.resolve_elem_addr(*elem_index)).drop(), @@ -700,6 +686,15 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { false } + #[inline(always)] + fn exec_jump_if_non_zero(&mut self, ip: u32) -> bool { + if self.store.stack.values.pop::() != 0 { + self.cf.instr_ptr = ip; + return true; + } + false + } + #[inline(always)] fn exec_branch_table(&mut self, default_ip: u32, len: u32) { let idx = self.store.stack.values.pop::(); @@ -844,6 +839,52 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { self.cf = cf; false } + + #[inline(always)] + fn local_mem_addr(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let Some(Ok(addr)) = memarg.offset().checked_add(addr).map(|a| a.try_into()) else { + return Err(Error::Trap(Trap::MemoryOutOfBounds { offset: addr as usize, len: N, max: 0 })); + }; + Ok(addr) + } + + #[inline(always)] + fn exec_store_local_local, const N: usize>( + &mut self, + memarg: MemoryArg, + addr_local: u8, + value_local: u8, + cast: fn(T) -> U, + ) -> Result<()> { + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(memarg.mem_addr())); + let addr = u64::from(self.store.stack.values.local_get::(&self.cf, u16::from(addr_local))); + let value = cast(self.store.stack.values.local_get::(&self.cf, u16::from(value_local))).to_mem_bytes(); + mem.store((memarg.offset() + addr) as usize, value.len(), &value)?; + Ok(()) + } + + #[inline(always)] + fn exec_i32_load_local_value(&self, memarg: MemoryArg, addr_local: u8) -> Result { + let mem = self.store.state.get_mem(self.module.resolve_mem_addr(memarg.mem_addr())); + mem.load_as::<4, i32>(self.local_mem_addr::<4>(memarg, addr_local)?) + } + + #[inline(always)] + fn exec_i32_load_local_tee(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + self.store.stack.values.push(value)?; + Ok(()) + } + + #[inline(always)] + fn exec_i32_load_local_set(&mut self, memarg: MemoryArg, addr_local: u8, dst_local: u8) -> Result<()> { + let value = self.exec_i32_load_local_value(memarg, addr_local)?; + self.store.stack.values.local_set(&self.cf, u16::from(dst_local), value); + Ok(()) + } + fn exec_global_get(&mut self, global_index: u32) -> Result<()> { self.store.stack.values.push_dyn(self.store.state.get_global_val(self.module.resolve_global_addr(global_index))) } @@ -910,6 +951,13 @@ impl<'store, const BUDGETED: bool> Executor<'store, BUDGETED> { let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); mem.fill(dst as usize, size as usize, val as u8) } + + fn exec_memory_fill_imm(&mut self, addr: u32, val: u8, size: i32) -> Result<()> { + let dst: i32 = self.store.stack.values.pop(); + let mem = self.store.state.get_mem_mut(self.module.resolve_mem_addr(addr)); + mem.fill(dst as usize, size as usize, val) + } + fn exec_memory_init(&mut self, data_index: u32, mem_index: u32) -> Result<()> { let size: i32 = self.store.stack.values.pop(); let offset: i32 = self.store.stack.values.pop(); diff --git a/crates/tinywasm/src/interpreter/stack/value_stack.rs b/crates/tinywasm/src/interpreter/stack/value_stack.rs index 1ec20fe..87d8452 100644 --- a/crates/tinywasm/src/interpreter/stack/value_stack.rs +++ b/crates/tinywasm/src/interpreter/stack/value_stack.rs @@ -82,6 +82,14 @@ impl Stack { } } + #[inline(always)] + pub(crate) fn get_mut(&mut self, index: usize) -> &mut T { + match self.data.get_mut(index) { + Some(v) => v, + None => unreachable!("Stack index out of bounds, this is a bug"), + } + } + pub(crate) fn truncate_keep(&mut self, n: usize, end_keep: usize) { debug_assert!(n <= self.len); let len = self.len; @@ -294,6 +302,16 @@ impl ValueStack { T::local_get(self, frame, index) } + #[inline] + pub(crate) fn local_update( + &mut self, + frame: &CallFrame, + index: LocalAddr, + func: impl FnOnce(&mut T), + ) { + T::local_update(self, frame, index, func) + } + #[inline] pub(crate) fn local_set(&mut self, frame: &CallFrame, index: LocalAddr, value: T) { T::local_set(self, frame, index, value); diff --git a/crates/tinywasm/src/interpreter/values.rs b/crates/tinywasm/src/interpreter/values.rs index a127cf6..48b688c 100644 --- a/crates/tinywasm/src/interpreter/values.rs +++ b/crates/tinywasm/src/interpreter/values.rs @@ -107,6 +107,7 @@ mod sealed { pub(crate) trait InternalValue: sealed::Sealed + Into + Copy + Default { fn stack_push(stack: &mut ValueStack, value: Self) -> Result<()>; fn local_get(stack: &ValueStack, frame: &CallFrame, index: LocalAddr) -> Self; + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)); fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self); fn stack_pop(stack: &mut ValueStack) -> Self; fn stack_peek(stack: &ValueStack) -> Self; @@ -137,6 +138,14 @@ macro_rules! impl_internalvalue { $to_outer(stack.$stack.get(frame.locals_base.$stack_base as usize + index as usize)) } + #[inline(always)] + fn local_update(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, func: impl FnOnce(&mut Self)) { + let slot = stack.$stack.get_mut(frame.locals_base.$stack_base as usize + index as usize); + let mut value = $to_outer(*slot); + func(&mut value); + *slot = $to_internal(value); + } + #[inline(always)] fn local_set(stack: &mut ValueStack, frame: &CallFrame, index: LocalAddr, value: Self) { stack.$stack.set(frame.locals_base.$stack_base as usize + index as usize, $to_internal(value)); diff --git a/crates/types/src/instructions.rs b/crates/types/src/instructions.rs index bccbdd1..dedebc5 100644 --- a/crates/types/src/instructions.rs +++ b/crates/types/src/instructions.rs @@ -56,16 +56,22 @@ pub enum Instruction { LocalCopy32(LocalAddr, LocalAddr), LocalCopy64(LocalAddr, LocalAddr), LocalCopy128(LocalAddr, LocalAddr), LocalCopyRef(LocalAddr, LocalAddr), I32AddLocals(LocalAddr, LocalAddr), I64AddLocals(LocalAddr, LocalAddr), I32AddConst(i32), I64AddConst(i64), + LocalAddConst32(LocalAddr, i32), LocalAddConst64(LocalAddr, i64), + LocalSetConst32(LocalAddr, i32), LocalSetConst64(LocalAddr, i64), I32StoreLocalLocal(MemoryArg, u8, u8), + I64StoreLocalLocal(MemoryArg, u8, u8), I32LoadLocalTee(MemoryArg, u8, u8), + I32LoadLocalSet(MemoryArg, u8, u8), I64XorRotlConst(i64), I64XorRotlConstTee(i64, LocalAddr), + // > Control Instructions (jump-oriented, lowered from structured control during parsing) // See Unreachable, Nop, Jump(u32), JumpIfZero(u32), + JumpIfNonZero(u32), DropKeepSmall { base32: u8, keep32: u8, base64: u8, keep64: u8, base128: u8, keep128: u8, base_ref: u8, keep_ref: u8 }, DropKeep32(u16, u16), DropKeep64(u16, u16), @@ -177,6 +183,7 @@ pub enum Instruction { MemoryInit(MemAddr, DataAddr), MemoryCopy(MemAddr, MemAddr), MemoryFill(MemAddr), + MemoryFillImm(MemAddr, u8, i32), DataDrop(DataAddr), ElemDrop(ElemAddr), -- cgit v1.3.1