Merge remote-tracking branch 'upstream/master'

This commit is contained in:
Anton Danilkin 2018-09-07 13:46:20 +03:00
commit 14eb317509
46 changed files with 1293 additions and 360 deletions

View File

@ -25,8 +25,11 @@ test_script:
- where chromedriver
- set CHROMEDRIVER=C:\Tools\WebDriver\chromedriver.exe
- cargo test -p js-sys --target wasm32-unknown-unknown
- cargo test --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --all-features
- cargo test -p webidl-tests --target wasm32-unknown-unknown
# Try just a few features for `web-sys`, unfortunately the whole crate blows
# system command line limits meaning we can't even spawn rustc to enable all
# the features.
- cargo build --manifest-path crates/web-sys/Cargo.toml --target wasm32-unknown-unknown --features "Node Window Document"
branches:
only:

View File

@ -32,6 +32,52 @@ Released YYYY-MM-DD.
--------------------------------------------------------------------------------
## 0.2.21
Released 2018-09-07
### Added
* Added many more bindings for `WebAssembly` in the `js-sys` crate.
### Fixed
* The "names" section of the wasm binary is now correctly preserved by
wasm-bindgen.
--------------------------------------------------------------------------------
## 0.2.20
Released 2018-09-06
### Added
* All of `wasm-bindgen` is configured to compile on stable Rust as of the
upcoming 1.30.0 release, scheduled for October 25, 2018.
* The underlying `JsValue` of a `Closure<T>` type can now be extracted at any
time.
* Initial and experimental support was added for modules that have shared memory
(use atomic instructions).
### Removed
* The `--wasm2asm` flag of `wasm2es6js` was removed because the `wasm2asm` tool
has been removed from upstream Binaryen. This is replaced with the new
`wasm2js` tool from Binaryen.
### Fixed
* The "schema" version for wasm-bindgen now changes on all publishes, meaning we
can't forget to update it. This means that the crate version and CLI version
must exactly match.
* The `wasm-bindgen` crate now has a `links` key which forbids multiple versions
of `wasm-bindgen` from being linked into a dependency graph, fixing obscure
linking errors with a more first-class error message.
* Binary releases for Windows has been fixed.
--------------------------------------------------------------------------------
## 0.2.19 (and 0.2.18)
Released 2018-08-27.

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
# Because only a single `wasm_bindgen` version can be used in a dependency
@ -35,13 +35,13 @@ nightly = []
xxx_debug_only_print_generated_code = ["wasm-bindgen-macro/xxx_debug_only_print_generated_code"]
[dependencies]
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.19" }
wasm-bindgen-macro = { path = "crates/macro", version = "=0.2.21" }
serde = { version = "1.0", optional = true }
serde_json = { version = "1.0", optional = true }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
js-sys = { path = 'crates/js-sys', version = '0.2.4' }
wasm-bindgen-test = { path = 'crates/test', version = '=0.2.19' }
js-sys = { path = 'crates/js-sys', version = '0.2.6' }
wasm-bindgen-test = { path = 'crates/test', version = '=0.2.21' }
serde_derive = "1.0"
wasm-bindgen-test-crate-a = { path = 'tests/crates/a', version = '0.1' }
wasm-bindgen-test-crate-b = { path = 'tests/crates/b', version = '0.1' }

View File

@ -1,2 +1,4 @@
// Empty `build.rs` so that `[package] links = ...` works in `Cargo.toml`.
fn main() {}
fn main() {
println!("cargo:rerun-if-changed=build.rs");
}

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-backend"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/backend"
@ -20,5 +20,5 @@ log = "0.4"
proc-macro2 = "0.4.8"
quote = '0.6'
serde_json = "1.0"
syn = { version = '0.14', features = ['full', 'visit'] }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.19" }
syn = { version = '0.15', features = ['full', 'visit'] }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.21" }

View File

@ -233,6 +233,7 @@ impl ImportedTypes for syn::GenericArgument {
syn::GenericArgument::Type(ty) => ty.imported_types(f),
syn::GenericArgument::Binding(_) => {}, // TODO
syn::GenericArgument::Const(_) => {}, // TODO
syn::GenericArgument::Constraint(_) => {}, // TODO
}
}
}

View File

@ -1,5 +1,6 @@
use proc_macro2::*;
use quote::{ToTokens, TokenStreamExt};
use syn::parse::Error;
#[macro_export]
macro_rules! err_span {
@ -26,6 +27,7 @@ enum Repr {
text: String,
span: Option<(Span, Span)>,
},
SynError(Error),
Multi {
diagnostics: Vec<Diagnostic>,
}
@ -62,11 +64,20 @@ impl Diagnostic {
pub fn panic(&self) -> ! {
match &self.inner {
Repr::Single { text, .. } => panic!("{}", text),
Repr::SynError(error) => panic!("{}", error),
Repr::Multi { diagnostics } => diagnostics[0].panic(),
}
}
}
impl From<Error> for Diagnostic {
fn from(err: Error) -> Diagnostic {
Diagnostic {
inner: Repr::SynError(err),
}
}
}
fn extract_spans(node: &ToTokens) -> Option<(Span, Span)> {
let mut t = TokenStream::new();
node.to_tokens(&mut t);
@ -95,6 +106,9 @@ impl ToTokens for Diagnostic {
diagnostic.to_tokens(dst);
}
}
Repr::SynError(err) => {
err.to_compile_error().to_tokens(dst);
}
}
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-cli-support"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli-support"
@ -17,6 +17,6 @@ parity-wasm = "0.32"
serde = "1.0"
serde_json = "1.0"
tempfile = "3.0"
wasm-bindgen-shared = { path = "../shared", version = '=0.2.19' }
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.19' }
wasm-bindgen-shared = { path = "../shared", version = '=0.2.21' }
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.21' }
wasm-gc-api = "0.1.9"

View File

@ -189,13 +189,6 @@ impl Descriptor {
}
}
pub fn ref_closure(&self) -> Option<&Closure> {
match *self {
Descriptor::Ref(ref s) => s.closure(),
_ => None,
}
}
pub fn closure(&self) -> Option<&Closure> {
match *self {
Descriptor::Closure(ref s) => Some(s),

View File

@ -0,0 +1,386 @@
//! Support for closures in wasm-bindgen
//!
//! This module contains the bulk of the support necessary to support closures
//! in `wasm-bindgen`. The main "support" here is that `Closure::wrap` creates
//! a `JsValue` through... well... unconventional mechanisms.
//!
//! This module contains one public function, `rewrite`. The function will
//! rewrite the wasm module to correctly call closure factories and thread
//! through values into the final `Closure` object. More details about how all
//! this works can be found in the code below.
use std::collections::{BTreeMap, HashMap, HashSet};
use std::mem;
use failure::Error;
use parity_wasm::elements::*;
use descriptor::Descriptor;
use js::Context;
use js::js2rust::Js2Rust;
pub fn rewrite(input: &mut Context) -> Result<(), Error> {
let info = ClosureDescriptors::new(input);
// Sanity check to make sure things look ok and skip everything below if
// there's not calls to `Closure::new`.
assert_eq!(
info.element_removal_list.len(),
info.code_idx_to_descriptor.len(),
);
if info.element_removal_list.len() == 0 {
return Ok(())
}
// Make sure the names section is available in the wasm module because we'll
// want to remap those function indices, and then actually remap all
// function indices. We're going to be injecting a few imported functions
// below which will shift the index space for all defined functions.
input.parse_wasm_names();
Remap {
code_idx_to_descriptor: &info.code_idx_to_descriptor,
old_num_imports: input.module
.import_section()
.map(|s| s.functions())
.unwrap_or(0) as u32,
}.remap_module(input.module);
info.delete_function_table_entries(input);
info.inject_imports(input)?;
Ok(())
}
#[derive(Default)]
struct ClosureDescriptors {
/// A list of elements to remove from the function table. The first element
/// of the pair is the index of the entry in the element section, and the
/// second element of the pair is the index within that entry to remove.
element_removal_list: Vec<(usize, usize)>,
/// A map from indexes in the code section which contain calls to
/// `__wbindgen_describe_closure` to the new function the whole function is
/// replaced with as well as the descriptor that the function describes.
///
/// This map is later used to replace all calls to the keys of this map with
/// calls to the value of the map.
code_idx_to_descriptor: BTreeMap<u32, (u32, Descriptor)>,
}
impl ClosureDescriptors {
/// Find all invocations of `__wbindgen_describe_closure`.
///
/// We'll be rewriting all calls to functions who call this import. Here we
/// iterate over all code found in the module, and anything which calls our
/// special imported function is interpreted. The result of interpretation will
/// inform of us of an entry to remove from the function table (as the describe
/// function is never needed at runtime) as well as a `Descriptor` which
/// describes the type of closure needed.
///
/// All this information is then returned in the `ClosureDescriptors` return
/// value.
fn new(input: &mut Context) -> ClosureDescriptors {
let wbindgen_describe_closure = match input.interpreter.describe_closure_idx() {
Some(i) => i,
None => return Default::default(),
};
let imports = input.module.import_section()
.map(|s| s.functions())
.unwrap_or(0);
let mut ret = ClosureDescriptors::default();
let code = match input.module.code_section() {
Some(code) => code,
None => return Default::default(),
};
for (i, function) in code.bodies().iter().enumerate() {
let mut call_found = false;
for instruction in function.code().elements() {
match instruction {
Instruction::Call(idx) if *idx == wbindgen_describe_closure => {
call_found = true;
break
}
_ => {}
}
}
if !call_found {
continue
}
let descriptor = input.interpreter.interpret_closure_descriptor(
i,
input.module,
&mut ret.element_removal_list,
).unwrap();
// `new_idx` is the function-space index of the function that we'll
// be injecting. Calls to the code function `i` will instead be
// rewritten to calls to `new_idx`, which is an import that we'll
// inject based on `descriptor`.
let new_idx = (ret.code_idx_to_descriptor.len() + imports) as u32;
ret.code_idx_to_descriptor.insert(
i as u32,
(new_idx, Descriptor::decode(descriptor)),
);
}
return ret
}
/// Here we remove elements from the function table. All our descriptor
/// functions are entries in this function table and can be removed once we
/// use them as they're not actually needed at runtime.
///
/// One option for removal is to replace the function table entry with an
/// index to a dummy function, but for now we simply remove the table entry
/// altogether by splitting the section and having multiple `elem` sections
/// with holes in them.
fn delete_function_table_entries(&self, input: &mut Context) {
let elements = input.module.elements_section_mut().unwrap();
let mut remove = HashMap::new();
for (entry, idx) in self.element_removal_list.iter().cloned() {
remove.entry(entry).or_insert(HashSet::new()).insert(idx);
}
let entries = mem::replace(elements.entries_mut(), Vec::new());
let empty = HashSet::new();
for (i, entry) in entries.into_iter().enumerate() {
let to_remove = remove.get(&i).unwrap_or(&empty);
let mut current = Vec::new();
assert_eq!(entry.offset().code().len(), 2);
let mut offset = match entry.offset().code()[0] {
Instruction::I32Const(x) => x,
_ => unreachable!(),
};
for (j, idx) in entry.members().iter().enumerate() {
// If we keep this entry, then keep going
if !to_remove.contains(&j) {
current.push(*idx);
continue
}
// If we have members of `current` then we save off a section
// of the function table, then update `offset` and keep going.
let next_offset = offset + (current.len() as i32) + 1;
if current.len() > 0 {
let members = mem::replace(&mut current, Vec::new());
let offset = InitExpr::new(vec![
Instruction::I32Const(offset),
Instruction::End,
]);
let new_entry = ElementSegment::new(0, offset, members);
elements.entries_mut().push(new_entry);
}
offset = next_offset;
}
// Any remaining function table entries get pushed at the end.
if current.len() > 0 {
let offset = InitExpr::new(vec![
Instruction::I32Const(offset),
Instruction::End,
]);
let new_entry = ElementSegment::new(0, offset, current);
elements.entries_mut().push(new_entry);
}
}
}
/// Inject new imports into the module.
///
/// This function will inject new imported functions into the `input` module
/// described by the fields internally. These new imports will be closure
/// factories and are freshly generated shim in JS.
fn inject_imports(&self, input: &mut Context) -> Result<(), Error> {
// We'll be injecting new imports and we'll need to give them all a
// type. The signature is all `(i32, i32) -> i32` currently and we know
// that this signature already exists in the module as it's the
// signature of our `#[inline(never)]` functions. Find the type
// signature index so we can assign it below.
let type_idx = input.module.type_section()
.unwrap()
.types()
.iter()
.position(|ty| {
let fnty = match ty {
Type::Function(f) => f,
};
fnty.params() == &[ValueType::I32, ValueType::I32] &&
fnty.return_type() == Some(ValueType::I32)
})
.unwrap();
// The last piece of the magic. For all our descriptors we found we
// inject a JS shim for the descriptor. This JS shim will manufacture a
// JS `function`, and prepare it to be invoked.
//
// Once all that's said and done we inject a new import into the wasm module
// of our new wrapper, and the `Remap` step above already wrote calls to
// this function within the module.
for (i, (_new_idx, descriptor)) in self.code_idx_to_descriptor.iter() {
let import_name = format!("__wbindgen_closure_wrapper{}", i);
let closure = descriptor.closure().unwrap();
let (js, _ts, _js_doc) = {
let mut builder = Js2Rust::new("", input);
if closure.mutable {
builder
.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.process(&closure.function)?
.finish("function", "this.f")
};
input.expose_add_heap_object();
input.function_table_needed = true;
let body = format!(
"function(ptr, f) {{
let cb = {};
cb.f = wasm.__wbg_function_table.get(f);
cb.a = ptr;
let real = cb.bind(cb);
real.original = cb;
return addHeapObject(real);
}}",
js,
);
input.export(&import_name, &body, None);
let new_import = ImportEntry::new(
"__wbindgen_placeholder__".to_string(),
import_name,
External::Function(type_idx as u32),
);
input.module.import_section_mut()
.unwrap()
.entries_mut()
.push(new_import);
}
Ok(())
}
}
struct Remap<'a> {
code_idx_to_descriptor: &'a BTreeMap<u32, (u32, Descriptor)>,
old_num_imports: u32,
}
impl<'a> Remap<'a> {
fn remap_module(&self, module: &mut Module) {
for section in module.sections_mut() {
match section {
Section::Export(e) => self.remap_export_section(e),
Section::Element(e) => self.remap_element_section(e),
Section::Code(e) => self.remap_code_section(e),
Section::Start(i) => { self.remap_idx(i); }
Section::Name(n) => self.remap_name_section(n),
_ => {}
}
}
}
fn remap_export_section(&self, section: &mut ExportSection) {
for entry in section.entries_mut() {
self.remap_export_entry(entry);
}
}
fn remap_export_entry(&self, entry: &mut ExportEntry) {
match entry.internal_mut() {
Internal::Function(i) => { self.remap_idx(i); }
_ => {}
}
}
fn remap_element_section(&self, section: &mut ElementSection) {
for entry in section.entries_mut() {
self.remap_element_entry(entry);
}
}
fn remap_element_entry(&self, entry: &mut ElementSegment) {
for member in entry.members_mut() {
self.remap_idx(member);
}
}
fn remap_code_section(&self, section: &mut CodeSection) {
for body in section.bodies_mut() {
self.remap_func_body(body);
}
}
fn remap_func_body(&self, body: &mut FuncBody) {
self.remap_instructions(body.code_mut());
}
fn remap_instructions(&self, code: &mut Instructions) {
for instr in code.elements_mut() {
self.remap_instruction(instr);
}
}
fn remap_instruction(&self, instr: &mut Instruction) {
match instr {
Instruction::Call(i) => { self.remap_idx(i); }
_ => {}
}
}
fn remap_name_section(&self, names: &mut NameSection) {
match names {
NameSection::Function(f) => self.remap_function_name_section(f),
NameSection::Local(f) => self.remap_local_name_section(f),
_ => {}
}
}
fn remap_function_name_section(&self, names: &mut FunctionNameSection) {
let map = names.names_mut();
let new = IndexMap::with_capacity(map.len());
for (mut idx, name) in mem::replace(map, new) {
if !self.remap_idx(&mut idx) {
map.insert(idx, name);
}
}
}
fn remap_local_name_section(&self, names: &mut LocalNameSection) {
let map = names.local_names_mut();
let new = IndexMap::with_capacity(map.len());
for (mut idx, name) in mem::replace(map, new) {
if !self.remap_idx(&mut idx) {
map.insert(idx, name);
}
}
}
/// Returns whether `idx` pointed to a previously known descriptor function
/// that we're switching to an import
fn remap_idx(&self, idx: &mut u32) -> bool {
// If this was an imported function we didn't reorder those, so nothing
// to do.
if *idx < self.old_num_imports {
return false
}
let code_idx = *idx - self.old_num_imports;
// If this `idx` points to a function which was effectively a descriptor
// function, then we want to re-point it to our imported function which
// is actually the shim factory.
if let Some((new_idx, _)) = self.code_idx_to_descriptor.get(&code_idx) {
*idx = *new_idx;
return true
}
// And finally, otherwise this is just a normal function reference we
// don't want to touch, but we're injecting imports which shifts all
// function indices.
*idx += self.code_idx_to_descriptor.len() as u32;
false
}
}

View File

@ -16,6 +16,7 @@ mod js2rust;
use self::js2rust::Js2Rust;
mod rust2js;
use self::rust2js::Rust2Js;
mod closures;
pub struct Context<'a> {
pub globals: String,
@ -394,6 +395,7 @@ impl<'a> Context<'a> {
self.create_memory_export();
self.unexport_unused_internal_exports();
closures::rewrite(self)?;
self.gc()?;
// Note that it's important `throw` comes last *after* we gc. The
@ -1714,8 +1716,8 @@ impl<'a> Context<'a> {
}
fn gc(&mut self) -> Result<(), Error> {
self.parse_wasm_names();
let module = mem::replace(self.module, Module::default());
let module = module.parse_names().unwrap_or_else(|p| p.1);
let result = wasm_gc::Config::new()
.demangle(self.config.demangle)
.keep_debug(self.config.keep_debug || self.config.debug)
@ -1727,9 +1729,16 @@ impl<'a> Context<'a> {
Ok(())
}
fn parse_wasm_names(&mut self) {
let module = mem::replace(self.module, Module::default());
let module = module.parse_names().unwrap_or_else(|p| p.1);
*self.module = module;
}
fn describe(&mut self, name: &str) -> Option<Descriptor> {
let name = format!("__wbindgen_describe_{}", name);
Some(Descriptor::decode(self.interpreter.interpret(&name, self.module)?))
let descriptor = self.interpreter.interpret_descriptor(&name, self.module)?;
Some(Descriptor::decode(descriptor))
}
fn global(&mut self, s: &str) {

View File

@ -275,54 +275,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
return Ok(());
}
if let Some(closure) = arg.ref_closure() {
let (js, _ts, _js_doc) = {
let mut builder = Js2Rust::new("", self.cx);
if closure.mutable {
builder
.prelude("let a = this.a;\n")
.prelude("this.a = 0;\n")
.rust_argument("a")
.finally("this.a = a;\n");
} else {
builder.rust_argument("this.a");
}
builder
.process(&closure.function)?
.finish("function", "this.f")
};
self.cx.expose_get_global_argument()?;
self.cx.expose_uint32_memory();
self.cx.expose_add_heap_object();
self.cx.function_table_needed = true;
let reset_idx = format!(
"\
let cb{0} = {js};\n\
cb{0}.f = wasm.__wbg_function_table.get(getGlobalArgument({f}));\n\
cb{0}.a = getGlobalArgument({a});\n\
let real = cb{0}.bind(cb{0});\n\
real.original = cb{0};\n\
idx{0} = getUint32Memory()[{0} / 4] = addHeapObject(real);\n\
",
abi,
js = js,
f = self.global_idx(),
a = self.global_idx(),
);
self.prelude(&format!(
"\
let idx{0} = getUint32Memory()[{0} / 4];\n\
if (idx{0} === 0xffffffff) {{\n\
{1}\
}}\n\
",
abi, &reset_idx
));
self.cx.expose_get_object();
self.js_arguments.push(format!("getObject(idx{})", abi));
return Ok(());
}
let invoc_arg = match *arg {
ref d if d.is_number() => abi,
Descriptor::Boolean => format!("{} !== 0", abi),

View File

@ -1,12 +1,9 @@
extern crate base64;
extern crate tempfile;
use std::collections::{HashMap, HashSet};
use std::fs;
use std::io;
use std::process::Command;
use std::collections::HashSet;
use failure::{Error, ResultExt};
use failure::Error;
use parity_wasm::elements::*;
pub struct Config {
@ -221,35 +218,3 @@ impl Output {
))
}
}
fn run(cmd: &mut Command, program: &str) -> Result<(), Error> {
let output = cmd.output().with_context(|e| {
if e.kind() == io::ErrorKind::NotFound {
format!(
"failed to execute `{}`, is the tool installed \
from the binaryen project?\ncommand line: {:?}",
program, cmd
)
} else {
format!("failed to execute: {:?}", cmd)
}
})?;
if output.status.success() {
return Ok(());
}
let mut s = format!("failed to execute: {:?}\nstatus: {}\n", cmd, output.status);
if !output.stdout.is_empty() {
s.push_str(&format!(
"----- stdout ------\n{}\n",
String::from_utf8_lossy(&output.stdout)
));
}
if !output.stderr.is_empty() {
s.push_str(&format!(
"----- stderr ------\n{}\n",
String::from_utf8_lossy(&output.stderr)
));
}
bail!("{}", s)
}

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-cli"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/cli"
@ -23,8 +23,8 @@ rouille = { version = "2.1.0", default-features = false }
serde = "1.0"
serde_derive = "1.0"
serde_json = "1.0"
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.19" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.19" }
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.21" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.21" }
openssl = { version = '0.10.11', optional = true }
[features]

View File

@ -7,12 +7,12 @@ license = "MIT/Apache-2.0"
name = "wasm-bindgen-futures"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/futures"
readme = "./README.md"
version = "0.2.19"
version = "0.2.21"
[dependencies]
futures = "0.1.20"
js-sys = { path = "../js-sys", version = '0.2.4' }
wasm-bindgen = { path = "../..", version = '0.2.19' }
js-sys = { path = "../js-sys", version = '0.2.6' }
wasm-bindgen = { path = "../..", version = '0.2.21' }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
wasm-bindgen-test = { path = '../test', version = '0.2.19' }
wasm-bindgen-test = { path = '../test', version = '0.2.21' }

View File

@ -1,6 +1,6 @@
[package]
name = "js-sys"
version = "0.2.4"
version = "0.2.6"
authors = ["The wasm-bindgen Developers"]
readme = "./README.md"
categories = ["wasm"]
@ -18,9 +18,9 @@ test = false
doctest = false
[dependencies]
wasm-bindgen = { path = "../..", version = "0.2.19" }
wasm-bindgen = { path = "../..", version = "0.2.21" }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
futures = "0.1.20"
wasm-bindgen-test = { path = '../test', version = '=0.2.19' }
wasm-bindgen-futures = { path = '../futures', version = '=0.2.19' }
wasm-bindgen-test = { path = '../test', version = '=0.2.21' }
wasm-bindgen-futures = { path = '../futures', version = '=0.2.21' }

View File

@ -1203,6 +1203,7 @@ extern {
#[wasm_bindgen]
extern "C" {
#[derive(Clone, Debug)]
#[wasm_bindgen(extends = Object)]
pub type Math;
/// The Math.abs() function returns the absolute value of a number, that is
@ -2127,6 +2128,7 @@ extern {
#[wasm_bindgen]
extern "C" {
#[derive(Clone, Debug)]
#[wasm_bindgen(extends = Object)]
pub type Reflect;
/// The static `Reflect.apply()` method calls a target function with
@ -2864,6 +2866,29 @@ pub mod WebAssembly {
#[wasm_bindgen(js_namespace = WebAssembly)]
pub fn compile(buffer_source: &JsValue) -> Promise;
/// The `WebAssembly.instantiate()` function allows you to compile and
/// instantiate WebAssembly code.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)
#[wasm_bindgen(js_namespace = WebAssembly, js_name = instantiate)]
pub fn instantiate_buffer(buffer: &[u8], imports: &Object) -> Promise;
/// The `WebAssembly.instantiate()` function allows you to compile and
/// instantiate WebAssembly code.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiate)
#[wasm_bindgen(js_namespace = WebAssembly, js_name = instantiate)]
pub fn instantiate_module(module: &Module, imports: &Object) -> Promise;
/// The `WebAssembly.instantiateStreaming()` function compiles and
/// instantiates a WebAssembly module directly from a streamed
/// underlying source. This is the most efficient, optimized way to load
/// wasm code.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/instantiateStreaming)
#[wasm_bindgen(js_namespace = WebAssembly, js_name = instantiateStreaming)]
pub fn instantiate_streaming(response: &Promise, imports: &Object) -> Promise;
/// The `WebAssembly.validate()` function validates a given typed
/// array of WebAssembly binary code, returning whether the bytes
/// form a valid wasm module (`true`) or not (`false`).
@ -2894,6 +2919,38 @@ pub mod WebAssembly {
pub fn new(message: &str) -> CompileError;
}
// WebAssembly.Instance
#[wasm_bindgen]
extern "C" {
/// A `WebAssembly.Instance` object is a stateful, executable instance
/// of a `WebAssembly.Module`. Instance objects contain all the exported
/// WebAssembly functions that allow calling into WebAssembly code from
/// JavaScript.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance)
#[wasm_bindgen(extends = Object, js_namespace = WebAssembly)]
#[derive(Clone, Debug)]
pub type Instance;
/// The `WebAssembly.Instance()` constructor function can be called to
/// synchronously instantiate a given `WebAssembly.Module`
/// object. However, the primary way to get an `Instance` is through the
/// asynchronous `WebAssembly.instantiateStreaming()` function.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance)
#[wasm_bindgen(catch, constructor, js_namespace = WebAssembly)]
pub fn new(module: &Module, imports: &Object) -> Result<Instance, JsValue>;
/// The `exports` readonly property of the `WebAssembly.Instance` object
/// prototype returns an object containing as its members all the
/// functions exported from the WebAssembly module instance, to allow
/// them to be accessed and used by JavaScript.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Instance/exports)
#[wasm_bindgen(getter, method, js_namespace = WebAssembly)]
pub fn exports(this: &Instance) -> Object;
}
// WebAssembly.LinkError
#[wasm_bindgen]
extern "C" {
@ -3004,6 +3061,28 @@ pub mod WebAssembly {
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/length)
#[wasm_bindgen(method, getter, js_namespace = WebAssembly)]
pub fn length(this: &Table) -> u32;
/// The `get()` prototype method of the `WebAssembly.Table()` object
/// retrieves a function reference stored at a given index.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/get)
#[wasm_bindgen(method, catch, js_namespace = WebAssembly)]
pub fn get(this: &Table, index: u32) -> Result<Function, JsValue>;
/// The `grow()` prototype method of the `WebAssembly.Table` object
/// increases the size of the `Table` instance by a specified number of
/// elements.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/grow)
#[wasm_bindgen(method, catch, js_namespace = WebAssembly)]
pub fn grow(this: &Table, additional_capacity: u32) -> Result<u32, JsValue>;
/// The `set()` prototype method of the `WebAssembly.Table` object mutates a
/// reference stored at a given index to a different value.
///
/// [MDN documentation](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WebAssembly/Table/set)
#[wasm_bindgen(method, catch, js_namespace = WebAssembly)]
pub fn set(this: &Table, index: u32, function: &Function) -> Result<(), JsValue>;
}
// WebAssembly.Memory
@ -3048,8 +3127,12 @@ pub mod WebAssembly {
// JSON
#[wasm_bindgen]
extern "C" {
/// The `JSON` object contains methods for parsing [JavaScript Object
/// Notation (JSON)](https://json.org/) and converting values to JSON. It
/// can't be called or constructed, and aside from its two method
/// properties, it has no interesting functionality of its own.
#[derive(Clone, Debug)]
#[wasm_bindgen(extends = Object)]
pub type JSON;
/// The `JSON.parse()` method parses a JSON string, constructing the

View File

@ -1,4 +1,4 @@
use wasm_bindgen::JsValue;
use wasm_bindgen::prelude::*;
use wasm_bindgen_test::*;
use wasm_bindgen::JsCast;
use js_sys::*;
@ -82,3 +82,15 @@ fn stringify_error() {
let err_msg: String = From::from(err.message());
assert!(err_msg.contains("rust really rocks"));
}
#[wasm_bindgen_test]
fn json_extends() {
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_name = JSON)]
static json: JSON;
}
assert!(json.is_instance_of::<Object>());
let _: &Object = json.as_ref();
}

View File

@ -1,9 +1,22 @@
use std::f64::consts::PI;
use std::f64::{NEG_INFINITY, NAN};
use wasm_bindgen::{JsCast, prelude::*};
use wasm_bindgen_test::*;
use js_sys::*;
#[wasm_bindgen_test]
fn math_extends() {
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_name = Math)]
static math: Math;
}
assert!(math.is_instance_of::<Object>());
let _: &Object = math.as_ref();
}
macro_rules! assert_eq {
($a:expr, $b:expr) => ({
let (a, b) = (&$a, &$b);

View File

@ -1,4 +1,4 @@
use wasm_bindgen::prelude::*;
use wasm_bindgen::{JsCast, prelude::*};
use wasm_bindgen_test::*;
use js_sys::*;
@ -180,3 +180,15 @@ fn set_prototype_of() {
let obj = JsValue::from(obj);
assert_eq!(JsValue::from(Reflect::get_prototype_of(&obj)), JsValue::null());
}
#[wasm_bindgen_test]
fn reflect_extends() {
#[wasm_bindgen]
extern {
#[wasm_bindgen(js_name = Reflect)]
static reflect: Reflect;
}
assert!(reflect.is_instance_of::<Object>());
let _: &Object = reflect.as_ref();
}

View File

@ -21,8 +21,25 @@ function getInvalidTableObject() {
return { element: "anyfunc", initial: 1, maximum: 0 }
}
function getImports() {
return {
imports: {
imported_func: function () {
return 1;
}
}
};
}
// Polyfill `WebAssembly.instantiateStreaming` for node.
if (!global.WebAssembly.instantiateStreaming) {
global.WebAssembly.instantiateStreaming =
(response, imports) => response.then(buf => WebAssembly.instantiate(buf, imports));
}
module.exports = {
getInvalidTableObject,
getTableObject,
getWasmArray,
getImports,
};

View File

@ -1,11 +1,11 @@
use futures::Future;
use js_sys::*;
use wasm_bindgen::{JsCast, prelude::*};
use wasm_bindgen::{prelude::*, JsCast};
use wasm_bindgen_futures::JsFuture;
use wasm_bindgen_test::*;
#[wasm_bindgen(module = "tests/wasm/WebAssembly.js")]
extern {
extern "C" {
#[wasm_bindgen(js_name = getWasmArray)]
fn get_wasm_array() -> Uint8Array;
@ -14,6 +14,9 @@ extern {
#[wasm_bindgen(js_name = getInvalidTableObject)]
fn get_invalid_table_object() -> Object;
#[wasm_bindgen(js_name = getImports)]
fn get_imports() -> Object;
}
fn get_invalid_wasm() -> JsValue {
@ -38,23 +41,19 @@ fn validate() {
#[wasm_bindgen_test(async)]
fn compile_compile_error() -> impl Future<Item = (), Error = JsValue> {
let p = WebAssembly::compile(&get_invalid_wasm());
JsFuture::from(p)
.map(|_| unreachable!())
.or_else(|e| {
assert!(e.is_instance_of::<WebAssembly::CompileError>());
Ok(())
})
JsFuture::from(p).map(|_| unreachable!()).or_else(|e| {
assert!(e.is_instance_of::<WebAssembly::CompileError>());
Ok(())
})
}
#[wasm_bindgen_test(async)]
fn compile_type_error() -> impl Future<Item = (), Error = JsValue> {
let p = WebAssembly::compile(&get_bad_type_wasm());
JsFuture::from(p)
.map(|_| unreachable!())
.or_else(|e| {
assert!(e.is_instance_of::<TypeError>());
Ok(())
})
JsFuture::from(p).map(|_| unreachable!()).or_else(|e| {
assert!(e.is_instance_of::<TypeError>());
Ok(())
})
}
#[wasm_bindgen_test(async)]
@ -63,8 +62,7 @@ fn compile_valid() -> impl Future<Item = (), Error = JsValue> {
JsFuture::from(p)
.map(|module| {
assert!(module.is_instance_of::<WebAssembly::Module>());
})
.map_err(|_| unreachable!())
}).map_err(|_| unreachable!())
}
#[wasm_bindgen_test]
@ -81,7 +79,9 @@ fn module_error() {
let error = WebAssembly::Module::new(&get_invalid_wasm()).err().unwrap();
assert!(error.is_instance_of::<WebAssembly::CompileError>());
let error = WebAssembly::Module::new(&get_bad_type_wasm()).err().unwrap();
let error = WebAssembly::Module::new(&get_bad_type_wasm())
.err()
.unwrap();
assert!(error.is_instance_of::<TypeError>());
}
@ -117,7 +117,9 @@ fn table_inheritance() {
#[wasm_bindgen_test]
fn table_error() {
let error = WebAssembly::Table::new(&get_invalid_table_object()).err().unwrap();
let error = WebAssembly::Table::new(&get_invalid_table_object())
.err()
.unwrap();
assert!(error.is_instance_of::<RangeError>());
}
@ -125,6 +127,15 @@ fn table_error() {
fn table() {
let table = WebAssembly::Table::new(&get_table_object().into()).unwrap();
assert_eq!(table.length(), 1);
assert!(table.get(0).is_ok());
assert!(table.get(999).is_err());
table.grow(1).unwrap();
assert_eq!(table.length(), 2);
let f = table.get(0).unwrap();
table.set(1, &f).unwrap();
}
#[wasm_bindgen_test]
@ -154,6 +165,47 @@ fn runtime_error_inheritance() {
let _: &Error = error.as_ref();
}
#[wasm_bindgen_test]
fn webassembly_instance() {
let module = WebAssembly::Module::new(&get_valid_wasm()).unwrap();
let imports = get_imports();
let instance = WebAssembly::Instance::new(&module, &imports).unwrap();
// Inheritance chain is correct.
assert!(instance.is_instance_of::<WebAssembly::Instance>());
assert!(instance.is_instance_of::<Object>());
let _: &Object = instance.as_ref();
// Has expected exports.
let exports = instance.exports();
assert!(Reflect::has(exports.as_ref(), &"exported_func".into()));
}
#[wasm_bindgen_test(async)]
fn instantiate_module() -> impl Future<Item = (), Error = JsValue> {
let module = WebAssembly::Module::new(&get_valid_wasm()).unwrap();
let imports = get_imports();
let p = WebAssembly::instantiate_module(&module, &imports);
JsFuture::from(p)
.map(|inst| {
assert!(inst.is_instance_of::<WebAssembly::Instance>());
})
}
#[wasm_bindgen_test(async)]
fn instantiate_streaming() -> impl Future<Item = (), Error = JsValue> {
let response = Promise::resolve(&get_valid_wasm());
let imports = get_imports();
let p = WebAssembly::instantiate_streaming(&response, &imports);
JsFuture::from(p)
.map(|obj| {
assert!(
Reflect::get(obj.as_ref(), &"instance".into())
.is_instance_of::<WebAssembly::Instance>()
);
})
}
#[wasm_bindgen_test]
fn memory_works() {
let obj = Object::new();
@ -166,7 +218,10 @@ fn memory_works() {
assert_eq!(mem.grow(2), 2);
assert_eq!(mem.grow(3), 4);
assert_eq!(
mem.buffer().dyn_into::<ArrayBuffer>().unwrap().byte_length(),
mem.buffer()
.dyn_into::<ArrayBuffer>()
.unwrap()
.byte_length(),
7 * 64 * 1024,
);
}

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-macro-support"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro-support"
@ -15,8 +15,8 @@ spans = ["wasm-bindgen-backend/spans"]
extra-traits = ["syn/extra-traits"]
[dependencies]
syn = { version = '0.14', features = ['full'] }
syn = { version = '0.15.0', features = ['full'] }
quote = '0.6'
proc-macro2 = "0.4.9"
wasm-bindgen-backend = { path = "../backend", version = "=0.2.19" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.19" }
wasm-bindgen-backend = { path = "../backend", version = "=0.2.21" }
wasm-bindgen-shared = { path = "../shared", version = "=0.2.21" }

View File

@ -20,8 +20,8 @@ mod parser;
/// Takes the parsed input from a `#[wasm_bindgen]` macro and returns the generated bindings
pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diagnostic> {
let item = syn_parse::<syn::Item>(input, "rust item")?;
let opts = syn_parse(attr, "#[wasm_bindgen] attribute options")?;
let item = syn::parse2::<syn::Item>(input)?;
let opts = syn::parse2(attr)?;
let mut tokens = proc_macro2::TokenStream::new();
let mut program = backend::ast::Program::default();
@ -29,10 +29,3 @@ pub fn expand(attr: TokenStream, input: TokenStream) -> Result<TokenStream, Diag
program.try_to_tokens(&mut tokens)?;
Ok(tokens)
}
fn syn_parse<T: syn::synom::Synom>(tokens: TokenStream, name: &str) -> Result<T, Diagnostic> {
syn::parse2(tokens.clone())
.map_err(|err| {
Diagnostic::span_error(&tokens, format!("error parsing {}: {}", name, err))
})
}

View File

@ -5,6 +5,7 @@ use proc_macro2::{Delimiter, Ident, Span, TokenStream, TokenTree};
use quote::ToTokens;
use shared;
use syn;
use syn::parse::{Parse, ParseStream, Result as SynResult};
/// Parsed attributes from a `#[wasm_bindgen(..)]`.
#[cfg_attr(feature = "extra-traits", derive(Debug, PartialEq, Eq))]
@ -39,7 +40,7 @@ impl BindgenAttrs {
if group.delimiter() != Delimiter::Parenthesis {
bail_span!(attr, "malformed #[wasm_bindgen] attribute");
}
super::syn_parse(group.stream(), "#[wasm_bindgen] attribute options")
Ok(syn::parse2(group.stream())?)
}
/// Get the first module attribute
@ -193,19 +194,15 @@ impl BindgenAttrs {
}
}
impl syn::synom::Synom for BindgenAttrs {
named!(parse -> Self, alt!(
do_parse!(
opts: call!(
syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated
) >>
(BindgenAttrs {
attrs: opts.into_iter().collect(),
})
) => { |s| s }
|
epsilon!() => { |_| BindgenAttrs { attrs: Vec::new() } }
));
impl Parse for BindgenAttrs {
fn parse(input: ParseStream) -> SynResult<Self> {
if input.is_empty() {
return Ok(BindgenAttrs { attrs: Vec::new() })
}
let opts = syn::punctuated::Punctuated::<_, syn::token::Comma>::parse_terminated(input)?;
Ok(BindgenAttrs { attrs: opts.into_iter().collect() })
}
}
/// The possible attributes in the `#[wasm_bindgen]`.
@ -230,109 +227,94 @@ pub enum BindgenAttr {
Variadic,
}
impl syn::synom::Synom for BindgenAttr {
named!(parse -> Self, alt!(
call!(term, "catch") => { |_| BindgenAttr::Catch }
|
call!(term, "constructor") => { |_| BindgenAttr::Constructor }
|
call!(term, "method") => { |_| BindgenAttr::Method }
|
do_parse!(
call!(term, "static_method_of") >>
punct!(=) >>
cls: call!(term2ident) >>
(cls)
)=> { BindgenAttr::StaticMethodOf }
|
do_parse!(
call!(term, "getter") >>
val: option!(do_parse!(
punct!(=) >>
s: call!(term2ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Getter }
|
do_parse!(
call!(term, "setter") >>
val: option!(do_parse!(
punct!(=) >>
s: call!(term2ident) >>
(s)
)) >>
(val)
)=> { BindgenAttr::Setter }
|
call!(term, "indexing_getter") => { |_| BindgenAttr::IndexingGetter }
|
call!(term, "indexing_setter") => { |_| BindgenAttr::IndexingSetter }
|
call!(term, "indexing_deleter") => { |_| BindgenAttr::IndexingDeleter }
|
call!(term, "structural") => { |_| BindgenAttr::Structural }
|
call!(term, "readonly") => { |_| BindgenAttr::Readonly }
|
do_parse!(
call!(term, "js_namespace") >>
punct!(=) >>
ns: call!(term2ident) >>
(ns)
)=> { BindgenAttr::JsNamespace }
|
do_parse!(
call!(term, "module") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::Module }
|
do_parse!(
call!(term, "js_name") >>
punct!(=) >>
name: alt!(
syn!(syn::LitStr) => { |s| s.value() }
|
call!(term2ident) => { |s| s.to_string() }
) >>
(name)
)=> { BindgenAttr::JsName }
|
do_parse!(
call!(term, "js_class") >>
punct!(=) >>
s: syn!(syn::LitStr) >>
(s.value())
)=> { BindgenAttr::JsClass }
|
do_parse!(
call!(term, "extends") >>
punct!(=) >>
ns: call!(term2ident) >>
(ns)
)=> { BindgenAttr::Extends }
|
call!(term, "variadic") => { |_| BindgenAttr::Variadic }
));
}
/// Consumes a `Ident` with the given name
fn term<'a>(cursor: syn::buffer::Cursor<'a>, name: &str) -> syn::synom::PResult<'a, ()> {
if let Some((ident, next)) = cursor.ident() {
if ident == name {
return Ok(((), next));
impl Parse for BindgenAttr {
fn parse(input: ParseStream) -> SynResult<Self> {
let original = input.fork();
let attr: Ident = input.parse()?;
if attr == "catch" {
return Ok(BindgenAttr::Catch)
}
if attr == "constructor" {
return Ok(BindgenAttr::Constructor)
}
if attr == "method" {
return Ok(BindgenAttr::Method)
}
if attr == "indexing_getter" {
return Ok(BindgenAttr::IndexingGetter)
}
if attr == "indexing_setter" {
return Ok(BindgenAttr::IndexingSetter)
}
if attr == "indexing_deleter" {
return Ok(BindgenAttr::IndexingDeleter)
}
if attr == "structural" {
return Ok(BindgenAttr::Structural)
}
if attr == "readonly" {
return Ok(BindgenAttr::Readonly)
}
if attr == "variadic" {
return Ok(BindgenAttr::Variadic)
}
if attr == "static_method_of" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::StaticMethodOf(input.parse::<AnyIdent>()?.0))
}
if attr == "getter" {
if input.parse::<Token![=]>().is_ok() {
return Ok(BindgenAttr::Getter(Some(input.parse::<AnyIdent>()?.0)))
} else {
return Ok(BindgenAttr::Getter(None))
}
}
if attr == "setter" {
if input.parse::<Token![=]>().is_ok() {
return Ok(BindgenAttr::Setter(Some(input.parse::<AnyIdent>()?.0)))
} else {
return Ok(BindgenAttr::Setter(None))
}
}
if attr == "js_namespace" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::JsNamespace(input.parse::<AnyIdent>()?.0))
}
if attr == "extends" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::Extends(input.parse::<AnyIdent>()?.0))
}
if attr == "module" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::Module(input.parse::<syn::LitStr>()?.value()))
}
if attr == "js_class" {
input.parse::<Token![=]>()?;
return Ok(BindgenAttr::JsClass(input.parse::<syn::LitStr>()?.value()))
}
if attr == "js_name" {
input.parse::<Token![=]>()?;
let val = match input.parse::<syn::LitStr>() {
Ok(str) => str.value(),
Err(_) => input.parse::<AnyIdent>()?.0.to_string(),
};
return Ok(BindgenAttr::JsName(val))
}
Err(original.error("unknown attribute"))
}
syn::parse_error()
}
/// Consumes a `Ident` and returns it.
fn term2ident<'a>(cursor: syn::buffer::Cursor<'a>) -> syn::synom::PResult<'a, Ident> {
match cursor.ident() {
Some(pair) => Ok(pair),
None => syn::parse_error(),
struct AnyIdent(Ident);
impl Parse for AnyIdent {
fn parse(input: ParseStream) -> SynResult<Self> {
input.step(|cursor| {
match cursor.ident() {
Some((ident, remaining)) => Ok((AnyIdent(ident), remaining)),
None => Err(cursor.error("expected an identifier")),
}
})
}
}
@ -841,6 +823,10 @@ impl<'a, 'b> MacroParse<()> for (&'a Ident, &'b mut syn::ImplItem) {
&*item,
"type definitions in impls aren't supported with #[wasm_bindgen]"
),
syn::ImplItem::Existential(_) => bail_span!(
&*item,
"existentials in impls aren't supported with #[wasm_bindgen]"
),
syn::ImplItem::Macro(_) => {
bail_span!(&*item, "macros in impls aren't supported");
}

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-macro"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/macro"
@ -18,5 +18,5 @@ spans = ["wasm-bindgen-macro-support/spans"]
xxx_debug_only_print_generated_code = []
[dependencies]
wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.19" }
wasm-bindgen-macro-support = { path = "../macro-support", version = "=0.2.21" }
quote = "0.6"

View File

@ -1,4 +1,4 @@
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
error: unknown attribute
--> $DIR/attribute-fails-to-parse.rs:5:16
|
5 | #[wasm_bindgen(nonsense)]

View File

@ -1,10 +1,10 @@
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
error: unknown attribute
--> $DIR/invalid-attr.rs:5:16
|
5 | #[wasm_bindgen(x)]
| ^
error: error parsing #[wasm_bindgen] attribute options: failed to parse anything
error: unknown attribute
--> $DIR/invalid-attr.rs:10:20
|
10 | #[wasm_bindgen(y)]

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-shared"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/shared"

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-test-macro"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
description = "Internal testing macro for wasm-bindgen"
license = "MIT/Apache-2.0"

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-test"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
description = "Internal testing crate for wasm-bindgen"
license = "MIT/Apache-2.0"
@ -9,11 +9,11 @@ repository = "https://github.com/rustwasm/wasm-bindgen"
[dependencies]
console_error_panic_hook = '0.1'
futures = "0.1"
js-sys = { path = '../js-sys', version = '0.2.4' }
js-sys = { path = '../js-sys', version = '0.2.6' }
scoped-tls = "0.1"
wasm-bindgen = { path = '../..', version = '0.2.19' }
wasm-bindgen-futures = { path = '../futures', version = '0.2.19' }
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.19' }
wasm-bindgen = { path = '../..', version = '0.2.21' }
wasm-bindgen-futures = { path = '../futures', version = '0.2.21' }
wasm-bindgen-test-macro = { path = '../test-macro', version = '=0.2.21' }
[lib]
test = false

View File

@ -10,6 +10,6 @@ serde_json = "1.0"
proc-macro2 = "0.4.8"
quote = "0.6"
syn = { version = "0.14", default-features = false }
syn = { version = "0.15", default-features = false }
wasm-bindgen = { path = "../..", default-features = false }
wasm-bindgen-backend = { path = "../backend", default-features = false }

View File

@ -1,6 +1,6 @@
[package]
name = "wasm-bindgen-wasm-interpreter"
version = "0.2.19"
version = "0.2.21"
authors = ["The wasm-bindgen Developers"]
license = "MIT/Apache-2.0"
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/wasm-interpreter"

View File

@ -35,17 +35,22 @@ pub struct Interpreter {
// calculations)
imports: usize,
// Function index of the `__wbindgen_describe` imported function. We special
// case this to know when the environment's imported function is called.
// Function index of the `__wbindgen_describe` and
// `__wbindgen_describe_closure` imported functions. We special case this
// to know when the environment's imported function is called.
describe_idx: Option<u32>,
describe_closure_idx: Option<u32>,
// A mapping of string names to the function index, filled with all exported
// functions.
name_map: HashMap<String, u32>,
// The numerical index of the code section in the wasm module, indexed into
// The numerical index of the sections in the wasm module, indexed into
// the module's list of sections.
code_idx: Option<usize>,
types_idx: Option<usize>,
functions_idx: Option<usize>,
elements_idx: Option<usize>,
// The current stack pointer (global 0) and wasm memory (the stack). Only
// used in a limited capacity.
@ -60,6 +65,18 @@ pub struct Interpreter {
// very specific to wasm-bindgen and is the purpose for the existence of
// this module.
descriptor: Vec<u32>,
// When invoking the `__wbindgen_describe_closure` imported function, this
// stores the last table index argument, used for finding a different
// descriptor.
descriptor_table_idx: Option<u32>,
}
struct Sections<'a> {
code: &'a CodeSection,
types: &'a TypeSection,
functions: &'a FunctionSection,
elements: &'a ElementSection,
}
impl Interpreter {
@ -82,6 +99,9 @@ impl Interpreter {
for (i, s) in module.sections().iter().enumerate() {
match s {
Section::Code(_) => ret.code_idx = Some(i),
Section::Element(_) => ret.elements_idx = Some(i),
Section::Type(_) => ret.types_idx = Some(i),
Section::Function(_) => ret.functions_idx = Some(i),
_ => {}
}
}
@ -101,10 +121,11 @@ impl Interpreter {
if entry.module() != "__wbindgen_placeholder__" {
continue
}
if entry.field() != "__wbindgen_describe" {
continue
if entry.field() == "__wbindgen_describe" {
ret.describe_idx = Some(idx - 1 as u32);
} else if entry.field() == "__wbindgen_describe_closure" {
ret.describe_closure_idx = Some(idx - 1 as u32);
}
ret.describe_idx = Some(idx - 1 as u32);
}
}
@ -142,47 +163,171 @@ impl Interpreter {
///
/// Returns `Some` if `func` was found in the `module` and `None` if it was
/// not found in the `module`.
pub fn interpret(&mut self, func: &str, module: &Module) -> Option<&[u32]> {
self.descriptor.truncate(0);
pub fn interpret_descriptor(
&mut self,
func: &str,
module: &Module,
) -> Option<&[u32]> {
let idx = *self.name_map.get(func)?;
let code = match &module.sections()[self.code_idx.unwrap()] {
Section::Code(s) => s,
_ => panic!(),
};
self.with_sections(module, |me, sections| {
me.interpret_descriptor_idx(idx, sections)
})
}
fn interpret_descriptor_idx(
&mut self,
idx: u32,
sections: &Sections,
) -> Option<&[u32]> {
self.descriptor.truncate(0);
// We should have a blank wasm and LLVM stack at both the start and end
// of the call.
assert_eq!(self.sp, self.mem.len() as i32);
assert_eq!(self.stack.len(), 0);
self.call(idx, code);
self.call(idx, sections);
assert_eq!(self.stack.len(), 0);
assert_eq!(self.sp, self.mem.len() as i32);
Some(&self.descriptor)
}
fn call(&mut self, idx: u32, code: &CodeSection) {
/// Interprets a "closure descriptor", figuring out the signature of the
/// closure that was intended.
///
/// This function will take a `code_idx` which is known to internally
/// execute `__wbindgen_describe_closure` and interpret it. The
/// `wasm-bindgen` crate controls all callers of this internal import. It
/// will then take the index passed to `__wbindgen_describe_closure` and
/// interpret it as a function pointer. This means it'll look up within the
/// element section (function table) which index it points to. Upon finding
/// the relevant entry it'll assume that function is a descriptor function,
/// and then it will execute the descriptor function.
///
/// The returned value is the return value of the descriptor function found.
/// The `entry_removal_list` list is also then populated with an index of
/// the entry in the elements section (and then the index within that
/// section) of the function that needs to be snip'd out.
pub fn interpret_closure_descriptor(
&mut self,
code_idx: usize,
module: &Module,
entry_removal_list: &mut Vec<(usize, usize)>,
) -> Option<&[u32]> {
self.with_sections(module, |me, sections| {
me._interpret_closure_descriptor(code_idx, sections, entry_removal_list)
})
}
fn _interpret_closure_descriptor(
&mut self,
code_idx: usize,
sections: &Sections,
entry_removal_list: &mut Vec<(usize, usize)>,
) -> Option<&[u32]> {
// Call the `code_idx` function. This is an internal `#[inline(never)]`
// whose code is completely controlled by the `wasm-bindgen` crate, so
// it should take two arguments and return one (all of which we don't
// care about here). What we're interested in is that while executing
// this function it'll call `__wbindgen_describe_closure` with an
// argument that we look for.
assert!(self.descriptor_table_idx.is_none());
let closure_descriptor_idx = (code_idx + self.imports) as u32;
self.stack.push(0);
self.stack.push(0);
self.call(closure_descriptor_idx, sections);
assert_eq!(self.stack.len(), 1);
self.stack.pop();
let descriptor_table_idx = self.descriptor_table_idx.take().unwrap();
// After we've got the table index of the descriptor function we're
// interested go take a look in the function table to find what the
// actual index of the function is.
let (entry_idx, offset, entry) = sections.elements.entries()
.iter()
.enumerate()
.filter_map(|(i, entry)| {
let code = entry.offset().code();
if code.len() != 2 {
return None
}
if code[1] != Instruction::End {
return None
}
match code[0] {
Instruction::I32Const(x) => Some((i, x as u32, entry)),
_ => None,
}
})
.find(|(_i, offset, entry)| {
*offset <= descriptor_table_idx &&
descriptor_table_idx < (*offset + entry.members().len() as u32)
})
.expect("failed to find index in table elements");
let idx = (descriptor_table_idx - offset) as usize;
let descriptor_idx = entry.members()[idx];
// This is used later to actually remove the entry from the table, but
// we don't do the removal just yet
entry_removal_list.push((entry_idx, idx));
// And now execute the descriptor!
self.interpret_descriptor_idx(descriptor_idx, sections)
}
/// Returns the function space index of the `__wbindgen_describe_closure`
/// imported function.
pub fn describe_closure_idx(&self) -> Option<u32> {
self.describe_closure_idx
}
fn call(&mut self, idx: u32, sections: &Sections) {
use parity_wasm::elements::Instruction::*;
let idx = idx as usize;
assert!(idx >= self.imports); // can't call imported functions
let body = &code.bodies()[idx - self.imports];
let code_idx = idx - self.imports;
let body = &sections.code.bodies()[code_idx];
// Allocate space for our call frame's local variables. All local
// variables should be of the `i32` type.
assert!(body.locals().len() <= 1, "too many local types");
let locals = body.locals()
let nlocals = body.locals()
.get(0)
.map(|i| {
assert_eq!(i.value_type(), ValueType::I32);
i.count()
})
.unwrap_or(0);
let mut locals = vec![0; locals as usize];
let code_sig = sections.functions.entries()[code_idx].type_ref();
let function_ty = match &sections.types.types()[code_sig as usize] {
Type::Function(t) => t,
};
let mut locals = Vec::with_capacity(function_ty.params().len() + nlocals as usize);
// Any function parameters we have get popped off the stack and put into
// the first few locals ...
for param in function_ty.params() {
assert_eq!(*param, ValueType::I32);
locals.push(self.stack.pop().unwrap());
}
// ... and the remaining locals all start as zero ...
for _ in 0..nlocals {
locals.push(0);
}
// ... and we expect one stack slot at the end if there's a returned
// value
let before = self.stack.len();
let stack_after = match function_ty.return_type() {
Some(t) => {
assert_eq!(t, ValueType::I32);
before + 1
}
None => before,
};
// Actual interpretation loop! We keep track of our stack's length to
// recover it as part of the `Return` instruction, and otherwise this is
// a pretty straightforward interpretation loop.
let before = self.stack.len();
for instr in body.code().elements() {
match instr {
I32Const(x) => self.stack.push(*x),
@ -198,8 +343,14 @@ impl Interpreter {
// Otherwise this is a normal call so we recurse.
if Some(*idx) == self.describe_idx {
self.descriptor.push(self.stack.pop().unwrap() as u32);
} else if Some(*idx) == self.describe_closure_idx {
self.descriptor_table_idx =
Some(self.stack.pop().unwrap() as u32);
assert_eq!(self.stack.pop(), Some(0));
assert_eq!(self.stack.pop(), Some(0));
self.stack.push(0);
} else {
self.call(*idx, code);
self.call(*idx, sections);
}
}
GetGlobal(0) => self.stack.push(self.sp),
@ -223,7 +374,7 @@ impl Interpreter {
let addr = self.stack.pop().unwrap() as u32;
self.stack.push(self.mem[((addr + *offset) as usize) / 4]);
}
Return => self.stack.truncate(before),
Return => self.stack.truncate(stack_after),
End => break,
// All other instructions shouldn't be used by our various
@ -238,6 +389,37 @@ impl Interpreter {
s => panic!("unknown instruction {:?}", s),
}
}
assert_eq!(self.stack.len(), before);
assert_eq!(self.stack.len(), stack_after);
}
fn with_sections<'a, T>(
&'a mut self,
module: &Module,
f: impl FnOnce(&'a mut Self, &Sections) -> T,
) -> T {
macro_rules! access_with_defaults {
($(
let $var: ident = module.sections[self.$field:ident]
($name:ident);
)*) => {$(
let default = Default::default();
let $var = match self.$field {
Some(i) => {
match &module.sections()[i] {
Section::$name(s) => s,
_ => panic!(),
}
}
None => &default,
};
)*}
}
access_with_defaults! {
let code = module.sections[self.code_idx] (Code);
let types = module.sections[self.types_idx] (Type);
let functions = module.sections[self.functions_idx] (Function);
let elements = module.sections[self.elements_idx] (Element);
}
f(self, &Sections { code, types, functions, elements })
}
}

View File

@ -18,13 +18,13 @@ wasm-bindgen-webidl = { path = "../webidl", version = "=0.2.17" }
sourcefile = "0.1"
[dependencies]
wasm-bindgen = { path = "../..", version = "0.2.19" }
js-sys = { path = '../js-sys', version = '0.2.4' }
wasm-bindgen = { path = "../..", version = "0.2.21" }
js-sys = { path = '../js-sys', version = '0.2.6' }
[target.'cfg(target_arch = "wasm32")'.dev-dependencies]
futures = "0.1"
wasm-bindgen-test = { path = '../test', version = '0.2.19' }
wasm-bindgen-futures = { path = '../futures', version = '0.2.19' }
wasm-bindgen-test = { path = '../test', version = '0.2.21' }
wasm-bindgen-futures = { path = '../futures', version = '0.2.21' }
# This list is generated by passing `__WASM_BINDGEN_DUMP_FEATURES=foo` when
# compiling this crate which dumps the total list of features to a file called

View File

@ -7,5 +7,5 @@ fn take_and_return_a_bunch_of_slices() {
let f = ArrayBufferTest::new().unwrap();
let x = f.get_buffer();
f.set_buffer(None);
f.set_buffer(Some(x));
f.set_buffer(Some(&x));
}

View File

@ -153,3 +153,13 @@ global.MixinFoo = class MixinFoo {
global.Overloads = class {
foo() {}
};
global.InvokeCallback = class {
invoke(f) { f(); }
callAdd(f) {
return f(1, 2);
}
callRepeat(f) {
return f('ab', 4);
}
};

View File

@ -1,4 +1,6 @@
use wasm_bindgen_test::*;
use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast;
include!(concat!(env!("OUT_DIR"), "/simple.rs"));
@ -130,3 +132,26 @@ fn overload_naming() {
o.foo_with_arg_and_f32("x", 2.0);
o.foo_with_arg_and_i16("x", 5);
}
#[wasm_bindgen_test]
fn callback() {
let o = InvokeCallback::new().unwrap();
{
static mut HIT: bool = false;
let cb = Closure::wrap(Box::new(move || {
unsafe { HIT = true; }
}) as Box<FnMut()>);
o.invoke(cb.as_ref().unchecked_ref());
assert!(unsafe { HIT });
}
let cb = Closure::wrap(Box::new(move |a, b| {
a + b
}) as Box<FnMut(u32, u32) -> u32>);
assert_eq!(o.call_add(cb.as_ref().unchecked_ref()), 3);
let cb = Closure::wrap(Box::new(move |a: String, b| {
a.repeat(b)
}) as Box<FnMut(String, usize) -> String>);
assert_eq!(o.call_repeat(cb.as_ref().unchecked_ref()), "abababab");
}

View File

@ -100,3 +100,15 @@ interface Overloads {
void foo(DOMString arg, optional long a);
void foo(DOMString arg, (float or short) b);
};
callback MyCallback = any();
callback AddCallback = long(long a, long b);
callback RepeatCallback = DOMString(DOMString a, long cnt);
callback GetAnswer = long();
[Constructor()]
interface InvokeCallback {
void invoke(MyCallback callback);
long callAdd(AddCallback callback);
DOMString callRepeat(RepeatCallback callback);
};

View File

@ -18,6 +18,6 @@ heck = "0.3"
log = "0.4.1"
proc-macro2 = "0.4.8"
quote = '0.6'
syn = { version = '0.14', features = ['full'] }
wasm-bindgen-backend = { version = "=0.2.19", path = "../backend" }
syn = { version = '0.15', features = ['full'] }
wasm-bindgen-backend = { version = "=0.2.21", path = "../backend" }
weedle = "0.7"

View File

@ -34,6 +34,7 @@ pub(crate) struct FirstPassRecord<'src> {
pub(crate) namespaces: BTreeMap<&'src str, NamespaceData<'src>>,
pub(crate) includes: BTreeMap<&'src str, BTreeSet<&'src str>>,
pub(crate) dictionaries: BTreeMap<&'src str, DictionaryData<'src>>,
pub(crate) callbacks: BTreeSet<&'src str>,
}
/// We need to collect interface data during the first pass, to be used later.
@ -136,12 +137,9 @@ impl<'src> FirstPass<'src, ()> for weedle::Definition<'src> {
Namespace(namespace) => namespace.first_pass(record, ()),
PartialNamespace(namespace) => namespace.first_pass(record, ()),
Typedef(typedef) => typedef.first_pass(record, ()),
Callback(callback) => callback.first_pass(record, ()),
Implements(_) => Ok(()),
Callback(..) => {
warn!("Unsupported WebIDL Callback definition: {:?}", self);
Ok(())
}
CallbackInterface(..) => {
warn!("Unsupported WebIDL CallbackInterface definition: {:?}", self);
Ok(())
@ -684,6 +682,13 @@ impl<'src> FirstPass<'src, &'src str> for weedle::namespace::OperationNamespaceM
}
}
impl<'src> FirstPass<'src, ()> for weedle::CallbackDefinition<'src> {
fn first_pass(&'src self, record: &mut FirstPassRecord<'src>, _: ()) -> Result<()> {
record.callbacks.insert(self.identifier.0);
Ok(())
}
}
impl<'a> FirstPassRecord<'a> {
pub fn all_superclasses<'me>(&'me self, interface: &str)
-> impl Iterator<Item = String> + 'me

View File

@ -28,6 +28,7 @@ pub(crate) enum IdlType<'a> {
Object,
Symbol,
Error,
Callback,
ArrayBuffer,
DataView,
@ -293,6 +294,8 @@ impl<'a> ToIdlType<'a> for Identifier<'a> {
Some(IdlType::Dictionary(self.0))
} else if record.enums.contains_key(self.0) {
Some(IdlType::Enum(self.0))
} else if record.callbacks.contains(self.0) {
Some(IdlType::Callback)
} else {
warn!("Unrecognized type: {}", self.0);
None
@ -364,6 +367,7 @@ impl<'a> IdlType<'a> {
IdlType::Object => dst.push_str("object"),
IdlType::Symbol => dst.push_str("symbol"),
IdlType::Error => dst.push_str("error"),
IdlType::Callback => dst.push_str("callback"),
IdlType::ArrayBuffer => dst.push_str("array_buffer"),
IdlType::DataView => dst.push_str("data_view"),
@ -426,6 +430,17 @@ impl<'a> IdlType<'a> {
/// Converts to syn type if possible.
pub(crate) fn to_syn_type(&self, pos: TypePosition) -> Option<syn::Type> {
let anyref = |ty| {
Some(match pos {
TypePosition::Argument => shared_ref(ty, false),
TypePosition::Return => ty,
})
};
let js_sys = |name: &str| {
let path = vec![rust_ident("js_sys"), rust_ident(name)];
let ty = leading_colon_path_ty(path);
anyref(ty)
};
match self {
IdlType::Boolean => Some(ident_ty(raw_ident("bool"))),
IdlType::Byte => Some(ident_ty(raw_ident("i8"))),
@ -446,17 +461,11 @@ impl<'a> IdlType<'a> {
TypePosition::Argument => Some(shared_ref(ident_ty(raw_ident("str")), false)),
TypePosition::Return => Some(ident_ty(raw_ident("String"))),
},
IdlType::Object => {
let path = vec![rust_ident("js_sys"), rust_ident("Object")];
Some(leading_colon_path_ty(path))
},
IdlType::Object => js_sys("Object"),
IdlType::Symbol => None,
IdlType::Error => None,
IdlType::ArrayBuffer => {
let path = vec![rust_ident("js_sys"), rust_ident("ArrayBuffer")];
Some(leading_colon_path_ty(path))
},
IdlType::ArrayBuffer => js_sys("ArrayBuffer"),
IdlType::DataView => None,
IdlType::Int8Array => Some(array("i8", pos, false)),
IdlType::Uint8Array => Some(array("u8", pos, false)),
@ -469,10 +478,7 @@ impl<'a> IdlType<'a> {
IdlType::Float32Array => Some(array("f32", pos, false)),
IdlType::Float64Array => Some(array("f64", pos, false)),
IdlType::ArrayBufferView | IdlType::BufferSource => {
let path = vec![rust_ident("js_sys"), rust_ident("Object")];
Some(leading_colon_path_ty(path))
},
IdlType::ArrayBufferView | IdlType::BufferSource => js_sys("Object"),
IdlType::Interface(name)
| IdlType::Dictionary(name) => {
let ty = ident_ty(rust_ident(camel_case_ident(name).as_str()));
@ -487,15 +493,7 @@ impl<'a> IdlType<'a> {
IdlType::Nullable(idl_type) => Some(option_ty(idl_type.to_syn_type(pos)?)),
IdlType::FrozenArray(_idl_type) => None,
IdlType::Sequence(_idl_type) => None,
IdlType::Promise(_idl_type) => {
let path = vec![rust_ident("js_sys"), rust_ident("Promise")];
let ty = leading_colon_path_ty(path);
if pos == TypePosition::Argument {
Some(shared_ref(ty, false))
} else {
Some(ty)
}
}
IdlType::Promise(_idl_type) => js_sys("Promise"),
IdlType::Record(_idl_type_from, _idl_type_to) => None,
IdlType::Union(idl_types) => {
// Handles union types in all places except operation argument types.
@ -521,9 +519,10 @@ impl<'a> IdlType<'a> {
IdlType::Any => {
let path = vec![rust_ident("wasm_bindgen"), rust_ident("JsValue")];
Some(leading_colon_path_ty(path))
anyref(leading_colon_path_ty(path))
},
IdlType::Void => None,
IdlType::Callback => js_sys("Function"),
}
}
@ -577,9 +576,12 @@ impl<'a> IdlType<'a> {
.iter()
.flat_map(|idl_type| idl_type.flatten())
.collect(),
IdlType::ArrayBufferView | IdlType::BufferSource =>
vec![IdlType::Object, IdlType::Uint8ArrayMut],
IdlType::ArrayBufferView => {
vec![IdlType::ArrayBufferView, IdlType::Uint8ArrayMut]
}
IdlType::BufferSource => {
vec![IdlType::BufferSource, IdlType::Uint8ArrayMut]
}
idl_type @ _ => vec![idl_type.clone()],
}
}

View File

@ -138,7 +138,7 @@ fn builtin_idents() -> BTreeSet<Ident> {
vec![
"str", "char", "bool", "JsValue", "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64",
"usize", "isize", "f32", "f64", "Result", "String", "Box", "Vec", "Option",
"ArrayBuffer", "Object", "Promise",
"ArrayBuffer", "Object", "Promise", "Function",
].into_iter()
.map(|id| proc_macro2::Ident::new(id, proc_macro2::Span::call_site())),
)

View File

@ -5,4 +5,99 @@ source lives at `wasm-bindgen/crates/web-sys`.
The `web-sys` crate is **entirely** mechanically generated inside `build.rs`
using `wasm-bindgen`'s WebIDL frontend and the WebIDL interface definitions for
Web APIs.
Web APIs. This means that `web-sys` isn't always the most ergonomic crate to
use, but it's intended to provide verified and correct bindings to the web
platform, and then better interfaces can be iterated on crates.io!
### Using `web-sys`
Let's say you want to use an API defined on the web. Chances are this API is
defined in `web-sys`, so let's go through some steps necessary to use it!
First up, search the [api documentation][api] for your API. For example if
we're looking for JS's [`fetch`][jsfetch] API we'd start out by [searching for
`fetch`][search-fetch]. The first thing you'll probably notice is that there's
no function called `fetch`! Fear not, though, as the API exists in multiple
forms:
* [`Window::fetch_with_str`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_str)
* [`Window::fetch_with_request`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_request)
* [`Window::fetch_with_str_and_init`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/str_and_inituct.Window.html#method.fetch_with_str_and_init)
* [`Window::fetch_with_request_and_init`](https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_request_and_init)
What's happening here is that the [`fetch` function][fetchfn] actually supports
multiple signatures of arguments, and we've taken the WebIDL definition for this
function and expanded it to unique signatures in Rust (as Rust doesn't have
function name overloading).
When an API is selected it should have documentation pointing at MDN indicating
what web API its binding. This is often a great way to double check arguments
and such as well, MDN is a great resource! You'll also notice in the
documentation that the API may require some `web-sys` Cargo features to be
activated. For example [`fetch_with_str`] requires the `Window` feature to be
activated. In general an API needs features corresponding to all the types
you'll find in the signature to be activated.
To load up this API let's depend on `web-sys`:
```toml
[dependencies]
wasm-bindgen = "0.2"
web-sys = { version = "0.1", features = ['Window'] }
# Or optionally,
# [target.wasm32-unknown-unknown.dependencies]
# ...
```
> **Note**: Currently `web-sys` is not available on crates.io so you'll also
> need to do this in your manifest:
>
> ```toml
> [patch.crates-io]
> web-sys = { git = 'https://github.com/rustwasm/wasm-bindgen' }
> wasm-bindgen = { git = 'https://github.com/rustwasm/wasm-bindgen' }
> ```
And next up we can use the API like so:
```rust
extern crate web_sys;
extern crate wasm_bindgen;
use wasm_bindgen::prelude::*;
use web_sys::Window;
#[wasm_bindgen]
pub fn run() {
let promise = Window::fetch_with_str("http://example.com/");
// ...
}
```
and you should be good to go!
### Type translations in `web-sys`
Most of the types specified in WebIDL have relatively straightforward
translations into `web-sys`, but it's worth calling out a few in particular:
* `BufferSource` and `ArrayBufferView` - these two types show up in a number of
APIs that generally deal with a buffer of bytes. We bind them in `web-sys`
with two different types, `Object` and `&mut [u8]`. Using `Object` allows
passing in arbitrary JS values which represent a view of bytes (like any typed
array object), and `&mut [u8]` allows using a raw slice in Rust. Unfortunately
we must pessimistically assume that JS will modify all slices as we don't
currently have information of whether they're modified or not.
* Callbacks are all represented as `js_sys::Function`. This means that all
callbacks going through `web-sys` are a raw JS value. You can work with this
by either juggling actual `js_sys::Function` instances or you can create a
`Closure<FnMut(...)>`, extract the underlying `JsValue` with `as_ref`, and
then use `JsCast::unchecked_ref` to convert it to a `js_sys::Function`.
[api]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/
[jsfetch]: https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API
[search-fetch]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/?search=fetch
[fetchfn]: https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/fetch
[`fetch_with_str`]: https://rustwasm.github.io/wasm-bindgen/api/web_sys/struct.Window.html#method.fetch_with_str

View File

@ -4,8 +4,6 @@
//! closures" from Rust to JS. Some more details can be found on the `Closure`
//! type itself.
#![allow(const_err)] // FIXME(rust-lang/rust#52603)
use std::cell::UnsafeCell;
#[cfg(feature = "nightly")]
use std::marker::Unsize;
@ -70,14 +68,12 @@ use throw;
/// }
/// ```
pub struct Closure<T: ?Sized> {
// Actually a `Rc` pointer, but in raw form so we can easily make copies.
// See below documentation for why this is in an `Rc`.
inner: *const UnsafeCell<Box<T>>,
js: UnsafeCell<ManuallyDrop<JsValue>>,
js: ManuallyDrop<JsValue>,
_keep_this_data_alive: Rc<UnsafeCell<Box<T>>>,
}
impl<T> Closure<T>
where T: ?Sized,
where T: ?Sized + WasmClosure,
{
/// Creates a new instance of `Closure` from the provided Rust closure.
///
@ -103,9 +99,73 @@ impl<T> Closure<T>
///
/// This is the function where the JS closure is manufactured.
pub fn wrap(t: Box<T>) -> Closure<T> {
let data = Rc::new(UnsafeCell::new(t));
let ptr = &*data as *const UnsafeCell<Box<T>>;
// Here we need to create a `JsValue` with the data and `T::invoke()`
// function pointer. To do that we... take a few unconventional turns.
// In essence what happens here is this:
//
// 1. First up, below we call a function, `breaks_if_inlined`. This
// function, as the name implies, does not work if it's inlined.
// More on that in a moment.
// 2. This function internally calls a special import recognized by the
// `wasm-bindgen` CLI tool, `__wbindgen_describe_closure`. This
// imported symbol is similar to `__wbindgen_describe` in that it's
// not intended to show up in the final binary but it's an
// intermediate state for a `wasm-bindgen` binary.
// 3. The `__wbindgen_describe_closure` import is namely passed a
// descriptor function, monomorphized for each invocation.
//
// Most of this doesn't actually make sense to happen at runtime! The
// real magic happens when `wasm-bindgen` comes along and updates our
// generated code. When `wasm-bindgen` runs it performs a few tasks:
//
// * First, it finds all functions that call
// `__wbindgen_describe_closure`. These are all `breaks_if_inlined`
// defined below as the symbol isn't called anywhere else.
// * Next, `wasm-bindgen` executes the `breaks_if_inlined`
// monomorphized functions, passing it dummy arguments. This will
// execute the function just enough to invoke the special import,
// namely telling us about the function pointer that is the describe
// shim.
// * This knowledge is then used to actually find the descriptor in the
// function table which is then executed to figure out the signature
// of the closure.
// * Finally, and probably most heinously, the call to
// `breaks_if_inlined` is rewritten to call an otherwise globally
// imported function. This globally imported function will generate
// the `JsValue` for this closure specialized for the signature in
// question.
//
// Later on `wasm-gc` will clean up all the dead code and ensure that
// we don't actually call `__wbindgen_describe_closure` at runtime. This
// means we will end up not actually calling `breaks_if_inlined` in the
// final binary, all calls to that function should be pruned.
//
// See crates/cli-support/src/js/closures.rs for a more information
// about what's going on here.
extern fn describe<T: WasmClosure + ?Sized>() {
inform(CLOSURE);
T::describe()
}
#[inline(never)]
unsafe fn breaks_if_inlined<T: WasmClosure + ?Sized>(
ptr: usize,
invoke: u32,
) -> u32 {
super::__wbindgen_describe_closure(ptr as u32, invoke, describe::<T> as u32)
}
let idx = unsafe {
breaks_if_inlined::<T>(ptr as usize, T::invoke_fn())
};
Closure {
inner: Rc::into_raw(Rc::new(UnsafeCell::new(t))),
js: UnsafeCell::new(ManuallyDrop::new(JsValue { idx: !0 })),
js: ManuallyDrop::new(JsValue { idx }),
_keep_this_data_alive: data,
}
}
@ -122,7 +182,7 @@ impl<T> Closure<T>
/// cleanup as it can.
pub fn forget(self) {
unsafe {
let idx = (*self.js.get()).idx;
let idx = self.js.idx;
if idx != !0 {
super::__wbindgen_cb_forget(idx);
}
@ -131,12 +191,17 @@ impl<T> Closure<T>
}
}
impl<T: ?Sized> AsRef<JsValue> for Closure<T> {
fn as_ref(&self) -> &JsValue {
&self.js
}
}
impl<T> WasmDescribe for Closure<T>
where T: WasmClosure + ?Sized,
{
fn describe() {
inform(CLOSURE);
T::describe();
inform(ANYREF);
}
}
@ -147,11 +212,7 @@ impl<'a, T> IntoWasmAbi for &'a Closure<T>
type Abi = u32;
fn into_abi(self, extra: &mut Stack) -> u32 {
unsafe {
extra.push(T::invoke_fn());
extra.push(self.inner as u32);
&mut (*self.js.get()).idx as *const u32 as u32
}
(&*self.js).into_abi(extra)
}
}
@ -170,11 +231,9 @@ impl<T> Drop for Closure<T>
{
fn drop(&mut self) {
unsafe {
let idx = (*self.js.get()).idx;
if idx != !0 {
super::__wbindgen_cb_drop(idx);
}
drop(Rc::from_raw(self.inner));
// this will implicitly drop our strong reference in addition to
// invalidating all future invocations of the closure
super::__wbindgen_cb_drop(self.js.idx);
}
}
}

View File

@ -433,6 +433,7 @@ externs! {
fn __wbindgen_cb_forget(idx: u32) -> ();
fn __wbindgen_describe(v: u32) -> ();
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(idx: u32, ptr: *mut *mut u8) -> usize;