Skip to content

Commit

Permalink
Merge pull request #24 from alexcrichton/spec-tests
Browse files Browse the repository at this point in the history
Add a suite of spec tests
  • Loading branch information
alexcrichton authored Jan 24, 2019
2 parents ee131e8 + 34b365d commit e8f257f
Show file tree
Hide file tree
Showing 10 changed files with 286 additions and 11 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[submodule "walrus-tests/tests/spec-tests"]
path = walrus-tests/tests/spec-tests
url = https://github.com/WebAssembly/testsuite
34 changes: 34 additions & 0 deletions src/ir/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -506,6 +506,12 @@ pub enum UnaryOp {
I64ReinterpretF64,
F32ReinterpretI32,
F64ReinterpretI64,

I32Extend8S,
I32Extend16S,
I64Extend8S,
I64Extend16S,
I64Extend32S,
}

/// The different kinds of load instructions that are part of a `Load` IR node
Expand All @@ -528,6 +534,20 @@ pub enum LoadKind {
I64_32 { sign_extend: bool },
}

impl LoadKind {
/// Returns the number of bytes loaded
pub fn width(&self) -> u32 {
use LoadKind::*;
match self {
I32_8 { .. } | I64_8 { .. } => 1,
I32_16 { .. } | I64_16 { .. } => 2,
I32 | F32 | I64_32 { .. } => 4,
I64 | F64 => 8,
V128 => 16,
}
}
}

/// The different kinds of store instructions that are part of a `Store` IR node
#[derive(Debug, Copy, Clone)]
#[allow(missing_docs)]
Expand All @@ -544,6 +564,20 @@ pub enum StoreKind {
I64_32,
}

impl StoreKind {
/// Returns the number of bytes stored
pub fn width(&self) -> u32 {
use StoreKind::*;
match self {
I32_8 | I64_8 => 1,
I32_16 | I64_16 => 2,
I32 | F32 | I64_32 => 4,
I64 | F64 => 8,
V128 => 16,
}
}
}

/// Arguments to memory operations, containing a constant offset from a dynamic
/// address as well as a predicted alignment.
#[derive(Debug, Copy, Clone)]
Expand Down
11 changes: 5 additions & 6 deletions src/module/exports.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,20 +3,19 @@
use super::globals::GlobalId;
use super::memories::MemoryId;
use super::tables::TableId;
use crate::arena_set::ArenaSet;
use crate::emit::{Emit, EmitContext, IdsToIndices};
use crate::error::Result;
use crate::module::functions::FunctionId;
use crate::module::Module;
use crate::parse::IndicesToIds;
use id_arena::Id;
use id_arena::{Arena, Id};
use parity_wasm::elements;

/// The id of an export.
pub type ExportId = Id<Export>;

/// A named item exported from the wasm.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Clone, Debug)]
pub struct Export {
id: ExportId,
/// The name of this export.
Expand All @@ -38,7 +37,7 @@ impl Export {
}

/// An exported item.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
#[derive(Copy, Clone, Debug)]
pub enum ExportItem {
/// An exported function.
Function(FunctionId),
Expand Down Expand Up @@ -77,7 +76,7 @@ impl ExportItem {
#[derive(Debug, Default)]
pub struct ModuleExports {
/// The arena containing this module's exports.
arena: ArenaSet<Export>,
arena: Arena<Export>,
}

impl ModuleExports {
Expand Down Expand Up @@ -112,7 +111,7 @@ impl Module {
elements::Internal::Global(t) => ExportItem::Global(ids.get_global(t)?),
};
let id = self.exports.arena.next_id();
self.exports.arena.insert(Export {
self.exports.arena.alloc(Export {
id,
name: exp.field().to_string(),
item,
Expand Down
6 changes: 6 additions & 0 deletions src/module/functions/local_function/emit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,12 @@ impl Emit<'_> {
I64ReinterpretF64 => elements::Instruction::I64ReinterpretF64,
F32ReinterpretI32 => elements::Instruction::F32ReinterpretI32,
F64ReinterpretI64 => elements::Instruction::F64ReinterpretI64,

I32Extend8S => elements::Instruction::I32Extend8S,
I32Extend16S => elements::Instruction::I32Extend16S,
I64Extend8S => elements::Instruction::I64Extend8S,
I64Extend16S => elements::Instruction::I64Extend16S,
I64Extend32S => elements::Instruction::I64Extend32S,
})
}

Expand Down
8 changes: 7 additions & 1 deletion src/module/functions/local_function/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,7 @@ fn validate_instruction<'a>(

let memarg = |align, offset| -> Result<MemArg> {
if align >= 32 {
failure::bail!("invalid alignment")
failure::bail!("invalid alignment");
}
Ok(MemArg {
align: 1 << (align as i32),
Expand Down Expand Up @@ -675,6 +675,12 @@ fn validate_instruction<'a>(
Instruction::F32ReinterpretI32 => one_op(ctx, I32, F32, UnaryOp::F32ReinterpretI32)?,
Instruction::F64ReinterpretI64 => one_op(ctx, I64, F64, UnaryOp::F64ReinterpretI64)?,

Instruction::I32Extend8S => one_op(ctx, I32, I32, UnaryOp::I32Extend8S)?,
Instruction::I32Extend16S => one_op(ctx, I32, I32, UnaryOp::I32Extend16S)?,
Instruction::I64Extend8S => one_op(ctx, I64, I64, UnaryOp::I64Extend8S)?,
Instruction::I64Extend16S => one_op(ctx, I64, I64, UnaryOp::I64Extend16S)?,
Instruction::I64Extend32S => one_op(ctx, I64, I64, UnaryOp::I64Extend32S)?,

Instruction::Drop => {
let (_, expr) = ctx.pop_operand()?;
let expr = ctx.func.alloc(Drop { expr });
Expand Down
107 changes: 104 additions & 3 deletions src/passes/validate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,27 @@

use crate::const_value::Const;
use crate::error::Result;
use crate::ir::Value;
use crate::ir::*;
use crate::module::functions::{Function, FunctionKind, LocalFunction};
use crate::module::globals::{Global, GlobalKind};
use crate::module::memories::Memory;
use crate::module::tables::{Table, TableKind};
use crate::module::Module;
use crate::ty::ValType;
use failure::{bail, ResultExt};
use std::collections::HashSet;

/// Validate a wasm module, returning an error if it fails to validate.
pub fn run(module: &Module) -> Result<()> {
// TODO: should a config option be added to lift these restrictions? They're
// only here for the spec tests...
if module.tables.iter().count() > 1 {
bail!("multiple tables not allowed in the wasm spec yet");
}
if module.memories.iter().count() > 1 {
bail!("multiple memories not allowed in the wasm spec yet");
}

for memory in module.memories.iter() {
validate_memory(memory)?;
}
Expand All @@ -24,11 +35,48 @@ pub fn run(module: &Module) -> Result<()> {
for global in module.globals.iter() {
validate_global(module, global)?;
}
Ok(())
validate_exports(module)?;

// Validate the start function, if present, has the correct signature
if let Some(start) = module.start {
let ty = module.funcs.get(start).ty();
let ty = module.types.get(ty);
if ty.results().len() > 0 || ty.params().len() > 0 {
bail!("start function must take no arguments and return nothing");
}
}

// Validate each function in the module, collecting errors and returning
// them all at once if there are any.
let mut errs = Vec::new();
for function in module.funcs.iter() {
let local = match &function.kind {
FunctionKind::Local(local) => local,
_ => continue,
};
let mut cx = Validate {
errs: &mut errs,
function,
local,
};
local.entry_block().visit(&mut cx);
}
if errs.len() == 0 {
return Ok(());
}

let mut msg = format!("errors validating module:\n");
for error in errs {
msg.push_str(&format!(" * {}\n", error));
for cause in error.iter_causes() {
msg.push_str(&format!(" * {}\n", cause));
}
}
bail!("{}", msg)
}

fn validate_memory(m: &Memory) -> Result<()> {
validate_limits(m.initial, m.maximum, u16::max_value().into())
validate_limits(m.initial, m.maximum, u32::from(u16::max_value()) + 1)
.context("when validating a memory")?;
Ok(())
}
Expand Down Expand Up @@ -61,6 +109,18 @@ fn validate_limits(initial: u32, maximum: Option<u32>, k: u32) -> Result<()> {
}
}

fn validate_exports(module: &Module) -> Result<()> {
// All exported names must be unique, so if there's any duplicate-named
// exports then we generate an error
let mut exports = HashSet::new();
for export in module.exports.iter() {
if !exports.insert(&export.name) {
bail!("duplicate export of `{}`", export.name)
}
}
Ok(())
}

fn validate_global(module: &Module, global: &Global) -> Result<()> {
match global.kind {
GlobalKind::Import(_) => return Ok(()),
Expand Down Expand Up @@ -94,3 +154,44 @@ fn validate_value(value: Value, ty: ValType) -> Result<()> {
}
Ok(())
}

struct Validate<'a> {
errs: &'a mut Vec<failure::Error>,
function: &'a Function,
local: &'a LocalFunction,
}

impl Validate<'_> {
fn memarg(&mut self, arg: &MemArg, width: u32) {
// The alignment of a memory operation must be less than or equal to the
// width of the memory operation, currently wasm doesn't allow
// over-aligned memory ops.
if arg.align > width {
self.err("memory operation with alignment greater than natural size");
}
}

fn err(&mut self, msg: &str) {
let mut err = failure::format_err!("{}", msg);
if let Some(name) = &self.function.name {
err = err.context(format!("in function {}", name)).into();
}
self.errs.push(err);
}
}

impl<'a> Visitor<'a> for Validate<'a> {
fn local_function(&self) -> &'a LocalFunction {
self.local
}

fn visit_load(&mut self, e: &Load) {
self.memarg(&e.arg, e.kind.width());
e.visit(self);
}

fn visit_store(&mut self, e: &Store) {
self.memarg(&e.arg, e.kind.width());
e.visit(self);
}
}
3 changes: 3 additions & 0 deletions walrus-tests/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ failure = "0.1.2"
parity-wasm = "0.35.7"
walrus = { path = ".." }
walrus-tests-utils = { path = "../walrus-tests-utils" }
tempfile = "3"
serde_json = { version = "1", features = ['preserve_order'] }
serde = { version = "1", features = ['derive'] }

[lib]
doctest = false
Expand Down
5 changes: 4 additions & 1 deletion walrus-tests/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ where
println!("cargo:rerun-if-changed={}", dir.as_ref().display());
for entry in WalkDir::new(dir) {
let entry = entry.unwrap();
if entry.path().extension() == Some(OsStr::new("wat")) {
if entry.path().extension() == Some(OsStr::new("wat"))
|| entry.path().extension() == Some(OsStr::new("wast"))
{
println!("cargo:rerun-if-changed={}", entry.path().display());
f(entry.path());
}
Expand Down Expand Up @@ -54,4 +56,5 @@ fn main() {
generate_tests("valid");
generate_tests("round_trip");
generate_tests("ir");
generate_tests("spec-tests");
}
1 change: 1 addition & 0 deletions walrus-tests/tests/spec-tests
Submodule spec-tests added at c8d7b0
Loading

0 comments on commit e8f257f

Please sign in to comment.