use super::*;
use crate::core::UntypedVal;
mod f32_store;
mod f64_store;
mod i32_store;
mod i32_store16;
mod i32_store8;
mod i64_store;
mod i64_store16;
mod i64_store32;
mod i64_store8;
use crate::core::TrapCode;
use core::fmt::Display;
fn test_store_for(
wasm_op: WasmOp,
offset: u32,
make_instr: fn(ptr: Register, offset: Const32<u32>) -> Instruction,
) {
assert!(
u16::try_from(offset).is_err(),
"this test requires non-16 bit offsets but found {offset}"
);
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func (param $ptr i32) (param $value {param_ty})
local.get $ptr
local.get $value
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([
make_instr(Register::from_i16(0), Const32::from(offset)),
Instruction::Register(Register::from_i16(1)),
Instruction::Return,
])
.run();
}
fn test_store(wasm_op: WasmOp, make_instr: fn(ptr: Register, offset: Const32<u32>) -> Instruction) {
test_store_for(wasm_op, u32::from(u16::MAX) + 1, make_instr);
test_store_for(wasm_op, u32::MAX - 1, make_instr);
test_store_for(wasm_op, u32::MAX, make_instr);
}
fn test_store_offset16_for(
wasm_op: WasmOp,
offset: u16,
make_instr: fn(ptr: Register, offset: u16, value: Register) -> Instruction,
) {
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func (param $ptr i32) (param $value {param_ty})
local.get $ptr
local.get $value
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([
make_instr(Register::from_i16(0), offset, Register::from_i16(1)),
Instruction::Return,
])
.run();
}
fn test_store_offset16(
wasm_op: WasmOp,
make_instr: fn(ptr: Register, offset: u16, value: Register) -> Instruction,
) {
test_store_offset16_for(wasm_op, 0, make_instr);
test_store_offset16_for(wasm_op, u16::MAX - 1, make_instr);
test_store_offset16_for(wasm_op, u16::MAX, make_instr);
}
fn test_store_offset16_imm_for<T>(
wasm_op: WasmOp,
offset: u16,
value: T,
make_instr: fn(ptr: Register, offset: u16, value: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
let param_ty = wasm_op.param_ty();
let display_value = DisplayWasm::from(value);
let wasm = format!(
r#"
(module
(memory 1)
(func (param $ptr i32)
local.get $ptr
{param_ty}.const {display_value}
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func(
ExpectedFunc::new([
make_instr(Register::from_i16(0), offset, Register::from_i16(-1)),
Instruction::Return,
])
.consts([value]),
)
.run();
}
fn test_store_offset16_imm<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(ptr: Register, offset: u16, value: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
test_store_offset16_imm_for(wasm_op, 0, value, make_instr);
test_store_offset16_imm_for(wasm_op, u16::MAX - 1, value, make_instr);
test_store_offset16_imm_for(wasm_op, u16::MAX, value, make_instr);
}
fn test_store_offset16_imm16_for<T>(
wasm_op: WasmOp,
offset: u16,
value: T,
make_instr: fn(ptr: Register, offset: u16, value: T) -> Instruction,
) where
T: Copy,
DisplayWasm<T>: Display,
{
let param_ty = wasm_op.param_ty();
let display_value = DisplayWasm::from(value);
let wasm = format!(
r#"
(module
(memory 1)
(func (param $ptr i32)
local.get $ptr
{param_ty}.const {display_value}
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([
make_instr(Register::from_i16(0), offset, value),
Instruction::Return,
])
.run();
}
fn test_store_offset16_imm16<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(ptr: Register, offset: u16, value: T) -> Instruction,
) where
T: Copy,
DisplayWasm<T>: Display,
{
test_store_offset16_imm16_for(wasm_op, 0, value, make_instr);
test_store_offset16_imm16_for(wasm_op, u16::MAX - 1, value, make_instr);
test_store_offset16_imm16_for(wasm_op, u16::MAX, value, make_instr);
}
fn test_store_imm_for<T>(
wasm_op: WasmOp,
offset: u32,
value: T,
make_instr: fn(ptr: Register, offset: Const32<u32>) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
assert!(
u16::try_from(offset).is_err(),
"this test requires non-16 bit offsets but found {offset}"
);
let param_ty = wasm_op.param_ty();
let display_value = DisplayWasm::from(value);
let wasm = format!(
r#"
(module
(memory 1)
(func (param $ptr i32)
local.get $ptr
{param_ty}.const {display_value}
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func(
ExpectedFunc::new([
make_instr(Register::from_i16(0), Const32::from(offset)),
Instruction::Register(Register::from(-1)),
Instruction::Return,
])
.consts([value]),
)
.run();
}
fn test_store_imm<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(ptr: Register, offset: Const32<u32>) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
test_store_imm_for(wasm_op, u32::from(u16::MAX) + 1, value, make_instr);
test_store_imm_for(wasm_op, u32::MAX - 1, value, make_instr);
test_store_imm_for(wasm_op, u32::MAX, value, make_instr);
}
fn test_store_at_for(
wasm_op: WasmOp,
ptr: u32,
offset: u32,
make_instr: fn(address: Const32<u32>, value: Register) -> Instruction,
) {
let address = ptr
.checked_add(offset)
.expect("testcase requires valid ptr+offset address");
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func (param $value {param_ty})
i32.const {ptr}
local.get $value
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([
make_instr(Const32::from(address), Register::from_i16(0)),
Instruction::Return,
])
.run();
}
fn test_store_at(
wasm_op: WasmOp,
make_instr: fn(address: Const32<u32>, value: Register) -> Instruction,
) {
test_store_at_for(wasm_op, 0, 0, make_instr);
test_store_at_for(wasm_op, 0, 1, make_instr);
test_store_at_for(wasm_op, 1, 0, make_instr);
test_store_at_for(wasm_op, 1, 1, make_instr);
test_store_at_for(wasm_op, 1000, 1000, make_instr);
test_store_at_for(wasm_op, 1, u32::MAX - 1, make_instr);
test_store_at_for(wasm_op, u32::MAX - 1, 1, make_instr);
test_store_at_for(wasm_op, 0, u32::MAX, make_instr);
test_store_at_for(wasm_op, u32::MAX, 0, make_instr);
}
fn test_store_at_overflow_for(wasm_op: WasmOp, ptr: u32, offset: u32) {
assert!(
ptr.checked_add(offset).is_none(),
"testcase expects overflowing ptr+offset address"
);
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func (param $value {param_ty})
i32.const {ptr}
local.get $value
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([Instruction::Trap(TrapCode::MemoryOutOfBounds)])
.run();
}
fn test_store_at_overflow(wasm_op: WasmOp) {
test_store_at_overflow_for(wasm_op, 1, u32::MAX);
test_store_at_overflow_for(wasm_op, u32::MAX, 1);
test_store_at_overflow_for(wasm_op, u32::MAX, u32::MAX);
}
fn test_store_at_imm_for<T>(
wasm_op: WasmOp,
ptr: u32,
offset: u32,
value: T,
make_instr: fn(address: Const32<u32>, value: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
let address = ptr
.checked_add(offset)
.expect("testcase requires valid ptr+offset address");
let display_value = DisplayWasm::from(value);
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func
i32.const {ptr}
{param_ty}.const {display_value}
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func(
ExpectedFunc::new([
make_instr(Const32::from(address), Register::from_i16(-1)),
Instruction::Return,
])
.consts([value]),
)
.run();
}
fn test_store_at_imm<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(address: Const32<u32>, value: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
test_store_at_imm_for(wasm_op, 0, 0, value, make_instr);
test_store_at_imm_for(wasm_op, 0, 1, value, make_instr);
test_store_at_imm_for(wasm_op, 1, 0, value, make_instr);
test_store_at_imm_for(wasm_op, 1, 1, value, make_instr);
test_store_at_imm_for(wasm_op, 1000, 1000, value, make_instr);
test_store_at_imm_for(wasm_op, 1, u32::MAX - 1, value, make_instr);
test_store_at_imm_for(wasm_op, u32::MAX - 1, 1, value, make_instr);
test_store_at_imm_for(wasm_op, 0, u32::MAX, value, make_instr);
test_store_at_imm_for(wasm_op, u32::MAX, 0, value, make_instr);
}
fn test_store_at_imm_overflow_for<T>(wasm_op: WasmOp, ptr: u32, offset: u32, value: T)
where
T: Copy,
DisplayWasm<T>: Display,
{
assert!(
ptr.checked_add(offset).is_none(),
"testcase expects overflowing ptr+offset address"
);
let display_value = DisplayWasm::from(value);
let param_ty = wasm_op.param_ty();
let wasm = format!(
r#"
(module
(memory 1)
(func
i32.const {ptr}
{param_ty}.const {display_value}
{wasm_op} offset={offset}
)
)
"#
);
TranslationTest::from_wat(&wasm)
.expect_func_instrs([Instruction::Trap(TrapCode::MemoryOutOfBounds)])
.run();
}
fn test_store_at_imm_overflow<T>(wasm_op: WasmOp, value: T)
where
T: Copy,
DisplayWasm<T>: Display,
{
test_store_at_imm_overflow_for(wasm_op, 1, u32::MAX, value);
test_store_at_imm_overflow_for(wasm_op, u32::MAX, 1, value);
test_store_at_imm_overflow_for(wasm_op, u32::MAX, u32::MAX, value);
}