diff --git a/crates/fuzz-stats/src/bin/failed-instantiations.rs b/crates/fuzz-stats/src/bin/failed-instantiations.rs index 254cf39e11..1799ea4180 100644 --- a/crates/fuzz-stats/src/bin/failed-instantiations.rs +++ b/crates/fuzz-stats/src/bin/failed-instantiations.rs @@ -118,7 +118,7 @@ impl State { let module = Module::new(&self.engine, &wasm).expect("failed to compile module"); let mut store = Store::new( &self.engine, - StoreLimits { + fuzz_stats::limits::StoreLimits { remaining_memory: 1 << 30, oom: false, }, @@ -181,34 +181,3 @@ impl State { println!(); } } - -struct StoreLimits { - remaining_memory: usize, - oom: bool, -} - -impl StoreLimits { - fn alloc(&mut self, amt: usize) -> bool { - match self.remaining_memory.checked_sub(amt) { - Some(mem) => { - self.remaining_memory = mem; - true - } - None => { - self.oom = true; - false - } - } - } -} - -impl ResourceLimiter for StoreLimits { - fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option) -> bool { - self.alloc(desired - current) - } - - fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option) -> bool { - let delta = (desired - current) as usize * std::mem::size_of::(); - self.alloc(delta) - } -} diff --git a/crates/fuzz-stats/src/lib.rs b/crates/fuzz-stats/src/lib.rs index 61147493ad..21f063fa76 100644 --- a/crates/fuzz-stats/src/lib.rs +++ b/crates/fuzz-stats/src/lib.rs @@ -1 +1,2 @@ pub mod dummy; +pub mod limits; diff --git a/crates/fuzz-stats/src/limits.rs b/crates/fuzz-stats/src/limits.rs new file mode 100644 index 0000000000..92797e4c22 --- /dev/null +++ b/crates/fuzz-stats/src/limits.rs @@ -0,0 +1,31 @@ +use wasmtime::*; +pub struct StoreLimits { + pub remaining_memory: usize, + pub oom: bool, +} + +impl StoreLimits { + fn alloc(&mut self, amt: usize) -> bool { + match self.remaining_memory.checked_sub(amt) { + Some(mem) => { + self.remaining_memory = mem; + true + } + None => { + self.oom = true; + false + } + } + } +} + +impl ResourceLimiter for StoreLimits { + fn memory_growing(&mut self, current: usize, desired: usize, _maximum: Option) -> bool { + self.alloc(desired - current) + } + + fn table_growing(&mut self, current: u32, desired: u32, _maximum: Option) -> bool { + let delta = (desired - current) as usize * std::mem::size_of::(); + self.alloc(delta) + } +} diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 70123716bb..35f7429446 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -449,6 +449,25 @@ pub trait Config: 'static + std::fmt::Debug { fn threads_enabled(&self) -> bool { false } + + /// Returns whether we should avoid generating code that will possibly trap. + /// + /// For some trapping instructions, this will emit extra instructions to + /// ensure they don't trap, while some instructions will simply be excluded. + /// In cases where we would run into a trap, we instead choose some + /// arbitrary non-trapping behavior. For example, if we detect that a Load + /// instruction would attempt to access out-of-bounds memory, we instead + /// pretend the load succeeded and push 0 onto the stack. + /// + /// One type of trap that we can't currently avoid is StackOverflow. Even + /// when `disallow_traps` is set to true, wasm-smith will eventually + /// generate a program that infinitely recurses, causing the call stack to + /// be exhausted. + /// + /// Defaults to `false`. + fn disallow_traps(&self) -> bool { + false + } } /// The default configuration. @@ -476,6 +495,7 @@ pub struct SwarmConfig { pub available_imports: Option>, pub bulk_memory_enabled: bool, pub canonicalize_nans: bool, + pub disallow_traps: bool, pub exceptions_enabled: bool, pub export_everything: bool, pub max_aliases: usize, @@ -597,6 +617,7 @@ impl<'a> Arbitrary<'a> for SwarmConfig { available_imports: None, threads_enabled: false, export_everything: false, + disallow_traps: false, }) } } @@ -795,4 +816,8 @@ impl Config for SwarmConfig { fn table_max_size_required(&self) -> bool { self.table_max_size_required } + + fn disallow_traps(&self) -> bool { + self.disallow_traps + } } diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 89cad1e429..a1d81a238a 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -2,7 +2,6 @@ mod code_builder; pub(crate) mod encode; -pub(crate) mod no_traps; mod terminate; use crate::{arbitrary_loop, limited_string, unique_string, Config, DefaultConfig}; @@ -1022,26 +1021,39 @@ impl Module { // Create a helper closure to choose an arbitrary offset. let mut offset_global_choices = vec![]; - for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()] - .iter() - .enumerate() - { - if !g.mutable && g.val_type == ValType::I32 { - offset_global_choices.push(i as u32); + if !self.config.disallow_traps() { + for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()] + .iter() + .enumerate() + { + if !g.mutable && g.val_type == ValType::I32 { + offset_global_choices.push(i as u32); + } } } - let arbitrary_active_elem = |u: &mut Unstructured, min: u32, table: Option| { + let arbitrary_active_elem = |u: &mut Unstructured, + min_mem_size: u32, + table: Option, + disallow_traps: bool, + table_ty: &TableType| { let (offset, max_size_hint) = if !offset_global_choices.is_empty() && u.arbitrary()? { let g = u.choose(&offset_global_choices)?; (Offset::Global(*g), None) } else { - let offset = arbitrary_offset(u, min.into(), u32::MAX.into(), 0)? as u32; - let max_size_hint = - if offset <= min && u.int_in_range(0..=CHANCE_OFFSET_INBOUNDS)? != 0 { - Some(min - offset) - } else { - None - }; + let max_mem_size = if disallow_traps { + table_ty.minimum + } else { + u32::MAX + }; + let offset = + arbitrary_offset(u, min_mem_size.into(), max_mem_size.into(), 0)? as u32; + let max_size_hint = if disallow_traps + || (offset <= min_mem_size && u.int_in_range(0..=CHANCE_OFFSET_INBOUNDS)? != 0) + { + Some(min_mem_size - offset) + } else { + None + }; (Offset::Const32(offset as i32), max_size_hint) }; Ok((ElementKind::Active { table, offset }, max_size_hint)) @@ -1051,7 +1063,7 @@ impl Module { dyn Fn(&mut Unstructured) -> Result<(ElementKind, Option)> + 'a; let mut funcrefs: Vec> = Vec::new(); let mut externrefs: Vec> = Vec::new(); - + let disallow_traps = self.config().disallow_traps(); for (i, ty) in self.tables.iter().enumerate() { // If this table starts with no capacity then any non-empty element // segment placed onto it will immediately trap, which isn't too @@ -1070,11 +1082,15 @@ impl Module { // If the first table is a funcref table then it's a candidate for // the MVP encoding of element segments. if i == 0 && ty.element_type == ValType::FuncRef { - dst.push(Box::new(move |u| arbitrary_active_elem(u, minimum, None))); + dst.push(Box::new(move |u| { + arbitrary_active_elem(u, minimum, None, disallow_traps, ty) + })); } if self.config.bulk_memory_enabled() { let idx = Some(i as u32); - dst.push(Box::new(move |u| arbitrary_active_elem(u, minimum, idx))); + dst.push(Box::new(move |u| { + arbitrary_active_elem(u, minimum, idx, disallow_traps, ty) + })); } } @@ -1197,41 +1213,40 @@ impl Module { if memories == 0 && !self.config.bulk_memory_enabled() { return Ok(()); } - + let disallow_traps = self.config.disallow_traps(); let mut choices32: Vec Result>> = vec![]; choices32.push(Box::new(|u, min_size, data_len| { - Ok(Offset::Const32(arbitrary_offset( - u, - u32::try_from(min_size.saturating_mul(64 * 1024)) - .unwrap_or(u32::MAX) - .into(), - u32::MAX.into(), - data_len, - )? as i32)) + let min = u32::try_from(min_size.saturating_mul(64 * 1024)) + .unwrap_or(u32::MAX) + .into(); + let max = if disallow_traps { min } else { u32::MAX.into() }; + Ok(Offset::Const32( + arbitrary_offset(u, min, max, data_len)? as i32 + )) })); let mut choices64: Vec Result>> = vec![]; choices64.push(Box::new(|u, min_size, data_len| { - Ok(Offset::Const64(arbitrary_offset( - u, - min_size.saturating_mul(64 * 1024), - u64::MAX, - data_len, - )? as i64)) + let min = min_size.saturating_mul(64 * 1024); + let max = if disallow_traps { min } else { u64::MAX }; + Ok(Offset::Const64( + arbitrary_offset(u, min, max, data_len)? as i64 + )) })); - - for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()] - .iter() - .enumerate() - { - if g.mutable { - continue; - } - if g.val_type == ValType::I32 { - choices32.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32)))); - } else if g.val_type == ValType::I64 { - choices64.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32)))); + if !self.config().disallow_traps() { + for (i, g) in self.globals[..self.globals.len() - self.defined_globals.len()] + .iter() + .enumerate() + { + if g.mutable { + continue; + } + if g.val_type == ValType::I32 { + choices32.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32)))); + } else if g.val_type == ValType::I64 { + choices64.push(Box::new(move |_, _, _| Ok(Offset::Global(i as u32)))); + } } } @@ -1277,7 +1292,24 @@ impl Module { } else { u.choose(&choices32)? }; - let offset = f(u, mem.minimum, init.len())?; + let mut offset = f(u, mem.minimum, init.len())?; + if self.config.disallow_traps() { + match &mut offset { + Offset::Const32(x) => { + let m = (mem.minimum * 64 * 1024) - init.len() as u64; + if m < i32::MAX as u64 { + *x = (*x).min(m as i32); + } + } + Offset::Const64(x) => { + let m = (mem.minimum * 64 * 1024) - init.len() as u64; + if m < i64::MAX as u64 { + *x = (*x).min(m as i64); + } + } + Offset::Global(_) => unreachable!(), + } + } DataSegmentKind::Active { offset, memory_index, @@ -1303,11 +1335,18 @@ impl Module { pub(crate) fn arbitrary_limits32( u: &mut Unstructured, + min_minimum: Option, max_minimum: u32, max_required: bool, max_inbounds: u32, ) -> Result<(u32, Option)> { - let (min, max) = arbitrary_limits64(u, max_minimum.into(), max_required, max_inbounds.into())?; + let (min, max) = arbitrary_limits64( + u, + min_minimum.map(Into::into), + max_minimum.into(), + max_required, + max_inbounds.into(), + )?; Ok(( u32::try_from(min).unwrap(), max.map(|i| u32::try_from(i).unwrap()), @@ -1316,11 +1355,12 @@ pub(crate) fn arbitrary_limits32( pub(crate) fn arbitrary_limits64( u: &mut Unstructured, + min_minimum: Option, max_minimum: u64, max_required: bool, max_inbounds: u64, ) -> Result<(u64, Option)> { - let min = gradually_grow(u, 0, max_inbounds, max_minimum)?; + let min = gradually_grow(u, min_minimum.unwrap_or(0), max_inbounds, max_minimum)?; let max = if max_required || u.arbitrary().unwrap_or(false) { Some(u.int_in_range(min..=max_minimum)?) } else { @@ -1371,13 +1411,22 @@ pub(crate) fn arbitrary_table_type(u: &mut Unstructured, config: &dyn Config) -> // We don't want to generate tables that are too large on average, so // keep the "inbounds" limit here a bit smaller. let max_inbounds = 10_000; - let max_elements = config.max_table_elements(); + let min_elements = if config.disallow_traps() { + Some(1) + } else { + None + }; + let max_elements = min_elements.unwrap_or(0).max(config.max_table_elements()); let (minimum, maximum) = arbitrary_limits32( u, + min_elements, max_elements, config.table_max_size_required(), max_inbounds.min(max_elements), )?; + if config.disallow_traps() { + assert!(minimum > 0); + } Ok(TableType { element_type: if config.reference_types_enabled() { *u.choose(&[ValType::FuncRef, ValType::ExternRef])? @@ -1397,9 +1446,17 @@ pub(crate) fn arbitrary_memtype(u: &mut Unstructured, config: &dyn Config) -> Re // depending on the maximum number of memories. let memory64 = config.memory64_enabled() && u.arbitrary()?; let max_inbounds = 16 * 1024 / u64::try_from(config.max_memories()).unwrap(); - let max_pages = config.max_memory_pages(memory64); + let min_pages = if config.disallow_traps() { + Some(1) + } else { + None + }; + let max_pages = min_pages + .unwrap_or(0) + .max(config.max_memory_pages(memory64)); let (minimum, maximum) = arbitrary_limits64( u, + min_pages, max_pages, config.memory_max_size_required() || shared, max_inbounds.min(max_pages), @@ -1538,15 +1595,20 @@ fn gradually_grow(u: &mut Unstructured, min: u64, max_inbounds: u64, max: u64) - /// Selects a reasonable offset for an element or data segment. This favors /// having the segment being in-bounds, but it may still generate /// any offset. -fn arbitrary_offset(u: &mut Unstructured, min: u64, max: u64, size: usize) -> Result { - let size = u64::try_from(size).unwrap(); +fn arbitrary_offset( + u: &mut Unstructured, + limit_min: u64, + limit_max: u64, + segment_size: usize, +) -> Result { + let size = u64::try_from(segment_size).unwrap(); // If the segment is too big for the whole memory, just give it any // offset. - if size > min { - u.int_in_range(0..=max) + if size > limit_min { + u.int_in_range(0..=limit_max) } else { - gradually_grow(u, 0, min - size, max) + gradually_grow(u, 0, limit_min - size, limit_max) } } diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 74bdcf788f..0b56b61bd5 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -6,6 +6,7 @@ use arbitrary::{Result, Unstructured}; use std::collections::{BTreeMap, BTreeSet}; use std::convert::TryFrom; use wasm_encoder::{BlockType, MemArg}; +mod no_traps; macro_rules! instructions { ( @@ -24,7 +25,7 @@ macro_rules! instructions { allowed_instructions: InstructionKinds, builder: &mut CodeBuilder, ) -> Option< - fn(&mut Unstructured<'_>, &Module, &mut CodeBuilder) -> Result + fn(&mut Unstructured<'_>, &Module, &mut CodeBuilder, &mut Vec) -> Result<()> > { builder.allocs.options.clear(); let mut cost = 0; @@ -87,7 +88,7 @@ macro_rules! instructions { // less than 1000. instructions! { // Control instructions. - (None, unreachable, Control, 990), + (Some(unreachable_valid), unreachable, Control, 990), (None, nop, Control, 800), (None, block, Control), (None, r#loop, Control), @@ -313,14 +314,14 @@ instructions! { (Some(simd_have_memory_and_offset), v128_load32_zero, Vector), (Some(simd_have_memory_and_offset), v128_load64_zero, Vector), (Some(simd_v128_store_valid), v128_store, Vector), - (Some(simd_have_memory_and_offset_and_v128), v128_load8_lane, Vector), - (Some(simd_have_memory_and_offset_and_v128), v128_load16_lane, Vector), - (Some(simd_have_memory_and_offset_and_v128), v128_load32_lane, Vector), - (Some(simd_have_memory_and_offset_and_v128), v128_load64_lane, Vector), - (Some(simd_v128_store_valid), v128_store8_lane, Vector), - (Some(simd_v128_store_valid), v128_store16_lane, Vector), - (Some(simd_v128_store_valid), v128_store32_lane, Vector), - (Some(simd_v128_store_valid), v128_store64_lane, Vector), + (Some(simd_load_lane_valid), v128_load8_lane, Vector), + (Some(simd_load_lane_valid), v128_load16_lane, Vector), + (Some(simd_load_lane_valid), v128_load32_lane, Vector), + (Some(simd_load_lane_valid), v128_load64_lane, Vector), + (Some(simd_store_lane_valid), v128_store8_lane, Vector), + (Some(simd_store_lane_valid), v128_store16_lane, Vector), + (Some(simd_store_lane_valid), v128_store32_lane, Vector), + (Some(simd_store_lane_valid), v128_store64_lane, Vector), (Some(simd_enabled), v128_const, Vector), (Some(simd_v128_v128_on_stack), i8x16_shuffle, Vector), (Some(simd_v128_on_stack), i8x16_extract_lane_s, Vector), @@ -568,7 +569,7 @@ pub(crate) struct CodeBuilderAllocations { // Dynamic set of options of instruction we can generate that are known to // be valid right now. options: Vec<( - fn(&mut Unstructured, &Module, &mut CodeBuilder) -> Result, + fn(&mut Unstructured, &Module, &mut CodeBuilder, &mut Vec) -> Result<()>, u32, )>, @@ -843,21 +844,28 @@ impl CodeBuilder<'_> { let keep_going = instructions.len() < max_instructions && u.arbitrary().map_or(false, |b: u8| b != 0); if !keep_going { - self.end_active_control_frames(u, &mut instructions); + self.end_active_control_frames( + u, + &mut instructions, + module.config.disallow_traps(), + ); break; } match choose_instruction(u, module, allowed_instructions, &mut self) { Some(f) => { - let inst = f(u, module, &mut self)?; - instructions.push(inst); + f(u, module, &mut self, &mut instructions)?; } // Choosing an instruction can fail because there is not enough // underlying data, so we really cannot generate any more // instructions. In this case we swallow that error and instead // just terminate our wasm function's frames. None => { - self.end_active_control_frames(u, &mut instructions); + self.end_active_control_frames( + u, + &mut instructions, + module.config.disallow_traps(), + ); break; } } @@ -932,25 +940,15 @@ impl CodeBuilder<'_> { // We'll need to temporarily save the top of the stack into a local, so // figure out that local here. Note that this tries to use the same // local if canonicalization happens more than once in a function. - let local = match ty { - Float::F32 => &mut self.f32_scratch, - Float::F64 => &mut self.f64_scratch, - Float::F32x4 | Float::F64x2 => &mut self.v128_scratch, + let (local, val_ty) = match ty { + Float::F32 => (&mut self.f32_scratch, ValType::F32), + Float::F64 => (&mut self.f64_scratch, ValType::F64), + Float::F32x4 | Float::F64x2 => (&mut self.v128_scratch, ValType::V128), }; let local = match *local { - Some(i) => i, - None => { - let val = self.locals.len() + self.func_ty.params.len() + self.extra_locals.len(); - *local = Some(val); - self.extra_locals.push(match ty { - Float::F32 => ValType::F32, - Float::F64 => ValType::F64, - Float::F32x4 | Float::F64x2 => ValType::V128, - }); - val - } + Some(i) => i as u32, + None => self.alloc_local(val_ty), }; - let local = local as u32; // Save the previous instruction's result into a local. This also leaves // a value on the stack as `val1` for the `select` instruction. @@ -1000,15 +998,22 @@ impl CodeBuilder<'_> { }); } + fn alloc_local(&mut self, ty: ValType) -> u32 { + let val = self.locals.len() + self.func_ty.params.len() + self.extra_locals.len(); + self.extra_locals.push(ty); + u32::try_from(val).unwrap() + } + fn end_active_control_frames( &mut self, u: &mut Unstructured<'_>, instructions: &mut Vec, + disallow_traps: bool, ) { while !self.allocs.controls.is_empty() { // Ensure that this label is valid by placing the right types onto // the operand stack for the end of the label. - self.guarantee_label_results(u, instructions); + self.guarantee_label_results(u, instructions, disallow_traps); // Remove the label and clear the operand stack since the label has // been removed. @@ -1024,7 +1029,7 @@ impl CodeBuilder<'_> { self.allocs .operands .extend(label.params.into_iter().map(Some)); - self.guarantee_label_results(u, instructions); + self.guarantee_label_results(u, instructions, disallow_traps); self.allocs.controls.pop(); self.allocs.operands.truncate(label.height); } @@ -1049,6 +1054,7 @@ impl CodeBuilder<'_> { &mut self, u: &mut Unstructured<'_>, instructions: &mut Vec, + disallow_traps: bool, ) { let mut operands = self.operands(); let label = self.allocs.controls.last().unwrap(); @@ -1061,7 +1067,7 @@ impl CodeBuilder<'_> { // Generating an unreachable instruction is always a valid way to // generate any types for a label, but it's not too interesting, so // don't favor it. - if u.arbitrary::().unwrap_or(0) == 1 { + if u.arbitrary::().unwrap_or(0) == 1 && !disallow_traps { instructions.push(Instruction::Unreachable); return; } @@ -1103,15 +1109,37 @@ fn arbitrary_val(ty: ValType, u: &mut Unstructured<'_>) -> Instruction { } } -fn unreachable(_: &mut Unstructured, _: &Module, _: &mut CodeBuilder) -> Result { - Ok(Instruction::Unreachable) +#[inline] +fn unreachable_valid(module: &Module, _: &mut CodeBuilder) -> bool { + !module.config.disallow_traps() +} + +fn unreachable( + _: &mut Unstructured, + _: &Module, + _: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { + instructions.push(Instruction::Unreachable); + Ok(()) } -fn nop(_: &mut Unstructured, _: &Module, _: &mut CodeBuilder) -> Result { - Ok(Instruction::Nop) +fn nop( + _: &mut Unstructured, + _: &Module, + _: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { + instructions.push(Instruction::Nop); + Ok(()) } -fn block(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn block( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let block_ty = builder.arbitrary_block_type(u, module)?; let (params, results) = module.params_results(&block_ty); let height = builder.allocs.operands.len() - params.len(); @@ -1121,7 +1149,8 @@ fn block(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Re results, height, }); - Ok(Instruction::Block(block_ty)) + instructions.push(Instruction::Block(block_ty)); + Ok(()) } #[inline] @@ -1129,7 +1158,12 @@ fn try_valid(module: &Module, _: &mut CodeBuilder) -> bool { module.config.exceptions_enabled() } -fn r#try(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn r#try( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let block_ty = builder.arbitrary_block_type(u, module)?; let (params, results) = module.params_results(&block_ty); let height = builder.allocs.operands.len() - params.len(); @@ -1139,7 +1173,8 @@ fn r#try(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Re results, height, }); - Ok(Instruction::Try(block_ty)) + instructions.push(Instruction::Try(block_ty)); + Ok(()) } #[inline] @@ -1151,7 +1186,12 @@ fn delegate_valid(module: &Module, builder: &mut CodeBuilder) -> bool { && end_valid(module, builder) } -fn delegate(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn delegate( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { // There will always be at least the function's return frame and try // control frame if we are emitting delegate let n = builder.allocs.controls.iter().count(); @@ -1162,7 +1202,8 @@ fn delegate(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Resu let target_relative_from_outer = target_relative_from_last - 1; // Delegate ends the try block builder.allocs.controls.pop(); - Ok(Instruction::Delegate(target_relative_from_outer as u32)) + instructions.push(Instruction::Delegate(target_relative_from_outer as u32)); + Ok(()) } #[inline] @@ -1176,7 +1217,12 @@ fn catch_valid(module: &Module, builder: &mut CodeBuilder) -> bool { && module.tags.len() > 0 } -fn catch(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn catch( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let tag_idx = u.int_in_range(0..=(module.tags.len() - 1))?; let tag_type = &module.tags[tag_idx]; let control = builder.allocs.controls.pop().unwrap(); @@ -1188,7 +1234,8 @@ fn catch(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Re kind: ControlKind::Catch, ..control }); - Ok(Instruction::Catch(tag_idx as u32)) + instructions.push(Instruction::Catch(tag_idx as u32)); + Ok(()) } #[inline] @@ -1201,7 +1248,12 @@ fn catch_all_valid(module: &Module, builder: &mut CodeBuilder) -> bool { && end_valid(module, builder) } -fn catch_all(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn catch_all( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let control = builder.allocs.controls.pop().unwrap(); // Pop the results for the previous try or catch builder.pop_operands(&control.results); @@ -1209,10 +1261,16 @@ fn catch_all(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Res kind: ControlKind::CatchAll, ..control }); - Ok(Instruction::CatchAll) + instructions.push(Instruction::CatchAll); + Ok(()) } -fn r#loop(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn r#loop( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let block_ty = builder.arbitrary_block_type(u, module)?; let (params, results) = module.params_results(&block_ty); let height = builder.allocs.operands.len() - params.len(); @@ -1222,7 +1280,8 @@ fn r#loop(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> R results, height, }); - Ok(Instruction::Loop(block_ty)) + instructions.push(Instruction::Loop(block_ty)); + Ok(()) } #[inline] @@ -1230,7 +1289,12 @@ fn if_valid(_: &Module, builder: &mut CodeBuilder) -> bool { builder.type_on_stack(ValType::I32) } -fn r#if(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn r#if( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let block_ty = builder.arbitrary_block_type(u, module)?; @@ -1242,7 +1306,8 @@ fn r#if(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Res results, height, }); - Ok(Instruction::If(block_ty)) + instructions.push(Instruction::If(block_ty)); + Ok(()) } #[inline] @@ -1253,7 +1318,12 @@ fn else_valid(_: &Module, builder: &mut CodeBuilder) -> bool { && builder.types_on_stack(&last_control.results) } -fn r#else(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn r#else( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let control = builder.allocs.controls.pop().unwrap(); builder.pop_operands(&control.results); builder.push_operands(&control.params); @@ -1261,7 +1331,8 @@ fn r#else(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result kind: ControlKind::Block, ..control }); - Ok(Instruction::Else) + instructions.push(Instruction::Else); + Ok(()) } #[inline] @@ -1279,9 +1350,15 @@ fn end_valid(_: &Module, builder: &mut CodeBuilder) -> bool { && !(control.kind == ControlKind::If && control.params != control.results) } -fn end(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn end( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.allocs.controls.pop(); - Ok(Instruction::End) + instructions.push(Instruction::End); + Ok(()) } #[inline] @@ -1293,7 +1370,12 @@ fn br_valid(_: &Module, builder: &mut CodeBuilder) -> bool { .any(|l| builder.label_types_on_stack(l)) } -fn br(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn br( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let n = builder .allocs .controls @@ -1314,7 +1396,8 @@ fn br(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result bool { is_valid } -fn br_if(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn br_if( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let n = builder @@ -1352,7 +1440,8 @@ fn br_if(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result< .filter(|(_, l)| builder.label_types_on_stack(l)) .nth(i) .unwrap(); - Ok(Instruction::BrIf(target as u32)) + instructions.push(Instruction::BrIf(target as u32)); + Ok(()) } #[inline] @@ -1366,7 +1455,12 @@ fn br_table_valid(module: &Module, builder: &mut CodeBuilder) -> bool { is_valid } -fn br_table(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn br_table( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let n = builder @@ -1402,7 +1496,8 @@ fn br_table(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Resu let tys = control.label_types().to_vec(); builder.pop_operands(&tys); - Ok(Instruction::BrTable(targets, default_target as u32)) + instructions.push(Instruction::BrTable(targets, default_target as u32)); + Ok(()) } #[inline] @@ -1410,10 +1505,16 @@ fn return_valid(_: &Module, builder: &mut CodeBuilder) -> bool { builder.label_types_on_stack(&builder.allocs.controls[0]) } -fn r#return(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn r#return( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let results = builder.allocs.controls[0].results.clone(); builder.pop_operands(&results); - Ok(Instruction::Return) + instructions.push(Instruction::Return); + Ok(()) } #[inline] @@ -1425,7 +1526,12 @@ fn call_valid(_: &Module, builder: &mut CodeBuilder) -> bool { .any(|k| builder.types_on_stack(k)) } -fn call(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn call( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let candidates = builder .allocs .functions @@ -1438,7 +1544,8 @@ fn call(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Res let (func_idx, ty) = module.funcs().nth(candidates[i] as usize).unwrap(); builder.pop_operands(&ty.params); builder.push_operands(&ty.results); - Ok(Instruction::Call(func_idx as u32)) + instructions.push(Instruction::Call(func_idx as u32)); + Ok(()) } #[inline] @@ -1446,6 +1553,13 @@ fn call_indirect_valid(module: &Module, builder: &mut CodeBuilder) -> bool { if builder.allocs.funcref_tables.is_empty() || !builder.type_on_stack(ValType::I32) { return false; } + if module.config.disallow_traps() { + // We have no way to reflect, at run time, on a `funcref` in + // the `i`th slot in a table and dynamically avoid trapping + // `call_indirect`s. Therefore, we can't emit *any* + // `call_indirect` instructions if we want to avoid traps. + return false; + } let ty = builder.allocs.operands.pop().unwrap(); let is_valid = module .func_types() @@ -1458,7 +1572,8 @@ fn call_indirect( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let choices = module @@ -1469,10 +1584,11 @@ fn call_indirect( builder.pop_operands(&ty.params); builder.push_operands(&ty.results); let table = *u.choose(&builder.allocs.funcref_tables)?; - Ok(Instruction::CallIndirect { + instructions.push(Instruction::CallIndirect { ty: *type_idx as u32, table, - }) + }); + Ok(()) } #[inline] @@ -1485,7 +1601,12 @@ fn throw_valid(module: &Module, builder: &mut CodeBuilder) -> bool { .any(|k| builder.types_on_stack(k)) } -fn throw(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Result { +fn throw( + u: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let candidates = builder .allocs .tags @@ -1499,7 +1620,8 @@ fn throw(u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder) -> Re // Tags have no results, throwing cannot return assert!(tag_type.func_type.results.len() == 0); builder.pop_operands(&tag_type.func_type.params); - Ok(Instruction::Throw(tag_idx as u32)) + instructions.push(Instruction::Throw(tag_idx as u32)); + Ok(()) } #[inline] @@ -1513,7 +1635,12 @@ fn rethrow_valid(module: &Module, builder: &mut CodeBuilder) -> bool { .any(|l| l.kind == ControlKind::Catch || l.kind == ControlKind::CatchAll) } -fn rethrow(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn rethrow( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let n = builder .allocs .controls @@ -1531,7 +1658,8 @@ fn rethrow(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Resul .filter(|(_, l)| l.kind == ControlKind::Catch || l.kind == ControlKind::CatchAll) .nth(i) .unwrap(); - Ok(Instruction::Rethrow(target as u32)) + instructions.push(Instruction::Rethrow(target as u32)); + Ok(()) } #[inline] @@ -1539,9 +1667,15 @@ fn drop_valid(_: &Module, builder: &mut CodeBuilder) -> bool { !builder.operands().is_empty() } -fn drop(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn drop( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.allocs.operands.pop(); - Ok(Instruction::Drop) + instructions.push(Instruction::Drop); + Ok(()) } #[inline] @@ -1554,7 +1688,12 @@ fn select_valid(_: &Module, builder: &mut CodeBuilder) -> bool { t.is_none() || u.is_none() || t == u } -fn select(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn select( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.allocs.operands.pop(); let t = builder.allocs.operands.pop().unwrap(); let u = builder.allocs.operands.pop().unwrap(); @@ -1562,11 +1701,12 @@ fn select(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result builder.allocs.operands.push(ty); match ty { Some(ty @ ValType::ExternRef) | Some(ty @ ValType::FuncRef) => { - Ok(Instruction::TypedSelect(ty)) + instructions.push(Instruction::TypedSelect(ty)) } Some(ValType::I32) | Some(ValType::I64) | Some(ValType::F32) | Some(ValType::F64) - | Some(ValType::V128) | None => Ok(Instruction::Select), + | Some(ValType::V128) | None => instructions.push(Instruction::Select), } + Ok(()) } #[inline] @@ -1574,7 +1714,12 @@ fn local_get_valid(_: &Module, builder: &mut CodeBuilder) -> bool { !builder.func_ty.params.is_empty() || !builder.locals.is_empty() } -fn local_get(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn local_get( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let num_params = builder.func_ty.params.len(); let n = num_params + builder.locals.len(); debug_assert!(n > 0); @@ -1584,7 +1729,8 @@ fn local_get(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Res } else { builder.locals[i - num_params] })); - Ok(Instruction::LocalGet(i as u32)) + instructions.push(Instruction::LocalGet(i as u32)); + Ok(()) } #[inline] @@ -1597,7 +1743,12 @@ fn local_set_valid(_: &Module, builder: &mut CodeBuilder) -> bool { .any(|ty| builder.type_on_stack(*ty)) } -fn local_set(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn local_set( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let n = builder .func_ty .params @@ -1617,10 +1768,16 @@ fn local_set(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Res .nth(i) .unwrap(); builder.allocs.operands.pop(); - Ok(Instruction::LocalSet(j as u32)) + instructions.push(Instruction::LocalSet(j as u32)); + Ok(()) } -fn local_tee(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn local_tee( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let n = builder .func_ty .params @@ -1639,7 +1796,8 @@ fn local_tee(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Res .filter(|(_, ty)| builder.type_on_stack(**ty)) .nth(i) .unwrap(); - Ok(Instruction::LocalTee(j as u32)) + instructions.push(Instruction::LocalTee(j as u32)); + Ok(()) } #[inline] @@ -1651,14 +1809,16 @@ fn global_get( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { debug_assert!(module.globals.len() > 0); let global_idx = u.int_in_range(0..=module.globals.len() - 1)?; builder .allocs .operands .push(Some(module.globals[global_idx].val_type)); - Ok(Instruction::GlobalGet(global_idx as u32)) + instructions.push(Instruction::GlobalGet(global_idx as u32)); + Ok(()) } #[inline] @@ -1670,7 +1830,12 @@ fn global_set_valid(_: &Module, builder: &mut CodeBuilder) -> bool { .any(|(ty, _)| builder.type_on_stack(*ty)) } -fn global_set(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn global_set( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let candidates = builder .allocs .mutable_globals @@ -1680,7 +1845,8 @@ fn global_set(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Re .1; let i = u.int_in_range(0..=candidates.len() - 1)?; builder.allocs.operands.pop(); - Ok(Instruction::GlobalSet(candidates[i])) + instructions.push(Instruction::GlobalSet(candidates[i])); + Ok(()) } #[inline] @@ -1703,140 +1869,274 @@ fn i32_load( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I32)); - Ok(Instruction::I32Load(memarg)) + if module.config.disallow_traps() { + no_traps::load(Instruction::I32Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I32Load(memarg)); + } + Ok(()) } fn i64_load( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load(memarg)) + if module.config.disallow_traps() { + no_traps::load(Instruction::I64Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I64Load(memarg)); + } + Ok(()) } fn f32_load( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::F32)); - Ok(Instruction::F32Load(memarg)) + if module.config.disallow_traps() { + no_traps::load(Instruction::F32Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F32Load(memarg)); + } + Ok(()) } fn f64_load( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::F64)); - Ok(Instruction::F64Load(memarg)) + if module.config.disallow_traps() { + no_traps::load(Instruction::F64Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F64Load(memarg)); + } + Ok(()) } fn i32_load_8_s( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); - Ok(Instruction::I32Load8S(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load8S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load8S(memarg)); + } + Ok(()) } fn i32_load_8_u( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); - Ok(Instruction::I32Load8U(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load8U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load8U(memarg)); + } + Ok(()) } fn i32_load_16_s( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); - Ok(Instruction::I32Load16S(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load16S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load16S(memarg)); + } + Ok(()) } fn i32_load_16_u( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); - Ok(Instruction::I32Load16U(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load16U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load16U(memarg)); + } + Ok(()) } fn i64_load_8_s( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load8S(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load8S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load8S(memarg)); + } + Ok(()) } fn i64_load_16_s( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load16S(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load16S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load16S(memarg)); + } + Ok(()) } fn i64_load_32_s( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load32S(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load32S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load32S(memarg)); + } + Ok(()) } fn i64_load_8_u( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load8U(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load8U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load8U(memarg)); + } + Ok(()) } fn i64_load_16_u( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load16U(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load16U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load16U(memarg)); + } + Ok(()) } fn i64_load_32_u( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); - Ok(Instruction::I64Load32U(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load32U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load32U(memarg)); + } + Ok(()) } #[inline] @@ -1854,10 +2154,16 @@ fn i32_store( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - Ok(Instruction::I32Store(memarg)) + if module.config.disallow_traps() { + no_traps::store(Instruction::I32Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I32Store(memarg)); + } + Ok(()) } #[inline] @@ -1869,10 +2175,16 @@ fn i64_store( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; - Ok(Instruction::I64Store(memarg)) + if module.config.disallow_traps() { + no_traps::store(Instruction::I64Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I64Store(memarg)); + } + Ok(()) } #[inline] @@ -1884,10 +2196,16 @@ fn f32_store( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - Ok(Instruction::F32Store(memarg)) + if module.config.disallow_traps() { + no_traps::store(Instruction::F32Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F32Store(memarg)); + } + Ok(()) } #[inline] @@ -1899,67 +2217,129 @@ fn f64_store( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; - Ok(Instruction::F64Store(memarg)) + if module.config.disallow_traps() { + no_traps::store(Instruction::F64Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F64Store(memarg)); + } + Ok(()) } fn i32_store_8( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0])?; - Ok(Instruction::I32Store8(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::I32Store8(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Store8(memarg)); + } + Ok(()) } fn i32_store_16( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0, 1])?; - Ok(Instruction::I32Store16(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::I32Store16(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Store16(memarg)); + } + Ok(()) } fn i64_store_8( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0])?; - Ok(Instruction::I64Store8(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store8(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store8(memarg)); + } + Ok(()) } fn i64_store_16( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1])?; - Ok(Instruction::I64Store16(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store16(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store16(memarg)); + } + Ok(()) } fn i64_store_32( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - Ok(Instruction::I64Store32(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store32(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store32(memarg)); + } + Ok(()) } fn memory_size( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let i = u.int_in_range(0..=module.memories.len() - 1)?; let ty = if module.memories[i].memory64 { ValType::I64 @@ -1967,7 +2347,8 @@ fn memory_size( ValType::I32 }; builder.push_operands(&[ty]); - Ok(Instruction::MemorySize(i as u32)) + instructions.push(Instruction::MemorySize(i as u32)); + Ok(()) } #[inline] @@ -1980,7 +2361,8 @@ fn memory_grow( u: &mut Unstructured, _module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let ty = if builder.type_on_stack(ValType::I32) { ValType::I32 } else { @@ -1989,13 +2371,15 @@ fn memory_grow( let index = memory_index(u, builder, ty)?; builder.pop_operands(&[ty]); builder.push_operands(&[ty]); - Ok(Instruction::MemoryGrow(index)) + instructions.push(Instruction::MemoryGrow(index)); + Ok(()) } #[inline] fn memory_init_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.bulk_memory_enabled() && have_data(module, builder) + && !module.config.disallow_traps() // Non-trapping memory init not yet implemented && (builder.allocs.memory32.len() > 0 && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32]) || (builder.allocs.memory64.len() > 0 @@ -2006,7 +2390,8 @@ fn memory_init( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); let ty = if builder.type_on_stack(ValType::I32) { ValType::I32 @@ -2016,12 +2401,14 @@ fn memory_init( let mem = memory_index(u, builder, ty)?; let data_index = data_index(u, module)?; builder.pop_operands(&[ty]); - Ok(Instruction::MemoryInit { mem, data_index }) + instructions.push(Instruction::MemoryInit { mem, data_index }); + Ok(()) } #[inline] fn memory_fill_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.bulk_memory_enabled() + && !module.config.disallow_traps() // Non-trapping memory fill generation not yet implemented && (builder.allocs.memory32.len() > 0 && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32]) || (builder.allocs.memory64.len() > 0 @@ -2032,7 +2419,8 @@ fn memory_fill( u: &mut Unstructured, _module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let ty = if builder.type_on_stack(ValType::I32) { ValType::I32 } else { @@ -2040,7 +2428,8 @@ fn memory_fill( }; let mem = memory_index(u, builder, ty)?; builder.pop_operands(&[ty, ValType::I32, ty]); - Ok(Instruction::MemoryFill(mem)) + instructions.push(Instruction::MemoryFill(mem)); + Ok(()) } #[inline] @@ -2049,6 +2438,12 @@ fn memory_copy_valid(module: &Module, builder: &mut CodeBuilder) -> bool { return false; } + // The non-trapping case for memory copy has not yet been implemented, + // so we are excluding them for now + if module.config.disallow_traps() { + return false; + } + if builder.types_on_stack(&[ValType::I64, ValType::I64, ValType::I64]) && builder.allocs.memory64.len() > 0 { @@ -2078,7 +2473,8 @@ fn memory_copy( u: &mut Unstructured, _module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let (src_mem, dst_mem) = if builder.types_on_stack(&[ValType::I64, ValType::I64, ValType::I64]) { builder.pop_operands(&[ValType::I64, ValType::I64, ValType::I64]); @@ -2107,7 +2503,8 @@ fn memory_copy( } else { unreachable!() }; - Ok(Instruction::MemoryCopy { dst_mem, src_mem }) + instructions.push(Instruction::MemoryCopy { dst_mem, src_mem }); + Ok(()) } #[inline] @@ -2119,32 +2516,58 @@ fn data_drop( u: &mut Unstructured, module: &Module, _builder: &mut CodeBuilder, -) -> Result { - Ok(Instruction::DataDrop(data_index(u, module)?)) + instructions: &mut Vec, +) -> Result<()> { + instructions.push(Instruction::DataDrop(data_index(u, module)?)); + Ok(()) } -fn i32_const(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_const( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let x = u.arbitrary()?; builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Const(x)) + instructions.push(Instruction::I32Const(x)); + Ok(()) } -fn i64_const(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_const( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let x = u.arbitrary()?; builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Const(x)) + instructions.push(Instruction::I64Const(x)); + Ok(()) } -fn f32_const(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_const( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let x = u.arbitrary()?; builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Const(x)) + instructions.push(Instruction::F32Const(x)); + Ok(()) } -fn f64_const(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_const( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let x = u.arbitrary()?; builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Const(x)) + instructions.push(Instruction::F64Const(x)); + Ok(()) } #[inline] @@ -2152,10 +2575,16 @@ fn i32_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.type_on_stack(ValType::I32) } -fn i32_eqz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_eqz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Eqz) + instructions.push(Instruction::I32Eqz); + Ok(()) } #[inline] @@ -2163,64 +2592,124 @@ fn i32_i32_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::I32, ValType::I32]) } -fn i32_eq(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_eq( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Eq) + instructions.push(Instruction::I32Eq); + Ok(()) } -fn i32_ne(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_ne( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Ne) + instructions.push(Instruction::I32Ne); + Ok(()) } -fn i32_lt_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_lt_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32LtS) + instructions.push(Instruction::I32LtS); + Ok(()) } -fn i32_lt_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_lt_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32LtU) + instructions.push(Instruction::I32LtU); + Ok(()) } -fn i32_gt_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_gt_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32GtS) + instructions.push(Instruction::I32GtS); + Ok(()) } -fn i32_gt_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_gt_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32GtU) + instructions.push(Instruction::I32GtU); + Ok(()) } -fn i32_le_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_le_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32LeS) + instructions.push(Instruction::I32LeS); + Ok(()) } -fn i32_le_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_le_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32LeU) + instructions.push(Instruction::I32LeU); + Ok(()) } -fn i32_ge_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_ge_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32GeS) + instructions.push(Instruction::I32GeS); + Ok(()) } -fn i32_ge_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_ge_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32GeU) + instructions.push(Instruction::I32GeU); + Ok(()) } #[inline] @@ -2228,10 +2717,16 @@ fn i64_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::I64]) } -fn i64_eqz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_eqz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64Eqz) + instructions.push(Instruction::I64Eqz); + Ok(()) } #[inline] @@ -2239,360 +2734,740 @@ fn i64_i64_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::I64, ValType::I64]) } -fn i64_eq(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_eq( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64Eq) + instructions.push(Instruction::I64Eq); + Ok(()) } -fn i64_ne(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_ne( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64Ne) + instructions.push(Instruction::I64Ne); + Ok(()) } -fn i64_lt_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_lt_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64LtS) + instructions.push(Instruction::I64LtS); + Ok(()) } -fn i64_lt_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_lt_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64LtU) + instructions.push(Instruction::I64LtU); + Ok(()) } -fn i64_gt_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_gt_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64GtS) + instructions.push(Instruction::I64GtS); + Ok(()) } -fn i64_gt_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_gt_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64GtU) + instructions.push(Instruction::I64GtU); + Ok(()) } -fn i64_le_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_le_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64LeS) + instructions.push(Instruction::I64LeS); + Ok(()) } -fn i64_le_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_le_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64LeU) + instructions.push(Instruction::I64LeU); + Ok(()) } -fn i64_ge_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_ge_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64GeS) + instructions.push(Instruction::I64GeS); + Ok(()) } -fn i64_ge_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_ge_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I64GeU) + instructions.push(Instruction::I64GeU); + Ok(()) } fn f32_f32_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::F32, ValType::F32]) } -fn f32_eq(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_eq( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Eq) + instructions.push(Instruction::F32Eq); + Ok(()) } -fn f32_ne(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_ne( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Ne) + instructions.push(Instruction::F32Ne); + Ok(()) } -fn f32_lt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_lt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Lt) + instructions.push(Instruction::F32Lt); + Ok(()) } -fn f32_gt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_gt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Gt) + instructions.push(Instruction::F32Gt); + Ok(()) } -fn f32_le(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_le( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Le) + instructions.push(Instruction::F32Le); + Ok(()) } -fn f32_ge(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_ge( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F32Ge) + instructions.push(Instruction::F32Ge); + Ok(()) } fn f64_f64_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::F64, ValType::F64]) } -fn f64_eq(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_eq( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Eq) + instructions.push(Instruction::F64Eq); + Ok(()) } -fn f64_ne(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_ne( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Ne) + instructions.push(Instruction::F64Ne); + Ok(()) } -fn f64_lt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_lt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Lt) + instructions.push(Instruction::F64Lt); + Ok(()) } -fn f64_gt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_gt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Gt) + instructions.push(Instruction::F64Gt); + Ok(()) } -fn f64_le(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_le( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Le) + instructions.push(Instruction::F64Le); + Ok(()) } -fn f64_ge(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { - builder.pop_operands(&[ValType::F64, ValType::F64]); +fn f64_ge( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { + builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::F64Ge) + instructions.push(Instruction::F64Ge); + Ok(()) } -fn i32_clz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_clz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Clz) + instructions.push(Instruction::I32Clz); + Ok(()) } -fn i32_ctz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_ctz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Ctz) + instructions.push(Instruction::I32Ctz); + Ok(()) } -fn i32_popcnt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_popcnt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Popcnt) + instructions.push(Instruction::I32Popcnt); + Ok(()) } -fn i32_add(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_add( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Add) + instructions.push(Instruction::I32Add); + Ok(()) } -fn i32_sub(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_sub( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Sub) + instructions.push(Instruction::I32Sub); + Ok(()) } -fn i32_mul(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_mul( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Mul) + instructions.push(Instruction::I32Mul); + Ok(()) } -fn i32_div_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_div_s( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32DivS) + if module.config.disallow_traps() { + no_traps::signed_div_rem(Instruction::I32DivS, builder, instructions); + } else { + instructions.push(Instruction::I32DivS); + } + Ok(()) } -fn i32_div_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_div_u( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32DivU) + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I32DivU, builder, instructions); + } else { + instructions.push(Instruction::I32DivU); + } + Ok(()) } -fn i32_rem_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rem_s( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32RemS) + if module.config.disallow_traps() { + no_traps::signed_div_rem(Instruction::I32RemS, builder, instructions); + } else { + instructions.push(Instruction::I32RemS); + } + Ok(()) } -fn i32_rem_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rem_u( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32RemU) + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I32RemU, builder, instructions); + } else { + instructions.push(Instruction::I32RemU); + } + Ok(()) } -fn i32_and(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_and( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32And) + instructions.push(Instruction::I32And); + Ok(()) } -fn i32_or(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_or( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Or) + instructions.push(Instruction::I32Or); + Ok(()) } -fn i32_xor(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_xor( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Xor) + instructions.push(Instruction::I32Xor); + Ok(()) } -fn i32_shl(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_shl( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Shl) + instructions.push(Instruction::I32Shl); + Ok(()) } -fn i32_shr_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_shr_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32ShrS) + instructions.push(Instruction::I32ShrS); + Ok(()) } -fn i32_shr_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_shr_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32ShrU) + instructions.push(Instruction::I32ShrU); + Ok(()) } -fn i32_rotl(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rotl( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Rotl) + instructions.push(Instruction::I32Rotl); + Ok(()) } -fn i32_rotr(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rotr( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Rotr) + instructions.push(Instruction::I32Rotr); + Ok(()) } -fn i64_clz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_clz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Clz) + instructions.push(Instruction::I64Clz); + Ok(()) } -fn i64_ctz(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_ctz( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Ctz) + instructions.push(Instruction::I64Ctz); + Ok(()) } -fn i64_popcnt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_popcnt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Popcnt) + instructions.push(Instruction::I64Popcnt); + Ok(()) } -fn i64_add(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_add( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Add) + instructions.push(Instruction::I64Add); + Ok(()) } -fn i64_sub(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_sub( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Sub) + instructions.push(Instruction::I64Sub); + Ok(()) } -fn i64_mul(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_mul( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Mul) + instructions.push(Instruction::I64Mul); + Ok(()) } -fn i64_div_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_div_s( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64DivS) + if module.config.disallow_traps() { + no_traps::signed_div_rem(Instruction::I64DivS, builder, instructions); + } else { + instructions.push(Instruction::I64DivS); + } + Ok(()) } -fn i64_div_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_div_u( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64DivU) + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I64DivU, builder, instructions); + } else { + instructions.push(Instruction::I64DivU); + } + Ok(()) } -fn i64_rem_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rem_s( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64RemS) + if module.config.disallow_traps() { + no_traps::signed_div_rem(Instruction::I64RemS, builder, instructions); + } else { + instructions.push(Instruction::I64RemS); + } + Ok(()) } -fn i64_rem_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rem_u( + _: &mut Unstructured, + module: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64RemU) + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I64RemU, builder, instructions); + } else { + instructions.push(Instruction::I64RemU); + } + Ok(()) } -fn i64_and(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_and( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64And) + instructions.push(Instruction::I64And); + Ok(()) } -fn i64_or(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_or( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Or) + instructions.push(Instruction::I64Or); + Ok(()) } -fn i64_xor(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_xor( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Xor) + instructions.push(Instruction::I64Xor); + Ok(()) } -fn i64_shl(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_shl( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Shl) + instructions.push(Instruction::I64Shl); + Ok(()) } -fn i64_shr_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_shr_s( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64ShrS) + instructions.push(Instruction::I64ShrS); + Ok(()) } -fn i64_shr_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_shr_u( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64ShrU) + instructions.push(Instruction::I64ShrU); + Ok(()) } -fn i64_rotl(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rotl( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Rotl) + instructions.push(Instruction::I64Rotl); + Ok(()) } -fn i64_rotr(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rotr( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Rotr) + instructions.push(Instruction::I64Rotr); + Ok(()) } #[inline] @@ -2600,92 +3475,172 @@ fn f32_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::F32]) } -fn f32_abs(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_abs( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Abs) + instructions.push(Instruction::F32Abs); + Ok(()) } -fn f32_neg(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_neg( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Neg) + instructions.push(Instruction::F32Neg); + Ok(()) } -fn f32_ceil(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_ceil( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Ceil) + instructions.push(Instruction::F32Ceil); + Ok(()) } -fn f32_floor(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_floor( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Floor) + instructions.push(Instruction::F32Floor); + Ok(()) } -fn f32_trunc(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_trunc( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Trunc) + instructions.push(Instruction::F32Trunc); + Ok(()) } -fn f32_nearest(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_nearest( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Nearest) + instructions.push(Instruction::F32Nearest); + Ok(()) } -fn f32_sqrt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_sqrt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Sqrt) + instructions.push(Instruction::F32Sqrt); + Ok(()) } -fn f32_add(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_add( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Add) + instructions.push(Instruction::F32Add); + Ok(()) } -fn f32_sub(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_sub( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Sub) + instructions.push(Instruction::F32Sub); + Ok(()) } -fn f32_mul(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_mul( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Mul) + instructions.push(Instruction::F32Mul); + Ok(()) } -fn f32_div(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_div( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Div) + instructions.push(Instruction::F32Div); + Ok(()) } -fn f32_min(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_min( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Min) + instructions.push(Instruction::F32Min); + Ok(()) } -fn f32_max(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f32_max( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Max) + instructions.push(Instruction::F32Max); + Ok(()) } fn f32_copysign( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32, ValType::F32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32Copysign) + instructions.push(Instruction::F32Copysign); + Ok(()) } #[inline] @@ -2693,102 +3648,184 @@ fn f64_on_stack(_: &Module, builder: &mut CodeBuilder) -> bool { builder.types_on_stack(&[ValType::F64]) } -fn f64_abs(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_abs( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Abs) + instructions.push(Instruction::F64Abs); + Ok(()) } -fn f64_neg(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_neg( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Neg) + instructions.push(Instruction::F64Neg); + Ok(()) } -fn f64_ceil(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_ceil( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Ceil) + instructions.push(Instruction::F64Ceil); + Ok(()) } -fn f64_floor(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_floor( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Floor) + instructions.push(Instruction::F64Floor); + Ok(()) } -fn f64_trunc(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_trunc( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Trunc) + instructions.push(Instruction::F64Trunc); + Ok(()) } -fn f64_nearest(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_nearest( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Nearest) + instructions.push(Instruction::F64Nearest); + Ok(()) } -fn f64_sqrt(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_sqrt( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Sqrt) + instructions.push(Instruction::F64Sqrt); + Ok(()) } -fn f64_add(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_add( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Add) + instructions.push(Instruction::F64Add); + Ok(()) } -fn f64_sub(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_sub( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Sub) + instructions.push(Instruction::F64Sub); + Ok(()) } -fn f64_mul(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_mul( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Mul) + instructions.push(Instruction::F64Mul); + Ok(()) } -fn f64_div(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_div( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Div) + instructions.push(Instruction::F64Div); + Ok(()) } -fn f64_min(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_min( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Min) + instructions.push(Instruction::F64Min); + Ok(()) } -fn f64_max(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn f64_max( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Max) + instructions.push(Instruction::F64Max); + Ok(()) } fn f64_copysign( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64, ValType::F64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64Copysign) + instructions.push(Instruction::F64Copysign); + Ok(()) } fn i32_wrap_i64( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32WrapI64) + instructions.push(Instruction::I32WrapI64); + Ok(()) } fn nontrapping_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { @@ -2797,22 +3834,34 @@ fn nontrapping_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool fn i32_trunc_f32_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncF32S) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF32S, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF32S); + } + Ok(()) } fn i32_trunc_f32_u( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncF32U) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF32U, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF32U); + } + Ok(()) } fn nontrapping_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { @@ -2821,222 +3870,290 @@ fn nontrapping_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool fn i32_trunc_f64_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncF64S) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF64S, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF64S); + } + Ok(()) } fn i32_trunc_f64_u( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncF64U) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF64U, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF64U); + } + Ok(()) } fn i64_extend_i32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64ExtendI32S) + instructions.push(Instruction::I64ExtendI32S); + Ok(()) } fn i64_extend_i32_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64ExtendI32U) + instructions.push(Instruction::I64ExtendI32U); + Ok(()) } fn i64_trunc_f32_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncF32S) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I64TruncF32S, builder, instructions); + } else { + instructions.push(Instruction::I64TruncF32S); + } + Ok(()) } fn i64_trunc_f32_u( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncF32U) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I64TruncF32U, builder, instructions); + } else { + instructions.push(Instruction::I64TruncF32U); + } + Ok(()) } fn i64_trunc_f64_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncF64S) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I64TruncF64S, builder, instructions); + } else { + instructions.push(Instruction::I64TruncF64S); + } + Ok(()) } fn i64_trunc_f64_u( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncF64U) + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I64TruncF64U, builder, instructions); + } else { + instructions.push(Instruction::I64TruncF64U); + } + Ok(()) } fn f32_convert_i32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32ConvertI32S) + instructions.push(Instruction::F32ConvertI32S); + Ok(()) } fn f32_convert_i32_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32ConvertI32U) + instructions.push(Instruction::F32ConvertI32U); + Ok(()) } fn f32_convert_i64_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32ConvertI64S) + instructions.push(Instruction::F32ConvertI64S); + Ok(()) } fn f32_convert_i64_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32ConvertI64U) + instructions.push(Instruction::F32ConvertI64U); + Ok(()) } fn f32_demote_f64( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32DemoteF64) + instructions.push(Instruction::F32DemoteF64); + Ok(()) } fn f64_convert_i32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64ConvertI32S) + instructions.push(Instruction::F64ConvertI32S); + Ok(()) } fn f64_convert_i32_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64ConvertI32U) + instructions.push(Instruction::F64ConvertI32U); + Ok(()) } fn f64_convert_i64_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64ConvertI64S) + instructions.push(Instruction::F64ConvertI64S); + Ok(()) } fn f64_convert_i64_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64ConvertI64U) + instructions.push(Instruction::F64ConvertI64U); + Ok(()) } fn f64_promote_f32( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64PromoteF32) + instructions.push(Instruction::F64PromoteF32); + Ok(()) } fn i32_reinterpret_f32( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32ReinterpretF32) + instructions.push(Instruction::I32ReinterpretF32); + Ok(()) } fn i64_reinterpret_f64( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64ReinterpretF64) + instructions.push(Instruction::I64ReinterpretF64); + Ok(()) } fn f32_reinterpret_i32( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::F32]); - Ok(Instruction::F32ReinterpretI32) + instructions.push(Instruction::F32ReinterpretI32); + Ok(()) } fn f64_reinterpret_i64( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::F64]); - Ok(Instruction::F64ReinterpretI64) + instructions.push(Instruction::F64ReinterpretI64); + Ok(()) } fn extendable_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { @@ -3047,20 +4164,24 @@ fn i32_extend_8_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Extend8S) + instructions.push(Instruction::I32Extend8S); + Ok(()) } fn i32_extend_16_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32Extend16S) + instructions.push(Instruction::I32Extend16S); + Ok(()) } fn extendable_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { @@ -3071,110 +4192,132 @@ fn i64_extend_8_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Extend8S) + instructions.push(Instruction::I64Extend8S); + Ok(()) } fn i64_extend_16_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Extend16S) + instructions.push(Instruction::I64Extend16S); + Ok(()) } fn i64_extend_32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64Extend32S) + instructions.push(Instruction::I64Extend32S); + Ok(()) } fn i32_trunc_sat_f32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncSatF32S) + instructions.push(Instruction::I32TruncSatF32S); + Ok(()) } fn i32_trunc_sat_f32_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncSatF32U) + instructions.push(Instruction::I32TruncSatF32U); + Ok(()) } fn i32_trunc_sat_f64_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncSatF64S) + instructions.push(Instruction::I32TruncSatF64S); + Ok(()) } fn i32_trunc_sat_f64_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32TruncSatF64U) + instructions.push(Instruction::I32TruncSatF64U); + Ok(()) } fn i64_trunc_sat_f32_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncSatF32S) + instructions.push(Instruction::I64TruncSatF32S); + Ok(()) } fn i64_trunc_sat_f32_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncSatF32U) + instructions.push(Instruction::I64TruncSatF32U); + Ok(()) } fn i64_trunc_sat_f64_s( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncSatF64S) + instructions.push(Instruction::I64TruncSatF64S); + Ok(()) } fn i64_trunc_sat_f64_u( _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncSatF64U) + instructions.push(Instruction::I64TruncSatF64U); + Ok(()) } fn memory_offset(u: &mut Unstructured, module: &Module, memory_index: u32) -> Result { @@ -3187,17 +4330,34 @@ fn memory_offset(u: &mut Unstructured, module: &Module, memory_index: u32) -> Re .maximum .map(|max| max.saturating_mul(65536)) .unwrap_or(u64::MAX); - let (min, max, true_max) = if memory_type.memory64 { - // 64-bit memories can use the limits calculated above as-is - (min, max, u64::MAX) - } else { - // 32-bit memories can't represent a full 4gb offset, so if that's the - // min/max sizes then we need to switch the m to `u32::MAX`. - ( - u64::from(u32::try_from(min).unwrap_or(u32::MAX)), - u64::from(u32::try_from(max).unwrap_or(u32::MAX)), - u64::from(u32::MAX), - ) + + let (min, max, true_max) = match (memory_type.memory64, module.config.disallow_traps()) { + (true, false) => { + // 64-bit memories can use the limits calculated above as-is + (min, max, u64::MAX) + } + (false, false) => { + // 32-bit memories can't represent a full 4gb offset, so if that's the + // min/max sizes then we need to switch the m to `u32::MAX`. + ( + u64::from(u32::try_from(min).unwrap_or(u32::MAX)), + u64::from(u32::try_from(max).unwrap_or(u32::MAX)), + u64::from(u32::MAX), + ) + } + // The logic for non-trapping versions of load/store involves pushing + // the offset + load/store size onto the stack as either an i32 or i64 + // value. So even though offsets can normally be as high as u32 or u64, + // we need to limit them to lower in order for our non-trapping logic to + // work. 16 is the number of bytes of the largest load type (V128). + (true, true) => { + let no_trap_max = (i64::MAX - 16) as u64; + (min, no_trap_max, no_trap_max) + } + (false, true) => { + let no_trap_max = (i32::MAX - 16) as u64; + (min, no_trap_max, no_trap_max) + } }; let choice = u.int_in_range(0..=a + b + c - 1)?; @@ -3255,10 +4415,16 @@ fn ref_null_valid(module: &Module, _: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() } -fn ref_null(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn ref_null( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let ty = *u.choose(&[ValType::ExternRef, ValType::FuncRef])?; builder.push_operands(&[ty]); - Ok(Instruction::RefNull(ty)) + instructions.push(Instruction::RefNull(ty)); + Ok(()) } #[inline] @@ -3266,10 +4432,16 @@ fn ref_func_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() && builder.allocs.referenced_functions.len() > 0 } -fn ref_func(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn ref_func( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { let i = *u.choose(&builder.allocs.referenced_functions)?; builder.push_operands(&[ValType::FuncRef]); - Ok(Instruction::RefFunc(i)) + instructions.push(Instruction::RefFunc(i)); + Ok(()) } #[inline] @@ -3278,16 +4450,23 @@ fn ref_is_null_valid(module: &Module, builder: &mut CodeBuilder) -> bool { && (builder.type_on_stack(ValType::ExternRef) || builder.type_on_stack(ValType::FuncRef)) } -fn ref_is_null(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn ref_is_null( + _: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { pop_reference_type(builder); builder.push_operands(&[ValType::I32]); - Ok(Instruction::RefIsNull) + instructions.push(Instruction::RefIsNull); + Ok(()) } #[inline] fn table_fill_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() && module.config.bulk_memory_enabled() + && !module.config.disallow_traps() // Non-trapping table fill generation not yet implemented && [ValType::ExternRef, ValType::FuncRef].iter().any(|ty| { builder.types_on_stack(&[ValType::I32, *ty, ValType::I32]) && module.tables.iter().any(|t| t.element_type == *ty) @@ -3298,17 +4477,20 @@ fn table_fill( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let ty = pop_reference_type(builder); builder.pop_operands(&[ValType::I32]); let table = table_index(ty, u, module)?; - Ok(Instruction::TableFill(table)) + instructions.push(Instruction::TableFill(table)); + Ok(()) } #[inline] fn table_set_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() + && !module.config.disallow_traps() // Non-trapping table.set generation not yet implemented && [ValType::ExternRef, ValType::FuncRef].iter().any(|ty| { builder.types_on_stack(&[ValType::I32, *ty]) && module.tables.iter().any(|t| t.element_type == *ty) @@ -3319,16 +4501,19 @@ fn table_set( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let ty = pop_reference_type(builder); builder.pop_operands(&[ValType::I32]); let table = table_index(ty, u, module)?; - Ok(Instruction::TableSet(table)) + instructions.push(Instruction::TableSet(table)); + Ok(()) } #[inline] fn table_get_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() + && !module.config.disallow_traps() // Non-trapping table.get generation not yet implemented && builder.type_on_stack(ValType::I32) && module.tables.len() > 0 } @@ -3337,12 +4522,14 @@ fn table_get( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let idx = u.int_in_range(0..=module.tables.len() - 1)?; let ty = module.tables[idx].element_type; builder.push_operands(&[ty]); - Ok(Instruction::TableGet(idx as u32)) + instructions.push(Instruction::TableGet(idx as u32)); + Ok(()) } #[inline] @@ -3354,10 +4541,12 @@ fn table_size( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let table = u.int_in_range(0..=module.tables.len() - 1)? as u32; builder.push_operands(&[ValType::I32]); - Ok(Instruction::TableSize(table)) + instructions.push(Instruction::TableSize(table)); + Ok(()) } #[inline] @@ -3373,17 +4562,20 @@ fn table_grow( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32]); let ty = pop_reference_type(builder); let table = table_index(ty, u, module)?; builder.push_operands(&[ValType::I32]); - Ok(Instruction::TableGrow(table)) + instructions.push(Instruction::TableGrow(table)); + Ok(()) } #[inline] fn table_copy_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() + && !module.config.disallow_traps() // Non-trapping table.copy generation not yet implemented && module.tables.len() > 0 && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32]) } @@ -3392,19 +4584,22 @@ fn table_copy( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32, ValType::I32]); let src_table = u.int_in_range(0..=module.tables.len() - 1)? as u32; let dst_table = table_index(module.tables[src_table as usize].element_type, u, module)?; - Ok(Instruction::TableCopy { + instructions.push(Instruction::TableCopy { src_table, dst_table, - }) + }); + Ok(()) } #[inline] fn table_init_valid(module: &Module, builder: &mut CodeBuilder) -> bool { module.config.reference_types_enabled() + && !module.config.disallow_traps() // Non-trapping table.init generation not yet implemented. && builder.allocs.table_init_possible && builder.types_on_stack(&[ValType::I32, ValType::I32, ValType::I32]) } @@ -3413,7 +4608,8 @@ fn table_init( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32, ValType::I32]); let segments = module .elems @@ -3424,10 +4620,11 @@ fn table_init( .collect::>(); let segment = *u.choose(&segments)?; let table = table_index(module.elems[segment].ty, u, module)?; - Ok(Instruction::TableInit { + instructions.push(Instruction::TableInit { elem_index: segment as u32, table, - }) + }); + Ok(()) } #[inline] @@ -3439,9 +4636,11 @@ fn elem_drop( u: &mut Unstructured, module: &Module, _builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { let segment = u.int_in_range(0..=module.elems.len() - 1)? as u32; - Ok(Instruction::ElemDrop(segment)) + instructions.push(Instruction::ElemDrop(segment)); + Ok(()) } fn pop_reference_type(builder: &mut CodeBuilder) -> ValType { @@ -3471,89 +4670,133 @@ fn lane_index(u: &mut Unstructured, number_of_lanes: u8) -> Result { #[inline] fn simd_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128]) } #[inline] fn simd_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.relaxed_simd_enabled() && builder.types_on_stack(&[ValType::V128]) + !module.config.disallow_traps() + && module.config.relaxed_simd_enabled() + && builder.types_on_stack(&[ValType::V128]) } #[inline] fn simd_v128_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::V128]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::V128]) } #[inline] fn simd_v128_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.relaxed_simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::V128]) + !module.config.disallow_traps() + && module.config.relaxed_simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::V128]) } #[inline] fn simd_v128_v128_v128_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() + !module.config.disallow_traps() + && module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::V128, ValType::V128]) } #[inline] fn simd_v128_v128_v128_on_stack_relaxed(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.relaxed_simd_enabled() + !module.config.disallow_traps() + && module.config.relaxed_simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::V128, ValType::V128]) } #[inline] fn simd_v128_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::I32]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::I32]) } #[inline] fn simd_v128_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::I64]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::I64]) } #[inline] fn simd_v128_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::F32]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::F32]) } #[inline] fn simd_v128_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.types_on_stack(&[ValType::V128, ValType::F64]) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.types_on_stack(&[ValType::V128, ValType::F64]) } #[inline] fn simd_i32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.type_on_stack(ValType::I32) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.type_on_stack(ValType::I32) } #[inline] fn simd_i64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.type_on_stack(ValType::I64) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.type_on_stack(ValType::I64) } #[inline] fn simd_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.type_on_stack(ValType::F32) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.type_on_stack(ValType::F32) } #[inline] fn simd_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && builder.type_on_stack(ValType::F64) + !module.config.disallow_traps() + && module.config.simd_enabled() + && builder.type_on_stack(ValType::F64) } #[inline] fn simd_have_memory_and_offset(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && have_memory_and_offset(module, builder) + !module.config.disallow_traps() + && module.config.simd_enabled() + && have_memory_and_offset(module, builder) } #[inline] fn simd_have_memory_and_offset_and_v128(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && store_valid(module, builder, || ValType::V128) + !module.config.disallow_traps() + && module.config.simd_enabled() + && store_valid(module, builder, || ValType::V128) +} + +#[inline] +fn simd_load_lane_valid(module: &Module, builder: &mut CodeBuilder) -> bool { + // The SIMD non-trapping case is not yet implemented. + !module.config.disallow_traps() && simd_have_memory_and_offset_and_v128(module, builder) } #[inline] fn simd_v128_store_valid(module: &Module, builder: &mut CodeBuilder) -> bool { - module.config.simd_enabled() && store_valid(module, builder, || ValType::V128) + !module.config.disallow_traps() + && module.config.simd_enabled() + && store_valid(module, builder, || ValType::V128) +} + +#[inline] +fn simd_store_lane_valid(module: &Module, builder: &mut CodeBuilder) -> bool { + // The SIMD non-trapping case is not yet implemented. + !module.config.disallow_traps() && simd_v128_store_valid(module, builder) } #[inline] @@ -3567,10 +4810,22 @@ macro_rules! simd_load { u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, - ) -> Result { + + instructions: &mut Vec, + ) -> Result<()> { let memarg = mem_arg(u, module, builder, $alignments)?; builder.push_operands(&[ValType::V128]); - Ok(Instruction::$instruction(memarg)) + if module.config.disallow_traps() { + no_traps::load( + Instruction::$instruction(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::$instruction(memarg)); + } + Ok(()) } }; } @@ -3593,10 +4848,21 @@ fn v128_store( u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::V128]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; - Ok(Instruction::V128Store(memarg)) + if module.config.disallow_traps() { + no_traps::store( + Instruction::V128Store(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::V128Store(memarg)); + } + Ok(()) } macro_rules! simd_load_lane { @@ -3605,14 +4871,16 @@ macro_rules! simd_load_lane { u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, - ) -> Result { + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands(&[ValType::V128]); let memarg = mem_arg(u, module, builder, $alignments)?; builder.push_operands(&[ValType::V128]); - Ok(Instruction::$instruction { + instructions.push(Instruction::$instruction { memarg, lane: lane_index(u, $number_of_lanes)?, - }) + }); + Ok(()) } }; } @@ -3628,13 +4896,15 @@ macro_rules! simd_store_lane { u: &mut Unstructured, module: &Module, builder: &mut CodeBuilder, - ) -> Result { + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands(&[ValType::V128]); let memarg = mem_arg(u, module, builder, $alignments)?; - Ok(Instruction::$instruction { + instructions.push(Instruction::$instruction { memarg, lane: lane_index(u, $number_of_lanes)?, - }) + }); + Ok(()) } }; } @@ -3644,24 +4914,32 @@ simd_store_lane!(V128Store16Lane, v128_store16_lane, &[0, 1], 8); simd_store_lane!(V128Store32Lane, v128_store32_lane, &[0, 1, 2], 4); simd_store_lane!(V128Store64Lane, v128_store64_lane, &[0, 1, 2, 3], 2); -fn v128_const(u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn v128_const( + u: &mut Unstructured, + _: &Module, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.push_operands(&[ValType::V128]); let c = i128::from_le_bytes(u.arbitrary()?); - Ok(Instruction::V128Const(c)) + instructions.push(Instruction::V128Const(c)); + Ok(()) } fn i8x16_shuffle( u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::V128, ValType::V128]); builder.push_operands(&[ValType::V128]); let mut lanes = [0; 16]; for i in 0..16 { lanes[i] = u.int_in_range(0..=31)?; } - Ok(Instruction::I8x16Shuffle(lanes)) + instructions.push(Instruction::I8x16Shuffle(lanes)); + Ok(()) } macro_rules! simd_lane_access { @@ -3670,10 +4948,12 @@ macro_rules! simd_lane_access { u: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, - ) -> Result { + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands($in_types); builder.push_operands($out_types); - Ok(Instruction::$instruction(lane_index(u, $number_of_lanes)?)) + instructions.push(Instruction::$instruction(lane_index(u, $number_of_lanes)?)); + Ok(()) } }; } @@ -3699,10 +4979,13 @@ macro_rules! simd_binop { _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, - ) -> Result { + + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands(&[ValType::V128, ValType::V128]); builder.push_operands(&[ValType::V128]); - Ok(Instruction::$instruction) + instructions.push(Instruction::$instruction); + Ok(()) } }; } @@ -3717,10 +5000,12 @@ macro_rules! simd_unop { _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, - ) -> Result { + + instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::$in_type]); builder.push_operands(&[ValType::$out_type]); - Ok(Instruction::$instruction) + instructions.push(Instruction::$instruction); + Ok(()) } }; } @@ -3731,10 +5016,13 @@ macro_rules! simd_ternop { _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, - ) -> Result { + + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands(&[ValType::V128, ValType::V128, ValType::V128]); builder.push_operands(&[ValType::V128]); - Ok(Instruction::$instruction) + instructions.push(Instruction::$instruction); + Ok(()) } }; } @@ -3745,10 +5033,13 @@ macro_rules! simd_shift { _: &mut Unstructured, _: &Module, builder: &mut CodeBuilder, - ) -> Result { + + instructions: &mut Vec, + ) -> Result<()> { builder.pop_operands(&[ValType::V128, ValType::I32]); builder.push_operands(&[ValType::V128]); - Ok(Instruction::$instruction) + instructions.push(Instruction::$instruction); + Ok(()) } }; } diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs new file mode 100644 index 0000000000..6b4bb126ee --- /dev/null +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -0,0 +1,641 @@ +use crate::core::*; +use wasm_encoder::{BlockType, Instruction, ValType}; + +use super::CodeBuilder; + +// For loads, we dynamically check whether the load will +// trap, and if it will then we generate a dummy value to +// use instead. +pub(crate) fn load<'a>( + inst: Instruction<'a>, + module: &Module, + builder: &mut CodeBuilder, + insts: &mut Vec>, +) { + let memarg = get_memarg(&inst); + let memory = &module.memories[memarg.memory_index as usize]; + let address_type = if memory.memory64 { + ValType::I64 + } else { + ValType::I32 + }; + // Add a temporary local to hold this load's address. + let address_local = builder.alloc_local(address_type); + + // Add a temporary local to hold the result of this load. + let load_type = type_of_memory_access(&inst); + let result_local = builder.alloc_local(load_type); + + // [address:address_type] + insts.push(Instruction::LocalSet(address_local)); + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::MemorySize(memarg.memory_index)); + // [mem_size_in_pages:address_type] + insts.push(int_const_inst(address_type, 65_536)); + // [mem_size_in_pages:address_type wasm_page_size:address_type] + insts.push(int_mul_inst(address_type)); + // [mem_size_in_bytes:address_type] + insts.push(int_const_inst( + address_type, + (memarg.offset + size_of_type_in_memory(load_type)) as i64, + )); + // [mem_size_in_bytes:address_type offset_and_size:address_type] + insts.push(Instruction::LocalGet(address_local)); + // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] + insts.push(int_add_inst(address_type)); + // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] + insts.push(int_le_u_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(int_const_inst(address_type, 0)); + // [address:address_type 0:address_type] + insts.push(int_le_s_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(inst); + // [result:load_type] + insts.push(Instruction::LocalSet(result_local)); + // [] + insts.push(Instruction::Br(1)); + // + } + // [] + insts.push(Instruction::End); + // [] + insts.push(dummy_value_inst(load_type)); + // [dummy_value:load_type] + insts.push(Instruction::LocalSet(result_local)); + // [] + } + // [] + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(result_local)); + // [result:load_type] +} + +// Stores are similar to loads: we check whether the store +// will trap, and if it will then we just drop the value. +pub(crate) fn store<'a>( + inst: Instruction<'a>, + module: &Module, + builder: &mut CodeBuilder, + insts: &mut Vec>, +) { + let memarg = get_memarg(&inst); + let memory = &module.memories[memarg.memory_index as usize]; + let address_type = if memory.memory64 { + ValType::I64 + } else { + ValType::I32 + }; + + // Add a temporary local to hold this store's address. + let address_local = builder.alloc_local(address_type); + + // Add a temporary local to hold the value to store. + let store_type = type_of_memory_access(&inst); + let value_local = builder.alloc_local(store_type); + + // [address:address_type value:store_type] + insts.push(Instruction::LocalSet(value_local)); + // [address:address_type] + insts.push(Instruction::LocalSet(address_local)); + // [] + insts.push(Instruction::MemorySize(memarg.memory_index)); + // [mem_size_in_pages:address_type] + insts.push(int_const_inst(address_type, 65_536)); + // [mem_size_in_pages:address_type wasm_page_size:address_type] + insts.push(int_mul_inst(address_type)); + // [mem_size_in_bytes:address_type] + insts.push(int_const_inst( + address_type, + (memarg.offset + size_of_type_in_memory(store_type)) as i64, + )); + // [mem_size_in_bytes:address_type offset_and_size:address_type] + insts.push(Instruction::LocalGet(address_local)); + // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] + insts.push(int_add_inst(address_type)); + // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] + insts.push(int_le_u_inst(address_type)); + // [store_will_trap:i32] + insts.push(Instruction::If(BlockType::Empty)); + insts.push(Instruction::Else); + { + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(int_const_inst(address_type, 0)); + // [address:address_type 0:address_type] + insts.push(int_le_s_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::If(BlockType::Empty)); + insts.push(Instruction::Else); + { + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(Instruction::LocalGet(value_local)); + // [address:address_type value:store_type] + insts.push(inst); + // [] + } + insts.push(Instruction::End); + } + // [] + insts.push(Instruction::End); +} + +// Unsigned integer division and remainder will trap when +// the divisor is 0. To avoid the trap, we will set any 0 +// divisors to 1 prior to the operation. +// +// The code below is equivalent to this expression: +// +// local.set $temp_divisor +// (select (i32.eqz (local.get $temp_divisor) (i32.const 1) (local.get $temp_divisor)) +pub(crate) fn unsigned_div_rem<'a>( + inst: Instruction<'a>, + builder: &mut CodeBuilder, + insts: &mut Vec>, +) { + let op_type = type_of_integer_operation(&inst); + let temp_divisor = builder.alloc_local(op_type); + + // [dividend:op_type divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(int_const_inst(op_type, 1)); + // [dividend:op_type 1:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type divisor:op_type] + insts.push(eqz_inst(op_type)); + // [dividend:op_type 1:op_type divisor:op_type is_zero:i32] + insts.push(Instruction::Select); + // [dividend:op_type divisor:op_type] + insts.push(inst); + // [result:op_type] +} + +pub(crate) fn trunc<'a>( + inst: Instruction<'a>, + builder: &mut CodeBuilder, + insts: &mut Vec>, +) { + // If NaN or ±inf, replace with dummy value. Our method of checking for NaN + // is to use `ne` because NaN is the only value that is not equal to itself + let conv_type = type_of_float_conversion(&inst); + let temp_float = builder.alloc_local(conv_type); + // [input:conv_type] + insts.push(Instruction::LocalTee(temp_float)); + // [input:conv_type] + insts.push(Instruction::LocalGet(temp_float)); + // [input:conv_type input:conv_type] + insts.push(ne_inst(conv_type)); + // [is_nan:i32] + insts.push(Instruction::LocalGet(temp_float)); + // [is_nan:i32 input:conv_type] + insts.push(flt_inf_const_inst(conv_type)); + // [is_nan:i32 input:conv_type inf:conv_type] + insts.push(eq_inst(conv_type)); + // [is_nan:i32 is_inf:i32] + insts.push(Instruction::LocalGet(temp_float)); + // [is_nan:i32 is_inf:i32 input:conv_type] + insts.push(flt_neg_inf_const_inst(conv_type)); + // [is_nan:i32 is_inf:i32 input:conv_type neg_inf:conv_type] + insts.push(eq_inst(conv_type)); + // [is_nan:i32 is_inf:i32 is_neg_inf:i32] + insts.push(Instruction::I32Or); + // [is_nan:i32 is_±inf:i32] + insts.push(Instruction::I32Or); + // [is_nan_or_inf:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(dummy_value_inst(conv_type)); + // [0:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + // [] + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [input_or_0:conv_type] + + // first ensure that it is >= the min value of our target type + insts.push(min_input_const_for_trunc(&inst)); + // [input_or_0:conv_type min_value_of_target_type:conv_type] + insts.push(flt_lt_inst(conv_type)); + // [input_lt_min:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(min_input_const_for_trunc(&inst)); + // [min_value_of_target_type:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [coerced_input:conv_type] + + // next ensure that it is <= the max value of our target type + insts.push(max_input_const_for_trunc(&inst)); + // [input_or_0:conv_type max_value_of_target_type:conv_type] + insts.push(flt_gt_inst(conv_type)); + // [input_gt_min:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(max_input_const_for_trunc(&inst)); + // [max_value_of_target_type:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [coerced_input:conv_type] + insts.push(inst); +} + +// Signed division and remainder will trap in the following instances: +// - The divisor is 0 +// - The result of the division is 2^(n-1) +pub(crate) fn signed_div_rem<'a>( + inst: Instruction<'a>, + builder: &mut CodeBuilder, + insts: &mut Vec>, +) { + // If divisor is 0, replace with 1 + let op_type = type_of_integer_operation(&inst); + let temp_divisor = builder.alloc_local(op_type); + // [dividend:op_type divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(int_const_inst(op_type, 1)); + // [dividend:op_type 1:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type divisor:op_type] + insts.push(eqz_inst(op_type)); + // [dividend:op_type 1:op_type divisor:op_type is_zero:i32] + insts.push(Instruction::Select); + // [dividend:op_type divisor:op_type] + // If dividend and divisor are -int.max and -1, replace + // divisor with 1. + let temp_dividend = builder.alloc_local(op_type); + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(Instruction::LocalSet(temp_dividend)); + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::LocalGet(temp_dividend)); + // [dividend:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(Instruction::LocalTee(temp_dividend)); + // [dividend:op_type] + insts.push(int_min_const_inst(op_type)); + // [dividend:op_type int_min:op_type] + insts.push(ne_inst(op_type)); + // [not_int_min:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(temp_divisor)); + // [divisor:op_type] + insts.push(int_const_inst(op_type, -1)); + // [divisor:op_type -1:op_type] + insts.push(ne_inst(op_type)); + // [not_neg_one:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(int_const_inst(op_type, 1)); + // [divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [] + insts.push(Instruction::Br(1)); + } + // [] + insts.push(Instruction::End); + } + // [] + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_dividend)); + // [dividend:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type divisor:op_type] + insts.push(inst); +} + +fn get_memarg(inst: &Instruction) -> wasm_encoder::MemArg { + match *inst { + Instruction::I32Load(memarg) + | Instruction::I64Load(memarg) + | Instruction::F32Load(memarg) + | Instruction::F64Load(memarg) + | Instruction::I32Load8S(memarg) + | Instruction::I32Load8U(memarg) + | Instruction::I32Load16S(memarg) + | Instruction::I32Load16U(memarg) + | Instruction::I64Load8S(memarg) + | Instruction::I64Load8U(memarg) + | Instruction::I64Load16S(memarg) + | Instruction::I64Load16U(memarg) + | Instruction::I64Load32S(memarg) + | Instruction::I64Load32U(memarg) + | Instruction::V128Load(memarg) + | Instruction::V128Load8x8S(memarg) + | Instruction::V128Load8x8U(memarg) + | Instruction::V128Load16x4S(memarg) + | Instruction::V128Load16x4U(memarg) + | Instruction::V128Load32x2S(memarg) + | Instruction::V128Load32x2U(memarg) + | Instruction::V128Load8Splat(memarg) + | Instruction::V128Load16Splat(memarg) + | Instruction::V128Load32Splat(memarg) + | Instruction::V128Load64Splat(memarg) + | Instruction::V128Load32Zero(memarg) + | Instruction::V128Load64Zero(memarg) + | Instruction::I32Store(memarg) + | Instruction::I64Store(memarg) + | Instruction::F32Store(memarg) + | Instruction::F64Store(memarg) + | Instruction::I32Store8(memarg) + | Instruction::I32Store16(memarg) + | Instruction::I64Store8(memarg) + | Instruction::I64Store16(memarg) + | Instruction::I64Store32(memarg) + | Instruction::V128Store(memarg) => memarg, + _ => unreachable!(), + } +} + +fn dummy_value_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(0), + ValType::I64 => Instruction::I64Const(0), + ValType::F32 => Instruction::F32Const(0.0), + ValType::F64 => Instruction::F64Const(0.0), + ValType::V128 => Instruction::V128Const(0), + ValType::FuncRef | ValType::ExternRef => Instruction::RefNull(ty), + } +} + +fn eq_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Eq, + ValType::F64 => Instruction::F64Eq, + ValType::I32 => Instruction::I32Eq, + ValType::I64 => Instruction::I64Eq, + _ => panic!("not a numeric type"), + } +} + +fn eqz_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Eqz, + ValType::I64 => Instruction::I64Eqz, + _ => panic!("not an integer type"), + } +} + +fn type_of_integer_operation(inst: &Instruction) -> ValType { + match inst { + Instruction::I32DivU + | Instruction::I32DivS + | Instruction::I32RemU + | Instruction::I32RemS => ValType::I32, + Instruction::I64RemU + | Instruction::I64DivU + | Instruction::I64DivS + | Instruction::I64RemS => ValType::I64, + _ => panic!("not integer division or remainder"), + } +} + +fn type_of_float_conversion(inst: &Instruction) -> ValType { + match inst { + Instruction::I32TruncF32S + | Instruction::I32TruncF32U + | Instruction::I64TruncF32S + | Instruction::I64TruncF32U => ValType::F32, + Instruction::I32TruncF64S + | Instruction::I32TruncF64U + | Instruction::I64TruncF64S + | Instruction::I64TruncF64U => ValType::F64, + _ => panic!("not a float -> integer conversion"), + } +} + +fn min_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + // This is the minimum float value that is representable as an i64 + let min_f64 = -9_223_372_036_854_775_000f64; + let min_f32 = -9_223_372_000_000_000_000f32; + + // This is the minimum float value that is representable as as i32 + let min_f32_as_i32 = -2_147_483_500f32; + match inst { + Instruction::I32TruncF32S => Instruction::F32Const(min_f32_as_i32), + Instruction::I32TruncF32U => Instruction::F32Const(0.0), + Instruction::I64TruncF32S => Instruction::F32Const(min_f32), + Instruction::I64TruncF32U => Instruction::F32Const(0.0), + Instruction::I32TruncF64S => Instruction::F64Const(i32::MIN as f64), + Instruction::I32TruncF64U => Instruction::F64Const(0.0), + Instruction::I64TruncF64S => Instruction::F64Const(min_f64), + Instruction::I64TruncF64U => Instruction::F64Const(0.0), + _ => panic!("not a trunc instruction"), + } +} + +fn max_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + // This is the maximum float value that is representable as as i64 + let max_f64_as_i64 = 9_223_372_036_854_775_000f64; + let max_f32_as_i64 = 9_223_371_500_000_000_000f32; + + // This is the maximum float value that is representable as as i32 + let max_f32_as_i32 = 2_147_483_500f32; + match inst { + Instruction::I32TruncF32S | Instruction::I32TruncF32U => { + Instruction::F32Const(max_f32_as_i32) + } + Instruction::I64TruncF32S | Instruction::I64TruncF32U => { + Instruction::F32Const(max_f32_as_i64) + } + Instruction::I32TruncF64S | Instruction::I32TruncF64U => { + Instruction::F64Const(i32::MAX as f64) + } + Instruction::I64TruncF64S | Instruction::I64TruncF64U => { + Instruction::F64Const(max_f64_as_i64) + } + _ => panic!("not a trunc instruction"), + } +} + +fn type_of_memory_access(inst: &Instruction) -> ValType { + match inst { + Instruction::I32Load(_) + | Instruction::I32Load8S(_) + | Instruction::I32Load8U(_) + | Instruction::I32Load16S(_) + | Instruction::I32Load16U(_) + | Instruction::I32Store(_) + | Instruction::I32Store8(_) + | Instruction::I32Store16(_) => ValType::I32, + + Instruction::I64Load(_) + | Instruction::I64Load8S(_) + | Instruction::I64Load8U(_) + | Instruction::I64Load16S(_) + | Instruction::I64Load16U(_) + | Instruction::I64Load32S(_) + | Instruction::I64Load32U(_) + | Instruction::I64Store(_) + | Instruction::I64Store8(_) + | Instruction::I64Store16(_) + | Instruction::I64Store32(_) => ValType::I64, + + Instruction::F32Load(_) | Instruction::F32Store(_) => ValType::F32, + + Instruction::F64Load(_) | Instruction::F64Store(_) => ValType::F64, + + Instruction::V128Load { .. } + | Instruction::V128Load8x8S { .. } + | Instruction::V128Load8x8U { .. } + | Instruction::V128Load16x4S { .. } + | Instruction::V128Load16x4U { .. } + | Instruction::V128Load32x2S { .. } + | Instruction::V128Load32x2U { .. } + | Instruction::V128Load8Splat { .. } + | Instruction::V128Load16Splat { .. } + | Instruction::V128Load32Splat { .. } + | Instruction::V128Load64Splat { .. } + | Instruction::V128Load32Zero { .. } + | Instruction::V128Load64Zero { .. } + | Instruction::V128Store { .. } => ValType::V128, + + _ => panic!("not a memory access instruction"), + } +} + +fn int_min_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(i32::MIN), + ValType::I64 => Instruction::I64Const(i64::MIN), + _ => panic!("not an int type"), + } +} + +fn int_const_inst<'a>(ty: ValType, x: i64) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(x as i32), + ValType::I64 => Instruction::I64Const(x), + _ => panic!("not an int type"), + } +} + +fn int_mul_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Mul, + ValType::I64 => Instruction::I64Mul, + _ => panic!("not an int type"), + } +} + +fn int_add_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Add, + ValType::I64 => Instruction::I64Add, + _ => panic!("not an int type"), + } +} + +fn int_le_u_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32LeU, + ValType::I64 => Instruction::I64LeU, + _ => panic!("not an int type"), + } +} + +fn int_le_s_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32LeS, + ValType::I64 => Instruction::I64LeS, + _ => panic!("not an int type"), + } +} + +fn ne_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Ne, + ValType::I64 => Instruction::I64Ne, + ValType::F32 => Instruction::F32Ne, + ValType::F64 => Instruction::F64Ne, + _ => panic!("not a numeric type"), + } +} + +fn flt_lt_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Lt, + ValType::F64 => Instruction::F64Lt, + _ => panic!("not a float type"), + } +} + +fn flt_gt_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Gt, + ValType::F64 => Instruction::F64Gt, + _ => panic!("not a float type"), + } +} + +fn flt_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Const(f32::INFINITY), + ValType::F64 => Instruction::F64Const(f64::INFINITY), + _ => panic!("not a float type"), + } +} + +fn flt_neg_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Const(f32::NEG_INFINITY), + ValType::F64 => Instruction::F64Const(f64::NEG_INFINITY), + _ => panic!("not a float type"), + } +} + +fn size_of_type_in_memory(ty: ValType) -> u64 { + match ty { + ValType::I32 => 4, + ValType::I64 => 8, + ValType::F32 => 4, + ValType::F64 => 8, + ValType::V128 => 16, + ValType::FuncRef | ValType::ExternRef => panic!("not a memory type"), + } +} diff --git a/crates/wasm-smith/src/core/no_traps.rs b/crates/wasm-smith/src/core/no_traps.rs deleted file mode 100755 index b7521df6c0..0000000000 --- a/crates/wasm-smith/src/core/no_traps.rs +++ /dev/null @@ -1,823 +0,0 @@ -use crate::core::*; -use wasm_encoder::{BlockType, Instruction, ValType}; - -const WASM_PAGE_SIZE: u64 = 65_536; - -/// The OpCode is not supported -#[derive(Debug)] -pub struct NotSupported<'a> { - opcode: wasm_encoder::Instruction<'a>, -} - -impl std::error::Error for NotSupported<'_> {} -impl std::fmt::Display for NotSupported<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!( - f, - "Opcode not supported for no-trapping mode: {:?}", - self.opcode - ) - } -} - -impl Module { - /// Ensure that this generated module will never trap. - /// - /// This will take a number of approaches to avoid traps, such as - /// - /// * mask loads' and stores' addresses to the associated memory's size - /// - /// * mask `table.get`s' and `table.set`'s index to the associated table's size - /// - /// * ensure that a divisor is never zero - /// - /// * replace `unreachable`s with dummy-value `return`s - /// - /// * Masking data and element segments' offsets to be in bounds of their - /// associated tables or memories - pub fn no_traps(&mut self) -> std::result::Result<(), NotSupported> { - self.no_trapping_segments(); - - let import_count = self - .imports - .iter() - .filter(|imp| match imp.entity_type { - EntityType::Func(_, _) => true, - _ => false, - }) - .count(); - for (i, code) in self.code.iter_mut().enumerate() { - let this_func_ty = &self.funcs[i + import_count].1; - let mut new_insts = vec![]; - - let insts = match &mut code.instructions { - Instructions::Generated(is) => std::mem::replace(is, vec![]), - Instructions::Arbitrary(_) => unreachable!(), - }; - - for inst in insts { - match inst { - // Replace `unreachable` with an early return of dummy values. - // - // We *could* instead abstractly interpret all these - // instructions and maintain a stack of types (the way the - // validation algorithm does) and insert dummy values - // whenever an instruction expects a value of type `T` but - // there is an `unreachable` on the stack. This would allow - // us to keep executing the rest of the code following the - // `unreachable`, but also is a ton more work, and it isn't - // clear that it would pay for itself. - Instruction::Unreachable => { - for ty in &this_func_ty.results { - new_insts.push(dummy_value_inst(*ty)); - } - new_insts.push(Instruction::Return); - } - - // We have no way to reflect, at run time, on a `funcref` in - // the `i`th slot in a table and dynamically avoid trapping - // `call_indirect`s. Therefore, we can't emit *any* - // `call_indirect` instructions. Instead, we consume the - // arguments and generate dummy results. - Instruction::CallIndirect { ty, table: _ } => { - // When we can, avoid emitting `drop`s to consume the - // arguments when possible, since dead code isn't - // usually an interesting thing to give to a Wasm - // compiler. Instead, prefer writing them to the first - // page of the first memory if possible. - let callee_func_ty = match &self.types[ty as usize] { - Type::Func(f) => f, - }; - let can_store_args_to_memory = callee_func_ty.params.len() - < usize::try_from(WASM_PAGE_SIZE).unwrap() - && self.memories.get(0).map_or(false, |m| m.minimum > 0); - let memory_64 = self.memories.get(0).map_or(false, |m| m.memory64); - let address = if memory_64 { - Instruction::I64Const(0) - } else { - Instruction::I32Const(0) - }; - let memarg = wasm_encoder::MemArg { - offset: 0, - align: 0, - memory_index: 0, - }; - - // handle table index if we are able, otherwise drop it - if can_store_args_to_memory { - let val_to_store = - u32::try_from(this_func_ty.params.len() + code.locals.len()) - .unwrap(); - code.locals.push(ValType::I32); - new_insts.push(Instruction::LocalSet(val_to_store)); - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::I32Store(memarg)); - } else { - new_insts.push(Instruction::Drop); - } - - for ty in callee_func_ty.params.iter().rev() { - let val_to_store = - u32::try_from(this_func_ty.params.len() + code.locals.len()) - .unwrap(); - - if let ValType::I32 - | ValType::I64 - | ValType::F32 - | ValType::F64 - | ValType::V128 = ty - { - if can_store_args_to_memory { - code.locals.push(*ty); - new_insts.push(Instruction::LocalSet(val_to_store)); - } - } - match ty { - ValType::I32 if can_store_args_to_memory => { - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::I32Store(memarg)); - } - ValType::I64 if can_store_args_to_memory => { - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::I64Store(memarg)); - } - ValType::F32 if can_store_args_to_memory => { - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::F32Store(memarg)); - } - ValType::F64 if can_store_args_to_memory => { - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::F64Store(memarg)); - } - ValType::V128 if can_store_args_to_memory => { - new_insts.push(address.clone()); - new_insts.push(Instruction::LocalGet(val_to_store)); - new_insts.push(Instruction::V128Store(memarg)); - } - _ => { - new_insts.push(Instruction::Drop); - } - } - } - - for ty in &callee_func_ty.results { - new_insts.push(dummy_value_inst(*ty)); - } - } - - // For loads, we dynamically check whether the load will - // trap, and if it will then we generate a dummy value to - // use instead. - Instruction::I32Load(memarg) - | Instruction::I64Load(memarg) - | Instruction::F32Load(memarg) - | Instruction::F64Load(memarg) - | Instruction::I32Load8S(memarg) - | Instruction::I32Load8U(memarg) - | Instruction::I32Load16S(memarg) - | Instruction::I32Load16U(memarg) - | Instruction::I64Load8S(memarg) - | Instruction::I64Load8U(memarg) - | Instruction::I64Load16S(memarg) - | Instruction::I64Load16U(memarg) - | Instruction::I64Load32S(memarg) - | Instruction::I64Load32U(memarg) - | Instruction::V128Load(memarg) - | Instruction::V128Load8x8S(memarg) - | Instruction::V128Load8x8U(memarg) - | Instruction::V128Load16x4S(memarg) - | Instruction::V128Load16x4U(memarg) - | Instruction::V128Load32x2S(memarg) - | Instruction::V128Load32x2U(memarg) - | Instruction::V128Load8Splat(memarg) - | Instruction::V128Load16Splat(memarg) - | Instruction::V128Load32Splat(memarg) - | Instruction::V128Load64Splat(memarg) - | Instruction::V128Load32Zero(memarg) - | Instruction::V128Load64Zero(memarg) => { - let memory = &self.memories[memarg.memory_index as usize]; - let address_type = if memory.memory64 { - ValType::I64 - } else { - ValType::I32 - }; - // Add a temporary local to hold this load's address. - let address_local = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(address_type); - - // Add a temporary local to hold the result of this load. - let load_type = type_of_memory_access(&inst); - let result_local = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(load_type); - - // [address:address_type] - new_insts.push(Instruction::LocalSet(address_local)); - // [] - new_insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); - { - // [] - new_insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); - { - // [] - new_insts.push(Instruction::MemorySize(memarg.memory_index)); - // [mem_size_in_pages:address_type] - new_insts.push(int_const_inst(address_type, 65_536)); - // [mem_size_in_pages:address_type wasm_page_size:address_type] - new_insts.push(int_mul_inst(address_type)); - // [mem_size_in_bytes:address_type] - new_insts.push(int_const_inst( - address_type, - (memarg.offset + size_of_type_in_memory(load_type)) as i64, - )); - // [mem_size_in_bytes:address_type offset_and_size:address_type] - new_insts.push(Instruction::LocalGet(address_local)); - // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] - new_insts.push(int_add_inst(address_type)); - // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] - new_insts.push(int_le_u_inst(address_type)); - // [load_will_trap:i32] - new_insts.push(Instruction::BrIf(0)); - // [] - new_insts.push(Instruction::LocalGet(address_local)); - // [address:address_type] - new_insts.push(inst); - // [result:load_type] - new_insts.push(Instruction::LocalSet(result_local)); - // [] - new_insts.push(Instruction::Br(1)); - // - } - // [] - new_insts.push(Instruction::End); - // [] - new_insts.push(dummy_value_inst(load_type)); - // [dummy_value:load_type] - new_insts.push(Instruction::LocalSet(result_local)); - // [] - } - // [] - new_insts.push(Instruction::End); - // [] - new_insts.push(Instruction::LocalGet(result_local)); - // [result:load_type] - } - - // Stores are similar to loads: we check whether the store - // will trap, and if it will then we just drop the value. - Instruction::I32Store(memarg) - | Instruction::I64Store(memarg) - | Instruction::F32Store(memarg) - | Instruction::F64Store(memarg) - | Instruction::I32Store8(memarg) - | Instruction::I32Store16(memarg) - | Instruction::I64Store8(memarg) - | Instruction::I64Store16(memarg) - | Instruction::I64Store32(memarg) - | Instruction::V128Store(memarg) => { - let memory = &self.memories[memarg.memory_index as usize]; - let address_type = if memory.memory64 { - ValType::I64 - } else { - ValType::I32 - }; - - // Add a temporary local to hold this store's address. - let address_local = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(address_type); - - // Add a temporary local to hold the value to store. - let store_type = type_of_memory_access(&inst); - let value_local = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(store_type); - - // [address:address_type value:store_type] - new_insts.push(Instruction::LocalSet(value_local)); - // [address:address_type] - new_insts.push(Instruction::LocalSet(address_local)); - // [] - new_insts.push(Instruction::MemorySize(memarg.memory_index)); - // [mem_size_in_pages:address_type] - new_insts.push(int_const_inst(address_type, 65_536)); - // [mem_size_in_pages:address_type wasm_page_size:address_type] - new_insts.push(int_mul_inst(address_type)); - // [mem_size_in_bytes:address_type] - new_insts.push(int_const_inst( - address_type, - (memarg.offset + size_of_type_in_memory(store_type)) as i64, - )); - // [mem_size_in_bytes:address_type offset_and_size:address_type] - new_insts.push(Instruction::LocalGet(address_local)); - // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] - new_insts.push(int_add_inst(address_type)); - // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] - new_insts.push(int_le_u_inst(address_type)); - // [store_will_trap:i32] - new_insts.push(Instruction::If(BlockType::Empty)); - new_insts.push(Instruction::Else); - { - // [] - new_insts.push(Instruction::LocalGet(address_local)); - // [address:address_type] - new_insts.push(Instruction::LocalGet(value_local)); - // [address:address_type value:store_type] - new_insts.push(inst); - // [] - } - // [] - new_insts.push(Instruction::End); - } - - Instruction::V128Load8Lane { memarg: _, lane: _ } - | Instruction::V128Load16Lane { memarg: _, lane: _ } - | Instruction::V128Load32Lane { memarg: _, lane: _ } - | Instruction::V128Load64Lane { memarg: _, lane: _ } - | Instruction::V128Store8Lane { memarg: _, lane: _ } - | Instruction::V128Store16Lane { memarg: _, lane: _ } - | Instruction::V128Store32Lane { memarg: _, lane: _ } - | Instruction::V128Store64Lane { memarg: _, lane: _ } => { - return Err(NotSupported { opcode: inst }) - } - - Instruction::MemoryCopy { - src_mem: _, - dst_mem: _, - } - | Instruction::MemoryFill(_) - | Instruction::MemoryInit { - mem: _, - data_index: _, - } => return Err(NotSupported { opcode: inst }), - - // Unsigned integer division and remainder will trap when - // the divisor is 0. To avoid the trap, we will set any 0 - // divisors to 1 prior to the operation. - // - // The code below is equivalent to this expression: - // - // local.set $temp_divisor - // (select (i32.eqz (local.get $temp_divisor) (i32.const 1) (local.get $temp_divisor)) - Instruction::I32RemU - | Instruction::I64RemU - | Instruction::I64DivU - | Instruction::I32DivU => { - let op_type = type_of_integer_operation(&inst); - let temp_divisor = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(op_type); - - // [dividend:op_type divisor:op_type] - new_insts.push(Instruction::LocalSet(temp_divisor)); - // [dividend:op_type] - new_insts.push(int_const_inst(op_type, 1)); - // [dividend:op_type 1:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type 1:op_type divisor:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type 1:op_type divisor:op_type divisor:op_type] - new_insts.push(eqz_inst(op_type)); - // [dividend:op_type 1:op_type divisor:op_type is_zero:i32] - new_insts.push(Instruction::Select); - // [dividend:op_type divisor:op_type] - new_insts.push(inst); - // [result:op_type] - } - - // Signed division and remainder will trap in the following instances: - // - The divisor is 0 - // - The result of the division is 2^(n-1) - Instruction::I32DivS - | Instruction::I32RemS - | Instruction::I64DivS - | Instruction::I64RemS => { - // If divisor is 0, replace with 1 - let op_type = type_of_integer_operation(&inst); - let temp_divisor = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(op_type); - - // [dividend:op_type divisor:op_type] - new_insts.push(Instruction::LocalSet(temp_divisor)); - // [dividend:op_type] - new_insts.push(int_const_inst(op_type, 1)); - // [dividend:op_type 1:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type 1:op_type divisor:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type 1:op_type divisor:op_type divisor:op_type] - new_insts.push(eqz_inst(op_type)); - // [dividend:op_type 1:op_type divisor:op_type is_zero:i32] - new_insts.push(Instruction::Select); - // [dividend:op_type divisor:op_type] - - // If dividend and divisor are -int.max and -1, replace - // divisor with 1. - let temp_dividend = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(op_type); - new_insts.push(Instruction::LocalSet(temp_divisor)); - // [dividend:op_type] - new_insts.push(Instruction::LocalSet(temp_dividend)); - // [] - new_insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); - { - new_insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); - { - // [] - new_insts.push(Instruction::LocalGet(temp_dividend)); - // [dividend:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type divisor:op_type] - new_insts.push(Instruction::LocalSet(temp_divisor)); - // [dividend:op_type] - new_insts.push(Instruction::LocalTee(temp_dividend)); - // [dividend:op_type] - new_insts.push(int_min_const_inst(op_type)); - // [dividend:op_type int_min:op_type] - new_insts.push(int_ne_inst(op_type)); - // [not_int_min:i32] - new_insts.push(Instruction::BrIf(0)); - // [] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [divisor:op_type] - new_insts.push(int_const_inst(op_type, -1)); - // [divisor:op_type -1:op_type] - new_insts.push(int_ne_inst(op_type)); - // [not_neg_one:i32] - new_insts.push(Instruction::BrIf(0)); - // [] - new_insts.push(int_const_inst(op_type, 1)); - // [divisor:op_type] - new_insts.push(Instruction::LocalSet(temp_divisor)); - // [] - new_insts.push(Instruction::Br(1)); - } - // [] - new_insts.push(Instruction::End); - } - // [] - new_insts.push(Instruction::End); - // [] - new_insts.push(Instruction::LocalGet(temp_dividend)); - // [dividend:op_type] - new_insts.push(Instruction::LocalGet(temp_divisor)); - // [dividend:op_type divisor:op_type] - new_insts.push(inst); - } - - Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U => { - // If NaN or ±inf, replace with dummy value - let conv_type = type_of_float_conversion(&inst); - let temp_float = - u32::try_from(this_func_ty.params.len() + code.locals.len()).unwrap(); - code.locals.push(conv_type); - - // [input:conv_type] - new_insts.push(Instruction::LocalTee(temp_float)); - // [input:conv_type] - new_insts.push(flt_nan_const_inst(conv_type)); - // [input:conv_type NaN:conv_type] - new_insts.push(eq_inst(conv_type)); - // [is_nan:i32] - new_insts.push(Instruction::LocalGet(temp_float)); - // [is_nan:i32 input:conv_type] - new_insts.push(flt_inf_const_inst(conv_type)); - // [is_nan:i32 input:conv_type inf:conv_type] - new_insts.push(eq_inst(conv_type)); - // [is_nan:i32 is_inf:i32] - new_insts.push(Instruction::LocalGet(temp_float)); - // [is_nan:i32 is_inf:i32 input:conv_type] - new_insts.push(flt_neg_inf_const_inst(conv_type)); - // [is_nan:i32 is_inf:i32 input:conv_type neg_inf:conv_type] - new_insts.push(eq_inst(conv_type)); - // [is_nan:i32 is_inf:i32 is_neg_inf:i32] - new_insts.push(Instruction::I32Or); - // [is_nan:i32 is_±inf:i32] - new_insts.push(Instruction::I32Or); - // [is_nan_or_inf:i32] - new_insts.push(Instruction::If(BlockType::Empty)); - { - // [] - new_insts.push(dummy_value_inst(conv_type)); - // [0:conv_type] - new_insts.push(Instruction::LocalSet(temp_float)); - // [] - } - new_insts.push(Instruction::End); - // [] - new_insts.push(Instruction::LocalGet(temp_float)); - - // [input_or_0:conv_type] - new_insts.push(inst); - } - Instruction::TableFill(_) - | Instruction::TableSet(_) - | Instruction::TableGet(_) - | Instruction::TableInit { - elem_index: _, - table: _, - } - | Instruction::TableCopy { - src_table: _, - dst_table: _, - } => return Err(NotSupported { opcode: inst }), - - // None of the other instructions can trap, so just copy them over. - inst => new_insts.push(inst), - } - } - - code.instructions = Instructions::Generated(new_insts); - } - Ok(()) - } - - /// Mask data and element segments' offsets to be in bounds of their - /// associated tables and memories. - fn no_trapping_segments(&mut self) { - for data in &mut self.data { - match &mut data.kind { - DataSegmentKind::Passive => continue, - DataSegmentKind::Active { - memory_index, - offset, - } => { - let mem = &mut self.memories[usize::try_from(*memory_index).unwrap()]; - - // Ensure that all memories have at least one - // page. Otherwise, if we had a zero-minimum memory, then we - // wouldn't be able to mask the initializers to be - // definitely in-bounds. - mem.minimum = std::cmp::max(1, mem.minimum); - mem.maximum = mem.maximum.map(|n| std::cmp::max(mem.minimum, n)); - - // Make sure that the data segment can fit into the memory. - data.init - .truncate(usize::try_from(mem.minimum * WASM_PAGE_SIZE).unwrap()); - let data_len = data.init.len() as u64; - - match offset { - Offset::Const64(n) => { - let n = *n as u64; - let n = n - .checked_rem(mem.minimum * WASM_PAGE_SIZE - data_len) - .unwrap_or(0); - *offset = Offset::Const64(n as i64); - } - Offset::Const32(n) => { - let n = *n as u64; - let n = n - .checked_rem(mem.minimum * WASM_PAGE_SIZE - data_len) - .unwrap_or(0); - let n = u32::try_from(n).unwrap(); - *offset = Offset::Const32(n as i32); - } - Offset::Global(_) => *offset = Offset::Const32(0), - } - } - } - } - - for elem in &mut self.elems { - match &mut elem.kind { - ElementKind::Passive | ElementKind::Declared => continue, - ElementKind::Active { table, offset } => { - let table = table.unwrap_or(0); - let table = usize::try_from(table).unwrap(); - let table = &mut self.tables[table]; - - // Ensure that we have room for at least one element. See - // comment above. - table.minimum = std::cmp::max(1, table.minimum); - table.maximum = table.maximum.map(|n| std::cmp::max(table.minimum, n)); - - // Make sure that the element segment can fit into the - // table. - let elem_len = match &mut elem.items { - Elements::Functions(fs) => { - fs.truncate(usize::try_from(table.minimum).unwrap()); - u32::try_from(fs.len()).unwrap() - } - Elements::Expressions(es) => { - es.truncate(usize::try_from(table.minimum).unwrap()); - u32::try_from(es.len()).unwrap() - } - }; - - match offset { - Offset::Const32(n) => { - let n = *n as u32; - let n = n.checked_rem(table.minimum - elem_len).unwrap_or(0); - *offset = Offset::Const32(n as i32); - } - Offset::Global(_) => { - *offset = Offset::Const32(0); - } - _ => unreachable!(), - } - } - } - } - } -} - -fn dummy_value_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Const(0), - ValType::I64 => Instruction::I64Const(0), - ValType::F32 => Instruction::F32Const(0.0), - ValType::F64 => Instruction::F64Const(0.0), - ValType::V128 => Instruction::V128Const(0), - ValType::FuncRef | ValType::ExternRef => Instruction::RefNull(ty), - } -} - -fn eq_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::F32 => Instruction::F32Eq, - ValType::F64 => Instruction::F64Eq, - ValType::I32 => Instruction::I32Eq, - ValType::I64 => Instruction::I64Eq, - _ => panic!("not a numeric type"), - } -} - -fn eqz_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Eqz, - ValType::I64 => Instruction::I64Eqz, - _ => panic!("not an integer type"), - } -} - -fn type_of_integer_operation(inst: &Instruction) -> ValType { - match inst { - Instruction::I32DivU - | Instruction::I32DivS - | Instruction::I32RemU - | Instruction::I32RemS => ValType::I32, - Instruction::I64RemU - | Instruction::I64DivU - | Instruction::I64DivS - | Instruction::I64RemS => ValType::I64, - _ => panic!("not integer division or remainder"), - } -} - -fn type_of_float_conversion(inst: &Instruction) -> ValType { - match inst { - Instruction::I32TruncF32S - | Instruction::I32TruncF32U - | Instruction::I64TruncF32S - | Instruction::I64TruncF32U => ValType::F32, - Instruction::I32TruncF64S - | Instruction::I32TruncF64U - | Instruction::I64TruncF64S - | Instruction::I64TruncF64U => ValType::F64, - _ => panic!("not a float -> integer conversion"), - } -} - -fn type_of_memory_access(inst: &Instruction) -> ValType { - match inst { - Instruction::I32Load(_) - | Instruction::I32Load8S(_) - | Instruction::I32Load8U(_) - | Instruction::I32Load16S(_) - | Instruction::I32Load16U(_) - | Instruction::I32Store(_) - | Instruction::I32Store8(_) - | Instruction::I32Store16(_) => ValType::I32, - - Instruction::I64Load(_) - | Instruction::I64Load8S(_) - | Instruction::I64Load8U(_) - | Instruction::I64Load16S(_) - | Instruction::I64Load16U(_) - | Instruction::I64Load32S(_) - | Instruction::I64Load32U(_) - | Instruction::I64Store(_) - | Instruction::I64Store8(_) - | Instruction::I64Store16(_) - | Instruction::I64Store32(_) => ValType::I64, - - Instruction::F32Load(_) | Instruction::F32Store(_) => ValType::F32, - - Instruction::F64Load(_) | Instruction::F64Store(_) => ValType::F64, - - Instruction::V128Load(_) - | Instruction::V128Load8x8S(_) - | Instruction::V128Load8x8U(_) - | Instruction::V128Load16x4S(_) - | Instruction::V128Load16x4U(_) - | Instruction::V128Load32x2S(_) - | Instruction::V128Load32x2U(_) - | Instruction::V128Load8Splat(_) - | Instruction::V128Load16Splat(_) - | Instruction::V128Load32Splat(_) - | Instruction::V128Load64Splat(_) - | Instruction::V128Load32Zero(_) - | Instruction::V128Load64Zero(_) - | Instruction::V128Store(_) => ValType::V128, - - _ => panic!("not a memory access instruction"), - } -} - -fn int_min_const_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Const(i32::MIN), - ValType::I64 => Instruction::I64Const(i64::MIN), - _ => panic!("not an int type"), - } -} - -fn int_const_inst<'a>(ty: ValType, x: i64) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Const(x as i32), - ValType::I64 => Instruction::I64Const(x), - _ => panic!("not an int type"), - } -} - -fn int_mul_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Mul, - ValType::I64 => Instruction::I64Mul, - _ => panic!("not an int type"), - } -} - -fn int_add_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Add, - ValType::I64 => Instruction::I64Add, - _ => panic!("not an int type"), - } -} - -fn int_le_u_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32LeU, - ValType::I64 => Instruction::I64LeU, - _ => panic!("not an int type"), - } -} - -fn int_ne_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::I32 => Instruction::I32Ne, - ValType::I64 => Instruction::I64Ne, - _ => panic!("not an int type"), - } -} - -fn flt_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::F32 => Instruction::F32Const(f32::INFINITY), - ValType::F64 => Instruction::F64Const(f64::INFINITY), - _ => panic!("not a float type"), - } -} - -fn flt_neg_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::F32 => Instruction::F32Const(f32::NEG_INFINITY), - ValType::F64 => Instruction::F64Const(f64::NEG_INFINITY), - _ => panic!("not a float type"), - } -} - -fn flt_nan_const_inst<'a>(ty: ValType) -> Instruction<'a> { - match ty { - ValType::F32 => Instruction::F32Const(f32::NAN), - ValType::F64 => Instruction::F64Const(f64::NAN), - _ => panic!("not a float type"), - } -} - -fn size_of_type_in_memory(ty: ValType) -> u64 { - match ty { - ValType::I32 => 4, - ValType::I64 => 8, - ValType::F32 => 4, - ValType::F64 => 8, - ValType::V128 => 16, - ValType::FuncRef | ValType::ExternRef => panic!("not a memory type"), - } -} diff --git a/crates/wasm-smith/src/lib.rs b/crates/wasm-smith/src/lib.rs index 22cc50b5eb..ff4f360290 100644 --- a/crates/wasm-smith/src/lib.rs +++ b/crates/wasm-smith/src/lib.rs @@ -59,8 +59,7 @@ mod config; mod core; pub use crate::core::{ - no_traps::NotSupported, ConfiguredModule, InstructionKind, InstructionKinds, - MaybeInvalidModule, Module, + ConfiguredModule, InstructionKind, InstructionKinds, MaybeInvalidModule, Module, }; use arbitrary::{Result, Unstructured}; pub use component::{Component, ConfiguredComponent}; diff --git a/crates/wasm-smith/tests/core.rs b/crates/wasm-smith/tests/core.rs index 97af657420..e5146cc459 100644 --- a/crates/wasm-smith/tests/core.rs +++ b/crates/wasm-smith/tests/core.rs @@ -203,13 +203,13 @@ fn smoke_test_no_trapping_mode() { let mut buf = vec![0; 2048]; for _ in 0..1024 { rng.fill_bytes(&mut buf); - let u = Unstructured::new(&buf); - if let Ok(mut module) = Module::arbitrary_take_rest(u) { - if module.no_traps().is_ok() { - let wasm_bytes = module.to_bytes(); - let mut validator = Validator::new_with_features(wasm_features()); - validate(&mut validator, &wasm_bytes); - } + let mut u = Unstructured::new(&buf); + let mut cfg = SwarmConfig::arbitrary(&mut u).unwrap(); + cfg.disallow_traps = true; + if let Ok(module) = Module::new(cfg, &mut u) { + let wasm_bytes = module.to_bytes(); + let mut validator = Validator::new_with_features(wasm_features()); + validate(&mut validator, &wasm_bytes); } } } diff --git a/fuzz/Cargo.toml b/fuzz/Cargo.toml index 58f6d426a3..6975f65dd1 100644 --- a/fuzz/Cargo.toml +++ b/fuzz/Cargo.toml @@ -69,3 +69,10 @@ path = "fuzz_targets/mutate.rs" test = false doc = false bench = false + +[[bin]] +name = "no-traps" +path = "fuzz_targets/no-traps.rs" +test = false +doc = false +required-features = ["wasmtime"] diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs new file mode 100644 index 0000000000..f4e73f2ebe --- /dev/null +++ b/fuzz/fuzz_targets/no-traps.rs @@ -0,0 +1,110 @@ +#![no_main] + +use arbitrary::Unstructured; +use libfuzzer_sys::fuzz_target; +use wasm_smith::SwarmConfig; +use wasmtime::*; + +#[cfg(feature = "wasmtime")] +#[path = "../../crates/fuzz-stats/src/lib.rs"] +pub mod fuzz_stats; + +// Define a fuzz target that accepts arbitrary +// `Module`s as input. +fuzz_target!(|data: &[u8]| { + // Use data to generate a random wasm module + let mut u = Unstructured::new(data); + let (wasm_bytes, config) = match wasm_tools_fuzz::generate_valid_module(&mut u, |config, _| { + config.disallow_traps = true; + config.threads_enabled = false; + config.exceptions_enabled = false; + config.max_memory_pages = config.max_memory_pages.min(100); + Ok(()) + }) { + Ok(m) => m, + Err(_) => return, + }; + validate_module(config, &wasm_bytes); + + // Configure the engine, module, and store + let mut eng_conf = Config::new(); + eng_conf.wasm_memory64(true); + eng_conf.wasm_multi_memory(true); + eng_conf.consume_fuel(true); + let engine = Engine::new(&eng_conf).unwrap(); + let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); + let mut store = Store::new( + &engine, + fuzz_stats::limits::StoreLimits { + remaining_memory: 1 << 30, + oom: false, + }, + ); + store.limiter(|s| s as &mut dyn ResourceLimiter); + set_fuel(&mut store, 1_000); + + // Instantiate the module + let inst_result = fuzz_stats::dummy::dummy_imports(&mut store, &module) + .and_then(|imports| Instance::new(&mut store, &module, &imports)); + let instance = match inst_result { + Ok(r) => r, + Err(err) if err.to_string().contains("all fuel consumed") => return, + Err(err) => panic!("generated wasm trapped in non-trapping mode: {}", err), + }; + + // Call all exported functions + for export in module.exports() { + match export.ty() { + ExternType::Func(func_ty) => { + let args = fuzz_stats::dummy::dummy_values(func_ty.params()); + let mut results = fuzz_stats::dummy::dummy_values(func_ty.results()); + let func = instance.get_func(&mut store, export.name()).unwrap(); + set_fuel(&mut store, 1_000); + match func.call(&mut store, &args, &mut results) { + Ok(_) => continue, + Err(err) if err.to_string().contains("all fuel consumed") => continue, + Err(err) => panic!("generated wasm trapped in non-trapping mode: {}", err), + } + } + _ => continue, + } + } +}); + +fn validate_module(config: SwarmConfig, wasm_bytes: &Vec) { + // Validate the module or component and assert that it passes validation. + let mut validator = wasmparser::Validator::new_with_features(wasmparser::WasmFeatures { + component_model: false, + multi_value: config.multi_value_enabled, + multi_memory: config.max_memories > 1, + bulk_memory: true, + reference_types: true, + simd: config.simd_enabled, + relaxed_simd: config.relaxed_simd_enabled, + memory64: config.memory64_enabled, + threads: config.threads_enabled, + exceptions: config.exceptions_enabled, + ..wasmparser::WasmFeatures::default() + }); + if let Err(e) = validator.validate_all(wasm_bytes) { + panic!("Invalid module: {}", e); + } +} + +fn set_fuel(store: &mut Store, fuel: u64) { + // This is necessary because consume_fuel below will err if there is <=0 + // fuel in the store. Since we are just using that call to get the current + // amount of fuel AND we are immediately adjusting the fuel to the value we + // can safely add 1 fuel here as a hacky work-around for the time being. + store.add_fuel(1_000).unwrap(); + // Determine the amount of fuel already within the store, if any, and + // add/consume as appropriate to set the remaining amount to` fuel`. + let remaining = store.consume_fuel(0).unwrap(); + if fuel > remaining { + store.add_fuel(fuel - remaining).unwrap(); + } else { + store.consume_fuel(remaining - fuel).unwrap(); + } + // double-check that the store has the expected amount of fuel remaining + assert_eq!(store.consume_fuel(0).unwrap(), fuel); +}