mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-15 01:30:51 +00:00
Rewrite wasm-bindgen with updated interface types proposal (#1882)
This commit is a pretty large scale rewrite of the internals of wasm-bindgen. No user-facing changes are expected as a result of this PR, but due to the scale of changes here it's likely inevitable that at least something will break. I'm hoping to get more testing in though before landing! The purpose of this PR is to update wasm-bindgen to the current state of the interface types proposal. The wasm-bindgen tool was last updated when it was still called "WebIDL bindings" so it's been awhile! All support is now based on https://github.com/bytecodealliance/wasm-interface-types which defines parsers/binary format/writers/etc for wasm-interface types. This is a pretty massive PR and unfortunately can't really be split up any more afaik. I don't really expect realistic review of all the code here (or commits), but some high-level changes are: * Interface types now consists of a set of "adapter functions". The IR in wasm-bindgen is modeled the same way not. * Each adapter function has a list of instructions, and these instructions work at a higher level than wasm itself, for example with strings. * The wasm-bindgen tool has a suite of instructions which are specific to it and not present in the standard. (like before with webidl bindings) * The anyref/multi-value transformations are now greatly simplified. They're simply "optimization passes" over adapter functions, removing instructions that are otherwise present. This way we don't have to juggle so much all over the place, and instructions always have the same meaning.
This commit is contained in:
parent
df34cf843e
commit
8e56cdacc5
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
@ -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;
|
||||
@ -226,7 +234,13 @@ impl Context {
|
||||
heap_dealloc,
|
||||
stack_pointer,
|
||||
}
|
||||
.run(module)
|
||||
.run(module)?;
|
||||
|
||||
Ok(Meta {
|
||||
table,
|
||||
alloc: heap_alloc,
|
||||
drop_slice,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -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))
|
||||
|
258
crates/anyref-xform/tests/all.rs
Normal file
258
crates/anyref-xform/tests/all.rs
Normal file
@ -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
crates/anyref-xform/tests/anyref-param-owned.wat
Normal file
31
crates/anyref-xform/tests/anyref-param-owned.wat
Normal file
@ -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:
|
||||
(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)))
|
||||
;)
|
43
crates/anyref-xform/tests/anyref-param.wat
Normal file
43
crates/anyref-xform/tests/anyref-param.wat
Normal file
@ -0,0 +1,43 @@
|
||||
;; @xform export "foo" (anyref_borrowed)
|
||||
|
||||
(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:
|
||||
(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)
|
||||
global.get 0
|
||||
i32.const 1
|
||||
i32.sub
|
||||
local.tee 1
|
||||
global.set 0
|
||||
local.get 1
|
||||
local.get 0
|
||||
table.set 0
|
||||
local.get 1
|
||||
call $foo
|
||||
local.get 1
|
||||
ref.null
|
||||
table.set 0
|
||||
local.get 1
|
||||
i32.const 1
|
||||
i32.add
|
||||
global.set 0)
|
||||
(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)
|
||||
(global (;0;) (mut i32) (i32.const 32))
|
||||
(export "foo" (func $foo anyref shim))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
50
crates/anyref-xform/tests/clone-ref-intrinsic.wat
Normal file
50
crates/anyref-xform/tests/clone-ref-intrinsic.wat
Normal file
@ -0,0 +1,50 @@
|
||||
;; @xform export "foo" (anyref_owned) anyref_owned
|
||||
|
||||
(module
|
||||
(import "__wbindgen_placeholder__" "__wbindgen_object_clone_ref"
|
||||
(func $clone (param i32) (result i32)))
|
||||
(func $foo (export "foo") (param i32) (result i32)
|
||||
local.get 0
|
||||
call $clone)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(type (;2;) (func (param i32) (result i32)))
|
||||
(type (;3;) (func (param anyref) (result anyref)))
|
||||
(func $foo anyref shim (type 3) (param anyref) (result anyref)
|
||||
(local i32)
|
||||
call $alloc
|
||||
local.tee 1
|
||||
local.get 0
|
||||
table.set 0
|
||||
local.get 1
|
||||
call $foo
|
||||
local.tee 1
|
||||
table.get 0
|
||||
local.get 1
|
||||
call $dealloc)
|
||||
(func $__wbindgen_object_clone_ref (type 2) (param i32) (result i32)
|
||||
(local i32)
|
||||
call $alloc
|
||||
local.tee 1
|
||||
local.get 0
|
||||
table.get 0
|
||||
table.set 0
|
||||
local.get 1)
|
||||
(func $foo (type 2) (param i32) (result i32)
|
||||
local.get 0
|
||||
call $__wbindgen_object_clone_ref)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(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)))
|
||||
;)
|
37
crates/anyref-xform/tests/drop-ref-intrinsic.wat
Normal file
37
crates/anyref-xform/tests/drop-ref-intrinsic.wat
Normal file
@ -0,0 +1,37 @@
|
||||
;; @xform export "foo" (anyref_owned)
|
||||
|
||||
(module
|
||||
(import "__wbindgen_placeholder__" "__wbindgen_object_drop_ref"
|
||||
(func $drop (param i32)))
|
||||
(func $foo (export "foo") (param i32)
|
||||
local.get 0
|
||||
call $drop)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(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 $foo (type 1) (param i32)
|
||||
local.get 0
|
||||
call $dealloc)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(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)))
|
||||
;)
|
36
crates/anyref-xform/tests/import-anyref-owned.wat
Normal file
36
crates/anyref-xform/tests/import-anyref-owned.wat
Normal file
@ -0,0 +1,36 @@
|
||||
;; @xform import "" "a" (anyref_owned)
|
||||
|
||||
(module
|
||||
(import "" "a" (func $a (param i32)))
|
||||
(func (export "foo")
|
||||
i32.const 0
|
||||
call $a)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (result i32)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(type (;3;) (func (param anyref)))
|
||||
(import "" "a" (func $a (type 3)))
|
||||
(func $a anyref shim (type 2) (param i32)
|
||||
local.get 0
|
||||
table.get 0
|
||||
local.get 0
|
||||
call $dealloc
|
||||
call $a)
|
||||
(func (;2;) (type 0)
|
||||
i32.const 0
|
||||
call $a anyref shim)
|
||||
(func $alloc (type 1) (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (type 2) (param i32))
|
||||
(table (;0;) 32 anyref)
|
||||
(export "foo" (func 2))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
36
crates/anyref-xform/tests/import-anyref-ret.wat
Normal file
36
crates/anyref-xform/tests/import-anyref-ret.wat
Normal file
@ -0,0 +1,36 @@
|
||||
;; @xform import "" "a" () anyref_owned
|
||||
|
||||
(module
|
||||
(import "" "a" (func $a (result i32)))
|
||||
(func (export "foo") (result i32)
|
||||
call $a)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (result anyref)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(import "" "a" (func $a (type 1)))
|
||||
(func $a anyref shim (type 0) (result i32)
|
||||
(local i32 anyref)
|
||||
call $a
|
||||
local.set 1
|
||||
call $alloc
|
||||
local.tee 0
|
||||
local.get 1
|
||||
table.set 0
|
||||
local.get 0)
|
||||
(func (;2;) (type 0) (result i32)
|
||||
call $a anyref shim)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (type 2) (param i32))
|
||||
(table (;0;) 32 anyref)
|
||||
(export "foo" (func 2))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
34
crates/anyref-xform/tests/import-anyref.wat
Normal file
34
crates/anyref-xform/tests/import-anyref.wat
Normal file
@ -0,0 +1,34 @@
|
||||
;; @xform import "" "a" (anyref_borrowed)
|
||||
|
||||
(module
|
||||
(import "" "a" (func $a (param i32)))
|
||||
(func (export "foo")
|
||||
i32.const 0
|
||||
call $a)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (result i32)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(type (;3;) (func (param anyref)))
|
||||
(import "" "a" (func $a (type 3)))
|
||||
(func $a anyref shim (type 2) (param i32)
|
||||
local.get 0
|
||||
table.get 0
|
||||
call $a)
|
||||
(func (;2;) (type 0)
|
||||
i32.const 0
|
||||
call $a anyref shim)
|
||||
(func $alloc (type 1) (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (type 2) (param i32))
|
||||
(table (;0;) 32 anyref)
|
||||
(export "foo" (func 2))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
52
crates/anyref-xform/tests/mixed-export.wat
Normal file
52
crates/anyref-xform/tests/mixed-export.wat
Normal file
@ -0,0 +1,52 @@
|
||||
;; @xform export "a" (other anyref_borrowed other anyref_owned other)
|
||||
|
||||
(module
|
||||
(func $a (export "a") (param f32 i32 i64 i32 i32))
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(type (;2;) (func (param f32 i32 i64 i32 i32)))
|
||||
(type (;3;) (func (param f32 anyref i64 anyref i32)))
|
||||
(func $a anyref shim (type 3) (param f32 anyref i64 anyref i32)
|
||||
(local i32 i32)
|
||||
global.get 0
|
||||
i32.const 1
|
||||
i32.sub
|
||||
local.tee 5
|
||||
global.set 0
|
||||
local.get 0
|
||||
local.get 5
|
||||
local.get 1
|
||||
table.set 0
|
||||
local.get 5
|
||||
local.get 2
|
||||
call $alloc
|
||||
local.tee 6
|
||||
local.get 3
|
||||
table.set 0
|
||||
local.get 6
|
||||
local.get 4
|
||||
call $a
|
||||
local.get 5
|
||||
ref.null
|
||||
table.set 0
|
||||
local.get 5
|
||||
i32.const 1
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(func $a (type 2) (param f32 i32 i64 i32 i32))
|
||||
(func $dealloc (type 1) (param i32))
|
||||
(table (;0;) 32 anyref)
|
||||
(global (;0;) (mut i32) (i32.const 32))
|
||||
(export "a" (func $a anyref shim))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
50
crates/anyref-xform/tests/mixed.wat
Normal file
50
crates/anyref-xform/tests/mixed.wat
Normal file
@ -0,0 +1,50 @@
|
||||
;; @xform import "" "a" (other anyref_borrowed other anyref_owned other)
|
||||
|
||||
(module
|
||||
(import "" "a" (func $a (param f32 i32 i64 i32 i32)))
|
||||
(func (export "foo")
|
||||
f32.const 1
|
||||
i32.const 2
|
||||
i64.const 3
|
||||
i32.const 4
|
||||
i32.const 5
|
||||
call $a)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func))
|
||||
(type (;1;) (func (result i32)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(type (;3;) (func (param f32 i32 i64 i32 i32)))
|
||||
(type (;4;) (func (param f32 anyref i64 anyref i32)))
|
||||
(import "" "a" (func $a (type 4)))
|
||||
(func $a anyref shim (type 3) (param f32 i32 i64 i32 i32)
|
||||
local.get 0
|
||||
local.get 1
|
||||
table.get 0
|
||||
local.get 2
|
||||
local.get 3
|
||||
table.get 0
|
||||
local.get 3
|
||||
call $dealloc
|
||||
local.get 4
|
||||
call $a)
|
||||
(func (;2;) (type 0)
|
||||
f32.const 0x1p+0 (;=1;)
|
||||
i32.const 2
|
||||
i64.const 3
|
||||
i32.const 4
|
||||
i32.const 5
|
||||
call $a anyref shim)
|
||||
(func $alloc (type 1) (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (type 2) (param i32))
|
||||
(table (;0;) 32 anyref)
|
||||
(export "foo" (func 2))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc)))
|
||||
;)
|
33
crates/anyref-xform/tests/ret-anyref.wat
Normal file
33
crates/anyref-xform/tests/ret-anyref.wat
Normal file
@ -0,0 +1,33 @@
|
||||
;; @xform export "foo" () anyref_owned
|
||||
|
||||
(module
|
||||
(func $foo (export "foo") (result i32)
|
||||
i32.const 0)
|
||||
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (result anyref)))
|
||||
(type (;2;) (func (param i32)))
|
||||
(func $foo anyref shim (type 1) (result anyref)
|
||||
(local i32)
|
||||
call $foo
|
||||
local.tee 0
|
||||
table.get 0
|
||||
local.get 0
|
||||
call $dealloc)
|
||||
(func $foo (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (type 2) (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)))
|
||||
;)
|
40
crates/anyref-xform/tests/table-grow-intrinsic.wat
Normal file
40
crates/anyref-xform/tests/table-grow-intrinsic.wat
Normal file
@ -0,0 +1,40 @@
|
||||
;; @xform export "foo" (anyref_owned)
|
||||
|
||||
(module
|
||||
(import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_grow"
|
||||
(func $grow (param i32) (result i32)))
|
||||
(func $foo (export "foo") (param i32)
|
||||
i32.const 0
|
||||
call $grow
|
||||
drop)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(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 $foo (type 1) (param i32)
|
||||
ref.null
|
||||
i32.const 0
|
||||
table.grow 0
|
||||
drop)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(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)))
|
||||
;)
|
38
crates/anyref-xform/tests/table-set-null-intrinsic.wat
Normal file
38
crates/anyref-xform/tests/table-set-null-intrinsic.wat
Normal file
@ -0,0 +1,38 @@
|
||||
;; @xform export "foo" (anyref_owned)
|
||||
|
||||
(module
|
||||
(import "__wbindgen_anyref_xform__" "__wbindgen_anyref_table_set_null"
|
||||
(func $set-null (param i32)))
|
||||
(func $foo (export "foo") (param i32)
|
||||
local.get 0
|
||||
call $set-null)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(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 $foo (type 1) (param i32)
|
||||
local.get 0
|
||||
ref.null
|
||||
table.set 0)
|
||||
(func $alloc (type 0) (result i32)
|
||||
i32.const 0)
|
||||
(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)))
|
||||
;)
|
35
crates/anyref-xform/tests/table.wat
Normal file
35
crates/anyref-xform/tests/table.wat
Normal file
@ -0,0 +1,35 @@
|
||||
;; @xform table 0 (anyref_owned)
|
||||
|
||||
(module
|
||||
(func $foo (param i32))
|
||||
(table (export "func") 0 funcref)
|
||||
(elem (i32.const 0) 0)
|
||||
(func $alloc (export "__wbindgen_anyref_table_alloc") (result i32)
|
||||
i32.const 0)
|
||||
(func $dealloc (export "__wbindgen_anyref_table_dealloc") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(type (;2;) (func (param anyref)))
|
||||
(func $closure0 anyref shim (type 2) (param anyref)
|
||||
(local i32)
|
||||
call $alloc
|
||||
local.tee 1
|
||||
local.get 0
|
||||
table.set 1
|
||||
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;) 2 funcref)
|
||||
(table (;1;) 32 anyref)
|
||||
(export "func" (table 0))
|
||||
(export "__wbindgen_anyref_table_alloc" (func $alloc))
|
||||
(export "__wbindgen_anyref_table_dealloc" (func $dealloc))
|
||||
(elem (;0;) (i32.const 0) $foo $closure0 anyref shim))
|
||||
;)
|
@ -618,12 +618,16 @@ impl ToTokens for ast::ImportType {
|
||||
|
||||
impl OptionIntoWasmAbi for #rust_name {
|
||||
#[inline]
|
||||
fn none() -> Self::Abi { 0 }
|
||||
fn none() -> Self::Abi {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> OptionIntoWasmAbi for &'a #rust_name {
|
||||
#[inline]
|
||||
fn none() -> Self::Abi { 0 }
|
||||
fn none() -> Self::Abi {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
impl FromWasmAbi for #rust_name {
|
||||
|
@ -18,11 +18,11 @@ log = "0.4"
|
||||
rustc-demangle = "0.1.13"
|
||||
serde_json = "1.0"
|
||||
tempfile = "3.0"
|
||||
walrus = "0.13.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-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' }
|
||||
wasm-webidl-bindings = "0.6.0"
|
||||
wit-walrus = "0.1.0"
|
||||
|
@ -1,160 +1,269 @@
|
||||
use crate::webidl::{NonstandardIncoming, NonstandardOutgoing};
|
||||
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
|
||||
use crate::wit::{AdapterKind, Instruction, NonstandardWitSection};
|
||||
use crate::wit::{AdapterType, InstructionData, StackChange, WasmBindgenAux};
|
||||
use anyhow::Error;
|
||||
use std::collections::HashSet;
|
||||
use std::collections::HashMap;
|
||||
use walrus::Module;
|
||||
use wasm_bindgen_anyref_xform::Context;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub fn process(module: &mut Module, wasm_interface_types: bool) -> Result<(), Error> {
|
||||
pub fn process(module: &mut Module) -> Result<(), Error> {
|
||||
let mut cfg = Context::default();
|
||||
cfg.prepare(module)?;
|
||||
let bindings = module
|
||||
let section = module
|
||||
.customs
|
||||
.get_typed_mut::<NonstandardWebidlSection>()
|
||||
.expect("webidl custom section should exist");
|
||||
.get_typed_mut::<NonstandardWitSection>()
|
||||
.expect("wit custom section should exist");
|
||||
|
||||
let implements = section
|
||||
.implements
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|(core, adapter)| (adapter, core))
|
||||
.collect::<HashMap<_, _>>();
|
||||
|
||||
// Transform all exported functions in the module, using the bindings listed
|
||||
// for each exported function.
|
||||
for (export, binding) in bindings.exports.iter_mut() {
|
||||
let ty = module.types.get(binding.wasm_ty);
|
||||
let args = Arguments::Incoming(&mut binding.incoming);
|
||||
let (args, ret) = extract_anyrefs(ty, args);
|
||||
cfg.export_xform(*export, &args, ret);
|
||||
}
|
||||
|
||||
// Transform all imported functions in the module, using the bindings listed
|
||||
// for each imported function.
|
||||
for (import, binding) in bindings.imports.iter_mut() {
|
||||
let ty = module.types.get(binding.wasm_ty);
|
||||
let args = Arguments::Outgoing(&mut binding.outgoing);
|
||||
let (args, ret) = extract_anyrefs(ty, args);
|
||||
cfg.import_xform(*import, &args, ret);
|
||||
}
|
||||
|
||||
// And finally transform all table elements that are used as function
|
||||
// pointers for closures and such.
|
||||
for (idx, binding) in bindings.elems.iter_mut() {
|
||||
let ty = module.types.get(binding.wasm_ty);
|
||||
let args = Arguments::Incoming(&mut binding.incoming);
|
||||
let (args, ret) = extract_anyrefs(ty, args);
|
||||
if let Some(new) = cfg.table_element_xform(*idx, &args, ret) {
|
||||
*idx = new;
|
||||
for (id, adapter) in section.adapters.iter_mut() {
|
||||
let instructions = match &mut adapter.kind {
|
||||
AdapterKind::Local { instructions } => instructions,
|
||||
AdapterKind::Import { .. } => continue,
|
||||
};
|
||||
if let Some(id) = implements.get(&id) {
|
||||
import_xform(
|
||||
&mut cfg,
|
||||
*id,
|
||||
instructions,
|
||||
&mut adapter.params,
|
||||
&mut adapter.results,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
if let Some(id) = find_call_export(instructions) {
|
||||
export_xform(&mut cfg, id, instructions);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
cfg.run(module)?;
|
||||
let meta = cfg.run(module)?;
|
||||
|
||||
// If our output is using WebAssembly interface types then our bindings will
|
||||
// never use this table, so no need to export it. Otherwise it's highly
|
||||
// likely in web/JS embeddings this will be used, so make sure we export it
|
||||
// to avoid it getting gc'd accidentally.
|
||||
if !wasm_interface_types {
|
||||
// Make sure to export the `anyref` table for the JS bindings since it
|
||||
// will need to be initialized. If it doesn't exist though then the
|
||||
// module must not use it, so we skip it.
|
||||
let table = module.tables.iter().find(|t| match t.kind {
|
||||
walrus::TableKind::Anyref(_) => true,
|
||||
_ => false,
|
||||
});
|
||||
let table = match table {
|
||||
Some(t) => t.id(),
|
||||
None => return Ok(()),
|
||||
};
|
||||
module.exports.add("__wbg_anyref_table", table);
|
||||
}
|
||||
|
||||
// Clean up now-unused intrinsics and shims and such
|
||||
walrus::passes::gc::run(module);
|
||||
|
||||
// The GC pass above may end up removing some imported intrinsics. For
|
||||
// example `__wbindgen_object_clone_ref` is no longer needed after the
|
||||
// anyref pass. Make sure to delete the associated metadata for those
|
||||
// intrinsics so we don't try to access stale intrinsics later on.
|
||||
let remaining_imports = module
|
||||
.imports
|
||||
.iter()
|
||||
.map(|i| i.id())
|
||||
.collect::<HashSet<_>>();
|
||||
module
|
||||
.customs
|
||||
.get_typed_mut::<NonstandardWebidlSection>()
|
||||
.expect("webidl custom section should exist")
|
||||
.imports
|
||||
.retain(|id, _| remaining_imports.contains(id));
|
||||
module
|
||||
let section = module
|
||||
.customs
|
||||
.get_typed_mut::<WasmBindgenAux>()
|
||||
.expect("wasm-bindgen aux section should exist")
|
||||
.import_map
|
||||
.retain(|id, _| remaining_imports.contains(id));
|
||||
.expect("wit custom section should exist");
|
||||
section.anyref_table = Some(meta.table);
|
||||
section.anyref_alloc = meta.alloc;
|
||||
section.anyref_drop_slice = meta.drop_slice;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum Arguments<'a> {
|
||||
Incoming(&'a mut [NonstandardIncoming]),
|
||||
Outgoing(&'a mut [NonstandardOutgoing]),
|
||||
fn find_call_export(instrs: &[InstructionData]) -> Option<Export> {
|
||||
instrs
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, instr)| match instr.instr {
|
||||
Instruction::CallExport(e) => Some(Export::Export(e)),
|
||||
Instruction::CallTableElement(e) => Some(Export::TableElement {
|
||||
idx: e,
|
||||
call_idx: i,
|
||||
}),
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
}
|
||||
|
||||
/// Extract a description of the anyref arguments from the function signature
|
||||
/// described by `f`.
|
||||
///
|
||||
/// The returned values are expected to be passed to the anyref transformation
|
||||
/// pass, and indicate which arguments (by index) in the wasm signature should
|
||||
/// be transformed from `i32` to `anyref` as well as whether the returned value
|
||||
/// is an `anyref` or not.
|
||||
///
|
||||
/// The `offset` argument here is typically 0 and indicates the offset at which
|
||||
/// the wasm abi arguments described by `f` start at. For closures this is 2
|
||||
/// because two synthetic arguments are injected into the wasm signature which
|
||||
/// aren't present in the `Function` signature.
|
||||
fn extract_anyrefs(ty: &walrus::Type, args: Arguments<'_>) -> (Vec<(usize, bool)>, bool) {
|
||||
let mut ret = Vec::new();
|
||||
enum Export {
|
||||
Export(walrus::ExportId),
|
||||
TableElement {
|
||||
/// Table element that we're calling
|
||||
idx: u32,
|
||||
/// Index in the instruction stream where the call instruction is found
|
||||
call_idx: usize,
|
||||
},
|
||||
}
|
||||
|
||||
// First find all the `anyref` arguments in the input type, and we'll
|
||||
// assume that they're owned anyref arguments for now (the `true`)
|
||||
for (i, arg) in ty.params().iter().enumerate() {
|
||||
if *arg == walrus::ValType::Anyref {
|
||||
ret.push((i, true));
|
||||
/// Adapts the `instrs` given which are an implementation of the import of `id`.
|
||||
///
|
||||
/// This function will pattern match outgoing arguments and update the
|
||||
/// instruction stream to remove any anyref-management instructions since
|
||||
/// we'll be sinking those into the WebAssembly module.
|
||||
fn import_xform(
|
||||
cx: &mut Context,
|
||||
id: walrus::ImportId,
|
||||
instrs: &mut Vec<InstructionData>,
|
||||
params: &mut [AdapterType],
|
||||
results: &mut [AdapterType],
|
||||
) {
|
||||
struct Arg {
|
||||
idx: usize,
|
||||
// Some(false) for a borrowed anyref, Some(true) for an owned one
|
||||
anyref: Option<bool>,
|
||||
}
|
||||
|
||||
let mut to_delete = Vec::new();
|
||||
let mut iter = instrs.iter().enumerate();
|
||||
let mut args = Vec::new();
|
||||
while let Some((i, instr)) = iter.next() {
|
||||
match instr.instr {
|
||||
Instruction::CallAdapter(_) => break,
|
||||
Instruction::AnyrefLoadOwned | Instruction::TableGet => {
|
||||
let owned = match instr.instr {
|
||||
Instruction::TableGet => false,
|
||||
_ => true,
|
||||
};
|
||||
let mut arg: Arg = match args.pop().unwrap() {
|
||||
Some(arg) => arg,
|
||||
None => panic!("previous instruction must be `arg.get`"),
|
||||
};
|
||||
arg.anyref = Some(owned);
|
||||
match params[arg.idx] {
|
||||
AdapterType::I32 => {}
|
||||
_ => panic!("must be `i32` type"),
|
||||
}
|
||||
params[arg.idx] = AdapterType::Anyref;
|
||||
args.push(Some(arg));
|
||||
to_delete.push(i);
|
||||
}
|
||||
Instruction::Standard(wit_walrus::Instruction::ArgGet(n)) => {
|
||||
args.push(Some(Arg {
|
||||
idx: n as usize,
|
||||
anyref: None,
|
||||
}));
|
||||
}
|
||||
_ => match instr.stack_change {
|
||||
StackChange::Modified { pushed, popped } => {
|
||||
for _ in 0..popped {
|
||||
args.pop();
|
||||
}
|
||||
for _ in 0..pushed {
|
||||
args.push(None);
|
||||
}
|
||||
}
|
||||
StackChange::Unknown => {
|
||||
panic!("must have stack change data");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Afterwards look through the argument list (specified with various
|
||||
// bindings) to find any borrowed anyref values and update our
|
||||
// transformation metadata accordingly. if we find one then the binding no
|
||||
// longer needs to remember its borrowed but rather it's just a simple cast
|
||||
// from wasm anyref to JS any.
|
||||
match args {
|
||||
Arguments::Incoming(incoming) => {
|
||||
for binding in incoming {
|
||||
let expr = match binding {
|
||||
NonstandardIncoming::BorrowedAnyref {
|
||||
val: ast::IncomingBindingExpression::Get(expr),
|
||||
} => expr.clone(),
|
||||
_ => continue,
|
||||
};
|
||||
ret.iter_mut().find(|p| p.0 == expr.idx as usize).unwrap().1 = false;
|
||||
let new_binding = ast::IncomingBindingExpressionAs {
|
||||
ty: walrus::ValType::Anyref,
|
||||
expr: Box::new(expr.into()),
|
||||
};
|
||||
*binding = NonstandardIncoming::Standard(new_binding.into());
|
||||
let mut ret_anyref = false;
|
||||
while let Some((i, instr)) = iter.next() {
|
||||
match instr.instr {
|
||||
Instruction::I32FromAnyrefOwned => {
|
||||
assert_eq!(results.len(), 1);
|
||||
match results[0] {
|
||||
AdapterType::I32 => {}
|
||||
_ => panic!("must be `i32` type"),
|
||||
}
|
||||
results[0] = AdapterType::Anyref;
|
||||
ret_anyref = true;
|
||||
to_delete.push(i);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
Arguments::Outgoing(outgoing) => {
|
||||
for binding in outgoing {
|
||||
let idx = match binding {
|
||||
NonstandardOutgoing::BorrowedAnyref { idx } => *idx,
|
||||
_ => continue,
|
||||
};
|
||||
ret.iter_mut().find(|p| p.0 == idx as usize).unwrap().1 = false;
|
||||
let new_binding = ast::OutgoingBindingExpressionAs {
|
||||
idx,
|
||||
ty: ast::WebidlScalarType::Any.into(),
|
||||
};
|
||||
*binding = NonstandardOutgoing::Standard(new_binding.into());
|
||||
}
|
||||
|
||||
// Delete all unnecessary anyref management insructions
|
||||
for idx in to_delete.into_iter().rev() {
|
||||
instrs.remove(idx);
|
||||
}
|
||||
|
||||
// Filter down our list of arguments to just the ones that are anyref
|
||||
// values.
|
||||
let args = args
|
||||
.iter()
|
||||
.filter_map(|arg| arg.as_ref())
|
||||
.filter_map(|arg| arg.anyref.map(|owned| (arg.idx, owned)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// ... and register this entire transformation with the anyref
|
||||
// transformation pass.
|
||||
cx.import_xform(id, &args, ret_anyref);
|
||||
}
|
||||
|
||||
/// Adapts the `instrs` of an adapter function that calls an export.
|
||||
///
|
||||
/// The `instrs` must be generated by wasm-bindgen itself and follow the
|
||||
/// pattern matched below to pass off to the anyref transformation pass. The
|
||||
/// signature of the adapter doesn't change (it remains as anyref-aware) but the
|
||||
/// signature of the export we're calling will change during the transformation.
|
||||
fn export_xform(cx: &mut Context, export: Export, instrs: &mut Vec<InstructionData>) {
|
||||
let mut to_delete = Vec::new();
|
||||
let mut iter = instrs.iter().enumerate();
|
||||
let mut args = Vec::new();
|
||||
|
||||
// Mutate instructions leading up to the `CallExport` instruction. We
|
||||
// maintain a stack of indicators whether the element at that stack slot is
|
||||
// unknown (`None`) or whether it's an owned/borrowed anyref
|
||||
// (`Some(owned)`).
|
||||
//
|
||||
// Note that we're going to delete the `I32FromAnyref*` instructions, so we
|
||||
// also maintain indices of the instructions to delete.
|
||||
while let Some((i, instr)) = iter.next() {
|
||||
match instr.instr {
|
||||
Instruction::CallExport(_) | Instruction::CallTableElement(_) => break,
|
||||
Instruction::I32FromAnyrefOwned => {
|
||||
args.pop();
|
||||
args.push(Some(true));
|
||||
to_delete.push(i);
|
||||
}
|
||||
Instruction::I32FromAnyrefBorrow => {
|
||||
args.pop();
|
||||
args.push(Some(false));
|
||||
to_delete.push(i);
|
||||
}
|
||||
_ => match instr.stack_change {
|
||||
StackChange::Modified { pushed, popped } => {
|
||||
for _ in 0..popped {
|
||||
args.pop();
|
||||
}
|
||||
for _ in 0..pushed {
|
||||
args.push(None);
|
||||
}
|
||||
}
|
||||
StackChange::Unknown => {
|
||||
panic!("must have stack change data");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// If one of the instructions after the call is an `AnyrefLoadOwned` then we
|
||||
// know that the function returned an anyref. Currently `&'static Anyref`
|
||||
// can't be done as a return value, so this is the only case we handle here.
|
||||
let mut ret_anyref = false;
|
||||
while let Some((i, instr)) = iter.next() {
|
||||
match instr.instr {
|
||||
Instruction::AnyrefLoadOwned => {
|
||||
ret_anyref = true;
|
||||
to_delete.push(i);
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Filter down our list of arguments to just the ones that are anyref
|
||||
// values.
|
||||
let args = args
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(i, owned)| owned.map(|owned| (i, owned)))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// ... and register this entire transformation with the anyref
|
||||
// transformation pass.
|
||||
match export {
|
||||
Export::Export(id) => {
|
||||
cx.export_xform(id, &args, ret_anyref);
|
||||
}
|
||||
Export::TableElement { idx, call_idx } => {
|
||||
if let Some(new_idx) = cx.table_element_xform(idx, &args, ret_anyref) {
|
||||
instrs[call_idx].instr = Instruction::CallTableElement(new_idx);
|
||||
}
|
||||
}
|
||||
}
|
||||
(ret, ty.results() == &[walrus::ValType::Anyref])
|
||||
|
||||
// Delete all unnecessary anyref management instructions. We're going to
|
||||
// sink these instructions into the wasm module itself.
|
||||
for idx in to_delete.into_iter().rev() {
|
||||
instrs.remove(idx);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ macro_rules! intrinsics {
|
||||
(pub enum Intrinsic {
|
||||
$(
|
||||
#[symbol = $sym:tt]
|
||||
#[signature = fn($($arg:expr),*) -> $ret:ident]
|
||||
#[signature = fn($($arg:expr),*) -> $ret:expr]
|
||||
$name:ident,
|
||||
)*
|
||||
}) => {
|
||||
@ -36,7 +36,7 @@ macro_rules! intrinsics {
|
||||
|
||||
/// Returns the expected signature of this intrinsic, used for
|
||||
/// generating a JS shim.
|
||||
pub fn binding(&self) -> Function {
|
||||
pub fn signature(&self) -> Function {
|
||||
use crate::descriptor::Descriptor::*;
|
||||
match self {
|
||||
$(
|
||||
@ -71,6 +71,14 @@ fn ref_string() -> Descriptor {
|
||||
Descriptor::Ref(Box::new(Descriptor::String))
|
||||
}
|
||||
|
||||
fn opt_string() -> Descriptor {
|
||||
Descriptor::Option(Box::new(Descriptor::String))
|
||||
}
|
||||
|
||||
fn opt_f64() -> Descriptor {
|
||||
Descriptor::Option(Box::new(Descriptor::F64))
|
||||
}
|
||||
|
||||
intrinsics! {
|
||||
pub enum Intrinsic {
|
||||
#[symbol = "__wbindgen_jsval_eq"]
|
||||
@ -122,10 +130,10 @@ intrinsics! {
|
||||
#[signature = fn(ref_string()) -> Anyref]
|
||||
SymbolNamedNew,
|
||||
#[symbol = "__wbindgen_number_get"]
|
||||
#[signature = fn(ref_anyref(), I32) -> F64]
|
||||
#[signature = fn(ref_anyref()) -> opt_f64()]
|
||||
NumberGet,
|
||||
#[symbol = "__wbindgen_string_get"]
|
||||
#[signature = fn(ref_anyref(), I32) -> I32]
|
||||
#[signature = fn(ref_anyref()) -> opt_string()]
|
||||
StringGet,
|
||||
#[symbol = "__wbindgen_boolean_get"]
|
||||
#[signature = fn(ref_anyref()) -> I32]
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,556 +0,0 @@
|
||||
//! Implementation of taking a `NonstandardIncoming` binding and generating JS
|
||||
//! which represents it and executes it for what we need.
|
||||
//!
|
||||
//! This module is used to generate JS for all our incoming bindings which
|
||||
//! includes arguments going into exports or return values from imports.
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::js::binding::JsBuilder;
|
||||
use crate::js::Context;
|
||||
use crate::webidl::NonstandardIncoming;
|
||||
use anyhow::{bail, Error};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub struct Incoming<'a, 'b> {
|
||||
cx: &'a mut Context<'b>,
|
||||
types: &'a [ast::WebidlTypeRef],
|
||||
js: &'a mut JsBuilder,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Incoming<'a, 'b> {
|
||||
pub fn new(
|
||||
cx: &'a mut Context<'b>,
|
||||
types: &'a [ast::WebidlTypeRef],
|
||||
js: &'a mut JsBuilder,
|
||||
) -> Incoming<'a, 'b> {
|
||||
Incoming { cx, types, js }
|
||||
}
|
||||
|
||||
pub fn process(&mut self, incoming: &NonstandardIncoming) -> Result<Vec<String>, Error> {
|
||||
let before = self.js.typescript_len();
|
||||
let ret = self.nonstandard(incoming)?;
|
||||
assert_eq!(before + 1, self.js.typescript_len());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn nonstandard(&mut self, incoming: &NonstandardIncoming) -> Result<Vec<String>, Error> {
|
||||
let single = match incoming {
|
||||
NonstandardIncoming::Standard(val) => return self.standard(val),
|
||||
|
||||
// Evaluate the `val` binding, store it into a one-element `BigInt`
|
||||
// array (appropriately typed) and then use a 32-bit view into the
|
||||
// `BigInt` array to extract the high/low bits and pass them through
|
||||
// in the ABI.
|
||||
NonstandardIncoming::Int64 { val, signed } => {
|
||||
self.js.typescript_required("BigInt");
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"
|
||||
{f}[0] = {expr};
|
||||
const low{i} = u32CvtShim[0];
|
||||
const high{i} = u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
expr = expr,
|
||||
));
|
||||
return Ok(vec![format!("low{}", i), format!("high{}", i)]);
|
||||
}
|
||||
|
||||
// Same as `IncomingBindingExpressionAllocCopy`, except we use a
|
||||
// different `VectorKind`
|
||||
NonstandardIncoming::AllocCopyInt64 {
|
||||
alloc_func_name: _,
|
||||
expr,
|
||||
signed,
|
||||
} => {
|
||||
let (expr, ty) = self.standard_typed(expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let kind = if *signed {
|
||||
VectorKind::I64
|
||||
} else {
|
||||
VectorKind::U64
|
||||
};
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
// Same as `IncomingBindingExpressionAllocCopy`, except we use a
|
||||
// different `VectorKind`
|
||||
NonstandardIncoming::AllocCopyAnyrefArray {
|
||||
alloc_func_name: _,
|
||||
expr,
|
||||
} => {
|
||||
let (expr, ty) = self.standard_typed(expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(VectorKind::Anyref)?;
|
||||
self.js.typescript_required(VectorKind::Anyref.js_ty());
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
|
||||
// There's no `char` in JS, so we take a string instead and just
|
||||
// forward along the first code point to Rust.
|
||||
NonstandardIncoming::Char { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::DomString.into());
|
||||
self.js.typescript_required("string");
|
||||
format!("{}.codePointAt(0)", expr)
|
||||
}
|
||||
|
||||
// When moving a type back into Rust we need to clear out the
|
||||
// internal pointer in JS to prevent it from being reused again in
|
||||
// the future.
|
||||
NonstandardIncoming::RustType { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.assert_class(&expr, &class);
|
||||
self.assert_not_moved(&expr);
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!("const ptr{} = {}.ptr;", i, expr));
|
||||
self.js.prelude(&format!("{}.ptr = 0;", expr));
|
||||
self.js.typescript_required(class);
|
||||
format!("ptr{}", i)
|
||||
}
|
||||
|
||||
// Here we can simply pass along the pointer with no extra fluff
|
||||
// needed.
|
||||
NonstandardIncoming::RustTypeRef { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.assert_class(&expr, &class);
|
||||
self.assert_not_moved(&expr);
|
||||
self.js.typescript_required(class);
|
||||
format!("{}.ptr", expr)
|
||||
}
|
||||
|
||||
// the "stack-ful" nature means that we're always popping from the
|
||||
// stack, and make sure that we actually clear our reference to
|
||||
// allow stale values to get GC'd
|
||||
NonstandardIncoming::BorrowedAnyref { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.cx.expose_global_stack_pointer();
|
||||
self.js.finally("heap[stack_pointer++] = undefined;");
|
||||
self.js.typescript_required("any");
|
||||
format!("addBorrowedObject({})", expr)
|
||||
}
|
||||
|
||||
// Similar to `AllocCopy`, except that we deallocate in a finally
|
||||
// block.
|
||||
NonstandardIncoming::MutableSlice { kind, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
let i = self.js.tmp();
|
||||
self.js
|
||||
.prelude(&format!("const ptr{} = {}({});", i, func, expr));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.finally_free_slice(&expr, i, *kind, true)?;
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
|
||||
// Pass `None` as a sentinel value that `val` will never take on.
|
||||
// This is only manufactured for specific underlying types.
|
||||
NonstandardIncoming::OptionU32Sentinel { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0}", expr)
|
||||
}
|
||||
|
||||
// Pass `true` as 1, `false` as 0, and `None` as a sentinel value.
|
||||
NonstandardIncoming::OptionBool { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("boolean");
|
||||
self.assert_optional_bool(&expr);
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0} ? 1 : 0", expr)
|
||||
}
|
||||
|
||||
// Pass `None` as a sentinel value a character can never have
|
||||
NonstandardIncoming::OptionChar { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("string");
|
||||
format!("isLikeNone({0}) ? 0xFFFFFF : {0}.codePointAt(0)", expr)
|
||||
}
|
||||
|
||||
// Pass `None` as the hole in the enum which no valid value can ever
|
||||
// take
|
||||
NonstandardIncoming::OptionIntegerEnum { val, hole } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
format!("isLikeNone({0}) ? {1} : {0}", expr, hole)
|
||||
}
|
||||
|
||||
// `None` here is zero, but if `Some` then we need to clear out the
|
||||
// internal pointer because the value is being moved.
|
||||
NonstandardIncoming::OptionRustType { class, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!("let ptr{} = 0;", i));
|
||||
self.js.prelude(&format!("if (!isLikeNone({0})) {{", expr));
|
||||
self.assert_class(&expr, class);
|
||||
self.assert_not_moved(&expr);
|
||||
self.js.prelude(&format!("ptr{} = {}.ptr;", i, expr));
|
||||
self.js.prelude(&format!("{}.ptr = 0;", expr));
|
||||
self.js.prelude("}");
|
||||
self.js.typescript_optional(class);
|
||||
format!("ptr{}", i)
|
||||
}
|
||||
|
||||
// The ABI produces four values here, all zero for `None` and 1 in
|
||||
// the first for the last two being the low/high bits
|
||||
NonstandardIncoming::OptionInt64 { val, signed } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
self.cx.expose_uint32_memory();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"\
|
||||
{f}[0] = isLikeNone({expr}) ? BigInt(0) : {expr};
|
||||
const low{i} = isLikeNone({expr}) ? 0 : u32CvtShim[0];
|
||||
const high{i} = isLikeNone({expr}) ? 0 : u32CvtShim[1];
|
||||
",
|
||||
i = i,
|
||||
f = f,
|
||||
expr = expr,
|
||||
));
|
||||
self.js.typescript_optional("BigInt");
|
||||
return Ok(vec![
|
||||
format!("!isLikeNone({0})", expr),
|
||||
"0".to_string(),
|
||||
format!("low{}", i),
|
||||
format!("high{}", i),
|
||||
]);
|
||||
}
|
||||
|
||||
// The ABI here is always an integral index into the anyref table,
|
||||
// and the anyref table just differs based on whether we ran the
|
||||
// anyref pass or not.
|
||||
NonstandardIncoming::OptionAnyref { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("any");
|
||||
if self.cx.config.anyref {
|
||||
self.cx.expose_add_to_anyref_table()?;
|
||||
format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", expr)
|
||||
} else {
|
||||
self.cx.expose_add_heap_object();
|
||||
format!("isLikeNone({0}) ? 0 : addHeapObject({0})", expr)
|
||||
}
|
||||
}
|
||||
|
||||
// Native types of wasm take a leading discriminant to indicate
|
||||
// whether the next value is valid or not.
|
||||
NonstandardIncoming::OptionNative { val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.typescript_optional("number");
|
||||
self.assert_optional_number(&expr);
|
||||
return Ok(vec![
|
||||
format!("!isLikeNone({0})", expr),
|
||||
format!("isLikeNone({0}) ? 0 : {0}", expr),
|
||||
]);
|
||||
}
|
||||
|
||||
// Similar to `AllocCopy`, except we're handling the undefined case
|
||||
// and passing null for the pointer value.
|
||||
NonstandardIncoming::OptionVector { kind, val } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});",
|
||||
expr,
|
||||
i = i,
|
||||
f = func,
|
||||
));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
|
||||
// An unfortunate smorgasboard of handling slices, transfers if
|
||||
// mutable, etc. Not the prettiest binding option here, and of
|
||||
// course never going to be standardized.
|
||||
NonstandardIncoming::OptionSlice { kind, val, mutable } => {
|
||||
let (expr, ty) = self.standard_typed(val)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::Any.into());
|
||||
let func = self.cx.pass_to_wasm_function(*kind)?;
|
||||
self.cx.expose_is_like_none();
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"const ptr{i} = isLikeNone({0}) ? 0 : {f}({0});",
|
||||
expr,
|
||||
i = i,
|
||||
f = func,
|
||||
));
|
||||
self.js
|
||||
.prelude(&format!("const len{} = WASM_VECTOR_LEN;", i));
|
||||
self.js.finally(&format!("if (ptr{} !== 0) {{", i));
|
||||
self.finally_free_slice(&expr, i, *kind, *mutable)?;
|
||||
self.js.finally("}");
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
return Ok(vec![format!("ptr{}", i), format!("len{}", i)]);
|
||||
}
|
||||
};
|
||||
Ok(vec![single])
|
||||
}
|
||||
|
||||
/// Evaluates the `standard` binding expression, returning the JS expression
|
||||
/// needed to evaluate the binding.
|
||||
fn standard(
|
||||
&mut self,
|
||||
standard: &ast::IncomingBindingExpression,
|
||||
) -> Result<Vec<String>, Error> {
|
||||
let single = match standard {
|
||||
ast::IncomingBindingExpression::As(as_) => {
|
||||
let (expr, ty) = self.standard_typed(&as_.expr)?;
|
||||
match ty {
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => {
|
||||
self.js.typescript_required("any");
|
||||
|
||||
// If the type here is anyref but we didn't run the
|
||||
// anyref pass that means we have to instead actually
|
||||
// pass in an index
|
||||
//
|
||||
// TODO: we should ideally move this `addHeapObject`
|
||||
// into a nonstanard binding whenever the anyref pass
|
||||
// doesn't already run rather than implicitly picking
|
||||
// it up here
|
||||
if self.cx.config.anyref {
|
||||
expr
|
||||
} else {
|
||||
self.cx.expose_add_heap_object();
|
||||
format!("addHeapObject({})", expr)
|
||||
}
|
||||
}
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => {
|
||||
self.js.typescript_required("boolean");
|
||||
self.assert_bool(&expr);
|
||||
// JS will already coerce booleans into numbers for us
|
||||
expr
|
||||
}
|
||||
_ => {
|
||||
self.js.typescript_required("number");
|
||||
self.assert_number(&expr);
|
||||
expr
|
||||
}
|
||||
}
|
||||
}
|
||||
ast::IncomingBindingExpression::Get(_) => {
|
||||
bail!("unsupported bare `get` in webidl bindings");
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocUtf8Str(expr) => {
|
||||
let (expr, ty) = self.standard_typed(&expr.expr)?;
|
||||
assert_eq!(ty, ast::WebidlScalarType::DomString.into());
|
||||
self.js.typescript_required("string");
|
||||
self.cx.expose_pass_string_to_wasm()?;
|
||||
return Ok(vec![
|
||||
format!("passStringToWasm({})", expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocCopy(expr) => {
|
||||
let (expr, ty) = self.standard_typed(&expr.expr)?;
|
||||
let scalar = match ty {
|
||||
ast::WebidlTypeRef::Scalar(s) => s,
|
||||
ast::WebidlTypeRef::Id(_) => {
|
||||
bail!("unsupported type passed to `alloc-copy` in webidl binding")
|
||||
}
|
||||
};
|
||||
let kind = match scalar {
|
||||
ast::WebidlScalarType::Int8Array => VectorKind::I8,
|
||||
ast::WebidlScalarType::Uint8Array => VectorKind::U8,
|
||||
ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8,
|
||||
ast::WebidlScalarType::Int16Array => VectorKind::I16,
|
||||
ast::WebidlScalarType::Uint16Array => VectorKind::U16,
|
||||
ast::WebidlScalarType::Int32Array => VectorKind::I32,
|
||||
ast::WebidlScalarType::Uint32Array => VectorKind::U32,
|
||||
ast::WebidlScalarType::Float32Array => VectorKind::F32,
|
||||
ast::WebidlScalarType::Float64Array => VectorKind::F64,
|
||||
_ => bail!("unsupported type passed to alloc-copy: {:?}", scalar),
|
||||
};
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
let func = self.cx.pass_to_wasm_function(kind)?;
|
||||
return Ok(vec![
|
||||
format!("{}({})", func, expr),
|
||||
"WASM_VECTOR_LEN".to_string(),
|
||||
]);
|
||||
}
|
||||
ast::IncomingBindingExpression::EnumToI32(_) => {
|
||||
bail!("unsupported enum-to-i32 conversion in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Field(_) => {
|
||||
bail!("unsupported field accessor in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::BindImport(_) => {
|
||||
bail!("unsupported import binding in webidl binding");
|
||||
}
|
||||
};
|
||||
Ok(vec![single])
|
||||
}
|
||||
|
||||
/// Evaluates the `standard` binding expression, returning both the
|
||||
/// JS expression to evaluate along with the WebIDL type of the expression.
|
||||
///
|
||||
/// Currently only supports `Get`.
|
||||
fn standard_typed(
|
||||
&mut self,
|
||||
standard: &ast::IncomingBindingExpression,
|
||||
) -> Result<(String, ast::WebidlTypeRef), Error> {
|
||||
match standard {
|
||||
ast::IncomingBindingExpression::As(_) => {
|
||||
bail!("unsupported as in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Get(expr) => {
|
||||
let arg = self.js.arg(expr.idx).to_string();
|
||||
let ty = self.types[expr.idx as usize];
|
||||
Ok((arg, ty))
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocUtf8Str(_) => {
|
||||
bail!("unsupported alloc-utf8-str in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::AllocCopy(_) => {
|
||||
bail!("unsupported alloc-copy in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::EnumToI32(_) => {
|
||||
bail!("unsupported enum-to-i32 in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::Field(_) => {
|
||||
bail!("unsupported field accessor in webidl binding");
|
||||
}
|
||||
ast::IncomingBindingExpression::BindImport(_) => {
|
||||
bail!("unsupported import binding in webidl binding");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_class(&mut self, arg: &str, class: &str) {
|
||||
self.cx.expose_assert_class();
|
||||
self.js
|
||||
.prelude(&format!("_assertClass({}, {});", arg, class));
|
||||
}
|
||||
|
||||
fn assert_number(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_assert_num();
|
||||
self.js.prelude(&format!("_assertNum({});", arg));
|
||||
}
|
||||
|
||||
fn assert_bool(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_assert_bool();
|
||||
self.js.prelude(&format!("_assertBoolean({});", arg));
|
||||
}
|
||||
|
||||
fn assert_optional_number(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.prelude(&format!("if (!isLikeNone({})) {{", arg));
|
||||
self.assert_number(arg);
|
||||
self.js.prelude("}");
|
||||
}
|
||||
|
||||
fn assert_optional_bool(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.cx.expose_is_like_none();
|
||||
self.js.prelude(&format!("if (!isLikeNone({})) {{", arg));
|
||||
self.assert_bool(arg);
|
||||
self.js.prelude("}");
|
||||
}
|
||||
|
||||
fn assert_not_moved(&mut self, arg: &str) {
|
||||
if !self.cx.config.debug {
|
||||
return;
|
||||
}
|
||||
self.js.prelude(&format!(
|
||||
"\
|
||||
if ({0}.ptr === 0) {{
|
||||
throw new Error('Attempt to use a moved value');
|
||||
}}
|
||||
",
|
||||
arg,
|
||||
));
|
||||
}
|
||||
|
||||
fn finally_free_slice(
|
||||
&mut self,
|
||||
expr: &str,
|
||||
i: usize,
|
||||
kind: VectorKind,
|
||||
mutable: bool,
|
||||
) -> Result<(), Error> {
|
||||
// If the slice was mutable it's currently a feature that we
|
||||
// mirror back updates to the original slice. This... is
|
||||
// arguably a misfeature of wasm-bindgen...
|
||||
if mutable {
|
||||
let get = self.cx.memview_function(kind);
|
||||
self.js.finally(&format!(
|
||||
"\
|
||||
{arg}.set({get}().subarray(\
|
||||
ptr{i} / {size}, \
|
||||
ptr{i} / {size} + len{i}\
|
||||
));\
|
||||
",
|
||||
i = i,
|
||||
arg = expr,
|
||||
get = get,
|
||||
size = kind.size()
|
||||
));
|
||||
}
|
||||
self.js.finally(&format!(
|
||||
"wasm.__wbindgen_free(ptr{i}, len{i} * {size});",
|
||||
i = i,
|
||||
size = kind.size(),
|
||||
));
|
||||
self.cx.require_internal_export("__wbindgen_free")
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,451 +0,0 @@
|
||||
//! Implementation of translating a `NonstandardOutgoing` expression to an
|
||||
//! actual JS shim and code snippet which ensures that bindings behave as we'd
|
||||
//! expect.
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::js::binding::JsBuilder;
|
||||
use crate::js::Context;
|
||||
use crate::webidl::NonstandardOutgoing;
|
||||
use anyhow::{bail, Error};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub struct Outgoing<'a, 'b> {
|
||||
cx: &'a mut Context<'b>,
|
||||
js: &'a mut JsBuilder,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Outgoing<'a, 'b> {
|
||||
pub fn new(cx: &'a mut Context<'b>, js: &'a mut JsBuilder) -> Outgoing<'a, 'b> {
|
||||
Outgoing { cx, js }
|
||||
}
|
||||
|
||||
pub fn process(&mut self, outgoing: &NonstandardOutgoing) -> Result<String, Error> {
|
||||
let before = self.js.typescript_len();
|
||||
let ret = self.nonstandard(outgoing)?;
|
||||
assert_eq!(before + 1, self.js.typescript_len());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
fn nonstandard(&mut self, outgoing: &NonstandardOutgoing) -> Result<String, Error> {
|
||||
match outgoing {
|
||||
NonstandardOutgoing::Standard(expr) => self.standard(expr),
|
||||
|
||||
// Converts the wasm argument, a single code unit, to a string.
|
||||
NonstandardOutgoing::Char { idx } => {
|
||||
self.js.typescript_required("string");
|
||||
Ok(format!("String.fromCodePoint({})", self.arg(*idx)))
|
||||
}
|
||||
|
||||
// Just need to wrap up the pointer we get from Rust into a JS type
|
||||
// and then we can pass that along
|
||||
NonstandardOutgoing::RustType { class, idx } => {
|
||||
self.js.typescript_required(class);
|
||||
self.cx.require_class_wrap(class);
|
||||
Ok(format!("{}.__wrap({})", class, self.arg(*idx)))
|
||||
}
|
||||
|
||||
// Just a small wrapper around `getObject`
|
||||
NonstandardOutgoing::BorrowedAnyref { idx } => {
|
||||
self.js.typescript_required("any");
|
||||
self.cx.expose_get_object();
|
||||
Ok(format!("getObject({})", self.arg(*idx)))
|
||||
}
|
||||
|
||||
// given the low/high bits we get from Rust, store them into a
|
||||
// temporary 64-bit conversion array and then load the BigInt out of
|
||||
// it.
|
||||
NonstandardOutgoing::Number64 {
|
||||
lo_idx,
|
||||
hi_idx,
|
||||
signed,
|
||||
} => {
|
||||
self.js.typescript_required("BigInt");
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"\
|
||||
u32CvtShim[0] = {low};
|
||||
u32CvtShim[1] = {high};
|
||||
const n{i} = {f}[0];
|
||||
",
|
||||
low = self.arg(*lo_idx),
|
||||
high = self.arg(*hi_idx),
|
||||
f = f,
|
||||
i = i,
|
||||
));
|
||||
Ok(format!("n{}", i))
|
||||
}
|
||||
|
||||
// Similar to `View` below, except using 64-bit types which don't
|
||||
// fit into webidl scalar types right now.
|
||||
NonstandardOutgoing::View64 {
|
||||
offset,
|
||||
length,
|
||||
signed,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
let kind = if *signed {
|
||||
VectorKind::I64
|
||||
} else {
|
||||
VectorKind::U64
|
||||
};
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
let f = self.cx.expose_get_vector_from_wasm(kind)?;
|
||||
Ok(format!("{}({}, {})", f, ptr, len))
|
||||
}
|
||||
|
||||
// Similar to `View` below, except using anyref types which have
|
||||
// fancy conversion functions on our end.
|
||||
NonstandardOutgoing::ViewAnyref { offset, length } => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
self.js.typescript_required(VectorKind::Anyref.js_ty());
|
||||
let f = self.cx.expose_get_vector_from_wasm(VectorKind::Anyref)?;
|
||||
Ok(format!("{}({}, {})", f, ptr, len))
|
||||
}
|
||||
|
||||
// Similar to `View` below, except we free the memory in JS right
|
||||
// now.
|
||||
//
|
||||
// TODO: we should free the memory in Rust to allow using standard
|
||||
// webidl bindings.
|
||||
NonstandardOutgoing::Vector {
|
||||
offset,
|
||||
length,
|
||||
kind,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
|
||||
let i = self.js.tmp();
|
||||
self.js
|
||||
.prelude(&format!("const v{} = {}({}, {}).slice();", i, f, ptr, len));
|
||||
self.prelude_free_vector(*offset, *length, *kind)?;
|
||||
Ok(format!("v{}", i))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::CachedString {
|
||||
offset,
|
||||
length,
|
||||
owned,
|
||||
optional,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
let tmp = self.js.tmp();
|
||||
|
||||
if *optional {
|
||||
self.js.typescript_optional("string");
|
||||
} else {
|
||||
self.js.typescript_required("string");
|
||||
}
|
||||
|
||||
self.cx.expose_get_cached_string_from_wasm()?;
|
||||
|
||||
self.js.prelude(&format!(
|
||||
"const v{} = getCachedStringFromWasm({}, {});",
|
||||
tmp, ptr, len
|
||||
));
|
||||
|
||||
if *owned {
|
||||
self.prelude_free_cached_string(&ptr, &len)?;
|
||||
}
|
||||
|
||||
Ok(format!("v{}", tmp))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::StackClosure {
|
||||
a,
|
||||
b,
|
||||
binding_idx,
|
||||
nargs,
|
||||
mutable,
|
||||
} => {
|
||||
self.js.typescript_optional("any");
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"const state{} = {{a: {}, b: {}}};",
|
||||
i,
|
||||
self.arg(*a),
|
||||
self.arg(*b),
|
||||
));
|
||||
let args = (0..*nargs)
|
||||
.map(|i| format!("arg{}", i))
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ");
|
||||
if *mutable {
|
||||
// Mutable closures need protection against being called
|
||||
// recursively, so ensure that we clear out one of the
|
||||
// internal pointers while it's being invoked.
|
||||
self.js.prelude(&format!(
|
||||
"const cb{i} = ({args}) => {{
|
||||
const a = state{i}.a;
|
||||
state{i}.a = 0;
|
||||
try {{
|
||||
return __wbg_elem_binding{idx}(a, state{i}.b, {args});
|
||||
}} finally {{
|
||||
state{i}.a = a;
|
||||
}}
|
||||
}};",
|
||||
i = i,
|
||||
args = args,
|
||||
idx = binding_idx,
|
||||
));
|
||||
} else {
|
||||
self.js.prelude(&format!(
|
||||
"const cb{i} = ({args}) => __wbg_elem_binding{idx}(state{i}.a, state{i}.b, {args});",
|
||||
i = i,
|
||||
args = args,
|
||||
idx = binding_idx,
|
||||
));
|
||||
}
|
||||
|
||||
// Make sure to null out our internal pointers when we return
|
||||
// back to Rust to ensure that any lingering references to the
|
||||
// closure will fail immediately due to null pointers passed in
|
||||
// to Rust.
|
||||
self.js.finally(&format!("state{}.a = state{0}.b = 0;", i));
|
||||
Ok(format!("cb{}", i))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionBool { idx } => {
|
||||
self.js.typescript_optional("boolean");
|
||||
Ok(format!(
|
||||
"{0} === 0xFFFFFF ? undefined : {0} !== 0",
|
||||
self.arg(*idx)
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionChar { idx } => {
|
||||
self.js.typescript_optional("string");
|
||||
Ok(format!(
|
||||
"{0} === 0xFFFFFF ? undefined : String.fromCodePoint({0})",
|
||||
self.arg(*idx)
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionIntegerEnum { idx, hole } => {
|
||||
self.js.typescript_optional("number");
|
||||
Ok(format!(
|
||||
"{0} === {1} ? undefined : {0}",
|
||||
self.arg(*idx),
|
||||
hole
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionRustType { class, idx } => {
|
||||
self.cx.require_class_wrap(class);
|
||||
self.js.typescript_optional(class);
|
||||
Ok(format!(
|
||||
"{0} === 0 ? undefined : {1}.__wrap({0})",
|
||||
self.arg(*idx),
|
||||
class,
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionU32Sentinel { idx } => {
|
||||
self.js.typescript_optional("number");
|
||||
Ok(format!(
|
||||
"{0} === 0xFFFFFF ? undefined : {0}",
|
||||
self.arg(*idx)
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionNative {
|
||||
signed,
|
||||
present,
|
||||
val,
|
||||
} => {
|
||||
self.js.typescript_optional("number");
|
||||
Ok(format!(
|
||||
"{} === 0 ? undefined : {}{}",
|
||||
self.arg(*present),
|
||||
self.arg(*val),
|
||||
if *signed { "" } else { " >>> 0" },
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionInt64 {
|
||||
present,
|
||||
_ignored,
|
||||
lo,
|
||||
hi,
|
||||
signed,
|
||||
} => {
|
||||
self.js.typescript_optional("BigInt");
|
||||
let f = if *signed {
|
||||
self.cx.expose_int64_cvt_shim()
|
||||
} else {
|
||||
self.cx.expose_uint64_cvt_shim()
|
||||
};
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!(
|
||||
"
|
||||
u32CvtShim[0] = {low};
|
||||
u32CvtShim[1] = {high};
|
||||
const n{i} = {present} === 0 ? undefined : {f}[0];
|
||||
",
|
||||
present = self.arg(*present),
|
||||
low = self.arg(*lo),
|
||||
high = self.arg(*hi),
|
||||
f = f,
|
||||
i = i,
|
||||
));
|
||||
Ok(format!("n{}", i))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionSlice {
|
||||
kind,
|
||||
offset,
|
||||
length,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
|
||||
Ok(format!(
|
||||
"{ptr} === 0 ? undefined : {f}({ptr}, {len})",
|
||||
ptr = ptr,
|
||||
len = len,
|
||||
f = f
|
||||
))
|
||||
}
|
||||
|
||||
NonstandardOutgoing::OptionVector {
|
||||
offset,
|
||||
length,
|
||||
kind,
|
||||
} => {
|
||||
let ptr = self.arg(*offset);
|
||||
let len = self.arg(*length);
|
||||
self.js.typescript_optional(kind.js_ty());
|
||||
let f = self.cx.expose_get_vector_from_wasm(*kind)?;
|
||||
let i = self.js.tmp();
|
||||
self.js.prelude(&format!("let v{};", i));
|
||||
self.js.prelude(&format!("if ({} !== 0) {{", ptr));
|
||||
self.js
|
||||
.prelude(&format!("v{} = {}({}, {}).slice();", i, f, ptr, len));
|
||||
self.prelude_free_vector(*offset, *length, *kind)?;
|
||||
self.js.prelude("}");
|
||||
Ok(format!("v{}", i))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Evaluates the `standard` binding expression, returning the JS expression
|
||||
/// needed to evaluate the binding.
|
||||
fn standard(&mut self, standard: &ast::OutgoingBindingExpression) -> Result<String, Error> {
|
||||
match standard {
|
||||
ast::OutgoingBindingExpression::As(expr) => match expr.ty {
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Any) => {
|
||||
self.js.typescript_required("any");
|
||||
if self.cx.config.anyref {
|
||||
Ok(self.arg(expr.idx))
|
||||
} else {
|
||||
self.cx.expose_take_object();
|
||||
Ok(format!("takeObject({})", self.arg(expr.idx)))
|
||||
}
|
||||
}
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::Boolean) => {
|
||||
self.js.typescript_required("boolean");
|
||||
Ok(format!("{} !== 0", self.arg(expr.idx)))
|
||||
}
|
||||
ast::WebidlTypeRef::Scalar(ast::WebidlScalarType::UnsignedLong) => {
|
||||
self.js.typescript_required("number");
|
||||
Ok(format!("{} >>> 0", self.arg(expr.idx)))
|
||||
}
|
||||
_ => {
|
||||
self.js.typescript_required("number");
|
||||
Ok(self.arg(expr.idx))
|
||||
}
|
||||
},
|
||||
ast::OutgoingBindingExpression::View(view) => {
|
||||
// TODO: deduplicate with same match statement in incoming
|
||||
// bindings
|
||||
let scalar = match view.ty {
|
||||
ast::WebidlTypeRef::Scalar(s) => s,
|
||||
ast::WebidlTypeRef::Id(_) => {
|
||||
bail!("unsupported type passed to `view` in webidl binding")
|
||||
}
|
||||
};
|
||||
let kind = match scalar {
|
||||
ast::WebidlScalarType::Int8Array => VectorKind::I8,
|
||||
ast::WebidlScalarType::Uint8Array => VectorKind::U8,
|
||||
ast::WebidlScalarType::Uint8ClampedArray => VectorKind::ClampedU8,
|
||||
ast::WebidlScalarType::Int16Array => VectorKind::I16,
|
||||
ast::WebidlScalarType::Uint16Array => VectorKind::U16,
|
||||
ast::WebidlScalarType::Int32Array => VectorKind::I32,
|
||||
ast::WebidlScalarType::Uint32Array => VectorKind::U32,
|
||||
ast::WebidlScalarType::Float32Array => VectorKind::F32,
|
||||
ast::WebidlScalarType::Float64Array => VectorKind::F64,
|
||||
_ => bail!("unsupported type passed to `view`: {:?}", scalar),
|
||||
};
|
||||
self.js.typescript_required(kind.js_ty());
|
||||
let ptr = self.arg(view.offset);
|
||||
let len = self.arg(view.length);
|
||||
let f = self.cx.expose_get_vector_from_wasm(kind)?;
|
||||
Ok(format!("{}({}, {})", f, ptr, len))
|
||||
}
|
||||
|
||||
ast::OutgoingBindingExpression::Utf8Str(expr) => {
|
||||
assert_eq!(expr.ty, ast::WebidlScalarType::DomString.into());
|
||||
self.js.typescript_required("string");
|
||||
let ptr = self.arg(expr.offset);
|
||||
let len = self.arg(expr.length);
|
||||
self.cx.expose_get_string_from_wasm()?;
|
||||
Ok(format!("getStringFromWasm({}, {})", ptr, len))
|
||||
}
|
||||
|
||||
ast::OutgoingBindingExpression::Utf8CStr(_) => {
|
||||
bail!("unsupported `utf8-cstr` found in outgoing webidl bindings");
|
||||
}
|
||||
ast::OutgoingBindingExpression::I32ToEnum(_) => {
|
||||
bail!("unsupported `i32-to-enum` found in outgoing webidl bindings");
|
||||
}
|
||||
ast::OutgoingBindingExpression::Copy(_) => {
|
||||
bail!("unsupported `copy` found in outgoing webidl bindings");
|
||||
}
|
||||
ast::OutgoingBindingExpression::Dict(_) => {
|
||||
bail!("unsupported `dict` found in outgoing webidl bindings");
|
||||
}
|
||||
ast::OutgoingBindingExpression::BindExport(_) => {
|
||||
bail!("unsupported `bind-export` found in outgoing webidl bindings");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn arg(&self, idx: u32) -> String {
|
||||
self.js.arg(idx).to_string()
|
||||
}
|
||||
|
||||
fn prelude_free_vector(
|
||||
&mut self,
|
||||
offset: u32,
|
||||
length: u32,
|
||||
kind: VectorKind,
|
||||
) -> Result<(), Error> {
|
||||
self.js.prelude(&format!(
|
||||
"wasm.__wbindgen_free({0}, {1} * {size});",
|
||||
self.arg(offset),
|
||||
self.arg(length),
|
||||
size = kind.size(),
|
||||
));
|
||||
self.cx.require_internal_export("__wbindgen_free")
|
||||
}
|
||||
|
||||
fn prelude_free_cached_string(&mut self, ptr: &str, len: &str) -> Result<(), Error> {
|
||||
self.js.prelude(&format!(
|
||||
"if ({ptr} !== 0) {{ wasm.__wbindgen_free({ptr}, {len}); }}",
|
||||
ptr = ptr,
|
||||
len = len,
|
||||
));
|
||||
|
||||
self.cx.require_internal_export("__wbindgen_free")
|
||||
}
|
||||
}
|
@ -16,8 +16,9 @@ mod descriptor;
|
||||
mod descriptors;
|
||||
mod intrinsic;
|
||||
mod js;
|
||||
mod multivalue;
|
||||
pub mod wasm2es6js;
|
||||
mod webidl;
|
||||
mod wit;
|
||||
|
||||
pub struct Bindgen {
|
||||
input: Input,
|
||||
@ -268,7 +269,7 @@ impl Bindgen {
|
||||
.generate_dwarf(self.keep_debug)
|
||||
.generate_name_section(!self.remove_name_section)
|
||||
.generate_producers_section(!self.remove_producers_section)
|
||||
.on_parse(wasm_webidl_bindings::binary::on_parse)
|
||||
.on_parse(wit_walrus::on_parse)
|
||||
.parse(&contents)
|
||||
.context("failed to parse input file as wasm")?;
|
||||
let stem = match &self.out_name {
|
||||
@ -329,11 +330,11 @@ impl Bindgen {
|
||||
|
||||
// Process and remove our raw custom sections emitted by the
|
||||
// #[wasm_bindgen] macro and the compiler. In their stead insert a
|
||||
// forward-compatible WebIDL bindings section (forward-compatible with
|
||||
// the webidl bindings proposal) as well as an auxiliary section for all
|
||||
// sorts of miscellaneous information and features #[wasm_bindgen]
|
||||
// supports that aren't covered by WebIDL bindings.
|
||||
webidl::process(
|
||||
// forward-compatible wasm interface types section as well as an
|
||||
// auxiliary section for all sorts of miscellaneous information and
|
||||
// features #[wasm_bindgen] supports that aren't covered by wasm
|
||||
// interface types.
|
||||
wit::process(
|
||||
&mut module,
|
||||
self.anyref,
|
||||
self.wasm_interface_types,
|
||||
@ -346,34 +347,32 @@ impl Bindgen {
|
||||
// currently off-by-default since `anyref` is still in development in
|
||||
// engines.
|
||||
if self.anyref {
|
||||
anyref::process(&mut module, self.wasm_interface_types)?;
|
||||
anyref::process(&mut module)?;
|
||||
}
|
||||
|
||||
let aux = module
|
||||
.customs
|
||||
.delete_typed::<webidl::WasmBindgenAux>()
|
||||
.delete_typed::<wit::WasmBindgenAux>()
|
||||
.expect("aux section should be present");
|
||||
let mut bindings = module
|
||||
let mut adapters = module
|
||||
.customs
|
||||
.delete_typed::<webidl::NonstandardWebidlSection>()
|
||||
.delete_typed::<wit::NonstandardWitSection>()
|
||||
.unwrap();
|
||||
|
||||
// Now that our module is massaged and good to go, feed it into the JS
|
||||
// shim generation which will actually generate JS for all this.
|
||||
let (npm_dependencies, (js, ts)) = {
|
||||
let mut cx = js::Context::new(&mut module, self)?;
|
||||
cx.generate(&aux, &bindings)?;
|
||||
let mut cx = js::Context::new(&mut module, self, &adapters, &aux)?;
|
||||
cx.generate()?;
|
||||
let npm_dependencies = cx.npm_dependencies.clone();
|
||||
(npm_dependencies, cx.finalize(stem)?)
|
||||
};
|
||||
|
||||
if self.wasm_interface_types {
|
||||
if self.multi_value {
|
||||
webidl::standard::add_multi_value(&mut module, &mut bindings)
|
||||
.context("failed to transform return pointers into multi-value Wasm")?;
|
||||
}
|
||||
webidl::standard::add_section(&mut module, &aux, &bindings)
|
||||
.with_context(|| "failed to generate a standard wasm bindings custom section")?;
|
||||
multivalue::run(&mut module, &mut adapters)
|
||||
.context("failed to transform return pointers into multi-value Wasm")?;
|
||||
wit::section::add(&mut module, &aux, &adapters)
|
||||
.context("failed to generate a standard wasm bindings custom section")?;
|
||||
} else {
|
||||
if self.multi_value {
|
||||
anyhow::bail!(
|
||||
|
77
crates/cli-support/src/multivalue.rs
Normal file
77
crates/cli-support/src/multivalue.rs
Normal file
@ -0,0 +1,77 @@
|
||||
use crate::wit::{Adapter, NonstandardWitSection};
|
||||
use crate::wit::{AdapterKind, Instruction};
|
||||
use anyhow::Error;
|
||||
use walrus::Module;
|
||||
use wasm_bindgen_multi_value_xform as multi_value_xform;
|
||||
use wasm_bindgen_wasm_conventions as wasm_conventions;
|
||||
|
||||
pub fn run(module: &mut Module, adapters: &mut NonstandardWitSection) -> Result<(), Error> {
|
||||
let mut to_xform = Vec::new();
|
||||
let mut slots = Vec::new();
|
||||
|
||||
for (_, adapter) in adapters.adapters.iter_mut() {
|
||||
extract_xform(adapter, &mut to_xform, &mut slots);
|
||||
}
|
||||
if to_xform.is_empty() {
|
||||
// Early exit to avoid failing if we don't have a memory or shadow stack
|
||||
// pointer because this is a minimal module that doesn't use linear
|
||||
// memory.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?;
|
||||
let memory = wasm_conventions::get_memory(module)?;
|
||||
let wrappers = multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?;
|
||||
|
||||
for (slot, id) in slots.into_iter().zip(wrappers) {
|
||||
*slot = id;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_xform<'a>(
|
||||
adapter: &'a mut Adapter,
|
||||
to_xform: &mut Vec<(walrus::FunctionId, usize, Vec<walrus::ValType>)>,
|
||||
slots: &mut Vec<&'a mut walrus::FunctionId>,
|
||||
) {
|
||||
let instructions = match &mut adapter.kind {
|
||||
AdapterKind::Local { instructions } => instructions,
|
||||
AdapterKind::Import { .. } => return,
|
||||
};
|
||||
|
||||
// If the first instruction is a `Retptr`, then this must be an exported
|
||||
// adapter which calls a wasm-defined function. Something we'd like to
|
||||
// adapt to multi-value!
|
||||
if let Some(Instruction::Retptr) = instructions.first().map(|e| &e.instr) {
|
||||
instructions.remove(0);
|
||||
let mut types = Vec::new();
|
||||
instructions.retain(|instruction| match instruction.instr {
|
||||
Instruction::LoadRetptr { ty, .. } => {
|
||||
types.push(ty.to_wasm().unwrap());
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
});
|
||||
let id = instructions
|
||||
.iter_mut()
|
||||
.filter_map(|i| match &mut i.instr {
|
||||
Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(f),
|
||||
_ => None,
|
||||
})
|
||||
.next()
|
||||
.expect("should have found call-core");
|
||||
|
||||
// LLVM currently always uses the first parameter for the return
|
||||
// pointer. We hard code that here, since we have no better option.
|
||||
to_xform.push((*id, 0, types));
|
||||
slots.push(id);
|
||||
return;
|
||||
}
|
||||
|
||||
// If the last instruction is a `StoreRetptr`, then this must be an adapter
|
||||
// which calls an imported function.
|
||||
//
|
||||
// FIXME(#1872) handle this
|
||||
// if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {}
|
||||
}
|
@ -1,250 +0,0 @@
|
||||
//! Location where `Binding` structures are actually created.
|
||||
//!
|
||||
//! This module is tasked with converting `Descriptor::Function` instances to
|
||||
//! `Binding`s. It uses the incoming/outgoing modules/builders to do most of the
|
||||
//! heavy lifting, and then this is the glue around the edges to make sure
|
||||
//! everything is processed, hooked up in the second, and then inserted into the
|
||||
//! right map.
|
||||
//!
|
||||
//! This module is called from `src/webidl/mod.rs` exclusively to populate the
|
||||
//! imports/exports/elements of the bindings section. Most of this module is
|
||||
//! largely just connecting the dots!
|
||||
|
||||
use crate::descriptor::Function;
|
||||
use crate::webidl::incoming::IncomingBuilder;
|
||||
use crate::webidl::outgoing::OutgoingBuilder;
|
||||
use crate::webidl::{Binding, NonstandardWebidlSection};
|
||||
use anyhow::{format_err, Error};
|
||||
use walrus::{FunctionId, Module, ValType};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
/// Adds an element to the `bindings.imports` map for the `import` specified
|
||||
/// that is supposed to have the signature specified in `binding`. This also
|
||||
/// expects that the imported item is called as `kind`.
|
||||
pub fn register_import(
|
||||
module: &mut Module,
|
||||
bindings: &mut NonstandardWebidlSection,
|
||||
import: walrus::ImportId,
|
||||
binding: Function,
|
||||
kind: ast::WebidlFunctionKind,
|
||||
) -> Result<(), Error> {
|
||||
let import = module.imports.get(import);
|
||||
let id = match import.kind {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let import_id = import.id();
|
||||
|
||||
// Process the return value first to determine if we need a return pointer
|
||||
// since that is always the first argument.
|
||||
let mut incoming = IncomingBuilder::default();
|
||||
incoming.process(&binding.ret)?;
|
||||
|
||||
// Next process all outgoing arguments, and configure the module/bindings
|
||||
// section to be available to the builder so we can recursively register
|
||||
// stack closures.
|
||||
let mut outgoing = OutgoingBuilder::default();
|
||||
outgoing.module = Some(module);
|
||||
outgoing.bindings_section = Some(bindings);
|
||||
if incoming.wasm.len() > 1 {
|
||||
outgoing.process_retptr();
|
||||
}
|
||||
for arg in binding.arguments.iter() {
|
||||
outgoing.process(arg)?;
|
||||
}
|
||||
|
||||
// A bit of destructuring to kill the borrow that the outgoing builder has
|
||||
// on the module/bindings.
|
||||
let OutgoingBuilder {
|
||||
wasm: outgoing_wasm,
|
||||
webidl: outgoing_webidl,
|
||||
bindings: outgoing_bindings,
|
||||
..
|
||||
} = outgoing;
|
||||
|
||||
// Boilerplate to assemble the `webidl_ty` and `wasm_ty` values.
|
||||
let webidl_ty = webidl_ty(
|
||||
&mut bindings.types,
|
||||
kind,
|
||||
&outgoing_webidl,
|
||||
&incoming.webidl,
|
||||
);
|
||||
let (wasm_ty, return_via_outptr) =
|
||||
assert_signature_match(module, id, &outgoing_wasm, &incoming.wasm);
|
||||
|
||||
// ... and finally insert it into our map!
|
||||
bindings.imports.insert(
|
||||
import_id,
|
||||
Binding {
|
||||
return_via_outptr,
|
||||
wasm_ty,
|
||||
incoming: incoming.bindings,
|
||||
outgoing: outgoing_bindings,
|
||||
webidl_ty,
|
||||
},
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Adds an element to `bindings.exports` for the `export` specified to have the
|
||||
/// `binding` given.
|
||||
pub fn register_export(
|
||||
module: &mut Module,
|
||||
bindings: &mut NonstandardWebidlSection,
|
||||
export: walrus::ExportId,
|
||||
binding: Function,
|
||||
) -> Result<(), Error> {
|
||||
let export = module.exports.get(export);
|
||||
let id = match export.item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let export_id = export.id();
|
||||
// Do the actual heavy lifting elsewhere to generate the `binding`.
|
||||
let binding = register_wasm_export(module, bindings, id, binding)?;
|
||||
bindings.exports.insert(export_id, binding);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Like `register_export` except registers a binding for a table element. In
|
||||
/// this case ensures that the table element `idx` is specified to have the
|
||||
/// `binding` signature specified, eventually updating `bindings.elems` list.
|
||||
///
|
||||
/// Returns the index of the item added in the `bindings.elems` list.
|
||||
pub fn register_table_element(
|
||||
module: &mut Module,
|
||||
bindings: &mut NonstandardWebidlSection,
|
||||
idx: u32,
|
||||
binding: Function,
|
||||
) -> Result<u32, Error> {
|
||||
let table = module
|
||||
.tables
|
||||
.main_function_table()?
|
||||
.ok_or_else(|| format_err!("no function table found"))?;
|
||||
let table = module.tables.get(table);
|
||||
let functions = match &table.kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
let id = functions.elements[idx as usize].unwrap();
|
||||
let ret = bindings.elems.len() as u32;
|
||||
// like above, largely just defer the work elsewhere
|
||||
let binding = register_wasm_export(module, bindings, id, binding)?;
|
||||
bindings.elems.push((idx, binding));
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Common routine to create a `Binding` for an exported wasm function, using
|
||||
/// incoming arguments and an outgoing return value.
|
||||
fn register_wasm_export(
|
||||
module: &mut Module,
|
||||
bindings: &mut NonstandardWebidlSection,
|
||||
id: walrus::FunctionId,
|
||||
binding: Function,
|
||||
) -> Result<Binding, Error> {
|
||||
// Like imports, process the return value first to determine if we need a
|
||||
// return pointer
|
||||
let mut outgoing = OutgoingBuilder::default();
|
||||
outgoing.process(&binding.ret)?;
|
||||
|
||||
// Afterwards process all arguments...
|
||||
let mut incoming = IncomingBuilder::default();
|
||||
if outgoing.wasm.len() > 1 {
|
||||
incoming.process_retptr();
|
||||
}
|
||||
for arg in binding.arguments.iter() {
|
||||
incoming.process(arg)?;
|
||||
}
|
||||
|
||||
// ... do similar boilerplate to imports (but with incoming/outgoing
|
||||
// swapped) to produce some types ...
|
||||
let webidl_ty = webidl_ty(
|
||||
&mut bindings.types,
|
||||
ast::WebidlFunctionKind::Static,
|
||||
&incoming.webidl,
|
||||
&outgoing.webidl,
|
||||
);
|
||||
let (wasm_ty, return_via_outptr) =
|
||||
assert_signature_match(module, id, &incoming.wasm, &outgoing.wasm);
|
||||
|
||||
// ... and there's our `Binding`!
|
||||
Ok(Binding {
|
||||
wasm_ty,
|
||||
incoming: incoming.bindings,
|
||||
outgoing: outgoing.bindings,
|
||||
webidl_ty,
|
||||
return_via_outptr,
|
||||
})
|
||||
}
|
||||
|
||||
/// Asserts that the `params` and `results` we've determined from an
|
||||
/// incoming/outgoing builder actually matches the signature of `id` in the
|
||||
/// `module` provided. This is a somewhat loose comparison since `anyref` in the
|
||||
/// expected lists will be present as `i32` in the actual module due to rustc
|
||||
/// limitations.
|
||||
///
|
||||
/// This at the end manufactures an actual `walrus::Type` that will be used to
|
||||
/// describe a WebIDL value. This manufactured value actually has `anyref` types
|
||||
/// in it and also respects the out ptr ABI that we currently use to handle
|
||||
/// multiple-value returns.
|
||||
fn assert_signature_match(
|
||||
module: &mut Module,
|
||||
id: FunctionId,
|
||||
params: &[ValType],
|
||||
mut results: &[ValType],
|
||||
) -> (walrus::TypeId, Option<Vec<walrus::ValType>>) {
|
||||
let ty = module.funcs.get(id).ty();
|
||||
let ty = module.types.get(ty);
|
||||
|
||||
fn assert_eq(expected: ValType, actual: ValType) {
|
||||
match expected {
|
||||
ValType::Anyref => assert_eq!(actual, ValType::I32),
|
||||
_ => assert_eq!(expected, actual),
|
||||
}
|
||||
}
|
||||
let mut ret_outptr = None;
|
||||
|
||||
match results.len() {
|
||||
0 => assert_eq!(ty.results().len(), 0),
|
||||
1 => assert_eq(results[0], ty.results()[0]),
|
||||
|
||||
// multi value isn't supported yet so all aggregate returns are done
|
||||
// through an outptr as the first argument. This means that our
|
||||
// signature should have no results. The new signature we create will
|
||||
// also have no results.
|
||||
_ => {
|
||||
assert_eq!(ty.results().len(), 0);
|
||||
ret_outptr = Some(results.to_vec());
|
||||
results = &[];
|
||||
}
|
||||
}
|
||||
|
||||
let mut iter = params.iter();
|
||||
for actual in ty.params().iter() {
|
||||
let expected = iter.next().unwrap();
|
||||
assert_eq(*expected, *actual);
|
||||
}
|
||||
assert!(iter.next().is_none());
|
||||
|
||||
(module.types.add(params, results), ret_outptr)
|
||||
}
|
||||
|
||||
// boilerplate to convert arguments to a `WebidlFunctionId`.
|
||||
fn webidl_ty(
|
||||
types: &mut ast::WebidlTypes,
|
||||
kind: ast::WebidlFunctionKind,
|
||||
params: &[ast::WebidlScalarType],
|
||||
results: &[ast::WebidlScalarType],
|
||||
) -> ast::WebidlFunctionId {
|
||||
let result = match results.len() {
|
||||
0 => None,
|
||||
1 => Some(results[0].into()),
|
||||
_ => panic!("too many results in a webidl return value"),
|
||||
};
|
||||
let func = ast::WebidlFunction {
|
||||
kind,
|
||||
params: params.iter().cloned().map(|x| x.into()).collect(),
|
||||
result,
|
||||
};
|
||||
types.insert(func)
|
||||
}
|
@ -1,492 +0,0 @@
|
||||
//! Nonstandard and wasm-bindgen specific definition of incoming bindings to a
|
||||
//! wasm module.
|
||||
//!
|
||||
//! This module provides a builder which is used to translate Rust types (aka a
|
||||
//! `Descriptor`) to a `NonstandardIncoming` definition which describes how the
|
||||
//! JS type is converted into a Rust type. We try to use standard webidl
|
||||
//! bindings as much as possible, but we have quite a few other bindings which
|
||||
//! require custom code and shims currently still.
|
||||
//!
|
||||
//! Note that the mirror operation, going from WebAssembly to JS, is found in
|
||||
//! the `outgoing.rs` module.
|
||||
|
||||
use crate::descriptor::{Descriptor, VectorKind};
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use walrus::ValType;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
/// A list of all incoming bindings from JS to WebAssembly that wasm-bindgen
|
||||
/// will take advantage of.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NonstandardIncoming {
|
||||
/// This is a standard vanilla incoming binding. When WebIDL bindings are
|
||||
/// implemented, this can be used as-is.
|
||||
Standard(ast::IncomingBindingExpression),
|
||||
|
||||
/// JS is passing a `BigInt` to Rust.
|
||||
Int64 {
|
||||
val: ast::IncomingBindingExpression,
|
||||
/// Whether it's a `u64` or `i64` in Rust.
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// JS is passing a `BigInt64Array` or `BigUint64Array` to Rust
|
||||
///
|
||||
/// A copy of the array needs to be made into the Rust address space.
|
||||
AllocCopyInt64 {
|
||||
alloc_func_name: String,
|
||||
expr: Box<ast::IncomingBindingExpression>,
|
||||
/// Whether or not this is for &[u64] or &[i64]
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// JS is passing an array of anyref values into Rust, and all the values
|
||||
/// need to be copied in.
|
||||
AllocCopyAnyrefArray {
|
||||
alloc_func_name: String,
|
||||
expr: Box<ast::IncomingBindingExpression>,
|
||||
},
|
||||
|
||||
/// A mutable slice of values going from JS to Rust, and after Rust finishes
|
||||
/// the JS slice is updated with the current value of the slice.
|
||||
MutableSlice {
|
||||
kind: VectorKind,
|
||||
val: ast::IncomingBindingExpression,
|
||||
},
|
||||
|
||||
/// This is either a slice or `undefined` being passed into Rust.
|
||||
OptionSlice {
|
||||
kind: VectorKind,
|
||||
val: ast::IncomingBindingExpression,
|
||||
mutable: bool,
|
||||
},
|
||||
|
||||
/// This is either a vector or `undefined` being passed into Rust.
|
||||
OptionVector {
|
||||
kind: VectorKind,
|
||||
val: ast::IncomingBindingExpression,
|
||||
},
|
||||
|
||||
/// Not actually used for `JsValue` but used for imported types, this is
|
||||
/// either `undefined` or the imported type getting passed into Rust.
|
||||
OptionAnyref { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An optional "native type" which includes i32/u32/f32/f64, all of which
|
||||
/// require a discriminant.
|
||||
OptionNative { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An optional integer type which uses an 0xffffff sentinel value for
|
||||
/// "none"
|
||||
OptionU32Sentinel { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An optional boolean using a special ABI for communicating `undefined`
|
||||
OptionBool { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An optional `char` which uses an ABI where `undefined` is a hole in the
|
||||
/// range of valid values for a `char` in Rust. Note that in JS a string is
|
||||
/// passed in.
|
||||
OptionChar { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An optional integral enum where `undefined` is the hole specified.
|
||||
OptionIntegerEnum {
|
||||
val: ast::IncomingBindingExpression,
|
||||
hole: u32,
|
||||
},
|
||||
|
||||
/// An optional `BigInt`.
|
||||
OptionInt64 {
|
||||
val: ast::IncomingBindingExpression,
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// An optional Rust-based type which internally has a pointer that's
|
||||
/// wrapped up in a JS class. This transfers ownership from JS to Rust.
|
||||
RustType {
|
||||
class: String,
|
||||
val: ast::IncomingBindingExpression,
|
||||
},
|
||||
|
||||
/// A reference to a Rust-based type where Rust won't take ownership of the
|
||||
/// value, it just has a temporary borrow on the input.
|
||||
RustTypeRef {
|
||||
class: String,
|
||||
val: ast::IncomingBindingExpression,
|
||||
},
|
||||
|
||||
/// An optional owned Rust type being transferred from JS to Rust.
|
||||
OptionRustType {
|
||||
class: String,
|
||||
val: ast::IncomingBindingExpression,
|
||||
},
|
||||
|
||||
/// A string from JS where the first character goes through to Rust.
|
||||
Char { val: ast::IncomingBindingExpression },
|
||||
|
||||
/// An arbitrary `anyref` being passed into Rust, but explicitly one that's
|
||||
/// borrowed and doesn't need to be persisted in a heap table.
|
||||
BorrowedAnyref { val: ast::IncomingBindingExpression },
|
||||
}
|
||||
|
||||
/// Builder used to create a incomig binding from a `Descriptor`.
|
||||
#[derive(Default)]
|
||||
pub struct IncomingBuilder {
|
||||
/// The wasm types that needs to be used to represent all the descriptors in
|
||||
/// Rust.
|
||||
pub wasm: Vec<ValType>,
|
||||
/// The WebIDL scalar types which match what JS will be providing.
|
||||
pub webidl: Vec<ast::WebidlScalarType>,
|
||||
/// The list of bindings necessary to connect `wasm` to `webidl` above.
|
||||
pub bindings: Vec<NonstandardIncoming>,
|
||||
}
|
||||
|
||||
impl IncomingBuilder {
|
||||
/// Adds an initial argument which is passed through verbatim, currently
|
||||
/// used to handle return pointers in Rust.
|
||||
pub fn process_retptr(&mut self) {
|
||||
self.number(ValType::I32, ast::WebidlScalarType::Long);
|
||||
}
|
||||
|
||||
/// Process a `Descriptor` as if it's being passed from JS to Rust. This
|
||||
/// will skip `Unit` and otherwise internally add a `NonstandardIncoming`
|
||||
/// binding necessary for the descriptor.
|
||||
pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
if let Descriptor::Unit = arg {
|
||||
return Ok(());
|
||||
}
|
||||
// This is a wrapper around `_process` to have a number of sanity checks
|
||||
// that we don't forget things. We should always produce at least one
|
||||
// wasm arge and exactly one webidl arg. Additionally the number of
|
||||
// bindings should always match the number of webidl types for now.
|
||||
assert_eq!(self.webidl.len(), self.bindings.len());
|
||||
let wasm_before = self.wasm.len();
|
||||
let webidl_before = self.webidl.len();
|
||||
self._process(arg)?;
|
||||
assert_eq!(self.webidl.len(), self.bindings.len());
|
||||
assert_eq!(webidl_before + 1, self.webidl.len());
|
||||
assert!(wasm_before < self.wasm.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Boolean => {
|
||||
let binding = self.expr_as(ValType::I32);
|
||||
self.wasm.push(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Boolean);
|
||||
self.bindings.push(NonstandardIncoming::Standard(binding));
|
||||
}
|
||||
Descriptor::Char => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::DomString);
|
||||
self.bindings.push(NonstandardIncoming::Char { val: expr });
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
let expr = self.expr_as(ValType::Anyref);
|
||||
self.wasm.push(ValType::Anyref);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardIncoming::Standard(expr));
|
||||
}
|
||||
Descriptor::RustStruct(class) => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardIncoming::RustType {
|
||||
val: expr,
|
||||
class: class.to_string(),
|
||||
});
|
||||
}
|
||||
Descriptor::I8 => self.number(ValType::I32, ast::WebidlScalarType::Byte),
|
||||
Descriptor::U8 => self.number(ValType::I32, ast::WebidlScalarType::Octet),
|
||||
Descriptor::I16 => self.number(ValType::I32, ast::WebidlScalarType::Short),
|
||||
Descriptor::U16 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedShort),
|
||||
Descriptor::I32 => self.number(ValType::I32, ast::WebidlScalarType::Long),
|
||||
Descriptor::U32 => self.number(ValType::I32, ast::WebidlScalarType::UnsignedLong),
|
||||
Descriptor::I64 => self.number64(true),
|
||||
Descriptor::U64 => self.number64(false),
|
||||
Descriptor::F32 => self.number(ValType::F32, ast::WebidlScalarType::Float),
|
||||
Descriptor::F64 => self.number(ValType::F64, ast::WebidlScalarType::Double),
|
||||
Descriptor::Enum { .. } => self.number(ValType::I32, ast::WebidlScalarType::Long),
|
||||
Descriptor::Ref(d) => self.process_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.process_ref(true, d)?,
|
||||
Descriptor::Option(d) => self.process_option(d)?,
|
||||
|
||||
Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!("unsupported argument type for calling Rust function from JS {:?}", arg)
|
||||
})? ;
|
||||
self.wasm.extend(&[ValType::I32; 2]);
|
||||
self.alloc_copy_kind(kind)
|
||||
}
|
||||
|
||||
// Can't be passed from JS to Rust yet
|
||||
Descriptor::Function(_) |
|
||||
Descriptor::Closure(_) |
|
||||
|
||||
// Always behind a `Ref`
|
||||
Descriptor::Slice(_) => bail!(
|
||||
"unsupported argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
|
||||
// nothing to do
|
||||
Descriptor::Unit => {}
|
||||
|
||||
// Largely synthetic and can't show up
|
||||
Descriptor::ClampedU8 => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::RustStruct(class) => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardIncoming::RustTypeRef {
|
||||
val: expr,
|
||||
class: class.to_string(),
|
||||
});
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::Anyref);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::BorrowedAnyref { val: expr });
|
||||
}
|
||||
Descriptor::String | Descriptor::CachedString | Descriptor::Slice(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported slice type for calling Rust function from JS {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
self.wasm.extend(&[ValType::I32; 2]);
|
||||
if mutable {
|
||||
self.bindings.push(NonstandardIncoming::MutableSlice {
|
||||
kind,
|
||||
val: self.expr_get(),
|
||||
});
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
} else {
|
||||
self.alloc_copy_kind(kind)
|
||||
}
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported reference argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
self.wasm.push(ValType::I32);
|
||||
self.bindings.push(NonstandardIncoming::OptionAnyref {
|
||||
val: self.expr_get(),
|
||||
});
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
}
|
||||
Descriptor::I8 => self.option_sentinel(),
|
||||
Descriptor::U8 => self.option_sentinel(),
|
||||
Descriptor::I16 => self.option_sentinel(),
|
||||
Descriptor::U16 => self.option_sentinel(),
|
||||
Descriptor::I32 => self.option_native(ValType::I32),
|
||||
Descriptor::U32 => self.option_native(ValType::I32),
|
||||
Descriptor::F32 => self.option_native(ValType::F32),
|
||||
Descriptor::F64 => self.option_native(ValType::F64),
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let expr = self.expr_get();
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.wasm.extend(&[walrus::ValType::I32; 4]);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::OptionInt64 { val: expr, signed });
|
||||
}
|
||||
Descriptor::Boolean => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(walrus::ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::OptionBool { val: expr });
|
||||
}
|
||||
Descriptor::Char => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(walrus::ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::OptionChar { val: expr });
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(walrus::ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardIncoming::OptionIntegerEnum {
|
||||
val: expr,
|
||||
hole: *hole,
|
||||
});
|
||||
}
|
||||
Descriptor::RustStruct(name) => {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(walrus::ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardIncoming::OptionRustType {
|
||||
val: expr,
|
||||
class: name.to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
Descriptor::Ref(_) | Descriptor::RefMut(_) => {
|
||||
let mutable = match arg {
|
||||
Descriptor::Ref(_) => false,
|
||||
_ => true,
|
||||
};
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling Rust function from JS {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
self.bindings.push(NonstandardIncoming::OptionSlice {
|
||||
kind,
|
||||
val: self.expr_get(),
|
||||
mutable,
|
||||
});
|
||||
self.wasm.extend(&[ValType::I32; 2]);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
}
|
||||
|
||||
Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling Rust function from JS {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
self.bindings.push(NonstandardIncoming::OptionVector {
|
||||
kind,
|
||||
val: self.expr_get(),
|
||||
});
|
||||
self.wasm.extend(&[ValType::I32; 2]);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported optional argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn expr_get(&self) -> ast::IncomingBindingExpression {
|
||||
let idx = self.webidl.len() as u32;
|
||||
ast::IncomingBindingExpressionGet { idx }.into()
|
||||
}
|
||||
|
||||
fn expr_as(&self, ty: ValType) -> ast::IncomingBindingExpression {
|
||||
ast::IncomingBindingExpressionAs {
|
||||
ty,
|
||||
expr: Box::new(self.expr_get()),
|
||||
}
|
||||
.into()
|
||||
}
|
||||
|
||||
fn alloc_func_name(&self) -> String {
|
||||
"__wbindgen_malloc".to_string()
|
||||
}
|
||||
|
||||
fn alloc_copy_kind(&mut self, kind: VectorKind) {
|
||||
use wasm_webidl_bindings::ast::WebidlScalarType::*;
|
||||
|
||||
match kind {
|
||||
VectorKind::I8 => self.alloc_copy(Int8Array),
|
||||
VectorKind::U8 => self.alloc_copy(Uint8Array),
|
||||
VectorKind::ClampedU8 => self.alloc_copy(Uint8ClampedArray),
|
||||
VectorKind::I16 => self.alloc_copy(Int16Array),
|
||||
VectorKind::U16 => self.alloc_copy(Uint16Array),
|
||||
VectorKind::I32 => self.alloc_copy(Int32Array),
|
||||
VectorKind::U32 => self.alloc_copy(Uint32Array),
|
||||
VectorKind::F32 => self.alloc_copy(Float32Array),
|
||||
VectorKind::F64 => self.alloc_copy(Float64Array),
|
||||
VectorKind::String => {
|
||||
let expr = ast::IncomingBindingExpressionAllocUtf8Str {
|
||||
alloc_func_name: self.alloc_func_name(),
|
||||
expr: Box::new(self.expr_get()),
|
||||
};
|
||||
self.webidl.push(DomString);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::Standard(expr.into()));
|
||||
}
|
||||
VectorKind::I64 | VectorKind::U64 => {
|
||||
let signed = match kind {
|
||||
VectorKind::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.bindings.push(NonstandardIncoming::AllocCopyInt64 {
|
||||
alloc_func_name: self.alloc_func_name(),
|
||||
expr: Box::new(self.expr_get()),
|
||||
signed,
|
||||
});
|
||||
self.webidl.push(Any);
|
||||
}
|
||||
VectorKind::Anyref => {
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::AllocCopyAnyrefArray {
|
||||
alloc_func_name: self.alloc_func_name(),
|
||||
expr: Box::new(self.expr_get()),
|
||||
});
|
||||
self.webidl.push(Any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc_copy(&mut self, webidl: ast::WebidlScalarType) {
|
||||
let expr = ast::IncomingBindingExpressionAllocCopy {
|
||||
alloc_func_name: self.alloc_func_name(),
|
||||
expr: Box::new(self.expr_get()),
|
||||
};
|
||||
self.webidl.push(webidl);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::Standard(expr.into()));
|
||||
}
|
||||
|
||||
fn number(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) {
|
||||
let binding = self.expr_as(wasm);
|
||||
self.wasm.push(wasm);
|
||||
self.webidl.push(webidl);
|
||||
self.bindings.push(NonstandardIncoming::Standard(binding));
|
||||
}
|
||||
|
||||
fn number64(&mut self, signed: bool) {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.extend(&[ValType::I32; 2]);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::Int64 { val: expr, signed });
|
||||
}
|
||||
|
||||
fn option_native(&mut self, wasm: ValType) {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::I32);
|
||||
self.wasm.push(wasm);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::OptionNative { val: expr });
|
||||
}
|
||||
|
||||
fn option_sentinel(&mut self) {
|
||||
let expr = self.expr_get();
|
||||
self.wasm.push(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardIncoming::OptionU32Sentinel { val: expr });
|
||||
}
|
||||
}
|
@ -1,556 +0,0 @@
|
||||
//! This module is used to define `NonstandardOutgoing`, a list of possible ways
|
||||
//! values in Rust can be passed to JS.
|
||||
//!
|
||||
//! Like the `NonstandardIncoming` list we attempt to use a standard
|
||||
//! `OutgoingBindingExpression` wherever possible but we naturally have a lot of
|
||||
//! features in `wasm-bindgen` which haven't been upstreamed into the WebIDL
|
||||
//! bindings standard yet (nor which are likely to ever get standardized). We
|
||||
//! attempt to use standard bindings aggressively and wherever possible, but
|
||||
//! sometimes we need to resort to our own custom bindings with our own custom
|
||||
//! JS shims for now.
|
||||
//!
|
||||
//! This module also houses the definition of converting a `Descriptor` to a
|
||||
//! `NonstandardOutgoing` binding, effectively defining how to translate from a
|
||||
//! Rust type to an outgoing binding.
|
||||
|
||||
use crate::descriptor::{Descriptor, VectorKind};
|
||||
use crate::webidl::NonstandardWebidlSection;
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use walrus::{Module, ValType};
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
/// A list of all possible outgoing bindings which can be used when converting
|
||||
/// Rust types to JS. This is predominantly used when calling an imported JS
|
||||
/// function.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NonstandardOutgoing {
|
||||
/// This is a standard upstream WebIDL outgoing binding expression. Where
|
||||
/// possible we can actually leave this in the wasm file and generate even
|
||||
/// less JS shim code.
|
||||
Standard(ast::OutgoingBindingExpression),
|
||||
|
||||
/// We're returning a pointer from Rust to JS to get wrapped in a JS class
|
||||
/// which has memory management around it.
|
||||
RustType { class: String, idx: u32 },
|
||||
|
||||
/// A single rust `char` value which is converted to a `string` in JS.
|
||||
Char { idx: u32 },
|
||||
|
||||
/// An `i64` or `u64` in Rust converted to a `BigInt` in JS
|
||||
Number64 {
|
||||
lo_idx: u32,
|
||||
hi_idx: u32,
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// A *borrowed* anyref value which has special meanings about ownership,
|
||||
/// namely Rust is still using the underlying value after the call returns.
|
||||
BorrowedAnyref { idx: u32 },
|
||||
|
||||
/// An owned vector is passed from Rust to JS. Note that this is currently a
|
||||
/// special binding because it requires memory management via deallocation
|
||||
/// in the JS shim.
|
||||
///
|
||||
/// TODO: we should strive to not have this nonstandard binding and instead
|
||||
/// do all the memory management in Rust. Ideally we'd use `AllocCopy` in
|
||||
/// place of this.
|
||||
Vector {
|
||||
offset: u32,
|
||||
length: u32,
|
||||
kind: VectorKind,
|
||||
},
|
||||
|
||||
/// A Rust String (or &str) which might be cached, or might be `None`.
|
||||
///
|
||||
/// If `offset` is 0 then it is cached, and the cached JsValue's index is in `length`.
|
||||
///
|
||||
/// If `offset` and `length` are both 0, then it is `None`.
|
||||
CachedString {
|
||||
offset: u32,
|
||||
length: u32,
|
||||
owned: bool,
|
||||
optional: bool,
|
||||
},
|
||||
|
||||
/// A `&[u64]` or `&[i64]` is being passed to JS, and the 64-bit sizes here
|
||||
/// aren't supported by WebIDL bindings yet.
|
||||
View64 {
|
||||
offset: u32,
|
||||
length: u32,
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// A list of `anyref` is being passed to JS, and it's got a somewhat
|
||||
/// magical representation with indics which doesn't map to WebIDL bindings.
|
||||
ViewAnyref { offset: u32, length: u32 },
|
||||
|
||||
/// An optional owned vector of data is being passed to JS.
|
||||
///
|
||||
/// TODO: with some cleverness this could probably use `AllocCopy`.
|
||||
OptionVector {
|
||||
offset: u32,
|
||||
length: u32,
|
||||
kind: VectorKind,
|
||||
},
|
||||
|
||||
/// An optional slice of data is being passed into JS.
|
||||
///
|
||||
/// TODO: with some cleverness this could probably use `AllocCopy`.
|
||||
OptionSlice {
|
||||
kind: VectorKind,
|
||||
offset: u32,
|
||||
length: u32,
|
||||
},
|
||||
|
||||
/// An optional "native type" like i32/u32/f32/f64 is being passed to JS,
|
||||
/// and this requires a discriminant in the ABI.
|
||||
OptionNative {
|
||||
present: u32,
|
||||
val: u32,
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// An optional number is being passed to JS where the number uses a
|
||||
/// sentinel value to represent `None`
|
||||
OptionU32Sentinel { idx: u32 },
|
||||
|
||||
/// An optional boolean with a special value for `None`
|
||||
OptionBool { idx: u32 },
|
||||
|
||||
/// An optional character with a special value for `None`
|
||||
OptionChar { idx: u32 },
|
||||
|
||||
/// An optional integral enum value with the specified `hole` being used for
|
||||
/// `None`.
|
||||
OptionIntegerEnum { idx: u32, hole: u32 },
|
||||
|
||||
/// An optional 64-bit integer being used.
|
||||
OptionInt64 {
|
||||
present: u32,
|
||||
_ignored: u32,
|
||||
lo: u32,
|
||||
hi: u32,
|
||||
signed: bool,
|
||||
},
|
||||
|
||||
/// An optional owned Rust type being transferred from Rust to JS.
|
||||
OptionRustType { class: String, idx: u32 },
|
||||
|
||||
/// A temporary stack closure being passed from Rust to JS. A JS function is
|
||||
/// manufactured and then neutered just before the call returns.
|
||||
StackClosure {
|
||||
/// Argument index of the first data pointer Rust needs
|
||||
a: u32,
|
||||
/// Argument index of the second data pointer Rust needs
|
||||
b: u32,
|
||||
/// The index of the shim in the element bindings section that we're
|
||||
/// going to be invoking.
|
||||
binding_idx: u32,
|
||||
/// Number of arguments to the closure
|
||||
nargs: usize,
|
||||
/// Whether or not this is a mutable closure (affects codegen and how
|
||||
/// it's called recursively)
|
||||
mutable: bool,
|
||||
},
|
||||
}
|
||||
|
||||
/// A definition of building `NonstandardOutgoing` expressions from a
|
||||
/// `Descriptor`.
|
||||
///
|
||||
/// This will internally keep track of wasm/webidl types generated as we visit
|
||||
/// `Descriptor` arguments and add more for a function signature.
|
||||
#[derive(Default)]
|
||||
pub struct OutgoingBuilder<'a> {
|
||||
/// All wasm types used so far to produce the resulting JS values.
|
||||
pub wasm: Vec<ValType>,
|
||||
/// The WebIDL types that we're passing along out of wasm.
|
||||
pub webidl: Vec<ast::WebidlScalarType>,
|
||||
/// The list of bindings we've created, currently 1:1 with the webidl above.
|
||||
pub bindings: Vec<NonstandardOutgoing>,
|
||||
|
||||
// These two arguments are optional and, if set, will enable creating
|
||||
// `StackClosure` bindings. They're not present for return values from
|
||||
// exported Rust functions, but they are available for the arguments of
|
||||
// calling imported functions.
|
||||
pub module: Option<&'a mut Module>,
|
||||
pub bindings_section: Option<&'a mut NonstandardWebidlSection>,
|
||||
}
|
||||
|
||||
impl OutgoingBuilder<'_> {
|
||||
/// Adds a dummy first argument which is passed through as an integer
|
||||
/// representing the return pointer.
|
||||
pub fn process_retptr(&mut self) {
|
||||
self.standard_as(ValType::I32, ast::WebidlScalarType::Long);
|
||||
}
|
||||
|
||||
/// Processes one more `Descriptor` as an argument to a JS function that
|
||||
/// wasm is calling.
|
||||
///
|
||||
/// This will internally skip `Unit` and otherwise build up the `bindings`
|
||||
/// map and ensure that it's correctly mapped from wasm to JS.
|
||||
pub fn process(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
if let Descriptor::Unit = arg {
|
||||
return Ok(());
|
||||
}
|
||||
assert_eq!(self.webidl.len(), self.bindings.len());
|
||||
let wasm_before = self.wasm.len();
|
||||
let webidl_before = self.webidl.len();
|
||||
self._process(arg)?;
|
||||
assert_eq!(self.webidl.len(), self.bindings.len());
|
||||
assert_eq!(webidl_before + 1, self.webidl.len());
|
||||
assert!(wasm_before < self.wasm.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _process(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Boolean => self.standard_as(ValType::I32, ast::WebidlScalarType::Boolean),
|
||||
Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any),
|
||||
Descriptor::I8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Byte),
|
||||
Descriptor::U8 => self.standard_as(ValType::I32, ast::WebidlScalarType::Octet),
|
||||
Descriptor::I16 => self.standard_as(ValType::I32, ast::WebidlScalarType::Short),
|
||||
Descriptor::U16 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedShort),
|
||||
Descriptor::I32 => self.standard_as(ValType::I32, ast::WebidlScalarType::Long),
|
||||
Descriptor::U32 => self.standard_as(ValType::I32, ast::WebidlScalarType::UnsignedLong),
|
||||
Descriptor::F32 => {
|
||||
self.standard_as(ValType::F32, ast::WebidlScalarType::UnrestrictedFloat)
|
||||
}
|
||||
Descriptor::F64 => {
|
||||
self.standard_as(ValType::F64, ast::WebidlScalarType::UnrestrictedDouble)
|
||||
}
|
||||
Descriptor::Enum { .. } => self.standard_as(ValType::I32, ast::WebidlScalarType::Long),
|
||||
|
||||
Descriptor::Char => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::DomString);
|
||||
self.bindings.push(NonstandardOutgoing::Char { idx });
|
||||
}
|
||||
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
let lo_idx = self.push_wasm(ValType::I32);
|
||||
let hi_idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::Number64 {
|
||||
lo_idx,
|
||||
hi_idx,
|
||||
signed,
|
||||
});
|
||||
}
|
||||
|
||||
Descriptor::RustStruct(class) => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::RustType {
|
||||
idx,
|
||||
class: class.to_string(),
|
||||
});
|
||||
}
|
||||
Descriptor::Ref(d) => self.process_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.process_ref(true, d)?,
|
||||
|
||||
Descriptor::CachedString => self.cached_string(false, true),
|
||||
|
||||
Descriptor::Vector(_) | Descriptor::String => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported argument type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let offset = self.push_wasm(ValType::I32);
|
||||
let length = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::Vector {
|
||||
offset,
|
||||
kind,
|
||||
length,
|
||||
})
|
||||
}
|
||||
|
||||
Descriptor::Option(d) => self.process_option(d)?,
|
||||
|
||||
Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!(
|
||||
"unsupported argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
|
||||
// nothing to do
|
||||
Descriptor::Unit => {}
|
||||
|
||||
// Largely synthetic and can't show up
|
||||
Descriptor::ClampedU8 => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
let idx = self.push_wasm(ValType::Anyref);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::BorrowedAnyref { idx });
|
||||
}
|
||||
Descriptor::CachedString => self.cached_string(false, false),
|
||||
Descriptor::Slice(_) | Descriptor::String => {
|
||||
use wasm_webidl_bindings::ast::WebidlScalarType::*;
|
||||
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported argument type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let offset = self.push_wasm(ValType::I32);
|
||||
let length = self.push_wasm(ValType::I32);
|
||||
match kind {
|
||||
VectorKind::I8 => self.standard_view(offset, length, Int8Array),
|
||||
VectorKind::U8 => self.standard_view(offset, length, Uint8Array),
|
||||
VectorKind::ClampedU8 => self.standard_view(offset, length, Uint8ClampedArray),
|
||||
VectorKind::I16 => self.standard_view(offset, length, Int16Array),
|
||||
VectorKind::U16 => self.standard_view(offset, length, Uint16Array),
|
||||
VectorKind::I32 => self.standard_view(offset, length, Int32Array),
|
||||
VectorKind::U32 => self.standard_view(offset, length, Uint32Array),
|
||||
VectorKind::F32 => self.standard_view(offset, length, Float32Array),
|
||||
VectorKind::F64 => self.standard_view(offset, length, Float64Array),
|
||||
VectorKind::String => {
|
||||
self.webidl.push(DomString);
|
||||
let binding = ast::OutgoingBindingExpressionUtf8Str {
|
||||
ty: ast::WebidlScalarType::DomString.into(),
|
||||
offset,
|
||||
length,
|
||||
};
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::Standard(binding.into()));
|
||||
}
|
||||
VectorKind::I64 | VectorKind::U64 => {
|
||||
let signed = match kind {
|
||||
VectorKind::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.webidl.push(Any);
|
||||
self.bindings.push(NonstandardOutgoing::View64 {
|
||||
offset,
|
||||
length,
|
||||
signed,
|
||||
});
|
||||
}
|
||||
VectorKind::Anyref => {
|
||||
self.webidl.push(Any);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::ViewAnyref { offset, length });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Descriptor::Function(descriptor) => {
|
||||
let module = self
|
||||
.module
|
||||
.as_mut()
|
||||
.ok_or_else(|| format_err!("cannot return a closure from Rust"))?;
|
||||
let section = self.bindings_section.as_mut().unwrap();
|
||||
// synthesize the a/b arguments that aren't present in the
|
||||
// signature from wasm-bindgen but are present in the wasm file.
|
||||
let mut descriptor = (**descriptor).clone();
|
||||
let nargs = descriptor.arguments.len();
|
||||
descriptor.arguments.insert(0, Descriptor::I32);
|
||||
descriptor.arguments.insert(0, Descriptor::I32);
|
||||
let binding_idx = super::bindings::register_table_element(
|
||||
module,
|
||||
section,
|
||||
descriptor.shim_idx,
|
||||
descriptor,
|
||||
)?;
|
||||
let a = self.push_wasm(ValType::I32);
|
||||
let b = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::StackClosure {
|
||||
a,
|
||||
b,
|
||||
binding_idx,
|
||||
nargs,
|
||||
mutable,
|
||||
});
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported reference argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => self.standard_as(ValType::Anyref, ast::WebidlScalarType::Any),
|
||||
Descriptor::I8 => self.option_sentinel(),
|
||||
Descriptor::U8 => self.option_sentinel(),
|
||||
Descriptor::I16 => self.option_sentinel(),
|
||||
Descriptor::U16 => self.option_sentinel(),
|
||||
Descriptor::I32 => self.option_native(true, ValType::I32),
|
||||
Descriptor::U32 => self.option_native(false, ValType::I32),
|
||||
Descriptor::F32 => self.option_native(true, ValType::F32),
|
||||
Descriptor::F64 => self.option_native(true, ValType::F64),
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
let binding = NonstandardOutgoing::OptionInt64 {
|
||||
present: self.push_wasm(ValType::I32),
|
||||
_ignored: self.push_wasm(ValType::I32),
|
||||
lo: self.push_wasm(ValType::I32),
|
||||
hi: self.push_wasm(ValType::I32),
|
||||
signed,
|
||||
};
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(binding);
|
||||
}
|
||||
Descriptor::Boolean => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionBool { idx });
|
||||
}
|
||||
Descriptor::Char => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionChar { idx });
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Long);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::OptionIntegerEnum { idx, hole: *hole });
|
||||
}
|
||||
Descriptor::RustStruct(name) => {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionRustType {
|
||||
idx,
|
||||
class: name.to_string(),
|
||||
});
|
||||
}
|
||||
Descriptor::Ref(d) => self.process_option_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.process_option_ref(true, d)?,
|
||||
|
||||
Descriptor::CachedString => self.cached_string(true, true),
|
||||
|
||||
Descriptor::String | Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let offset = self.push_wasm(ValType::I32);
|
||||
let length = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionVector {
|
||||
kind,
|
||||
offset,
|
||||
length,
|
||||
})
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported optional argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
let idx = self.push_wasm(ValType::Anyref);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::BorrowedAnyref { idx });
|
||||
}
|
||||
Descriptor::CachedString => self.cached_string(true, false),
|
||||
Descriptor::String | Descriptor::Slice(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let offset = self.push_wasm(ValType::I32);
|
||||
let length = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionSlice {
|
||||
kind,
|
||||
offset,
|
||||
length,
|
||||
});
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported optional ref argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn push_wasm(&mut self, ty: ValType) -> u32 {
|
||||
self.wasm.push(ty);
|
||||
self.wasm.len() as u32 - 1
|
||||
}
|
||||
|
||||
fn standard_as(&mut self, wasm: ValType, webidl: ast::WebidlScalarType) {
|
||||
let binding = ast::OutgoingBindingExpressionAs {
|
||||
ty: webidl.into(),
|
||||
idx: self.push_wasm(wasm),
|
||||
};
|
||||
self.webidl.push(webidl);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::Standard(binding.into()));
|
||||
}
|
||||
|
||||
fn standard_view(&mut self, offset: u32, length: u32, ty: ast::WebidlScalarType) {
|
||||
let binding = ast::OutgoingBindingExpressionView {
|
||||
ty: ty.into(),
|
||||
offset,
|
||||
length,
|
||||
};
|
||||
self.webidl.push(ty);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::Standard(binding.into()));
|
||||
}
|
||||
|
||||
fn cached_string(&mut self, optional: bool, owned: bool) {
|
||||
let offset = self.push_wasm(ValType::I32);
|
||||
let length = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::DomString);
|
||||
self.bindings.push(NonstandardOutgoing::CachedString {
|
||||
offset,
|
||||
length,
|
||||
owned,
|
||||
optional,
|
||||
})
|
||||
}
|
||||
|
||||
fn option_native(&mut self, signed: bool, ty: ValType) {
|
||||
let present = self.push_wasm(ValType::I32);
|
||||
let val = self.push_wasm(ty);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings.push(NonstandardOutgoing::OptionNative {
|
||||
signed,
|
||||
present,
|
||||
val,
|
||||
});
|
||||
}
|
||||
|
||||
fn option_sentinel(&mut self) {
|
||||
let idx = self.push_wasm(ValType::I32);
|
||||
self.webidl.push(ast::WebidlScalarType::Any);
|
||||
self.bindings
|
||||
.push(NonstandardOutgoing::OptionU32Sentinel { idx });
|
||||
}
|
||||
}
|
@ -1,562 +0,0 @@
|
||||
//! Support for generating a standard WebIDL custom section
|
||||
//!
|
||||
//! This module has all the necessary support for generating a full-fledged
|
||||
//! standard WebIDL custom section as defined by the `wasm-webidl-bindings`
|
||||
//! crate. This module also critically assumes that the WebAssembly module
|
||||
//! being generated **must be standalone**. In this mode all sorts of features
|
||||
//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures,
|
||||
//! imports of global js names, js getters/setters, exporting structs, etc.
|
||||
//! These features may all eventually come to the standard bindings proposal,
|
||||
//! but it will likely take some time. In the meantime this module simply focuses
|
||||
//! on taking what's already a valid wasm module and letting it through with a
|
||||
//! standard WebIDL custom section. All other modules generate an error during
|
||||
//! this binding process.
|
||||
//!
|
||||
//! Note that when this function is called and used we're also not actually
|
||||
//! generating any JS glue. Any JS glue currently generated is also invalid if
|
||||
//! the module contains the wasm bindings section and it's actually respected.
|
||||
|
||||
use crate::descriptor::VectorKind;
|
||||
use crate::webidl::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName};
|
||||
use crate::webidl::{NonstandardIncoming, NonstandardOutgoing};
|
||||
use crate::webidl::{NonstandardWebidlSection, WasmBindgenAux};
|
||||
use anyhow::{bail, Context, Error};
|
||||
use walrus::Module;
|
||||
use wasm_bindgen_multi_value_xform as multi_value_xform;
|
||||
use wasm_bindgen_wasm_conventions as wasm_conventions;
|
||||
use wasm_webidl_bindings::ast;
|
||||
|
||||
pub fn add_multi_value(
|
||||
module: &mut Module,
|
||||
bindings: &mut NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
let mut to_xform = vec![];
|
||||
for (id, binding) in &bindings.exports {
|
||||
if let Some(ref results) = binding.return_via_outptr {
|
||||
// LLVM currently always uses the first parameter for the return
|
||||
// pointer. We hard code that here, since we have no better option.
|
||||
let return_pointer_index = 0;
|
||||
to_xform.push((*id, return_pointer_index, &results[..]));
|
||||
}
|
||||
}
|
||||
|
||||
if to_xform.is_empty() {
|
||||
// Early exit to avoid failing if we don't have a memory or shadow stack
|
||||
// pointer because this is a minimal module that doesn't use linear
|
||||
// memory.
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
let shadow_stack_pointer = wasm_conventions::get_shadow_stack_pointer(module)?;
|
||||
let memory = wasm_conventions::get_memory(module)?;
|
||||
multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?;
|
||||
|
||||
// Finally, unset `return_via_outptr`, fix up its incoming bindings'
|
||||
// argument numberings, and update its function type.
|
||||
for (id, binding) in &mut bindings.exports {
|
||||
if binding.return_via_outptr.take().is_none() {
|
||||
continue;
|
||||
}
|
||||
if binding.incoming.is_empty() {
|
||||
bail!("missing incoming binding expression for return pointer parameter");
|
||||
}
|
||||
if !is_ret_ptr_bindings(binding.incoming.remove(0)) {
|
||||
bail!("unexpected incoming binding expression for return pointer parameter");
|
||||
}
|
||||
|
||||
fixup_binding_argument_gets(&mut binding.incoming)?;
|
||||
|
||||
let func = match module.exports.get(*id).item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
binding.wasm_ty = module.funcs.get(func).ty();
|
||||
|
||||
// Be sure to delete the out-param pointer from the WebIDL type as well.
|
||||
let webidl_ty = bindings
|
||||
.types
|
||||
.get::<ast::WebidlFunction>(binding.webidl_ty)
|
||||
.unwrap();
|
||||
let mut new_ty = webidl_ty.clone();
|
||||
new_ty.params.remove(0);
|
||||
binding.webidl_ty = bindings.types.insert(new_ty);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn is_ret_ptr_bindings(b: NonstandardIncoming) -> bool {
|
||||
match b {
|
||||
NonstandardIncoming::Standard(ast::IncomingBindingExpression::As(
|
||||
ast::IncomingBindingExpressionAs {
|
||||
ty: walrus::ValType::I32,
|
||||
expr,
|
||||
},
|
||||
)) => match *expr {
|
||||
ast::IncomingBindingExpression::Get(ast::IncomingBindingExpressionGet { idx: 0 }) => {
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
},
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// Since we removed the first parameter (which was the return pointer) now all
|
||||
// of the `Get` binding expression's are off by one. This function fixes these
|
||||
// `Get`s.
|
||||
fn fixup_binding_argument_gets(incoming: &mut [NonstandardIncoming]) -> Result<(), Error> {
|
||||
for inc in incoming {
|
||||
fixup_nonstandard_incoming(inc)?;
|
||||
}
|
||||
return Ok(());
|
||||
|
||||
fn fixup_nonstandard_incoming(inc: &mut NonstandardIncoming) -> Result<(), Error> {
|
||||
match inc {
|
||||
NonstandardIncoming::Standard(s) => fixup_standard_incoming(s),
|
||||
_ => bail!("found usage of non-standard bindings when in standard-bindings-only mode"),
|
||||
}
|
||||
}
|
||||
|
||||
fn fixup_standard_incoming(s: &mut ast::IncomingBindingExpression) -> Result<(), Error> {
|
||||
match s {
|
||||
ast::IncomingBindingExpression::Get(e) => {
|
||||
if e.idx == 0 {
|
||||
bail!(
|
||||
"found usage of removed return pointer parameter in \
|
||||
non-return pointer bindings"
|
||||
);
|
||||
} else {
|
||||
e.idx -= 1;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
ast::IncomingBindingExpression::As(e) => fixup_standard_incoming(&mut e.expr),
|
||||
ast::IncomingBindingExpression::AllocUtf8Str(e) => fixup_standard_incoming(&mut e.expr),
|
||||
ast::IncomingBindingExpression::AllocCopy(e) => fixup_standard_incoming(&mut e.expr),
|
||||
ast::IncomingBindingExpression::EnumToI32(e) => fixup_standard_incoming(&mut e.expr),
|
||||
ast::IncomingBindingExpression::Field(e) => fixup_standard_incoming(&mut e.expr),
|
||||
ast::IncomingBindingExpression::BindImport(e) => fixup_standard_incoming(&mut e.expr),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_section(
|
||||
module: &mut Module,
|
||||
aux: &WasmBindgenAux,
|
||||
nonstandard: &NonstandardWebidlSection,
|
||||
) -> Result<(), Error> {
|
||||
let mut section = ast::WebidlBindings::default();
|
||||
let WasmBindgenAux {
|
||||
extra_typescript: _, // ignore this even if it's specified
|
||||
local_modules,
|
||||
snippets,
|
||||
package_jsons,
|
||||
export_map,
|
||||
import_map,
|
||||
imports_with_catch,
|
||||
imports_with_variadic,
|
||||
imports_with_assert_no_shim: _, // not relevant for this purpose
|
||||
enums,
|
||||
structs,
|
||||
} = aux;
|
||||
|
||||
for (export, binding) in nonstandard.exports.iter() {
|
||||
// First up make sure this is something that's actually valid to export
|
||||
// form a vanilla WebAssembly module with WebIDL bindings.
|
||||
match &export_map[export].kind {
|
||||
AuxExportKind::Function(_) => {}
|
||||
AuxExportKind::Constructor(name) => {
|
||||
bail!(
|
||||
"cannot export `{}` constructor function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
name,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Getter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` getter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Setter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` setter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::StaticFunction { class, name } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` static function when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
AuxExportKind::Method { class, name, .. } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` method when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let name = &module.exports.get(*export).name;
|
||||
let params = extract_incoming(&binding.incoming).with_context(|| {
|
||||
format!(
|
||||
"failed to map arguments for export `{}` to standard \
|
||||
binding expressions",
|
||||
name
|
||||
)
|
||||
})?;
|
||||
let result = extract_outgoing(&binding.outgoing).with_context(|| {
|
||||
format!(
|
||||
"failed to map return value for export `{}` to standard \
|
||||
binding expressions",
|
||||
name
|
||||
)
|
||||
})?;
|
||||
|
||||
assert!(binding.return_via_outptr.is_none());
|
||||
let binding = section.bindings.insert(ast::ExportBinding {
|
||||
wasm_ty: binding.wasm_ty,
|
||||
webidl_ty: copy_ty(
|
||||
&mut section.types,
|
||||
binding.webidl_ty.into(),
|
||||
&nonstandard.types,
|
||||
),
|
||||
params: ast::IncomingBindingMap { bindings: params },
|
||||
result: ast::OutgoingBindingMap { bindings: result },
|
||||
});
|
||||
let func = match module.exports.get(*export).item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
section.binds.insert(ast::Bind {
|
||||
func,
|
||||
binding: binding.into(),
|
||||
});
|
||||
}
|
||||
|
||||
for (import, binding) in nonstandard.imports.iter() {
|
||||
check_standard_import(&import_map[import])?;
|
||||
let (module_name, name) = {
|
||||
let import = module.imports.get(*import);
|
||||
(&import.module, &import.name)
|
||||
};
|
||||
let params = extract_outgoing(&binding.outgoing).with_context(|| {
|
||||
format!(
|
||||
"failed to map arguments of import `{}::{}` to standard \
|
||||
binding expressions",
|
||||
module_name, name,
|
||||
)
|
||||
})?;
|
||||
let result = extract_incoming(&binding.incoming).with_context(|| {
|
||||
format!(
|
||||
"failed to map return value of import `{}::{}` to standard \
|
||||
binding expressions",
|
||||
module_name, name,
|
||||
)
|
||||
})?;
|
||||
assert!(binding.return_via_outptr.is_none());
|
||||
let binding = section.bindings.insert(ast::ImportBinding {
|
||||
wasm_ty: binding.wasm_ty,
|
||||
webidl_ty: copy_ty(
|
||||
&mut section.types,
|
||||
binding.webidl_ty.into(),
|
||||
&nonstandard.types,
|
||||
),
|
||||
params: ast::OutgoingBindingMap { bindings: params },
|
||||
result: ast::IncomingBindingMap { bindings: result },
|
||||
});
|
||||
let func = match module.imports.get(*import).kind {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
section.binds.insert(ast::Bind {
|
||||
func,
|
||||
binding: binding.into(),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some((name, _)) = local_modules.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS modules being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS snippets being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = package_jsons.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
package.json being consumed as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
path.display(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(import) = imports_with_catch.iter().next() {
|
||||
let import = module.imports.get(*import);
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(catch)]` on the `{}::{}` import because a \
|
||||
a standalone wasm file is being generated",
|
||||
import.module,
|
||||
import.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(import) = imports_with_variadic.iter().next() {
|
||||
let import = module.imports.get(*import);
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(variadic)]` on the `{}::{}` import because a \
|
||||
a standalone wasm file is being generated",
|
||||
import.module,
|
||||
import.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(enum_) = enums.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting an `enum` from the wasm file, cannot export `{}`",
|
||||
enum_.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(struct_) = structs.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting a `struct` from the wasm file, cannot export `{}`",
|
||||
struct_.name,
|
||||
);
|
||||
}
|
||||
|
||||
if nonstandard.elems.len() > 0 {
|
||||
// Note that this is a pretty cryptic error message, but we in theory
|
||||
// shouldn't ever hit this since closures always show up as some form
|
||||
// of nonstandard binding which was previously checked.
|
||||
bail!("generating a standalone wasm file requires no table element bindings");
|
||||
}
|
||||
|
||||
module.customs.add(section);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn extract_incoming(
|
||||
nonstandard: &[NonstandardIncoming],
|
||||
) -> Result<Vec<ast::IncomingBindingExpression>, Error> {
|
||||
let mut exprs = Vec::new();
|
||||
for expr in nonstandard {
|
||||
let desc = match expr {
|
||||
NonstandardIncoming::Standard(e) => {
|
||||
exprs.push(e.clone());
|
||||
continue;
|
||||
}
|
||||
NonstandardIncoming::Int64 { .. } => "64-bit integer",
|
||||
NonstandardIncoming::AllocCopyInt64 { .. } => "64-bit integer array",
|
||||
NonstandardIncoming::AllocCopyAnyrefArray { .. } => "array of JsValue",
|
||||
NonstandardIncoming::MutableSlice { .. } => "mutable slice",
|
||||
NonstandardIncoming::OptionSlice { .. } => "optional slice",
|
||||
NonstandardIncoming::OptionVector { .. } => "optional vector",
|
||||
NonstandardIncoming::OptionAnyref { .. } => "optional anyref",
|
||||
NonstandardIncoming::OptionNative { .. } => "optional integer",
|
||||
NonstandardIncoming::OptionU32Sentinel { .. } => "optional integer",
|
||||
NonstandardIncoming::OptionBool { .. } => "optional bool",
|
||||
NonstandardIncoming::OptionChar { .. } => "optional char",
|
||||
NonstandardIncoming::OptionIntegerEnum { .. } => "optional enum",
|
||||
NonstandardIncoming::OptionInt64 { .. } => "optional integer",
|
||||
NonstandardIncoming::RustType { .. } => "native Rust type",
|
||||
NonstandardIncoming::RustTypeRef { .. } => "reference to Rust type",
|
||||
NonstandardIncoming::OptionRustType { .. } => "optional Rust type",
|
||||
NonstandardIncoming::Char { .. } => "character",
|
||||
NonstandardIncoming::BorrowedAnyref { .. } => "borrowed anyref",
|
||||
};
|
||||
bail!(
|
||||
"cannot represent {} with a standard bindings expression",
|
||||
desc
|
||||
);
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
fn extract_outgoing(
|
||||
nonstandard: &[NonstandardOutgoing],
|
||||
) -> Result<Vec<ast::OutgoingBindingExpression>, Error> {
|
||||
let mut exprs = Vec::new();
|
||||
for expr in nonstandard {
|
||||
let desc = match expr {
|
||||
NonstandardOutgoing::Standard(e) => {
|
||||
exprs.push(e.clone());
|
||||
continue;
|
||||
}
|
||||
// ... yeah ... let's just leak strings
|
||||
// see comment at top of this module about returning strings for
|
||||
// what this is doing and why it's weird
|
||||
NonstandardOutgoing::Vector {
|
||||
offset,
|
||||
length,
|
||||
kind: VectorKind::String,
|
||||
} => {
|
||||
exprs.push(
|
||||
ast::OutgoingBindingExpressionUtf8Str {
|
||||
offset: *offset,
|
||||
length: *length,
|
||||
ty: ast::WebidlScalarType::DomString.into(),
|
||||
}
|
||||
.into(),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
NonstandardOutgoing::RustType { .. } => "rust type",
|
||||
NonstandardOutgoing::Char { .. } => "character",
|
||||
NonstandardOutgoing::Number64 { .. } => "64-bit integer",
|
||||
NonstandardOutgoing::BorrowedAnyref { .. } => "borrowed anyref",
|
||||
NonstandardOutgoing::Vector { .. } => "vector",
|
||||
NonstandardOutgoing::CachedString { .. } => "cached string",
|
||||
NonstandardOutgoing::View64 { .. } => "64-bit slice",
|
||||
NonstandardOutgoing::ViewAnyref { .. } => "anyref slice",
|
||||
NonstandardOutgoing::OptionVector { .. } => "optional vector",
|
||||
NonstandardOutgoing::OptionSlice { .. } => "optional slice",
|
||||
NonstandardOutgoing::OptionNative { .. } => "optional integer",
|
||||
NonstandardOutgoing::OptionU32Sentinel { .. } => "optional integer",
|
||||
NonstandardOutgoing::OptionBool { .. } => "optional boolean",
|
||||
NonstandardOutgoing::OptionChar { .. } => "optional character",
|
||||
NonstandardOutgoing::OptionIntegerEnum { .. } => "optional enum",
|
||||
NonstandardOutgoing::OptionInt64 { .. } => "optional 64-bit integer",
|
||||
NonstandardOutgoing::OptionRustType { .. } => "optional rust type",
|
||||
NonstandardOutgoing::StackClosure { .. } => "closures",
|
||||
};
|
||||
bail!(
|
||||
"cannot represent {} with a standard bindings expression",
|
||||
desc
|
||||
);
|
||||
}
|
||||
Ok(exprs)
|
||||
}
|
||||
|
||||
/// Recursively clones `ty` into` dst` where it originally indexes values in
|
||||
/// `src`, returning a new type ref which indexes inside of `dst`.
|
||||
pub fn copy_ty(
|
||||
dst: &mut ast::WebidlTypes,
|
||||
ty: ast::WebidlTypeRef,
|
||||
src: &ast::WebidlTypes,
|
||||
) -> ast::WebidlTypeRef {
|
||||
let id = match ty {
|
||||
ast::WebidlTypeRef::Id(id) => id,
|
||||
ast::WebidlTypeRef::Scalar(_) => return ty,
|
||||
};
|
||||
let ty: &ast::WebidlCompoundType = src.get(id).unwrap();
|
||||
match ty {
|
||||
ast::WebidlCompoundType::Function(f) => {
|
||||
let params = f
|
||||
.params
|
||||
.iter()
|
||||
.map(|param| copy_ty(dst, *param, src))
|
||||
.collect();
|
||||
let result = f.result.map(|ty| copy_ty(dst, ty, src));
|
||||
dst.insert(ast::WebidlFunction {
|
||||
kind: f.kind.clone(),
|
||||
params,
|
||||
result,
|
||||
})
|
||||
.into()
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn check_standard_import(import: &AuxImport) -> Result<(), Error> {
|
||||
let desc_js = |js: &JsImport| {
|
||||
let mut extra = String::new();
|
||||
for field in js.fields.iter() {
|
||||
extra.push_str(".");
|
||||
extra.push_str(field);
|
||||
}
|
||||
match &js.name {
|
||||
JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => {
|
||||
format!("global `{}{}`", name, extra)
|
||||
}
|
||||
JsImportName::Module { module, name } => {
|
||||
format!("`{}{}` from '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::LocalModule { module, name } => {
|
||||
format!("`{}{}` from local module '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::InlineJs {
|
||||
unique_crate_identifier,
|
||||
name,
|
||||
..
|
||||
} => format!(
|
||||
"`{}{}` from inline js in '{}'",
|
||||
name, extra, unique_crate_identifier
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
let item = match import {
|
||||
AuxImport::Value(AuxValue::Bare(js)) => {
|
||||
if js.fields.len() == 0 {
|
||||
if let JsImportName::Module { .. } = js.name {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
desc_js(js)
|
||||
}
|
||||
AuxImport::Value(AuxValue::Getter(js, name))
|
||||
| AuxImport::Value(AuxValue::Setter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassGetter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassSetter(js, name)) => {
|
||||
format!("field access of `{}` for {}", name, desc_js(js))
|
||||
}
|
||||
AuxImport::ValueWithThis(js, method) => format!("method `{}.{}`", desc_js(js), method),
|
||||
AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)),
|
||||
AuxImport::Static(js) => format!("static js value {}", desc_js(js)),
|
||||
AuxImport::StructuralMethod(name) => format!("structural method `{}`", name),
|
||||
AuxImport::StructuralGetter(name)
|
||||
| AuxImport::StructuralSetter(name)
|
||||
| AuxImport::StructuralClassGetter(_, name)
|
||||
| AuxImport::StructuralClassSetter(_, name) => {
|
||||
format!("structural field access of `{}`", name)
|
||||
}
|
||||
AuxImport::IndexingDeleterOfClass(_)
|
||||
| AuxImport::IndexingDeleterOfObject
|
||||
| AuxImport::IndexingGetterOfClass(_)
|
||||
| AuxImport::IndexingGetterOfObject
|
||||
| AuxImport::IndexingSetterOfClass(_)
|
||||
| AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"),
|
||||
AuxImport::WrapInExportedClass(name) => {
|
||||
format!("wrapping a pointer in a `{}` js class wrapper", name)
|
||||
}
|
||||
AuxImport::Intrinsic(intrinsic) => {
|
||||
format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name())
|
||||
}
|
||||
AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"),
|
||||
};
|
||||
bail!(
|
||||
"cannot generate a standalone WebAssembly module which \
|
||||
contains an import of {} since it requires JS glue",
|
||||
item
|
||||
);
|
||||
}
|
385
crates/cli-support/src/wit/incoming.rs
Normal file
385
crates/cli-support/src/wit/incoming.rs
Normal file
@ -0,0 +1,385 @@
|
||||
//! Definition of how to convert Rust types (`Description`) into wasm types
|
||||
//! through adapter functions.
|
||||
//!
|
||||
//! Note that many Rust types use "nonstandard" instructions which only work in
|
||||
//! the JS output, not for the "pure wasm interface types" output.
|
||||
//!
|
||||
//! Note that the mirror operation, going from WebAssembly to JS, is found in
|
||||
//! the `outgoing.rs` module.
|
||||
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::wit::InstructionData;
|
||||
use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange};
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use walrus::ValType;
|
||||
|
||||
impl InstructionBuilder<'_, '_> {
|
||||
/// Process a `Descriptor` as if it's being passed from JS to Rust. This
|
||||
/// will skip `Unit` and otherwise internally add instructions necessary to
|
||||
/// convert the foreign type into the Rust bits.
|
||||
pub fn incoming(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
if let Descriptor::Unit = arg {
|
||||
return Ok(());
|
||||
}
|
||||
// This is a wrapper around `_incoming` to have a number of sanity checks
|
||||
// that we don't forget things. We should always produce at least one
|
||||
// wasm arge and exactly one webidl arg. Additionally the number of
|
||||
// bindings should always match the number of webidl types for now.
|
||||
let input_before = self.input.len();
|
||||
let output_before = self.output.len();
|
||||
self._incoming(arg)?;
|
||||
assert_eq!(
|
||||
input_before + 1,
|
||||
self.input.len(),
|
||||
"didn't push an input {:?}",
|
||||
arg
|
||||
);
|
||||
assert!(
|
||||
output_before < self.output.len(),
|
||||
"didn't push more outputs {:?}",
|
||||
arg
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _incoming(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
use walrus::ValType as WasmVT;
|
||||
use wit_walrus::ValType as WitVT;
|
||||
match arg {
|
||||
Descriptor::Boolean => {
|
||||
self.instruction(
|
||||
&[AdapterType::Bool],
|
||||
Instruction::I32FromBool,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.instruction(
|
||||
&[AdapterType::String],
|
||||
Instruction::I32FromStringFirstChar,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromAnyrefOwned,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::RustStruct(class) => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromAnyrefRustOwned {
|
||||
class: class.clone(),
|
||||
},
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::I8 => self.number(WitVT::S8, WasmVT::I32),
|
||||
Descriptor::U8 => self.number(WitVT::U8, WasmVT::I32),
|
||||
Descriptor::I16 => self.number(WitVT::S16, WasmVT::I32),
|
||||
Descriptor::U16 => self.number(WitVT::U16, WasmVT::I32),
|
||||
Descriptor::I32 => self.number(WitVT::S32, WasmVT::I32),
|
||||
Descriptor::U32 => self.number(WitVT::U32, WasmVT::I32),
|
||||
Descriptor::I64 => self.number64(true),
|
||||
Descriptor::U64 => self.number64(false),
|
||||
Descriptor::F32 => {
|
||||
self.get(AdapterType::F32);
|
||||
self.output.push(AdapterType::F32);
|
||||
}
|
||||
Descriptor::F64 => {
|
||||
self.get(AdapterType::F64);
|
||||
self.output.push(AdapterType::F64);
|
||||
}
|
||||
Descriptor::Enum { .. } => self.number(WitVT::U32, WasmVT::I32),
|
||||
Descriptor::Ref(d) => self.incoming_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.incoming_ref(true, d)?,
|
||||
Descriptor::Option(d) => self.incoming_option(d)?,
|
||||
|
||||
Descriptor::String | Descriptor::CachedString => {
|
||||
let std = wit_walrus::Instruction::StringToMemory {
|
||||
malloc: self.cx.malloc()?,
|
||||
mem: self.cx.memory()?,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::String],
|
||||
Instruction::Standard(std),
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!("unsupported argument type for calling Rust function from JS {:?}", arg)
|
||||
})?;
|
||||
self.instruction(
|
||||
&[AdapterType::Vector(kind)],
|
||||
Instruction::VectorToMemory {
|
||||
kind,
|
||||
malloc: self.cx.malloc()?,
|
||||
mem: self.cx.memory()?,
|
||||
},
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
}
|
||||
|
||||
// Can't be passed from JS to Rust yet
|
||||
Descriptor::Function(_) |
|
||||
Descriptor::Closure(_) |
|
||||
|
||||
// Always behind a `Ref`
|
||||
Descriptor::Slice(_) => bail!(
|
||||
"unsupported argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
|
||||
// nothing to do
|
||||
Descriptor::Unit => {}
|
||||
|
||||
// Largely synthetic and can't show up
|
||||
Descriptor::ClampedU8 => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn incoming_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::RustStruct(class) => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromAnyrefRustBorrow {
|
||||
class: class.clone(),
|
||||
},
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromAnyrefBorrow,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::String | Descriptor::CachedString => {
|
||||
// This allocation is cleaned up once it's received in Rust.
|
||||
let std = wit_walrus::Instruction::StringToMemory {
|
||||
malloc: self.cx.malloc()?,
|
||||
mem: self.cx.memory()?,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::String],
|
||||
Instruction::Standard(std),
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Slice(_) => {
|
||||
// like strings, this allocation is cleaned up after being
|
||||
// received in Rust.
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported argument type for calling Rust function from JS {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
if mutable {
|
||||
self.instruction(
|
||||
&[AdapterType::Vector(kind)],
|
||||
Instruction::MutableSliceToMemory {
|
||||
kind,
|
||||
malloc: self.cx.malloc()?,
|
||||
mem: self.cx.memory()?,
|
||||
free: self.cx.free()?,
|
||||
},
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
} else {
|
||||
self.instruction(
|
||||
&[AdapterType::Vector(kind)],
|
||||
Instruction::VectorToMemory {
|
||||
kind,
|
||||
malloc: self.cx.malloc()?,
|
||||
mem: self.cx.memory()?,
|
||||
},
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
}
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported reference argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn incoming_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionAnyref,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::I8 => self.in_option_sentinel(),
|
||||
Descriptor::U8 => self.in_option_sentinel(),
|
||||
Descriptor::I16 => self.in_option_sentinel(),
|
||||
Descriptor::U16 => self.in_option_sentinel(),
|
||||
Descriptor::I32 => self.in_option_native(ValType::I32),
|
||||
Descriptor::U32 => self.in_option_native(ValType::I32),
|
||||
Descriptor::F32 => self.in_option_native(ValType::F32),
|
||||
Descriptor::F64 => self.in_option_native(ValType::F64),
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32SplitOption64 { signed },
|
||||
&[AdapterType::I32; 3],
|
||||
);
|
||||
}
|
||||
Descriptor::Boolean => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionBool,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionChar,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionEnum { hole: *hole },
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
Descriptor::RustStruct(name) => {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionRust {
|
||||
class: name.to_string(),
|
||||
},
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling Rust function from JS {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let malloc = self.cx.malloc()?;
|
||||
let mem = self.cx.memory()?;
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::OptionVector { kind, malloc, mem },
|
||||
&[AdapterType::I32; 2],
|
||||
);
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported optional argument type for calling Rust function from JS: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get(&mut self, ty: AdapterType) {
|
||||
self.input.push(ty);
|
||||
|
||||
// If we're generating instructions in the return position then the
|
||||
// arguments are already on the stack to consume, otherwise we need to
|
||||
// fetch them from the parameters.
|
||||
if !self.return_position {
|
||||
let idx = self.input.len() as u32 - 1;
|
||||
let std = wit_walrus::Instruction::ArgGet(idx);
|
||||
self.instructions.push(InstructionData {
|
||||
instr: Instruction::Standard(std),
|
||||
stack_change: StackChange::Modified {
|
||||
pushed: 1,
|
||||
popped: 0,
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
pub fn instruction(
|
||||
&mut self,
|
||||
inputs: &[AdapterType],
|
||||
instr: Instruction,
|
||||
outputs: &[AdapterType],
|
||||
) {
|
||||
// If we're generating instructions in the return position then the
|
||||
// arguments are already on the stack to consume, otherwise we need to
|
||||
// fetch them from the parameters.
|
||||
if !self.return_position {
|
||||
for input in inputs {
|
||||
self.get(*input);
|
||||
}
|
||||
} else {
|
||||
self.input.extend_from_slice(inputs);
|
||||
}
|
||||
|
||||
self.instructions.push(InstructionData {
|
||||
instr,
|
||||
stack_change: StackChange::Modified {
|
||||
popped: inputs.len(),
|
||||
pushed: outputs.len(),
|
||||
},
|
||||
});
|
||||
self.output.extend_from_slice(outputs);
|
||||
}
|
||||
|
||||
fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) {
|
||||
let std = wit_walrus::Instruction::IntToWasm {
|
||||
input,
|
||||
output,
|
||||
trap: false,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::from_wit(input)],
|
||||
Instruction::Standard(std),
|
||||
&[AdapterType::from_wasm(output).unwrap()],
|
||||
);
|
||||
}
|
||||
|
||||
fn number64(&mut self, signed: bool) {
|
||||
self.instruction(
|
||||
&[if signed {
|
||||
AdapterType::S64
|
||||
} else {
|
||||
AdapterType::U64
|
||||
}],
|
||||
Instruction::I32Split64 { signed },
|
||||
&[AdapterType::I32, AdapterType::I32],
|
||||
);
|
||||
}
|
||||
|
||||
fn in_option_native(&mut self, wasm: ValType) {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::FromOptionNative { ty: wasm },
|
||||
&[AdapterType::I32, AdapterType::from_wasm(wasm).unwrap()],
|
||||
);
|
||||
}
|
||||
|
||||
fn in_option_sentinel(&mut self) {
|
||||
self.instruction(
|
||||
&[AdapterType::Anyref],
|
||||
Instruction::I32FromOptionU32Sentinel,
|
||||
&[AdapterType::I32],
|
||||
);
|
||||
}
|
||||
}
|
File diff suppressed because it is too large
Load Diff
360
crates/cli-support/src/wit/nonstandard.rs
Normal file
360
crates/cli-support/src/wit/nonstandard.rs
Normal file
@ -0,0 +1,360 @@
|
||||
use crate::intrinsic::Intrinsic;
|
||||
use crate::wit::AdapterId;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::path::PathBuf;
|
||||
use walrus::TypedCustomSectionId;
|
||||
|
||||
/// A synthetic custom section which is not standardized, never will be, and
|
||||
/// cannot be serialized or parsed. This is synthesized from all of the
|
||||
/// compiler-emitted wasm-bindgen sections and then immediately removed to be
|
||||
/// processed in the JS generation pass.
|
||||
#[derive(Default, Debug)]
|
||||
pub struct WasmBindgenAux {
|
||||
/// Extra typescript annotations that should be appended to the generated
|
||||
/// TypeScript file. This is provided via a custom attribute in Rust code.
|
||||
pub extra_typescript: String,
|
||||
|
||||
/// A map from identifier to the contents of each local module defined via
|
||||
/// the `#[wasm_bindgen(module = "/foo.js")]` import options.
|
||||
pub local_modules: HashMap<String, String>,
|
||||
|
||||
/// A map from unique crate identifier to the list of inline JS snippets for
|
||||
/// that crate identifier.
|
||||
pub snippets: HashMap<String, Vec<String>>,
|
||||
|
||||
/// A list of all `package.json` files that are intended to be included in
|
||||
/// the final build.
|
||||
pub package_jsons: HashSet<PathBuf>,
|
||||
|
||||
/// A map from exported function id to where it's expected to be exported
|
||||
/// to.
|
||||
pub export_map: HashMap<AdapterId, AuxExport>,
|
||||
|
||||
/// A map from imported function id to what it's expected to import.
|
||||
pub import_map: HashMap<AdapterId, AuxImport>,
|
||||
|
||||
/// Small bits of metadata about imports.
|
||||
pub imports_with_catch: HashSet<AdapterId>,
|
||||
pub imports_with_variadic: HashSet<AdapterId>,
|
||||
pub imports_with_assert_no_shim: HashSet<AdapterId>,
|
||||
|
||||
/// Auxiliary information to go into JS/TypeScript bindings describing the
|
||||
/// exported enums from Rust.
|
||||
pub enums: Vec<AuxEnum>,
|
||||
|
||||
/// Auxiliary information to go into JS/TypeScript bindings describing the
|
||||
/// exported structs from Rust and their fields they've got exported.
|
||||
pub structs: Vec<AuxStruct>,
|
||||
|
||||
/// Information about various internal functions used to manage the `anyref`
|
||||
/// table, later used to process JS bindings.
|
||||
pub anyref_table: Option<walrus::TableId>,
|
||||
pub anyref_alloc: Option<walrus::FunctionId>,
|
||||
pub anyref_drop_slice: Option<walrus::FunctionId>,
|
||||
}
|
||||
|
||||
pub type WasmBindgenAuxId = TypedCustomSectionId<WasmBindgenAux>;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuxExport {
|
||||
/// When generating errors about this export, a helpful name to remember it
|
||||
/// by.
|
||||
pub debug_name: String,
|
||||
/// Comments parsed in Rust and forwarded here to show up in JS bindings.
|
||||
pub comments: String,
|
||||
/// Argument names in Rust forwarded here to configure the names that show
|
||||
/// up in TypeScript bindings.
|
||||
pub arg_names: Option<Vec<String>>,
|
||||
/// What kind of function this is and where it shows up
|
||||
pub kind: AuxExportKind,
|
||||
}
|
||||
|
||||
/// All possible kinds of exports from a wasm module.
|
||||
///
|
||||
/// This `enum` says where to place an exported wasm function. For example it
|
||||
/// may want to get hooked up to a JS class, or it may want to be exported as a
|
||||
/// free function (etc).
|
||||
///
|
||||
/// TODO: it feels like this should not really be here per se. We probably want
|
||||
/// to either construct the JS object itself from within wasm or somehow move
|
||||
/// more of this information into some other section. Really what this is is
|
||||
/// sort of an "export map" saying how to wire up all the free functions from
|
||||
/// the wasm module into the output expected JS module. All our functions here
|
||||
/// currently take integer parameters and require a JS wrapper, but ideally
|
||||
/// we'd change them one day to taking/receiving `anyref` which then use some
|
||||
/// sort of webidl import to customize behavior or something like that. In any
|
||||
/// case this doesn't feel quite right in terms of priviledge separation, so
|
||||
/// we'll want to work on this. For now though it works.
|
||||
#[derive(Debug)]
|
||||
pub enum AuxExportKind {
|
||||
/// A free function that's just listed on the exported module
|
||||
Function(String),
|
||||
|
||||
/// A function that's used to create an instane of a class. The function
|
||||
/// actually return just an integer which is put on an JS object currently.
|
||||
Constructor(String),
|
||||
|
||||
/// This function is intended to be a getter for a field on a class. The
|
||||
/// first argument is the internal pointer and the returned value is
|
||||
/// expected to be the field.
|
||||
Getter { class: String, field: String },
|
||||
|
||||
/// This function is intended to be a setter for a field on a class. The
|
||||
/// first argument is the internal pointer and the second argument is
|
||||
/// expected to be the field's new value.
|
||||
Setter { class: String, field: String },
|
||||
|
||||
/// This is a free function (ish) but scoped inside of a class name.
|
||||
StaticFunction { class: String, name: String },
|
||||
|
||||
/// This is a member function of a class where the first parameter is the
|
||||
/// implicit integer stored in the class instance.
|
||||
Method {
|
||||
class: String,
|
||||
name: String,
|
||||
/// Whether or not this is calling a by-value method in Rust and should
|
||||
/// clear the internal pointer in JS automatically.
|
||||
consumed: bool,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuxEnum {
|
||||
/// The name of this enum
|
||||
pub name: String,
|
||||
/// The copied Rust comments to forward to JS
|
||||
pub comments: String,
|
||||
/// A list of variants with their name and value
|
||||
pub variants: Vec<(String, u32)>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct AuxStruct {
|
||||
/// The name of this struct
|
||||
pub name: String,
|
||||
/// The copied Rust comments to forward to JS
|
||||
pub comments: String,
|
||||
/// Whether to generate helper methods for inspecting the class
|
||||
pub is_inspectable: bool,
|
||||
}
|
||||
|
||||
/// All possible types of imports that can be imported by a wasm module.
|
||||
///
|
||||
/// This `enum` is intended to map out what an imported value is. For example
|
||||
/// this contains a ton of shims and various ways you can call a function. The
|
||||
/// base variant here is `Value` which simply means "hook this up to the import"
|
||||
/// and the signatures will match up.
|
||||
///
|
||||
/// Note that this is *not* the same as the webidl bindings section. This is
|
||||
/// intended to be coupled with that to map out what actually gets hooked up to
|
||||
/// an import in the wasm module. The two work in tandem.
|
||||
///
|
||||
/// Some of these items here are native to JS (like `Value`, indexing
|
||||
/// operations, etc). Others are shims generated by wasm-bindgen (like `Closure`
|
||||
/// or `Instanceof`).
|
||||
#[derive(Debug)]
|
||||
pub enum AuxImport {
|
||||
/// This import is expected to simply be whatever is the value that's
|
||||
/// imported
|
||||
Value(AuxValue),
|
||||
|
||||
/// A static method on a class is being imported, and the `this` of the
|
||||
/// function call is expected to always be the class.
|
||||
ValueWithThis(JsImport, String),
|
||||
|
||||
/// This import is expected to be a function that takes an `anyref` and
|
||||
/// returns a `bool`. It's expected that it tests if the argument is an
|
||||
/// instance of (using `instanceof`) the name specified.
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
Instanceof(JsImport),
|
||||
|
||||
/// This import is expected to be a shim that returns the JS value named by
|
||||
/// `JsImport`.
|
||||
Static(JsImport),
|
||||
|
||||
/// This import is intended to manufacture a JS closure with the given
|
||||
/// signature and then return that back to Rust.
|
||||
Closure {
|
||||
mutable: bool, // whether or not this was a `FnMut` closure
|
||||
dtor: u32, // table element index of the destructor function
|
||||
adapter: AdapterId, // the adapter which translates the types for this closure
|
||||
nargs: usize,
|
||||
},
|
||||
|
||||
/// This import is expected to be a shim that simply calls the `foo` method
|
||||
/// on the first object, passing along all other parameters and returning
|
||||
/// the resulting value.
|
||||
StructuralMethod(String),
|
||||
|
||||
/// This import is a "structural getter" which simply returns the `.field`
|
||||
/// value of the first argument as an object.
|
||||
///
|
||||
/// e.g. `function(x) { return x.foo; }`
|
||||
StructuralGetter(String),
|
||||
|
||||
/// This import is a "structural getter" which simply returns the `.field`
|
||||
/// value of the specified class
|
||||
///
|
||||
/// e.g. `function() { return TheClass.foo; }`
|
||||
StructuralClassGetter(JsImport, String),
|
||||
|
||||
/// This import is a "structural setter" which simply sets the `.field`
|
||||
/// value of the first argument to the second argument.
|
||||
///
|
||||
/// e.g. `function(x, y) { x.foo = y; }`
|
||||
StructuralSetter(String),
|
||||
|
||||
/// This import is a "structural setter" which simply sets the `.field`
|
||||
/// value of the specified class to the first argument of the function.
|
||||
///
|
||||
/// e.g. `function(x) { TheClass.foo = x; }`
|
||||
StructuralClassSetter(JsImport, String),
|
||||
|
||||
/// This import is expected to be a shim that is an indexing getter of the
|
||||
/// JS class here, where the first argument of the function is the field to
|
||||
/// look up. The return value is the value of the field.
|
||||
///
|
||||
/// e.g. `function(x) { return TheClass[x]; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingGetterOfClass(JsImport),
|
||||
|
||||
/// This import is expected to be a shim that is an indexing getter of the
|
||||
/// first argument interpreted as an object where the field to look up is
|
||||
/// the second argument.
|
||||
///
|
||||
/// e.g. `function(x, y) { return x[y]; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingGetterOfObject,
|
||||
|
||||
/// This import is expected to be a shim that is an indexing setter of the
|
||||
/// JS class here, where the first argument of the function is the field to
|
||||
/// set and the second is the value to set it to.
|
||||
///
|
||||
/// e.g. `function(x, y) { TheClass[x] = y; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingSetterOfClass(JsImport),
|
||||
|
||||
/// This import is expected to be a shim that is an indexing setter of the
|
||||
/// first argument interpreted as an object where the next two fields are
|
||||
/// the field to set and the value to set it to.
|
||||
///
|
||||
/// e.g. `function(x, y, z) { x[y] = z; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingSetterOfObject,
|
||||
|
||||
/// This import is expected to be a shim that is an indexing deleter of the
|
||||
/// JS class here, where the first argument of the function is the field to
|
||||
/// delete.
|
||||
///
|
||||
/// e.g. `function(x) { delete TheClass[x]; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingDeleterOfClass(JsImport),
|
||||
|
||||
/// This import is expected to be a shim that is an indexing deleter of the
|
||||
/// first argument interpreted as an object where the second argument is
|
||||
/// the field to delete.
|
||||
///
|
||||
/// e.g. `function(x, y) { delete x[y]; }`
|
||||
///
|
||||
/// TODO: can we use `Reflect` or something like that to avoid an extra kind
|
||||
/// of import here?
|
||||
IndexingDeleterOfObject,
|
||||
|
||||
/// This import is a generated shim which will wrap the provided pointer in
|
||||
/// a JS object corresponding to the Class name given here. The class name
|
||||
/// is one that is exported from the Rust/wasm.
|
||||
///
|
||||
/// TODO: sort of like the export map below we should ideally create the
|
||||
/// `anyref` from within Rust itself and then return it directly rather than
|
||||
/// requiring an intrinsic here to do so.
|
||||
WrapInExportedClass(String),
|
||||
|
||||
/// This is an intrinsic function expected to be implemented with a JS glue
|
||||
/// shim. Each intrinsic has its own expected signature and implementation.
|
||||
Intrinsic(Intrinsic),
|
||||
}
|
||||
|
||||
/// Values that can be imported verbatim to hook up to an import.
|
||||
#[derive(Debug)]
|
||||
pub enum AuxValue {
|
||||
/// A bare JS value, no transformations, just put it in the slot.
|
||||
Bare(JsImport),
|
||||
|
||||
/// A getter function for the class listed for the field, acquired using
|
||||
/// `getOwnPropertyDescriptor`.
|
||||
Getter(JsImport, String),
|
||||
|
||||
/// Like `Getter`, but accesses a field of a class instead of an instance
|
||||
/// of the class.
|
||||
ClassGetter(JsImport, String),
|
||||
|
||||
/// Like `Getter`, except the `set` property.
|
||||
Setter(JsImport, String),
|
||||
|
||||
/// Like `Setter`, but for class fields instead of instance fields.
|
||||
ClassSetter(JsImport, String),
|
||||
}
|
||||
|
||||
/// What can actually be imported and typically a value in each of the variants
|
||||
/// above of `AuxImport`
|
||||
///
|
||||
/// A `JsImport` is intended to indicate what exactly is being imported for a
|
||||
/// particular operation.
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub struct JsImport {
|
||||
/// The base of whatever is being imported, either from a module, the global
|
||||
/// namespace, or similar.
|
||||
pub name: JsImportName,
|
||||
/// Various field accesses (like `.foo.bar.baz`) to hang off the `name`
|
||||
/// above.
|
||||
pub fields: Vec<String>,
|
||||
}
|
||||
|
||||
/// Return value of `determine_import` which is where we look at an imported
|
||||
/// function AST and figure out where it's actually being imported from
|
||||
/// (performing some validation checks and whatnot).
|
||||
#[derive(Debug, Hash, Eq, PartialEq, Clone)]
|
||||
pub enum JsImportName {
|
||||
/// An item is imported from the global scope. The `name` is what's
|
||||
/// imported.
|
||||
Global { name: String },
|
||||
/// Same as `Global`, except the `name` is imported via an ESM import from
|
||||
/// the specified `module` path.
|
||||
Module { module: String, name: String },
|
||||
/// Same as `Module`, except we're importing from a local module defined in
|
||||
/// a local JS snippet.
|
||||
LocalModule { module: String, name: String },
|
||||
/// Same as `Module`, except we're importing from an `inline_js` attribute
|
||||
InlineJs {
|
||||
unique_crate_identifier: String,
|
||||
snippet_idx_in_crate: usize,
|
||||
name: String,
|
||||
},
|
||||
/// A global import which may have a number of vendor prefixes associated
|
||||
/// with it, like `webkitAudioPrefix`. The `name` is the name to test
|
||||
/// whether it's prefixed.
|
||||
VendorPrefixed { name: String, prefixes: Vec<String> },
|
||||
}
|
||||
|
||||
impl walrus::CustomSection for WasmBindgenAux {
|
||||
fn name(&self) -> &str {
|
||||
"wasm-bindgen custom section"
|
||||
}
|
||||
|
||||
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
|
||||
panic!("shouldn't emit custom sections just yet");
|
||||
}
|
||||
}
|
382
crates/cli-support/src/wit/outgoing.rs
Normal file
382
crates/cli-support/src/wit/outgoing.rs
Normal file
@ -0,0 +1,382 @@
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::wit::{AdapterType, Instruction, InstructionBuilder};
|
||||
use crate::wit::{InstructionData, StackChange};
|
||||
use anyhow::{bail, format_err, Error};
|
||||
use walrus::ValType;
|
||||
|
||||
impl InstructionBuilder<'_, '_> {
|
||||
/// Processes one more `Descriptor` as an argument to a JS function that
|
||||
/// wasm is calling.
|
||||
///
|
||||
/// This will internally skip `Unit` and otherwise build up the `bindings`
|
||||
/// map and ensure that it's correctly mapped from wasm to JS.
|
||||
pub fn outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
if let Descriptor::Unit = arg {
|
||||
return Ok(());
|
||||
}
|
||||
// Similar rationale to `incoming.rs` around these sanity checks.
|
||||
let input_before = self.input.len();
|
||||
let output_before = self.output.len();
|
||||
self._outgoing(arg)?;
|
||||
assert_eq!(output_before + 1, self.output.len());
|
||||
assert!(input_before < self.input.len());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn _outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Boolean => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::BoolFromI32,
|
||||
&[AdapterType::Bool],
|
||||
);
|
||||
}
|
||||
Descriptor::Anyref => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::AnyrefLoadOwned,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::I8 => self.outgoing_i32(AdapterType::S8),
|
||||
Descriptor::U8 => self.outgoing_i32(AdapterType::U8),
|
||||
Descriptor::I16 => self.outgoing_i32(AdapterType::S16),
|
||||
Descriptor::U16 => self.outgoing_i32(AdapterType::U16),
|
||||
Descriptor::I32 => self.outgoing_i32(AdapterType::S32),
|
||||
Descriptor::U32 => self.outgoing_i32(AdapterType::U32),
|
||||
Descriptor::F32 => {
|
||||
self.get(AdapterType::F32);
|
||||
self.output.push(AdapterType::F32);
|
||||
}
|
||||
Descriptor::F64 => {
|
||||
self.get(AdapterType::F64);
|
||||
self.output.push(AdapterType::F64);
|
||||
}
|
||||
Descriptor::Enum { .. } => self.outgoing_i32(AdapterType::U32),
|
||||
|
||||
Descriptor::Char => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::StringFromChar,
|
||||
&[AdapterType::String],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::I64FromLoHi { signed },
|
||||
&[if signed {
|
||||
AdapterType::S64
|
||||
} else {
|
||||
AdapterType::U64
|
||||
}],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::RustStruct(class) => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::RustFromI32 {
|
||||
class: class.to_string(),
|
||||
},
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::Ref(d) => self.outgoing_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.outgoing_ref(true, d)?,
|
||||
|
||||
Descriptor::CachedString => self.cached_string(false, true)?,
|
||||
|
||||
Descriptor::String => {
|
||||
// fetch the ptr/length ...
|
||||
self.get(AdapterType::I32);
|
||||
self.get(AdapterType::I32);
|
||||
|
||||
// ... then defer a call to `free` to happen later
|
||||
let free = self.cx.free()?;
|
||||
let std = wit_walrus::Instruction::DeferCallCore(free);
|
||||
self.instructions.push(InstructionData {
|
||||
instr: Instruction::Standard(std),
|
||||
stack_change: StackChange::Modified {
|
||||
popped: 2,
|
||||
pushed: 2,
|
||||
},
|
||||
});
|
||||
|
||||
// ... and then convert it to a string type
|
||||
let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?);
|
||||
self.instructions.push(InstructionData {
|
||||
instr: Instruction::Standard(std),
|
||||
stack_change: StackChange::Modified {
|
||||
popped: 2,
|
||||
pushed: 1,
|
||||
},
|
||||
});
|
||||
self.output.push(AdapterType::String);
|
||||
}
|
||||
|
||||
Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported argument type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let mem = self.cx.memory()?;
|
||||
let free = self.cx.free()?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::VectorLoad { kind, mem, free },
|
||||
&[AdapterType::Vector(kind)],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::Option(d) => self.outgoing_option(d)?,
|
||||
|
||||
Descriptor::Function(_) | Descriptor::Closure(_) | Descriptor::Slice(_) => bail!(
|
||||
"unsupported argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
|
||||
// nothing to do
|
||||
Descriptor::Unit => {}
|
||||
|
||||
// Largely synthetic and can't show up
|
||||
Descriptor::ClampedU8 => unreachable!(),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outgoing_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::TableGet,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::CachedString => self.cached_string(false, false)?,
|
||||
|
||||
Descriptor::String => {
|
||||
let std = wit_walrus::Instruction::MemoryToString(self.cx.memory()?);
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::Standard(std),
|
||||
&[AdapterType::String],
|
||||
);
|
||||
}
|
||||
Descriptor::Slice(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported argument type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let mem = self.cx.memory()?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::View { kind, mem },
|
||||
&[AdapterType::Vector(kind)],
|
||||
);
|
||||
}
|
||||
|
||||
Descriptor::Function(descriptor) => {
|
||||
// synthesize the a/b arguments that aren't present in the
|
||||
// signature from wasm-bindgen but are present in the wasm file.
|
||||
let mut descriptor = (**descriptor).clone();
|
||||
let nargs = descriptor.arguments.len();
|
||||
descriptor.arguments.insert(0, Descriptor::I32);
|
||||
descriptor.arguments.insert(0, Descriptor::I32);
|
||||
let adapter = self
|
||||
.cx
|
||||
.table_element_adapter(descriptor.shim_idx, descriptor)?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::StackClosure {
|
||||
adapter,
|
||||
nargs,
|
||||
mutable,
|
||||
},
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported reference argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outgoing_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
// This is set to `undefined` in the `None` case and otherwise
|
||||
// is the valid owned index.
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::AnyrefLoadOwned,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::I8 => self.out_option_sentinel(),
|
||||
Descriptor::U8 => self.out_option_sentinel(),
|
||||
Descriptor::I16 => self.out_option_sentinel(),
|
||||
Descriptor::U16 => self.out_option_sentinel(),
|
||||
Descriptor::I32 => self.option_native(true, ValType::I32),
|
||||
Descriptor::U32 => self.option_native(false, ValType::I32),
|
||||
Descriptor::F32 => self.option_native(true, ValType::F32),
|
||||
Descriptor::F64 => self.option_native(true, ValType::F64),
|
||||
Descriptor::I64 | Descriptor::U64 => {
|
||||
let signed = match arg {
|
||||
Descriptor::I64 => true,
|
||||
_ => false,
|
||||
};
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 3],
|
||||
Instruction::Option64FromI32 { signed },
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::Boolean => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::OptionBoolFromI32,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::Char => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::OptionCharFromI32,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::Enum { hole } => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::OptionEnumFromI32 { hole: *hole },
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::RustStruct(name) => {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::OptionRustFromI32 {
|
||||
class: name.to_string(),
|
||||
},
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::Ref(d) => self.outgoing_option_ref(false, d)?,
|
||||
Descriptor::RefMut(d) => self.outgoing_option_ref(true, d)?,
|
||||
|
||||
Descriptor::CachedString => self.cached_string(true, true)?,
|
||||
|
||||
Descriptor::String | Descriptor::Vector(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let mem = self.cx.memory()?;
|
||||
let free = self.cx.free()?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::OptionVectorLoad { kind, mem, free },
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
|
||||
_ => bail!(
|
||||
"unsupported optional argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outgoing_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> {
|
||||
match arg {
|
||||
Descriptor::Anyref => {
|
||||
// If this is `Some` then it's the index, otherwise if it's
|
||||
// `None` then it's the index pointing to undefined.
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::TableGet,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
Descriptor::CachedString => self.cached_string(true, false)?,
|
||||
Descriptor::String | Descriptor::Slice(_) => {
|
||||
let kind = arg.vector_kind().ok_or_else(|| {
|
||||
format_err!(
|
||||
"unsupported optional slice type for calling JS function from Rust {:?}",
|
||||
arg
|
||||
)
|
||||
})?;
|
||||
let mem = self.cx.memory()?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::OptionView { kind, mem },
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
_ => bail!(
|
||||
"unsupported optional ref argument type for calling JS function from Rust: {:?}",
|
||||
arg
|
||||
),
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn outgoing_i32(&mut self, output: AdapterType) {
|
||||
let std = wit_walrus::Instruction::WasmToInt {
|
||||
input: walrus::ValType::I32,
|
||||
output: output.to_wit().unwrap(),
|
||||
trap: false,
|
||||
};
|
||||
self.instruction(&[AdapterType::I32], Instruction::Standard(std), &[output]);
|
||||
}
|
||||
|
||||
fn cached_string(&mut self, optional: bool, owned: bool) -> Result<(), Error> {
|
||||
let mem = self.cx.memory()?;
|
||||
let free = self.cx.free()?;
|
||||
self.instruction(
|
||||
&[AdapterType::I32; 2],
|
||||
Instruction::CachedStringLoad {
|
||||
owned,
|
||||
optional,
|
||||
mem,
|
||||
free,
|
||||
},
|
||||
&[AdapterType::String],
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn option_native(&mut self, signed: bool, ty: ValType) {
|
||||
self.instruction(
|
||||
&[AdapterType::I32, AdapterType::from_wasm(ty).unwrap()],
|
||||
Instruction::ToOptionNative { signed, ty },
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
|
||||
fn out_option_sentinel(&mut self) {
|
||||
self.instruction(
|
||||
&[AdapterType::I32],
|
||||
Instruction::OptionU32Sentinel,
|
||||
&[AdapterType::Anyref],
|
||||
);
|
||||
}
|
||||
}
|
414
crates/cli-support/src/wit/section.rs
Normal file
414
crates/cli-support/src/wit/section.rs
Normal file
@ -0,0 +1,414 @@
|
||||
//! Support for generating a standard wasm interface types
|
||||
//!
|
||||
//! This module has all the necessary support for generating a full-fledged
|
||||
//! standard wasm interface types section as defined by the `wit_walrus`
|
||||
//! crate. This module also critically assumes that the WebAssembly module
|
||||
//! being generated **must be standalone**. In this mode all sorts of features
|
||||
//! supported by `#[wasm_bindgen]` aren't actually supported, such as closures,
|
||||
//! imports of global js names, js getters/setters, exporting structs, etc.
|
||||
//! These features may all eventually come to the standard bindings proposal,
|
||||
//! but it will likely take some time. In the meantime this module simply focuses
|
||||
//! on taking what's already a valid wasm module and letting it through with a
|
||||
//! standard WebIDL custom section. All other modules generate an error during
|
||||
//! this binding process.
|
||||
//!
|
||||
//! Note that when this function is called and used we're also not actually
|
||||
//! generating any JS glue. Any JS glue currently generated is also invalid if
|
||||
//! the module contains the wasm bindings section and it's actually respected.
|
||||
|
||||
use crate::wit::{AdapterId, AdapterJsImportKind, AdapterType, Instruction};
|
||||
use crate::wit::{AdapterKind, NonstandardWitSection, WasmBindgenAux};
|
||||
use crate::wit::{AuxExport, InstructionData};
|
||||
use crate::wit::{AuxExportKind, AuxImport, AuxValue, JsImport, JsImportName};
|
||||
use anyhow::{anyhow, bail, Context, Error};
|
||||
use std::collections::HashMap;
|
||||
use walrus::Module;
|
||||
|
||||
pub fn add(
|
||||
module: &mut Module,
|
||||
aux: &WasmBindgenAux,
|
||||
nonstandard: &NonstandardWitSection,
|
||||
) -> Result<(), Error> {
|
||||
let mut section = wit_walrus::WasmInterfaceTypes::default();
|
||||
let WasmBindgenAux {
|
||||
extra_typescript: _, // ignore this even if it's specified
|
||||
local_modules,
|
||||
snippets,
|
||||
package_jsons,
|
||||
export_map,
|
||||
import_map,
|
||||
imports_with_catch,
|
||||
imports_with_variadic,
|
||||
imports_with_assert_no_shim: _, // not relevant for this purpose
|
||||
enums,
|
||||
structs,
|
||||
anyref_table: _, // not relevant
|
||||
anyref_alloc: _, // not relevant
|
||||
anyref_drop_slice: _, // not relevant
|
||||
} = aux;
|
||||
|
||||
let adapter_context = |id: AdapterId| {
|
||||
if let Some((name, _)) = nonstandard.exports.iter().find(|p| p.1 == id) {
|
||||
return format!("in function export `{}`", name);
|
||||
}
|
||||
if let Some((core, _)) = nonstandard.implements.iter().find(|p| p.1 == id) {
|
||||
let import = module.imports.get(*core);
|
||||
return format!(
|
||||
"in function import from `{}::{}`",
|
||||
import.module, import.name
|
||||
);
|
||||
}
|
||||
unreachable!()
|
||||
};
|
||||
|
||||
let mut us2walrus = HashMap::new();
|
||||
for (us, func) in nonstandard.adapters.iter() {
|
||||
if let Some(export) = export_map.get(us) {
|
||||
check_standard_export(export).context(adapter_context(*us))?;
|
||||
}
|
||||
if let Some(import) = import_map.get(us) {
|
||||
check_standard_import(import).context(adapter_context(*us))?;
|
||||
}
|
||||
let params = translate_tys(&func.params).context(adapter_context(*us))?;
|
||||
let results = translate_tys(&func.results).context(adapter_context(*us))?;
|
||||
let ty = section.types.add(params, results);
|
||||
let walrus = match &func.kind {
|
||||
AdapterKind::Local { .. } => section.funcs.add_local(ty, Vec::new()),
|
||||
AdapterKind::Import {
|
||||
module,
|
||||
name,
|
||||
kind: AdapterJsImportKind::Normal,
|
||||
} => section.add_import_func(module, name, ty).0,
|
||||
AdapterKind::Import {
|
||||
module,
|
||||
name,
|
||||
kind: AdapterJsImportKind::Constructor,
|
||||
} => {
|
||||
bail!(
|
||||
"interfaces types doesn't support import of `{}::{}` \
|
||||
as a constructor",
|
||||
module,
|
||||
name
|
||||
);
|
||||
}
|
||||
AdapterKind::Import {
|
||||
module,
|
||||
name,
|
||||
kind: AdapterJsImportKind::Method,
|
||||
} => {
|
||||
bail!(
|
||||
"interfaces types doesn't support import of `{}::{}` \
|
||||
as a method",
|
||||
module,
|
||||
name
|
||||
);
|
||||
}
|
||||
};
|
||||
us2walrus.insert(*us, walrus);
|
||||
}
|
||||
|
||||
for (core, adapter) in nonstandard.implements.iter() {
|
||||
let core = match module.imports.get(*core).kind {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => bail!("cannot implement a non-function"),
|
||||
};
|
||||
section.implements.add(us2walrus[adapter], core);
|
||||
}
|
||||
|
||||
for (name, adapter) in nonstandard.exports.iter() {
|
||||
section.exports.add(name, us2walrus[adapter]);
|
||||
}
|
||||
|
||||
for (id, func) in nonstandard.adapters.iter() {
|
||||
let instructions = match &func.kind {
|
||||
AdapterKind::Local { instructions } => instructions,
|
||||
AdapterKind::Import { .. } => continue,
|
||||
};
|
||||
let result = match &mut section.funcs.get_mut(us2walrus[id]).kind {
|
||||
wit_walrus::FuncKind::Local(i) => i,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
|
||||
for instruction in instructions {
|
||||
result.push(
|
||||
translate_instruction(instruction, &us2walrus, module)
|
||||
.with_context(|| adapter_context(*id))?,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if let Some((name, _)) = local_modules.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS modules being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some((name, _)) = snippets.iter().filter(|(_, v)| !v.is_empty()).next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
local JS snippets being specified as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(path) = package_jsons.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
package.json being consumed as well, `{}` cannot be used \
|
||||
since a standalone wasm file is being generated",
|
||||
path.display(),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(id) = imports_with_catch.iter().next() {
|
||||
bail!(
|
||||
"{}\ngenerating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(catch)]`",
|
||||
adapter_context(*id),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(id) = imports_with_variadic.iter().next() {
|
||||
bail!(
|
||||
"{}\ngenerating a bindings section is currently incompatible with \
|
||||
`#[wasm_bindgen(variadic)]`",
|
||||
adapter_context(*id),
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(enum_) = enums.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting an `enum` from the wasm file, cannot export `{}`",
|
||||
enum_.name,
|
||||
);
|
||||
}
|
||||
|
||||
if let Some(struct_) = structs.iter().next() {
|
||||
bail!(
|
||||
"generating a bindings section is currently incompatible with \
|
||||
exporting a `struct` from the wasm file, cannot export `{}`",
|
||||
struct_.name,
|
||||
);
|
||||
}
|
||||
|
||||
module.customs.add(section);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn translate_instruction(
|
||||
instr: &InstructionData,
|
||||
us2walrus: &HashMap<AdapterId, wit_walrus::FuncId>,
|
||||
module: &Module,
|
||||
) -> Result<wit_walrus::Instruction, Error> {
|
||||
use Instruction::*;
|
||||
|
||||
match &instr.instr {
|
||||
Standard(s) => Ok(s.clone()),
|
||||
CallAdapter(id) => {
|
||||
let id = us2walrus[id];
|
||||
Ok(wit_walrus::Instruction::CallAdapter(id))
|
||||
}
|
||||
CallExport(e) => match module.exports.get(*e).item {
|
||||
walrus::ExportItem::Function(f) => Ok(wit_walrus::Instruction::CallCore(f)),
|
||||
_ => bail!("can only call exported functions"),
|
||||
},
|
||||
CallTableElement(e) => {
|
||||
let table = module
|
||||
.tables
|
||||
.main_function_table()?
|
||||
.ok_or_else(|| anyhow!("no function table found in module"))?;
|
||||
let functions = match &module.tables.get(table).kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
match functions.elements.get(*e as usize) {
|
||||
Some(Some(f)) => Ok(wit_walrus::Instruction::CallCore(*f)),
|
||||
_ => bail!("expected to find an element of the function table"),
|
||||
}
|
||||
}
|
||||
StoreRetptr { .. } | LoadRetptr { .. } | Retptr => {
|
||||
bail!("return pointers aren't supported in wasm interface types");
|
||||
}
|
||||
I32FromBool | BoolFromI32 => {
|
||||
bail!("booleans aren't supported in wasm interface types");
|
||||
}
|
||||
I32FromStringFirstChar | StringFromChar => {
|
||||
bail!("chars aren't supported in wasm interface types");
|
||||
}
|
||||
I32FromAnyrefOwned | I32FromAnyrefBorrow | AnyrefLoadOwned | TableGet => {
|
||||
bail!("anyref pass failed to sink into wasm module");
|
||||
}
|
||||
I32FromAnyrefRustOwned { .. } | I32FromAnyrefRustBorrow { .. } | RustFromI32 { .. } => {
|
||||
bail!("rust types aren't supported in wasm interface types");
|
||||
}
|
||||
I32Split64 { .. } | I64FromLoHi { .. } => {
|
||||
bail!("64-bit integers aren't supported in wasm-bindgen");
|
||||
}
|
||||
I32SplitOption64 { .. }
|
||||
| I32FromOptionAnyref
|
||||
| I32FromOptionU32Sentinel
|
||||
| I32FromOptionRust { .. }
|
||||
| I32FromOptionBool
|
||||
| I32FromOptionChar
|
||||
| I32FromOptionEnum { .. }
|
||||
| FromOptionNative { .. }
|
||||
| OptionVector { .. }
|
||||
| OptionRustFromI32 { .. }
|
||||
| OptionVectorLoad { .. }
|
||||
| OptionView { .. }
|
||||
| OptionU32Sentinel
|
||||
| ToOptionNative { .. }
|
||||
| OptionBoolFromI32
|
||||
| OptionCharFromI32
|
||||
| OptionEnumFromI32 { .. }
|
||||
| Option64FromI32 { .. } => {
|
||||
bail!("optional types aren't supported in wasm bindgen");
|
||||
}
|
||||
MutableSliceToMemory { .. } | VectorToMemory { .. } | VectorLoad { .. } | View { .. } => {
|
||||
bail!("vector slices aren't supported in wasm interface types yet");
|
||||
}
|
||||
CachedStringLoad { .. } => {
|
||||
bail!("cached strings aren't supported in wasm interface types");
|
||||
}
|
||||
StackClosure { .. } => {
|
||||
bail!("closures aren't supported in wasm interface types");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn check_standard_import(import: &AuxImport) -> Result<(), Error> {
|
||||
let desc_js = |js: &JsImport| {
|
||||
let mut extra = String::new();
|
||||
for field in js.fields.iter() {
|
||||
extra.push_str(".");
|
||||
extra.push_str(field);
|
||||
}
|
||||
match &js.name {
|
||||
JsImportName::Global { name } | JsImportName::VendorPrefixed { name, .. } => {
|
||||
format!("global `{}{}`", name, extra)
|
||||
}
|
||||
JsImportName::Module { module, name } => {
|
||||
format!("`{}{}` from '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::LocalModule { module, name } => {
|
||||
format!("`{}{}` from local module '{}'", name, extra, module)
|
||||
}
|
||||
JsImportName::InlineJs {
|
||||
unique_crate_identifier,
|
||||
name,
|
||||
..
|
||||
} => format!(
|
||||
"`{}{}` from inline js in '{}'",
|
||||
name, extra, unique_crate_identifier
|
||||
),
|
||||
}
|
||||
};
|
||||
|
||||
let item = match import {
|
||||
AuxImport::Value(AuxValue::Bare(js)) => {
|
||||
if js.fields.len() == 0 {
|
||||
if let JsImportName::Module { .. } = js.name {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
desc_js(js)
|
||||
}
|
||||
AuxImport::Value(AuxValue::Getter(js, name))
|
||||
| AuxImport::Value(AuxValue::Setter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassGetter(js, name))
|
||||
| AuxImport::Value(AuxValue::ClassSetter(js, name)) => {
|
||||
format!("field access of `{}` for {}", name, desc_js(js))
|
||||
}
|
||||
AuxImport::ValueWithThis(js, method) => format!("method `{}.{}`", desc_js(js), method),
|
||||
AuxImport::Instanceof(js) => format!("instance of check of {}", desc_js(js)),
|
||||
AuxImport::Static(js) => format!("static js value {}", desc_js(js)),
|
||||
AuxImport::StructuralMethod(name) => format!("structural method `{}`", name),
|
||||
AuxImport::StructuralGetter(name)
|
||||
| AuxImport::StructuralSetter(name)
|
||||
| AuxImport::StructuralClassGetter(_, name)
|
||||
| AuxImport::StructuralClassSetter(_, name) => {
|
||||
format!("structural field access of `{}`", name)
|
||||
}
|
||||
AuxImport::IndexingDeleterOfClass(_)
|
||||
| AuxImport::IndexingDeleterOfObject
|
||||
| AuxImport::IndexingGetterOfClass(_)
|
||||
| AuxImport::IndexingGetterOfObject
|
||||
| AuxImport::IndexingSetterOfClass(_)
|
||||
| AuxImport::IndexingSetterOfObject => format!("indexing getters/setters/deleters"),
|
||||
AuxImport::WrapInExportedClass(name) => {
|
||||
format!("wrapping a pointer in a `{}` js class wrapper", name)
|
||||
}
|
||||
AuxImport::Intrinsic(intrinsic) => {
|
||||
format!("wasm-bindgen specific intrinsic `{}`", intrinsic.name())
|
||||
}
|
||||
AuxImport::Closure { .. } => format!("creating a `Closure` wrapper"),
|
||||
};
|
||||
bail!(
|
||||
"cannot generate a standalone WebAssembly module which \
|
||||
contains an import of {} since it requires JS glue",
|
||||
item
|
||||
);
|
||||
}
|
||||
|
||||
fn check_standard_export(export: &AuxExport) -> Result<(), Error> {
|
||||
// First up make sure this is something that's actually valid to export
|
||||
// form a vanilla WebAssembly module with WebIDL bindings.
|
||||
match &export.kind {
|
||||
AuxExportKind::Function(_) => Ok(()),
|
||||
AuxExportKind::Constructor(name) => {
|
||||
bail!(
|
||||
"cannot export `{}` constructor function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
name,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Getter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` getter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::Setter { class, field } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` setter function when generating \
|
||||
a standalone WebAssembly module with no JS glue",
|
||||
class,
|
||||
field,
|
||||
);
|
||||
}
|
||||
AuxExportKind::StaticFunction { class, name } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` static function when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
AuxExportKind::Method { class, name, .. } => {
|
||||
bail!(
|
||||
"cannot export `{}::{}` method when \
|
||||
generating a standalone WebAssembly module with no \
|
||||
JS glue",
|
||||
class,
|
||||
name
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn translate_tys(tys: &[AdapterType]) -> Result<Vec<wit_walrus::ValType>, Error> {
|
||||
tys.iter()
|
||||
.map(|ty| {
|
||||
ty.to_wit()
|
||||
.ok_or_else(|| anyhow!("type {:?} isn't supported in standard interface types", ty))
|
||||
})
|
||||
.collect()
|
||||
}
|
359
crates/cli-support/src/wit/standard.rs
Normal file
359
crates/cli-support/src/wit/standard.rs
Normal file
@ -0,0 +1,359 @@
|
||||
use crate::descriptor::VectorKind;
|
||||
use std::borrow::Cow;
|
||||
use std::collections::HashMap;
|
||||
use walrus::{ImportId, TypedCustomSectionId};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
pub struct NonstandardWitSection {
|
||||
/// A list of adapter functions, keyed by their id.
|
||||
pub adapters: HashMap<AdapterId, Adapter>,
|
||||
|
||||
/// A list of pairs for adapter functions that implement core wasm imports.
|
||||
pub implements: Vec<(ImportId, AdapterId)>,
|
||||
|
||||
/// A list of adapter functions and the names they're exported under.
|
||||
pub exports: Vec<(String, AdapterId)>,
|
||||
}
|
||||
|
||||
pub type NonstandardWitSectionId = TypedCustomSectionId<NonstandardWitSection>;
|
||||
|
||||
#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, PartialOrd, Ord)]
|
||||
pub struct AdapterId(pub usize);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Adapter {
|
||||
pub id: AdapterId,
|
||||
pub params: Vec<AdapterType>,
|
||||
pub results: Vec<AdapterType>,
|
||||
pub kind: AdapterKind,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum AdapterKind {
|
||||
Local {
|
||||
instructions: Vec<InstructionData>,
|
||||
},
|
||||
Import {
|
||||
module: String,
|
||||
name: String,
|
||||
kind: AdapterJsImportKind,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InstructionData {
|
||||
pub instr: Instruction,
|
||||
pub stack_change: StackChange,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum StackChange {
|
||||
Modified { pushed: usize, popped: usize },
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq)]
|
||||
pub enum AdapterJsImportKind {
|
||||
/// The first argument is an `anyref` which is the `this` of the function
|
||||
/// call
|
||||
Method,
|
||||
/// The value imported should be invoked as `new`
|
||||
Constructor,
|
||||
/// A bland function import
|
||||
Normal,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub enum AdapterType {
|
||||
S8,
|
||||
S16,
|
||||
S32,
|
||||
S64,
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
U64,
|
||||
F32,
|
||||
F64,
|
||||
String,
|
||||
Anyref,
|
||||
Bool,
|
||||
I32,
|
||||
I64,
|
||||
Vector(VectorKind),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Instruction {
|
||||
/// A known instruction in the "standard"
|
||||
Standard(wit_walrus::Instruction),
|
||||
|
||||
/// A call to one of our own defined adapters, similar to the standard
|
||||
/// call-adapter instruction
|
||||
CallAdapter(AdapterId),
|
||||
/// Call an exported function in the core module
|
||||
CallExport(walrus::ExportId),
|
||||
/// Call an element in the function table of the core module
|
||||
CallTableElement(u32),
|
||||
|
||||
/// An instruction to store `ty` at the `offset` index in the return pointer
|
||||
StoreRetptr {
|
||||
ty: AdapterType,
|
||||
offset: usize,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
/// An instruction to load `ty` at the `offset` index from the return pointer
|
||||
LoadRetptr {
|
||||
ty: AdapterType,
|
||||
offset: usize,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
/// An instruction which pushes the return pointer onto the stack.
|
||||
Retptr,
|
||||
|
||||
/// Pops a `bool` from the stack and pushes an `i32` equivalent
|
||||
I32FromBool,
|
||||
/// Pops a `string` from the stack and pushes the first character as `i32`
|
||||
I32FromStringFirstChar,
|
||||
/// Pops an `anyref` from the stack, allocates space in the anyref table,
|
||||
/// returns the index it was stored at.
|
||||
I32FromAnyrefOwned,
|
||||
/// Pops an `anyref` from the stack, pushes it onto the anyref wasm table
|
||||
/// stack, and returns the index it was stored at.
|
||||
I32FromAnyrefBorrow,
|
||||
/// Pops an `anyref` from the stack, assumes it's a Rust class given, and
|
||||
/// deallocates the JS object and returns the i32 Rust pointer.
|
||||
I32FromAnyrefRustOwned {
|
||||
class: String,
|
||||
},
|
||||
/// Pops an `anyref` from the stack, assumes it's a Rust class given, and
|
||||
/// passes the pointer to Rust which will be borrowed for the duration of a
|
||||
/// call
|
||||
I32FromAnyrefRustBorrow {
|
||||
class: String,
|
||||
},
|
||||
/// Pops an `anyref` from the stack, pushes 0 if it's "none" or the
|
||||
/// consumed pointer value if it's "some".
|
||||
I32FromOptionRust {
|
||||
class: String,
|
||||
},
|
||||
/// Pops an `s64` or `u64` from the stack, pushing two `i32` values.
|
||||
I32Split64 {
|
||||
signed: bool,
|
||||
},
|
||||
/// Pops an `s64` or `u64` from the stack, pushing three `i32` values.
|
||||
/// First is the "some/none" bit, and the next is the low bits, and the
|
||||
/// next is the high bits.
|
||||
I32SplitOption64 {
|
||||
signed: bool,
|
||||
},
|
||||
/// Pops an `anyref` from the stack, pushes either 0 if it's "none" or and
|
||||
/// index into the owned wasm table it was stored at if it's "some"
|
||||
I32FromOptionAnyref,
|
||||
/// Pops an `anyref` from the stack, pushes either a sentinel value if it's
|
||||
/// "none" or the integer value of it if it's "some"
|
||||
I32FromOptionU32Sentinel,
|
||||
/// Pops an `anyref` from the stack, pushes 0 for "none", 1 for
|
||||
/// "some(false)', and 2 for "some(true)"
|
||||
I32FromOptionBool,
|
||||
/// Pops an `anyref` from the stack, pushes a sentinel for "none" or the
|
||||
/// value if it's "some"
|
||||
I32FromOptionChar,
|
||||
/// Pops an `anyref` from the stack, pushes `hole` for "none" or the
|
||||
/// value if it's "some"
|
||||
I32FromOptionEnum {
|
||||
hole: u32,
|
||||
},
|
||||
/// Pops any anyref from the stack and then pushes two values. First is a
|
||||
/// 0/1 if it's none/some and second is `ty` value if it was there or 0 if
|
||||
/// it wasn't there.
|
||||
FromOptionNative {
|
||||
ty: walrus::ValType,
|
||||
},
|
||||
|
||||
/// Pops a vector value of `kind` from the stack, allocates memory with
|
||||
/// `malloc`, and then copies all the data into `mem`. Pushes the pointer
|
||||
/// and length as i32.
|
||||
VectorToMemory {
|
||||
kind: VectorKind,
|
||||
malloc: walrus::FunctionId,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
MutableSliceToMemory {
|
||||
kind: VectorKind,
|
||||
malloc: walrus::FunctionId,
|
||||
free: walrus::FunctionId,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
|
||||
/// Pops an anyref, pushes pointer/length or all zeros
|
||||
OptionVector {
|
||||
kind: VectorKind,
|
||||
malloc: walrus::FunctionId,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
|
||||
/// pops a `i32`, pushes `bool`
|
||||
BoolFromI32,
|
||||
/// pops `i32`, loads anyref at that slot, dealloates anyref, pushes `anyref`
|
||||
AnyrefLoadOwned,
|
||||
/// pops `i32`, pushes string from that `char`
|
||||
StringFromChar,
|
||||
/// pops two `i32`, pushes a 64-bit number
|
||||
I64FromLoHi {
|
||||
signed: bool,
|
||||
},
|
||||
/// pops `i32`, pushes an anyref for the wrapped rust class
|
||||
RustFromI32 {
|
||||
class: String,
|
||||
},
|
||||
OptionRustFromI32 {
|
||||
class: String,
|
||||
},
|
||||
/// pops ptr/length i32, loads string from cache
|
||||
CachedStringLoad {
|
||||
owned: bool,
|
||||
optional: bool,
|
||||
mem: walrus::MemoryId,
|
||||
free: walrus::FunctionId,
|
||||
},
|
||||
/// pops ptr/length, pushes a vector, frees the original data
|
||||
VectorLoad {
|
||||
kind: VectorKind,
|
||||
mem: walrus::MemoryId,
|
||||
free: walrus::FunctionId,
|
||||
},
|
||||
/// pops ptr/length, pushes a vector, frees the original data
|
||||
OptionVectorLoad {
|
||||
kind: VectorKind,
|
||||
mem: walrus::MemoryId,
|
||||
free: walrus::FunctionId,
|
||||
},
|
||||
/// pops i32, loads anyref from anyref table
|
||||
TableGet,
|
||||
/// pops two i32 data pointers, pushes an anyref closure
|
||||
StackClosure {
|
||||
adapter: AdapterId,
|
||||
nargs: usize,
|
||||
mutable: bool,
|
||||
},
|
||||
/// pops two i32 data pointers, pushes a vector view
|
||||
View {
|
||||
kind: VectorKind,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
/// pops two i32 data pointers, pushes a vector view
|
||||
OptionView {
|
||||
kind: VectorKind,
|
||||
mem: walrus::MemoryId,
|
||||
},
|
||||
/// pops i32, pushes it viewed as an optional value with a known sentinel
|
||||
OptionU32Sentinel,
|
||||
/// pops an i32, then `ty`, then pushes anyref
|
||||
ToOptionNative {
|
||||
ty: walrus::ValType,
|
||||
signed: bool,
|
||||
},
|
||||
OptionBoolFromI32,
|
||||
OptionCharFromI32,
|
||||
OptionEnumFromI32 {
|
||||
hole: u32,
|
||||
},
|
||||
Option64FromI32 {
|
||||
signed: bool,
|
||||
},
|
||||
}
|
||||
|
||||
impl AdapterType {
|
||||
pub fn from_wit(wit: wit_walrus::ValType) -> AdapterType {
|
||||
match wit {
|
||||
wit_walrus::ValType::S8 => AdapterType::S8,
|
||||
wit_walrus::ValType::S16 => AdapterType::S16,
|
||||
wit_walrus::ValType::S32 => AdapterType::S32,
|
||||
wit_walrus::ValType::S64 => AdapterType::S64,
|
||||
wit_walrus::ValType::U8 => AdapterType::U8,
|
||||
wit_walrus::ValType::U16 => AdapterType::U16,
|
||||
wit_walrus::ValType::U32 => AdapterType::U32,
|
||||
wit_walrus::ValType::U64 => AdapterType::U64,
|
||||
wit_walrus::ValType::F32 => AdapterType::F32,
|
||||
wit_walrus::ValType::F64 => AdapterType::F64,
|
||||
wit_walrus::ValType::String => AdapterType::String,
|
||||
wit_walrus::ValType::Anyref => AdapterType::Anyref,
|
||||
wit_walrus::ValType::I32 => AdapterType::I32,
|
||||
wit_walrus::ValType::I64 => AdapterType::I64,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_wasm(wasm: walrus::ValType) -> Option<AdapterType> {
|
||||
Some(match wasm {
|
||||
walrus::ValType::I32 => AdapterType::I32,
|
||||
walrus::ValType::I64 => AdapterType::I64,
|
||||
walrus::ValType::F32 => AdapterType::F32,
|
||||
walrus::ValType::F64 => AdapterType::F64,
|
||||
walrus::ValType::Anyref => AdapterType::Anyref,
|
||||
walrus::ValType::V128 => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_wasm(&self) -> Option<walrus::ValType> {
|
||||
Some(match self {
|
||||
AdapterType::I32 => walrus::ValType::I32,
|
||||
AdapterType::I64 => walrus::ValType::I64,
|
||||
AdapterType::F32 => walrus::ValType::F32,
|
||||
AdapterType::F64 => walrus::ValType::F64,
|
||||
AdapterType::Anyref => walrus::ValType::Anyref,
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn to_wit(&self) -> Option<wit_walrus::ValType> {
|
||||
Some(match self {
|
||||
AdapterType::S8 => wit_walrus::ValType::S8,
|
||||
AdapterType::S16 => wit_walrus::ValType::S16,
|
||||
AdapterType::S32 => wit_walrus::ValType::S32,
|
||||
AdapterType::S64 => wit_walrus::ValType::S64,
|
||||
AdapterType::U8 => wit_walrus::ValType::U8,
|
||||
AdapterType::U16 => wit_walrus::ValType::U16,
|
||||
AdapterType::U32 => wit_walrus::ValType::U32,
|
||||
AdapterType::U64 => wit_walrus::ValType::U64,
|
||||
AdapterType::F32 => wit_walrus::ValType::F32,
|
||||
AdapterType::F64 => wit_walrus::ValType::F64,
|
||||
AdapterType::String => wit_walrus::ValType::String,
|
||||
AdapterType::Anyref => wit_walrus::ValType::Anyref,
|
||||
AdapterType::I32 => wit_walrus::ValType::I32,
|
||||
AdapterType::I64 => wit_walrus::ValType::I64,
|
||||
AdapterType::Bool | AdapterType::Vector(_) => return None,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl NonstandardWitSection {
|
||||
pub fn append(
|
||||
&mut self,
|
||||
params: Vec<AdapterType>,
|
||||
results: Vec<AdapterType>,
|
||||
kind: AdapterKind,
|
||||
) -> AdapterId {
|
||||
let id = AdapterId(self.adapters.len());
|
||||
self.adapters.insert(
|
||||
id,
|
||||
Adapter {
|
||||
id,
|
||||
params,
|
||||
results,
|
||||
kind,
|
||||
},
|
||||
);
|
||||
return id;
|
||||
}
|
||||
}
|
||||
|
||||
impl walrus::CustomSection for NonstandardWitSection {
|
||||
fn name(&self) -> &str {
|
||||
"nonstandard wit section"
|
||||
}
|
||||
|
||||
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
|
||||
panic!("shouldn't emit custom sections just yet");
|
||||
}
|
||||
}
|
@ -24,7 +24,7 @@ rouille = { version = "3.0.0", default-features = false }
|
||||
serde = { version = "1.0", features = ['derive'] }
|
||||
serde_derive = "1.0"
|
||||
serde_json = "1.0"
|
||||
walrus = { version = "0.13.0", features = ['parallel'] }
|
||||
walrus = { version = "0.14.0", features = ['parallel'] }
|
||||
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.55" }
|
||||
wasm-bindgen-shared = { path = "../shared", version = "=0.2.55" }
|
||||
|
||||
|
@ -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
|
||||
|
@ -108,23 +108,27 @@
|
||||
/// return pointer parameter that will be removed. The `Vec<walrus::ValType>`
|
||||
/// is the new result type that will be returned directly instead of via the
|
||||
/// return pointer.
|
||||
///
|
||||
/// Returns a list of wrappers which have multi value signatures and call the
|
||||
/// corresponding element in the `to_xform` list.
|
||||
pub fn run(
|
||||
module: &mut walrus::Module,
|
||||
memory: walrus::MemoryId,
|
||||
shadow_stack_pointer: walrus::GlobalId,
|
||||
to_xform: &[(walrus::ExportId, usize, &[walrus::ValType])],
|
||||
) -> Result<(), anyhow::Error> {
|
||||
for &(export, return_pointer_index, results) in to_xform {
|
||||
xform_one(
|
||||
to_xform: &[(walrus::FunctionId, usize, Vec<walrus::ValType>)],
|
||||
) -> Result<Vec<walrus::FunctionId>, anyhow::Error> {
|
||||
let mut wrappers = Vec::new();
|
||||
for (func, return_pointer_index, results) in to_xform {
|
||||
wrappers.push(xform_one(
|
||||
module,
|
||||
memory,
|
||||
shadow_stack_pointer,
|
||||
export,
|
||||
return_pointer_index,
|
||||
*func,
|
||||
*return_pointer_index,
|
||||
results,
|
||||
)?;
|
||||
)?);
|
||||
}
|
||||
Ok(())
|
||||
Ok(wrappers)
|
||||
}
|
||||
|
||||
// Ensure that `n` is aligned to `align`, rounding up as necessary.
|
||||
@ -137,19 +141,14 @@ fn xform_one(
|
||||
module: &mut walrus::Module,
|
||||
memory: walrus::MemoryId,
|
||||
shadow_stack_pointer: walrus::GlobalId,
|
||||
export: walrus::ExportId,
|
||||
func: walrus::FunctionId,
|
||||
return_pointer_index: usize,
|
||||
results: &[walrus::ValType],
|
||||
) -> Result<(), anyhow::Error> {
|
||||
) -> Result<walrus::FunctionId, anyhow::Error> {
|
||||
if module.globals.get(shadow_stack_pointer).ty != walrus::ValType::I32 {
|
||||
anyhow::bail!("shadow stack pointer global does not have type `i32`");
|
||||
}
|
||||
|
||||
let func = match module.exports.get(export).item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => anyhow::bail!("can only multi-value transform exported functions, found non-function"),
|
||||
};
|
||||
|
||||
// Compute the total size of all results, potentially with padding to ensure
|
||||
// that each result is aligned.
|
||||
let mut results_size = 0;
|
||||
@ -300,13 +299,7 @@ fn xform_one(
|
||||
module.funcs.get_mut(wrapper).name = Some(format!("{} multivalue shim", name));
|
||||
}
|
||||
|
||||
// Replace the old export with our new multi-value wrapper for it!
|
||||
match module.exports.get_mut(export).item {
|
||||
walrus::ExportItem::Function(ref mut f) => *f = wrapper,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(wrapper)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
37
crates/multi-value-xform/tests/align.wat
Normal file
37
crates/multi-value-xform/tests/align.wat
Normal file
@ -0,0 +1,37 @@
|
||||
;; @xform export "foo" (f64 i32 i64)
|
||||
|
||||
(module
|
||||
(global (mut i32) (i32.const 0))
|
||||
(memory 1)
|
||||
|
||||
(func $foo (export "foo") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result f64 i32 i64)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(func $foo multivalue shim (type 0) (result f64 i32 i64)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 32
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $foo
|
||||
local.get 0
|
||||
f64.load
|
||||
local.get 0
|
||||
i32.load offset=8
|
||||
local.get 0
|
||||
i64.load offset=16
|
||||
local.get 0
|
||||
i32.const 32
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $foo (type 1) (param i32))
|
||||
(memory (;0;) 1)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "foo" (func $foo multivalue shim)))
|
||||
;)
|
225
crates/multi-value-xform/tests/all.rs
Normal file
225
crates/multi-value-xform/tests/all.rs
Normal file
@ -0,0 +1,225 @@
|
||||
//! 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::{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 exports = Vec::new();
|
||||
let mut xforms = Vec::new();
|
||||
for directive in test.directives.iter() {
|
||||
let export = walrus
|
||||
.exports
|
||||
.iter()
|
||||
.find(|e| e.name == directive.name)
|
||||
.unwrap();
|
||||
let id = match export.item {
|
||||
walrus::ExportItem::Function(id) => id,
|
||||
_ => panic!("must be function export"),
|
||||
};
|
||||
exports.push(export.id());
|
||||
xforms.push((id, 0, directive.tys.clone()));
|
||||
}
|
||||
let memory = walrus.memories.iter().next().unwrap().id();
|
||||
let stack_pointer = walrus.globals.iter().next().unwrap().id();
|
||||
let ret = wasm_bindgen_multi_value_xform::run(&mut walrus, memory, stack_pointer, &xforms)?;
|
||||
for (export, id) in exports.into_iter().zip(ret) {
|
||||
walrus.exports.get_mut(export).item = walrus::ExportItem::Function(id);
|
||||
}
|
||||
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 {
|
||||
name: String,
|
||||
tys: Vec<walrus::ValType>,
|
||||
}
|
||||
|
||||
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, ValType};
|
||||
|
||||
parser.parse::<kw::export>()?;
|
||||
let name = parser.parse()?;
|
||||
let mut tys = Vec::new();
|
||||
parser.parens(|p| {
|
||||
while !p.is_empty() {
|
||||
tys.push(match p.parse()? {
|
||||
ValType::I32 => walrus::ValType::I32,
|
||||
ValType::I64 => walrus::ValType::I64,
|
||||
ValType::F32 => walrus::ValType::F32,
|
||||
ValType::F64 => walrus::ValType::F64,
|
||||
_ => panic!(),
|
||||
});
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
Ok(Directive { name, tys })
|
||||
}
|
||||
}
|
39
crates/multi-value-xform/tests/many.wat
Normal file
39
crates/multi-value-xform/tests/many.wat
Normal file
@ -0,0 +1,39 @@
|
||||
;; @xform export "foo" (i32 f32 f64 i64)
|
||||
|
||||
(module
|
||||
(global (mut i32) (i32.const 0))
|
||||
(memory 1)
|
||||
|
||||
(func $foo (export "foo") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32 f32 f64 i64)))
|
||||
(type (;1;) (func (param i32)))
|
||||
(func $foo multivalue shim (type 0) (result i32 f32 f64 i64)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 32
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $foo
|
||||
local.get 0
|
||||
i32.load
|
||||
local.get 0
|
||||
f32.load offset=4
|
||||
local.get 0
|
||||
f64.load offset=8
|
||||
local.get 0
|
||||
i64.load offset=16
|
||||
local.get 0
|
||||
i32.const 32
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $foo (type 1) (param i32))
|
||||
(memory (;0;) 1)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "foo" (func $foo multivalue shim)))
|
||||
;)
|
93
crates/multi-value-xform/tests/simple.wat
Normal file
93
crates/multi-value-xform/tests/simple.wat
Normal file
@ -0,0 +1,93 @@
|
||||
;; @xform export "i32" (i32)
|
||||
;; @xform export "i64" (i64)
|
||||
;; @xform export "f32" (f32)
|
||||
;; @xform export "f64" (f64)
|
||||
|
||||
(module
|
||||
(global (mut i32) (i32.const 0))
|
||||
(memory 1)
|
||||
|
||||
(func $i32 (export "i32") (param i32))
|
||||
(func $i64 (export "i64") (param i32))
|
||||
(func $f32 (export "f32") (param i32))
|
||||
(func $f64 (export "f64") (param i32))
|
||||
)
|
||||
|
||||
(; CHECK-ALL:
|
||||
(module
|
||||
(type (;0;) (func (result i32)))
|
||||
(type (;1;) (func (result i64)))
|
||||
(type (;2;) (func (result f32)))
|
||||
(type (;3;) (func (result f64)))
|
||||
(type (;4;) (func (param i32)))
|
||||
(func $i32 multivalue shim (type 0) (result i32)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $i32
|
||||
local.get 0
|
||||
i32.load
|
||||
local.get 0
|
||||
i32.const 16
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $i64 multivalue shim (type 1) (result i64)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $i64
|
||||
local.get 0
|
||||
i64.load
|
||||
local.get 0
|
||||
i32.const 16
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $f32 multivalue shim (type 2) (result f32)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $f32
|
||||
local.get 0
|
||||
f32.load
|
||||
local.get 0
|
||||
i32.const 16
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $f64 multivalue shim (type 3) (result f64)
|
||||
(local i32)
|
||||
global.get 0
|
||||
i32.const 16
|
||||
i32.sub
|
||||
local.tee 0
|
||||
global.set 0
|
||||
local.get 0
|
||||
call $f64
|
||||
local.get 0
|
||||
f64.load
|
||||
local.get 0
|
||||
i32.const 16
|
||||
i32.add
|
||||
global.set 0)
|
||||
(func $i32 (type 4) (param i32))
|
||||
(func $i64 (type 4) (param i32))
|
||||
(func $f32 (type 4) (param i32))
|
||||
(func $f64 (type 4) (param i32))
|
||||
(memory (;0;) 1)
|
||||
(global (;0;) (mut i32) (i32.const 0))
|
||||
(export "i32" (func $i32 multivalue shim))
|
||||
(export "i64" (func $i64 multivalue shim))
|
||||
(export "f32" (func $f32 multivalue shim))
|
||||
(export "f64" (func $f64 multivalue shim)))
|
||||
;)
|
@ -13,5 +13,5 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
walrus = "0.13.0"
|
||||
walrus = "0.14.0"
|
||||
wasm-bindgen-wasm-conventions = { path = "../wasm-conventions", version = "=0.2.55" }
|
||||
|
@ -10,5 +10,5 @@ description = "Utilities for working with Wasm codegen conventions (usually esta
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
walrus = "0.13.0"
|
||||
walrus = "0.14.0"
|
||||
anyhow = "1.0"
|
||||
|
@ -14,7 +14,7 @@ edition = '2018'
|
||||
[dependencies]
|
||||
anyhow = "1.0"
|
||||
log = "0.4"
|
||||
walrus = "0.13.0"
|
||||
walrus = "0.14.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
@ -51,7 +51,6 @@ unsafe impl WasmAbi for Wasm64 {}
|
||||
#[repr(C)]
|
||||
pub struct WasmOptional64 {
|
||||
pub present: u32,
|
||||
pub padding: u32,
|
||||
pub low: u32,
|
||||
pub high: u32,
|
||||
}
|
||||
@ -177,13 +176,11 @@ macro_rules! type_64 {
|
||||
match self {
|
||||
None => WasmOptional64 {
|
||||
present: 0,
|
||||
padding: 0,
|
||||
low: 0 as u32,
|
||||
high: 0 as u32,
|
||||
},
|
||||
Some(me) => WasmOptional64 {
|
||||
present: 1,
|
||||
padding: 0,
|
||||
low: me as u32,
|
||||
high: (me >> 32) as u32,
|
||||
},
|
||||
|
@ -6,5 +6,6 @@ mod impls;
|
||||
mod slices;
|
||||
mod traits;
|
||||
|
||||
pub use self::impls::*;
|
||||
pub use self::slices::WasmSlice;
|
||||
pub use self::traits::*;
|
||||
|
35
src/lib.rs
35
src/lib.rs
@ -14,7 +14,7 @@ use core::marker;
|
||||
use core::mem;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
|
||||
use crate::convert::FromWasmAbi;
|
||||
use crate::convert::{FromWasmAbi, WasmOptionalF64, WasmSlice};
|
||||
|
||||
macro_rules! if_std {
|
||||
($($i:item)*) => ($(
|
||||
@ -226,10 +226,8 @@ impl JsValue {
|
||||
T: for<'a> serde::de::Deserialize<'a>,
|
||||
{
|
||||
unsafe {
|
||||
let mut ret = [0usize; 2];
|
||||
__wbindgen_json_serialize(&mut ret, self.idx);
|
||||
let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||
let s = String::from_utf8_unchecked(s);
|
||||
let ret = __wbindgen_json_serialize(self.idx);
|
||||
let s = String::from_abi(ret);
|
||||
serde_json::from_str(&s)
|
||||
}
|
||||
}
|
||||
@ -240,15 +238,7 @@ impl JsValue {
|
||||
/// If this JS value is not an instance of a number then this returns
|
||||
/// `None`.
|
||||
pub fn as_f64(&self) -> Option<f64> {
|
||||
let mut invalid = 0;
|
||||
unsafe {
|
||||
let ret = __wbindgen_number_get(self.idx, &mut invalid);
|
||||
if invalid == 1 {
|
||||
None
|
||||
} else {
|
||||
Some(ret)
|
||||
}
|
||||
}
|
||||
unsafe { FromWasmAbi::from_abi(__wbindgen_number_get(self.idx)) }
|
||||
}
|
||||
|
||||
/// Tests whether this JS value is a JS string.
|
||||
@ -278,16 +268,7 @@ impl JsValue {
|
||||
/// [caveats]: https://rustwasm.github.io/docs/wasm-bindgen/reference/types/str.html
|
||||
#[cfg(feature = "std")]
|
||||
pub fn as_string(&self) -> Option<String> {
|
||||
unsafe {
|
||||
let mut len = 0;
|
||||
let ptr = __wbindgen_string_get(self.idx, &mut len);
|
||||
if ptr.is_null() {
|
||||
None
|
||||
} else {
|
||||
let data = Vec::from_raw_parts(ptr, len, len);
|
||||
Some(String::from_utf8_unchecked(data))
|
||||
}
|
||||
}
|
||||
unsafe { FromWasmAbi::from_abi(__wbindgen_string_get(self.idx)) }
|
||||
}
|
||||
|
||||
/// Returns the `bool` value of this JS value if it's an instance of a
|
||||
@ -523,9 +504,9 @@ externs! {
|
||||
fn __wbindgen_is_string(idx: u32) -> u32;
|
||||
fn __wbindgen_is_falsy(idx: u32) -> u32;
|
||||
|
||||
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
|
||||
fn __wbindgen_number_get(idx: u32) -> WasmOptionalF64;
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
|
||||
fn __wbindgen_string_get(idx: u32) -> WasmSlice;
|
||||
|
||||
fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();
|
||||
|
||||
@ -539,7 +520,7 @@ externs! {
|
||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
||||
|
||||
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> ();
|
||||
fn __wbindgen_json_serialize(idx: u32) -> WasmSlice;
|
||||
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
|
||||
|
||||
fn __wbindgen_memory() -> u32;
|
||||
|
@ -115,3 +115,15 @@ class StaticMethodCheck {
|
||||
}
|
||||
|
||||
exports.StaticMethodCheck = StaticMethodCheck;
|
||||
|
||||
exports.receive_undefined = val => {
|
||||
assert.strictEqual(val, undefined);
|
||||
};
|
||||
|
||||
const VAL = {};
|
||||
|
||||
exports.receive_some = val => {
|
||||
assert.strictEqual(val, VAL);
|
||||
};
|
||||
|
||||
exports.get_some_val = () => VAL;
|
||||
|
@ -57,6 +57,18 @@ extern "C" {
|
||||
fn static_method_of_right_this();
|
||||
|
||||
static STATIC_STRING: String;
|
||||
|
||||
#[derive(Clone)]
|
||||
type PassOutOptionUndefined;
|
||||
fn get_some_val() -> PassOutOptionUndefined;
|
||||
#[wasm_bindgen(js_name = "receive_undefined")]
|
||||
fn receive_undefined_ref(arg: Option<&PassOutOptionUndefined>);
|
||||
#[wasm_bindgen(js_name = "receive_undefined")]
|
||||
fn receive_undefined_owned(arg: Option<PassOutOptionUndefined>);
|
||||
#[wasm_bindgen(js_name = "receive_some")]
|
||||
fn receive_some_ref(arg: Option<&PassOutOptionUndefined>);
|
||||
#[wasm_bindgen(js_name = "receive_some")]
|
||||
fn receive_some_owned(arg: Option<PassOutOptionUndefined>);
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -248,3 +260,17 @@ fn static_string_ok() {
|
||||
fn static_method_of_has_right_this() {
|
||||
StaticMethodCheck::static_method_of_right_this();
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
fn pass_out_options_as_undefined() {
|
||||
receive_undefined_ref(None);
|
||||
receive_undefined_ref(None);
|
||||
receive_undefined_owned(None);
|
||||
receive_undefined_owned(None);
|
||||
|
||||
let v = get_some_val();
|
||||
receive_some_ref(Some(&v));
|
||||
receive_some_ref(Some(&v));
|
||||
receive_some_owned(Some(v.clone()));
|
||||
receive_some_owned(Some(v));
|
||||
}
|
||||
|
@ -41,63 +41,27 @@ exports.js_export = () => {
|
||||
assert.deepStrictEqual(wasm.export_f64(f64), f64);
|
||||
};
|
||||
|
||||
exports.import_js_i8 = a => {
|
||||
const test_import = (a, b, c) => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
assert.strictEqual(b.length, 2);
|
||||
assert.strictEqual(b[0], 1);
|
||||
assert.strictEqual(b[1], 2);
|
||||
assert.strictEqual(c, undefined);
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.import_js_u8 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.import_js_i16 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.import_js_u16 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.import_js_i32 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
exports.import_js_isize = exports.import_js_i32;
|
||||
|
||||
exports.import_js_u32 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
exports.import_js_usize = exports.import_js_u32;
|
||||
|
||||
exports.import_js_f32 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
|
||||
exports.import_js_f64 = a => {
|
||||
assert.strictEqual(a.length, 2);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
return a;
|
||||
};
|
||||
exports.import_js_i8 = test_import;
|
||||
exports.import_js_u8 = test_import;
|
||||
exports.import_js_i16 = test_import;
|
||||
exports.import_js_u16 = test_import;
|
||||
exports.import_js_i32 = test_import;
|
||||
exports.import_js_isize = test_import;
|
||||
exports.import_js_u32 = test_import;
|
||||
exports.import_js_usize = test_import;
|
||||
exports.import_js_f32 = test_import;
|
||||
exports.import_js_f64 = test_import;
|
||||
|
||||
exports.js_import = () => {
|
||||
const i8 = new Int8Array(2);
|
||||
@ -152,12 +116,19 @@ exports.js_pass_array = () => {
|
||||
wasm.pass_array_rust_f64([1, 2]);
|
||||
};
|
||||
|
||||
const import_mut_foo = a => {
|
||||
const import_mut_foo = (a, b, c) => {
|
||||
assert.strictEqual(a.length, 3);
|
||||
assert.strictEqual(a[0], 1);
|
||||
assert.strictEqual(a[1], 2);
|
||||
a[0] = 4;
|
||||
a[1] = 5;
|
||||
assert.strictEqual(b.length, 3);
|
||||
assert.strictEqual(b[0], 4);
|
||||
assert.strictEqual(b[1], 5);
|
||||
assert.strictEqual(b[2], 6);
|
||||
b[0] = 8;
|
||||
b[1] = 7;
|
||||
assert.strictEqual(c, undefined);
|
||||
};
|
||||
|
||||
exports.import_mut_js_i8 = import_mut_foo;
|
||||
|
@ -55,7 +55,7 @@ macro_rules! import_macro {
|
||||
($(($rust:ident, $js:ident, $i:ident))*) => ($(
|
||||
#[wasm_bindgen(module = "tests/wasm/slice.js")]
|
||||
extern "C" {
|
||||
fn $js(a: &[$i]) -> Vec<$i>;
|
||||
fn $js(a: &[$i], b: Option<&[$i]>, c: Option<&[$i]>) -> Vec<$i>;
|
||||
}
|
||||
|
||||
#[wasm_bindgen]
|
||||
@ -63,7 +63,7 @@ macro_rules! import_macro {
|
||||
assert_eq!(a.len(), 2);
|
||||
assert_eq!(a[0], 1 as $i);
|
||||
assert_eq!(a[1], 2 as $i);
|
||||
$js(a)
|
||||
$js(a, Some(a), None)
|
||||
}
|
||||
)*)
|
||||
}
|
||||
@ -120,19 +120,27 @@ macro_rules! import_mut_macro {
|
||||
$(
|
||||
#[wasm_bindgen(module = "tests/wasm/slice.js")]
|
||||
extern "C" {
|
||||
fn $js(a: &mut [$i]);
|
||||
fn $js(a: &mut [$i], b: Option<&mut [$i]>, c: Option<&mut [$i]>);
|
||||
}
|
||||
|
||||
fn $rust() {
|
||||
let mut buf = [
|
||||
let mut buf1 = [
|
||||
1 as $i,
|
||||
2 as $i,
|
||||
3 as $i,
|
||||
];
|
||||
$js(&mut buf);
|
||||
assert_eq!(buf[0], 4 as $i);
|
||||
assert_eq!(buf[1], 5 as $i);
|
||||
assert_eq!(buf[2], 3 as $i);
|
||||
let mut buf2 = [
|
||||
4 as $i,
|
||||
5 as $i,
|
||||
6 as $i,
|
||||
];
|
||||
$js(&mut buf1, Some(&mut buf2), None);
|
||||
assert_eq!(buf1[0], 4 as $i);
|
||||
assert_eq!(buf1[1], 5 as $i);
|
||||
assert_eq!(buf1[2], 3 as $i);
|
||||
assert_eq!(buf2[0], 8 as $i);
|
||||
assert_eq!(buf2[1], 7 as $i);
|
||||
assert_eq!(buf2[2], 6 as $i);
|
||||
}
|
||||
)*
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user