use crate::cast;
use crate::globals::Globals;
use crate::import::{ImportInvalidError, ImportInvokeError, Importer};
use crate::memory::Memory;
use crate::stack::{CallFrame, Stack, StackAccess};
use crate::table::Table;
use crate::trap::{Result, Trap, TrapReason};
use crate::value::{Float, LittleEndian, Value};
use wain_ast as ast;
use wain_ast::AsValType;
enum ExecState {
Breaking(u32), Ret, Continue, }
type ExecResult = Result<ExecState>;
fn fmin<F: Float>(l: F, r: F) -> F {
if l.is_nan() {
l.to_arithmetic_nan()
} else if r.is_nan() {
r.to_arithmetic_nan()
} else if l == r {
F::from_bits(l.to_bits() | r.to_bits())
} else {
l.min(r)
}
}
fn fmax<F: Float>(l: F, r: F) -> F {
if l.is_nan() {
l.to_arithmetic_nan()
} else if r.is_nan() {
r.to_arithmetic_nan()
} else if l == r {
F::from_bits(l.to_bits() & r.to_bits())
} else {
l.max(r)
}
}
pub struct Machine<'module, 'source, I: Importer> {
module: &'module ast::Module<'source>,
table: Table, stack: Stack,
memory: Memory, globals: Globals,
importer: I,
}
impl<'m, 's, I: Importer> Machine<'m, 's, I> {
pub fn instantiate(module: &'m ast::Module<'s>, importer: I) -> Result<Self> {
fn unknown_import<'s>(import: &ast::Import<'s>, at: usize) -> Box<Trap> {
Trap::new(
TrapReason::UnknownImport {
mod_name: import.mod_name.0.to_string(),
name: import.name.0.to_string(),
kind: "function",
},
at,
)
}
for func in module.funcs.iter() {
match &func.kind {
ast::FuncKind::Body { .. } => break, ast::FuncKind::Import(i) => {
let mod_name = &i.mod_name.0;
if mod_name == "env" {
let fty = &module.types[func.idx as usize];
let name = &i.name.0;
match importer.validate(name, &fty.params, fty.results.get(0).copied()) {
Some(ImportInvalidError::NotFound) => {
return Err(unknown_import(i, func.start));
}
Some(ImportInvalidError::SignatureMismatch {
expected_params,
expected_ret,
}) => {
return Err(Trap::new(
TrapReason::FuncSignatureMismatch {
import: Some((mod_name.to_string(), name.to_string())),
expected_params: expected_params.iter().copied().collect(),
expected_results: expected_ret.into_iter().collect(),
actual_params: fty.params.iter().copied().collect(),
actual_results: fty.results.clone().into_boxed_slice(),
},
func.start,
))
}
None => { }
}
} else {
return Err(unknown_import(i, func.start));
}
}
}
}
let globals = Globals::instantiate(&module.globals)?;
let mut table = Table::allocate(&module.tables)?;
let mut memory = Memory::allocate(&module.memories)?;
let stack = Stack::default();
for elem in module.elems.iter() {
table.new_elem(elem, &globals)?;
}
for data in module.data.iter() {
memory.new_data(data, &globals)?;
}
let mut machine = Self {
module,
table,
stack,
memory,
globals,
importer,
};
if let Some(start) = &machine.module.entrypoint {
machine.invoke_by_funcidx(start.idx)?;
}
Ok(machine)
}
pub fn module(&self) -> &'m ast::Module<'s> {
&self.module
}
pub fn memory(&self) -> &Memory {
&self.memory
}
pub fn get_global(&self, name: &str) -> Option<Value> {
self.module
.exports
.iter()
.find_map(|e| match e.kind {
ast::ExportKind::Global(idx) if e.name.0 == name => Some(idx),
_ => None,
})
.map(|idx| {
let ty = self.module.globals[idx as usize].ty;
self.globals.get_any(idx, ty)
})
}
fn invoke_import(
&mut self,
import: &ast::Import<'s>,
has_ret: bool,
pos: usize,
) -> Result<bool> {
if import.mod_name.0 == "env" {
match self
.importer
.call(&import.name.0, &mut self.stack, &mut self.memory)
{
Ok(()) => return Ok(has_ret),
Err(ImportInvokeError::Fatal { message }) => {
return Err(Trap::new(
TrapReason::ImportFuncCallFail {
mod_name: import.mod_name.0.to_string(),
name: import.name.0.to_string(),
msg: message,
},
pos,
))
}
}
}
unreachable!(
"fatal: invalid import at runtime: {}::{}",
import.mod_name.0, import.name.0
);
}
fn invoke_by_funcidx(&mut self, funcidx: u32) -> Result<bool> {
let func = &self.module.funcs[funcidx as usize];
let fty = &self.module.types[func.idx as usize];
let (locals, body) = match &func.kind {
ast::FuncKind::Import(i) => {
return self.invoke_import(i, !fty.results.is_empty(), func.start)
}
ast::FuncKind::Body { locals, expr } => (locals, expr),
};
let frame = CallFrame::new(&self.stack, &fty.params, locals);
self.stack.extend_zero_values(&locals);
for insn in body.iter() {
match insn.execute(self, &frame)? {
ExecState::Continue => {}
ExecState::Ret | ExecState::Breaking(_) => break,
}
}
if fty.results.is_empty() {
self.stack.restore(frame.base_addr, frame.base_idx); Ok(false)
} else {
let v: Value = self.stack.pop();
self.stack.restore(frame.base_addr, frame.base_idx); self.stack.push(v); Ok(true)
}
}
pub fn invoke(&mut self, name: impl AsRef<str>, args: &[Value]) -> Result<Option<Value>> {
fn find_func_to_invoke<'s>(
name: &str,
exports: &[ast::Export<'s>],
) -> Result<(u32, usize)> {
for export in exports {
if export.name.0 == name {
let actual = match export.kind {
ast::ExportKind::Func(idx) => return Ok((idx, export.start)),
ast::ExportKind::Table(_) => "table",
ast::ExportKind::Memory(_) => "memory",
ast::ExportKind::Global(_) => "global variable",
};
return Err(Trap::new(
TrapReason::WrongInvokeTarget {
name: name.to_string(),
actual: Some(actual),
},
export.start,
));
}
}
Err(Trap::new(
TrapReason::WrongInvokeTarget {
name: name.to_string(),
actual: None,
},
0,
))
}
let name = name.as_ref();
let (funcidx, start) = find_func_to_invoke(name, &self.module.exports)?;
let arg_types = &self.module.types[self.module.funcs[funcidx as usize].idx as usize].params;
if args
.iter()
.map(Value::valtype)
.ne(arg_types.iter().copied())
{
return Err(Trap::new(
TrapReason::InvokeInvalidArgs {
name: name.to_string(),
args: args.iter().cloned().collect(),
arg_types: arg_types.iter().copied().collect(),
},
start,
));
}
for arg in args {
self.stack.push(arg.clone());
}
if self.invoke_by_funcidx(funcidx)? {
Ok(Some(self.stack.pop()))
} else {
Ok(None)
}
}
fn mem_addr(&mut self, mem: &ast::Mem) -> usize {
let addr = self.stack.pop::<i32>() as usize;
addr + mem.offset as usize
}
fn load<V: LittleEndian>(&mut self, mem: &ast::Mem, at: usize) -> Result<V> {
let addr = self.mem_addr(mem);
Ok(self.memory.load(addr, at)?)
}
fn store<V: LittleEndian>(&mut self, mem: &ast::Mem, v: V, at: usize) -> Result<()> {
let addr = self.mem_addr(mem);
self.memory.store(addr, v, at)?;
Ok(())
}
fn unop<T, F>(&mut self, op: F)
where
T: StackAccess + LittleEndian,
F: FnOnce(T) -> T,
{
let ret = op(self.stack.top());
self.stack.write_top_bytes(ret);
}
fn binop<T, F>(&mut self, op: F)
where
T: StackAccess + LittleEndian,
F: FnOnce(T, T) -> T,
{
let c2 = self.stack.pop();
let c1 = self.stack.top();
let ret = op(c1, c2);
self.stack.write_top_bytes(ret);
}
fn binop_trap<T, F>(&mut self, op: F) -> Result<()>
where
T: StackAccess + LittleEndian,
F: FnOnce(T, T) -> Result<T>,
{
let c2 = self.stack.pop();
let c1 = self.stack.top();
let ret = op(c1, c2)?;
self.stack.write_top_bytes(ret);
Ok(())
}
fn testop<T, F>(&mut self, op: F)
where
T: StackAccess + LittleEndian,
F: FnOnce(T) -> bool,
{
let ret = op(self.stack.top());
self.stack.write_top::<T, i32>(if ret { 1 } else { 0 });
}
fn relop<T, F>(&mut self, op: F)
where
T: StackAccess + LittleEndian,
F: FnOnce(T, T) -> bool,
{
let c2 = self.stack.pop();
let c1 = self.stack.top();
let ret = op(c1, c2);
self.stack.write_top::<T, i32>(if ret { 1i32 } else { 0 });
}
fn cvtop<T, U, F>(&mut self, op: F)
where
T: StackAccess,
U: StackAccess + LittleEndian + AsValType,
F: FnOnce(T) -> U,
{
let ret = op(self.stack.top());
self.stack.write_top::<T, U>(ret);
}
fn cvtop_trap<T, U, F>(&mut self, op: F) -> Result<()>
where
T: StackAccess,
U: StackAccess + LittleEndian + AsValType,
F: FnOnce(T) -> Result<U>,
{
let ret = op(self.stack.top())?;
self.stack.write_top::<T, U>(ret);
Ok(())
}
}
trait Execute<'f, 'm, 's, I: Importer> {
fn execute(&self, machine: &mut Machine<'m, 's, I>, frame: &CallFrame<'f>) -> ExecResult;
}
impl<'f, 'm, 's, I: Importer> Execute<'f, 'm, 's, I> for Vec<ast::Instruction> {
fn execute(&self, machine: &mut Machine<'m, 's, I>, frame: &CallFrame<'f>) -> ExecResult {
for insn in self.iter() {
match insn.execute(machine, frame)? {
ExecState::Continue => {}
state => return Ok(state), }
}
Ok(ExecState::Continue)
}
}
impl<'f, 'm, 's, I: Importer> Execute<'f, 'm, 's, I> for ast::Instruction {
#[allow(clippy::cognitive_complexity)]
fn execute(&self, machine: &mut Machine<'m, 's, I>, frame: &CallFrame<'f>) -> ExecResult {
use ast::InsnKind::*;
#[allow(clippy::float_cmp)]
match &self.kind {
Block { ty, body } => {
let label = machine.stack.push_label(*ty);
match body.execute(machine, frame)? {
ExecState::Continue => {}
ExecState::Ret => return Ok(ExecState::Ret),
ExecState::Breaking(0) => {}
ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
}
machine.stack.pop_label(label);
}
Loop { ty, body } => loop {
let label = machine.stack.push_label(*ty);
match body.execute(machine, frame)? {
ExecState::Continue => {
machine.stack.pop_label(label);
break;
}
ExecState::Ret => return Ok(ExecState::Ret),
ExecState::Breaking(0) => continue,
ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
}
},
If {
ty,
then_body,
else_body,
} => {
let cond: i32 = machine.stack.pop();
let label = machine.stack.push_label(*ty);
let insns = if cond != 0 { then_body } else { else_body };
match insns.execute(machine, frame)? {
ExecState::Continue => {}
ExecState::Ret => return Ok(ExecState::Ret),
ExecState::Breaking(0) => {}
ExecState::Breaking(level) => return Ok(ExecState::Breaking(level - 1)),
}
machine.stack.pop_label(label);
}
Unreachable => return Err(Trap::new(TrapReason::ReachUnreachable, self.start)),
Nop => { }
Br(labelidx) => return Ok(ExecState::Breaking(*labelidx)),
BrIf(labelidx) => {
let cond: i32 = machine.stack.pop();
if cond != 0 {
return Ok(ExecState::Breaking(*labelidx));
}
}
BrTable {
labels,
default_label,
} => {
let idx: i32 = machine.stack.pop();
let idx = idx as usize;
let labelidx = if idx < labels.len() {
labels[idx]
} else {
*default_label
};
return Ok(ExecState::Breaking(labelidx));
}
Return => return Ok(ExecState::Ret),
Call(funcidx) => {
machine.invoke_by_funcidx(*funcidx)?;
}
CallIndirect(typeidx) => {
let expected = &machine.module.types[*typeidx as usize];
let elemidx: i32 = machine.stack.pop();
let funcidx = machine.table.at(elemidx as usize, self.start)?;
let func = &machine.module.funcs[funcidx as usize];
let actual = &machine.module.types[func.idx as usize];
if expected.params.iter().ne(actual.params.iter())
|| expected.results.iter().ne(actual.results.iter())
{
return Err(Trap::new(
TrapReason::FuncSignatureMismatch {
import: None,
expected_params: expected.params.clone().into_boxed_slice(),
expected_results: expected.results.clone().into_boxed_slice(),
actual_params: actual.params.clone().into_boxed_slice(),
actual_results: actual.results.clone().into_boxed_slice(),
},
self.start,
));
}
machine.invoke_by_funcidx(funcidx)?;
}
Drop => {
machine.stack.pop::<Value>();
}
Select => {
let cond: i32 = machine.stack.pop();
if cond != 0 {
let _val2: Value = machine.stack.pop();
} else {
let val2: Value = machine.stack.pop();
let _val1: Value = machine.stack.pop();
machine.stack.push(val2);
}
}
LocalGet(localidx) => {
let addr = frame.local_addr(*localidx);
match frame.local_type(*localidx) {
ast::ValType::I32 => machine.stack.push(machine.stack.read::<i32>(addr)),
ast::ValType::I64 => machine.stack.push(machine.stack.read::<i64>(addr)),
ast::ValType::F32 => machine.stack.push(machine.stack.read::<f32>(addr)),
ast::ValType::F64 => machine.stack.push(machine.stack.read::<f64>(addr)),
}
}
LocalSet(localidx) => {
let addr = frame.local_addr(*localidx);
let val = machine.stack.pop();
machine.stack.write_any(addr, val);
}
LocalTee(localidx) => {
let addr = frame.local_addr(*localidx);
let val = machine.stack.top();
machine.stack.write_any(addr, val);
}
GlobalGet(globalidx) => match machine.module.globals[*globalidx as usize].ty {
ast::ValType::I32 => machine.stack.push(machine.globals.get::<i32>(*globalidx)),
ast::ValType::I64 => machine.stack.push(machine.globals.get::<i64>(*globalidx)),
ast::ValType::F32 => machine.stack.push(machine.globals.get::<f32>(*globalidx)),
ast::ValType::F64 => machine.stack.push(machine.globals.get::<f64>(*globalidx)),
},
GlobalSet(globalidx) => machine.globals.set_any(*globalidx, machine.stack.top()),
I32Load(mem) => {
let v: i32 = machine.load(mem, self.start)?;
machine.stack.push(v);
}
I64Load(mem) => {
let v: i64 = machine.load(mem, self.start)?;
machine.stack.push(v);
}
F32Load(mem) => {
let v: f32 = machine.load(mem, self.start)?;
machine.stack.push(v);
}
F64Load(mem) => {
let v: f64 = machine.load(mem, self.start)?;
machine.stack.push(v);
}
I32Load8S(mem) => {
let v: i8 = machine.load(mem, self.start)?;
machine.stack.push(v as i32);
}
I32Load8U(mem) => {
let v: u8 = machine.load(mem, self.start)?;
machine.stack.push(v as i32);
}
I32Load16S(mem) => {
let v: i16 = machine.load(mem, self.start)?;
machine.stack.push(v as i32);
}
I32Load16U(mem) => {
let v: u16 = machine.load(mem, self.start)?;
machine.stack.push(v as i32);
}
I64Load8S(mem) => {
let v: i8 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I64Load8U(mem) => {
let v: u8 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I64Load16S(mem) => {
let v: i16 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I64Load16U(mem) => {
let v: u16 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I64Load32S(mem) => {
let v: i32 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I64Load32U(mem) => {
let v: u32 = machine.load(mem, self.start)?;
machine.stack.push(v as i64);
}
I32Store(mem) => {
let v: i32 = machine.stack.pop();
machine.store(mem, v, self.start)?;
}
I64Store(mem) => {
let v: i64 = machine.stack.pop();
machine.store(mem, v, self.start)?;
}
F32Store(mem) => {
let v: f32 = machine.stack.pop();
machine.store(mem, v, self.start)?;
}
F64Store(mem) => {
let v: f64 = machine.stack.pop();
machine.store(mem, v, self.start)?;
}
I32Store8(mem) => {
let v: i32 = machine.stack.pop();
machine.store(mem, v as i8, self.start)?;
}
I32Store16(mem) => {
let v: i32 = machine.stack.pop();
machine.store(mem, v as i16, self.start)?;
}
I64Store8(mem) => {
let v: i64 = machine.stack.pop();
machine.store(mem, v as i8, self.start)?;
}
I64Store16(mem) => {
let v: i64 = machine.stack.pop();
machine.store(mem, v as i16, self.start)?;
}
I64Store32(mem) => {
let v: i64 = machine.stack.pop();
machine.store(mem, v as i32, self.start)?;
}
MemorySize => machine.stack.push(machine.memory.size() as i32),
MemoryGrow => {
let pages: i32 = machine.stack.pop();
let prev_pages = machine.memory.grow(pages as u32);
machine.stack.push(prev_pages);
}
I32Const(i) => machine.stack.push(*i),
I64Const(i) => machine.stack.push(*i),
F32Const(f) => machine.stack.push(*f),
F64Const(f) => machine.stack.push(*f),
I32Clz => machine.unop::<i32, _>(|v| v.leading_zeros() as i32),
I64Clz => machine.unop::<i64, _>(|v| v.leading_zeros() as i64),
I32Ctz => machine.unop::<i32, _>(|v| v.trailing_zeros() as i32),
I64Ctz => machine.unop::<i64, _>(|v| v.trailing_zeros() as i64),
I32Popcnt => machine.unop::<i32, _>(|v| v.count_ones() as i32),
I64Popcnt => machine.unop::<i64, _>(|v| v.count_ones() as i64),
I32Add => machine.binop::<i32, _>(|l, r| l.wrapping_add(r)),
I64Add => machine.binop::<i64, _>(|l, r| l.wrapping_add(r)),
I32Sub => machine.binop::<i32, _>(|l, r| l.wrapping_sub(r)),
I64Sub => machine.binop::<i64, _>(|l, r| l.wrapping_sub(r)),
I32Mul => machine.binop::<i32, _>(|l, r| l.wrapping_mul(r)),
I64Mul => machine.binop::<i64, _>(|l, r| l.wrapping_mul(r)),
I32DivS => machine.binop_trap::<i32, _>(|l, r| match l.checked_div(r) {
Some(i) => Ok(i),
None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
})?,
I64DivS => machine.binop_trap::<i64, _>(|l, r| match l.checked_div(r) {
Some(i) => Ok(i),
None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
})?,
I32DivU => {
machine.binop_trap::<i32, _>(|l, r| match (l as u32).checked_div(r as u32) {
Some(u) => Ok(u as i32),
None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
})?
}
I64DivU => {
machine.binop_trap::<i64, _>(|l, r| match (l as u64).checked_div(r as u64) {
Some(u) => Ok(u as i64),
None => Err(Trap::new(TrapReason::DivByZeroOrOverflow, self.start)),
})?
}
I32RemS => machine.binop_trap::<i32, _>(|l, r| {
if r == 0 {
Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
} else {
Ok(l.wrapping_rem(r))
}
})?,
I64RemS => machine.binop_trap::<i64, _>(|l, r| {
if r == 0 {
Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
} else {
Ok(l.wrapping_rem(r))
}
})?,
I32RemU => machine.binop_trap::<i32, _>(|l, r| {
if r == 0 {
Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
} else {
Ok((l as u32 % r as u32) as i32) }
})?,
I64RemU => machine.binop_trap::<i64, _>(|l, r| {
if r == 0 {
Err(Trap::new(TrapReason::RemZeroDivisor, self.start))
} else {
Ok((l as u64 % r as u64) as i64) }
})?,
I32And => machine.binop::<i32, _>(|l, r| l & r),
I64And => machine.binop::<i64, _>(|l, r| l & r),
I32Or => machine.binop::<i32, _>(|l, r| l | r),
I64Or => machine.binop::<i64, _>(|l, r| l | r),
I32Xor => machine.binop::<i32, _>(|l, r| l ^ r),
I64Xor => machine.binop::<i64, _>(|l, r| l ^ r),
I32Shl => machine.binop::<i32, _>(|l, r| l.wrapping_shl(r as u32)),
I64Shl => machine.binop::<i64, _>(|l, r| l.wrapping_shl(r as u32)),
I32ShrS => machine.binop::<i32, _>(|l, r| l.wrapping_shr(r as u32)),
I64ShrS => machine.binop::<i64, _>(|l, r| l.wrapping_shr(r as u32)),
I32ShrU => machine.binop::<i32, _>(|l, r| (l as u32).wrapping_shr(r as u32) as i32),
I64ShrU => machine.binop::<i64, _>(|l, r| (l as u64).wrapping_shr(r as u32) as i64),
I32Rotl => machine.binop::<i32, _>(|l, r| l.rotate_left(r as u32)),
I64Rotl => machine.binop::<i64, _>(|l, r| l.rotate_left(r as u32)),
I32Rotr => machine.binop::<i32, _>(|l, r| l.rotate_right(r as u32)),
I64Rotr => machine.binop::<i64, _>(|l, r| l.rotate_right(r as u32)),
F32Abs => machine.unop::<f32, _>(|f| f.abs()),
F64Abs => machine.unop::<f64, _>(|f| f.abs()),
F32Neg => machine.unop::<f32, _>(|f| -f),
F64Neg => machine.unop::<f64, _>(|f| -f),
F32Ceil => machine.unop::<f32, _>(|f| f.ceil()),
F64Ceil => machine.unop::<f64, _>(|f| f.ceil()),
F32Floor => machine.unop::<f32, _>(|f| f.floor()),
F64Floor => machine.unop::<f64, _>(|f| f.floor()),
F32Trunc => machine.unop::<f32, _>(|f| f.trunc()),
F64Trunc => machine.unop::<f64, _>(|f| f.trunc()),
F32Nearest => machine.unop::<f32, _>(|f| {
let fround = f.round();
if (f - fround).abs() == 0.5 && fround % 2.0 != 0.0 {
f.trunc()
} else {
fround
}
}),
F64Nearest => machine.unop::<f64, _>(|f| {
let fround = f.round();
if (f - fround).abs() == 0.5 && fround % 2.0 != 0.0 {
f.trunc()
} else {
fround
}
}),
F32Sqrt => machine.unop::<f32, _>(|f| f.sqrt()),
F64Sqrt => machine.unop::<f64, _>(|f| f.sqrt()),
F32Add => machine.binop::<f32, _>(|l, r| l + r),
F64Add => machine.binop::<f64, _>(|l, r| l + r),
F32Sub => machine.binop::<f32, _>(|l, r| l - r),
F64Sub => machine.binop::<f64, _>(|l, r| l - r),
F32Mul => machine.binop::<f32, _>(|l, r| l * r),
F64Mul => machine.binop::<f64, _>(|l, r| l * r),
F32Div => machine.binop::<f32, _>(|l, r| l / r),
F64Div => machine.binop::<f64, _>(|l, r| l / r),
F32Min => machine.binop::<f32, _>(fmin),
F64Min => machine.binop::<f64, _>(fmin),
F32Max => machine.binop::<f32, _>(fmax),
F64Max => machine.binop::<f64, _>(fmax),
F32Copysign => machine.binop::<f32, _>(|l, r| l.copysign(r)),
F64Copysign => machine.binop::<f64, _>(|l, r| l.copysign(r)),
I32Eqz => machine.testop::<i32, _>(|i| i == 0),
I64Eqz => machine.testop::<i64, _>(|i| i == 0),
I32Eq => machine.relop::<i32, _>(|l, r| l == r),
I64Eq => machine.relop::<i64, _>(|l, r| l == r),
I32Ne => machine.relop::<i32, _>(|l, r| l != r),
I64Ne => machine.relop::<i64, _>(|l, r| l != r),
I32LtS => machine.relop::<i32, _>(|l, r| l < r),
I64LtS => machine.relop::<i64, _>(|l, r| l < r),
I32LtU => machine.relop::<i32, _>(|l, r| (l as u32) < r as u32),
I64LtU => machine.relop::<i64, _>(|l, r| (l as u64) < r as u64),
I32GtS => machine.relop::<i32, _>(|l, r| l > r),
I64GtS => machine.relop::<i64, _>(|l, r| l > r),
I32GtU => machine.relop::<i32, _>(|l, r| l as u32 > r as u32),
I64GtU => machine.relop::<i64, _>(|l, r| l as u64 > r as u64),
I32LeS => machine.relop::<i32, _>(|l, r| l <= r),
I64LeS => machine.relop::<i64, _>(|l, r| l <= r),
I32LeU => machine.relop::<i32, _>(|l, r| l as u32 <= r as u32),
I64LeU => machine.relop::<i64, _>(|l, r| l as u64 <= r as u64),
I32GeS => machine.relop::<i32, _>(|l, r| l >= r),
I64GeS => machine.relop::<i64, _>(|l, r| l >= r),
I32GeU => machine.relop::<i32, _>(|l, r| l as u32 >= r as u32),
I64GeU => machine.relop::<i64, _>(|l, r| l as u64 >= r as u64),
F32Eq => machine.relop::<f32, _>(|l, r| l == r),
F64Eq => machine.relop::<f64, _>(|l, r| l == r),
F32Ne => machine.relop::<f32, _>(|l, r| l != r),
F64Ne => machine.relop::<f64, _>(|l, r| l != r),
F32Lt => machine.relop::<f32, _>(|l, r| l < r),
F64Lt => machine.relop::<f64, _>(|l, r| l < r),
F32Gt => machine.relop::<f32, _>(|l, r| l > r),
F64Gt => machine.relop::<f64, _>(|l, r| l > r),
F32Le => machine.relop::<f32, _>(|l, r| l <= r),
F64Le => machine.relop::<f64, _>(|l, r| l <= r),
F32Ge => machine.relop::<f32, _>(|l, r| l >= r),
F64Ge => machine.relop::<f64, _>(|l, r| l >= r),
I64ExtendI32U => machine.cvtop::<i32, i64, _>(|v| v as u32 as i64),
I64ExtendI32S => machine.cvtop::<i32, i64, _>(|v| v as i64),
I32WrapI64 => machine.cvtop::<i64, i32, _>(|v| v as i32),
I32TruncF32U => machine.cvtop_trap::<f32, i32, _>(|v| match cast::f32_to_u32(v) {
Some(u) => Ok(u as i32),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F32(v),
dest_type: "u32",
},
self.start,
)),
})?,
I32TruncF64U => machine.cvtop_trap::<f64, i32, _>(|v| match cast::f64_to_u32(v) {
Some(u) => Ok(u as i32),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F64(v),
dest_type: "u32",
},
self.start,
)),
})?,
I64TruncF32U => machine.cvtop_trap::<f32, i64, _>(|v| match cast::f32_to_u64(v) {
Some(u) => Ok(u as i64),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F32(v),
dest_type: "u64",
},
self.start,
)),
})?,
I64TruncF64U => machine.cvtop_trap::<f64, i64, _>(|v| match cast::f64_to_u64(v) {
Some(u) => Ok(u as i64),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F64(v),
dest_type: "u64",
},
self.start,
)),
})?,
I32TruncF32S => machine.cvtop_trap::<f32, i32, _>(|v| match cast::f32_to_i32(v) {
Some(u) => Ok(u),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F32(v),
dest_type: "i32",
},
self.start,
)),
})?,
I32TruncF64S => machine.cvtop_trap::<f64, i32, _>(|v| match cast::f64_to_i32(v) {
Some(u) => Ok(u),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F64(v),
dest_type: "i32",
},
self.start,
)),
})?,
I64TruncF32S => machine.cvtop_trap::<f32, i64, _>(|v| match cast::f32_to_i64(v) {
Some(u) => Ok(u),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F32(v),
dest_type: "i64",
},
self.start,
)),
})?,
I64TruncF64S => machine.cvtop_trap::<f64, i64, _>(|v| match cast::f64_to_i64(v) {
Some(u) => Ok(u),
None => Err(Trap::new(
TrapReason::ValueOutOfRange {
src_val: Value::F64(v),
dest_type: "i64",
},
self.start,
)),
})?,
F64PromoteF32 => machine.cvtop::<f32, f64, _>(|v| v as f64),
F32DemoteF64 => machine.cvtop::<f64, f32, _>(|v| v as f32),
F32ConvertI32U => machine.cvtop::<i32, f32, _>(|v| v as u32 as f32),
F32ConvertI64U => machine.cvtop::<i64, f32, _>(|v| v as u64 as f32),
F64ConvertI32U => machine.cvtop::<i32, f64, _>(|v| v as u32 as f64),
F64ConvertI64U => machine.cvtop::<i64, f64, _>(|v| v as u64 as f64),
F32ConvertI32S => machine.cvtop::<i32, f32, _>(|v| v as f32),
F32ConvertI64S => machine.cvtop::<i64, f32, _>(|v| v as f32),
F64ConvertI32S => machine.cvtop::<i32, f64, _>(|v| v as f64),
F64ConvertI64S => machine.cvtop::<i64, f64, _>(|v| v as f64),
I32ReinterpretF32 => machine.stack.write_top_type(i32::VAL_TYPE),
I64ReinterpretF64 => machine.stack.write_top_type(i64::VAL_TYPE),
F32ReinterpretI32 => machine.stack.write_top_type(f32::VAL_TYPE),
F64ReinterpretI64 => machine.stack.write_top_type(f64::VAL_TYPE),
}
Ok(ExecState::Continue)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::import::DefaultImporter;
use std::borrow::Cow;
use std::env;
use std::fmt;
use std::fs;
use std::io::{self, Read, Write};
use std::path::PathBuf;
use std::result;
use wain_syntax_text::parse;
use wain_validate::validate;
struct Discard;
impl Read for Discard {
fn read(&mut self, b: &mut [u8]) -> io::Result<usize> {
Ok(b.len())
}
}
impl Write for Discard {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
Ok(buf.len())
}
fn flush(&mut self) -> io::Result<()> {
Ok(())
}
}
#[test]
fn hello_world() {
fn unwrap<T, E: fmt::Display>(res: result::Result<T, E>) -> T {
match res {
Ok(x) => x,
Err(e) => panic!("unwrap failed with error message:\n{}", e),
}
}
fn exec(file: PathBuf) -> Result<Vec<u8>> {
let source = fs::read_to_string(file).unwrap();
let ast = unwrap(parse(&source));
unwrap(validate(&ast));
let mut stdout = vec![];
{
let importer = DefaultImporter::with_stdio(Discard, &mut stdout);
let mut machine = unwrap(Machine::instantiate(&ast.module, importer));
machine.invoke("_start", &[])?;
}
Ok(stdout)
}
let mut dir = env::current_dir().unwrap();
dir.pop();
dir.push("examples");
dir.push("hello");
let dir = dir;
let stdout = exec(dir.join("hello.wat")).unwrap();
assert_eq!(stdout, b"Hello, world\n");
let stdout = exec(dir.join("hello_global.wat")).unwrap();
assert_eq!(stdout, b"Hello, world\n");
let stdout = exec(dir.join("hello_indirect_call.wat")).unwrap();
assert_eq!(stdout, b"Hello, world\n");
let stdout = exec(dir.join("hello_struct.wat")).unwrap();
assert_eq!(stdout, b"Hello, world\n");
}
fn exec_insns(ty: ast::ValType, insns: Vec<ast::InsnKind>) -> Result<Option<Value>> {
let expr = insns
.into_iter()
.map(|kind| ast::Instruction { start: 0, kind })
.collect();
let mut module = ast::Module::default();
module.memories.push(ast::Memory {
start: 0,
ty: ast::MemType {
limit: ast::Limits::From(0),
},
import: None,
});
module.types.push(ast::FuncType {
start: 0,
params: vec![],
results: vec![ty],
});
module.funcs.push(ast::Func {
start: 0,
idx: 0,
kind: ast::FuncKind::Body {
locals: vec![],
expr,
},
});
module.exports.push(ast::Export {
start: 0,
name: ast::Name(Cow::Borrowed("test")),
kind: ast::ExportKind::Func(0),
});
let importer = DefaultImporter::with_stdio(Discard, Discard);
let mut machine = Machine::instantiate(&module, importer)?;
machine.invoke("test", &[])
}
#[test]
fn nearest_edge_cases() {
use ast::InsnKind::*;
use ast::ValType::*;
let f = exec_insns(F32, vec![F32Const(4.5), F32Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F32(f) if f == 4.0));
let f = exec_insns(F32, vec![F32Const(3.5), F32Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F32(f) if f == 4.0));
let f = exec_insns(F32, vec![F32Const(-0.5), F32Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F32(f) if f == 0.0 && f.is_sign_negative()));
let f = exec_insns(F32, vec![F32Const(0.5), F32Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F32(f) if f == 0.0 && f.is_sign_positive()));
let f = exec_insns(F64, vec![F64Const(4.5), F64Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F64(f) if f == 4.0));
let f = exec_insns(F64, vec![F64Const(3.5), F64Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F64(f) if f == 4.0));
let f = exec_insns(F64, vec![F64Const(-0.5), F64Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F64(f) if f == 0.0 && f.is_sign_negative()));
let f = exec_insns(F64, vec![F64Const(0.5), F64Nearest])
.unwrap()
.unwrap();
assert!(matches!(f, Value::F64(f) if f == 0.0 && f.is_sign_positive() ));
}
#[test]
fn int_overflow() {
use ast::InsnKind::*;
use ast::ValType::*;
let i = exec_insns(I32, vec![I32Const(i32::MAX), I32Const(1), I32Add])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I32(i) if i == i32::MIN));
let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(1), I32Sub])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I32(i) if i == i32::MAX));
let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(-1), I32Mul])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I32(i) if i == i32::MIN));
let i = exec_insns(I64, vec![I64Const(i64::MAX), I64Const(1), I64Add])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I64(i) if i == i64::MIN));
let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(1), I64Sub])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I64(i) if i == i64::MAX));
let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(-1), I64Mul])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I64(i) if i == i64::MIN));
}
#[test]
fn div_rem_edge_cases() {
use ast::InsnKind::*;
use ast::ValType::*;
let i = exec_insns(I32, vec![I32Const(i32::MIN), I32Const(-1), I32RemS])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I32(0)), "{:?}", i);
let i = exec_insns(I64, vec![I64Const(i64::MIN), I64Const(-1), I64RemS])
.unwrap()
.unwrap();
assert!(matches!(i, Value::I64(0)), "{:?}", i);
let e = exec_insns(I32, vec![I32Const(1), I32Const(0), I32RemS]).unwrap_err();
assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
let e = exec_insns(I32, vec![I32Const(1), I32Const(0), I32RemU]).unwrap_err();
assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
let e = exec_insns(I64, vec![I64Const(1), I64Const(0), I64RemS]).unwrap_err();
assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
let e = exec_insns(I64, vec![I64Const(1), I64Const(0), I64RemU]).unwrap_err();
assert!(matches!(e.reason, TrapReason::RemZeroDivisor));
}
#[test]
fn fmin_edge_cases() {
use ast::InsnKind::*;
use ast::ValType::*;
let i = exec_insns(F32, vec![F32Const(0.0), F32Const(-0.0), F32Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x8000_0000));
let i = exec_insns(F32, vec![F32Const(-0.0), F32Const(0.0), F32Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x8000_0000));
let i = exec_insns(F32, vec![F32Const(1.0), F32Const(1.0), F32Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f == 1.0));
let i = exec_insns(F32, vec![F32Const(-42.0), F32Const(-42.0), F32Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f == -42.0));
let i = exec_insns(
F32,
vec![
F32Const(f32::NEG_INFINITY),
F32Const(f32::from_bits(0x7f80_0001)),
F32Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
let i = exec_insns(
F32,
vec![
F32Const(f32::from_bits(0x7fff_ffff)),
F32Const(f32::NEG_INFINITY),
F32Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff));
let i = exec_insns(
F32,
vec![
F32Const(f32::from_bits(0x7f80_0001)),
F32Const(f32::from_bits(0x7fff_ffff)),
F32Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
let i = exec_insns(F64, vec![F64Const(0.0), F64Const(-0.0), F64Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x8000_0000_0000_0000));
let i = exec_insns(F64, vec![F64Const(-0.0), F64Const(0.0), F64Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x8000_0000_0000_0000));
let i = exec_insns(F64, vec![F64Const(1.0), F64Const(1.0), F64Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f == 1.0));
let i = exec_insns(F64, vec![F64Const(-42.0), F64Const(-42.0), F64Min])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f == -42.0));
let i = exec_insns(
F64,
vec![
F64Const(f64::NEG_INFINITY),
F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
F64Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
let i = exec_insns(
F64,
vec![
F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
F64Const(f64::NEG_INFINITY),
F64Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff_ffff_ffff));
let i = exec_insns(
F64,
vec![
F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
F64Min,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
}
#[test]
fn fmax_edge_cases() {
use ast::InsnKind::*;
use ast::ValType::*;
let i = exec_insns(F32, vec![F32Const(0.0), F32Const(-0.0), F32Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x0000_0000));
let i = exec_insns(F32, vec![F32Const(-0.0), F32Const(0.0), F32Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.to_bits() == 0x0000_0000));
let i = exec_insns(F32, vec![F32Const(1.0), F32Const(1.0), F32Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f == 1.0));
let i = exec_insns(F32, vec![F32Const(-42.0), F32Const(-42.0), F32Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f == -42.0));
let i = exec_insns(
F32,
vec![
F32Const(f32::INFINITY),
F32Const(f32::from_bits(0x7f80_0001)),
F32Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
let i = exec_insns(
F32,
vec![
F32Const(f32::from_bits(0x7fff_ffff)),
F32Const(f32::INFINITY),
F32Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff));
let i = exec_insns(
F32,
vec![
F32Const(f32::from_bits(0x7f80_0001)),
F32Const(f32::from_bits(0x7fff_ffff)),
F32Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F32(f) if f.is_nan() && f.to_bits() == 0x7fc0_0001));
let i = exec_insns(F64, vec![F64Const(0.0), F64Const(-0.0), F64Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x0000_0000_0000_0000));
let i = exec_insns(F64, vec![F64Const(-0.0), F64Const(0.0), F64Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.to_bits() == 0x0000_0000_0000_0000));
let i = exec_insns(F64, vec![F64Const(1.0), F64Const(1.0), F64Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f == 1.0));
let i = exec_insns(F64, vec![F64Const(-42.0), F64Const(-42.0), F64Max])
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f == -42.0));
let i = exec_insns(
F64,
vec![
F64Const(f64::INFINITY),
F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
F64Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
let i = exec_insns(
F64,
vec![
F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
F64Const(f64::INFINITY),
F64Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7fff_ffff_ffff_ffff));
let i = exec_insns(
F64,
vec![
F64Const(f64::from_bits(0x7ff0_0000_0000_0001)),
F64Const(f64::from_bits(0x7fff_ffff_ffff_ffff)),
F64Max,
],
)
.unwrap()
.unwrap();
assert!(matches!(i, Value::F64(f) if f.is_nan() && f.to_bits() == 0x7ff8_0000_0000_0001));
}
}