mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-11 22:56:06 +00:00
Merge pull request #1566 from alexcrichton/webidl-bindings-refactor-1
First refactor for WebIDL bindings
This commit is contained in:
commit
cf2a42ce7c
@ -101,7 +101,7 @@ jobs:
|
|||||||
steps:
|
steps:
|
||||||
- template: ci/azure-install-rust.yml
|
- template: ci/azure-install-rust.yml
|
||||||
- template: ci/azure-install-node.yml
|
- template: ci/azure-install-node.yml
|
||||||
- template: ci/azure-install-sccache.yml
|
#- template: ci/azure-install-sccache.yml
|
||||||
- script: cargo test -p wasm-bindgen-webidl
|
- script: cargo test -p wasm-bindgen-webidl
|
||||||
- script: cargo test -p webidl-tests --target wasm32-unknown-unknown
|
- script: cargo test -p webidl-tests --target wasm32-unknown-unknown
|
||||||
env:
|
env:
|
||||||
@ -128,7 +128,7 @@ jobs:
|
|||||||
cd wabt/build
|
cd wabt/build
|
||||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=off -DCMAKE_CXX_COMPILER_LAUNCHER=$RUSTC_WRAPPER
|
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=off -DCMAKE_CXX_COMPILER_LAUNCHER=$RUSTC_WRAPPER
|
||||||
cmake --build . -- -j$(nproc)
|
cmake --build . -- -j$(nproc)
|
||||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$PWD"
|
echo "##vso[task.prependpath]$PWD"
|
||||||
- script: cargo test -p wasm-bindgen-wasm-interpreter
|
- script: cargo test -p wasm-bindgen-wasm-interpreter
|
||||||
|
|
||||||
- job: test_typescript_output
|
- job: test_typescript_output
|
||||||
@ -171,7 +171,7 @@ jobs:
|
|||||||
- script: |
|
- script: |
|
||||||
set -e
|
set -e
|
||||||
curl -L https://github.com/japaric/xargo/releases/download/v0.3.13/xargo-v0.3.13-x86_64-unknown-linux-musl.tar.gz | tar xzf -
|
curl -L https://github.com/japaric/xargo/releases/download/v0.3.13/xargo-v0.3.13-x86_64-unknown-linux-musl.tar.gz | tar xzf -
|
||||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$PWD"
|
echo "##vso[task.prependpath]$PWD"
|
||||||
displayName: "install xargo"
|
displayName: "install xargo"
|
||||||
- script: |
|
- script: |
|
||||||
set -e
|
set -e
|
||||||
|
@ -3,17 +3,20 @@ parameters:
|
|||||||
|
|
||||||
steps:
|
steps:
|
||||||
- bash: |
|
- bash: |
|
||||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $TOOLCHAIN
|
set -e
|
||||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin"
|
if command -v rustup; then
|
||||||
|
rustup update $TOOLCHAIN
|
||||||
|
rustup default $TOOLCHAIN
|
||||||
|
else
|
||||||
|
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $TOOLCHAIN
|
||||||
|
echo "##vso[task.prependpath]$HOME/.cargo/bin"
|
||||||
|
fi
|
||||||
displayName: Install rust - Unix
|
displayName: Install rust - Unix
|
||||||
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
||||||
env:
|
env:
|
||||||
TOOLCHAIN: ${{ parameters.toolchain }}
|
TOOLCHAIN: ${{ parameters.toolchain }}
|
||||||
|
|
||||||
- script: |
|
- bash: rustup update --no-self-update $TOOLCHAIN && rustup default $TOOLCHAIN
|
||||||
curl -sSf -o rustup-init.exe https://win.rustup.rs
|
|
||||||
rustup-init.exe -y --default-toolchain %TOOLCHAIN%
|
|
||||||
echo "##vso[task.setvariable variable=PATH;]%PATH%;%USERPROFILE%\.cargo\bin"
|
|
||||||
displayName: Install rust - Windows
|
displayName: Install rust - Windows
|
||||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||||
env:
|
env:
|
||||||
|
@ -4,5 +4,5 @@ steps:
|
|||||||
- script: |
|
- script: |
|
||||||
set -ex
|
set -ex
|
||||||
cargo build -p wasm-bindgen-cli
|
cargo build -p wasm-bindgen-cli
|
||||||
ln -snf `pwd`/target/debug/wasm-bindgen $HOME/.cargo/bin/wasm-bindgen
|
ln -snf `pwd`/target/debug/wasm-bindgen $(dirname `which cargo`)/wasm-bindgen
|
||||||
displayName: "install wasm-bindgen for `wasm-pack` to use"
|
displayName: "install wasm-bindgen for `wasm-pack` to use"
|
||||||
|
@ -13,4 +13,4 @@ edition = '2018'
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
walrus = "0.7.0"
|
walrus = "0.8.0"
|
||||||
|
@ -20,6 +20,7 @@ use std::cmp;
|
|||||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use walrus::ir::*;
|
use walrus::ir::*;
|
||||||
|
use walrus::{ExportId, ImportId};
|
||||||
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
|
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
|
||||||
|
|
||||||
// must be kept in sync with src/lib.rs and ANYREF_HEAP_START
|
// must be kept in sync with src/lib.rs and ANYREF_HEAP_START
|
||||||
@ -32,8 +33,8 @@ pub struct Context {
|
|||||||
// Functions within the module that we're gonna be wrapping, organized by
|
// Functions within the module that we're gonna be wrapping, organized by
|
||||||
// type. The `Function` contains information about what arguments/return
|
// type. The `Function` contains information about what arguments/return
|
||||||
// values in the function signature should turn into anyref.
|
// values in the function signature should turn into anyref.
|
||||||
imports: HashMap<String, HashMap<String, Function>>,
|
imports: HashMap<ImportId, Function>,
|
||||||
exports: HashMap<String, Function>,
|
exports: HashMap<ExportId, Function>,
|
||||||
elements: BTreeMap<u32, (u32, Function)>,
|
elements: BTreeMap<u32, (u32, Function)>,
|
||||||
|
|
||||||
// When wrapping closures with new shims, this is the index of the next
|
// When wrapping closures with new shims, this is the index of the next
|
||||||
@ -42,9 +43,6 @@ pub struct Context {
|
|||||||
|
|
||||||
// The anyref table we'll be using, injected after construction
|
// The anyref table we'll be using, injected after construction
|
||||||
table: Option<TableId>,
|
table: Option<TableId>,
|
||||||
|
|
||||||
// Whether or not the transformation will actually be run at the end
|
|
||||||
pub enabled: bool,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct Transform<'a> {
|
struct Transform<'a> {
|
||||||
@ -68,7 +66,6 @@ struct Transform<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct Function {
|
struct Function {
|
||||||
name: String,
|
|
||||||
// A map of argument index to whether it's an owned or borrowed anyref
|
// A map of argument index to whether it's an owned or borrowed anyref
|
||||||
// (owned = true)
|
// (owned = true)
|
||||||
args: HashMap<usize, bool>,
|
args: HashMap<usize, bool>,
|
||||||
@ -87,10 +84,6 @@ impl Context {
|
|||||||
/// large the function table is so we know what indexes to hand out when
|
/// large the function table is so we know what indexes to hand out when
|
||||||
/// we're appending entries.
|
/// we're appending entries.
|
||||||
pub fn prepare(&mut self, module: &mut Module) -> Result<(), Error> {
|
pub fn prepare(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||||
if !self.enabled {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Figure out what the maximum index of functions pointers are. We'll
|
// Figure out what the maximum index of functions pointers are. We'll
|
||||||
// be adding new entries to the function table later (maybe) so
|
// be adding new entries to the function table later (maybe) so
|
||||||
// precalculate this ahead of time.
|
// precalculate this ahead of time.
|
||||||
@ -118,19 +111,13 @@ impl Context {
|
|||||||
/// transformed. The actual transformation happens later during `run`.
|
/// transformed. The actual transformation happens later during `run`.
|
||||||
pub fn import_xform(
|
pub fn import_xform(
|
||||||
&mut self,
|
&mut self,
|
||||||
module: &str,
|
id: ImportId,
|
||||||
name: &str,
|
|
||||||
anyref: &[(usize, bool)],
|
anyref: &[(usize, bool)],
|
||||||
ret_anyref: bool,
|
ret_anyref: bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
if !self.enabled {
|
if let Some(f) = self.function(anyref, ret_anyref) {
|
||||||
return self;
|
self.imports.insert(id, f);
|
||||||
}
|
}
|
||||||
let f = self.function(name, anyref, ret_anyref);
|
|
||||||
self.imports
|
|
||||||
.entry(module.to_string())
|
|
||||||
.or_insert_with(Default::default)
|
|
||||||
.insert(name.to_string(), f);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,15 +125,13 @@ impl Context {
|
|||||||
/// transformed. The actual transformation happens later during `run`.
|
/// transformed. The actual transformation happens later during `run`.
|
||||||
pub fn export_xform(
|
pub fn export_xform(
|
||||||
&mut self,
|
&mut self,
|
||||||
name: &str,
|
id: ExportId,
|
||||||
anyref: &[(usize, bool)],
|
anyref: &[(usize, bool)],
|
||||||
ret_anyref: bool,
|
ret_anyref: bool,
|
||||||
) -> &mut Self {
|
) -> &mut Self {
|
||||||
if !self.enabled {
|
if let Some(f) = self.function(anyref, ret_anyref) {
|
||||||
return self;
|
self.exports.insert(id, f);
|
||||||
}
|
}
|
||||||
let f = self.function(name, anyref, ret_anyref);
|
|
||||||
self.exports.insert(name.to_string(), f);
|
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -158,34 +143,26 @@ impl Context {
|
|||||||
idx: u32,
|
idx: u32,
|
||||||
anyref: &[(usize, bool)],
|
anyref: &[(usize, bool)],
|
||||||
ret_anyref: bool,
|
ret_anyref: bool,
|
||||||
) -> u32 {
|
) -> Option<u32> {
|
||||||
if !self.enabled {
|
self.function(anyref, ret_anyref).map(|f| {
|
||||||
return idx;
|
let ret = self.next_element;
|
||||||
}
|
self.next_element += 1;
|
||||||
let name = format!("closure{}", idx);
|
self.elements.insert(ret, (idx, f));
|
||||||
let f = self.function(&name, anyref, ret_anyref);
|
ret
|
||||||
let ret = self.next_element;
|
})
|
||||||
self.next_element += 1;
|
|
||||||
self.elements.insert(ret, (idx, f));
|
|
||||||
ret
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn function(&self, name: &str, anyref: &[(usize, bool)], ret_anyref: bool) -> Function {
|
fn function(&self, anyref: &[(usize, bool)], ret_anyref: bool) -> Option<Function> {
|
||||||
Function {
|
if !ret_anyref && anyref.len() == 0 {
|
||||||
name: name.to_string(),
|
return None;
|
||||||
|
}
|
||||||
|
Some(Function {
|
||||||
args: anyref.iter().cloned().collect(),
|
args: anyref.iter().cloned().collect(),
|
||||||
ret_anyref,
|
ret_anyref,
|
||||||
}
|
})
|
||||||
}
|
|
||||||
|
|
||||||
pub fn anyref_table_id(&self) -> TableId {
|
|
||||||
self.table.unwrap()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
|
pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||||
if !self.enabled {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
let table = self.table.unwrap();
|
let table = self.table.unwrap();
|
||||||
|
|
||||||
// Inject a stack pointer global which will be used for managing the
|
// Inject a stack pointer global which will be used for managing the
|
||||||
@ -261,9 +238,7 @@ impl Transform<'_> {
|
|||||||
|
|
||||||
// Perform transformations of imports, exports, and function pointers.
|
// Perform transformations of imports, exports, and function pointers.
|
||||||
self.process_imports(module);
|
self.process_imports(module);
|
||||||
for m in self.cx.imports.values() {
|
assert!(self.cx.imports.is_empty());
|
||||||
assert!(m.is_empty());
|
|
||||||
}
|
|
||||||
self.process_exports(module);
|
self.process_exports(module);
|
||||||
assert!(self.cx.exports.is_empty());
|
assert!(self.cx.exports.is_empty());
|
||||||
self.process_elements(module)?;
|
self.process_elements(module)?;
|
||||||
@ -333,20 +308,15 @@ impl Transform<'_> {
|
|||||||
walrus::ImportKind::Function(f) => f,
|
walrus::ImportKind::Function(f) => f,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
let import = {
|
let func = match self.cx.imports.remove(&import.id()) {
|
||||||
let entries = match self.cx.imports.get_mut(&import.module) {
|
Some(s) => s,
|
||||||
Some(s) => s,
|
None => continue,
|
||||||
None => continue,
|
|
||||||
};
|
|
||||||
match entries.remove(&import.name) {
|
|
||||||
Some(s) => s,
|
|
||||||
None => continue,
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let shim = self.append_shim(
|
let shim = self.append_shim(
|
||||||
f,
|
f,
|
||||||
import,
|
&import.name,
|
||||||
|
func,
|
||||||
&mut module.types,
|
&mut module.types,
|
||||||
&mut module.funcs,
|
&mut module.funcs,
|
||||||
&mut module.locals,
|
&mut module.locals,
|
||||||
@ -356,29 +326,25 @@ impl Transform<'_> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn process_exports(&mut self, module: &mut Module) {
|
fn process_exports(&mut self, module: &mut Module) {
|
||||||
let mut new_exports = Vec::new();
|
// let mut new_exports = Vec::new();
|
||||||
for export in module.exports.iter() {
|
for export in module.exports.iter_mut() {
|
||||||
let f = match export.item {
|
let f = match export.item {
|
||||||
walrus::ExportItem::Function(f) => f,
|
walrus::ExportItem::Function(f) => f,
|
||||||
_ => continue,
|
_ => continue,
|
||||||
};
|
};
|
||||||
let function = match self.cx.exports.remove(&export.name) {
|
let function = match self.cx.exports.remove(&export.id()) {
|
||||||
Some(s) => s,
|
Some(s) => s,
|
||||||
None => continue,
|
None => continue,
|
||||||
};
|
};
|
||||||
let shim = self.append_shim(
|
let shim = self.append_shim(
|
||||||
f,
|
f,
|
||||||
|
&export.name,
|
||||||
function,
|
function,
|
||||||
&mut module.types,
|
&mut module.types,
|
||||||
&mut module.funcs,
|
&mut module.funcs,
|
||||||
&mut module.locals,
|
&mut module.locals,
|
||||||
);
|
);
|
||||||
new_exports.push((export.name.to_string(), shim, export.id()));
|
export.item = shim.into();
|
||||||
}
|
|
||||||
|
|
||||||
for (name, shim, old_id) in new_exports {
|
|
||||||
module.exports.add(&name, shim);
|
|
||||||
module.exports.delete(old_id);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,6 +368,7 @@ impl Transform<'_> {
|
|||||||
let target = kind.elements[idx as usize].unwrap();
|
let target = kind.elements[idx as usize].unwrap();
|
||||||
let shim = self.append_shim(
|
let shim = self.append_shim(
|
||||||
target,
|
target,
|
||||||
|
&format!("closure{}", idx),
|
||||||
function,
|
function,
|
||||||
&mut module.types,
|
&mut module.types,
|
||||||
&mut module.funcs,
|
&mut module.funcs,
|
||||||
@ -422,6 +389,7 @@ impl Transform<'_> {
|
|||||||
fn append_shim(
|
fn append_shim(
|
||||||
&mut self,
|
&mut self,
|
||||||
shim_target: FunctionId,
|
shim_target: FunctionId,
|
||||||
|
name: &str,
|
||||||
mut func: Function,
|
mut func: Function,
|
||||||
types: &mut walrus::ModuleTypes,
|
types: &mut walrus::ModuleTypes,
|
||||||
funcs: &mut walrus::ModuleFunctions,
|
funcs: &mut walrus::ModuleFunctions,
|
||||||
@ -625,7 +593,7 @@ impl Transform<'_> {
|
|||||||
// nice name for debugging and then we're good to go!
|
// nice name for debugging and then we're good to go!
|
||||||
let expr = builder.with_side_effects(before, result, after);
|
let expr = builder.with_side_effects(before, result, after);
|
||||||
let id = builder.finish_parts(shim_ty, params, vec![expr], types, funcs);
|
let id = builder.finish_parts(shim_ty, params, vec![expr], types, funcs);
|
||||||
let name = format!("{}_anyref_shim", func.name);
|
let name = format!("{}_anyref_shim", name);
|
||||||
funcs.get_mut(id).name = Some(name);
|
funcs.get_mut(id).name = Some(name);
|
||||||
self.shims.insert(id);
|
self.shims.insert(id);
|
||||||
return id;
|
return id;
|
||||||
|
@ -18,7 +18,7 @@ log = "0.4"
|
|||||||
rustc-demangle = "0.1.13"
|
rustc-demangle = "0.1.13"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
tempfile = "3.0"
|
tempfile = "3.0"
|
||||||
walrus = "0.7.0"
|
walrus = "0.8.0"
|
||||||
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.45' }
|
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.45' }
|
||||||
wasm-bindgen-shared = { path = "../shared", version = '=0.2.45' }
|
wasm-bindgen-shared = { path = "../shared", version = '=0.2.45' }
|
||||||
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.45' }
|
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.45' }
|
||||||
|
139
crates/cli-support/src/anyref.rs
Normal file
139
crates/cli-support/src/anyref.rs
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
use crate::descriptor::{Closure, Descriptor, Function};
|
||||||
|
use crate::webidl::{AuxImport, ImportBinding, WasmBindgenAux, WebidlCustomSection};
|
||||||
|
use failure::Error;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use walrus::Module;
|
||||||
|
use wasm_bindgen_anyref_xform::Context;
|
||||||
|
|
||||||
|
pub fn process(module: &mut Module) -> Result<(), Error> {
|
||||||
|
let mut cfg = Context::default();
|
||||||
|
cfg.prepare(module)?;
|
||||||
|
let bindings = module
|
||||||
|
.customs
|
||||||
|
.get_typed_mut::<WebidlCustomSection>()
|
||||||
|
.expect("webidl custom section should exist");
|
||||||
|
|
||||||
|
for (export, binding) in bindings.exports.iter_mut() {
|
||||||
|
let (args, ret) = extract_anyrefs(binding, 0);
|
||||||
|
cfg.export_xform(*export, &args, ret);
|
||||||
|
process_closure_arguments(&mut cfg, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (import, kind) in bindings.imports.iter_mut() {
|
||||||
|
let binding = match kind {
|
||||||
|
ImportBinding::Function(f) => f,
|
||||||
|
ImportBinding::Constructor(f) => f,
|
||||||
|
ImportBinding::Method(f) => f,
|
||||||
|
};
|
||||||
|
let (args, ret) = extract_anyrefs(binding, 0);
|
||||||
|
cfg.import_xform(*import, &args, ret);
|
||||||
|
process_closure_arguments(&mut cfg, binding);
|
||||||
|
}
|
||||||
|
|
||||||
|
let aux = module
|
||||||
|
.customs
|
||||||
|
.get_typed_mut::<WasmBindgenAux>()
|
||||||
|
.expect("webidl custom section should exist");
|
||||||
|
for import in aux.import_map.values_mut() {
|
||||||
|
match import {
|
||||||
|
AuxImport::Closure(f) => process_closure(&mut cfg, f),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg.run(module)?;
|
||||||
|
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::<WebidlCustomSection>()
|
||||||
|
.expect("webidl custom section should exist")
|
||||||
|
.imports
|
||||||
|
.retain(|id, _| remaining_imports.contains(id));
|
||||||
|
module
|
||||||
|
.customs
|
||||||
|
.get_typed_mut::<WasmBindgenAux>()
|
||||||
|
.expect("wasm-bindgen aux section should exist")
|
||||||
|
.import_map
|
||||||
|
.retain(|id, _| remaining_imports.contains(id));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Process the `function` provided to ensure that all references to `Closure`
|
||||||
|
/// descriptors are processed below.
|
||||||
|
fn process_closure_arguments(cfg: &mut Context, function: &mut Function) {
|
||||||
|
for arg in function.arguments.iter_mut() {
|
||||||
|
process_descriptor(cfg, arg);
|
||||||
|
}
|
||||||
|
process_descriptor(cfg, &mut function.ret);
|
||||||
|
|
||||||
|
fn process_descriptor(cfg: &mut Context, descriptor: &mut Descriptor) {
|
||||||
|
match descriptor {
|
||||||
|
Descriptor::Ref(d)
|
||||||
|
| Descriptor::RefMut(d)
|
||||||
|
| Descriptor::Option(d)
|
||||||
|
| Descriptor::Slice(d)
|
||||||
|
| Descriptor::Clamped(d)
|
||||||
|
| Descriptor::Vector(d) => process_descriptor(cfg, d),
|
||||||
|
Descriptor::Closure(c) => process_closure(cfg, c),
|
||||||
|
Descriptor::Function(c) => process_function(cfg, c),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_function(cfg: &mut Context, function: &mut Function) {
|
||||||
|
let (args, ret) = extract_anyrefs(&function, 2);
|
||||||
|
if let Some(new) = cfg.table_element_xform(function.shim_idx, &args, ret) {
|
||||||
|
function.shim_idx = new;
|
||||||
|
}
|
||||||
|
process_closure_arguments(cfg, function);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that the `Closure` is processed in case any of its arguments
|
||||||
|
/// recursively contain `anyref` and such.
|
||||||
|
fn process_closure(cfg: &mut Context, closure: &mut Closure) {
|
||||||
|
let (args, ret) = extract_anyrefs(&closure.function, 2);
|
||||||
|
if let Some(new) = cfg.table_element_xform(closure.shim_idx, &args, ret) {
|
||||||
|
closure.shim_idx = new;
|
||||||
|
}
|
||||||
|
process_closure_arguments(cfg, &mut closure.function);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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(f: &Function, offset: usize) -> (Vec<(usize, bool)>, bool) {
|
||||||
|
let mut args = Vec::new();
|
||||||
|
let mut cur = offset;
|
||||||
|
if f.ret.abi_returned_through_pointer() {
|
||||||
|
cur += 1;
|
||||||
|
}
|
||||||
|
for arg in f.arguments.iter() {
|
||||||
|
if arg.is_anyref() {
|
||||||
|
args.push((cur, true));
|
||||||
|
} else if arg.is_ref_anyref() {
|
||||||
|
args.push((cur, false));
|
||||||
|
}
|
||||||
|
cur += arg.abi_arg_count();
|
||||||
|
}
|
||||||
|
(args, f.ret.is_anyref())
|
||||||
|
}
|
@ -38,7 +38,7 @@ tys! {
|
|||||||
CLAMPED
|
CLAMPED
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Descriptor {
|
pub enum Descriptor {
|
||||||
I8,
|
I8,
|
||||||
U8,
|
U8,
|
||||||
@ -67,14 +67,14 @@ pub enum Descriptor {
|
|||||||
Clamped(Box<Descriptor>),
|
Clamped(Box<Descriptor>),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Function {
|
pub struct Function {
|
||||||
pub arguments: Vec<Descriptor>,
|
pub arguments: Vec<Descriptor>,
|
||||||
pub shim_idx: u32,
|
pub shim_idx: u32,
|
||||||
pub ret: Descriptor,
|
pub ret: Descriptor,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Closure {
|
pub struct Closure {
|
||||||
pub shim_idx: u32,
|
pub shim_idx: u32,
|
||||||
pub dtor_idx: u32,
|
pub dtor_idx: u32,
|
||||||
@ -146,9 +146,9 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn unwrap_function(&self) -> &Function {
|
pub fn unwrap_function(self) -> Function {
|
||||||
match *self {
|
match self {
|
||||||
Descriptor::Function(ref f) => f,
|
Descriptor::Function(f) => *f,
|
||||||
_ => panic!("not a function"),
|
_ => panic!("not a function"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -199,10 +199,10 @@ impl Descriptor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn closure(&self) -> Option<&Closure> {
|
pub fn unwrap_closure(self) -> Closure {
|
||||||
match *self {
|
match self {
|
||||||
Descriptor::Closure(ref s) => Some(s),
|
Descriptor::Closure(s) => *s,
|
||||||
_ => None,
|
_ => panic!("not a closure"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,6 +292,83 @@ impl Descriptor {
|
|||||||
_ => false,
|
_ => false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn abi_returned_through_pointer(&self) -> bool {
|
||||||
|
if self.vector_kind().is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if self.get_64().is_some() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
match self {
|
||||||
|
Descriptor::Option(inner) => match &**inner {
|
||||||
|
Descriptor::Anyref
|
||||||
|
| Descriptor::RustStruct(_)
|
||||||
|
| Descriptor::Enum { .. }
|
||||||
|
| Descriptor::Char
|
||||||
|
| Descriptor::Boolean
|
||||||
|
| Descriptor::I8
|
||||||
|
| Descriptor::U8
|
||||||
|
| Descriptor::I16
|
||||||
|
| Descriptor::U16 => false,
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn abi_arg_count(&self) -> usize {
|
||||||
|
if let Descriptor::Option(inner) = self {
|
||||||
|
if inner.get_64().is_some() {
|
||||||
|
return 4;
|
||||||
|
}
|
||||||
|
if let Descriptor::Ref(inner) = &**inner {
|
||||||
|
match &**inner {
|
||||||
|
Descriptor::Anyref => return 1,
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if self.stack_closure().is_some() {
|
||||||
|
return 2;
|
||||||
|
}
|
||||||
|
if self.abi_returned_through_pointer() {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_abi_return_correct(&self, before: usize, after: usize) {
|
||||||
|
if before != after {
|
||||||
|
assert_eq!(
|
||||||
|
before + 1,
|
||||||
|
after,
|
||||||
|
"abi_returned_through_pointer wrong for {:?}",
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
self.abi_returned_through_pointer(),
|
||||||
|
"abi_returned_through_pointer wrong for {:?}",
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
assert!(
|
||||||
|
!self.abi_returned_through_pointer(),
|
||||||
|
"abi_returned_through_pointer wrong for {:?}",
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assert_abi_arg_correct(&self, before: usize, after: usize) {
|
||||||
|
assert_eq!(
|
||||||
|
before + self.abi_arg_count(),
|
||||||
|
after,
|
||||||
|
"abi_arg_count wrong for {:?}",
|
||||||
|
self,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get(a: &mut &[u32]) -> u32 {
|
fn get(a: &mut &[u32]) -> u32 {
|
||||||
|
196
crates/cli-support/src/descriptors.rs
Normal file
196
crates/cli-support/src/descriptors.rs
Normal file
@ -0,0 +1,196 @@
|
|||||||
|
//! Management of wasm-bindgen descriptor functions.
|
||||||
|
//!
|
||||||
|
//! The purpose of this module is to basically execute a pass on a raw wasm
|
||||||
|
//! module that just came out of the compiler. The pass will execute all
|
||||||
|
//! relevant descriptor functions contained in the module which wasm-bindgen
|
||||||
|
//! uses to convey type infomation here, to the CLI.
|
||||||
|
//!
|
||||||
|
//! All descriptor functions are removed after this pass runs and in their stead
|
||||||
|
//! a new custom section, defined in this module, is inserted into the
|
||||||
|
//! `walrus::Module` which contains all the results of all the descriptor
|
||||||
|
//! functions.
|
||||||
|
|
||||||
|
use crate::descriptor::{Closure, Descriptor};
|
||||||
|
use failure::Error;
|
||||||
|
use std::borrow::Cow;
|
||||||
|
use std::collections::{HashMap, HashSet};
|
||||||
|
use std::mem;
|
||||||
|
use walrus::ImportId;
|
||||||
|
use walrus::{CustomSection, FunctionId, LocalFunction, Module, TypedCustomSectionId};
|
||||||
|
use wasm_bindgen_wasm_interpreter::Interpreter;
|
||||||
|
|
||||||
|
#[derive(Default, Debug)]
|
||||||
|
pub struct WasmBindgenDescriptorsSection {
|
||||||
|
pub descriptors: HashMap<String, Descriptor>,
|
||||||
|
pub closure_imports: HashMap<ImportId, Closure>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId<WasmBindgenDescriptorsSection>;
|
||||||
|
|
||||||
|
/// Execute all `__wbindgen_describe_*` functions in a module, inserting a
|
||||||
|
/// custom section which represents the executed value of each descriptor.
|
||||||
|
///
|
||||||
|
/// Afterwards this will delete all descriptor functions from the module.
|
||||||
|
pub fn execute(module: &mut Module) -> Result<WasmBindgenDescriptorsSectionId, Error> {
|
||||||
|
let mut section = WasmBindgenDescriptorsSection::default();
|
||||||
|
let mut interpreter = Interpreter::new(module)?;
|
||||||
|
|
||||||
|
section.execute_exports(module, &mut interpreter)?;
|
||||||
|
section.execute_closures(module, &mut interpreter)?;
|
||||||
|
|
||||||
|
// Delete all descriptor functions and imports from the module now that
|
||||||
|
// we've executed all of them.
|
||||||
|
walrus::passes::gc::run(module);
|
||||||
|
|
||||||
|
Ok(module.customs.add(section))
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WasmBindgenDescriptorsSection {
|
||||||
|
fn execute_exports(
|
||||||
|
&mut self,
|
||||||
|
module: &mut Module,
|
||||||
|
interpreter: &mut Interpreter,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for export in module.exports.iter() {
|
||||||
|
let prefix = "__wbindgen_describe_";
|
||||||
|
if !export.name.starts_with(prefix) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let id = match export.item {
|
||||||
|
walrus::ExportItem::Function(id) => id,
|
||||||
|
_ => panic!("{} export not a function", export.name),
|
||||||
|
};
|
||||||
|
if let Some(d) = interpreter.interpret_descriptor(id, module) {
|
||||||
|
let name = &export.name[prefix.len()..];
|
||||||
|
let descriptor = Descriptor::decode(d);
|
||||||
|
self.descriptors.insert(name.to_string(), descriptor);
|
||||||
|
}
|
||||||
|
to_remove.push(export.id());
|
||||||
|
}
|
||||||
|
|
||||||
|
for id in to_remove {
|
||||||
|
module.exports.delete(id);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_closures(
|
||||||
|
&mut self,
|
||||||
|
module: &mut Module,
|
||||||
|
interpreter: &mut Interpreter,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
use walrus::ir::*;
|
||||||
|
|
||||||
|
// If our describe closure intrinsic isn't present or wasn't linked
|
||||||
|
// then there's no closures, so nothing to do!
|
||||||
|
let wbindgen_describe_closure = match interpreter.describe_closure_id() {
|
||||||
|
Some(i) => i,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Find all functions which call `wbindgen_describe_closure`. These are
|
||||||
|
// specially codegen'd so we know the rough structure of them. For each
|
||||||
|
// one we delegate to the interpreter to figure out the actual result.
|
||||||
|
let mut element_removal_list = HashSet::new();
|
||||||
|
let mut func_to_descriptor = HashMap::new();
|
||||||
|
for (id, local) in module.funcs.iter_local() {
|
||||||
|
let entry = local.entry_block();
|
||||||
|
let mut find = FindDescribeClosure {
|
||||||
|
func: local,
|
||||||
|
wbindgen_describe_closure,
|
||||||
|
cur: entry.into(),
|
||||||
|
call: None,
|
||||||
|
};
|
||||||
|
find.visit_block_id(&entry);
|
||||||
|
if let Some(call) = find.call {
|
||||||
|
let descriptor = interpreter
|
||||||
|
.interpret_closure_descriptor(id, module, &mut element_removal_list)
|
||||||
|
.unwrap();
|
||||||
|
func_to_descriptor.insert(id, (call, Descriptor::decode(descriptor)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// For all indirect functions that were closure descriptors, delete them
|
||||||
|
// from the function table since we've executed them and they're not
|
||||||
|
// necessary in the final binary.
|
||||||
|
let table_id = match interpreter.function_table_id() {
|
||||||
|
Some(id) => id,
|
||||||
|
None => return Ok(()),
|
||||||
|
};
|
||||||
|
let table = module.tables.get_mut(table_id);
|
||||||
|
let table = match &mut table.kind {
|
||||||
|
walrus::TableKind::Function(f) => f,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
for idx in element_removal_list {
|
||||||
|
log::trace!("delete element {}", idx);
|
||||||
|
assert!(table.elements[idx].is_some());
|
||||||
|
table.elements[idx] = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// And finally replace all calls of `wbindgen_describe_closure` with a
|
||||||
|
// freshly manufactured import. Save off the type of this import in
|
||||||
|
// ourselves, and then we're good to go.
|
||||||
|
let ty = module.funcs.get(wbindgen_describe_closure).ty();
|
||||||
|
for (func, (call_instr, descriptor)) in func_to_descriptor {
|
||||||
|
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
|
||||||
|
let id = module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
|
||||||
|
let import_id = module
|
||||||
|
.imports
|
||||||
|
.iter()
|
||||||
|
.find(|i| i.name == import_name)
|
||||||
|
.unwrap()
|
||||||
|
.id();
|
||||||
|
module.funcs.get_mut(id).name = Some(import_name);
|
||||||
|
|
||||||
|
let local = match &mut module.funcs.get_mut(func).kind {
|
||||||
|
walrus::FunctionKind::Local(l) => l,
|
||||||
|
_ => unreachable!(),
|
||||||
|
};
|
||||||
|
let call = local.get_mut(call_instr).unwrap_call_mut();
|
||||||
|
assert_eq!(call.func, wbindgen_describe_closure);
|
||||||
|
call.func = id;
|
||||||
|
self.closure_imports
|
||||||
|
.insert(import_id, descriptor.unwrap_closure());
|
||||||
|
}
|
||||||
|
return Ok(());
|
||||||
|
|
||||||
|
struct FindDescribeClosure<'a> {
|
||||||
|
func: &'a LocalFunction,
|
||||||
|
wbindgen_describe_closure: FunctionId,
|
||||||
|
cur: ExprId,
|
||||||
|
call: Option<ExprId>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> Visitor<'a> for FindDescribeClosure<'a> {
|
||||||
|
fn local_function(&self) -> &'a LocalFunction {
|
||||||
|
self.func
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_expr_id(&mut self, id: &ExprId) {
|
||||||
|
let prev = mem::replace(&mut self.cur, *id);
|
||||||
|
id.visit(self);
|
||||||
|
self.cur = prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn visit_call(&mut self, call: &Call) {
|
||||||
|
call.visit(self);
|
||||||
|
if call.func == self.wbindgen_describe_closure {
|
||||||
|
assert!(self.call.is_none());
|
||||||
|
self.call = Some(self.cur);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CustomSection for WasmBindgenDescriptorsSection {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"wasm-bindgen descriptors"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
|
||||||
|
panic!("shouldn't emit custom sections just yet");
|
||||||
|
}
|
||||||
|
}
|
152
crates/cli-support/src/intrinsic.rs
Normal file
152
crates/cli-support/src/intrinsic.rs
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
//! Definition of all wasm-bindgen intrinsics.
|
||||||
|
//!
|
||||||
|
//! This contains a definition of all intrinsics used by `src/lib.rs` in the
|
||||||
|
//! wasm-bindgen crate. Each intrinsic listed here is part of an `enum
|
||||||
|
//! Intrinsic` and is generated through a macro to reduce repetition.
|
||||||
|
//!
|
||||||
|
//! Intrinsics in this module currently largely contain their expected symbol
|
||||||
|
//! name as well as the signature of the function that it expects.
|
||||||
|
|
||||||
|
use crate::descriptor::{self, Descriptor, Function};
|
||||||
|
|
||||||
|
macro_rules! intrinsics {
|
||||||
|
(pub enum Intrinsic {
|
||||||
|
$(
|
||||||
|
#[symbol = $sym:tt]
|
||||||
|
#[signature = fn($($arg:expr),*) -> $ret:ident]
|
||||||
|
$name:ident,
|
||||||
|
)*
|
||||||
|
}) => {
|
||||||
|
/// All wasm-bindgen intrinsics that could be depended on by a wasm
|
||||||
|
/// module.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub enum Intrinsic {
|
||||||
|
$($name,)*
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Intrinsic {
|
||||||
|
/// Returns the corresponding intrinsic for a symbol name, if one
|
||||||
|
/// matches.
|
||||||
|
pub fn from_symbol(symbol: &str) -> Option<Intrinsic> {
|
||||||
|
match symbol {
|
||||||
|
$($sym => Some(Intrinsic::$name),)*
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the expected signature of this intrinsic, used for
|
||||||
|
/// generating a JS shim.
|
||||||
|
pub fn binding(&self) -> Function {
|
||||||
|
use crate::descriptor::Descriptor::*;
|
||||||
|
match self {
|
||||||
|
$(
|
||||||
|
Intrinsic::$name => {
|
||||||
|
descriptor::Function {
|
||||||
|
shim_idx: 0,
|
||||||
|
arguments: vec![$($arg),*],
|
||||||
|
ret: $ret,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ref_anyref() -> Descriptor {
|
||||||
|
Descriptor::Ref(Box::new(Descriptor::Anyref))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn ref_string() -> Descriptor {
|
||||||
|
Descriptor::Ref(Box::new(Descriptor::String))
|
||||||
|
}
|
||||||
|
|
||||||
|
intrinsics! {
|
||||||
|
pub enum Intrinsic {
|
||||||
|
#[symbol = "__wbindgen_jsval_eq"]
|
||||||
|
#[signature = fn(ref_anyref(), ref_anyref()) -> Boolean]
|
||||||
|
JsvalEq,
|
||||||
|
#[symbol = "__wbindgen_is_function"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsFunction,
|
||||||
|
#[symbol = "__wbindgen_is_undefined"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsUndefined,
|
||||||
|
#[symbol = "__wbindgen_is_null"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsNull,
|
||||||
|
#[symbol = "__wbindgen_is_object"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsObject,
|
||||||
|
#[symbol = "__wbindgen_is_symbol"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsSymbol,
|
||||||
|
#[symbol = "__wbindgen_is_string"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Boolean]
|
||||||
|
IsString,
|
||||||
|
#[symbol = "__wbindgen_object_clone_ref"]
|
||||||
|
#[signature = fn(ref_anyref()) -> Anyref]
|
||||||
|
ObjectCloneRef,
|
||||||
|
#[symbol = "__wbindgen_object_drop_ref"]
|
||||||
|
#[signature = fn(Anyref) -> Unit]
|
||||||
|
ObjectDropRef,
|
||||||
|
#[symbol = "__wbindgen_cb_drop"]
|
||||||
|
#[signature = fn(Anyref) -> Boolean]
|
||||||
|
CallbackDrop,
|
||||||
|
#[symbol = "__wbindgen_cb_forget"]
|
||||||
|
#[signature = fn(Anyref) -> Unit]
|
||||||
|
CallbackForget,
|
||||||
|
#[symbol = "__wbindgen_number_new"]
|
||||||
|
#[signature = fn(F64) -> Anyref]
|
||||||
|
NumberNew,
|
||||||
|
#[symbol = "__wbindgen_string_new"]
|
||||||
|
#[signature = fn(ref_string()) -> Anyref]
|
||||||
|
StringNew,
|
||||||
|
#[symbol = "__wbindgen_symbol_anonymous_new"]
|
||||||
|
#[signature = fn() -> Anyref]
|
||||||
|
SymbolAnonymousNew,
|
||||||
|
#[symbol = "__wbindgen_symbol_named_new"]
|
||||||
|
#[signature = fn(ref_string()) -> Anyref]
|
||||||
|
SymbolNamedNew,
|
||||||
|
#[symbol = "__wbindgen_number_get"]
|
||||||
|
#[signature = fn(ref_anyref(), F64) -> F64]
|
||||||
|
NumberGet,
|
||||||
|
#[symbol = "__wbindgen_string_get"]
|
||||||
|
#[signature = fn(ref_anyref(), I32) -> I32]
|
||||||
|
StringGet,
|
||||||
|
#[symbol = "__wbindgen_boolean_get"]
|
||||||
|
#[signature = fn(ref_anyref()) -> F64]
|
||||||
|
BooleanGet,
|
||||||
|
#[symbol = "__wbindgen_throw"]
|
||||||
|
#[signature = fn(ref_string()) -> Unit]
|
||||||
|
Throw,
|
||||||
|
#[symbol = "__wbindgen_rethrow"]
|
||||||
|
#[signature = fn(Anyref) -> Unit]
|
||||||
|
Rethrow,
|
||||||
|
#[symbol = "__wbindgen_memory"]
|
||||||
|
#[signature = fn() -> Anyref]
|
||||||
|
Memory,
|
||||||
|
#[symbol = "__wbindgen_module"]
|
||||||
|
#[signature = fn() -> Anyref]
|
||||||
|
Module,
|
||||||
|
#[symbol = "__wbindgen_function_table"]
|
||||||
|
#[signature = fn() -> Anyref]
|
||||||
|
FunctionTable,
|
||||||
|
#[symbol = "__wbindgen_debug_string"]
|
||||||
|
#[signature = fn(ref_anyref()) -> String]
|
||||||
|
DebugString,
|
||||||
|
#[symbol = "__wbindgen_json_parse"]
|
||||||
|
#[signature = fn(ref_string()) -> Anyref]
|
||||||
|
JsonParse,
|
||||||
|
#[symbol = "__wbindgen_json_serialize"]
|
||||||
|
#[signature = fn(ref_anyref()) -> String]
|
||||||
|
JsonSerialize,
|
||||||
|
#[symbol = "__wbindgen_anyref_heap_live_count"]
|
||||||
|
#[signature = fn() -> F64]
|
||||||
|
AnyrefHeapLiveCount,
|
||||||
|
#[symbol = "__wbindgen_init_nyref_table"]
|
||||||
|
#[signature = fn() -> Unit]
|
||||||
|
InitAnyrefTable,
|
||||||
|
}
|
||||||
|
}
|
@ -1,258 +0,0 @@
|
|||||||
//! 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 crate::descriptor::Descriptor;
|
|
||||||
use crate::js::js2rust::{ExportedShim, Js2Rust};
|
|
||||||
use crate::js::Context;
|
|
||||||
use failure::Error;
|
|
||||||
use std::collections::{BTreeMap, HashSet};
|
|
||||||
use std::mem;
|
|
||||||
use walrus::ir::{Expr, ExprId};
|
|
||||||
use walrus::{FunctionId, LocalFunction};
|
|
||||||
|
|
||||||
pub fn rewrite(input: &mut Context) -> Result<(), Error> {
|
|
||||||
let info = ClosureDescriptors::new(input);
|
|
||||||
|
|
||||||
if info.element_removal_list.len() == 0 {
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
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: HashSet<usize>,
|
|
||||||
|
|
||||||
/// A map from local functions which contain calls to
|
|
||||||
/// `__wbindgen_describe_closure` to the information about the closure
|
|
||||||
/// descriptor it contains.
|
|
||||||
///
|
|
||||||
/// This map is later used to replace all calls to the keys of this map with
|
|
||||||
/// calls to the value of the map.
|
|
||||||
func_to_descriptor: BTreeMap<FunctionId, DescribeInstruction>,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct DescribeInstruction {
|
|
||||||
call: ExprId,
|
|
||||||
descriptor: 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 {
|
|
||||||
use walrus::ir::*;
|
|
||||||
|
|
||||||
let wbindgen_describe_closure = match input.interpreter.describe_closure_id() {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return Default::default(),
|
|
||||||
};
|
|
||||||
let mut ret = ClosureDescriptors::default();
|
|
||||||
|
|
||||||
for (id, local) in input.module.funcs.iter_local() {
|
|
||||||
let entry = local.entry_block();
|
|
||||||
let mut find = FindDescribeClosure {
|
|
||||||
func: local,
|
|
||||||
wbindgen_describe_closure,
|
|
||||||
cur: entry.into(),
|
|
||||||
call: None,
|
|
||||||
};
|
|
||||||
find.visit_block_id(&entry);
|
|
||||||
if let Some(call) = find.call {
|
|
||||||
let descriptor = input
|
|
||||||
.interpreter
|
|
||||||
.interpret_closure_descriptor(id, input.module, &mut ret.element_removal_list)
|
|
||||||
.unwrap();
|
|
||||||
ret.func_to_descriptor.insert(
|
|
||||||
id,
|
|
||||||
DescribeInstruction {
|
|
||||||
call,
|
|
||||||
descriptor: Descriptor::decode(descriptor),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
struct FindDescribeClosure<'a> {
|
|
||||||
func: &'a LocalFunction,
|
|
||||||
wbindgen_describe_closure: FunctionId,
|
|
||||||
cur: ExprId,
|
|
||||||
call: Option<ExprId>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> Visitor<'a> for FindDescribeClosure<'a> {
|
|
||||||
fn local_function(&self) -> &'a LocalFunction {
|
|
||||||
self.func
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_expr_id(&mut self, id: &ExprId) {
|
|
||||||
let prev = mem::replace(&mut self.cur, *id);
|
|
||||||
id.visit(self);
|
|
||||||
self.cur = prev;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visit_call(&mut self, call: &Call) {
|
|
||||||
call.visit(self);
|
|
||||||
if call.func == self.wbindgen_describe_closure {
|
|
||||||
assert!(self.call.is_none());
|
|
||||||
self.call = Some(self.cur);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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 table_id = match input.interpreter.function_table_id() {
|
|
||||||
Some(id) => id,
|
|
||||||
None => return,
|
|
||||||
};
|
|
||||||
let table = input.module.tables.get_mut(table_id);
|
|
||||||
let table = match &mut table.kind {
|
|
||||||
walrus::TableKind::Function(f) => f,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
for idx in self.element_removal_list.iter().cloned() {
|
|
||||||
log::trace!("delete element {}", idx);
|
|
||||||
assert!(table.elements[idx].is_some());
|
|
||||||
table.elements[idx] = None;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// 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> {
|
|
||||||
let wbindgen_describe_closure = match input.interpreter.describe_closure_id() {
|
|
||||||
Some(i) => i,
|
|
||||||
None => return Ok(()),
|
|
||||||
};
|
|
||||||
|
|
||||||
// We'll be injecting new imports and we'll need to give them all a
|
|
||||||
// type. The signature is all `(i32, i32, i32) -> i32` currently
|
|
||||||
let ty = input.module.funcs.get(wbindgen_describe_closure).ty();
|
|
||||||
|
|
||||||
// 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 then rewrite the appropriate call
|
|
||||||
// instruction.
|
|
||||||
for (func, instr) in self.func_to_descriptor.iter() {
|
|
||||||
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
|
|
||||||
|
|
||||||
let closure = instr.descriptor.closure().unwrap();
|
|
||||||
|
|
||||||
let mut shim = closure.shim_idx;
|
|
||||||
let (js, _ts, _js_doc) = {
|
|
||||||
let mut builder = Js2Rust::new("", input);
|
|
||||||
|
|
||||||
// First up with a closure we increment the internal reference
|
|
||||||
// count. This ensures that the Rust closure environment won't
|
|
||||||
// be deallocated while we're invoking it.
|
|
||||||
builder.prelude("this.cnt++;");
|
|
||||||
|
|
||||||
if closure.mutable {
|
|
||||||
// For mutable closures they can't be invoked recursively.
|
|
||||||
// To handle that we swap out the `this.a` pointer with zero
|
|
||||||
// while we invoke it. If we finish and the closure wasn't
|
|
||||||
// destroyed, then we put back the pointer so a future
|
|
||||||
// invocation can succeed.
|
|
||||||
builder
|
|
||||||
.prelude("let a = this.a;")
|
|
||||||
.prelude("this.a = 0;")
|
|
||||||
.rust_argument("a")
|
|
||||||
.rust_argument("b")
|
|
||||||
.finally("if (--this.cnt === 0) d(a, b);")
|
|
||||||
.finally("else this.a = a;");
|
|
||||||
} else {
|
|
||||||
// For shared closures they can be invoked recursively so we
|
|
||||||
// just immediately pass through `this.a`. If we end up
|
|
||||||
// executing the destructor, however, we clear out the
|
|
||||||
// `this.a` pointer to prevent it being used again the
|
|
||||||
// future.
|
|
||||||
builder
|
|
||||||
.rust_argument("this.a")
|
|
||||||
.rust_argument("b")
|
|
||||||
.finally("if (--this.cnt === 0) { d(this.a, b); this.a = 0; }");
|
|
||||||
}
|
|
||||||
builder.process(&closure.function, None)?.finish(
|
|
||||||
"function",
|
|
||||||
"f",
|
|
||||||
ExportedShim::TableElement(&mut shim),
|
|
||||||
)
|
|
||||||
};
|
|
||||||
input.function_table_needed = true;
|
|
||||||
let body = format!(
|
|
||||||
"function(a, b, _ignored) {{
|
|
||||||
const f = wasm.__wbg_function_table.get({});
|
|
||||||
const d = wasm.__wbg_function_table.get({});
|
|
||||||
const cb = {};
|
|
||||||
cb.a = a;
|
|
||||||
cb.cnt = 1;
|
|
||||||
let real = cb.bind(cb);
|
|
||||||
real.original = cb;
|
|
||||||
return {};
|
|
||||||
}}",
|
|
||||||
shim,
|
|
||||||
closure.dtor_idx,
|
|
||||||
js,
|
|
||||||
input.add_heap_object("real"),
|
|
||||||
);
|
|
||||||
input.export(&import_name, &body, None)?;
|
|
||||||
|
|
||||||
let module = "__wbindgen_placeholder__";
|
|
||||||
let id = input.module.add_import_func(module, &import_name, ty);
|
|
||||||
input.anyref.import_xform(module, &import_name, &[], true);
|
|
||||||
input.module.funcs.get_mut(id).name = Some(import_name);
|
|
||||||
|
|
||||||
let local = match &mut input.module.funcs.get_mut(*func).kind {
|
|
||||||
walrus::FunctionKind::Local(l) => l,
|
|
||||||
_ => unreachable!(),
|
|
||||||
};
|
|
||||||
match local.get_mut(instr.call) {
|
|
||||||
Expr::Call(e) => {
|
|
||||||
assert_eq!(e.func, wbindgen_describe_closure);
|
|
||||||
e.func = id;
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
@ -51,7 +51,7 @@ pub struct Js2Rust<'a, 'b: 'a> {
|
|||||||
|
|
||||||
/// Typescript expression representing the type of the return value of this
|
/// Typescript expression representing the type of the return value of this
|
||||||
/// function.
|
/// function.
|
||||||
ret_ty: String,
|
pub ret_ty: String,
|
||||||
|
|
||||||
/// Expression used to generate the return value. The string "RET" in this
|
/// Expression used to generate the return value. The string "RET" in this
|
||||||
/// expression is replaced with the actual wasm invocation eventually.
|
/// expression is replaced with the actual wasm invocation eventually.
|
||||||
@ -68,15 +68,6 @@ pub struct Js2Rust<'a, 'b: 'a> {
|
|||||||
/// The string value here is the class that this should be a constructor
|
/// The string value here is the class that this should be a constructor
|
||||||
/// for.
|
/// for.
|
||||||
constructor: Option<String>,
|
constructor: Option<String>,
|
||||||
|
|
||||||
/// metadata for anyref transformations
|
|
||||||
anyref_args: Vec<(usize, bool)>,
|
|
||||||
ret_anyref: bool,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum ExportedShim<'a> {
|
|
||||||
Named(&'a str),
|
|
||||||
TableElement(&'a mut u32),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Js2Rust<'a, 'b> {
|
impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||||
@ -92,31 +83,36 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
ret_ty: String::new(),
|
ret_ty: String::new(),
|
||||||
ret_expr: String::new(),
|
ret_expr: String::new(),
|
||||||
constructor: None,
|
constructor: None,
|
||||||
anyref_args: Vec::new(),
|
|
||||||
ret_anyref: false,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Generates all bindings necessary for the signature in `Function`,
|
/// Generates all bindings necessary for the signature in `Function`,
|
||||||
/// creating necessary argument conversions and return value processing.
|
/// creating necessary argument conversions and return value processing.
|
||||||
pub fn process<'c, I>(
|
pub fn process(
|
||||||
&mut self,
|
&mut self,
|
||||||
function: &Function,
|
function: &Function,
|
||||||
opt_arg_names: I,
|
opt_arg_names: &Option<Vec<String>>,
|
||||||
) -> Result<&mut Self, Error>
|
) -> Result<&mut Self, Error> {
|
||||||
where
|
let arg_names = match opt_arg_names {
|
||||||
I: Into<Option<&'c Vec<String>>>,
|
Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(),
|
||||||
{
|
None => vec![None; function.arguments.len()],
|
||||||
if let Some(arg_names) = opt_arg_names.into() {
|
};
|
||||||
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
assert_eq!(arg_names.len(), function.arguments.len());
|
||||||
self.argument(arg, arg_name.as_str())?;
|
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
||||||
}
|
// Process the function argument and assert that the metadata about
|
||||||
} else {
|
// the number of arguments on the Rust side required is correct.
|
||||||
for arg in function.arguments.iter() {
|
let before = self.rust_arguments.len();
|
||||||
self.argument(arg, None)?;
|
self.argument(arg, arg_name)?;
|
||||||
}
|
arg.assert_abi_arg_correct(before, self.rust_arguments.len());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Process the return argument, and assert that the metadata returned
|
||||||
|
// about the descriptor is indeed correct.
|
||||||
|
let before = self.rust_arguments.len();
|
||||||
self.ret(&function.ret)?;
|
self.ret(&function.ret)?;
|
||||||
|
function
|
||||||
|
.ret
|
||||||
|
.assert_abi_return_correct(before, self.rust_arguments.len());
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -183,12 +179,9 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
ret
|
ret
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn argument<'c, I>(&mut self, arg: &Descriptor, opt_arg_name: I) -> Result<&mut Self, Error>
|
fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> {
|
||||||
where
|
|
||||||
I: Into<Option<&'c str>>,
|
|
||||||
{
|
|
||||||
let i = self.arg_idx;
|
let i = self.arg_idx;
|
||||||
let name = self.abi_arg(opt_arg_name.into());
|
let name = self.abi_arg(arg_name);
|
||||||
|
|
||||||
let (arg, optional) = match arg {
|
let (arg, optional) = match arg {
|
||||||
Descriptor::Option(t) => (&**t, true),
|
Descriptor::Option(t) => (&**t, true),
|
||||||
@ -254,7 +247,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
self.rust_arguments
|
self.rust_arguments
|
||||||
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
|
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
|
||||||
} else {
|
} else {
|
||||||
self.anyref_args.push((self.rust_arguments.len(), true));
|
|
||||||
self.rust_arguments.push(name);
|
self.rust_arguments.push(name);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -451,7 +443,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
self.js_arguments
|
self.js_arguments
|
||||||
.push(JsArgument::required(name.clone(), "any".to_string()));
|
.push(JsArgument::required(name.clone(), "any".to_string()));
|
||||||
if self.cx.config.anyref {
|
if self.cx.config.anyref {
|
||||||
self.anyref_args.push((self.rust_arguments.len(), false));
|
|
||||||
self.rust_arguments.push(name);
|
self.rust_arguments.push(name);
|
||||||
} else {
|
} else {
|
||||||
// the "stack-ful" nature means that we're always popping from the
|
// the "stack-ful" nature means that we're always popping from the
|
||||||
@ -494,7 +485,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
fn ret(&mut self, ty: &Descriptor) -> Result<&mut Self, Error> {
|
||||||
if let Some(name) = ty.rust_struct() {
|
if let Some(name) = ty.rust_struct() {
|
||||||
match &self.constructor {
|
match &self.constructor {
|
||||||
Some(class) if class == name => {
|
Some(class) if class == name => {
|
||||||
@ -568,7 +559,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
if ty.is_anyref() {
|
if ty.is_anyref() {
|
||||||
self.ret_ty = "any".to_string();
|
self.ret_ty = "any".to_string();
|
||||||
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
||||||
self.ret_anyref = true;
|
|
||||||
return Ok(self);
|
return Ok(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -786,12 +776,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
/// Returns two strings, the first of which is the JS expression for the
|
/// Returns two strings, the first of which is the JS expression for the
|
||||||
/// generated function shim and the second is a TypeScript signature of the
|
/// generated function shim and the second is a TypeScript signature of the
|
||||||
/// JS expression.
|
/// JS expression.
|
||||||
pub fn finish(
|
pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) {
|
||||||
&mut self,
|
|
||||||
prefix: &str,
|
|
||||||
invoc: &str,
|
|
||||||
exported_shim: ExportedShim,
|
|
||||||
) -> (String, String, String) {
|
|
||||||
let js_args = self
|
let js_args = self
|
||||||
.js_arguments
|
.js_arguments
|
||||||
.iter()
|
.iter()
|
||||||
@ -856,23 +841,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
|||||||
}
|
}
|
||||||
ts.push(';');
|
ts.push(';');
|
||||||
|
|
||||||
if self.ret_anyref || self.anyref_args.len() > 0 {
|
|
||||||
match exported_shim {
|
|
||||||
ExportedShim::Named(name) => {
|
|
||||||
self.cx
|
|
||||||
.anyref
|
|
||||||
.export_xform(name, &self.anyref_args, self.ret_anyref);
|
|
||||||
}
|
|
||||||
ExportedShim::TableElement(idx) => {
|
|
||||||
*idx = self.cx.anyref.table_element_xform(
|
|
||||||
*idx,
|
|
||||||
&self.anyref_args,
|
|
||||||
self.ret_anyref,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(js, ts, self.js_doc_comments())
|
(js, ts, self.js_doc_comments())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,13 @@
|
|||||||
use crate::descriptor::{Descriptor, Function};
|
use crate::descriptor::Descriptor;
|
||||||
use crate::js::js2rust::ExportedShim;
|
use crate::intrinsic::Intrinsic;
|
||||||
use crate::js::{Context, ImportTarget, Js2Rust};
|
use crate::js::{Context, Js2Rust};
|
||||||
|
use crate::webidl::{AuxImport, AuxValue, ImportBinding};
|
||||||
use failure::{bail, Error};
|
use failure::{bail, Error};
|
||||||
|
|
||||||
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
||||||
/// JS, then invoking an imported JS function.
|
/// JS, then invoking an imported JS function.
|
||||||
pub struct Rust2Js<'a, 'b: 'a> {
|
pub struct Rust2Js<'a, 'b: 'a> {
|
||||||
pub cx: &'a mut Context<'b>,
|
cx: &'a mut Context<'b>,
|
||||||
|
|
||||||
/// Arguments of the JS shim that we're generating, aka the variables passed
|
/// Arguments of the JS shim that we're generating, aka the variables passed
|
||||||
/// from Rust which are only numbers.
|
/// from Rust which are only numbers.
|
||||||
@ -41,10 +42,27 @@ pub struct Rust2Js<'a, 'b: 'a> {
|
|||||||
/// Whether or not the last argument is a slice representing variadic arguments.
|
/// Whether or not the last argument is a slice representing variadic arguments.
|
||||||
variadic: bool,
|
variadic: bool,
|
||||||
|
|
||||||
|
/// What sort of style this invocation will be like, see the variants of
|
||||||
|
/// this enum for more information.
|
||||||
|
style: Style,
|
||||||
|
|
||||||
/// list of arguments that are anyref, and whether they're an owned anyref
|
/// list of arguments that are anyref, and whether they're an owned anyref
|
||||||
/// or not.
|
/// or not.
|
||||||
pub anyref_args: Vec<(usize, bool)>,
|
anyref_args: Vec<(usize, bool)>,
|
||||||
pub ret_anyref: bool,
|
ret_anyref: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PartialEq)]
|
||||||
|
enum Style {
|
||||||
|
/// The imported function is expected to be invoked with `new` to create a
|
||||||
|
/// JS object.
|
||||||
|
Constructor,
|
||||||
|
/// The imported function is expected to be invoked where the first
|
||||||
|
/// parameter is the `this` and the rest of the arguments are the
|
||||||
|
/// function's arguments.
|
||||||
|
Method,
|
||||||
|
/// Just a normal function call.
|
||||||
|
Function,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, 'b> Rust2Js<'a, 'b> {
|
impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||||
@ -63,6 +81,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
variadic: false,
|
variadic: false,
|
||||||
anyref_args: Vec::new(),
|
anyref_args: Vec::new(),
|
||||||
ret_anyref: false,
|
ret_anyref: false,
|
||||||
|
style: Style::Function,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,11 +102,35 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
|
|
||||||
/// Generates all bindings necessary for the signature in `Function`,
|
/// Generates all bindings necessary for the signature in `Function`,
|
||||||
/// creating necessary argument conversions and return value processing.
|
/// creating necessary argument conversions and return value processing.
|
||||||
pub fn process(&mut self, function: &Function) -> Result<&mut Self, Error> {
|
pub fn process(&mut self, binding: &ImportBinding) -> Result<&mut Self, Error> {
|
||||||
|
let function = match binding {
|
||||||
|
ImportBinding::Constructor(f) => {
|
||||||
|
self.style = Style::Constructor;
|
||||||
|
f
|
||||||
|
}
|
||||||
|
ImportBinding::Method(f) => {
|
||||||
|
self.style = Style::Method;
|
||||||
|
f
|
||||||
|
}
|
||||||
|
ImportBinding::Function(f) => {
|
||||||
|
self.style = Style::Function;
|
||||||
|
f
|
||||||
|
}
|
||||||
|
};
|
||||||
for arg in function.arguments.iter() {
|
for arg in function.arguments.iter() {
|
||||||
|
// Process the function argument and assert that the metadata about
|
||||||
|
// the number of arguments on the Rust side required is correct.
|
||||||
|
let before = self.shim_arguments.len();
|
||||||
self.argument(arg)?;
|
self.argument(arg)?;
|
||||||
|
arg.assert_abi_arg_correct(before, self.shim_arguments.len());
|
||||||
}
|
}
|
||||||
|
// Process the return argument, and assert that the metadata returned
|
||||||
|
// about the descriptor is indeed correct.
|
||||||
|
let before = self.shim_arguments.len();
|
||||||
self.ret(&function.ret)?;
|
self.ret(&function.ret)?;
|
||||||
|
function
|
||||||
|
.ret
|
||||||
|
.assert_abi_return_correct(before, self.shim_arguments.len());
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,7 +317,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
|
|
||||||
if let Some((f, mutable)) = arg.stack_closure() {
|
if let Some((f, mutable)) = arg.stack_closure() {
|
||||||
let arg2 = self.shim_argument();
|
let arg2 = self.shim_argument();
|
||||||
let mut shim = f.shim_idx;
|
|
||||||
let (js, _ts, _js_doc) = {
|
let (js, _ts, _js_doc) = {
|
||||||
let mut builder = Js2Rust::new("", self.cx);
|
let mut builder = Js2Rust::new("", self.cx);
|
||||||
if mutable {
|
if mutable {
|
||||||
@ -286,13 +328,12 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
} else {
|
} else {
|
||||||
builder.rust_argument("this.a");
|
builder.rust_argument("this.a");
|
||||||
}
|
}
|
||||||
builder.rust_argument("this.b").process(f, None)?.finish(
|
builder
|
||||||
"function",
|
.rust_argument("this.b")
|
||||||
"this.f",
|
.process(f, &None)?
|
||||||
ExportedShim::TableElement(&mut shim),
|
.finish("function", "this.f")
|
||||||
)
|
|
||||||
};
|
};
|
||||||
self.cx.function_table_needed = true;
|
self.cx.export_function_table()?;
|
||||||
self.global_idx();
|
self.global_idx();
|
||||||
self.prelude(&format!(
|
self.prelude(&format!(
|
||||||
"\
|
"\
|
||||||
@ -304,7 +345,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
abi,
|
abi,
|
||||||
arg2,
|
arg2,
|
||||||
js = js,
|
js = js,
|
||||||
idx = shim,
|
idx = f.shim_idx,
|
||||||
));
|
));
|
||||||
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
|
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
|
||||||
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
|
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
|
||||||
@ -575,7 +616,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
///
|
///
|
||||||
/// This is used as an optimization to wire up imports directly where
|
/// This is used as an optimization to wire up imports directly where
|
||||||
/// possible and avoid a shim in some circumstances.
|
/// possible and avoid a shim in some circumstances.
|
||||||
pub fn is_noop(&self) -> bool {
|
fn is_noop(&self) -> bool {
|
||||||
let Rust2Js {
|
let Rust2Js {
|
||||||
// fields which may affect whether we do nontrivial work
|
// fields which may affect whether we do nontrivial work
|
||||||
catch,
|
catch,
|
||||||
@ -594,6 +635,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
global_idx: _,
|
global_idx: _,
|
||||||
anyref_args: _,
|
anyref_args: _,
|
||||||
ret_anyref: _,
|
ret_anyref: _,
|
||||||
|
style,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
!catch &&
|
!catch &&
|
||||||
@ -607,96 +649,277 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
// similarly we want to make sure that all the arguments are simply
|
// similarly we want to make sure that all the arguments are simply
|
||||||
// forwarded from the shim we would generate to the import,
|
// forwarded from the shim we would generate to the import,
|
||||||
// requiring no transformations
|
// requiring no transformations
|
||||||
js_arguments == shim_arguments
|
js_arguments == shim_arguments &&
|
||||||
|
// method/constructor invocations require some JS shimming right
|
||||||
|
// now, so only normal function-style invocations may get wired up
|
||||||
|
*style == Style::Function
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn finish(&mut self, invoc: &ImportTarget, shim: &str) -> Result<String, Error> {
|
pub fn finish(&mut self, target: &AuxImport) -> Result<String, Error> {
|
||||||
let mut ret = String::new();
|
|
||||||
ret.push_str("function(");
|
|
||||||
ret.push_str(&self.shim_arguments.join(", "));
|
|
||||||
if self.catch {
|
|
||||||
if self.shim_arguments.len() > 0 {
|
|
||||||
ret.push_str(", ")
|
|
||||||
}
|
|
||||||
ret.push_str("exnptr");
|
|
||||||
}
|
|
||||||
ret.push_str(") {\n");
|
|
||||||
ret.push_str(&self.prelude);
|
|
||||||
|
|
||||||
let variadic = self.variadic;
|
let variadic = self.variadic;
|
||||||
let ret_expr = &self.ret_expr;
|
let variadic_args = |js_arguments: &[String]| {
|
||||||
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
Ok(if !variadic {
|
||||||
let ret = if variadic {
|
format!("{}", js_arguments.join(", "))
|
||||||
|
} else {
|
||||||
let (last_arg, args) = match js_arguments.split_last() {
|
let (last_arg, args) = match js_arguments.split_last() {
|
||||||
Some(pair) => pair,
|
Some(pair) => pair,
|
||||||
None => bail!("a function with no arguments cannot be variadic"),
|
None => bail!("a function with no arguments cannot be variadic"),
|
||||||
};
|
};
|
||||||
if args.len() > 0 {
|
if args.len() > 0 {
|
||||||
ret_expr.replace(
|
format!("{}, ...{}", args.join(", "), last_arg)
|
||||||
"JS",
|
|
||||||
&format!("{}({}, ...{})", invoc, args.join(", "), last_arg),
|
|
||||||
)
|
|
||||||
} else {
|
} else {
|
||||||
ret_expr.replace("JS", &format!("{}(...{})", invoc, last_arg))
|
format!("...{}", last_arg)
|
||||||
}
|
}
|
||||||
} else {
|
})
|
||||||
ret_expr.replace("JS", &format!("{}({})", invoc, js_arguments.join(", ")))
|
|
||||||
};
|
|
||||||
Ok(ret)
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let js_arguments = &self.js_arguments;
|
let invoc = match target {
|
||||||
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
AuxImport::Value(val) => match self.style {
|
||||||
if variadic {
|
Style::Constructor => {
|
||||||
bail!("{} cannot be variadic", desc);
|
let js = match val {
|
||||||
}
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||||
match (class, js_arguments.len()) {
|
_ => bail!("invalid import set for constructor"),
|
||||||
(None, n) if n == amt + 1 => Ok((js_arguments[0].clone(), &js_arguments[1..])),
|
};
|
||||||
(None, _) => bail!("setters must have {} arguments", amt + 1),
|
format!("new {}({})", js, variadic_args(&self.js_arguments)?)
|
||||||
(Some(class), n) if n == amt => Ok((class.clone(), &js_arguments[..])),
|
}
|
||||||
(Some(_), _) => bail!("static setters must have {} arguments", amt),
|
Style::Method => {
|
||||||
}
|
let descriptor = |anchor: &str, extra: &str, field: &str, which: &str| {
|
||||||
};
|
format!(
|
||||||
|
"GetOwnOrInheritedPropertyDescriptor({}{}, '{}').{}",
|
||||||
|
anchor, extra, field, which
|
||||||
|
)
|
||||||
|
};
|
||||||
|
let js = match val {
|
||||||
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||||
|
AuxValue::Getter(class, field) => {
|
||||||
|
self.cx.expose_get_inherited_descriptor();
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
descriptor(&class, ".prototype", field, "get")
|
||||||
|
}
|
||||||
|
AuxValue::ClassGetter(class, field) => {
|
||||||
|
self.cx.expose_get_inherited_descriptor();
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
descriptor(&class, "", field, "get")
|
||||||
|
}
|
||||||
|
AuxValue::Setter(class, field) => {
|
||||||
|
self.cx.expose_get_inherited_descriptor();
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
descriptor(&class, ".prototype", field, "set")
|
||||||
|
}
|
||||||
|
AuxValue::ClassSetter(class, field) => {
|
||||||
|
self.cx.expose_get_inherited_descriptor();
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
descriptor(&class, "", field, "set")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
format!("{}.call({})", js, variadic_args(&self.js_arguments)?)
|
||||||
|
}
|
||||||
|
Style::Function => {
|
||||||
|
let js = match val {
|
||||||
|
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||||
|
_ => bail!("invalid import set for constructor"),
|
||||||
|
};
|
||||||
|
if self.is_noop() {
|
||||||
|
self.cx.expose_does_not_exist();
|
||||||
|
// TODO: comment this
|
||||||
|
let js = format!("typeof {} === 'undefined' ? doesNotExist : {0}", js);
|
||||||
|
return Ok(js);
|
||||||
|
}
|
||||||
|
format!("{}({})", js, variadic_args(&self.js_arguments)?)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
let mut invoc = match invoc {
|
AuxImport::Instanceof(js) => {
|
||||||
ImportTarget::Function(f) => handle_variadic(&f, &self.js_arguments)?,
|
let js = self.cx.import_name(js)?;
|
||||||
ImportTarget::Constructor(c) => {
|
assert!(self.style == Style::Function);
|
||||||
handle_variadic(&format!("new {}", c), &self.js_arguments)?
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("{} instanceof {}", self.js_arguments[0], js)
|
||||||
}
|
}
|
||||||
ImportTarget::Method(f) => handle_variadic(&format!("{}.call", f), &self.js_arguments)?,
|
|
||||||
ImportTarget::StructuralMethod(f) => {
|
AuxImport::Static(js) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
self.cx.import_name(js)?
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::Closure(closure) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 3);
|
||||||
|
let (js, _ts, _js_doc) = {
|
||||||
|
let mut builder = Js2Rust::new("", self.cx);
|
||||||
|
|
||||||
|
// First up with a closure we increment the internal reference
|
||||||
|
// count. This ensures that the Rust closure environment won't
|
||||||
|
// be deallocated while we're invoking it.
|
||||||
|
builder.prelude("this.cnt++;");
|
||||||
|
|
||||||
|
if closure.mutable {
|
||||||
|
// For mutable closures they can't be invoked recursively.
|
||||||
|
// To handle that we swap out the `this.a` pointer with zero
|
||||||
|
// while we invoke it. If we finish and the closure wasn't
|
||||||
|
// destroyed, then we put back the pointer so a future
|
||||||
|
// invocation can succeed.
|
||||||
|
builder
|
||||||
|
.prelude("let a = this.a;")
|
||||||
|
.prelude("this.a = 0;")
|
||||||
|
.rust_argument("a")
|
||||||
|
.rust_argument("b")
|
||||||
|
.finally("if (--this.cnt === 0) d(a, b);")
|
||||||
|
.finally("else this.a = a;");
|
||||||
|
} else {
|
||||||
|
// For shared closures they can be invoked recursively so we
|
||||||
|
// just immediately pass through `this.a`. If we end up
|
||||||
|
// executing the destructor, however, we clear out the
|
||||||
|
// `this.a` pointer to prevent it being used again the
|
||||||
|
// future.
|
||||||
|
builder
|
||||||
|
.rust_argument("this.a")
|
||||||
|
.rust_argument("b")
|
||||||
|
.finally("if (--this.cnt === 0) {")
|
||||||
|
.finally("d(this.a, b);")
|
||||||
|
.finally("this.a = 0;")
|
||||||
|
.finally("}");
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
.process(&closure.function, &None)?
|
||||||
|
.finish("function", "f")
|
||||||
|
};
|
||||||
|
self.cx.export_function_table()?;
|
||||||
|
let body = format!(
|
||||||
|
"
|
||||||
|
const f = wasm.__wbg_function_table.get({});
|
||||||
|
const d = wasm.__wbg_function_table.get({});
|
||||||
|
const b = {};
|
||||||
|
const cb = {};
|
||||||
|
cb.a = {};
|
||||||
|
cb.cnt = 1;
|
||||||
|
let real = cb.bind(cb);
|
||||||
|
real.original = cb;
|
||||||
|
",
|
||||||
|
closure.shim_idx,
|
||||||
|
closure.dtor_idx,
|
||||||
|
&self.js_arguments[1],
|
||||||
|
js,
|
||||||
|
&self.js_arguments[0],
|
||||||
|
);
|
||||||
|
self.prelude(&body);
|
||||||
|
"real".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::StructuralMethod(name) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
let (receiver, args) = match self.js_arguments.split_first() {
|
let (receiver, args) = match self.js_arguments.split_first() {
|
||||||
Some(pair) => pair,
|
Some(pair) => pair,
|
||||||
None => bail!("methods must have at least one argument"),
|
None => bail!("structural method calls must have at least one argument"),
|
||||||
};
|
};
|
||||||
handle_variadic(&format!("{}.{}", receiver, f), args)?
|
format!("{}.{}({})", receiver, name, variadic_args(args)?)
|
||||||
}
|
}
|
||||||
ImportTarget::StructuralGetter(class, field) => {
|
|
||||||
let (receiver, _) = fixed("getter", class, 0)?;
|
AuxImport::StructuralGetter(field) => {
|
||||||
let expr = format!("{}.{}", receiver, field);
|
assert!(self.style == Style::Function);
|
||||||
self.ret_expr.replace("JS", &expr)
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("{}.{}", self.js_arguments[0], field)
|
||||||
}
|
}
|
||||||
ImportTarget::StructuralSetter(class, field) => {
|
|
||||||
let (receiver, val) = fixed("setter", class, 1)?;
|
AuxImport::StructuralClassGetter(class, field) => {
|
||||||
let expr = format!("{}.{} = {}", receiver, field, val[0]);
|
assert!(self.style == Style::Function);
|
||||||
self.ret_expr.replace("JS", &expr)
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
format!("{}.{}", class, field)
|
||||||
}
|
}
|
||||||
ImportTarget::StructuralIndexingGetter(class) => {
|
|
||||||
let (receiver, field) = fixed("indexing getter", class, 1)?;
|
AuxImport::StructuralSetter(field) => {
|
||||||
let expr = format!("{}[{}]", receiver, field[0]);
|
assert!(self.style == Style::Function);
|
||||||
self.ret_expr.replace("JS", &expr)
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
format!(
|
||||||
|
"{}.{} = {}",
|
||||||
|
self.js_arguments[0], field, self.js_arguments[1]
|
||||||
|
)
|
||||||
}
|
}
|
||||||
ImportTarget::StructuralIndexingSetter(class) => {
|
|
||||||
let (receiver, field) = fixed("indexing setter", class, 2)?;
|
AuxImport::StructuralClassSetter(class, field) => {
|
||||||
let expr = format!("{}[{}] = {}", receiver, field[0], field[1]);
|
assert!(self.style == Style::Function);
|
||||||
self.ret_expr.replace("JS", &expr)
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
format!("{}.{} = {}", class, field, self.js_arguments[0])
|
||||||
}
|
}
|
||||||
ImportTarget::StructuralIndexingDeleter(class) => {
|
|
||||||
let (receiver, field) = fixed("indexing deleter", class, 1)?;
|
AuxImport::IndexingGetterOfClass(class) => {
|
||||||
let expr = format!("delete {}[{}]", receiver, field[0]);
|
assert!(self.style == Style::Function);
|
||||||
self.ret_expr.replace("JS", &expr)
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
format!("{}[{}]", class, self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::IndexingGetterOfObject => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
format!("{}[{}]", self.js_arguments[0], self.js_arguments[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::IndexingSetterOfClass(class) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
format!(
|
||||||
|
"{}[{}] = {}",
|
||||||
|
class, self.js_arguments[0], self.js_arguments[1]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::IndexingSetterOfObject => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 3);
|
||||||
|
format!(
|
||||||
|
"{}[{}] = {}",
|
||||||
|
self.js_arguments[0], self.js_arguments[1], self.js_arguments[2]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::IndexingDeleterOfClass(class) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
let class = self.cx.import_name(class)?;
|
||||||
|
format!("delete {}[{}]", class, self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::IndexingDeleterOfObject => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
format!("delete {}[{}]", self.js_arguments[0], self.js_arguments[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::WrapInExportedClass(class) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.cx.require_class_wrap(class);
|
||||||
|
if self.is_noop() {
|
||||||
|
return Ok(format!("{}.__wrap", class));
|
||||||
|
}
|
||||||
|
format!("{}.__wrap({})", class, self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
AuxImport::Intrinsic(intrinsic) => {
|
||||||
|
assert!(self.style == Style::Function);
|
||||||
|
assert!(!variadic);
|
||||||
|
self.intrinsic_expr(intrinsic)?
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let mut invoc = self.ret_expr.replace("JS", &invoc);
|
||||||
|
|
||||||
if self.catch {
|
if self.catch {
|
||||||
self.cx.expose_handle_error()?;
|
self.cx.expose_handle_error()?;
|
||||||
@ -718,20 +941,20 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
}} catch (e) {{\n\
|
}} catch (e) {{\n\
|
||||||
let error = (function () {{
|
let error = (function () {{
|
||||||
try {{
|
try {{
|
||||||
return e instanceof Error
|
return e instanceof Error \
|
||||||
? `${{e.message}}\n\nStack:\n${{e.stack}}`
|
? `${{e.message}}\\n\\nStack:\\n${{e.stack}}` \
|
||||||
: e.toString();
|
: e.toString();
|
||||||
}} catch(_) {{
|
}} catch(_) {{
|
||||||
return \"<failed to stringify thrown value>\";
|
return \"<failed to stringify thrown value>\";
|
||||||
}}
|
}}
|
||||||
}}());
|
}}());
|
||||||
console.error(\"wasm-bindgen: imported JS function `{}` that \
|
console.error(\"wasm-bindgen: imported JS function that \
|
||||||
was not marked as `catch` threw an error:\", \
|
was not marked as `catch` threw an error:\", \
|
||||||
error);
|
error);
|
||||||
throw e;
|
throw e;
|
||||||
}}\
|
}}\
|
||||||
",
|
",
|
||||||
&invoc, shim,
|
&invoc,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -747,35 +970,20 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
&invoc, &self.finally
|
&invoc, &self.finally
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
ret.push_str(&invoc);
|
let mut ret = String::new();
|
||||||
|
ret.push_str("function(");
|
||||||
ret.push_str("\n}\n");
|
ret.push_str(&self.shim_arguments.join(", "));
|
||||||
|
if self.catch {
|
||||||
if self.ret_anyref || self.anyref_args.len() > 0 {
|
if self.shim_arguments.len() > 0 {
|
||||||
// Some return values go at the the beginning of the argument list
|
ret.push_str(", ")
|
||||||
// (they force a return pointer). Handle that here by offsetting all
|
|
||||||
// our arg indices by one, but throw in some sanity checks for if
|
|
||||||
// this ever changes.
|
|
||||||
if let Some(start) = self.shim_arguments.get(0) {
|
|
||||||
if start == "ret" {
|
|
||||||
assert!(!self.ret_anyref);
|
|
||||||
if let Some(next) = self.shim_arguments.get(1) {
|
|
||||||
assert_eq!(next, "arg0");
|
|
||||||
}
|
|
||||||
for (idx, _) in self.anyref_args.iter_mut() {
|
|
||||||
*idx += 1;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
assert_eq!(start, "arg0");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
self.cx.anyref.import_xform(
|
ret.push_str("exnptr");
|
||||||
"__wbindgen_placeholder__",
|
|
||||||
shim,
|
|
||||||
&self.anyref_args,
|
|
||||||
self.ret_anyref,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
ret.push_str(") {\n");
|
||||||
|
ret.push_str(&self.prelude);
|
||||||
|
|
||||||
|
ret.push_str(&invoc);
|
||||||
|
ret.push_str("\n}\n");
|
||||||
|
|
||||||
Ok(ret)
|
Ok(ret)
|
||||||
}
|
}
|
||||||
@ -801,4 +1009,213 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
|||||||
}
|
}
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn intrinsic_expr(&mut self, intrinsic: &Intrinsic) -> Result<String, Error> {
|
||||||
|
let expr = match intrinsic {
|
||||||
|
Intrinsic::JsvalEq => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
format!("{} === {}", self.js_arguments[0], self.js_arguments[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsFunction => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("typeof({}) === 'function'", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsUndefined => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("{} === undefined", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsNull => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("{} === null", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsObject => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.prelude(&format!("const val = {};", self.js_arguments[0]));
|
||||||
|
format!("typeof(val) === 'object' && val !== null ? 1 : 0")
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsSymbol => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("typeof({}) === 'symbol'", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::IsString => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("typeof({}) === 'string'", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::ObjectCloneRef => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.js_arguments[0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::ObjectDropRef => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.js_arguments[0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::CallbackDrop => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.prelude(&format!("const obj = {}.original;", self.js_arguments[0]));
|
||||||
|
self.prelude("if (obj.cnt-- == 1) {");
|
||||||
|
self.prelude("obj.a = 0;");
|
||||||
|
self.prelude("return true;");
|
||||||
|
self.prelude("}");
|
||||||
|
"false".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::CallbackForget => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.js_arguments[0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::NumberNew => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.js_arguments[0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::StringNew => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.js_arguments[0].clone()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::SymbolNamedNew => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("Symbol({})", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::SymbolAnonymousNew => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
"Symbol()".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::NumberGet => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
self.cx.expose_uint8_memory();
|
||||||
|
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
||||||
|
self.prelude("if (typeof(obj) === 'number') return obj;");
|
||||||
|
self.prelude(&format!("getUint8Memory()[{}] = 1;", self.js_arguments[1]));
|
||||||
|
"0".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::StringGet => {
|
||||||
|
self.cx.expose_pass_string_to_wasm()?;
|
||||||
|
self.cx.expose_uint32_memory();
|
||||||
|
assert_eq!(self.js_arguments.len(), 2);
|
||||||
|
self.prelude(&format!("const obj = {};", self.js_arguments[0]));
|
||||||
|
self.prelude("if (typeof(obj) !== 'string') return 0;");
|
||||||
|
self.prelude("const ptr = passStringToWasm(obj);");
|
||||||
|
self.prelude(&format!(
|
||||||
|
"getUint32Memory()[{} / 4] = WASM_VECTOR_LEN;",
|
||||||
|
self.js_arguments[1],
|
||||||
|
));
|
||||||
|
"ptr".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::BooleanGet => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.prelude(&format!("const v = {};", self.js_arguments[0]));
|
||||||
|
format!("typeof(v) === 'boolean' ? (v ? 1 : 0) : 2")
|
||||||
|
}
|
||||||
|
Intrinsic::Throw => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("throw new Error({})", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::Rethrow => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("throw {}", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::Module => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
if !self.cx.config.mode.no_modules() && !self.cx.config.mode.web() {
|
||||||
|
bail!(
|
||||||
|
"`wasm_bindgen::module` is currently only supported with \
|
||||||
|
`--target no-modules` and `--target web`"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
format!("init.__wbindgen_wasm_module")
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::Memory => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
self.cx.memory().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::FunctionTable => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
self.cx.export_function_table()?;
|
||||||
|
format!("wasm.__wbg_function_table")
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::DebugString => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
self.cx.expose_debug_string();
|
||||||
|
format!("debugString({})", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::JsonParse => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("JSON.parse({})", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::JsonSerialize => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 1);
|
||||||
|
format!("JSON.stringify({})", self.js_arguments[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::AnyrefHeapLiveCount => {
|
||||||
|
assert_eq!(self.js_arguments.len(), 0);
|
||||||
|
if self.cx.config.anyref {
|
||||||
|
// Eventually we should add support to the anyref-xform to
|
||||||
|
// re-write calls to the imported
|
||||||
|
// `__wbindgen_anyref_heap_live_count` function into calls to
|
||||||
|
// the exported `__wbindgen_anyref_heap_live_count_impl`
|
||||||
|
// function, and to un-export that function.
|
||||||
|
//
|
||||||
|
// But for now, we just bounce wasm -> js -> wasm because it is
|
||||||
|
// easy.
|
||||||
|
self.cx.require_internal_export("__wbindgen_anyref_heap_live_count_impl")?;
|
||||||
|
"wasm.__wbindgen_anyref_heap_live_count_impl()".into()
|
||||||
|
} else {
|
||||||
|
self.cx.expose_global_heap();
|
||||||
|
self.prelude(
|
||||||
|
"
|
||||||
|
let free_count = 0;
|
||||||
|
let next = heap_next;
|
||||||
|
while (next < heap.length) {
|
||||||
|
free_count += 1;
|
||||||
|
next = heap[next];
|
||||||
|
}
|
||||||
|
",
|
||||||
|
);
|
||||||
|
format!(
|
||||||
|
"heap.length - free_count - {} - {}",
|
||||||
|
super::INITIAL_HEAP_OFFSET,
|
||||||
|
super::INITIAL_HEAP_VALUES.len(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Intrinsic::InitAnyrefTable => {
|
||||||
|
self.cx.expose_anyref_table();
|
||||||
|
String::from(
|
||||||
|
"
|
||||||
|
const table = wasm.__wbg_anyref_table;
|
||||||
|
const offset = table.grow(4);
|
||||||
|
table.set(offset + 0, undefined);
|
||||||
|
table.set(offset + 1, null);
|
||||||
|
table.set(offset + 2, true);
|
||||||
|
table.set(offset + 3, false);
|
||||||
|
",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(expr)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,10 +9,14 @@ use std::path::{Path, PathBuf};
|
|||||||
use std::str;
|
use std::str;
|
||||||
use walrus::Module;
|
use walrus::Module;
|
||||||
|
|
||||||
|
mod anyref;
|
||||||
mod decode;
|
mod decode;
|
||||||
|
mod intrinsic;
|
||||||
mod descriptor;
|
mod descriptor;
|
||||||
|
mod descriptors;
|
||||||
mod js;
|
mod js;
|
||||||
pub mod wasm2es6js;
|
pub mod wasm2es6js;
|
||||||
|
mod webidl;
|
||||||
|
|
||||||
pub struct Bindgen {
|
pub struct Bindgen {
|
||||||
input: Input,
|
input: Input,
|
||||||
@ -282,99 +286,76 @@ impl Bindgen {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut program_storage = Vec::new();
|
|
||||||
let programs = extract_programs(&mut module, &mut program_storage)
|
|
||||||
.with_context(|_| "failed to extract wasm-bindgen custom sections")?;
|
|
||||||
|
|
||||||
if let Some(cfg) = &self.threads {
|
if let Some(cfg) = &self.threads {
|
||||||
cfg.run(&mut module)
|
cfg.run(&mut module)
|
||||||
.with_context(|_| "failed to prepare module for threading")?;
|
.with_context(|_| "failed to prepare module for threading")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If requested, turn all mangled symbols into prettier unmangled
|
||||||
|
// symbols with the help of `rustc-demangle`.
|
||||||
if self.demangle {
|
if self.demangle {
|
||||||
demangle(&mut module);
|
demangle(&mut module);
|
||||||
}
|
}
|
||||||
|
unexported_unused_lld_things(&mut module);
|
||||||
|
|
||||||
// Here we're actually instantiating the module we've parsed above for
|
// We're making quite a few changes, list ourselves as a producer.
|
||||||
// execution. Why, you might be asking, are we executing wasm code? A
|
module
|
||||||
// good question!
|
.producers
|
||||||
//
|
.add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
|
||||||
// Transmitting information from `#[wasm_bindgen]` here to the CLI tool
|
|
||||||
// is pretty tricky. Specifically information about the types involved
|
|
||||||
// with a function signature (especially generic ones) can be hefty to
|
|
||||||
// translate over. As a result, the macro emits a bunch of shims which,
|
|
||||||
// when executed, will describe to us what the types look like.
|
|
||||||
//
|
|
||||||
// This means that whenever we encounter an import or export we'll
|
|
||||||
// execute a shim function which informs us about its type so we can
|
|
||||||
// then generate the appropriate bindings.
|
|
||||||
let mut instance = wasm_bindgen_wasm_interpreter::Interpreter::new(&module)?;
|
|
||||||
|
|
||||||
let mut memories = module.memories.iter().map(|m| m.id());
|
// Learn about the type signatures of all wasm-bindgen imports and
|
||||||
let memory = memories.next();
|
// exports by executing `__wbindgen_describe_*` functions. This'll
|
||||||
if memories.next().is_some() {
|
// effectively move all the descriptor functions to their own custom
|
||||||
bail!("multiple memories currently not supported");
|
// sections.
|
||||||
|
descriptors::execute(&mut module)?;
|
||||||
|
|
||||||
|
// 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(&mut module)?;
|
||||||
|
|
||||||
|
// Now that we've got type information from the webidl processing pass,
|
||||||
|
// touch up the output of rustc to insert anyref shims where necessary.
|
||||||
|
// This is only done if the anyref pass is enabled, which it's
|
||||||
|
// currently off-by-default since `anyref` is still in development in
|
||||||
|
// engines.
|
||||||
|
if self.anyref {
|
||||||
|
anyref::process(&mut module)?;
|
||||||
}
|
}
|
||||||
drop(memories);
|
|
||||||
let memory = memory.unwrap_or_else(|| module.memories.add_local(false, 1, None));
|
|
||||||
|
|
||||||
|
// If we're in a testing mode then remove the start function since we
|
||||||
|
// shouldn't execute it.
|
||||||
|
if !self.emit_start {
|
||||||
|
module.start = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (js, ts) = {
|
let (js, ts) = {
|
||||||
let mut cx = js::Context {
|
let mut cx = js::Context::new(&mut module, self)?;
|
||||||
globals: String::new(),
|
|
||||||
imports: String::new(),
|
|
||||||
imports_post: String::new(),
|
|
||||||
footer: String::new(),
|
|
||||||
typescript: format!("/* tslint:disable */\n"),
|
|
||||||
exposed_globals: Some(Default::default()),
|
|
||||||
required_internal_exports: Default::default(),
|
|
||||||
imported_names: Default::default(),
|
|
||||||
defined_identifiers: Default::default(),
|
|
||||||
exported_classes: Some(Default::default()),
|
|
||||||
config: &self,
|
|
||||||
module: &mut module,
|
|
||||||
function_table_needed: false,
|
|
||||||
interpreter: &mut instance,
|
|
||||||
memory,
|
|
||||||
imported_functions: Default::default(),
|
|
||||||
imported_statics: Default::default(),
|
|
||||||
direct_imports: Default::default(),
|
|
||||||
local_modules: Default::default(),
|
|
||||||
start: None,
|
|
||||||
anyref: Default::default(),
|
|
||||||
snippet_offsets: Default::default(),
|
|
||||||
npm_dependencies: Default::default(),
|
|
||||||
package_json_read: Default::default(),
|
|
||||||
};
|
|
||||||
cx.anyref.enabled = self.anyref;
|
|
||||||
cx.anyref.prepare(cx.module)?;
|
|
||||||
for program in programs.iter() {
|
|
||||||
js::SubContext {
|
|
||||||
program,
|
|
||||||
cx: &mut cx,
|
|
||||||
vendor_prefixes: Default::default(),
|
|
||||||
}
|
|
||||||
.generate()?;
|
|
||||||
|
|
||||||
let offset = cx
|
let aux = cx.module.customs.delete_typed::<webidl::WasmBindgenAux>()
|
||||||
.snippet_offsets
|
.expect("aux section should be present");
|
||||||
.entry(program.unique_crate_identifier)
|
cx.generate(&aux)?;
|
||||||
.or_insert(0);
|
|
||||||
for js in program.inline_js.iter() {
|
// Write out all local JS snippets to the final destination now that
|
||||||
let name = format!("inline{}.js", *offset);
|
// we've collected them from all the programs.
|
||||||
*offset += 1;
|
for (identifier, list) in aux.snippets.iter() {
|
||||||
|
for (i, js) in list.iter().enumerate() {
|
||||||
|
let name = format!("inline{}.js", i);
|
||||||
let path = out_dir
|
let path = out_dir
|
||||||
.join("snippets")
|
.join("snippets")
|
||||||
.join(program.unique_crate_identifier)
|
.join(identifier)
|
||||||
.join(name);
|
.join(name);
|
||||||
fs::create_dir_all(path.parent().unwrap())?;
|
fs::create_dir_all(path.parent().unwrap())?;
|
||||||
fs::write(&path, js)
|
fs::write(&path, js)
|
||||||
.with_context(|_| format!("failed to write `{}`", path.display()))?;
|
.with_context(|_| format!("failed to write `{}`", path.display()))?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
for (path, contents) in aux.local_modules.iter() {
|
||||||
// Write out all local JS snippets to the final destination now that
|
|
||||||
// we've collected them from all the programs.
|
|
||||||
for (path, contents) in cx.local_modules.iter() {
|
|
||||||
let path = out_dir.join("snippets").join(path);
|
let path = out_dir.join("snippets").join(path);
|
||||||
fs::create_dir_all(path.parent().unwrap())?;
|
fs::create_dir_all(path.parent().unwrap())?;
|
||||||
fs::write(&path, contents)
|
fs::write(&path, contents)
|
||||||
@ -394,6 +375,8 @@ impl Bindgen {
|
|||||||
cx.finalize(stem)?
|
cx.finalize(stem)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// And now that we've got all our JS and TypeScript, actually write it
|
||||||
|
// out to the filesystem.
|
||||||
let extension = if self.mode.nodejs_experimental_modules() {
|
let extension = if self.mode.nodejs_experimental_modules() {
|
||||||
"mjs"
|
"mjs"
|
||||||
} else {
|
} else {
|
||||||
@ -503,127 +486,6 @@ impl Bindgen {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn extract_programs<'a>(
|
|
||||||
module: &mut Module,
|
|
||||||
program_storage: &'a mut Vec<Vec<u8>>,
|
|
||||||
) -> Result<Vec<decode::Program<'a>>, Error> {
|
|
||||||
let my_version = wasm_bindgen_shared::version();
|
|
||||||
assert!(program_storage.is_empty());
|
|
||||||
|
|
||||||
while let Some(raw) = module.customs.remove_raw("__wasm_bindgen_unstable") {
|
|
||||||
log::debug!(
|
|
||||||
"custom section '{}' looks like a wasm bindgen section",
|
|
||||||
raw.name
|
|
||||||
);
|
|
||||||
program_storage.push(raw.data);
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut ret = Vec::new();
|
|
||||||
for program in program_storage.iter() {
|
|
||||||
let mut payload = &program[..];
|
|
||||||
while let Some(data) = get_remaining(&mut payload) {
|
|
||||||
// Historical versions of wasm-bindgen have used JSON as the custom
|
|
||||||
// data section format. Newer versions, however, are using a custom
|
|
||||||
// serialization protocol that looks much more like the wasm spec.
|
|
||||||
//
|
|
||||||
// We, however, want a sanity check to ensure that if we're running
|
|
||||||
// against the wrong wasm-bindgen we get a nicer error than an
|
|
||||||
// internal decode error. To that end we continue to verify a tiny
|
|
||||||
// bit of json at the beginning of each blob before moving to the
|
|
||||||
// next blob. This should keep us compatible with older wasm-bindgen
|
|
||||||
// instances as well as forward-compatible for now.
|
|
||||||
//
|
|
||||||
// Note, though, that as `wasm-pack` picks up steam it's hoped we
|
|
||||||
// can just delete this entirely. The `wasm-pack` project already
|
|
||||||
// manages versions for us, so we in theory should need this check
|
|
||||||
// less and less over time.
|
|
||||||
if let Some(their_version) = verify_schema_matches(data)? {
|
|
||||||
bail!(
|
|
||||||
"
|
|
||||||
|
|
||||||
it looks like the Rust project used to create this wasm file was linked against
|
|
||||||
a different version of wasm-bindgen than this binary:
|
|
||||||
|
|
||||||
rust wasm file: {}
|
|
||||||
this binary: {}
|
|
||||||
|
|
||||||
Currently the bindgen format is unstable enough that these two version must
|
|
||||||
exactly match, so it's required that these two version are kept in sync by
|
|
||||||
either updating the wasm-bindgen dependency or this binary. You should be able
|
|
||||||
to update the wasm-bindgen dependency with:
|
|
||||||
|
|
||||||
cargo update -p wasm-bindgen
|
|
||||||
|
|
||||||
or you can update the binary with
|
|
||||||
|
|
||||||
cargo install -f wasm-bindgen-cli
|
|
||||||
|
|
||||||
if this warning fails to go away though and you're not sure what to do feel free
|
|
||||||
to open an issue at https://github.com/rustwasm/wasm-bindgen/issues!
|
|
||||||
",
|
|
||||||
their_version,
|
|
||||||
my_version,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
let next = get_remaining(&mut payload).unwrap();
|
|
||||||
log::debug!("found a program of length {}", next.len());
|
|
||||||
ret.push(<decode::Program as decode::Decode>::decode_all(next));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(ret)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_remaining<'a>(data: &mut &'a [u8]) -> Option<&'a [u8]> {
|
|
||||||
if data.len() == 0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
let len = ((data[0] as usize) << 0)
|
|
||||||
| ((data[1] as usize) << 8)
|
|
||||||
| ((data[2] as usize) << 16)
|
|
||||||
| ((data[3] as usize) << 24);
|
|
||||||
let (a, b) = data[4..].split_at(len);
|
|
||||||
*data = b;
|
|
||||||
Some(a)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn verify_schema_matches<'a>(data: &'a [u8]) -> Result<Option<&'a str>, Error> {
|
|
||||||
macro_rules! bad {
|
|
||||||
() => {
|
|
||||||
bail!("failed to decode what looked like wasm-bindgen data")
|
|
||||||
};
|
|
||||||
}
|
|
||||||
let data = match str::from_utf8(data) {
|
|
||||||
Ok(s) => s,
|
|
||||||
Err(_) => bad!(),
|
|
||||||
};
|
|
||||||
log::debug!("found version specifier {}", data);
|
|
||||||
if !data.starts_with("{") || !data.ends_with("}") {
|
|
||||||
bad!()
|
|
||||||
}
|
|
||||||
let needle = "\"schema_version\":\"";
|
|
||||||
let rest = match data.find(needle) {
|
|
||||||
Some(i) => &data[i + needle.len()..],
|
|
||||||
None => bad!(),
|
|
||||||
};
|
|
||||||
let their_schema_version = match rest.find("\"") {
|
|
||||||
Some(i) => &rest[..i],
|
|
||||||
None => bad!(),
|
|
||||||
};
|
|
||||||
if their_schema_version == wasm_bindgen_shared::SCHEMA_VERSION {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
let needle = "\"version\":\"";
|
|
||||||
let rest = match data.find(needle) {
|
|
||||||
Some(i) => &data[i + needle.len()..],
|
|
||||||
None => bad!(),
|
|
||||||
};
|
|
||||||
let their_version = match rest.find("\"") {
|
|
||||||
Some(i) => &rest[..i],
|
|
||||||
None => bad!(),
|
|
||||||
};
|
|
||||||
Ok(Some(their_version))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn reset_indentation(s: &str) -> String {
|
fn reset_indentation(s: &str) -> String {
|
||||||
let mut indent: u32 = 0;
|
let mut indent: u32 = 0;
|
||||||
let mut dst = String::new();
|
let mut dst = String::new();
|
||||||
@ -728,3 +590,21 @@ impl OutputMode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Remove a number of internal exports that are synthesized by Rust's linker,
|
||||||
|
/// LLD. These exports aren't typically ever needed and just add extra space to
|
||||||
|
/// the binary.
|
||||||
|
fn unexported_unused_lld_things(module: &mut Module) {
|
||||||
|
let mut to_remove = Vec::new();
|
||||||
|
for export in module.exports.iter() {
|
||||||
|
match export.name.as_str() {
|
||||||
|
"__heap_base" | "__data_end" | "__indirect_function_table" => {
|
||||||
|
to_remove.push(export.id());
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for id in to_remove {
|
||||||
|
module.exports.delete(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
1289
crates/cli-support/src/webidl.rs
Normal file
1289
crates/cli-support/src/webidl.rs
Normal file
File diff suppressed because it is too large
Load Diff
@ -24,7 +24,7 @@ rouille = { version = "3.0.0", default-features = false }
|
|||||||
serde = { version = "1.0", features = ['derive'] }
|
serde = { version = "1.0", features = ['derive'] }
|
||||||
serde_derive = "1.0"
|
serde_derive = "1.0"
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
walrus = "0.7.0"
|
walrus = "0.8.0"
|
||||||
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.45" }
|
wasm-bindgen-cli-support = { path = "../cli-support", version = "=0.2.45" }
|
||||||
wasm-bindgen-shared = { path = "../shared", version = "=0.2.45" }
|
wasm-bindgen-shared = { path = "../shared", version = "=0.2.45" }
|
||||||
|
|
||||||
|
@ -71,7 +71,7 @@ fn rmain() -> Result<(), Error> {
|
|||||||
// that any exported function with the prefix `__wbg_test` is a test we need
|
// that any exported function with the prefix `__wbg_test` is a test we need
|
||||||
// to execute.
|
// to execute.
|
||||||
let wasm = fs::read(&wasm_file_to_test).context("failed to read wasm file")?;
|
let wasm = fs::read(&wasm_file_to_test).context("failed to read wasm file")?;
|
||||||
let wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?;
|
let mut wasm = walrus::Module::from_buffer(&wasm).context("failed to deserialize wasm module")?;
|
||||||
let mut tests = Vec::new();
|
let mut tests = Vec::new();
|
||||||
|
|
||||||
for export in wasm.exports.iter() {
|
for export in wasm.exports.iter() {
|
||||||
@ -94,11 +94,8 @@ fn rmain() -> Result<(), Error> {
|
|||||||
// `wasm_bindgen_test_configure` macro, which emits a custom section for us
|
// `wasm_bindgen_test_configure` macro, which emits a custom section for us
|
||||||
// to read later on.
|
// to read later on.
|
||||||
let mut node = true;
|
let mut node = true;
|
||||||
for (_id, custom) in wasm.customs.iter() {
|
if let Some(section) = wasm.customs.remove_raw("__wasm_bindgen_test_unstable") {
|
||||||
if custom.name() != "__wasm_bindgen_test_unstable" {
|
node = !section.data.contains(&0x01);
|
||||||
continue;
|
|
||||||
}
|
|
||||||
node = !custom.data().contains(&0x01);
|
|
||||||
}
|
}
|
||||||
let headless = env::var("NO_HEADLESS").is_err();
|
let headless = env::var("NO_HEADLESS").is_err();
|
||||||
let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err();
|
let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err();
|
||||||
|
@ -22,10 +22,10 @@ pub fn execute(
|
|||||||
const og = console[method];
|
const og = console[method];
|
||||||
const on_method = `on_console_${{method}}`;
|
const on_method = `on_console_${{method}}`;
|
||||||
console[method] = function (...args) {{
|
console[method] = function (...args) {{
|
||||||
|
og.apply(this, args);
|
||||||
if (handlers[on_method]) {{
|
if (handlers[on_method]) {{
|
||||||
handlers[on_method](args);
|
handlers[on_method](args);
|
||||||
}}
|
}}
|
||||||
og.apply(this, args);
|
|
||||||
}};
|
}};
|
||||||
}};
|
}};
|
||||||
|
|
||||||
|
@ -22,10 +22,15 @@ fn no_modules_rejects_npm() {
|
|||||||
.file("package.json", "")
|
.file("package.json", "")
|
||||||
.wasm_bindgen("--no-modules");
|
.wasm_bindgen("--no-modules");
|
||||||
cmd.assert()
|
cmd.assert()
|
||||||
.stderr("\
|
.stderr(
|
||||||
error: failed to generate bindings for JS import `foo`
|
str::is_match(
|
||||||
caused by: import from `foo` module not allowed with `--target no-modules`; use `nodejs`, `web`, or `bundler` target instead
|
"\
|
||||||
")
|
error: NPM dependencies have been specified in `.*` but this is only \
|
||||||
|
compatible with the `bundler` and `nodejs` targets
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
.failure();
|
.failure();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,4 +13,4 @@ edition = "2018"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
walrus = "0.7.0"
|
walrus = "0.8.0"
|
||||||
|
@ -14,7 +14,7 @@ edition = '2018'
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
failure = "0.1"
|
failure = "0.1"
|
||||||
log = "0.4"
|
log = "0.4"
|
||||||
walrus = "0.7.0"
|
walrus = "0.8.0"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile = "3"
|
tempfile = "3"
|
||||||
|
@ -127,12 +127,7 @@ impl Interpreter {
|
|||||||
///
|
///
|
||||||
/// Returns `Some` if `func` was found in the `module` and `None` if it was
|
/// Returns `Some` if `func` was found in the `module` and `None` if it was
|
||||||
/// not found in the `module`.
|
/// not found in the `module`.
|
||||||
pub fn interpret_descriptor(&mut self, func: &str, module: &Module) -> Option<&[u32]> {
|
pub fn interpret_descriptor(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> {
|
||||||
let id = *self.name_map.get(func)?;
|
|
||||||
self.interpret_descriptor_id(id, module)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn interpret_descriptor_id(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> {
|
|
||||||
self.descriptor.truncate(0);
|
self.descriptor.truncate(0);
|
||||||
|
|
||||||
// We should have a blank wasm and LLVM stack at both the start and end
|
// We should have a blank wasm and LLVM stack at both the start and end
|
||||||
@ -212,7 +207,7 @@ impl Interpreter {
|
|||||||
entry_removal_list.insert(descriptor_table_idx);
|
entry_removal_list.insert(descriptor_table_idx);
|
||||||
|
|
||||||
// And now execute the descriptor!
|
// And now execute the descriptor!
|
||||||
self.interpret_descriptor_id(descriptor_id, module)
|
self.interpret_descriptor(descriptor_id, module)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the function id of the `__wbindgen_describe_closure`
|
/// Returns the function id of the `__wbindgen_describe_closure`
|
||||||
|
@ -17,7 +17,17 @@ fn interpret(wat: &str, name: &str, result: Option<&[u32]>) {
|
|||||||
assert!(status.success());
|
assert!(status.success());
|
||||||
let module = walrus::Module::from_file(output.path()).unwrap();
|
let module = walrus::Module::from_file(output.path()).unwrap();
|
||||||
let mut i = Interpreter::new(&module).unwrap();
|
let mut i = Interpreter::new(&module).unwrap();
|
||||||
assert_eq!(i.interpret_descriptor(name, &module), result);
|
let id = module
|
||||||
|
.exports
|
||||||
|
.iter()
|
||||||
|
.filter(|e| e.name == name)
|
||||||
|
.filter_map(|e| match e.item {
|
||||||
|
walrus::ExportItem::Function(f) => Some(f),
|
||||||
|
_ => None,
|
||||||
|
})
|
||||||
|
.next()
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(i.interpret_descriptor(id, &module), result);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -30,7 +40,6 @@ fn smoke() {
|
|||||||
)
|
)
|
||||||
"#;
|
"#;
|
||||||
interpret(wat, "foo", Some(&[]));
|
interpret(wat, "foo", Some(&[]));
|
||||||
interpret(wat, "bar", None);
|
|
||||||
|
|
||||||
let wat = r#"
|
let wat = r#"
|
||||||
(module
|
(module
|
||||||
|
@ -116,6 +116,6 @@ function render(scene) {
|
|||||||
rendering.stop();
|
rendering.stop();
|
||||||
rendering = null;
|
rendering = null;
|
||||||
}
|
}
|
||||||
rendering = new State(scene.render(concurrency.value, pool, ctx));
|
rendering = new State(scene.render(parseInt(concurrency.value), pool, ctx));
|
||||||
pool = null; // previous call took ownership of `pool`, zero it out here too
|
pool = null; // previous call took ownership of `pool`, zero it out here too
|
||||||
}
|
}
|
||||||
|
@ -109,7 +109,7 @@ impl Slab {
|
|||||||
None => internal_error("slot out of bounds"),
|
None => internal_error("slot out of bounds"),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
self.data.len() as u32 - free_count - super::JSIDX_RESERVED
|
self.data.len() as u32 - free_count
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
38
src/lib.rs
38
src/lib.rs
@ -13,7 +13,6 @@ use core::fmt;
|
|||||||
use core::marker;
|
use core::marker;
|
||||||
use core::mem;
|
use core::mem;
|
||||||
use core::ops::{Deref, DerefMut};
|
use core::ops::{Deref, DerefMut};
|
||||||
use core::ptr;
|
|
||||||
|
|
||||||
use crate::convert::FromWasmAbi;
|
use crate::convert::FromWasmAbi;
|
||||||
|
|
||||||
@ -171,9 +170,13 @@ impl JsValue {
|
|||||||
/// JS object corresponding to the symbol created.
|
/// JS object corresponding to the symbol created.
|
||||||
pub fn symbol(description: Option<&str>) -> JsValue {
|
pub fn symbol(description: Option<&str>) -> JsValue {
|
||||||
unsafe {
|
unsafe {
|
||||||
let ptr = description.map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
match description {
|
||||||
let len = description.map(|s| s.len()).unwrap_or(0);
|
Some(description) => JsValue::_new(__wbindgen_symbol_named_new(
|
||||||
JsValue::_new(__wbindgen_symbol_new(ptr, len))
|
description.as_ptr(),
|
||||||
|
description.len(),
|
||||||
|
)),
|
||||||
|
None => JsValue::_new(__wbindgen_symbol_anonymous_new()),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,9 +223,9 @@ impl JsValue {
|
|||||||
T: for<'a> serde::de::Deserialize<'a>,
|
T: for<'a> serde::de::Deserialize<'a>,
|
||||||
{
|
{
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut ptr = ptr::null_mut();
|
let mut ret = [0usize; 2];
|
||||||
let len = __wbindgen_json_serialize(self.idx, &mut ptr);
|
__wbindgen_json_serialize(&mut ret, self.idx);
|
||||||
let s = Vec::from_raw_parts(ptr, len, len);
|
let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||||
let s = String::from_utf8_unchecked(s);
|
let s = String::from_utf8_unchecked(s);
|
||||||
serde_json::from_str(&s)
|
serde_json::from_str(&s)
|
||||||
}
|
}
|
||||||
@ -333,14 +336,10 @@ impl JsValue {
|
|||||||
#[cfg(feature = "std")]
|
#[cfg(feature = "std")]
|
||||||
fn as_debug_string(&self) -> String {
|
fn as_debug_string(&self) -> String {
|
||||||
unsafe {
|
unsafe {
|
||||||
let mut len = 0;
|
let mut ret = [0; 2];
|
||||||
let ptr = __wbindgen_debug_string(self.idx, &mut len);
|
__wbindgen_debug_string(&mut ret, self.idx);
|
||||||
if ptr.is_null() {
|
let data = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||||
unreachable!("`__wbindgen_debug_string` must return a valid string")
|
String::from_utf8_unchecked(data)
|
||||||
} else {
|
|
||||||
let data = Vec::from_raw_parts(ptr, len, len);
|
|
||||||
String::from_utf8_unchecked(data)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -492,7 +491,8 @@ externs! {
|
|||||||
|
|
||||||
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
|
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
|
||||||
fn __wbindgen_number_new(f: f64) -> u32;
|
fn __wbindgen_number_new(f: f64) -> u32;
|
||||||
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
|
fn __wbindgen_symbol_named_new(ptr: *const u8, len: usize) -> u32;
|
||||||
|
fn __wbindgen_symbol_anonymous_new() -> u32;
|
||||||
|
|
||||||
fn __wbindgen_anyref_heap_live_count() -> u32;
|
fn __wbindgen_anyref_heap_live_count() -> u32;
|
||||||
|
|
||||||
@ -507,7 +507,7 @@ externs! {
|
|||||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||||
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
|
fn __wbindgen_string_get(idx: u32, len: *mut usize) -> *mut u8;
|
||||||
|
|
||||||
fn __wbindgen_debug_string(idx: u32, len: *mut usize) -> *mut u8;
|
fn __wbindgen_debug_string(ret: *mut [usize; 2], idx: u32) -> ();
|
||||||
|
|
||||||
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
fn __wbindgen_throw(a: *const u8, b: usize) -> !;
|
||||||
fn __wbindgen_rethrow(a: u32) -> !;
|
fn __wbindgen_rethrow(a: u32) -> !;
|
||||||
@ -519,7 +519,7 @@ externs! {
|
|||||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> 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_parse(ptr: *const u8, len: usize) -> u32;
|
||||||
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
|
fn __wbindgen_json_serialize(ret: *mut [usize; 2], idx: u32) -> ();
|
||||||
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
|
fn __wbindgen_jsval_eq(a: u32, b: u32) -> u32;
|
||||||
|
|
||||||
fn __wbindgen_memory() -> u32;
|
fn __wbindgen_memory() -> u32;
|
||||||
@ -557,7 +557,7 @@ impl Drop for JsValue {
|
|||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
unsafe {
|
unsafe {
|
||||||
// We definitely should never drop anything in the stack area
|
// We definitely should never drop anything in the stack area
|
||||||
debug_assert!(self.idx >= JSIDX_OFFSET);
|
debug_assert!(self.idx >= JSIDX_OFFSET, "free of stack slot {}", self.idx);
|
||||||
|
|
||||||
// Otherwise if we're not dropping one of our reserved values,
|
// Otherwise if we're not dropping one of our reserved values,
|
||||||
// actually call the intrinsic. See #1054 for eventually removing
|
// actually call the intrinsic. See #1054 for eventually removing
|
||||||
|
@ -126,3 +126,13 @@ exports.pass_reference_first_arg_twice = (a, b, c) => {
|
|||||||
exports.call_destroyed = f => {
|
exports.call_destroyed = f => {
|
||||||
assert.throws(f, /invoked recursively or destroyed/);
|
assert.throws(f, /invoked recursively or destroyed/);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let FORGOTTEN_CLOSURE = null;
|
||||||
|
|
||||||
|
exports.js_store_forgotten_closure = f => {
|
||||||
|
FORGOTTEN_CLOSURE = f;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.js_call_forgotten_closure = () => {
|
||||||
|
FORGOTTEN_CLOSURE();
|
||||||
|
};
|
||||||
|
@ -108,6 +108,9 @@ extern "C" {
|
|||||||
c: &mut FnMut(&RefFirstArgument),
|
c: &mut FnMut(&RefFirstArgument),
|
||||||
);
|
);
|
||||||
fn call_destroyed(a: &JsValue);
|
fn call_destroyed(a: &JsValue);
|
||||||
|
|
||||||
|
fn js_store_forgotten_closure(closure: &Closure<Fn()>);
|
||||||
|
fn js_call_forgotten_closure();
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
@ -573,3 +576,11 @@ fn call_destroyed_doesnt_segfault() {
|
|||||||
drop(a);
|
drop(a);
|
||||||
call_destroyed(&b);
|
call_destroyed(&b);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn forget_works() {
|
||||||
|
let a = Closure::wrap(Box::new(|| {}) as Box<Fn()>);
|
||||||
|
js_store_forgotten_closure(&a);
|
||||||
|
a.forget();
|
||||||
|
js_call_forgotten_closure();
|
||||||
|
}
|
||||||
|
@ -139,6 +139,7 @@ fn rename_type() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
|
#[cfg(ignored)] // TODO: fix this before landing
|
||||||
fn switch_methods() {
|
fn switch_methods() {
|
||||||
assert!(!switch_methods_called());
|
assert!(!switch_methods_called());
|
||||||
SwitchMethods::a();
|
SwitchMethods::a();
|
||||||
@ -158,7 +159,7 @@ fn switch_methods() {
|
|||||||
|
|
||||||
assert!(!switch_methods_called());
|
assert!(!switch_methods_called());
|
||||||
SwitchMethods::new().b();
|
SwitchMethods::new().b();
|
||||||
assert!(switch_methods_called());
|
assert!(!switch_methods_called());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[wasm_bindgen_test]
|
#[wasm_bindgen_test]
|
||||||
|
@ -213,3 +213,12 @@ fn string_roundtrip() {
|
|||||||
pub fn do_string_roundtrip(s: String) -> String {
|
pub fn do_string_roundtrip(s: String) -> String {
|
||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen_test]
|
||||||
|
fn anyref_heap_live_count() {
|
||||||
|
let x = wasm_bindgen::anyref_heap_live_count();
|
||||||
|
let y = JsValue::null().clone();
|
||||||
|
assert!(wasm_bindgen::anyref_heap_live_count() > x);
|
||||||
|
drop(y);
|
||||||
|
assert_eq!(x, wasm_bindgen::anyref_heap_live_count());
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user