Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rewrite wasm-bindgen with updated interface types proposal #1882

Merged
merged 36 commits into from
Dec 3, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
5595132
wip
alexcrichton Nov 20, 2019
9c63620
Lots of initial work for wasm interface types
alexcrichton Nov 21, 2019
061e463
Uncomment and fix `wit/mod.rs`
alexcrichton Nov 21, 2019
41455de
Implement support for loading standard sections
alexcrichton Nov 21, 2019
f6710bf
wip
alexcrichton Nov 22, 2019
8a102fe
Get a lot more pieces up and at least compiling
alexcrichton Nov 25, 2019
c019a03
More work in progress
alexcrichton Nov 26, 2019
0f8c16c
Another round of attempts
alexcrichton Nov 26, 2019
d334ff8
Mliestone: producing parseable JS!
alexcrichton Nov 26, 2019
cab1b08
Milestone, main wasm test suite passing!
alexcrichton Nov 27, 2019
0f21a8a
Now passing the anyref test suite!
alexcrichton Nov 27, 2019
3298373
Depend on git wit-walrus
alexcrichton Nov 27, 2019
eca0e54
Merge remote-tracking branch 'origin/master' into wasm-interface-types
alexcrichton Nov 28, 2019
4664199
Fix botched merge
alexcrichton Nov 28, 2019
93c31b5
Remove unnecessary commented out code
alexcrichton Nov 28, 2019
20ac2dc
Beef up a test about optional anyref types
alexcrichton Nov 28, 2019
7f2729e
Uncomment and fix init function generation
alexcrichton Nov 28, 2019
46ba8b6
Try to fix compilation of serde feature
alexcrichton Nov 28, 2019
7495d4d
Add some unit tests for the anyref transform
alexcrichton Nov 30, 2019
8210940
Add some more anyref transform tests
alexcrichton Nov 30, 2019
cbdbeb2
Add an anyref function table test
alexcrichton Nov 30, 2019
24818fa
Add some tests for the multivalue transform
alexcrichton Nov 30, 2019
69261ea
Run rustfmt
alexcrichton Nov 30, 2019
a1e2203
Run multi-value tests on CI
alexcrichton Nov 30, 2019
a4bce8f
Fix the typescript output of wasm-bindgen
alexcrichton Nov 30, 2019
1487c98
Update to published versions of all crates
alexcrichton Dec 2, 2019
07bd358
Remove magical names for anyref items
alexcrichton Dec 3, 2019
0690f2e
Fix a typo
alexcrichton Dec 3, 2019
20091ce
Add some comments around JS generation
alexcrichton Dec 3, 2019
959e8a8
Comment out dead code for now
alexcrichton Dec 3, 2019
f83f377
Remove some typos
alexcrichton Dec 3, 2019
18ba7ee
Remove commented out modules
alexcrichton Dec 3, 2019
30af757
Improve tests for optional slices
alexcrichton Dec 3, 2019
a042729
Don't allow dead code in the CLI any more
alexcrichton Dec 3, 2019
9867d21
Fix logic for logging errors on imports
alexcrichton Dec 3, 2019
79a911c
Re-enable the direct import optimization
alexcrichton Dec 3, 2019
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions azure-pipelines.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,10 @@ jobs:
displayName: "wasm-bindgen-cli-support tests"
- script: cargo test -p wasm-bindgen-cli
displayName: "wasm-bindgen-cli tests"
- script: cargo test -p wasm-bindgen-anyref-xform
displayName: "wasm-bindgen-anyref-xform tests"
- script: cargo test -p wasm-bindgen-multi-value-xform
displayName: "wasm-bindgen-multi-value-xform tests"

- job: test_web_sys
displayName: "Run web-sys crate tests"
Expand Down
12 changes: 11 additions & 1 deletion crates/anyref-xform/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,14 @@ edition = '2018'

[dependencies]
anyhow = "1.0"
walrus = "0.13.0"
walrus = "0.14.0"

[dev-dependencies]
rayon = "1.0"
wasmprinter = "0.2"
wast = "3.0"
wat = "1.0"

[[test]]
name = "all"
harness = false
22 changes: 18 additions & 4 deletions crates/anyref-xform/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ pub struct Context {
table: Option<TableId>,
}

pub struct Meta {
pub table: TableId,
pub alloc: Option<FunctionId>,
pub drop_slice: Option<FunctionId>,
}

struct Transform<'a> {
cx: &'a mut Context,

Expand Down Expand Up @@ -161,7 +167,7 @@ impl Context {
})
}

pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
pub fn run(&mut self, module: &mut Module) -> Result<Meta, Error> {
let table = self.table.unwrap();

// Inject a stack pointer global which will be used for managing the
Expand All @@ -171,6 +177,7 @@ impl Context {

let mut heap_alloc = None;
let mut heap_dealloc = None;
let mut drop_slice = None;

// Find exports of some intrinsics which we only need for a runtime
// implementation.
Expand All @@ -182,7 +189,8 @@ impl Context {
match export.name.as_str() {
"__wbindgen_anyref_table_alloc" => heap_alloc = Some(f),
"__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f),
_ => {}
"__wbindgen_drop_anyref_slice" => drop_slice = Some(f),
_ => continue,
}
}
let mut clone_ref = None;
Expand Down Expand Up @@ -226,7 +234,13 @@ impl Context {
heap_dealloc,
stack_pointer,
}
.run(module)
.run(module)?;

Ok(Meta {
table,
alloc: heap_alloc,
drop_slice,
})
}
}

Expand Down Expand Up @@ -619,7 +633,7 @@ impl Transform<'_> {
// with a fresh type we've been calculating so far. Give the function a
// nice name for debugging and then we're good to go!
let id = builder.finish(params, funcs);
let name = format!("{}_anyref_shim", name);
let name = format!("{} anyref shim", name);
funcs.get_mut(id).name = Some(name);
self.shims.insert(id);
Ok((id, anyref_ty))
Expand Down
258 changes: 258 additions & 0 deletions crates/anyref-xform/tests/all.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,258 @@
//! A small test framework to execute a test function over all files in a
//! directory.
//!
//! Each file in the directory has its own `CHECK-ALL` annotation indicating the
//! expected output of the test. That can be automatically updated with
//! `BLESS=1` in the environment. Otherwise the test are checked against the
//! listed expectation.

use anyhow::{anyhow, bail, Context, Result};
use rayon::prelude::*;
use std::env;
use std::fs;
use std::path::{Path, PathBuf};
use wast::parser::{Parse, Parser};

fn main() {
run("tests".as_ref(), runtest);
}

fn runtest(test: &Test) -> Result<String> {
let wasm = wat::parse_file(&test.file)?;
let mut walrus = walrus::Module::from_buffer(&wasm)?;
let mut cx = wasm_bindgen_anyref_xform::Context::default();
cx.prepare(&mut walrus)?;
for directive in test.directives.iter() {
match &directive.kind {
DirectiveKind::Export(name) => {
let export = walrus
.exports
.iter()
.find(|e| e.name == *name)
.ok_or_else(|| anyhow!("failed to find export"))?;
cx.export_xform(export.id(), &directive.args, directive.ret_anyref);
}
DirectiveKind::Import(module, field) => {
let import = walrus
.imports
.iter()
.find(|e| e.module == *module && e.name == *field)
.ok_or_else(|| anyhow!("failed to find export"))?;
cx.import_xform(import.id(), &directive.args, directive.ret_anyref);
}
DirectiveKind::Table(idx) => {
cx.table_element_xform(*idx, &directive.args, directive.ret_anyref);
}
}
}
cx.run(&mut walrus)?;
walrus::passes::gc::run(&mut walrus);
let printed = wasmprinter::print_bytes(&walrus.emit_wasm())?;
Ok(printed)
}

fn run(dir: &Path, run: fn(&Test) -> Result<String>) {
let mut tests = Vec::new();
find_tests(dir, &mut tests);
let filter = std::env::args().nth(1);

let bless = env::var("BLESS").is_ok();
let tests = tests
.iter()
.filter(|test| {
if let Some(filter) = &filter {
if let Some(s) = test.file_name().and_then(|s| s.to_str()) {
if !s.contains(filter) {
return false;
}
}
}
true
})
.collect::<Vec<_>>();

println!("\nrunning {} tests\n", tests.len());

let errors = tests
.par_iter()
.filter_map(|test| run_test(test, bless, run).err())
.collect::<Vec<_>>();

if !errors.is_empty() {
for msg in errors.iter() {
eprintln!("error: {:?}", msg);
}

panic!("{} tests failed", errors.len())
}

println!("test result: ok. {} passed\n", tests.len());
}

fn run_test(test: &Path, bless: bool, run: fn(&Test) -> anyhow::Result<String>) -> Result<()> {
(|| -> Result<_> {
let expected = Test::from_file(test)?;
let actual = run(&expected)?;
expected.check(&actual, bless)?;
Ok(())
})()
.context(format!("test failed - {}", test.display()))?;
Ok(())
}

fn find_tests(path: &Path, tests: &mut Vec<PathBuf>) {
for f in path.read_dir().unwrap() {
let f = f.unwrap();
if f.file_type().unwrap().is_dir() {
find_tests(&f.path(), tests);
continue;
}
match f.path().extension().and_then(|s| s.to_str()) {
Some("wat") => {}
_ => continue,
}
tests.push(f.path());
}
}

struct Test {
file: PathBuf,
directives: Vec<Directive>,
assertion: Option<String>,
}

struct Directive {
args: Vec<(usize, bool)>,
ret_anyref: bool,
kind: DirectiveKind,
}

enum DirectiveKind {
Import(String, String),
Export(String),
Table(u32),
}

impl Test {
fn from_file(path: &Path) -> Result<Test> {
let contents = fs::read_to_string(path)?;
let mut iter = contents.lines();
let mut assertion = None;
let mut directives = Vec::new();
while let Some(line) = iter.next() {
if line.starts_with("(; CHECK-ALL:") {
let mut pattern = String::new();
while let Some(line) = iter.next() {
if line == ";)" {
break;
}
pattern.push_str(line);
pattern.push_str("\n");
}
while pattern.ends_with("\n") {
pattern.pop();
}
if iter.next().is_some() {
bail!("CHECK-ALL must be at the end of the file");
}
assertion = Some(pattern);
continue;
}

if !line.starts_with(";; @xform") {
continue;
}
let directive = &line[9..];
let buf = wast::parser::ParseBuffer::new(directive)?;
directives.push(wast::parser::parse::<Directive>(&buf)?);
}
Ok(Test {
file: path.to_path_buf(),
directives,
assertion,
})
}

fn check(&self, output: &str, bless: bool) -> Result<()> {
if bless {
update_output(&self.file, output)
} else if let Some(pattern) = &self.assertion {
if output == pattern {
return Ok(());
}
bail!(
"expected\n {}\n\nactual\n {}",
pattern.replace("\n", "\n "),
output.replace("\n", "\n ")
);
} else {
bail!(
"no test assertions were found in this file, but you can \
rerun tests with `BLESS=1` to automatically add assertions \
to this file"
);
}
}
}

fn update_output(path: &Path, output: &str) -> Result<()> {
let contents = fs::read_to_string(path)?;
let start = contents.find("(; CHECK-ALL:").unwrap_or(contents.len());

let mut new_output = String::new();
for line in output.lines() {
new_output.push_str(line);
new_output.push_str("\n");
}
let new = format!(
"{}\n\n(; CHECK-ALL:\n{}\n;)\n",
contents[..start].trim(),
new_output.trim_end()
);
fs::write(path, new)?;
Ok(())
}

impl<'a> Parse<'a> for Directive {
fn parse(parser: Parser<'a>) -> wast::parser::Result<Self> {
use wast::kw;
wast::custom_keyword!(anyref_owned);
wast::custom_keyword!(anyref_borrowed);
wast::custom_keyword!(other);

let kind = if parser.peek::<kw::import>() {
parser.parse::<kw::import>()?;
DirectiveKind::Import(parser.parse()?, parser.parse()?)
} else if parser.peek::<kw::export>() {
parser.parse::<kw::export>()?;
DirectiveKind::Export(parser.parse()?)
} else {
parser.parse::<kw::table>()?;
DirectiveKind::Table(parser.parse()?)
};
let mut args = Vec::new();
parser.parens(|p| {
let mut i = 0;
while !p.is_empty() {
if parser.peek::<anyref_owned>() {
parser.parse::<anyref_owned>()?;
args.push((i, true));
} else if parser.peek::<anyref_borrowed>() {
parser.parse::<anyref_borrowed>()?;
args.push((i, false));
} else {
parser.parse::<other>()?;
}
i += 1;
}
Ok(())
})?;

let ret_anyref = parser.parse::<Option<anyref_owned>>()?.is_some();
Ok(Directive {
args,
ret_anyref,
kind,
})
}
}
31 changes: 31 additions & 0 deletions crates/anyref-xform/tests/anyref-param-owned.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
;; @xform export "foo" (anyref_owned)

(module
(func $foo (export "foo") (param i32))
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
i32.const 0)
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
)

(; CHECK-ALL:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice. Maybe worth pulling our simple filecheck implementation into a reusable crate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've been tweaking this as I've gone along for various crates, but it's starting to reach a steady state, yeah, so we should probably look into extracting it soon!

(module
(type (;0;) (func (result i32)))
(type (;1;) (func (param i32)))
(type (;2;) (func (param anyref)))
(func $foo anyref shim (type 2) (param anyref)
(local i32)
call $alloc
local.tee 1
local.get 0
table.set 0
local.get 1
call $foo)
(func $alloc (type 0) (result i32)
i32.const 0)
(func $foo (type 1) (param i32))
(func $dealloc (type 1) (param i32))
(table (;0;) 32 anyref)
(export "foo" (func $foo anyref shim))
(export "__wbindgen_anyref_table_alloc" (func $alloc))
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
;)
Loading