use crate::cast;
use crate::globals::Globals;
use crate::import::{ImportError, Importer};
use crate::memory::Memory;
use crate::stack::{CallFrame, Stack, StackAccess};
use crate::table::Table;
use crate::trap::{Result, Trap, TrapReason};
use crate::value::{LittleEndian, Value};
use wain_ast as ast;
#[derive(PartialEq)]
#[cfg_attr(test, derive(Debug))]
pub enum Run {
Success,
Warning(&'static str),
}
enum ExecState {
Breaking(u32),
Ret,
Continue,
}
type ExecResult = Result<ExecState>;
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> {
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)?;
}
Ok(Self {
module,
table,
stack,
memory,
globals,
importer,
})
}
fn invoke_import(&mut self, import: &ast::Import<'s>, pos: usize) -> ExecResult {
if import.mod_name.0 == "env" {
match self
.importer
.call(&import.name.0, &mut self.stack, &mut self.memory)
{
Ok(()) => return Ok(ExecState::Continue),
Err(ImportError::NotFound) => { }
Err(ImportError::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,
))
}
}
}
Err(Trap::unknown_import(import, "function", pos))
}
fn invoke(&mut self, funcidx: u32) -> ExecResult {
let func = &self.module.funcs[funcidx as usize];
let (locals, body) = match &func.kind {
ast::FuncKind::Import(i) => return self.invoke_import(i, func.start),
ast::FuncKind::Body { locals, expr } => (locals, expr),
};
let fty = &self.module.types[func.idx as usize];
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 => break,
ExecState::Breaking(_) => unreachable!(),
}
}
if fty.results.is_empty() {
self.stack.restore(frame.base_addr, frame.base_idx);
} else {
let v: Value = self.stack.pop();
self.stack.restore(frame.base_addr, frame.base_idx);
self.stack.push(v);
}
Ok(ExecState::Continue)
}
pub fn execute(&mut self) -> Result<Run> {
if let Some(start) = &self.module.entrypoint {
return self.invoke(start.idx).map(|_| Run::Success);
}
for export in self.module.exports.iter() {
if export.name.0 == "_start" {
if let ast::ExportKind::Func(idx) = &export.kind {
return self.invoke(*idx).map(|_| Run::Success);
}
}
}
Ok(Run::Warning("no entrypoint found. 'start' section nor '_start' exported function is set to the module"))
}
fn mem_addr(&mut self, mem: &ast::Mem) -> usize {
let mut addr = self.stack.pop::<i32>() as usize;
if let Some(off) = mem.offset {
addr += off as usize;
}
addr
}
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: StackAccess, F: FnOnce(T) -> T>(&mut self, op: F) {
let ret = op(self.stack.pop());
self.stack.push(ret);
}
fn binop<T: StackAccess, F: FnOnce(T, T) -> T>(&mut self, op: F) {
let c2 = self.stack.pop();
let c1 = self.stack.pop();
let ret = op(c1, c2);
self.stack.push(ret);
}
fn testop<T: StackAccess, F: FnOnce(T) -> bool>(&mut self, op: F) {
let ret = op(self.stack.pop());
self.stack.push::<i32>(if ret { 1 } else { 0 });
}
fn relop<T: StackAccess, F: FnOnce(T, T) -> bool>(&mut self, op: F) {
let c2 = self.stack.pop();
let c1 = self.stack.pop();
let ret = op(c1, c2);
self.stack.push::<i32>(if ret { 1 } else { 0 });
}
fn cvtop<T: StackAccess, U: StackAccess, F: FnOnce(T) -> U>(&mut self, op: F) {
let ret = op(self.stack.pop());
self.stack.push(ret);
}
}
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) => return machine.invoke(*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 {
expected_params: expected.params.clone(),
expected_results: expected.results.clone(),
actual_params: actual.params.clone(),
actual_results: actual.results.clone(),
},
self.start,
));
}
return machine.invoke(funcidx);
}
Drop => {
machine.stack.pop::<Value>();
}
Select => {
let cond: i32 = machine.stack.pop();
let val2: Value = machine.stack.pop();
let val1: Value = machine.stack.pop();
machine.stack.push(if cond != 0 { val1 } else { 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.overflowing_add(r).0),
I64Add => machine.binop::<i64, _>(|l, r| l.overflowing_add(r).0),
I32Sub => machine.binop::<i32, _>(|l, r| l.overflowing_sub(r).0),
I64Sub => machine.binop::<i64, _>(|l, r| l.overflowing_sub(r).0),
I32Mul => machine.binop::<i32, _>(|l, r| l.overflowing_mul(r).0),
I64Mul => machine.binop::<i64, _>(|l, r| l.overflowing_mul(r).0),
I32DivS => machine.binop::<i32, _>(|l, r| l / r),
I64DivS => machine.binop::<i64, _>(|l, r| l / r),
I32DivU => machine.binop::<i32, _>(|l, r| (l as u32 / r as u32) as i32),
I64DivU => machine.binop::<i64, _>(|l, r| (l as u64 / r as u64) as i64),
I32RemS => machine.binop::<i32, _>(|l, r| l % r),
I64RemS => machine.binop::<i64, _>(|l, r| l % r),
I32RemU => machine.binop::<i32, _>(|l, r| (l as u32 % r as u32) as i32),
I64RemU => machine.binop::<i64, _>(|l, r| (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.overflowing_shl(r as u32).0),
I64Shl => machine.binop::<i32, _>(|l, r| l.overflowing_shl(r as u32).0),
I32ShrS => machine.binop::<i32, _>(|l, r| l.overflowing_shr(r as u32).0),
I64ShrS => machine.binop::<i64, _>(|l, r| l.overflowing_shr(r as u32).0),
I32ShrU => machine.binop::<i32, _>(|l, r| (l as u32 >> r as u32) as i32),
I64ShrU => machine.binop::<i64, _>(|l, r| (l as u64 >> r as u64) 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| f.round()),
F64Nearest => machine.unop::<f64, _>(|f| f.round()),
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, _>(|l, r| l.min(r)),
F64Min => machine.binop::<f64, _>(|l, r| l.min(r)),
F32Max => machine.binop::<f32, _>(|l, r| l.max(r)),
F64Max => machine.binop::<f64, _>(|l, r| l.max(r)),
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::<f32, i32, _>(|v| cast::f32_to_u32(v) as i32),
I32TruncF64U => machine.cvtop::<f64, i32, _>(|v| cast::f64_to_u32(v) as i32),
I64TruncF32U => machine.cvtop::<f32, i64, _>(|v| cast::f32_to_u64(v) as i64),
I64TruncF64U => machine.cvtop::<f64, i64, _>(|v| cast::f64_to_u64(v) as i64),
I32TruncF32S => machine.cvtop::<f32, i32, _>(cast::f32_to_i32),
I32TruncF64S => machine.cvtop::<f64, i32, _>(cast::f64_to_i32),
I64TruncF32S => machine.cvtop::<f32, i64, _>(cast::f32_to_i64),
I64TruncF64S => machine.cvtop::<f64, i64, _>(cast::f64_to_i64),
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.cvtop::<f32, i32, _>(|v| v.to_bits() as i32),
I64ReinterpretF64 => machine.cvtop::<f64, i64, _>(|v| v.to_bits() as i64),
F32ReinterpretI32 => machine.cvtop::<i32, f32, _>(|v| f32::from_bits(v as u32)),
F64ReinterpretI64 => machine.cvtop::<i64, f64, _>(|v| f64::from_bits(v as u64)),
}
Ok(ExecState::Continue)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::import::DefaultImporter;
use std::env;
use std::fmt;
use std::fs;
use std::io::{self, Read};
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())
}
}
#[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) -> (Run, Vec<u8>) {
let source = fs::read_to_string(file).unwrap();
let ast = unwrap(parse(&source));
unwrap(validate(&ast));
let mut stdout = vec![];
let run = {
let importer = DefaultImporter::with_stdio(Discard, &mut stdout);
let mut machine = unwrap(Machine::instantiate(&ast.module, importer));
unwrap(machine.execute())
};
(run, stdout)
}
let mut dir = env::current_dir().unwrap();
dir.pop();
dir.push("examples");
dir.push("hello");
let dir = dir;
let (run, stdout) = exec(dir.join("hello.wat"));
assert_eq!(run, Run::Success);
assert_eq!(stdout, b"Hello, world\n");
let (run, stdout) = exec(dir.join("hello_global.wat"));
assert_eq!(run, Run::Success);
assert_eq!(stdout, b"Hello, world\n");
let (run, stdout) = exec(dir.join("hello_indirect_call.wat"));
assert_eq!(run, Run::Success);
assert_eq!(stdout, b"Hello, world\n");
let (run, stdout) = exec(dir.join("hello_struct.wat"));
assert_eq!(run, Run::Success);
assert_eq!(stdout, b"Hello, world\n");
}
}