diff options
Diffstat (limited to 'third_party/rust/wasm-smith/src/core/code_builder')
-rw-r--r-- | third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs | 644 |
1 files changed, 644 insertions, 0 deletions
diff --git a/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs b/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs new file mode 100644 index 0000000000..a1232b6922 --- /dev/null +++ b/third_party/rust/wasm-smith/src/core/code_builder/no_traps.rs @@ -0,0 +1,644 @@ +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<Instruction<'a>>, +) { + let memarg = get_memarg(&inst); + let memory = &module.memories[memarg.memory_index as usize]; + let address_type = if memory.memory64 { + ValType::I64 + } else { + ValType::I32 + }; + // Add a temporary local to hold this load's address. + let address_local = builder.alloc_local(address_type); + + // Add a temporary local to hold the result of this load. + let load_type = type_of_memory_access(&inst); + let result_local = builder.alloc_local(load_type); + + // [address:address_type] + insts.push(Instruction::LocalSet(address_local)); + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::MemorySize(memarg.memory_index)); + // [mem_size_in_pages:address_type] + insts.push(int_const_inst(address_type, 65_536)); + // [mem_size_in_pages:address_type wasm_page_size:address_type] + insts.push(int_mul_inst(address_type)); + // [mem_size_in_bytes:address_type] + insts.push(int_const_inst( + address_type, + (memarg.offset + size_of_type_in_memory(load_type)) as i64, + )); + // [mem_size_in_bytes:address_type offset_and_size:address_type] + insts.push(Instruction::LocalGet(address_local)); + // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] + insts.push(int_add_inst(address_type)); + // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] + insts.push(int_le_u_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(int_const_inst(address_type, 0)); + // [address:address_type 0:address_type] + insts.push(int_le_s_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(inst); + // [result:load_type] + insts.push(Instruction::LocalSet(result_local)); + // [] + insts.push(Instruction::Br(1)); + // <unreachable> + } + // [] + 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<Instruction<'a>>, +) { + let memarg = get_memarg(&inst); + let memory = &module.memories[memarg.memory_index as usize]; + let address_type = if memory.memory64 { + ValType::I64 + } else { + ValType::I32 + }; + + // Add a temporary local to hold this store's address. + let address_local = builder.alloc_local(address_type); + + // Add a temporary local to hold the value to store. + let store_type = type_of_memory_access(&inst); + let value_local = builder.alloc_local(store_type); + + // [address:address_type value:store_type] + insts.push(Instruction::LocalSet(value_local)); + // [address:address_type] + insts.push(Instruction::LocalSet(address_local)); + // [] + insts.push(Instruction::MemorySize(memarg.memory_index)); + // [mem_size_in_pages:address_type] + insts.push(int_const_inst(address_type, 65_536)); + // [mem_size_in_pages:address_type wasm_page_size:address_type] + insts.push(int_mul_inst(address_type)); + // [mem_size_in_bytes:address_type] + insts.push(int_const_inst( + address_type, + (memarg.offset + size_of_type_in_memory(store_type)) as i64, + )); + // [mem_size_in_bytes:address_type offset_and_size:address_type] + insts.push(Instruction::LocalGet(address_local)); + // [mem_size_in_bytes:address_type offset_and_size:address_type address:address_type] + insts.push(int_add_inst(address_type)); + // [mem_size_in_bytes:address_type highest_byte_accessed:address_type] + insts.push(int_le_u_inst(address_type)); + // [store_will_trap:i32] + insts.push(Instruction::If(BlockType::Empty)); + insts.push(Instruction::Else); + { + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(int_const_inst(address_type, 0)); + // [address:address_type 0:address_type] + insts.push(int_le_s_inst(address_type)); + // [load_will_trap:i32] + insts.push(Instruction::If(BlockType::Empty)); + insts.push(Instruction::Else); + { + // [] + insts.push(Instruction::LocalGet(address_local)); + // [address:address_type] + insts.push(Instruction::LocalGet(value_local)); + // [address:address_type value:store_type] + insts.push(inst); + // [] + } + insts.push(Instruction::End); + } + // [] + insts.push(Instruction::End); +} + +// Unsigned integer division and remainder will trap when +// the divisor is 0. To avoid the trap, we will set any 0 +// divisors to 1 prior to the operation. +// +// The code below is equivalent to this expression: +// +// local.set $temp_divisor +// (select (i32.eqz (local.get $temp_divisor) (i32.const 1) (local.get $temp_divisor)) +pub(crate) fn unsigned_div_rem<'a>( + inst: Instruction<'a>, + builder: &mut CodeBuilder, + insts: &mut Vec<Instruction<'a>>, +) { + 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<Instruction<'a>>, +) { + // If NaN or ±inf, replace with dummy value. Our method of checking for NaN + // is to use `ne` because NaN is the only value that is not equal to itself + let conv_type = type_of_float_conversion(&inst); + let temp_float = builder.alloc_local(conv_type); + // [input:conv_type] + insts.push(Instruction::LocalTee(temp_float)); + // [input:conv_type] + insts.push(Instruction::LocalGet(temp_float)); + // [input:conv_type input:conv_type] + insts.push(ne_inst(conv_type)); + // [is_nan:i32] + insts.push(Instruction::LocalGet(temp_float)); + // [is_nan:i32 input:conv_type] + insts.push(flt_inf_const_inst(conv_type)); + // [is_nan:i32 input:conv_type inf:conv_type] + insts.push(eq_inst(conv_type)); + // [is_nan:i32 is_inf:i32] + insts.push(Instruction::LocalGet(temp_float)); + // [is_nan:i32 is_inf:i32 input:conv_type] + insts.push(flt_neg_inf_const_inst(conv_type)); + // [is_nan:i32 is_inf:i32 input:conv_type neg_inf:conv_type] + insts.push(eq_inst(conv_type)); + // [is_nan:i32 is_inf:i32 is_neg_inf:i32] + insts.push(Instruction::I32Or); + // [is_nan:i32 is_±inf:i32] + insts.push(Instruction::I32Or); + // [is_nan_or_inf:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(dummy_value_inst(conv_type)); + // [0:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + // [] + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [input_or_0:conv_type] + + // first ensure that it is >= the min value of our target type + insts.push(min_input_const_for_trunc(&inst)); + // [input_or_0:conv_type min_value_of_target_type:conv_type] + insts.push(flt_lt_inst(conv_type)); + // [input_lt_min:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(min_input_const_for_trunc(&inst)); + // [min_value_of_target_type:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [coerced_input:conv_type] + + // next ensure that it is <= the max value of our target type + insts.push(max_input_const_for_trunc(&inst)); + // [input_or_0:conv_type max_value_of_target_type:conv_type] + insts.push(flt_gt_inst(conv_type)); + // [input_gt_min:i32] + insts.push(Instruction::If(BlockType::Empty)); + { + // [] + insts.push(max_input_const_for_trunc(&inst)); + // [max_value_of_target_type:conv_type] + insts.push(Instruction::LocalSet(temp_float)); + } + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_float)); + // [coerced_input:conv_type] + insts.push(inst); +} + +// Signed division and remainder will trap in the following instances: +// - The divisor is 0 +// - The result of the division is 2^(n-1) +pub(crate) fn signed_div_rem<'a>( + inst: Instruction<'a>, + builder: &mut CodeBuilder, + insts: &mut Vec<Instruction<'a>>, +) { + // If divisor is 0, replace with 1 + let op_type = type_of_integer_operation(&inst); + let temp_divisor = builder.alloc_local(op_type); + // [dividend:op_type divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(int_const_inst(op_type, 1)); + // [dividend:op_type 1:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type 1:op_type divisor:op_type divisor:op_type] + insts.push(eqz_inst(op_type)); + // [dividend:op_type 1:op_type divisor:op_type is_zero:i32] + insts.push(Instruction::Select); + // [dividend:op_type divisor:op_type] + // If dividend and divisor are -int.max and -1, replace + // divisor with 1. + let temp_dividend = builder.alloc_local(op_type); + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(Instruction::LocalSet(temp_dividend)); + // [] + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + insts.push(Instruction::Block(wasm_encoder::BlockType::Empty)); + { + // [] + insts.push(Instruction::LocalGet(temp_dividend)); + // [dividend:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [dividend:op_type] + insts.push(Instruction::LocalTee(temp_dividend)); + // [dividend:op_type] + insts.push(int_min_const_inst(op_type)); + // [dividend:op_type int_min:op_type] + insts.push(ne_inst(op_type)); + // [not_int_min:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(Instruction::LocalGet(temp_divisor)); + // [divisor:op_type] + insts.push(int_const_inst(op_type, -1)); + // [divisor:op_type -1:op_type] + insts.push(ne_inst(op_type)); + // [not_neg_one:i32] + insts.push(Instruction::BrIf(0)); + // [] + insts.push(int_const_inst(op_type, 1)); + // [divisor:op_type] + insts.push(Instruction::LocalSet(temp_divisor)); + // [] + insts.push(Instruction::Br(1)); + } + // [] + insts.push(Instruction::End); + } + // [] + insts.push(Instruction::End); + // [] + insts.push(Instruction::LocalGet(temp_dividend)); + // [dividend:op_type] + insts.push(Instruction::LocalGet(temp_divisor)); + // [dividend:op_type divisor:op_type] + insts.push(inst); +} + +fn get_memarg(inst: &Instruction) -> wasm_encoder::MemArg { + match *inst { + Instruction::I32Load(memarg) + | Instruction::I64Load(memarg) + | Instruction::F32Load(memarg) + | Instruction::F64Load(memarg) + | Instruction::I32Load8S(memarg) + | Instruction::I32Load8U(memarg) + | Instruction::I32Load16S(memarg) + | Instruction::I32Load16U(memarg) + | Instruction::I64Load8S(memarg) + | Instruction::I64Load8U(memarg) + | Instruction::I64Load16S(memarg) + | Instruction::I64Load16U(memarg) + | Instruction::I64Load32S(memarg) + | Instruction::I64Load32U(memarg) + | Instruction::V128Load(memarg) + | Instruction::V128Load8x8S(memarg) + | Instruction::V128Load8x8U(memarg) + | Instruction::V128Load16x4S(memarg) + | Instruction::V128Load16x4U(memarg) + | Instruction::V128Load32x2S(memarg) + | Instruction::V128Load32x2U(memarg) + | Instruction::V128Load8Splat(memarg) + | Instruction::V128Load16Splat(memarg) + | Instruction::V128Load32Splat(memarg) + | Instruction::V128Load64Splat(memarg) + | Instruction::V128Load32Zero(memarg) + | Instruction::V128Load64Zero(memarg) + | Instruction::I32Store(memarg) + | Instruction::I64Store(memarg) + | Instruction::F32Store(memarg) + | Instruction::F64Store(memarg) + | Instruction::I32Store8(memarg) + | Instruction::I32Store16(memarg) + | Instruction::I64Store8(memarg) + | Instruction::I64Store16(memarg) + | Instruction::I64Store32(memarg) + | Instruction::V128Store(memarg) => memarg, + _ => unreachable!(), + } +} + +fn dummy_value_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(0), + ValType::I64 => Instruction::I64Const(0), + ValType::F32 => Instruction::F32Const(0.0), + ValType::F64 => Instruction::F64Const(0.0), + ValType::V128 => Instruction::V128Const(0), + ValType::Ref(ty) => { + assert!(ty.nullable); + Instruction::RefNull(ty.heap_type) + } + } +} + +fn eq_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Eq, + ValType::F64 => Instruction::F64Eq, + ValType::I32 => Instruction::I32Eq, + ValType::I64 => Instruction::I64Eq, + _ => panic!("not a numeric type"), + } +} + +fn eqz_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Eqz, + ValType::I64 => Instruction::I64Eqz, + _ => panic!("not an integer type"), + } +} + +fn type_of_integer_operation(inst: &Instruction) -> ValType { + match inst { + Instruction::I32DivU + | Instruction::I32DivS + | Instruction::I32RemU + | Instruction::I32RemS => ValType::I32, + Instruction::I64RemU + | Instruction::I64DivU + | Instruction::I64DivS + | Instruction::I64RemS => ValType::I64, + _ => panic!("not integer division or remainder"), + } +} + +fn type_of_float_conversion(inst: &Instruction) -> ValType { + match inst { + Instruction::I32TruncF32S + | Instruction::I32TruncF32U + | Instruction::I64TruncF32S + | Instruction::I64TruncF32U => ValType::F32, + Instruction::I32TruncF64S + | Instruction::I32TruncF64U + | Instruction::I64TruncF64S + | Instruction::I64TruncF64U => ValType::F64, + _ => panic!("not a float -> integer conversion"), + } +} + +fn min_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + // This is the minimum float value that is representable as an i64 + let min_f64 = -9_223_372_036_854_775_000f64; + let min_f32 = -9_223_372_000_000_000_000f32; + + // This is the minimum float value that is representable as as i32 + let min_f32_as_i32 = -2_147_483_500f32; + match inst { + Instruction::I32TruncF32S => Instruction::F32Const(min_f32_as_i32), + Instruction::I32TruncF32U => Instruction::F32Const(0.0), + Instruction::I64TruncF32S => Instruction::F32Const(min_f32), + Instruction::I64TruncF32U => Instruction::F32Const(0.0), + Instruction::I32TruncF64S => Instruction::F64Const(i32::MIN as f64), + Instruction::I32TruncF64U => Instruction::F64Const(0.0), + Instruction::I64TruncF64S => Instruction::F64Const(min_f64), + Instruction::I64TruncF64U => Instruction::F64Const(0.0), + _ => panic!("not a trunc instruction"), + } +} + +fn max_input_const_for_trunc<'a>(inst: &Instruction) -> Instruction<'a> { + // This is the maximum float value that is representable as as i64 + let max_f64_as_i64 = 9_223_372_036_854_775_000f64; + let max_f32_as_i64 = 9_223_371_500_000_000_000f32; + + // This is the maximum float value that is representable as as i32 + let max_f32_as_i32 = 2_147_483_500f32; + match inst { + Instruction::I32TruncF32S | Instruction::I32TruncF32U => { + Instruction::F32Const(max_f32_as_i32) + } + Instruction::I64TruncF32S | Instruction::I64TruncF32U => { + Instruction::F32Const(max_f32_as_i64) + } + Instruction::I32TruncF64S | Instruction::I32TruncF64U => { + Instruction::F64Const(i32::MAX as f64) + } + Instruction::I64TruncF64S | Instruction::I64TruncF64U => { + Instruction::F64Const(max_f64_as_i64) + } + _ => panic!("not a trunc instruction"), + } +} + +fn type_of_memory_access(inst: &Instruction) -> ValType { + match inst { + Instruction::I32Load(_) + | Instruction::I32Load8S(_) + | Instruction::I32Load8U(_) + | Instruction::I32Load16S(_) + | Instruction::I32Load16U(_) + | Instruction::I32Store(_) + | Instruction::I32Store8(_) + | Instruction::I32Store16(_) => ValType::I32, + + Instruction::I64Load(_) + | Instruction::I64Load8S(_) + | Instruction::I64Load8U(_) + | Instruction::I64Load16S(_) + | Instruction::I64Load16U(_) + | Instruction::I64Load32S(_) + | Instruction::I64Load32U(_) + | Instruction::I64Store(_) + | Instruction::I64Store8(_) + | Instruction::I64Store16(_) + | Instruction::I64Store32(_) => ValType::I64, + + Instruction::F32Load(_) | Instruction::F32Store(_) => ValType::F32, + + Instruction::F64Load(_) | Instruction::F64Store(_) => ValType::F64, + + Instruction::V128Load { .. } + | Instruction::V128Load8x8S { .. } + | Instruction::V128Load8x8U { .. } + | Instruction::V128Load16x4S { .. } + | Instruction::V128Load16x4U { .. } + | Instruction::V128Load32x2S { .. } + | Instruction::V128Load32x2U { .. } + | Instruction::V128Load8Splat { .. } + | Instruction::V128Load16Splat { .. } + | Instruction::V128Load32Splat { .. } + | Instruction::V128Load64Splat { .. } + | Instruction::V128Load32Zero { .. } + | Instruction::V128Load64Zero { .. } + | Instruction::V128Store { .. } => ValType::V128, + + _ => panic!("not a memory access instruction"), + } +} + +fn int_min_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(i32::MIN), + ValType::I64 => Instruction::I64Const(i64::MIN), + _ => panic!("not an int type"), + } +} + +fn int_const_inst<'a>(ty: ValType, x: i64) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Const(x as i32), + ValType::I64 => Instruction::I64Const(x), + _ => panic!("not an int type"), + } +} + +fn int_mul_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Mul, + ValType::I64 => Instruction::I64Mul, + _ => panic!("not an int type"), + } +} + +fn int_add_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Add, + ValType::I64 => Instruction::I64Add, + _ => panic!("not an int type"), + } +} + +fn int_le_u_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32LeU, + ValType::I64 => Instruction::I64LeU, + _ => panic!("not an int type"), + } +} + +fn int_le_s_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32LeS, + ValType::I64 => Instruction::I64LeS, + _ => panic!("not an int type"), + } +} + +fn ne_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::I32 => Instruction::I32Ne, + ValType::I64 => Instruction::I64Ne, + ValType::F32 => Instruction::F32Ne, + ValType::F64 => Instruction::F64Ne, + _ => panic!("not a numeric type"), + } +} + +fn flt_lt_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Lt, + ValType::F64 => Instruction::F64Lt, + _ => panic!("not a float type"), + } +} + +fn flt_gt_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Gt, + ValType::F64 => Instruction::F64Gt, + _ => panic!("not a float type"), + } +} + +fn flt_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Const(f32::INFINITY), + ValType::F64 => Instruction::F64Const(f64::INFINITY), + _ => panic!("not a float type"), + } +} + +fn flt_neg_inf_const_inst<'a>(ty: ValType) -> Instruction<'a> { + match ty { + ValType::F32 => Instruction::F32Const(f32::NEG_INFINITY), + ValType::F64 => Instruction::F64Const(f64::NEG_INFINITY), + _ => panic!("not a float type"), + } +} + +fn size_of_type_in_memory(ty: ValType) -> u64 { + match ty { + ValType::I32 => 4, + ValType::I64 => 8, + ValType::F32 => 4, + ValType::F64 => 8, + ValType::V128 => 16, + ValType::Ref(_) => panic!("not a memory type"), + } +} |