diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 569ebfbccaf..27759af6a7e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -92,6 +92,9 @@ jobs: # - template: ci/azure-install-sccache.yml - script: rustup target add wasm32-unknown-unknown displayName: "install wasm target" + - task: NodeTool@0 + inputs: + versionSpec: '>=13.0' - script: cargo test -p wasm-bindgen-cli-support displayName: "wasm-bindgen-cli-support tests" - script: cargo test -p wasm-bindgen-cli diff --git a/crates/cli-support/Cargo.toml b/crates/cli-support/Cargo.toml index 82e3f9e8f1d..eccac38dca9 100644 --- a/crates/cli-support/Cargo.toml +++ b/crates/cli-support/Cargo.toml @@ -12,17 +12,19 @@ Shared support for the wasm-bindgen-cli package, an internal dependency edition = '2018' [dependencies] -base64 = "0.9" anyhow = "1.0" +base64 = "0.9" log = "0.4" rustc-demangle = "0.1.13" serde_json = "1.0" tempfile = "3.0" walrus = "0.14.0" wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.55' } -wasm-bindgen-shared = { path = "../shared", version = '=0.2.55' } wasm-bindgen-multi-value-xform = { path = '../multi-value-xform', version = '=0.2.55' } +wasm-bindgen-shared = { path = "../shared", version = '=0.2.55' } wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.55' } wasm-bindgen-wasm-conventions = { path = '../wasm-conventions', version = '=0.2.55' } wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.55' } +wit-text = "0.1.1" wit-walrus = "0.1.0" +wit-validator = "0.1.0" diff --git a/crates/cli-support/src/lib.rs b/crates/cli-support/src/lib.rs index 23fedd9b819..1a25f5c6e57 100755 --- a/crates/cli-support/src/lib.rs +++ b/crates/cli-support/src/lib.rs @@ -264,8 +264,10 @@ impl Bindgen { (mem::replace(m, blank_module), &name[..]) } Input::Path(ref path) => { - let contents = fs::read(&path) + let wasm = wit_text::parse_file(&path) .with_context(|| format!("failed to read `{}`", path.display()))?; + wit_validator::validate(&wasm) + .with_context(|| format!("failed to validate `{}`", path.display()))?; let module = walrus::ModuleConfig::new() // Skip validation of the module as LLVM's output is // generally already well-formed and so we won't gain much @@ -278,7 +280,7 @@ impl Bindgen { .generate_name_section(!self.remove_name_section) .generate_producers_section(!self.remove_producers_section) .on_parse(wit_walrus::on_parse) - .parse(&contents) + .parse(&wasm) .context("failed to parse input file as wasm")?; let stem = match &self.out_name { Some(name) => &name, diff --git a/crates/cli-support/src/wit/mod.rs b/crates/cli-support/src/wit/mod.rs index 8b15cc14744..febd4d5cfe9 100644 --- a/crates/cli-support/src/wit/mod.rs +++ b/crates/cli-support/src/wit/mod.rs @@ -92,8 +92,8 @@ pub fn process( impl<'a> Context<'a> { fn init(&mut self) -> Result<(), Error> { - let stack_pointer = wasm_bindgen_wasm_conventions::get_shadow_stack_pointer(self.module)?; - self.aux.shadow_stack_pointer = Some(stack_pointer); + self.aux.shadow_stack_pointer = + wasm_bindgen_wasm_conventions::get_shadow_stack_pointer(self.module); // Make a map from string name to ids of all exports for export in self.module.exports.iter() { diff --git a/crates/cli/Cargo.toml b/crates/cli/Cargo.toml index fe2bdfb18c1..862c0632f0e 100644 --- a/crates/cli/Cargo.toml +++ b/crates/cli/Cargo.toml @@ -36,6 +36,7 @@ rayon = "1.0" tempfile = "3.0" walrus = "0.14" wit-printer = "0.1" +wit-text = "0.1" wit-validator = "0.1" wit-walrus = "0.1" @@ -43,5 +44,9 @@ wit-walrus = "0.1" name = "reference" harness = false +[[test]] +name = "interface-types" +harness = false + [features] vendored-openssl = ['openssl/vendored'] diff --git a/crates/cli/tests/interface-types.rs b/crates/cli/tests/interface-types.rs new file mode 100644 index 00000000000..a5d8d9a7291 --- /dev/null +++ b/crates/cli/tests/interface-types.rs @@ -0,0 +1,85 @@ +use anyhow::{bail, Result}; +use assert_cmd::prelude::*; +use rayon::prelude::*; +use std::env; +use std::path::Path; +use std::process::Command; + +fn main() -> Result<()> { + let filter = env::args().nth(1); + + let mut tests = Vec::new(); + let dir = env::current_dir()?.join("tests/interface-types"); + for entry in dir.read_dir()? { + let path = entry?.path(); + if path.extension().and_then(|s| s.to_str()) != Some("wit") { + continue; + } + if let Some(filter) = &filter { + if !path.display().to_string().contains(filter) { + continue; + } + } + tests.push(path); + } + tests.sort(); + + let errs = tests + .par_iter() + .filter_map(|t| runtest(t).err().map(|e| (t, e))) + .collect::>(); + + if errs.len() == 0 { + println!("{} tests passed", tests.len()); + return Ok(()); + } + eprintln!("failed tests:\n"); + for (test, err) in errs { + eprintln!("{} failure\n{}", test.display(), tab(&format!("{:?}", err))); + } + bail!("tests failed"); +} + +fn runtest(test: &Path) -> Result<()> { + let js = test.with_extension("js"); + let td = tempfile::TempDir::new()?; + + let mut bindgen = Command::cargo_bin("wasm-bindgen")?; + bindgen + .arg("--out-dir") + .arg(td.path()) + .arg(test) + .arg("--out-name=wasm") + .arg("--nodejs") + .arg("--no-typescript"); + exec(&mut bindgen)?; + + exec(Command::new("node") + .arg("--experimental-wasm-anyref") + .arg("--experimental-wasm-mv") + .arg(&js).env("NODE_PATH", td.path()))?; + + Ok(()) +} + +fn exec(cmd: &mut Command) -> Result<()> { + let output = cmd.output()?; + if output.status.success() { + return Ok(()); + } + let mut err = format!("command failed {:?}", cmd); + err.push_str(&format!("\nstatus: {}", output.status)); + err.push_str(&format!( + "\nstderr:\n{}", + tab(&String::from_utf8_lossy(&output.stderr)) + )); + err.push_str(&format!( + "\nstdout:\n{}", + tab(&String::from_utf8_lossy(&output.stdout)) + )); + bail!("{}", err); +} + +fn tab(s: &str) -> String { + format!(" {}", s.replace("\n", "\n ")) +} diff --git a/crates/cli/tests/interface-types/anyref.js b/crates/cli/tests/interface-types/anyref.js new file mode 100644 index 00000000000..20caf0d46db --- /dev/null +++ b/crates/cli/tests/interface-types/anyref.js @@ -0,0 +1,16 @@ +const assert = require('assert'); +const wasm = require('wasm'); + +const obj = {}; +assert.strictEqual(wasm.foo(obj), obj); + +wasm.store('x'); +assert.strictEqual(wasm.load(), 'x'); + +const obj2 = {}; +wasm.store(obj2); +assert.strictEqual(wasm.load(), obj2); +assert.strictEqual(wasm.load(), obj2); + +wasm.store(undefined); +assert.strictEqual(wasm.load(), undefined); diff --git a/crates/cli/tests/interface-types/anyref.wit b/crates/cli/tests/interface-types/anyref.wit new file mode 100644 index 00000000000..8903f853ddb --- /dev/null +++ b/crates/cli/tests/interface-types/anyref.wit @@ -0,0 +1,26 @@ +(module + (func $foo (param anyref) (result anyref) + local.get 0) + + (func $store (param anyref) + i32.const 0 + local.get 0 + table.set 0) + + (func $load (result anyref) + i32.const 0 + table.get 0) + + (table 1 anyref) + + (@interface func (export "foo") (param anyref) (result anyref) + arg.get 0 + call-core $foo) + + (@interface func (export "store") (param anyref) + arg.get 0 + call-core $store) + + (@interface func (export "load") (result anyref) + call-core $load) +) diff --git a/crates/cli/tests/interface-types/defer-call.js b/crates/cli/tests/interface-types/defer-call.js new file mode 100644 index 00000000000..a8abdc319dc --- /dev/null +++ b/crates/cli/tests/interface-types/defer-call.js @@ -0,0 +1,5 @@ +const wasm = require('wasm'); +const assert = require('assert'); + +assert.strictEqual(wasm.foo(), 0); +assert.strictEqual(wasm.get(), 1); diff --git a/crates/cli/tests/interface-types/defer-call.wit b/crates/cli/tests/interface-types/defer-call.wit new file mode 100644 index 00000000000..4b4b5955ce6 --- /dev/null +++ b/crates/cli/tests/interface-types/defer-call.wit @@ -0,0 +1,21 @@ +(module + (global $ctr (mut i32) (i32.const 0)) + + (func $increment + global.get $ctr + i32.const 1 + i32.add + global.set $ctr) + + (func $get (result i32) + global.get $ctr) + + (@interface func (export "foo") (result s32) + defer-call-core $increment + call-core $get + i32-to-s32) + + (@interface func (export "get") (result s32) + call-core $get + i32-to-s32) +) diff --git a/crates/cli/tests/interface-types/empty.js b/crates/cli/tests/interface-types/empty.js new file mode 100644 index 00000000000..050212fa60f --- /dev/null +++ b/crates/cli/tests/interface-types/empty.js @@ -0,0 +1,2 @@ +const m = require('wasm'); + diff --git a/crates/cli/tests/interface-types/empty.wit b/crates/cli/tests/interface-types/empty.wit new file mode 100644 index 00000000000..3af8f254547 --- /dev/null +++ b/crates/cli/tests/interface-types/empty.wit @@ -0,0 +1 @@ +(module) diff --git a/crates/cli/tests/interface-types/integers.js b/crates/cli/tests/interface-types/integers.js new file mode 100644 index 00000000000..05cbabc5228 --- /dev/null +++ b/crates/cli/tests/interface-types/integers.js @@ -0,0 +1,9 @@ +const assert = require('assert'); +const wasm = require('wasm'); + +assert.strictEqual(wasm.add_i8(0, 1), 1); +assert.strictEqual(wasm.add_u8(0, 1), 1); +assert.strictEqual(wasm.add_i16(0, 1), 1); +assert.strictEqual(wasm.add_u16(0, 1), 1); +assert.strictEqual(wasm.add_i32(0, 1), 1); +assert.strictEqual(wasm.add_u32(0, 1), 1); diff --git a/crates/cli/tests/interface-types/integers.wit b/crates/cli/tests/interface-types/integers.wit new file mode 100644 index 00000000000..b240b84516f --- /dev/null +++ b/crates/cli/tests/interface-types/integers.wit @@ -0,0 +1,54 @@ +(module + (func $add_i32 (param i32 i32) (result i32) + local.get 0 + local.get 1 + i32.add) + + (@interface func (export "add_i8") (param s8) (param s8) (result s8) + arg.get 0 + s8-to-i32 + arg.get 1 + s8-to-i32 + call-core $add_i32 + i32-to-s8) + + (@interface func (export "add_i16") (param s16) (param s16) (result s16) + arg.get 0 + s16-to-i32 + arg.get 1 + s16-to-i32 + call-core $add_i32 + i32-to-s16) + + (@interface func (export "add_i32") (param s32) (param s32) (result s32) + arg.get 0 + s32-to-i32 + arg.get 1 + s32-to-i32 + call-core $add_i32 + i32-to-s32) + + (@interface func (export "add_u8") (param s8) (param s8) (result s8) + arg.get 0 + s8-to-i32 + arg.get 1 + s8-to-i32 + call-core $add_i32 + i32-to-s8) + + (@interface func (export "add_u16") (param u16) (param u16) (result u16) + arg.get 0 + u16-to-i32 + arg.get 1 + u16-to-i32 + call-core $add_i32 + i32-to-u16) + + (@interface func (export "add_u32") (param u32) (param u32) (result u32) + arg.get 0 + u32-to-i32 + arg.get 1 + u32-to-i32 + call-core $add_i32 + i32-to-u32) +) diff --git a/crates/cli/tests/interface-types/memory-to-string.js b/crates/cli/tests/interface-types/memory-to-string.js new file mode 100644 index 00000000000..df3cc794572 --- /dev/null +++ b/crates/cli/tests/interface-types/memory-to-string.js @@ -0,0 +1,5 @@ +const wasm = require('wasm'); +const assert = require('assert'); + +assert.strictEqual(wasm.foo(), 'foo'); +assert.strictEqual(wasm.hexa(), 'hexa'); diff --git a/crates/cli/tests/interface-types/memory-to-string.wit b/crates/cli/tests/interface-types/memory-to-string.wit new file mode 100644 index 00000000000..95fd609799b --- /dev/null +++ b/crates/cli/tests/interface-types/memory-to-string.wit @@ -0,0 +1,21 @@ +(module + (memory 1) + + (func $foo (result i32 i32) + i32.const 0 + i32.const 3) + (func $hexa (result i32 i32) + i32.const 10 + i32.const 4) + + (data (i32.const 0) "foo") + (data (i32.const 10) "hexa") + + (@interface func (export "foo") (result string) + call-core $foo + memory-to-string) + + (@interface func (export "hexa") (result string) + call-core $hexa + memory-to-string) +) diff --git a/crates/cli/tests/interface-types/no-wasm.js b/crates/cli/tests/interface-types/no-wasm.js new file mode 100644 index 00000000000..50b0b21a17e --- /dev/null +++ b/crates/cli/tests/interface-types/no-wasm.js @@ -0,0 +1,5 @@ +const assert = require('assert'); +const wasm = require('wasm'); + +wasm.nop(); +assert.strictEqual(wasm.roundtrip(1), 1); diff --git a/crates/cli/tests/interface-types/no-wasm.wit b/crates/cli/tests/interface-types/no-wasm.wit new file mode 100644 index 00000000000..91c427f0311 --- /dev/null +++ b/crates/cli/tests/interface-types/no-wasm.wit @@ -0,0 +1,5 @@ +(module + (@interface func (export "nop")) + (@interface func (export "roundtrip") (param s32) (result s32) + arg.get 0) +) diff --git a/crates/cli/tests/interface-types/string-to-memory.js b/crates/cli/tests/interface-types/string-to-memory.js new file mode 100644 index 00000000000..cc9051bf0e0 --- /dev/null +++ b/crates/cli/tests/interface-types/string-to-memory.js @@ -0,0 +1,11 @@ +const wasm = require('wasm'); +const assert = require('assert'); + +const test = s => { + wasm.set(s); + assert.strictEqual(s, wasm.get()); +}; + +test(''); +test('x'); +test('aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'); diff --git a/crates/cli/tests/interface-types/string-to-memory.wit b/crates/cli/tests/interface-types/string-to-memory.wit new file mode 100644 index 00000000000..25f9576b343 --- /dev/null +++ b/crates/cli/tests/interface-types/string-to-memory.wit @@ -0,0 +1,27 @@ +(module + (memory 1) + + (global $glen (mut i32) (i32.const 0)) + (global $gptr (mut i32) (i32.const 0)) + + (func $malloc (param i32) (result i32) i32.const 23) + + (func $set (param $ptr i32) (param $len i32) + local.get $ptr + global.set $gptr + local.get $len + global.set $glen) + + (func $get (result i32 i32) + global.get $gptr + global.get $glen) + + (@interface func (export "set") (param string) + arg.get 0 + string-to-memory $malloc + call-core $set) + + (@interface func (export "get") (result string) + call-core $get + memory-to-string) +) diff --git a/crates/threads-xform/src/lib.rs b/crates/threads-xform/src/lib.rs index 38f2f09c232..18b8d9f480a 100644 --- a/crates/threads-xform/src/lib.rs +++ b/crates/threads-xform/src/lib.rs @@ -105,7 +105,8 @@ impl Config { } let memory = wasm_conventions::get_memory(module)?; - let stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?; + let stack_pointer = wasm_conventions::get_shadow_stack_pointer(module) + .ok_or_else(|| anyhow!("failed to find shadow stack pointer"))?; let addr = allocate_static_data(module, memory, 4, 4)?; let zero = InitExpr::Value(Value::I32(0)); let globals = Globals { diff --git a/crates/wasm-conventions/src/lib.rs b/crates/wasm-conventions/src/lib.rs index 61d84908fad..b7fd80e3591 100755 --- a/crates/wasm-conventions/src/lib.rs +++ b/crates/wasm-conventions/src/lib.rs @@ -33,7 +33,7 @@ pub fn get_memory(module: &Module) -> Result { /// /// It must have been previously added to the module's exports via /// `export_shadow_stack_pointer`. -pub fn get_shadow_stack_pointer(module: &Module) -> Result { +pub fn get_shadow_stack_pointer(module: &Module) -> Option { let candidates = module .globals .iter() @@ -48,12 +48,10 @@ pub fn get_shadow_stack_pointer(module: &Module) -> Result { }) .collect::>(); - let ssp = match candidates.len() { - 0 => bail!("could not find the shadow stack pointer for the module"), + match candidates.len() { + 0 => None, // TODO: have an actual check here. - 1 => candidates[0].id(), - _ => bail!("too many mutable globals to infer which is the shadow stack pointer"), - }; - - Ok(ssp) + 1 => Some(candidates[0].id()), + _ => None, + } }