Compare commits

...

32 Commits
master ... fuzz

Author SHA1 Message Date
Sergey Pepyakin 5f14102a59 Merge remote-tracking branch 'origin/master' into fuzz
# Conflicts:
#	benches/Cargo.toml
2018-06-22 13:00:53 +03:00
Sergey Pepyakin 978b9ff2ea Merge remote-tracking branch 'origin/flat-stack' into fuzz 2018-06-14 21:49:10 +03:00
Sergey Pepyakin 59ddf11432 Inline always instruction dispatch function. 2018-06-14 18:49:28 +03:00
Sergey Pepyakin 03f378a000 Cache memory index. 2018-06-14 18:48:25 +03:00
Sergey Pepyakin ea1e15bc3f Optimize a bit more. 2018-06-14 18:40:26 +03:00
Sergey Pepyakin ca4036fb16 Optimize value stack 2018-06-14 18:34:47 +03:00
Sergey Pepyakin d5e47a03ab Ignore traces. 2018-06-14 18:34:33 +03:00
Sergey Pepyakin 4802635c95 Another round of cleaning. 2018-06-14 17:06:52 +03:00
Sergey Pepyakin 0110ea2d57 Clean 2018-06-14 17:02:56 +03:00
Sergey Pepyakin 1e758afaa2 Clean 2018-06-14 16:58:27 +03:00
Sergey Pepyakin f45b45459c Calibrate the limits. 2018-06-14 16:38:14 +03:00
Sergey Pepyakin de27ef3745 Use Vec instead of VecDeque. 2018-06-14 16:35:55 +03:00
Sergey Pepyakin e9f201bde9 WIP 2018-06-14 16:06:45 +03:00
Sergey Pepyakin aba44ca5ed WIP 2018-06-13 22:28:28 +03:00
Sergey Pepyakin bd3d8fc762 Bunch of other tests. 2018-06-13 17:42:56 +03:00
Sergey Pepyakin a9bf01a60f Working 2018-06-13 16:15:45 +03:00
Sergey Pepyakin 5e20cc28f8 Tests 2018-06-13 12:05:20 +03:00
Sergey Pepyakin d0e13db6f2 WIP 2 2018-06-13 11:32:44 +03:00
Sergey Pepyakin 5653e2809f WIP 2018-06-12 22:13:37 +03:00
Sergey Pepyakin 1702372696 Define Instruction Set. 2018-06-12 16:09:31 +03:00
Sergey Pepyakin 52ed4bb589 Stack hash. 2018-04-18 10:46:47 +03:00
Sergey Pepyakin 959503b49d Update parity-wasm 2018-04-18 10:46:47 +03:00
Sergey Pepyakin 21e4d28949 Update honggfuzz to 0.5.9. 2018-04-18 10:46:47 +03:00
Sergey Pepyakin 3ace65501c Update parity-wasm. 2018-04-18 10:46:47 +03:00
Sergey Pepyakin 7b4bc1c9e8 Impl hfuzz 2018-04-18 10:46:47 +03:00
Sergey Pepyakin fa7564692e Add honggfuzz. 2018-04-18 10:46:47 +03:00
Sergey Pepyakin 84a16a42f9 Version of libfuzzer pinned 2018-04-18 10:46:47 +03:00
Sergey Pepyakin c626208a0a Oops. Revert to temp file creation. 2018-04-18 10:46:42 +03:00
Sergey Pepyakin 213bc160bf Refactor 2018-04-18 10:46:39 +03:00
Sergey Pepyakin 7100f6c31b Also stderr 2018-04-18 10:46:38 +03:00
Sergey Pepyakin 067c08302d Redirect output of spec to /dev/null 2018-04-18 10:46:38 +03:00
Sergey Pepyakin 6c40a5b0b6 Add fuzzing against spec interpreter. 2018-04-18 10:46:37 +03:00
21 changed files with 2978 additions and 996 deletions

View File

@ -11,7 +11,8 @@ keywords = ["wasm", "webassembly", "bytecode", "interpreter"]
exclude = [ "/res/*", "/tests/*", "/fuzz/*", "/benches/*" ]
[dependencies]
parity-wasm = "0.27"
# parity-wasm = "0.27"
parity-wasm = { git = "https://github.com/paritytech/parity-wasm.git", rev = "0a61083238d8d9d8d9f6451a5d0da17674b11c21" }
byteorder = "1.0"
memory_units = "0.3.0"
nan-preserving-float = "0.1.0"

2
benches/.gitignore vendored
View File

@ -1 +1,3 @@
/target
*.trace

View File

@ -7,3 +7,6 @@ authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
wasmi = { path = ".." }
assert_matches = "1.2"
wabt = "0.3"
[profile.bench]
debug = true

View File

@ -12,9 +12,11 @@ cargo-fuzz = true
wasmi = { path = ".." }
wabt = "0.2.0"
wasmparser = "0.14.1"
tempdir = "0.3.6"
[dependencies.libfuzzer-sys]
git = "https://github.com/rust-fuzz/libfuzzer-sys.git"
rev = "737524f7de1e85342b8b6cd1c01edc71018183ba"
# Prevent this from interfering with workspaces
[workspace]
@ -31,3 +33,7 @@ path = "fuzz_targets/load_wabt.rs"
[[bin]]
name = "load_wasmparser"
path = "fuzz_targets/load_wasmparser.rs"
[[bin]]
name = "load_spec"
path = "fuzz_targets/load_spec.rs"

View File

@ -0,0 +1,53 @@
#![no_main]
#[macro_use]
extern crate libfuzzer_sys;
extern crate wabt;
extern crate wasmi;
extern crate tempdir;
use std::fs::File;
use std::io::Write;
use std::process::{Command, Stdio};
fn run_spec(data: &[u8]) -> Result<(), ()> {
let temp_dir = tempdir::TempDir::new("spec").unwrap();
let mut seed_path = temp_dir.path().to_path_buf();
seed_path.push("test.wasm");
{
let mut seedfile =
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
seedfile.write_all(data).expect(
"write fuzzer input to temporary file",
);
seedfile.flush().expect(
"flush fuzzer input to temporary file before starting wasm-opt",
);
}
let exit_status = Command::new("wasm")
.arg("-d")
.arg(&seed_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.status()
.expect("failed to execute `wasm`");
if exit_status.success() {
Ok(())
} else {
Err(())
}
}
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
Ok(())
}
fuzz_target!(|data: &[u8]| {
let wasmi_result = run_wasmi(data);
let wasm_result = run_spec(data);
assert_eq!(wasmi_result.is_ok(), wasm_result.is_ok());
});

2
hfuzz/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
hfuzz_workspace/
hfuzz_target/

10
hfuzz/Cargo.toml Normal file
View File

@ -0,0 +1,10 @@
[package]
name = "hfuzz"
version = "0.1.0"
authors = ["Sergey Pepyakin <s.pepyakin@gmail.com>"]
[dependencies]
honggfuzz = "=0.5.9" # Strict equal since hfuzz requires dep and cmd versions to match.
wasmi = { path = ".." }
tempdir = "0.3.6"
wabt = "0.2.0"

70
hfuzz/src/main.rs Normal file
View File

@ -0,0 +1,70 @@
#[macro_use] extern crate honggfuzz;
extern crate wabt;
extern crate wasmi;
extern crate tempdir;
use std::fs::File;
use std::io::Write;
use std::process::{Command, Stdio};
fn dump_all_into_buf(src: &[u8], buf: &mut [u8; 64]) {
let common_len = usize::min(src.len(), buf.len());
buf[0..common_len].copy_from_slice(&src[0..common_len]);
}
fn run_spec(data: &[u8], stdout_msg_buf: &mut [u8; 64], stderr_msg_buf: &mut [u8; 64]) -> Result<(), ()> {
let temp_dir = tempdir::TempDir::new("spec").unwrap();
let mut seed_path = temp_dir.path().to_path_buf();
seed_path.push("test.wasm");
{
let mut seedfile =
File::create(&seed_path).expect("open temporary file for writing to store fuzzer input");
seedfile.write_all(data).expect(
"write fuzzer input to temporary file",
);
seedfile.flush().expect(
"flush fuzzer input to temporary file before starting wasm-opt",
);
}
let output = Command::new("wasm")
.arg("-d")
.arg(&seed_path)
.stdout(Stdio::null())
.stderr(Stdio::null())
.output()
.expect("failed to execute `wasm`");
if output.status.success() {
Ok(())
} else {
dump_all_into_buf(&output.stdout, stdout_msg_buf);
dump_all_into_buf(&output.stderr, stderr_msg_buf);
Err(())
}
}
fn run_wasmi(data: &[u8]) -> Result<(), ()> {
let _ = wasmi::Module::from_buffer(data).map_err(|_| ())?;
Ok(())
}
fn main() {
loop {
fuzz!(|data: &[u8]| {
// Keep messages on stack. This should lead to a different stack hashes for
// different error messages.
let mut stdout_msg_buf: [u8; 64] = [0; 64];
let mut stderr_msg_buf: [u8; 64] = [0; 64];
let wasmi_result = run_wasmi(data);
let wasm_result = run_spec(data, &mut stdout_msg_buf, &mut stderr_msg_buf);
if wasmi_result.is_ok() != wasm_result.is_ok() {
panic!("stdout: {:?}, stderr: {:?}", &stdout_msg_buf[..], &stderr_msg_buf as &[u8]);
}
});
}
}

5
hfuzz/test.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
export HFUZZ_RUN_ARGS="--max_file_size 2048"
rustup run nightly cargo hfuzz run hfuzz

View File

@ -1,4 +1,3 @@
use parity_wasm::elements::BlockType;
pub mod stack;
@ -7,38 +6,4 @@ pub const DEFAULT_MEMORY_INDEX: u32 = 0;
/// Index of default table.
pub const DEFAULT_TABLE_INDEX: u32 = 0;
/// Control stack frame.
#[derive(Debug, Clone)]
pub struct BlockFrame {
/// Frame type.
pub frame_type: BlockFrameType,
/// A signature, which is a block signature type indicating the number and types of result values of the region.
pub block_type: BlockType,
/// A label for reference to block instruction.
pub begin_position: usize,
/// A label for reference from branch instructions.
pub branch_position: usize,
/// A label for reference from end instructions.
pub end_position: usize,
/// A limit integer value, which is an index into the value stack indicating where to reset it to on a branch to that label.
pub value_stack_len: usize,
/// Boolean which signals whether value stack became polymorphic. Value stack starts in non-polymorphic state and
/// becomes polymorphic only after an instruction that never passes control further is executed,
/// i.e. `unreachable`, `br` (but not `br_if`!), etc.
pub polymorphic_stack: bool,
}
/// Type of block frame.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum BlockFrameType {
/// Function frame.
Function,
/// Usual block frame.
Block,
/// Loop frame (branching to the beginning of block).
Loop,
/// True-subblock of if expression.
IfTrue,
/// False-subblock of if expression.
IfFalse,
}
// TODO: Move BlockFrame under validation.

View File

@ -1,5 +1,4 @@
use std::collections::VecDeque;
use std::error;
use std::fmt;
@ -22,7 +21,7 @@ impl error::Error for Error {
#[derive(Debug)]
pub struct StackWithLimit<T> where T: Clone {
/// Stack values.
values: VecDeque<T>,
values: Vec<T>,
/// Stack limit (maximal stack len).
limit: usize,
}
@ -30,7 +29,7 @@ pub struct StackWithLimit<T> where T: Clone {
impl<T> StackWithLimit<T> where T: Clone {
pub fn with_limit(limit: usize) -> Self {
StackWithLimit {
values: VecDeque::new(),
values: Vec::new(),
limit: limit
}
}
@ -43,19 +42,17 @@ impl<T> StackWithLimit<T> where T: Clone {
self.values.len()
}
pub fn limit(&self) -> usize {
self.limit
}
pub fn top(&self) -> Result<&T, Error> {
let len = self.values.len();
self.values
.back()
.get(len - 1)
.ok_or_else(|| Error("non-empty stack expected".into()))
}
pub fn top_mut(&mut self) -> Result<&mut T, Error> {
let len = self.values.len();
self.values
.back_mut()
.get_mut(len - 1)
.ok_or_else(|| Error("non-empty stack expected".into()))
}
@ -72,13 +69,13 @@ impl<T> StackWithLimit<T> where T: Clone {
return Err(Error(format!("exceeded stack limit {}", self.limit)));
}
self.values.push_back(value);
self.values.push(value);
Ok(())
}
pub fn pop(&mut self) -> Result<T, Error> {
self.values
.pop_back()
.pop()
.ok_or_else(|| Error("non-empty stack expected".into()))
}

View File

@ -1,12 +1,12 @@
use std::rc::{Rc, Weak};
use std::fmt;
use std::collections::HashMap;
use parity_wasm::elements::{Local, Opcodes};
use parity_wasm::elements::Local;
use {Trap, TrapKind, Signature};
use host::Externals;
use runner::{check_function_args, Interpreter};
use value::RuntimeValue;
use module::ModuleInstance;
use isa;
/// Reference to a function (See [`FuncInstance`] for details).
///
@ -158,6 +158,5 @@ impl FuncInstance {
#[derive(Clone, Debug)]
pub struct FuncBody {
pub locals: Vec<Local>,
pub opcodes: Opcodes,
pub labels: HashMap<usize, usize>,
pub code: isa::Instructions,
}

255
src/isa.rs Normal file
View File

@ -0,0 +1,255 @@
//! An instruction set used by wasmi.
//!
//! The instruction set is mostly derived from Wasm. However,
//! there is a substantial difference.
//!
//! # Structured Stack Machine vs Traditional One
//!
//! Wasm is a structured stack machine. Wasm encodes control flow in structures
//! similar to that commonly found in a programming languages
//! such as if, while. That contrasts to a traditional stack machine which
//! encodes all control flow with goto-like instructions.
//!
//! Structured stack machine code aligns well with goals of Wasm,
//! namely providing fast validation of Wasm code and compilation to native code.
//!
//! Unfortunately, the downside of structured stack machine code is
//! that it is less convenient to interpret. For example, let's look at
//! the following example in hypothetical structured stack machine:
//!
//! ```plain
//! loop
//! ...
//! if_true_jump_to_end
//! ...
//! end
//! ```
//!
//! To execute `if_true_jump_to_end` , the interpreter needs to skip all instructions
//! until it reaches the *matching* `end`. That's quite inefficient compared
//! to a plain goto to the specific position.
//!
//! # Differences from Wasm
//!
//! - There is no `nop` instruction.
//! - All control flow strucutres are flattened to plain gotos.
//! - Implicit returns via reaching function scope `End` are replaced with an explicit `return` instruction.
//! - Locals live on the value stack now.
//! - Load/store instructions doesn't take `align` parameter.
//! - *.const store value in straight encoding.
//! - Reserved immediates are ignored for `call_indirect`, `current_memory`, `grow_memory`.
//!
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Target {
pub dst_pc: u32,
pub drop: u32,
pub keep: u8,
}
#[allow(unused)] // TODO: Remove
#[derive(Debug, Clone, PartialEq)]
pub enum Instruction {
/// Push a local variable or an argument from the specified depth.
GetLocal(u32),
/// Pop a value and put it in at the specified depth.
SetLocal(u32),
/// Copy a value to the specified depth.
TeeLocal(u32),
/// Similar to the Wasm ones, but instead of a label depth
/// they specify direct PC.
Br(Target),
BrIfEqz(Target),
BrIfNez(Target),
/// Last one is the default.
///
/// Can be less than zero.
BrTable(Box<[Target]>),
Unreachable,
Return {
drop: u32,
keep: u8,
},
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,
}
#[derive(Debug, Clone)]
pub struct Instructions {
pub code: Vec<Instruction>,
}

View File

@ -109,7 +109,6 @@ extern crate nan_preserving_float;
use std::fmt;
use std::error;
use std::collections::HashMap;
/// Error type which can thrown by wasm code or by host environment.
///
@ -356,6 +355,7 @@ mod imports;
mod global;
mod func;
mod types;
mod isa;
#[cfg(test)]
mod tests;
@ -378,7 +378,7 @@ pub mod memory_units {
/// Deserialized module prepared for instantiation.
pub struct Module {
labels: HashMap<usize, HashMap<usize, usize>>,
code_map: Vec<isa::Instructions>,
module: parity_wasm::elements::Module,
}
@ -418,12 +418,12 @@ impl Module {
pub fn from_parity_wasm_module(module: parity_wasm::elements::Module) -> Result<Module, Error> {
use validation::{validate_module, ValidatedModule};
let ValidatedModule {
labels,
code_map,
module,
} = validate_module(module)?;
Ok(Module {
labels,
code_map,
module,
})
}
@ -524,7 +524,7 @@ impl Module {
&self.module
}
pub(crate) fn labels(&self) -> &HashMap<usize, HashMap<usize, usize>> {
&self.labels
pub(crate) fn code(&self) -> &Vec<isa::Instructions> {
&self.code_map
}
}

View File

@ -291,7 +291,7 @@ impl ModuleInstance {
}
}
let labels = loaded_module.labels();
let code = loaded_module.code();
{
let funcs = module.function_section().map(|fs| fs.entries()).unwrap_or(
&[],
@ -308,13 +308,12 @@ impl ModuleInstance {
let signature = instance.signature_by_index(ty.type_ref()).expect(
"Due to validation type should exists",
);
let labels = labels.get(&index).expect(
let code = code.get(index).expect(
"At func validation time labels are collected; Collected labels are added by index; qed",
).clone();
let func_body = FuncBody {
locals: body.locals().to_vec(),
opcodes: body.code().clone(),
labels: labels,
code: code,
};
let func_instance =
FuncInstance::alloc_internal(Rc::downgrade(&instance.0), signature, func_body);

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
use std::error;
use std::fmt;
use std::collections::{HashMap, HashSet};
use std::collections::HashSet;
use parity_wasm::elements::{
BlockType, External, GlobalEntry, GlobalType, Internal, MemoryType, Module, Opcode,
ResizableLimits, TableType, ValueType, InitExpr, Type
@ -9,6 +9,7 @@ use common::stack;
use self::context::ModuleContextBuilder;
use self::func::Validator;
use memory_units::Pages;
use isa;
mod context;
mod func;
@ -40,7 +41,7 @@ impl From<stack::Error> for Error {
#[derive(Clone)]
pub struct ValidatedModule {
pub labels: HashMap<usize, HashMap<usize, usize>>,
pub code_map: Vec<isa::Instructions>,
pub module: Module,
}
@ -167,7 +168,7 @@ pub fn deny_floating_point(module: &Module) -> Result<(), Error> {
pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
let mut context_builder = ModuleContextBuilder::new();
let mut imported_globals = Vec::new();
let mut labels = HashMap::new();
let mut code_map = Vec::new();
// Copy types from module as is.
context_builder.set_types(
@ -257,12 +258,12 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
index
)),
)?;
let func_labels = Validator::validate_function(&context, function, function_body)
let code = Validator::validate_function(&context, function, function_body)
.map_err(|e| {
let Error(ref msg) = e;
Error(format!("Function #{} validation error: {}", index, msg))
})?;
labels.insert(index, func_labels);
code_map.push(code);
}
}
@ -374,7 +375,7 @@ pub fn validate_module(module: Module) -> Result<ValidatedModule, Error> {
Ok(ValidatedModule {
module,
labels
code_map,
})
}

View File

@ -1,9 +1,12 @@
use super::validate_module;
use super::{validate_module, ValidatedModule};
use parity_wasm::builder::module;
use parity_wasm::elements::{
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
Opcode, Opcodes, TableType, ValueType, BlockType
External, GlobalEntry, GlobalType, ImportEntry, InitExpr, MemoryType,
Opcode, Opcodes, TableType, ValueType, BlockType, deserialize_buffer,
Module,
};
use isa;
use wabt;
#[test]
fn empty_is_valid() {
@ -299,3 +302,584 @@ fn if_else_with_return_type_validation() {
.build();
validate_module(m).unwrap();
}
fn validate(wat: &str) -> ValidatedModule {
let wasm = wabt::wat2wasm(wat).unwrap();
let module = deserialize_buffer::<Module>(&wasm).unwrap();
let validated_module = validate_module(module).unwrap();
validated_module
}
fn compile(wat: &str) -> Vec<isa::Instruction> {
let validated_module = validate(wat);
let code = &validated_module.code_map[0];
code.code.clone()
}
#[test]
fn implicit_return_no_value() {
let code = compile(r#"
(module
(func (export "call")
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 0,
keep: 0,
}
]
)
}
#[test]
fn implicit_return_with_value() {
let code = compile(r#"
(module
(func (export "call") (result i32)
i32.const 0
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::Return {
drop: 0,
keep: 1,
}
]
)
}
#[test]
fn implicit_return_param() {
let code = compile(r#"
(module
(func (export "call") (param i32)
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 1,
keep: 0,
}
]
)
}
#[test]
fn get_local() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 1,
}
]
)
}
#[test]
fn explicit_return() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
get_local 0
return
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
isa::Instruction::Return {
drop: 1,
keep: 1,
}
]
)
}
#[test]
fn add_params() {
let code = compile(r#"
(module
(func (export "call") (param i32) (param i32) (result i32)
get_local 0
get_local 1
i32.add
)
)
"#);
assert_eq!(
code,
vec![
// This is tricky. Locals are now loaded from the stack. The load
// happens from address relative of the current stack pointer. The first load
// takes the value below the previous one (i.e the second argument) and then, it increments
// the stack pointer. And then the same thing hapens with the value below the previous one
// (which happens to be the value loaded by the first get_local).
isa::Instruction::GetLocal(2),
isa::Instruction::GetLocal(2),
isa::Instruction::I32Add,
isa::Instruction::Return {
drop: 2,
keep: 1,
}
]
)
}
#[test]
fn drop_locals() {
let code = compile(r#"
(module
(func (export "call") (param i32)
(local i32)
get_local 0
set_local 1
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Return {
drop: 2,
keep: 0,
}
]
)
}
#[test]
fn if_without_else() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
i32.const 1
if
i32.const 2
return
end
i32.const 3
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Return {
drop: 1, // 1 param
keep: 1, // 1 result
},
isa::Instruction::I32Const(3),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
]
)
}
#[test]
fn if_else() {
let code = compile(r#"
(module
(func (export "call")
(local i32)
i32.const 1
if
i32.const 2
set_local 0
else
i32.const 3
set_local 0
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 5,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::SetLocal(1),
isa::Instruction::Br(isa::Target {
dst_pc: 7,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::SetLocal(1),
isa::Instruction::Return {
drop: 1,
keep: 0,
},
]
)
}
#[test]
fn if_else_returns_result() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 2
else
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: 5,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn if_else_branch_from_true_branch() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
i32.const 1
br_if 0
drop
i32.const 2
else
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 8,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 9,
drop: 0,
keep: 1,
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(2),
isa::Instruction::Br(isa::Target {
dst_pc: 9,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn if_else_branch_from_false_branch() {
let code = compile(r#"
(module
(func (export "call")
i32.const 1
if (result i32)
i32.const 1
else
i32.const 2
i32.const 1
br_if 0
drop
i32.const 3
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfEqz(isa::Target {
dst_pc: 4,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::Br(isa::Target {
dst_pc: 9,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 9,
drop: 0,
keep: 1,
}),
isa::Instruction::Drop,
isa::Instruction::I32Const(3),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn loop_() {
let code = compile(r#"
(module
(func (export "call")
loop (result i32)
i32.const 1
br_if 0
i32.const 2
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 0,
drop: 0,
keep: 0,
}),
isa::Instruction::I32Const(2),
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn loop_empty() {
let code = compile(r#"
(module
(func (export "call")
loop
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn brtable() {
let code = compile(r#"
(module
(func (export "call")
block $1
loop $2
i32.const 0
br_table $2 $1
end
end
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: 0,
keep: 0,
drop: 0,
},
isa::Target {
dst_pc: 2,
keep: 0,
drop: 0,
},
].into_boxed_slice()
),
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn brtable_returns_result() {
let code = compile(r#"
(module
(func (export "call")
block $1 (result i32)
block $2 (result i32)
i32.const 0
i32.const 1
br_table $2 $1
end
unreachable
end
drop
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::I32Const(0),
isa::Instruction::I32Const(1),
isa::Instruction::BrTable(
vec![
isa::Target {
dst_pc: 3,
keep: 1,
drop: 0,
},
isa::Target {
dst_pc: 4,
keep: 1,
drop: 0,
},
].into_boxed_slice()
),
isa::Instruction::Unreachable,
isa::Instruction::Drop,
isa::Instruction::Return {
drop: 0,
keep: 0,
},
]
)
}
#[test]
fn wabt_example() {
let code = compile(r#"
(module
(func (export "call") (param i32) (result i32)
block $exit
get_local 0
br_if $exit
i32.const 1
return
end
i32.const 2
return
)
)
"#);
assert_eq!(
code,
vec![
isa::Instruction::GetLocal(1),
isa::Instruction::BrIfNez(isa::Target {
dst_pc: 4,
keep: 0,
drop: 0,
}),
isa::Instruction::I32Const(1),
isa::Instruction::Return {
drop: 1, // 1 parameter
keep: 1, // return value
},
isa::Instruction::I32Const(2),
isa::Instruction::Return {
drop: 1,
keep: 1,
},
isa::Instruction::Return {
drop: 1,
keep: 1,
},
]
)
}

View File

@ -20,6 +20,26 @@ impl<'a> Locals<'a> {
}
}
/// Returns parameter count.
pub fn param_count(&self) -> u32 {
self.params.len() as u32
}
/// Returns total count of all declared locals and paramaterers.
///
/// Returns `Err` if count overflows 32-bit value.
pub fn count(&self) -> Result<u32, Error> {
let mut acc = self.param_count();
for locals_group in self.local_groups {
acc = acc
.checked_add(locals_group.count())
.ok_or_else(||
Error(String::from("Locals range no in 32-bit range"))
)?;
}
Ok(acc)
}
/// Returns the type of a local variable (either a declared local or a param).
///
/// Returns `Err` in the case of overflow or when idx falls out of range.
@ -29,7 +49,7 @@ impl<'a> Locals<'a> {
}
// If an index doesn't point to a param, then we have to look into local declarations.
let mut start_idx = self.params.len() as u32;
let mut start_idx = self.param_count();
for locals_group in self.local_groups {
let end_idx = start_idx
.checked_add(locals_group.count())

View File

@ -360,6 +360,8 @@ fn try_spec(name: &str) -> Result<(), Error> {
}};
}
println!("Running spec cmd {}: {:?}", line, kind);
match kind {
CommandKind::Module { name, module, .. } => {
load_module(&module.into_vec()?, &name, &mut spec_driver)