Remove `Box<[Target]>` from `Instruction` (#141)

This also allows `Instruction` to be `Copy`, which massively speeds
up `<Instructions as Clone>::clone` since it can now just `memcpy`
the bytes using SIMD instead of having to switch on every single
element. I haven't looked at the disassembly of `InstructionIter::next`
yet, it could be that there are even more improvements yet to be gained
from either:

* Only doing work on `BrTable` (this might already be the case depending
  on the whims of the optimiser)
* Using `unsafe` to make it a noop (we really don't want to do this,
  obviously, since it means that `Instructions` has to be immovable)
This commit is contained in:
Jef 2018-11-15 12:18:47 +01:00 committed by Sergey Pepyakin
parent 7b4c648acb
commit e11ba15373
4 changed files with 1277 additions and 686 deletions

View File

@ -74,7 +74,6 @@ use alloc::prelude::*;
///
/// Note that this is a `enum` since Wasm doesn't support multiple return
/// values at the moment.
#[repr(u8)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Keep {
None,
@ -90,30 +89,46 @@ pub struct DropKeep {
pub keep: Keep,
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct Target {
pub dst_pc: u32,
pub drop_keep: DropKeep,
}
/// A relocation entry that specifies.
#[derive(Debug)]
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub enum Reloc {
/// Patch the destination of the branch instruction (br, br_eqz, br_nez)
/// at the specified pc.
Br {
pc: u32,
},
Br { pc: u32 },
/// Patch the specified destination index inside of br_table instruction at
/// the specified pc.
BrTable {
pc: u32,
idx: usize,
},
BrTable { pc: u32, idx: usize },
}
#[derive(Copy, Clone, Debug, PartialEq, Eq)]
pub struct BrTargets<'a> {
stream: &'a [InstructionInternal],
}
impl<'a> BrTargets<'a> {
pub(crate) fn from_internal(targets: &'a [InstructionInternal]) -> Self {
BrTargets { stream: targets }
}
#[inline]
pub fn get(&self, index: u32) -> Target {
match self.stream[index.min(self.stream.len() as u32 - 1) as usize] {
InstructionInternal::BrTableTarget(target) => target,
_ => panic!("BrTable has incorrect target count"),
}
}
}
/// The main interpreted instruction type. This is what is returned by `InstructionIter`, but
/// it is not what is stored internally. For that, see `InstructionInternal`.
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
pub enum Instruction<'a> {
/// Push a local variable or an argument from the specified depth.
GetLocal(u32),
@ -138,7 +153,202 @@ pub enum Instruction {
/// is greater than length of the branch table, then the last index will be used.
///
/// Validation ensures that there should be at least one target.
BrTable(Box<[Target]>),
BrTable(BrTargets<'a>),
Unreachable,
Return(DropKeep),
Call(u32),
CallIndirect(u32),
Drop,
Select,
GetGlobal(u32),
SetGlobal(u32),
I32Load(u32),
I64Load(u32),
F32Load(u32),
F64Load(u32),
I32Load8S(u32),
I32Load8U(u32),
I32Load16S(u32),
I32Load16U(u32),
I64Load8S(u32),
I64Load8U(u32),
I64Load16S(u32),
I64Load16U(u32),
I64Load32S(u32),
I64Load32U(u32),
I32Store(u32),
I64Store(u32),
F32Store(u32),
F64Store(u32),
I32Store8(u32),
I32Store16(u32),
I64Store8(u32),
I64Store16(u32),
I64Store32(u32),
CurrentMemory,
GrowMemory,
I32Const(i32),
I64Const(i64),
F32Const(u32),
F64Const(u64),
I32Eqz,
I32Eq,
I32Ne,
I32LtS,
I32LtU,
I32GtS,
I32GtU,
I32LeS,
I32LeU,
I32GeS,
I32GeU,
I64Eqz,
I64Eq,
I64Ne,
I64LtS,
I64LtU,
I64GtS,
I64GtU,
I64LeS,
I64LeU,
I64GeS,
I64GeU,
F32Eq,
F32Ne,
F32Lt,
F32Gt,
F32Le,
F32Ge,
F64Eq,
F64Ne,
F64Lt,
F64Gt,
F64Le,
F64Ge,
I32Clz,
I32Ctz,
I32Popcnt,
I32Add,
I32Sub,
I32Mul,
I32DivS,
I32DivU,
I32RemS,
I32RemU,
I32And,
I32Or,
I32Xor,
I32Shl,
I32ShrS,
I32ShrU,
I32Rotl,
I32Rotr,
I64Clz,
I64Ctz,
I64Popcnt,
I64Add,
I64Sub,
I64Mul,
I64DivS,
I64DivU,
I64RemS,
I64RemU,
I64And,
I64Or,
I64Xor,
I64Shl,
I64ShrS,
I64ShrU,
I64Rotl,
I64Rotr,
F32Abs,
F32Neg,
F32Ceil,
F32Floor,
F32Trunc,
F32Nearest,
F32Sqrt,
F32Add,
F32Sub,
F32Mul,
F32Div,
F32Min,
F32Max,
F32Copysign,
F64Abs,
F64Neg,
F64Ceil,
F64Floor,
F64Trunc,
F64Nearest,
F64Sqrt,
F64Add,
F64Sub,
F64Mul,
F64Div,
F64Min,
F64Max,
F64Copysign,
I32WrapI64,
I32TruncSF32,
I32TruncUF32,
I32TruncSF64,
I32TruncUF64,
I64ExtendSI32,
I64ExtendUI32,
I64TruncSF32,
I64TruncUF32,
I64TruncSF64,
I64TruncUF64,
F32ConvertSI32,
F32ConvertUI32,
F32ConvertSI64,
F32ConvertUI64,
F32DemoteF64,
F64ConvertSI32,
F64ConvertUI32,
F64ConvertSI64,
F64ConvertUI64,
F64PromoteF32,
I32ReinterpretF32,
I64ReinterpretF64,
F32ReinterpretI32,
F64ReinterpretI64,
}
/// The internally-stored instruction type. This differs from `Instruction` in that the `BrTable`
/// target list is "unrolled" into seperate instructions in order to be able to A) improve cache
/// usage and B) allow this struct to be `Copy` and therefore allow `Instructions::clone` to be
/// a `memcpy`. It also means that `Instructions::drop` is trivial. The overall speedup on some
/// benchmarks is as high as 13%.
///
/// When returning instructions we convert to `Instruction`, whose `BrTable` variant internally
/// borrows the list of instructions and returns targets by reading it.
#[derive(Copy, Debug, Clone, PartialEq, Eq)]
pub(crate) enum InstructionInternal {
GetLocal(u32),
SetLocal(u32),
TeeLocal(u32),
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
BrTable { count: u32 },
BrTableTarget(Target),
Unreachable,
Return(DropKeep),
@ -318,7 +528,7 @@ pub enum Instruction {
#[derive(Debug, Clone)]
pub struct Instructions {
vec: Vec<Instruction>,
vec: Vec<InstructionInternal>,
}
impl Instructions {
@ -332,27 +542,27 @@ impl Instructions {
self.vec.len() as u32
}
pub fn push(&mut self, instruction: Instruction) {
pub(crate) fn push(&mut self, instruction: InstructionInternal) {
self.vec.push(instruction);
}
pub fn patch_relocation(&mut self, reloc: Reloc, dst_pc: u32) {
match reloc {
Reloc::Br { pc } => match self.vec[pc as usize] {
Instruction::Br(ref mut target)
| Instruction::BrIfEqz(ref mut target)
| Instruction::BrIfNez(ref mut target) => target.dst_pc = dst_pc,
InstructionInternal::Br(ref mut target)
| InstructionInternal::BrIfEqz(ref mut target)
| InstructionInternal::BrIfNez(ref mut target) => target.dst_pc = dst_pc,
_ => panic!("branch relocation points to a non-branch instruction"),
},
Reloc::BrTable { pc, idx } => match self.vec[pc as usize] {
Instruction::BrTable(ref mut targets) => targets[idx].dst_pc = dst_pc,
Reloc::BrTable { pc, idx } => match &mut self.vec[pc as usize + idx + 1] {
InstructionInternal::BrTableTarget(target) => target.dst_pc = dst_pc,
_ => panic!("brtable relocation points to not brtable instruction"),
}
},
}
}
pub fn iterate_from(&self, position: u32) -> InstructionIter {
InstructionIter{
InstructionIter {
instructions: &self.vec,
position,
}
@ -360,7 +570,7 @@ impl Instructions {
}
pub struct InstructionIter<'a> {
instructions: &'a [Instruction],
instructions: &'a [InstructionInternal],
position: u32,
}
@ -372,13 +582,212 @@ impl<'a> InstructionIter<'a> {
}
impl<'a> Iterator for InstructionIter<'a> {
type Item = &'a Instruction;
type Item = Instruction<'a>;
#[inline]
fn next(&mut self) -> Option<<Self as Iterator>::Item> {
self.instructions.get(self.position as usize).map(|instruction| {
self.position += 1;
instruction
})
fn next(&mut self) -> Option<Self::Item> {
let internal = if let Some(i) = self.instructions.get(self.position as usize) {
i
} else {
return None;
};
let out = match *internal {
InstructionInternal::GetLocal(x) => Instruction::GetLocal(x),
InstructionInternal::SetLocal(x) => Instruction::SetLocal(x),
InstructionInternal::TeeLocal(x) => Instruction::TeeLocal(x),
InstructionInternal::Br(x) => Instruction::Br(x),
InstructionInternal::BrIfEqz(x) => Instruction::BrIfEqz(x),
InstructionInternal::BrIfNez(x) => Instruction::BrIfNez(x),
InstructionInternal::BrTable { count } => {
let start = self.position as usize + 1;
self.position += count;
Instruction::BrTable(BrTargets::from_internal(
&self.instructions[start..start + count as usize],
))
}
InstructionInternal::BrTableTarget(_) => panic!("Executed BrTableTarget"),
InstructionInternal::Unreachable => Instruction::Unreachable,
InstructionInternal::Return(x) => Instruction::Return(x),
InstructionInternal::Call(x) => Instruction::Call(x),
InstructionInternal::CallIndirect(x) => Instruction::CallIndirect(x),
InstructionInternal::Drop => Instruction::Drop,
InstructionInternal::Select => Instruction::Select,
InstructionInternal::GetGlobal(x) => Instruction::GetGlobal(x),
InstructionInternal::SetGlobal(x) => Instruction::SetGlobal(x),
InstructionInternal::I32Load(x) => Instruction::I32Load(x),
InstructionInternal::I64Load(x) => Instruction::I64Load(x),
InstructionInternal::F32Load(x) => Instruction::F32Load(x),
InstructionInternal::F64Load(x) => Instruction::F64Load(x),
InstructionInternal::I32Load8S(x) => Instruction::I32Load8S(x),
InstructionInternal::I32Load8U(x) => Instruction::I32Load8U(x),
InstructionInternal::I32Load16S(x) => Instruction::I32Load16S(x),
InstructionInternal::I32Load16U(x) => Instruction::I32Load16U(x),
InstructionInternal::I64Load8S(x) => Instruction::I64Load8S(x),
InstructionInternal::I64Load8U(x) => Instruction::I64Load8U(x),
InstructionInternal::I64Load16S(x) => Instruction::I64Load16S(x),
InstructionInternal::I64Load16U(x) => Instruction::I64Load16U(x),
InstructionInternal::I64Load32S(x) => Instruction::I64Load32S(x),
InstructionInternal::I64Load32U(x) => Instruction::I64Load32U(x),
InstructionInternal::I32Store(x) => Instruction::I32Store(x),
InstructionInternal::I64Store(x) => Instruction::I64Store(x),
InstructionInternal::F32Store(x) => Instruction::F32Store(x),
InstructionInternal::F64Store(x) => Instruction::F64Store(x),
InstructionInternal::I32Store8(x) => Instruction::I32Store8(x),
InstructionInternal::I32Store16(x) => Instruction::I32Store16(x),
InstructionInternal::I64Store8(x) => Instruction::I64Store8(x),
InstructionInternal::I64Store16(x) => Instruction::I64Store16(x),
InstructionInternal::I64Store32(x) => Instruction::I64Store32(x),
InstructionInternal::CurrentMemory => Instruction::CurrentMemory,
InstructionInternal::GrowMemory => Instruction::GrowMemory,
InstructionInternal::I32Const(x) => Instruction::I32Const(x),
InstructionInternal::I64Const(x) => Instruction::I64Const(x),
InstructionInternal::F32Const(x) => Instruction::F32Const(x),
InstructionInternal::F64Const(x) => Instruction::F64Const(x),
InstructionInternal::I32Eqz => Instruction::I32Eqz,
InstructionInternal::I32Eq => Instruction::I32Eq,
InstructionInternal::I32Ne => Instruction::I32Ne,
InstructionInternal::I32LtS => Instruction::I32LtS,
InstructionInternal::I32LtU => Instruction::I32LtU,
InstructionInternal::I32GtS => Instruction::I32GtS,
InstructionInternal::I32GtU => Instruction::I32GtU,
InstructionInternal::I32LeS => Instruction::I32LeS,
InstructionInternal::I32LeU => Instruction::I32LeU,
InstructionInternal::I32GeS => Instruction::I32GeS,
InstructionInternal::I32GeU => Instruction::I32GeU,
InstructionInternal::I64Eqz => Instruction::I64Eqz,
InstructionInternal::I64Eq => Instruction::I64Eq,
InstructionInternal::I64Ne => Instruction::I64Ne,
InstructionInternal::I64LtS => Instruction::I64LtS,
InstructionInternal::I64LtU => Instruction::I64LtU,
InstructionInternal::I64GtS => Instruction::I64GtS,
InstructionInternal::I64GtU => Instruction::I64GtU,
InstructionInternal::I64LeS => Instruction::I64LeS,
InstructionInternal::I64LeU => Instruction::I64LeU,
InstructionInternal::I64GeS => Instruction::I64GeS,
InstructionInternal::I64GeU => Instruction::I64GeU,
InstructionInternal::F32Eq => Instruction::F32Eq,
InstructionInternal::F32Ne => Instruction::F32Ne,
InstructionInternal::F32Lt => Instruction::F32Lt,
InstructionInternal::F32Gt => Instruction::F32Gt,
InstructionInternal::F32Le => Instruction::F32Le,
InstructionInternal::F32Ge => Instruction::F32Ge,
InstructionInternal::F64Eq => Instruction::F64Eq,
InstructionInternal::F64Ne => Instruction::F64Ne,
InstructionInternal::F64Lt => Instruction::F64Lt,
InstructionInternal::F64Gt => Instruction::F64Gt,
InstructionInternal::F64Le => Instruction::F64Le,
InstructionInternal::F64Ge => Instruction::F64Ge,
InstructionInternal::I32Clz => Instruction::I32Clz,
InstructionInternal::I32Ctz => Instruction::I32Ctz,
InstructionInternal::I32Popcnt => Instruction::I32Popcnt,
InstructionInternal::I32Add => Instruction::I32Add,
InstructionInternal::I32Sub => Instruction::I32Sub,
InstructionInternal::I32Mul => Instruction::I32Mul,
InstructionInternal::I32DivS => Instruction::I32DivS,
InstructionInternal::I32DivU => Instruction::I32DivU,
InstructionInternal::I32RemS => Instruction::I32RemS,
InstructionInternal::I32RemU => Instruction::I32RemU,
InstructionInternal::I32And => Instruction::I32And,
InstructionInternal::I32Or => Instruction::I32Or,
InstructionInternal::I32Xor => Instruction::I32Xor,
InstructionInternal::I32Shl => Instruction::I32Shl,
InstructionInternal::I32ShrS => Instruction::I32ShrS,
InstructionInternal::I32ShrU => Instruction::I32ShrU,
InstructionInternal::I32Rotl => Instruction::I32Rotl,
InstructionInternal::I32Rotr => Instruction::I32Rotr,
InstructionInternal::I64Clz => Instruction::I64Clz,
InstructionInternal::I64Ctz => Instruction::I64Ctz,
InstructionInternal::I64Popcnt => Instruction::I64Popcnt,
InstructionInternal::I64Add => Instruction::I64Add,
InstructionInternal::I64Sub => Instruction::I64Sub,
InstructionInternal::I64Mul => Instruction::I64Mul,
InstructionInternal::I64DivS => Instruction::I64DivS,
InstructionInternal::I64DivU => Instruction::I64DivU,
InstructionInternal::I64RemS => Instruction::I64RemS,
InstructionInternal::I64RemU => Instruction::I64RemU,
InstructionInternal::I64And => Instruction::I64And,
InstructionInternal::I64Or => Instruction::I64Or,
InstructionInternal::I64Xor => Instruction::I64Xor,
InstructionInternal::I64Shl => Instruction::I64Shl,
InstructionInternal::I64ShrS => Instruction::I64ShrS,
InstructionInternal::I64ShrU => Instruction::I64ShrU,
InstructionInternal::I64Rotl => Instruction::I64Rotl,
InstructionInternal::I64Rotr => Instruction::I64Rotr,
InstructionInternal::F32Abs => Instruction::F32Abs,
InstructionInternal::F32Neg => Instruction::F32Neg,
InstructionInternal::F32Ceil => Instruction::F32Ceil,
InstructionInternal::F32Floor => Instruction::F32Floor,
InstructionInternal::F32Trunc => Instruction::F32Trunc,
InstructionInternal::F32Nearest => Instruction::F32Nearest,
InstructionInternal::F32Sqrt => Instruction::F32Sqrt,
InstructionInternal::F32Add => Instruction::F32Add,
InstructionInternal::F32Sub => Instruction::F32Sub,
InstructionInternal::F32Mul => Instruction::F32Mul,
InstructionInternal::F32Div => Instruction::F32Div,
InstructionInternal::F32Min => Instruction::F32Min,
InstructionInternal::F32Max => Instruction::F32Max,
InstructionInternal::F32Copysign => Instruction::F32Copysign,
InstructionInternal::F64Abs => Instruction::F64Abs,
InstructionInternal::F64Neg => Instruction::F64Neg,
InstructionInternal::F64Ceil => Instruction::F64Ceil,
InstructionInternal::F64Floor => Instruction::F64Floor,
InstructionInternal::F64Trunc => Instruction::F64Trunc,
InstructionInternal::F64Nearest => Instruction::F64Nearest,
InstructionInternal::F64Sqrt => Instruction::F64Sqrt,
InstructionInternal::F64Add => Instruction::F64Add,
InstructionInternal::F64Sub => Instruction::F64Sub,
InstructionInternal::F64Mul => Instruction::F64Mul,
InstructionInternal::F64Div => Instruction::F64Div,
InstructionInternal::F64Min => Instruction::F64Min,
InstructionInternal::F64Max => Instruction::F64Max,
InstructionInternal::F64Copysign => Instruction::F64Copysign,
InstructionInternal::I32WrapI64 => Instruction::I32WrapI64,
InstructionInternal::I32TruncSF32 => Instruction::I32TruncSF32,
InstructionInternal::I32TruncUF32 => Instruction::I32TruncUF32,
InstructionInternal::I32TruncSF64 => Instruction::I32TruncSF64,
InstructionInternal::I32TruncUF64 => Instruction::I32TruncUF64,
InstructionInternal::I64ExtendSI32 => Instruction::I64ExtendSI32,
InstructionInternal::I64ExtendUI32 => Instruction::I64ExtendUI32,
InstructionInternal::I64TruncSF32 => Instruction::I64TruncSF32,
InstructionInternal::I64TruncUF32 => Instruction::I64TruncUF32,
InstructionInternal::I64TruncSF64 => Instruction::I64TruncSF64,
InstructionInternal::I64TruncUF64 => Instruction::I64TruncUF64,
InstructionInternal::F32ConvertSI32 => Instruction::F32ConvertSI32,
InstructionInternal::F32ConvertUI32 => Instruction::F32ConvertUI32,
InstructionInternal::F32ConvertSI64 => Instruction::F32ConvertSI64,
InstructionInternal::F32ConvertUI64 => Instruction::F32ConvertUI64,
InstructionInternal::F32DemoteF64 => Instruction::F32DemoteF64,
InstructionInternal::F64ConvertSI32 => Instruction::F64ConvertSI32,
InstructionInternal::F64ConvertUI32 => Instruction::F64ConvertUI32,
InstructionInternal::F64ConvertSI64 => Instruction::F64ConvertSI64,
InstructionInternal::F64ConvertUI64 => Instruction::F64ConvertUI64,
InstructionInternal::F64PromoteF32 => Instruction::F64PromoteF32,
InstructionInternal::I32ReinterpretF32 => Instruction::I32ReinterpretF32,
InstructionInternal::I64ReinterpretF64 => Instruction::I64ReinterpretF64,
InstructionInternal::F32ReinterpretI32 => Instruction::F32ReinterpretI32,
InstructionInternal::F64ReinterpretI64 => Instruction::F64ReinterpretI64,
};
self.position += 1;
Some(out)
}
}

View File

@ -338,10 +338,15 @@ impl Interpreter {
instructions: &isa::Instructions,
) -> Result<RunResult, TrapKind> {
let mut iter = instructions.iterate_from(function_context.position);
loop {
let instruction = iter.next().expect("instruction");
match self.run_instruction(function_context, instruction)? {
loop {
let instruction = iter.next().expect(
"Ran out of instructions, this should be impossible \
since validation ensures that we either have an explicit \
return or an implicit block `end`.",
);
match self.run_instruction(function_context, &instruction)? {
InstructionOutcome::RunNextInstruction => {}
InstructionOutcome::Branch(target) => {
iter = instructions.iterate_from(target.dst_pc);
@ -370,10 +375,10 @@ impl Interpreter {
match instruction {
&isa::Instruction::Unreachable => self.run_unreachable(context),
&isa::Instruction::Br(ref target) => self.run_br(context, target.clone()),
&isa::Instruction::BrIfEqz(ref target) => self.run_br_eqz(target.clone()),
&isa::Instruction::BrIfNez(ref target) => self.run_br_nez(target.clone()),
&isa::Instruction::BrTable(ref targets) => self.run_br_table(targets),
&isa::Instruction::Br(target) => self.run_br(context, target.clone()),
&isa::Instruction::BrIfEqz(target) => self.run_br_eqz(target.clone()),
&isa::Instruction::BrIfNez(target) => self.run_br_nez(target.clone()),
&isa::Instruction::BrTable(targets) => self.run_br_table(targets),
&isa::Instruction::Return(drop_keep) => self.run_return(drop_keep),
&isa::Instruction::Call(index) => self.run_call(context, index),
@ -615,17 +620,11 @@ impl Interpreter {
}
}
fn run_br_table(&mut self, table: &[isa::Target]) -> Result<InstructionOutcome, TrapKind> {
fn run_br_table(&mut self, targets: isa::BrTargets) -> Result<InstructionOutcome, TrapKind> {
let index: u32 = self.value_stack.pop_as();
let dst = if (index as usize) < table.len() - 1 {
table[index as usize].clone()
} else {
table
.last()
.expect("Due to validation there should be at least one label")
.clone()
};
let dst = targets.get(index);
Ok(InstructionOutcome::Branch(dst))
}
@ -1261,8 +1260,8 @@ struct FunctionContext {
impl FunctionContext {
pub fn new(function: FuncRef) -> Self {
let module = match *function.as_internal() {
FuncInstanceInternal::Internal { ref module, .. } => module.upgrade().expect("module deallocated"),
let module = match function.as_internal() {
FuncInstanceInternal::Internal { module, .. } => module.upgrade().expect("module deallocated"),
FuncInstanceInternal::Host { .. } => panic!("Host functions can't be called as internally defined functions; Thus FunctionContext can be created only with internally defined functions; qed"),
};
let memory = module.memory_by_index(DEFAULT_MEMORY_INDEX);

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,10 @@
use super::{validate_module, ValidatedModule};
use isa;
use parity_wasm::builder::module;
use parity_wasm::elements::{
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
Instruction, Instructions, TableType, ValueType, BlockType, deserialize_buffer,
Module,
deserialize_buffer, BlockType, External, GlobalEntry, GlobalType, ImportEntry, InitExpr,
Instruction, Instructions, MemoryType, Module, TableType, ValueType,
};
use isa;
use wabt;
#[test]
@ -27,45 +26,34 @@ fn limits() {
for (min, max, is_valid) in test_cases {
// defined table
let m = module()
.table()
.with_min(min)
.with_max(max)
.build()
.build();
let m = module().table().with_min(min).with_max(max).build().build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max))
)
)
.build();
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(min, max)),
)).build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// defined memory
let m = module()
.memory()
.with_min(min)
.with_max(max)
.build()
.with_min(min)
.with_max(max)
.build()
.build();
assert_eq!(validate_module(m).is_ok(), is_valid);
// imported table
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max))
)
)
.build();
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(min, max)),
)).build();
assert_eq!(validate_module(m).is_ok(), is_valid);
}
}
@ -73,92 +61,63 @@ fn limits() {
#[test]
fn global_init_const() {
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(
vec![Instruction::I32Const(42), Instruction::End]
)
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
)).build();
assert!(validate_module(m).is_ok());
// init expr type differs from declared global type
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I64, true),
InitExpr::new(vec![Instruction::I32Const(42), Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
}
#[test]
fn global_init_global() {
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false))
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End])
)
)
.build();
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
)).with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
)).build();
assert!(validate_module(m).is_ok());
// get_global can reference only previously defined globals
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
// get_global can reference only const globals
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true))
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End])
)
)
.build();
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
)).with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
// get_global in init_expr can only refer to imported globals.
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End])
)
)
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, false),
InitExpr::new(vec![Instruction::I32Const(0), Instruction::End]),
)).with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::GetGlobal(0), Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
}
@ -166,35 +125,26 @@ fn global_init_global() {
fn global_init_misc() {
// without delimiting End opcode
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42)])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::I32Const(42)]),
)).build();
assert!(validate_module(m).is_err());
// empty init expr
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::End])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
// not an constant opcode used
let m = module()
.with_global(
GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::Unreachable, Instruction::End])
)
)
.build();
.with_global(GlobalEntry::new(
GlobalType::new(ValueType::I32, true),
InitExpr::new(vec![Instruction::Unreachable, Instruction::End]),
)).build();
assert!(validate_module(m).is_err());
}
@ -202,31 +152,25 @@ fn global_init_misc() {
fn module_limits_validity() {
// module cannot contain more than 1 memory atm.
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None))
)
)
.memory()
.with_min(10)
.build()
.with_import(ImportEntry::new(
"core".into(),
"memory".into(),
External::Memory(MemoryType::new(10, None)),
)).memory()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
// module cannot contain more than 1 table atm.
let m = module()
.with_import(
ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None))
)
)
.table()
.with_min(10)
.build()
.with_import(ImportEntry::new(
"core".into(),
"table".into(),
External::Table(TableType::new(10, None)),
)).table()
.with_min(10)
.build()
.build();
assert!(validate_module(m).is_err());
}
@ -236,19 +180,27 @@ fn funcs() {
// recursive function calls is legal.
let m = module()
.function()
.signature().return_type().i32().build()
.body().with_instructions(Instructions::new(vec![
Instruction::Call(1),
Instruction::End,
])).build()
.build()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(1),
Instruction::End,
])).build()
.build()
.function()
.signature().return_type().i32().build()
.body().with_instructions(Instructions::new(vec![
Instruction::Call(0),
Instruction::End,
])).build()
.build()
.signature()
.return_type()
.i32()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::Call(0),
Instruction::End,
])).build()
.build()
.build();
assert!(validate_module(m).is_ok());
}
@ -257,26 +209,20 @@ fn funcs() {
fn globals() {
// import immutable global is legal.
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false))
)
)
.build();
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, false)),
)).build();
assert!(validate_module(m).is_ok());
// import mutable global is invalid.
let m = module()
.with_import(
ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true))
)
)
.build();
.with_import(ImportEntry::new(
"env".into(),
"ext_global".into(),
External::Global(GlobalType::new(ValueType::I32, true)),
)).build();
assert!(validate_module(m).is_err());
}
@ -284,21 +230,23 @@ fn globals() {
fn if_else_with_return_type_validation() {
let m = module()
.function()
.signature().build()
.body().with_instructions(Instructions::new(vec![
Instruction::I32Const(1),
Instruction::If(BlockType::NoResult),
Instruction::I32Const(1),
Instruction::If(BlockType::Value(ValueType::I32)),
Instruction::I32Const(1),
Instruction::Else,
Instruction::I32Const(2),
Instruction::End,
Instruction::Drop,
Instruction::End,
Instruction::End,
])).build()
.build()
.signature()
.build()
.body()
.with_instructions(Instructions::new(vec![
Instruction::I32Const(1),
Instruction::If(BlockType::NoResult),
Instruction::I32Const(1),
Instruction::If(BlockType::Value(ValueType::I32)),
Instruction::I32Const(1),
Instruction::Else,
Instruction::I32Const(2),
Instruction::End,
Instruction::Drop,
Instruction::End,
Instruction::End,
])).build()
.build()
.build();
validate_module(m).unwrap();
}
@ -310,10 +258,8 @@ fn validate(wat: &str) -> ValidatedModule {
validated_module
}
fn compile(wat: &str) -> (Vec<isa::Instruction>, Vec<u32>) {
let validated_module = validate(wat);
let code = &validated_module.code_map[0];
fn compile(module: &ValidatedModule) -> (Vec<isa::Instruction>, Vec<u32>) {
let code = &module.code_map[0];
let mut instructions = Vec::new();
let mut pcs = Vec::new();
let mut iter = code.iterate_from(0);
@ -323,41 +269,56 @@ fn compile(wat: &str) -> (Vec<isa::Instruction>, Vec<u32>) {
instructions.push(instruction.clone());
pcs.push(pc);
} else {
break
break;
}
}
(instructions, pcs)
}
macro_rules! targets {
($($target:expr),*) => {
::isa::BrTargets::from_internal(
&[$($target,)*]
.iter()
.map(|&target| ::isa::InstructionInternal::BrTableTarget(target))
.collect::<Vec<_>>()[..]
)
};
}
#[test]
fn implicit_return_no_value() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
})
]
vec![isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
})]
)
}
#[test]
fn implicit_return_with_value() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (result i32)
i32.const 0
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -372,32 +333,36 @@ fn implicit_return_with_value() {
#[test]
fn implicit_return_param() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32)
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::None,
}),
]
vec![isa::Instruction::Return(isa::DropKeep {
drop: 1,
keep: isa::Keep::None,
}),]
)
}
#[test]
fn get_local() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -412,14 +377,17 @@ fn get_local() {
#[test]
fn explicit_return() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
return
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -438,7 +406,8 @@ fn explicit_return() {
#[test]
fn add_params() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32) (param i32) (result i32)
get_local 0
@ -446,7 +415,9 @@ fn add_params() {
i32.add
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -468,7 +439,8 @@ fn add_params() {
#[test]
fn drop_locals() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32)
(local i32)
@ -476,7 +448,9 @@ fn drop_locals() {
set_local 1
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -492,7 +466,8 @@ fn drop_locals() {
#[test]
fn if_without_else() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
i32.const 1
@ -503,7 +478,9 @@ fn if_without_else() {
i32.const 3
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
@ -517,7 +494,7 @@ fn if_without_else() {
}),
isa::Instruction::I32Const(2),
isa::Instruction::Return(isa::DropKeep {
drop: 1, // 1 param
drop: 1, // 1 param
keep: isa::Keep::Single, // 1 result
}),
isa::Instruction::I32Const(3),
@ -531,7 +508,8 @@ fn if_without_else() {
#[test]
fn if_else() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
(local i32)
@ -545,7 +523,9 @@ fn if_else() {
end
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
@ -578,7 +558,8 @@ fn if_else() {
#[test]
fn if_else_returns_result() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
@ -590,7 +571,9 @@ fn if_else_returns_result() {
drop
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
@ -622,7 +605,8 @@ fn if_else_returns_result() {
#[test]
fn if_else_branch_from_true_branch() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
@ -638,7 +622,9 @@ fn if_else_branch_from_true_branch() {
drop
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
@ -680,7 +666,8 @@ fn if_else_branch_from_true_branch() {
#[test]
fn if_else_branch_from_false_branch() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
i32.const 1
@ -696,7 +683,9 @@ fn if_else_branch_from_false_branch() {
drop
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
@ -738,7 +727,8 @@ fn if_else_branch_from_false_branch() {
#[test]
fn loop_() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
loop (result i32)
@ -749,7 +739,9 @@ fn loop_() {
drop
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
@ -773,28 +765,30 @@ fn loop_() {
#[test]
fn loop_empty() {
let (code, _) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
loop
end
)
)
"#);
"#,
);
let (code, _) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),
]
vec![isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
}),]
)
}
#[test]
fn brtable() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
block $1
@ -805,29 +799,29 @@ fn brtable() {
end
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: 0,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: 0,
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
isa::Target {
dst_pc: pcs[2],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
},
isa::Target {
dst_pc: pcs[2],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
},
].into_boxed_slice()
),
}
]),
isa::Instruction::Return(isa::DropKeep {
drop: 0,
keep: isa::Keep::None,
@ -838,7 +832,8 @@ fn brtable() {
#[test]
fn brtable_returns_result() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call")
block $1 (result i32)
@ -852,30 +847,31 @@ fn brtable_returns_result() {
drop
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
println!("{:?}", (&code, &pcs));
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::I32Const(1),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: pcs[3],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
},
isa::Instruction::BrTable(targets![
isa::Target {
dst_pc: pcs[3],
drop_keep: isa::DropKeep {
drop: 0,
keep: isa::Keep::Single,
},
isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
keep: isa::Keep::Single,
drop: 0,
},
},
isa::Target {
dst_pc: pcs[4],
drop_keep: isa::DropKeep {
keep: isa::Keep::Single,
drop: 0,
},
].into_boxed_slice()
),
}
]),
isa::Instruction::Unreachable,
isa::Instruction::Drop,
isa::Instruction::Return(isa::DropKeep {
@ -888,7 +884,8 @@ fn brtable_returns_result() {
#[test]
fn wabt_example() {
let (code, pcs) = compile(r#"
let module = validate(
r#"
(module
(func (export "call") (param i32) (result i32)
block $exit
@ -901,7 +898,9 @@ fn wabt_example() {
return
)
)
"#);
"#,
);
let (code, pcs) = compile(&module);
assert_eq!(
code,
vec![