From 1fd44cf153206b8d20a6b6a83c6560ee2a23692e Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 14 Sep 2022 13:29:59 -0400 Subject: [PATCH 01/28] Change CodeBuilder functions to emit multiple instructions Co-authored-by: Nick Fitzgerald --- crates/wasm-smith/src/core/code_builder.rs | 1840 +++++++++++++++----- 1 file changed, 1394 insertions(+), 446 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 74bdcf788f..e3379b6cdf 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -24,7 +24,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; @@ -568,7 +568,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, )>, @@ -849,8 +849,7 @@ impl CodeBuilder<'_> { 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 @@ -1103,15 +1102,32 @@ fn arbitrary_val(ty: ValType, u: &mut Unstructured<'_>) -> Instruction { } } -fn unreachable(_: &mut Unstructured, _: &Module, _: &mut CodeBuilder) -> Result { - Ok(Instruction::Unreachable) +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 +1137,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 +1146,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 +1161,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 +1174,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 +1190,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 +1205,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 +1222,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 +1236,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 +1249,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 +1268,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 +1277,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 +1294,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 +1306,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 +1319,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 +1338,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 +1358,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 +1384,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 +1428,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 +1443,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 +1484,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 +1493,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 +1514,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 +1532,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] @@ -1458,7 +1553,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 +1565,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 +1582,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 +1601,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 +1616,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 +1639,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 +1648,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 +1669,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 +1682,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 +1695,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 +1710,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 +1724,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 +1749,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 +1777,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 +1790,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 +1811,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 +1826,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 +1850,168 @@ 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + instructions.push(Instruction::I64Load32U(memarg)); + Ok(()) } #[inline] @@ -1854,10 +2029,12 @@ 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)) + instructions.push(Instruction::I32Store(memarg)); + Ok(()) } #[inline] @@ -1869,10 +2046,12 @@ 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)) + instructions.push(Instruction::I64Store(memarg)); + Ok(()) } #[inline] @@ -1884,10 +2063,12 @@ 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)) + instructions.push(Instruction::F32Store(memarg)); + Ok(()) } #[inline] @@ -1899,67 +2080,80 @@ 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)) + 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)) + 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)) + 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)) + 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)) + 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)) + 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 +2161,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 +2175,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,7 +2185,8 @@ 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] @@ -2006,7 +2203,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,7 +2214,8 @@ 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] @@ -2032,7 +2231,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 +2240,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] @@ -2078,9 +2279,9 @@ fn memory_copy( u: &mut Unstructured, _module: &Module, builder: &mut CodeBuilder, -) -> Result { - let (src_mem, dst_mem) = if builder.types_on_stack(&[ValType::I64, ValType::I64, ValType::I64]) - { + 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]); ( memory_index(u, builder, ValType::I64)?, @@ -2107,7 +2308,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 +2321,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 +2380,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 +2397,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 +2522,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 +2539,708 @@ 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 { +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, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - Ok(Instruction::I32DivS) + instructions.push(Instruction::I32DivS); + Ok(()) } -fn i32_div_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_div_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::I32DivU) + instructions.push(Instruction::I32DivU); + Ok(()) } -fn i32_rem_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rem_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::I32RemS) + instructions.push(Instruction::I32RemS); + Ok(()) } -fn i32_rem_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i32_rem_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::I32RemU) + 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, + builder: &mut CodeBuilder, + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64DivS) + instructions.push(Instruction::I64DivS); + Ok(()) } -fn i64_div_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_div_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::I64DivU) + instructions.push(Instruction::I64DivU); + Ok(()) } -fn i64_rem_s(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rem_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::I64RemS) + instructions.push(Instruction::I64RemS); + Ok(()) } -fn i64_rem_u(_: &mut Unstructured, _: &Module, builder: &mut CodeBuilder) -> Result { +fn i64_rem_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::I64RemU) + 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 +3248,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 +3421,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 { @@ -2799,20 +3609,24 @@ fn i32_trunc_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::I32TruncF32S) + instructions.push(Instruction::I32TruncF32S); + Ok(()) } fn i32_trunc_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::I32TruncF32U) + instructions.push(Instruction::I32TruncF32U); + Ok(()) } fn nontrapping_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool { @@ -2823,220 +3637,264 @@ fn i32_trunc_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::I32TruncF64S) + instructions.push(Instruction::I32TruncF64S); + Ok(()) } fn i32_trunc_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::I32TruncF64U) + 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, builder: &mut CodeBuilder, -) -> Result { + instructions: &mut Vec, +) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - Ok(Instruction::I64TruncF32S) + instructions.push(Instruction::I64TruncF32S); + Ok(()) } fn i64_trunc_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::I64TruncF32U) + instructions.push(Instruction::I64TruncF32U); + Ok(()) } fn i64_trunc_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::I64TruncF64S) + instructions.push(Instruction::I64TruncF64S); + Ok(()) } fn i64_trunc_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::I64TruncF64U) + 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 +3905,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 +3933,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 { @@ -3255,10 +4139,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 +4156,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,10 +4174,16 @@ 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] @@ -3298,12 +4200,14 @@ 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] @@ -3319,11 +4223,13 @@ 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] @@ -3337,12 +4243,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 +4262,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,12 +4283,14 @@ 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] @@ -3392,14 +4304,16 @@ 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] @@ -3413,7 +4327,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 +4339,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 +4355,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 { @@ -3567,10 +4485,13 @@ 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)) + instructions.push(Instruction::$instruction(memarg)); + Ok(()) } }; } @@ -3593,10 +4514,12 @@ 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)) + instructions.push(Instruction::V128Store(memarg)); + Ok(()) } macro_rules! simd_load_lane { @@ -3605,14 +4528,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 +4553,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 +4571,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 +4605,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 +4636,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 +4657,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 +4673,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 +4690,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(()) } }; } From a9fc52eb102d7b7c8cbf7d0bdecfc2f94461985e Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:23:47 -0400 Subject: [PATCH 02/28] Move no-trapping logic to a code_builder module --- .../src/core/code_builder/no_traps.rs | 524 +++++++++++ crates/wasm-smith/src/core/no_traps.rs | 823 ------------------ 2 files changed, 524 insertions(+), 823 deletions(-) create mode 100644 crates/wasm-smith/src/core/code_builder/no_traps.rs delete mode 100755 crates/wasm-smith/src/core/no_traps.rs 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..6942d52947 --- /dev/null +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -0,0 +1,524 @@ +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(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(Instruction::LocalGet(value_local)); + // [address:address_type value:store_type] + insts.push(inst); + // [] + } + // [] + 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 + 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(flt_nan_const_inst(conv_type)); + // [input:conv_type NaN:conv_type] + insts.push(eq_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] + 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(int_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(int_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::I32Load8_S(memarg) + | Instruction::I32Load8_U(memarg) + | Instruction::I32Load16_S(memarg) + | Instruction::I32Load16_U(memarg) + | Instruction::I64Load8_S(memarg) + | Instruction::I64Load8_U(memarg) + | Instruction::I64Load16_S(memarg) + | Instruction::I64Load16_U(memarg) + | Instruction::I64Load32_S(memarg) + | Instruction::I64Load32_U(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 type_of_memory_access(inst: &Instruction) -> ValType { + match inst { + Instruction::I32Load(_) + | Instruction::I32Load8_S(_) + | Instruction::I32Load8_U(_) + | Instruction::I32Load16_S(_) + | Instruction::I32Load16_U(_) + | Instruction::I32Store(_) + | Instruction::I32Store8(_) + | Instruction::I32Store16(_) => ValType::I32, + + Instruction::I64Load(_) + | Instruction::I64Load8_S(_) + | Instruction::I64Load8_U(_) + | Instruction::I64Load16_S(_) + | Instruction::I64Load16_U(_) + | Instruction::I64Load32_S(_) + | Instruction::I64Load32_U(_) + | 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/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"), - } -} From 0123e51217b75e9e854c7b5c0a22af2d8f6c7496 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 20 Sep 2022 11:25:47 -0400 Subject: [PATCH 03/28] Add configuration option to disallow traps --- crates/wasm-smith/src/config.rs | 10 + crates/wasm-smith/src/core.rs | 11 +- crates/wasm-smith/src/core/code_builder.rs | 395 +++++++++++++++++---- crates/wasm-smith/src/lib.rs | 3 +- 4 files changed, 342 insertions(+), 77 deletions(-) diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 70123716bb..ad266c7618 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -449,6 +449,16 @@ 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. + /// + /// Defaults to `false`. + fn disallow_traps(&self) -> bool { + false + } } /// The default configuration. diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 89cad1e429..4ae6f3713a 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}; @@ -199,6 +198,16 @@ impl Module { type_size: 0, } } + + fn import_count(&self) -> usize { + self.imports + .iter() + .filter(|imp| match imp.entity_type { + EntityType::Func(_, _) => true, + _ => false, + }) + .count() + } } impl<'a, C: Config + Arbitrary<'a>> Arbitrary<'a> for ConfiguredModule { diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index e3379b6cdf..dcaf353efd 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 { ( @@ -931,25 +932,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. @@ -999,6 +990,12 @@ 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<'_>, @@ -1854,7 +1851,11 @@ fn i32_load( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I32)); - instructions.push(Instruction::I32Load(memarg)); + if module.config.disallow_traps() { + no_traps::load(Instruction::I32Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I32Load(memarg)); + } Ok(()) } @@ -1866,7 +1867,11 @@ fn i64_load( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load(memarg)); + if module.config.disallow_traps() { + no_traps::load(Instruction::I64Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I64Load(memarg)); + } Ok(()) } @@ -1878,7 +1883,11 @@ fn f32_load( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::F32)); - instructions.push(Instruction::F32Load(memarg)); + if module.config.disallow_traps() { + no_traps::load(Instruction::F32Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F32Load(memarg)); + } Ok(()) } @@ -1890,7 +1899,11 @@ fn f64_load( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::F64)); - instructions.push(Instruction::F64Load(memarg)); + if module.config.disallow_traps() { + no_traps::load(Instruction::F64Load(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F64Load(memarg)); + } Ok(()) } @@ -1902,7 +1915,16 @@ fn i32_load_8_s( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); - instructions.push(Instruction::I32Load8S(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load8S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load8S(memarg)); + } Ok(()) } @@ -1914,7 +1936,16 @@ fn i32_load_8_u( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); - instructions.push(Instruction::I32Load8U(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load8U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load8U(memarg)); + } Ok(()) } @@ -1926,7 +1957,16 @@ fn i32_load_16_s( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); - instructions.push(Instruction::I32Load16S(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load16S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load16S(memarg)); + } Ok(()) } @@ -1938,7 +1978,16 @@ fn i32_load_16_u( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); - instructions.push(Instruction::I32Load16U(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I32Load16U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Load16U(memarg)); + } Ok(()) } @@ -1950,7 +1999,16 @@ fn i64_load_8_s( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load8S(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load8S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load8S(memarg)); + } Ok(()) } @@ -1962,7 +2020,16 @@ fn i64_load_16_s( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load16S(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load16S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load16S(memarg)); + } Ok(()) } @@ -1974,7 +2041,16 @@ fn i64_load_32_s( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load32S(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load32S(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load32S(memarg)); + } Ok(()) } @@ -1986,7 +2062,16 @@ fn i64_load_8_u( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load8U(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load8U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load8U(memarg)); + } Ok(()) } @@ -1998,7 +2083,16 @@ fn i64_load_16_u( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load16U(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load16U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load16U(memarg)); + } Ok(()) } @@ -2010,7 +2104,16 @@ fn i64_load_32_u( ) -> Result<()> { let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); - instructions.push(Instruction::I64Load32U(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::I64Load32U(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Load32U(memarg)); + } Ok(()) } @@ -2033,7 +2136,11 @@ fn i32_store( ) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - instructions.push(Instruction::I32Store(memarg)); + if module.config.disallow_traps() { + no_traps::store(Instruction::I32Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I32Store(memarg)); + } Ok(()) } @@ -2050,7 +2157,11 @@ fn i64_store( ) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; - instructions.push(Instruction::I64Store(memarg)); + if module.config.disallow_traps() { + no_traps::store(Instruction::I64Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::I64Store(memarg)); + } Ok(()) } @@ -2067,7 +2178,11 @@ fn f32_store( ) -> Result<()> { builder.pop_operands(&[ValType::F32]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - instructions.push(Instruction::F32Store(memarg)); + if module.config.disallow_traps() { + no_traps::store(Instruction::F32Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F32Store(memarg)); + } Ok(()) } @@ -2084,7 +2199,11 @@ fn f64_store( ) -> Result<()> { builder.pop_operands(&[ValType::F64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; - instructions.push(Instruction::F64Store(memarg)); + if module.config.disallow_traps() { + no_traps::store(Instruction::F64Store(memarg), module, builder, instructions); + } else { + instructions.push(Instruction::F64Store(memarg)); + } Ok(()) } @@ -2096,7 +2215,16 @@ fn i32_store_8( ) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0])?; - instructions.push(Instruction::I32Store8(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::I32Store8(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Store8(memarg)); + } Ok(()) } @@ -2108,7 +2236,16 @@ fn i32_store_16( ) -> Result<()> { builder.pop_operands(&[ValType::I32]); let memarg = mem_arg(u, module, builder, &[0, 1])?; - instructions.push(Instruction::I32Store16(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::I32Store16(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I32Store16(memarg)); + } Ok(()) } @@ -2120,7 +2257,16 @@ fn i64_store_8( ) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0])?; - instructions.push(Instruction::I64Store8(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store8(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store8(memarg)); + } Ok(()) } @@ -2132,7 +2278,16 @@ fn i64_store_16( ) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1])?; - instructions.push(Instruction::I64Store16(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store16(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store16(memarg)); + } Ok(()) } @@ -2144,7 +2299,16 @@ fn i64_store_32( ) -> Result<()> { builder.pop_operands(&[ValType::I64]); let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; - instructions.push(Instruction::I64Store32(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::I64Store32(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::I64Store32(memarg)); + } Ok(()) } @@ -2281,7 +2445,8 @@ fn memory_copy( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let (src_mem, dst_mem) = if builder.types_on_stack(&[ValType::I64, ValType::I64, ValType::I64]) { + 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]); ( memory_index(u, builder, ValType::I64)?, @@ -2885,49 +3050,65 @@ fn i32_mul( fn i32_div_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32, ValType::I32]); builder.push_operands(&[ValType::I32]); - instructions.push(Instruction::I32RemU); + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I32RemU, builder, instructions); + } else { + instructions.push(Instruction::I32RemU); + } Ok(()) } @@ -3101,49 +3282,65 @@ fn i64_mul( fn i64_div_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - instructions.push(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, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64, ValType::I64]); builder.push_operands(&[ValType::I64]); - instructions.push(Instruction::I64RemU); + if module.config.disallow_traps() { + no_traps::unsigned_div_rem(Instruction::I64RemU, builder, instructions); + } else { + instructions.push(Instruction::I64RemU); + } Ok(()) } @@ -3607,25 +3804,33 @@ fn nontrapping_f32_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool fn i32_trunc_f32_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - instructions.push(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, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I32]); - instructions.push(Instruction::I32TruncF32U); + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF32U, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF32U); + } Ok(()) } @@ -3635,25 +3840,33 @@ fn nontrapping_f64_on_stack(module: &Module, builder: &mut CodeBuilder) -> bool fn i32_trunc_f64_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - instructions.push(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, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I32]); - instructions.push(Instruction::I32TruncF64U); + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I32TruncF64U, builder, instructions); + } else { + instructions.push(Instruction::I32TruncF64U); + } Ok(()) } @@ -3683,49 +3896,65 @@ fn i64_extend_i32_u( fn i64_trunc_f32_s( _: &mut Unstructured, - _: &Module, + module: &Module, builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - instructions.push(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, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); builder.push_operands(&[ValType::I64]); - instructions.push(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, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - instructions.push(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, instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); builder.push_operands(&[ValType::I64]); - instructions.push(Instruction::I64TruncF64U); + if module.config.disallow_traps() { + no_traps::trunc(Instruction::I64TruncF64U, builder, instructions); + } else { + instructions.push(Instruction::I64TruncF64U); + } Ok(()) } @@ -4490,7 +4719,16 @@ macro_rules! simd_load { ) -> Result<()> { let memarg = mem_arg(u, module, builder, $alignments)?; builder.push_operands(&[ValType::V128]); - instructions.push(Instruction::$instruction(memarg)); + if module.config.disallow_traps() { + no_traps::load( + Instruction::$instruction(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::$instruction(memarg)); + } Ok(()) } }; @@ -4518,7 +4756,16 @@ fn v128_store( ) -> Result<()> { builder.pop_operands(&[ValType::V128]); let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; - instructions.push(Instruction::V128Store(memarg)); + if module.config.disallow_traps() { + no_traps::store( + Instruction::V128Store(memarg), + module, + builder, + instructions, + ); + } else { + instructions.push(Instruction::V128Store(memarg)); + } Ok(()) } 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}; From 77bc09f0c93f4463105db78f4469a4c624823b74 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 20 Sep 2022 12:23:17 -0400 Subject: [PATCH 04/28] Don't generate trapping instructions that haven't been handled yet in non-trapping mode --- crates/wasm-smith/src/config.rs | 2 + crates/wasm-smith/src/core/code_builder.rs | 55 ++++++++++++++++++---- crates/wasm-smith/tests/core.rs | 14 +++--- 3 files changed, 55 insertions(+), 16 deletions(-) diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index ad266c7618..3c8c1ca47b 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -486,6 +486,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, @@ -607,6 +608,7 @@ impl<'a> Arbitrary<'a> for SwarmConfig { available_imports: None, threads_enabled: false, export_everything: false, + disallow_traps: false, }) } } diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index dcaf353efd..e3b1a049e4 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -88,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), @@ -314,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), @@ -1099,6 +1099,11 @@ fn arbitrary_val(ty: ValType, u: &mut Unstructured<'_>) -> Instruction { } } +#[inline] +fn unreachable_valid(module: &Module, _: &mut CodeBuilder) -> bool { + !module.config.disallow_traps() +} + fn unreachable( _: &mut Unstructured, _: &Module, @@ -1538,6 +1543,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() @@ -2357,6 +2369,7 @@ fn memory_grow( 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 @@ -2385,6 +2398,7 @@ fn memory_init( #[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 @@ -2414,6 +2428,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 { @@ -4419,6 +4439,7 @@ fn ref_is_null( 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) @@ -4442,6 +4463,7 @@ fn table_fill( #[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) @@ -4464,6 +4486,7 @@ fn table_set( #[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 } @@ -4525,6 +4548,7 @@ fn table_grow( #[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]) } @@ -4548,6 +4572,7 @@ fn table_copy( #[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]) } @@ -4698,11 +4723,23 @@ fn simd_have_memory_and_offset_and_v128(module: &Module, builder: &mut CodeBuild 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) } +#[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] fn simd_enabled(module: &Module, _: &mut CodeBuilder) -> bool { module.config.simd_enabled() 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); } } } From e21bd9391d4ff3ad4ff82192afc1d1844ebd657e Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 20 Sep 2022 13:32:35 -0400 Subject: [PATCH 05/28] Remove unused import_count function --- crates/wasm-smith/src/core.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index 4ae6f3713a..b35d12c11b 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -198,16 +198,6 @@ impl Module { type_size: 0, } } - - fn import_count(&self) -> usize { - self.imports - .iter() - .filter(|imp| match imp.entity_type { - EntityType::Func(_, _) => true, - _ => false, - }) - .count() - } } impl<'a, C: Config + Arbitrary<'a>> Arbitrary<'a> for ConfiguredModule { From 2fcf98ef4e64f85308604525ba3c724a6acae8a9 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 20 Sep 2022 15:41:06 -0400 Subject: [PATCH 06/28] Update crates/wasm-smith/src/core/code_builder.rs --- crates/wasm-smith/src/core/code_builder.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index e3b1a049e4..785d2da0ea 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -2430,7 +2430,7 @@ fn memory_copy_valid(module: &Module, builder: &mut CodeBuilder) -> bool { // The non-trapping case for memory copy has not yet been implemented, // so we are excluding them for now - if !module.config.disallow_traps() { + if module.config.disallow_traps() { return false; } From 9ec356a2200d1ea702c3b09b201a479cb588b97b Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 22 Sep 2022 19:25:26 -0400 Subject: [PATCH 07/28] Add first pass at fuzzer to verify non-trapping mode does not trap --- fuzz/Cargo.toml | 7 ++++ fuzz/fuzz_targets/no-traps.rs | 68 +++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+) create mode 100644 fuzz/fuzz_targets/no-traps.rs 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..4ea71295c8 --- /dev/null +++ b/fuzz/fuzz_targets/no-traps.rs @@ -0,0 +1,68 @@ +#![no_main] + +use arbitrary::Unstructured; +use libfuzzer_sys::fuzz_target; +use wasmtime::*; + +#[cfg(feature = "wasmtime")] +#[path = "../../crates/fuzz-stats/src/dummy.rs"] +pub mod dummy; + +// Define a fuzz target that accepts arbitrary +// `Module`s as input. +fuzz_target!(|data: &[u8]| { + 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.allow_start_export = false; + config.max_memories = config.max_memories.min(1); + config.memory64_enabled = true; + config.exceptions_enabled = false; + Ok(()) + }) { + Ok(m) => m, + Err(_) => return, + }; + // 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); + } + let mut eng_conf = Config::new(); + eng_conf.wasm_memory64(true); + let engine = Engine::new(&eng_conf).unwrap(); + let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); + let mut store = Store::new(&engine, ()); + let instance = dummy::dummy_imports(&mut store, &module) + .and_then(|imports| Instance::new(&mut store, &module, &imports)) + .unwrap(); + for export in module.exports() { + match export.ty() { + ExternType::Func(func_ty) => { + let args = dummy::dummy_values(func_ty.params()); + let mut results = dummy::dummy_values(func_ty.results()); + let func = instance.get_func(&mut store, export.name()).unwrap(); + func_ty.results(); + store.add_fuel(1_000).unwrap(); + match func.call(&mut store, &args, &mut results) { + Ok(_) => continue, + Err(_) => panic!("wasm trapped"), + } + } + _ => continue, + } + } +}); From 2b2490719076c7654e03b809feb9b14623c9f4a5 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Mon, 3 Oct 2022 17:21:57 -0400 Subject: [PATCH 08/28] rebase --- .../src/core/code_builder/no_traps.rs | 68 +++++++++---------- 1 file changed, 34 insertions(+), 34 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 6942d52947..0261c3f00f 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -300,29 +300,29 @@ fn get_memarg(inst: &Instruction) -> wasm_encoder::MemArg { | Instruction::I64Load(memarg) | Instruction::F32Load(memarg) | Instruction::F64Load(memarg) - | Instruction::I32Load8_S(memarg) - | Instruction::I32Load8_U(memarg) - | Instruction::I32Load16_S(memarg) - | Instruction::I32Load16_U(memarg) - | Instruction::I64Load8_S(memarg) - | Instruction::I64Load8_U(memarg) - | Instruction::I64Load16_S(memarg) - | Instruction::I64Load16_U(memarg) - | Instruction::I64Load32_S(memarg) - | Instruction::I64Load32_U(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::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) @@ -332,7 +332,7 @@ fn get_memarg(inst: &Instruction) -> wasm_encoder::MemArg { | Instruction::I64Store8(memarg) | Instruction::I64Store16(memarg) | Instruction::I64Store32(memarg) - | Instruction::V128Store { memarg } => memarg, + | Instruction::V128Store(memarg) => memarg, _ => unreachable!(), } } @@ -397,21 +397,21 @@ fn type_of_float_conversion(inst: &Instruction) -> ValType { fn type_of_memory_access(inst: &Instruction) -> ValType { match inst { Instruction::I32Load(_) - | Instruction::I32Load8_S(_) - | Instruction::I32Load8_U(_) - | Instruction::I32Load16_S(_) - | Instruction::I32Load16_U(_) + | Instruction::I32Load8S(_) + | Instruction::I32Load8U(_) + | Instruction::I32Load16S(_) + | Instruction::I32Load16U(_) | Instruction::I32Store(_) | Instruction::I32Store8(_) | Instruction::I32Store16(_) => ValType::I32, Instruction::I64Load(_) - | Instruction::I64Load8_S(_) - | Instruction::I64Load8_U(_) - | Instruction::I64Load16_S(_) - | Instruction::I64Load16_U(_) - | Instruction::I64Load32_S(_) - | Instruction::I64Load32_U(_) + | Instruction::I64Load8S(_) + | Instruction::I64Load8U(_) + | Instruction::I64Load16S(_) + | Instruction::I64Load16U(_) + | Instruction::I64Load32S(_) + | Instruction::I64Load32U(_) | Instruction::I64Store(_) | Instruction::I64Store8(_) | Instruction::I64Store16(_) From df12a02cb9ae32adc0e55d05b428e1b5da13ca58 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 4 Oct 2022 17:31:32 -0400 Subject: [PATCH 09/28] Address review comments --- fuzz/fuzz_targets/no-traps.rs | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 4ea71295c8..7c65ec56b1 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -15,10 +15,8 @@ fuzz_target!(|data: &[u8]| { let (wasm_bytes, config) = match wasm_tools_fuzz::generate_valid_module(&mut u, |config, _| { config.disallow_traps = true; config.threads_enabled = false; - config.allow_start_export = false; - config.max_memories = config.max_memories.min(1); - config.memory64_enabled = true; config.exceptions_enabled = false; + config.max_memory_pages = config.max_memory_pages.min(100); Ok(()) }) { Ok(m) => m, @@ -43,9 +41,12 @@ fuzz_target!(|data: &[u8]| { } 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, ()); + set_fuel(&mut store, 1_000); let instance = dummy::dummy_imports(&mut store, &module) .and_then(|imports| Instance::new(&mut store, &module, &imports)) .unwrap(); @@ -56,13 +57,32 @@ fuzz_target!(|data: &[u8]| { let mut results = dummy::dummy_values(func_ty.results()); let func = instance.get_func(&mut store, export.name()).unwrap(); func_ty.results(); - store.add_fuel(1_000).unwrap(); + set_fuel(&mut store, 1_000); match func.call(&mut store, &args, &mut results) { Ok(_) => continue, - Err(_) => panic!("wasm trapped"), + Err(err) if err.to_string().contains("all fuel consumed") => continue, + Err(_) => panic!("generated wasm trapped in non-trapping mode"), } } _ => continue, } } }); + +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).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); +} From 1bd49f4cd5bf45da8c76594bb07e665885c6b076 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Mon, 24 Oct 2022 17:48:24 -0400 Subject: [PATCH 10/28] Handle table and element segments in no-trapping mode Co-authored-by: Nick Fitzgerald --- crates/wasm-smith/src/config.rs | 4 + crates/wasm-smith/src/core.rs | 165 +++++++++++++++++++++----------- 2 files changed, 115 insertions(+), 54 deletions(-) diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 3c8c1ca47b..3e8d95a886 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -807,4 +807,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 b35d12c11b..f75b0594f7 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -1021,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)) @@ -1050,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 @@ -1069,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) + })); } } @@ -1196,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)))); + } } } @@ -1276,7 +1292,18 @@ 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) => { + *x = (*x).min(mem.minimum as i32 * 64 * 1024); + } + Offset::Const64(x) => { + *x = (*x).min(mem.minimum as i64 * 64 * 1024); + } + Offset::Global(_) => unreachable!(), + } + } DataSegmentKind::Active { offset, memory_index, @@ -1302,11 +1329,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()), @@ -1315,11 +1349,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 { @@ -1370,13 +1405,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])? @@ -1396,9 +1440,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), @@ -1537,15 +1589,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) } } From c21d147650ebab35362dba3b4b4967be3640be65 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:33:31 -0400 Subject: [PATCH 11/28] Fix smoke test for non-trapping mode It was occasionally running into an 'attempt to multiply with overflow' for 32-bit memories, so we check the value before casting to avoid that. --- crates/wasm-smith/src/core.rs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index f75b0594f7..dfe9dca399 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -1296,7 +1296,10 @@ impl Module { if self.config.disallow_traps() { match &mut offset { Offset::Const32(x) => { - *x = (*x).min(mem.minimum as i32 * 64 * 1024); + let m = mem.minimum * 64 * 1024; + if m < i32::MAX as u64 { + *x = (*x).min(m as i32); + } } Offset::Const64(x) => { *x = (*x).min(mem.minimum as i64 * 64 * 1024); From bb29731449cfb4440ae5152ca7285655f725819b Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 25 Oct 2022 16:55:12 -0400 Subject: [PATCH 12/28] Clamp generated data offset to min memory size minus data length --- crates/wasm-smith/src/core.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/wasm-smith/src/core.rs b/crates/wasm-smith/src/core.rs index dfe9dca399..a1d81a238a 100644 --- a/crates/wasm-smith/src/core.rs +++ b/crates/wasm-smith/src/core.rs @@ -1296,13 +1296,16 @@ impl Module { if self.config.disallow_traps() { match &mut offset { Offset::Const32(x) => { - let m = mem.minimum * 64 * 1024; + 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) => { - *x = (*x).min(mem.minimum as i64 * 64 * 1024); + 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!(), } From 1f8268076c3538914bca279c20896ed77914dd55 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 25 Oct 2022 17:17:57 -0400 Subject: [PATCH 13/28] Handle running out of fuel during instantiation --- fuzz/fuzz_targets/no-traps.rs | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 7c65ec56b1..61210e0c5f 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -47,9 +47,14 @@ fuzz_target!(|data: &[u8]| { let module = Module::from_binary(&engine, &wasm_bytes).unwrap(); let mut store = Store::new(&engine, ()); set_fuel(&mut store, 1_000); - let instance = dummy::dummy_imports(&mut store, &module) - .and_then(|imports| Instance::new(&mut store, &module, &imports)) - .unwrap(); + let inst_result = 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(_) => panic!("generated wasm trapped in non-trapping mode"), + }; + for export in module.exports() { match export.ty() { ExternType::Func(func_ty) => { From da9a663598e0271e99e8bef4c896135ce8367725 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:27:14 -0400 Subject: [PATCH 14/28] Don't generate simd instructions in non-trapping mode --- crates/wasm-smith/src/core/code_builder.rs | 66 ++++++++++++++++------ fuzz/fuzz_targets/no-traps.rs | 3 +- 2 files changed, 50 insertions(+), 19 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 785d2da0ea..3f99467ca0 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -4643,84 +4643,114 @@ 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] @@ -4731,7 +4761,9 @@ fn simd_load_lane_valid(module: &Module, builder: &mut CodeBuilder) -> bool { #[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] diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 61210e0c5f..6d13913e98 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -52,7 +52,7 @@ fuzz_target!(|data: &[u8]| { let instance = match inst_result { Ok(r) => r, Err(err) if err.to_string().contains("all fuel consumed") => return, - Err(_) => panic!("generated wasm trapped in non-trapping mode"), + Err(err) => panic!("generated wasm trapped in non-trapping mode: {}", err), }; for export in module.exports() { @@ -61,7 +61,6 @@ fuzz_target!(|data: &[u8]| { let args = dummy::dummy_values(func_ty.params()); let mut results = dummy::dummy_values(func_ty.results()); let func = instance.get_func(&mut store, export.name()).unwrap(); - func_ty.results(); set_fuel(&mut store, 1_000); match func.call(&mut store, &args, &mut results) { Ok(_) => continue, From b2a21dd5c5d8937b847651fe80bb7d269b84eca7 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 26 Oct 2022 14:27:32 -0400 Subject: [PATCH 15/28] Use ne rather than eq to check for NaN values. NaN can't be checked for using eq, but it is the only value that is not equal to itself, so ne will work. --- .../src/core/code_builder/no_traps.rs | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 0261c3f00f..de50fdd83f 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -176,15 +176,16 @@ pub(crate) fn trunc<'a>( builder: &mut CodeBuilder, insts: &mut Vec>, ) { - // If NaN or ±inf, replace with dummy value + // 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(flt_nan_const_inst(conv_type)); - // [input:conv_type NaN:conv_type] - insts.push(eq_inst(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] @@ -263,7 +264,7 @@ pub(crate) fn signed_div_rem<'a>( // [dividend:op_type] insts.push(int_min_const_inst(op_type)); // [dividend:op_type int_min:op_type] - insts.push(int_ne_inst(op_type)); + insts.push(ne_inst(op_type)); // [not_int_min:i32] insts.push(Instruction::BrIf(0)); // [] @@ -271,7 +272,7 @@ pub(crate) fn signed_div_rem<'a>( // [divisor:op_type] insts.push(int_const_inst(op_type, -1)); // [divisor:op_type -1:op_type] - insts.push(int_ne_inst(op_type)); + insts.push(ne_inst(op_type)); // [not_neg_one:i32] insts.push(Instruction::BrIf(0)); // [] @@ -480,11 +481,13 @@ fn int_le_u_inst<'a>(ty: ValType) -> Instruction<'a> { } } -fn int_ne_inst<'a>(ty: ValType) -> Instruction<'a> { +fn ne_inst<'a>(ty: ValType) -> Instruction<'a> { match ty { ValType::I32 => Instruction::I32Ne, ValType::I64 => Instruction::I64Ne, - _ => panic!("not an int type"), + ValType::F32 => Instruction::F32Ne, + ValType::F64 => Instruction::F64Ne, + _ => panic!("not a numeric type"), } } @@ -504,14 +507,6 @@ fn flt_neg_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { } } -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, From e90582eea7f72d86f453015efe3bbb27e54900ee Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 26 Oct 2022 16:42:48 -0400 Subject: [PATCH 16/28] Perform bounds checking for trunc instructions in non-trapping mode --- .../src/core/code_builder/no_traps.rs | 82 +++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index de50fdd83f..5d944d6e5d 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -215,6 +215,40 @@ pub(crate) fn trunc<'a>( // [] 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); } @@ -395,6 +429,38 @@ fn type_of_float_conversion(inst: &Instruction) -> ValType { } } +fn min_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + match inst { + Instruction::I32TruncF32S => Instruction::F32Const(i32::MIN as f32), + Instruction::I32TruncF32U => Instruction::F32Const(0.0), + Instruction::I64TruncF32S => Instruction::F32Const(i64::MIN as 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(i64::MIN as f64), + Instruction::I64TruncF64U => Instruction::F64Const(0.0), + _ => panic!("not a trunc instruction"), + } +} + +fn max_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + match inst { + Instruction::I32TruncF32S | Instruction::I32TruncF32U => { + Instruction::F32Const(i32::MAX as f32) + } + Instruction::I64TruncF32S | Instruction::I64TruncF32U => { + Instruction::F32Const(i64::MAX as f32) + } + Instruction::I32TruncF64S | Instruction::I32TruncF64U => { + Instruction::F64Const(i32::MAX as f64) + } + Instruction::I64TruncF64S | Instruction::I64TruncF64U => { + Instruction::F64Const(i64::MAX as f64) + } + _ => panic!("not a trunc instruction"), + } +} + fn type_of_memory_access(inst: &Instruction) -> ValType { match inst { Instruction::I32Load(_) @@ -491,6 +557,22 @@ fn ne_inst<'a>(ty: ValType) -> Instruction<'a> { } } +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), From 62c27b3ae1835e0c375e4fd3db3e5f9a8088582e Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 26 Oct 2022 17:12:00 -0400 Subject: [PATCH 17/28] Adjust fuel to avoid "out of fuel" error on consume(0) --- fuzz/fuzz_targets/no-traps.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 6d13913e98..73167df9c8 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -78,7 +78,7 @@ fn set_fuel(store: &mut Store, fuel: u64) { // 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).unwrap(); + store.add_fuel(100).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(); From 4fda694570ddf6c141cd1498301c4e1639e23bea Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 26 Oct 2022 19:23:26 -0400 Subject: [PATCH 18/28] Explicitly specify the largest/smallest i64 that can be represented as an f64. This is necessary because `i64::MAX as f64` will result in an f64 value that is not representable by an i64. --- crates/wasm-smith/src/core/code_builder/no_traps.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 5d944d6e5d..31b398ba70 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -437,7 +437,7 @@ fn min_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { Instruction::I64TruncF32U => Instruction::F32Const(0.0), Instruction::I32TruncF64S => Instruction::F64Const(i32::MIN as f64), Instruction::I32TruncF64U => Instruction::F64Const(0.0), - Instruction::I64TruncF64S => Instruction::F64Const(i64::MIN as f64), + Instruction::I64TruncF64S => Instruction::F64Const(-9_223_372_036_854_775_000.0 as f64), Instruction::I64TruncF64U => Instruction::F64Const(0.0), _ => panic!("not a trunc instruction"), } @@ -455,7 +455,7 @@ fn max_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { Instruction::F64Const(i32::MAX as f64) } Instruction::I64TruncF64S | Instruction::I64TruncF64U => { - Instruction::F64Const(i64::MAX as f64) + Instruction::F64Const(9_223_372_036_854_775_000.0 as f64) } _ => panic!("not a trunc instruction"), } From d091301b20ab249ce152ea6a9eee21caef03db62 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:24:20 -0400 Subject: [PATCH 19/28] As part of our non-trapping logic for load operations, we emit instructions that must check if the memory offset plus the size of the type is within the bounds of our memory. In order for us to emit this code, that value must fit in a u64. In the case where the memory offset is u64::MAX, then our rust code panics in trying to generate the wasm due to an attempt to add with overflow. To solve this, I've added an upper bound on our memory offsets for load operations while running in non-trapping mode. --- crates/wasm-smith/src/core/code_builder.rs | 42 ++++++++++++++-------- fuzz/fuzz_targets/no-traps.rs | 2 +- 2 files changed, 29 insertions(+), 15 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 3f99467ca0..a9ede9ba1a 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -1877,9 +1877,10 @@ fn i64_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load(Instruction::I64Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::I64Load(memarg)); @@ -1893,9 +1894,10 @@ fn f32_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::F32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load(Instruction::F32Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::F32Load(memarg)); @@ -1909,9 +1911,10 @@ fn f64_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::F64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load(Instruction::F64Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::F64Load(memarg)); @@ -1925,9 +1928,10 @@ fn i32_load_8_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load8S(memarg), module, @@ -1946,9 +1950,10 @@ fn i32_load_8_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load8U(memarg), module, @@ -1967,9 +1972,10 @@ fn i32_load_16_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load16S(memarg), module, @@ -1988,9 +1994,10 @@ fn i32_load_16_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load16U(memarg), module, @@ -2009,9 +2016,10 @@ fn i64_load_8_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load8S(memarg), module, @@ -2030,9 +2038,10 @@ fn i64_load_16_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load16S(memarg), module, @@ -2051,9 +2060,10 @@ fn i64_load_32_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load32S(memarg), module, @@ -2072,9 +2082,10 @@ fn i64_load_8_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load8U(memarg), module, @@ -2093,9 +2104,10 @@ fn i64_load_16_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load16U(memarg), module, @@ -2114,9 +2126,10 @@ fn i64_load_32_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load32U(memarg), module, @@ -4786,9 +4799,10 @@ macro_rules! simd_load { instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, $alignments)?; + let mut memarg = mem_arg(u, module, builder, $alignments)?; builder.push_operands(&[ValType::V128]); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 16); no_traps::load( Instruction::$instruction(memarg), module, diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 73167df9c8..c77cf5cad8 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -65,7 +65,7 @@ fuzz_target!(|data: &[u8]| { match func.call(&mut store, &args, &mut results) { Ok(_) => continue, Err(err) if err.to_string().contains("all fuel consumed") => continue, - Err(_) => panic!("generated wasm trapped in non-trapping mode"), + Err(err) => panic!("generated wasm trapped in non-trapping mode: {}", err), } } _ => continue, From a42cabf77fb8cdd9e97f23e9fa31741d26cf7bea Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 27 Oct 2022 14:29:09 -0400 Subject: [PATCH 20/28] Don't emit unreachable instructions in non-trapping mode --- crates/wasm-smith/src/core/code_builder.rs | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index a9ede9ba1a..072004ccae 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -844,7 +844,11 @@ 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; } @@ -857,7 +861,11 @@ impl CodeBuilder<'_> { // 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; } } @@ -1000,11 +1008,12 @@ impl CodeBuilder<'_> { &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. @@ -1020,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); } @@ -1045,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(); @@ -1057,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; } From 1b35fdbdfdf16f24bbb5de8d16eef662edd2fb2e Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Wed, 2 Nov 2022 15:08:05 -0400 Subject: [PATCH 21/28] Explicitly specify max/min values for f32 --- .../src/core/code_builder/no_traps.rs | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 31b398ba70..799d68bb2f 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -430,33 +430,36 @@ fn type_of_float_conversion(inst: &Instruction) -> ValType { } 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; match inst { Instruction::I32TruncF32S => Instruction::F32Const(i32::MIN as f32), Instruction::I32TruncF32U => Instruction::F32Const(0.0), - Instruction::I64TruncF32S => Instruction::F32Const(i64::MIN as f32), + 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(-9_223_372_036_854_775_000.0 as f64), + 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 = 9_223_372_036_854_775_000f64; + let max_f32 = 9_223_371_500_000_000_000f32; + match inst { Instruction::I32TruncF32S | Instruction::I32TruncF32U => { Instruction::F32Const(i32::MAX as f32) } - Instruction::I64TruncF32S | Instruction::I64TruncF32U => { - Instruction::F32Const(i64::MAX as f32) - } + Instruction::I64TruncF32S | Instruction::I64TruncF32U => Instruction::F32Const(max_f32), Instruction::I32TruncF64S | Instruction::I32TruncF64U => { Instruction::F64Const(i32::MAX as f64) } - Instruction::I64TruncF64S | Instruction::I64TruncF64U => { - Instruction::F64Const(9_223_372_036_854_775_000.0 as f64) - } + Instruction::I64TruncF64S | Instruction::I64TruncF64U => Instruction::F64Const(max_f64), _ => panic!("not a trunc instruction"), } } From 9c9f68b542706e51b192d83af737783efa31fe17 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:28:53 -0400 Subject: [PATCH 22/28] Handle min/max f32 values representable as i32 --- .../src/core/code_builder/no_traps.rs | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 799d68bb2f..8b6dd103fe 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -433,8 +433,11 @@ 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(i32::MIN as f32), + 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), @@ -448,18 +451,24 @@ fn min_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { 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 = 9_223_372_036_854_775_000f64; - let max_f32 = 9_223_371_500_000_000_000f32; + 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(i32::MAX as f32) + Instruction::F32Const(max_f32_as_i32) + } + Instruction::I64TruncF32S | Instruction::I64TruncF32U => { + Instruction::F32Const(max_f32_as_i64) } - Instruction::I64TruncF32S | Instruction::I64TruncF32U => Instruction::F32Const(max_f32), Instruction::I32TruncF64S | Instruction::I32TruncF64U => { Instruction::F64Const(i32::MAX as f64) } - Instruction::I64TruncF64S | Instruction::I64TruncF64U => Instruction::F64Const(max_f64), + Instruction::I64TruncF64S | Instruction::I64TruncF64U => { + Instruction::F64Const(max_f64_as_i64) + } _ => panic!("not a trunc instruction"), } } From 9dde658073b0c5719d2d91ebaaa233f9c9b6b04d Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:29:50 -0400 Subject: [PATCH 23/28] Ensure stores/loads with negative addresses don't trap --- .../src/core/code_builder/no_traps.rs | 36 ++++++++++++++++--- 1 file changed, 32 insertions(+), 4 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder/no_traps.rs b/crates/wasm-smith/src/core/code_builder/no_traps.rs index 8b6dd103fe..6b4bb126ee 100644 --- a/crates/wasm-smith/src/core/code_builder/no_traps.rs +++ b/crates/wasm-smith/src/core/code_builder/no_traps.rs @@ -56,6 +56,14 @@ pub(crate) fn load<'a>( // [] 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)); @@ -129,10 +137,22 @@ pub(crate) fn store<'a>( // [] 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(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); @@ -559,6 +579,14 @@ fn int_le_u_inst<'a>(ty: ValType) -> Instruction<'a> { } } +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, From eed452cbc7eaf6f8704e4148478b6c8d15c51d83 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Thu, 3 Nov 2022 17:31:57 -0400 Subject: [PATCH 24/28] Don't allow stores with memarg.offset==i64::MAX while in non-trapping mode --- crates/wasm-smith/src/core/code_builder.rs | 33 ++++++++++++++-------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 072004ccae..07705cec84 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -1871,9 +1871,10 @@ fn i32_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load(Instruction::I32Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::I32Load(memarg)); @@ -2170,8 +2171,9 @@ fn i32_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store(Instruction::I32Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::I32Store(memarg)); @@ -2191,8 +2193,9 @@ fn i64_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store(Instruction::I64Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::I64Store(memarg)); @@ -2212,8 +2215,9 @@ fn f32_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store(Instruction::F32Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::F32Store(memarg)); @@ -2233,8 +2237,9 @@ fn f64_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store(Instruction::F64Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::F64Store(memarg)); @@ -2249,8 +2254,9 @@ fn i32_store_8( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store( Instruction::I32Store8(memarg), module, @@ -2270,8 +2276,9 @@ fn i32_store_16( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store( Instruction::I32Store16(memarg), module, @@ -2291,8 +2298,9 @@ fn i64_store_8( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let memarg = mem_arg(u, module, builder, &[0])?; + let mut memarg = mem_arg(u, module, builder, &[0])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store8(memarg), module, @@ -2312,8 +2320,9 @@ fn i64_store_16( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let memarg = mem_arg(u, module, builder, &[0, 1])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store16(memarg), module, @@ -2333,8 +2342,9 @@ fn i64_store_32( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store32(memarg), module, @@ -4848,8 +4858,9 @@ fn v128_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::V128]); - let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; + let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; if module.config.disallow_traps() { + memarg.offset = memarg.offset.min(u64::MAX - 16); no_traps::store( Instruction::V128Store(memarg), module, From d2df2f883057f89e58053a6e384f414cb88e5d7f Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:49:19 -0400 Subject: [PATCH 25/28] Refactor mem offset calculation to be in one place --- crates/wasm-smith/src/core/code_builder.rs | 114 ++++++++++----------- fuzz/fuzz_targets/no-traps.rs | 2 +- 2 files changed, 54 insertions(+), 62 deletions(-) diff --git a/crates/wasm-smith/src/core/code_builder.rs b/crates/wasm-smith/src/core/code_builder.rs index 07705cec84..0b56b61bd5 100644 --- a/crates/wasm-smith/src/core/code_builder.rs +++ b/crates/wasm-smith/src/core/code_builder.rs @@ -1871,10 +1871,9 @@ fn i32_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load(Instruction::I32Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::I32Load(memarg)); @@ -1888,10 +1887,9 @@ fn i64_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load(Instruction::I64Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::I64Load(memarg)); @@ -1905,10 +1903,9 @@ fn f32_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::F32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load(Instruction::F32Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::F32Load(memarg)); @@ -1922,10 +1919,9 @@ fn f64_load( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; builder.allocs.operands.push(Some(ValType::F64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load(Instruction::F64Load(memarg), module, builder, instructions); } else { instructions.push(Instruction::F64Load(memarg)); @@ -1939,10 +1935,9 @@ fn i32_load_8_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load8S(memarg), module, @@ -1961,10 +1956,9 @@ fn i32_load_8_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load8U(memarg), module, @@ -1983,10 +1977,9 @@ fn i32_load_16_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load16S(memarg), module, @@ -2005,10 +1998,9 @@ fn i32_load_16_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I32)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::load( Instruction::I32Load16U(memarg), module, @@ -2027,10 +2019,9 @@ fn i64_load_8_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load8S(memarg), module, @@ -2049,10 +2040,9 @@ fn i64_load_16_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load16S(memarg), module, @@ -2071,10 +2061,9 @@ fn i64_load_32_s( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load32S(memarg), module, @@ -2093,10 +2082,9 @@ fn i64_load_8_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load8U(memarg), module, @@ -2115,10 +2103,9 @@ fn i64_load_16_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load16U(memarg), module, @@ -2137,10 +2124,9 @@ fn i64_load_32_u( builder: &mut CodeBuilder, instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; builder.allocs.operands.push(Some(ValType::I64)); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::load( Instruction::I64Load32U(memarg), module, @@ -2171,9 +2157,8 @@ fn i32_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store(Instruction::I32Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::I32Store(memarg)); @@ -2193,9 +2178,8 @@ fn i64_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store(Instruction::I64Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::I64Store(memarg)); @@ -2215,9 +2199,8 @@ fn f32_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F32]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store(Instruction::F32Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::F32Store(memarg)); @@ -2237,9 +2220,8 @@ fn f64_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::F64]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store(Instruction::F64Store(memarg), module, builder, instructions); } else { instructions.push(Instruction::F64Store(memarg)); @@ -2254,9 +2236,8 @@ fn i32_store_8( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store( Instruction::I32Store8(memarg), module, @@ -2276,9 +2257,8 @@ fn i32_store_16( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I32]); - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 4); no_traps::store( Instruction::I32Store16(memarg), module, @@ -2298,9 +2278,8 @@ fn i64_store_8( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let mut memarg = mem_arg(u, module, builder, &[0])?; + let memarg = mem_arg(u, module, builder, &[0])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store8(memarg), module, @@ -2320,9 +2299,8 @@ fn i64_store_16( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let mut memarg = mem_arg(u, module, builder, &[0, 1])?; + let memarg = mem_arg(u, module, builder, &[0, 1])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store16(memarg), module, @@ -2342,9 +2320,8 @@ fn i64_store_32( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::I64]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 8); no_traps::store( Instruction::I64Store32(memarg), module, @@ -4353,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)?; @@ -4819,10 +4813,9 @@ macro_rules! simd_load { instructions: &mut Vec, ) -> Result<()> { - let mut memarg = mem_arg(u, module, builder, $alignments)?; + let memarg = mem_arg(u, module, builder, $alignments)?; builder.push_operands(&[ValType::V128]); if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 16); no_traps::load( Instruction::$instruction(memarg), module, @@ -4858,9 +4851,8 @@ fn v128_store( instructions: &mut Vec, ) -> Result<()> { builder.pop_operands(&[ValType::V128]); - let mut memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; + let memarg = mem_arg(u, module, builder, &[0, 1, 2, 3, 4])?; if module.config.disallow_traps() { - memarg.offset = memarg.offset.min(u64::MAX - 16); no_traps::store( Instruction::V128Store(memarg), module, diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index c77cf5cad8..8151e0adcf 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -78,7 +78,7 @@ fn set_fuel(store: &mut Store, fuel: u64) { // 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(100).unwrap(); + 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(); From 7ef8bac0136d7e4dc1788a680fff8feb1e74adef Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Fri, 4 Nov 2022 14:56:44 -0400 Subject: [PATCH 26/28] Use a ResourceLimiter to prevent libfuzzer from running out of memory. This was happening with modules that had a large number of memory.grow or table.grow instructions. --- .../src/bin/failed-instantiations.rs | 33 +------------------ crates/fuzz-stats/src/lib.rs | 1 + crates/fuzz-stats/src/limits.rs | 31 +++++++++++++++++ fuzz/fuzz_targets/no-traps.rs | 19 +++++++---- 4 files changed, 46 insertions(+), 38 deletions(-) create mode 100644 crates/fuzz-stats/src/limits.rs 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/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 8151e0adcf..4e7dd003a4 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -5,8 +5,8 @@ use libfuzzer_sys::fuzz_target; use wasmtime::*; #[cfg(feature = "wasmtime")] -#[path = "../../crates/fuzz-stats/src/dummy.rs"] -pub mod dummy; +#[path = "../../crates/fuzz-stats/src/lib.rs"] +pub mod fuzz_stats; // Define a fuzz target that accepts arbitrary // `Module`s as input. @@ -45,9 +45,16 @@ fuzz_target!(|data: &[u8]| { 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, ()); + 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); - let inst_result = dummy::dummy_imports(&mut store, &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, @@ -58,8 +65,8 @@ fuzz_target!(|data: &[u8]| { for export in module.exports() { match export.ty() { ExternType::Func(func_ty) => { - let args = dummy::dummy_values(func_ty.params()); - let mut results = dummy::dummy_values(func_ty.results()); + 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) { From 3a761e636d9df801bd87bed54a5d8387f14fa443 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Mon, 7 Nov 2022 09:21:56 -0700 Subject: [PATCH 27/28] Clean up code a little bit --- fuzz/fuzz_targets/no-traps.rs | 45 ++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 17 deletions(-) diff --git a/fuzz/fuzz_targets/no-traps.rs b/fuzz/fuzz_targets/no-traps.rs index 4e7dd003a4..f4e73f2ebe 100644 --- a/fuzz/fuzz_targets/no-traps.rs +++ b/fuzz/fuzz_targets/no-traps.rs @@ -2,6 +2,7 @@ use arbitrary::Unstructured; use libfuzzer_sys::fuzz_target; +use wasm_smith::SwarmConfig; use wasmtime::*; #[cfg(feature = "wasmtime")] @@ -11,6 +12,7 @@ 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; @@ -22,23 +24,9 @@ fuzz_target!(|data: &[u8]| { Ok(m) => m, Err(_) => return, }; - // 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); - } + 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); @@ -54,6 +42,8 @@ fuzz_target!(|data: &[u8]| { ); 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 { @@ -62,6 +52,7 @@ fuzz_target!(|data: &[u8]| { 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) => { @@ -80,6 +71,26 @@ fuzz_target!(|data: &[u8]| { } }); +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 From 9a1a86f7e0113541df135375f72cd98c49175b87 Mon Sep 17 00:00:00 2001 From: Rainy Sinclair <844493+itsrainy@users.noreply.github.com> Date: Tue, 8 Nov 2022 11:18:32 -0700 Subject: [PATCH 28/28] Add doc to `disallow_traps` explaining StackOverflow traps --- crates/wasm-smith/src/config.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/crates/wasm-smith/src/config.rs b/crates/wasm-smith/src/config.rs index 3e8d95a886..35f7429446 100644 --- a/crates/wasm-smith/src/config.rs +++ b/crates/wasm-smith/src/config.rs @@ -452,8 +452,17 @@ pub trait Config: 'static + std::fmt::Debug { /// 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. + /// 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 {