mod display_wasm;
pub mod driver;
mod fuzz;
mod op;
pub mod wasm_type;
use self::{
display_wasm::DisplayWasm,
driver::{ExpectedFunc, TranslationTest},
};
use crate::{
core::UntypedVal,
engine::bytecode::{AnyConst32, Const16, Const32, Instruction, Register},
Config,
Engine,
Module,
};
use std::{fmt::Display, format};
fn create_module(config: &Config, bytes: &[u8]) -> Module {
let engine = Engine::new(config);
Module::new(&engine, bytes).unwrap()
}
macro_rules! swap_ops {
($fn_name:path) => {
|result: Register, lhs: Const16<_>, rhs: Register| -> Instruction {
$fn_name(result, rhs, lhs)
}
};
}
use swap_ops;
fn assert_func_bodies<E, T>(wasm: &str, expected: E)
where
E: IntoIterator<Item = T>,
T: IntoIterator<Item = Instruction>,
<T as IntoIterator>::IntoIter: ExactSizeIterator,
{
let mut testcase = TranslationTest::from_wat(wasm);
for instrs in expected {
testcase.expect_func_instrs(instrs);
}
testcase.run();
}
#[derive(Debug, Copy, Clone)]
pub enum WasmOp {
Binary { ty: WasmType, op: &'static str },
Cmp { ty: WasmType, op: &'static str },
Load { ty: WasmType, op: &'static str },
Store { ty: WasmType, op: &'static str },
}
impl WasmOp {
pub const fn binary(ty: WasmType, op: &'static str) -> Self {
Self::Binary { ty, op }
}
pub const fn cmp(ty: WasmType, op: &'static str) -> Self {
Self::Cmp { ty, op }
}
pub const fn load(ty: WasmType, op: &'static str) -> Self {
Self::Load { ty, op }
}
pub const fn store(ty: WasmType, op: &'static str) -> Self {
Self::Store { ty, op }
}
pub fn param_ty(&self) -> WasmType {
match self {
Self::Binary { ty, op: _ } => *ty,
Self::Cmp { ty, op: _ } => *ty,
Self::Load { .. } => panic!("load instructions have no parameters"),
Self::Store { ty, op: _ } => *ty,
}
}
pub fn result_ty(&self) -> WasmType {
match self {
Self::Binary { ty, op: _ } => *ty,
Self::Cmp { ty: _, op: _ } => WasmType::I32,
Self::Load { ty, op: _ } => *ty,
Self::Store { .. } => panic!("store instructions have no results"),
}
}
pub fn display_ty(&self) -> WasmType {
match self {
Self::Binary { .. } => self.param_ty(),
Self::Cmp { .. } => self.param_ty(),
Self::Load { .. } => self.result_ty(),
Self::Store { .. } => self.param_ty(),
}
}
pub fn op(&self) -> &'static str {
match self {
WasmOp::Binary { ty: _, op } => op,
WasmOp::Cmp { ty: _, op } => op,
WasmOp::Load { ty: _, op } => op,
WasmOp::Store { ty: _, op } => op,
}
}
}
impl Display for WasmOp {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "{}.{}", self.display_ty(), self.op())
}
}
#[derive(Debug, Copy, Clone)]
pub enum WasmType {
I32,
I64,
F32,
F64,
}
impl Display for WasmType {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::I32 => write!(f, "i32"),
Self::I64 => write!(f, "i64"),
Self::F32 => write!(f, "f32"),
Self::F64 => write!(f, "f64"),
}
}
}
fn test_binary_reg_reg(
wasm_op: WasmOp,
make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction,
) {
let param_ty = wasm_op.param_ty();
let result_ty = wasm_op.result_ty();
let wasm = format!(
r#"
(module
(func (param {param_ty}) (param {param_ty}) (result {result_ty})
local.get 0
local.get 1
{wasm_op}
)
)
"#,
);
let expected = [
make_instr(
Register::from_i16(2),
Register::from_i16(0),
Register::from_i16(1),
),
Instruction::return_reg(2),
];
assert_func_bodies(&wasm, [expected]);
}
fn testcase_binary_reg_imm<T>(wasm_op: WasmOp, value: T) -> TranslationTest
where
T: Copy,
DisplayWasm<T>: Display,
{
let param_ty = wasm_op.param_ty();
let result_ty = wasm_op.result_ty();
let display_value = DisplayWasm::from(value);
let wasm = format!(
r#"
(module
(func (param {param_ty}) (result {result_ty})
local.get 0
{param_ty}.const {display_value}
{wasm_op}
)
)
"#,
);
TranslationTest::from_wat(&wasm)
}
fn testcase_binary_imm_reg<T>(wasm_op: WasmOp, value: T) -> TranslationTest
where
T: Copy,
DisplayWasm<T>: Display,
{
let param_ty = wasm_op.param_ty();
let result_ty = wasm_op.result_ty();
let display_value = DisplayWasm::from(value);
let wasm = format!(
r#"
(module
(func (param {param_ty}) (result {result_ty})
{param_ty}.const {display_value}
local.get 0
{wasm_op}
)
)
"#,
);
TranslationTest::from_wat(&wasm)
}
fn test_binary_reg_imm16<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(result: Register, lhs: Register, rhs: Const16<T>) -> Instruction,
) where
T: Copy + TryInto<Const16<T>>,
DisplayWasm<T>: Display,
{
let immediate: Const16<T> = value
.try_into()
.unwrap_or_else(|_| panic!("failed to convert {} to Const16", DisplayWasm::from(value)));
let expected = [
make_instr(Register::from_i16(1), Register::from_i16(0), immediate),
Instruction::return_reg(1),
];
test_binary_reg_imm_with(wasm_op, value, expected).run()
}
fn test_binary_reg_imm16_rev<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(result: Register, lhs: Const16<T>, rhs: Register) -> Instruction,
) where
T: Copy + TryInto<Const16<T>>,
DisplayWasm<T>: Display,
{
let immediate: Const16<T> = value
.try_into()
.unwrap_or_else(|_| panic!("failed to convert {} to Const16", DisplayWasm::from(value)));
let expected = [
make_instr(Register::from_i16(1), immediate, Register::from_i16(0)),
Instruction::return_reg(1),
];
test_binary_reg_imm_rev_with(wasm_op, value, expected).run()
}
fn test_binary_reg_imm32<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
let expected = [
make_instr(
Register::from_i16(1),
Register::from_i16(0),
Register::from_i16(-1),
),
Instruction::return_reg(1),
];
let mut testcase = testcase_binary_reg_imm(wasm_op, value);
testcase.expect_func(ExpectedFunc::new(expected).consts([value.into()]));
testcase.run()
}
fn test_binary_reg_imm32_rev<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
let expected = [
make_instr(
Register::from_i16(1),
Register::from_i16(-1),
Register::from_i16(0),
),
Instruction::return_reg(1),
];
let mut testcase = testcase_binary_imm_reg(wasm_op, value);
testcase.expect_func(ExpectedFunc::new(expected).consts([value.into()]));
testcase.run()
}
fn test_binary_reg_imm32_rev_commutative<T>(
wasm_op: WasmOp,
value: T,
make_instr: fn(result: Register, lhs: Register, rhs: Register) -> Instruction,
) where
T: Copy + Into<UntypedVal>,
DisplayWasm<T>: Display,
{
let expected = [
make_instr(
Register::from_i16(1),
Register::from_i16(0),
Register::from_i16(-1),
),
Instruction::return_reg(1),
];
let mut testcase = testcase_binary_imm_reg(wasm_op, value);
testcase.expect_func(ExpectedFunc::new(expected).consts([value.into()]));
testcase.run()
}
fn test_binary_reg_imm_with<T, E>(wasm_op: WasmOp, value: T, expected: E) -> TranslationTest
where
T: Copy,
DisplayWasm<T>: Display,
E: IntoIterator<Item = Instruction>,
<E as IntoIterator>::IntoIter: ExactSizeIterator,
{
let mut testcase = testcase_binary_reg_imm(wasm_op, value);
testcase.expect_func_instrs(expected);
testcase
}
fn test_binary_reg_imm_rev_with<T, E>(wasm_op: WasmOp, value: T, expected: E) -> TranslationTest
where
T: Copy,
DisplayWasm<T>: Display,
E: IntoIterator<Item = Instruction>,
<E as IntoIterator>::IntoIter: ExactSizeIterator,
{
let mut testcase = testcase_binary_imm_reg(wasm_op, value);
testcase.expect_func_instrs(expected);
testcase
}
fn testcase_binary_consteval<T>(wasm_op: WasmOp, lhs: T, rhs: T) -> TranslationTest
where
T: Copy,
DisplayWasm<T>: Display,
{
let param_ty = wasm_op.param_ty();
let result_ty = wasm_op.result_ty();
let display_lhs = DisplayWasm::from(lhs);
let display_rhs = DisplayWasm::from(rhs);
let wasm = format!(
r#"
(module
(func (result {result_ty})
{param_ty}.const {display_lhs}
{param_ty}.const {display_rhs}
{wasm_op}
)
)
"#,
);
TranslationTest::from_wat(&wasm)
}
fn test_binary_consteval<T, E>(wasm_op: WasmOp, lhs: T, rhs: T, expected: E)
where
T: Copy,
DisplayWasm<T>: Display,
E: IntoIterator<Item = Instruction>,
<E as IntoIterator>::IntoIter: ExactSizeIterator,
{
testcase_binary_consteval(wasm_op, lhs, rhs)
.expect_func_instrs(expected)
.run()
}
fn test_binary_same_reg<E>(wasm_op: WasmOp, expected: E)
where
E: IntoIterator<Item = Instruction>,
<E as IntoIterator>::IntoIter: ExactSizeIterator,
{
let param_ty = wasm_op.param_ty();
let result_ty = wasm_op.result_ty();
let wasm = format!(
r#"
(module
(func (param {param_ty}) (result {result_ty})
local.get 0
local.get 0
{wasm_op}
)
)
"#,
);
assert_func_bodies(&wasm, [expected]);
}