From 0e71e0d1b1b5f157dc62bdccf9d1e56a135dbae7 Mon Sep 17 00:00:00 2001 From: Folkert Date: Tue, 25 Jul 2023 19:22:03 +0200 Subject: [PATCH] implement signed integer extension operations --- crates/wasm_interp/src/instance.rs | 20 +++++++++ crates/wasm_interp/src/tests/test_convert.rs | 45 ++++++++++++++++++++ crates/wasm_module/src/opcodes.rs | 34 +++++++++++---- 3 files changed, 90 insertions(+), 9 deletions(-) diff --git a/crates/wasm_interp/src/instance.rs b/crates/wasm_interp/src/instance.rs index 2278a2d2552..0c094dcb9fb 100644 --- a/crates/wasm_interp/src/instance.rs +++ b/crates/wasm_interp/src/instance.rs @@ -1728,6 +1728,26 @@ impl<'a, I: ImportDispatcher> Instance<'a, I> { self.value_store .push(Value::F64(f64::from_ne_bytes(x.to_ne_bytes()))); } + I32EXTEND8S => { + let x = self.value_store.pop_i32()?; + self.value_store.push(Value::I32(x as i8 as i32)); + } + I32EXTEND16S => { + let x = self.value_store.pop_i32()?; + self.value_store.push(Value::I32(x as i16 as i32)); + } + I64EXTEND8S => { + let x = self.value_store.pop_i64()?; + self.value_store.push(Value::I64(x as i8 as i64)); + } + I64EXTEND16S => { + let x = self.value_store.pop_i64()?; + self.value_store.push(Value::I64(x as i16 as i64)); + } + I64EXTEND32S => { + let x = self.value_store.pop_i64()?; + self.value_store.push(Value::I64(x as i32 as i64)); + } } if let Some(debug_string) = &self.debug_string { diff --git a/crates/wasm_interp/src/tests/test_convert.rs b/crates/wasm_interp/src/tests/test_convert.rs index 2548c838e1c..40b545a5360 100644 --- a/crates/wasm_interp/src/tests/test_convert.rs +++ b/crates/wasm_interp/src/tests/test_convert.rs @@ -247,3 +247,48 @@ fn test_f64reinterpreti64() { Value::F64(0.01171875), ); } + +#[test] +fn test_i32extend8s() { + test_op_example( + I32EXTEND8S, + [Value::from(0xFFu32 as i32)], + Value::I32(0xFFFFFFFFu32 as i32), + ) +} + +#[test] +fn test_i32extend16s() { + test_op_example( + I32EXTEND16S, + [Value::from(0xFFFFu32)], + Value::I32(0xFFFFFFFFu32 as i32), + ) +} + +#[test] +fn test_i64extend8s() { + test_op_example( + I64EXTEND8S, + [Value::from(0xFFu64 as i64)], + Value::I64(u64::MAX as i64), + ) +} + +#[test] +fn test_i64extend16s() { + test_op_example( + I64EXTEND16S, + [Value::from(0xFFFFu64 as i64)], + Value::I64(u64::MAX as i64), + ) +} + +#[test] +fn test_i64extend32s() { + test_op_example( + I64EXTEND32S, + [Value::from(0xFFFFFFFFu64 as i64)], + Value::I64(u64::MAX as i64), + ) +} diff --git a/crates/wasm_module/src/opcodes.rs b/crates/wasm_module/src/opcodes.rs index 340ad28571d..948a75faab2 100644 --- a/crates/wasm_module/src/opcodes.rs +++ b/crates/wasm_module/src/opcodes.rs @@ -185,6 +185,12 @@ pub enum OpCode { I64REINTERPRETF64 = 0xbd, F32REINTERPRETI32 = 0xbe, F64REINTERPRETI64 = 0xbf, + + I32EXTEND8S = 0xc0, + I32EXTEND16S = 0xc1, + I64EXTEND8S = 0xc2, + I64EXTEND16S = 0xc3, + I64EXTEND32S = 0xc4, } pub const LOOKUP_TABLE: [Option; 256] = { @@ -366,17 +372,28 @@ pub const LOOKUP_TABLE: [Option; 256] = { result[0xbe] = Some(F32REINTERPRETI32); result[0xbf] = Some(F64REINTERPRETI64); + result[0xc0] = Some(I32EXTEND8S); + result[0xc1] = Some(I32EXTEND16S); + result[0xc2] = Some(I64EXTEND8S); + result[0xc3] = Some(I64EXTEND16S); + result[0xc4] = Some(I64EXTEND32S); + result }; impl From for OpCode { fn from(value: u8) -> Self { - // invalid instruction bytes can be genuine because we don't support all instructions, and - // new ones get added or stabilized. It could also be a bug in the interpreter, e.g. some - // opcode does not move the instruction pointer correctly. - match LOOKUP_TABLE[value as usize] { - None => unreachable!("unsupported instruction byte {value:#x?}"), - Some(op) => op, + if false { + // considerably faster in practice + unsafe { std::mem::transmute(value) } + } else { + // invalid instruction bytes can be genuine because we don't support all instructions, and + // new ones get added or stabilized. It could also be a bug in the interpreter, e.g. some + // opcode does not move the instruction pointer correctly. + match LOOKUP_TABLE[value as usize] { + None => unreachable!("unsupported instruction byte {value:#x?}"), + Some(op) => op, + } } } } @@ -468,9 +485,8 @@ fn immediates_for(op: OpCode) -> Result { | I64EXTENDUI32 | I64TRUNCSF32 | I64TRUNCUF32 | I64TRUNCSF64 | I64TRUNCUF64 | F32CONVERTSI32 | F32CONVERTUI32 | F32CONVERTSI64 | F32CONVERTUI64 | F32DEMOTEF64 | F64CONVERTSI32 | F64CONVERTUI32 | F64CONVERTSI64 | F64CONVERTUI64 | F64PROMOTEF32 - | I32REINTERPRETF32 | I64REINTERPRETF64 | F32REINTERPRETI32 | F64REINTERPRETI64 => { - NoImmediate - } + | I32REINTERPRETF32 | I64REINTERPRETF64 | F32REINTERPRETI32 | F64REINTERPRETI64 + | I32EXTEND8S | I32EXTEND16S | I64EXTEND8S | I64EXTEND16S | I64EXTEND32S => NoImmediate, // Catch-all in case of an invalid cast from u8 to OpCode while parsing binary // (rustc keeps this code, I verified in Compiler Explorer)