mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +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:
|
||||
- template: ci/azure-install-rust.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 webidl-tests --target wasm32-unknown-unknown
|
||||
env:
|
||||
@ -128,7 +128,7 @@ jobs:
|
||||
cd wabt/build
|
||||
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTS=off -DCMAKE_CXX_COMPILER_LAUNCHER=$RUSTC_WRAPPER
|
||||
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
|
||||
|
||||
- job: test_typescript_output
|
||||
@ -171,7 +171,7 @@ jobs:
|
||||
- script: |
|
||||
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 -
|
||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$PWD"
|
||||
echo "##vso[task.prependpath]$PWD"
|
||||
displayName: "install xargo"
|
||||
- script: |
|
||||
set -e
|
||||
|
@ -3,17 +3,20 @@ parameters:
|
||||
|
||||
steps:
|
||||
- bash: |
|
||||
curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain $TOOLCHAIN
|
||||
echo "##vso[task.setvariable variable=PATH;]$PATH:$HOME/.cargo/bin"
|
||||
set -e
|
||||
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
|
||||
condition: ne( variables['Agent.OS'], 'Windows_NT' )
|
||||
env:
|
||||
TOOLCHAIN: ${{ parameters.toolchain }}
|
||||
|
||||
- script: |
|
||||
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"
|
||||
- bash: rustup update --no-self-update $TOOLCHAIN && rustup default $TOOLCHAIN
|
||||
displayName: Install rust - Windows
|
||||
condition: eq( variables['Agent.OS'], 'Windows_NT' )
|
||||
env:
|
||||
|
@ -4,5 +4,5 @@ steps:
|
||||
- script: |
|
||||
set -ex
|
||||
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"
|
||||
|
@ -13,4 +13,4 @@ edition = '2018'
|
||||
|
||||
[dependencies]
|
||||
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::mem;
|
||||
use walrus::ir::*;
|
||||
use walrus::{ExportId, ImportId};
|
||||
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
|
||||
|
||||
// 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
|
||||
// type. The `Function` contains information about what arguments/return
|
||||
// values in the function signature should turn into anyref.
|
||||
imports: HashMap<String, HashMap<String, Function>>,
|
||||
exports: HashMap<String, Function>,
|
||||
imports: HashMap<ImportId, Function>,
|
||||
exports: HashMap<ExportId, Function>,
|
||||
elements: BTreeMap<u32, (u32, Function)>,
|
||||
|
||||
// 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
|
||||
table: Option<TableId>,
|
||||
|
||||
// Whether or not the transformation will actually be run at the end
|
||||
pub enabled: bool,
|
||||
}
|
||||
|
||||
struct Transform<'a> {
|
||||
@ -68,7 +66,6 @@ struct Transform<'a> {
|
||||
}
|
||||
|
||||
struct Function {
|
||||
name: String,
|
||||
// A map of argument index to whether it's an owned or borrowed anyref
|
||||
// (owned = true)
|
||||
args: HashMap<usize, bool>,
|
||||
@ -87,10 +84,6 @@ impl Context {
|
||||
/// large the function table is so we know what indexes to hand out when
|
||||
/// we're appending entries.
|
||||
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
|
||||
// be adding new entries to the function table later (maybe) so
|
||||
// precalculate this ahead of time.
|
||||
@ -118,19 +111,13 @@ impl Context {
|
||||
/// transformed. The actual transformation happens later during `run`.
|
||||
pub fn import_xform(
|
||||
&mut self,
|
||||
module: &str,
|
||||
name: &str,
|
||||
id: ImportId,
|
||||
anyref: &[(usize, bool)],
|
||||
ret_anyref: bool,
|
||||
) -> &mut Self {
|
||||
if !self.enabled {
|
||||
return self;
|
||||
if let Some(f) = self.function(anyref, ret_anyref) {
|
||||
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
|
||||
}
|
||||
|
||||
@ -138,15 +125,13 @@ impl Context {
|
||||
/// transformed. The actual transformation happens later during `run`.
|
||||
pub fn export_xform(
|
||||
&mut self,
|
||||
name: &str,
|
||||
id: ExportId,
|
||||
anyref: &[(usize, bool)],
|
||||
ret_anyref: bool,
|
||||
) -> &mut Self {
|
||||
if !self.enabled {
|
||||
return self;
|
||||
if let Some(f) = self.function(anyref, ret_anyref) {
|
||||
self.exports.insert(id, f);
|
||||
}
|
||||
let f = self.function(name, anyref, ret_anyref);
|
||||
self.exports.insert(name.to_string(), f);
|
||||
self
|
||||
}
|
||||
|
||||
@ -158,34 +143,26 @@ impl Context {
|
||||
idx: u32,
|
||||
anyref: &[(usize, bool)],
|
||||
ret_anyref: bool,
|
||||
) -> u32 {
|
||||
if !self.enabled {
|
||||
return idx;
|
||||
}
|
||||
let name = format!("closure{}", idx);
|
||||
let f = self.function(&name, anyref, ret_anyref);
|
||||
let ret = self.next_element;
|
||||
self.next_element += 1;
|
||||
self.elements.insert(ret, (idx, f));
|
||||
ret
|
||||
) -> Option<u32> {
|
||||
self.function(anyref, ret_anyref).map(|f| {
|
||||
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 {
|
||||
Function {
|
||||
name: name.to_string(),
|
||||
fn function(&self, anyref: &[(usize, bool)], ret_anyref: bool) -> Option<Function> {
|
||||
if !ret_anyref && anyref.len() == 0 {
|
||||
return None;
|
||||
}
|
||||
Some(Function {
|
||||
args: anyref.iter().cloned().collect(),
|
||||
ret_anyref,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn anyref_table_id(&self) -> TableId {
|
||||
self.table.unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||
if !self.enabled {
|
||||
return Ok(());
|
||||
}
|
||||
let table = self.table.unwrap();
|
||||
|
||||
// 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.
|
||||
self.process_imports(module);
|
||||
for m in self.cx.imports.values() {
|
||||
assert!(m.is_empty());
|
||||
}
|
||||
assert!(self.cx.imports.is_empty());
|
||||
self.process_exports(module);
|
||||
assert!(self.cx.exports.is_empty());
|
||||
self.process_elements(module)?;
|
||||
@ -333,20 +308,15 @@ impl Transform<'_> {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
let import = {
|
||||
let entries = match self.cx.imports.get_mut(&import.module) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
match entries.remove(&import.name) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
}
|
||||
let func = match self.cx.imports.remove(&import.id()) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
|
||||
let shim = self.append_shim(
|
||||
f,
|
||||
import,
|
||||
&import.name,
|
||||
func,
|
||||
&mut module.types,
|
||||
&mut module.funcs,
|
||||
&mut module.locals,
|
||||
@ -356,29 +326,25 @@ impl Transform<'_> {
|
||||
}
|
||||
|
||||
fn process_exports(&mut self, module: &mut Module) {
|
||||
let mut new_exports = Vec::new();
|
||||
for export in module.exports.iter() {
|
||||
// let mut new_exports = Vec::new();
|
||||
for export in module.exports.iter_mut() {
|
||||
let f = match export.item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
let function = match self.cx.exports.remove(&export.name) {
|
||||
let function = match self.cx.exports.remove(&export.id()) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let shim = self.append_shim(
|
||||
f,
|
||||
&export.name,
|
||||
function,
|
||||
&mut module.types,
|
||||
&mut module.funcs,
|
||||
&mut module.locals,
|
||||
);
|
||||
new_exports.push((export.name.to_string(), shim, export.id()));
|
||||
}
|
||||
|
||||
for (name, shim, old_id) in new_exports {
|
||||
module.exports.add(&name, shim);
|
||||
module.exports.delete(old_id);
|
||||
export.item = shim.into();
|
||||
}
|
||||
}
|
||||
|
||||
@ -402,6 +368,7 @@ impl Transform<'_> {
|
||||
let target = kind.elements[idx as usize].unwrap();
|
||||
let shim = self.append_shim(
|
||||
target,
|
||||
&format!("closure{}", idx),
|
||||
function,
|
||||
&mut module.types,
|
||||
&mut module.funcs,
|
||||
@ -422,6 +389,7 @@ impl Transform<'_> {
|
||||
fn append_shim(
|
||||
&mut self,
|
||||
shim_target: FunctionId,
|
||||
name: &str,
|
||||
mut func: Function,
|
||||
types: &mut walrus::ModuleTypes,
|
||||
funcs: &mut walrus::ModuleFunctions,
|
||||
@ -625,7 +593,7 @@ impl Transform<'_> {
|
||||
// nice name for debugging and then we're good to go!
|
||||
let expr = builder.with_side_effects(before, result, after);
|
||||
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);
|
||||
self.shims.insert(id);
|
||||
return id;
|
||||
|
@ -18,7 +18,7 @@ log = "0.4"
|
||||
rustc-demangle = "0.1.13"
|
||||
serde_json = "1.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-shared = { path = "../shared", 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
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Descriptor {
|
||||
I8,
|
||||
U8,
|
||||
@ -67,14 +67,14 @@ pub enum Descriptor {
|
||||
Clamped(Box<Descriptor>),
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Function {
|
||||
pub arguments: Vec<Descriptor>,
|
||||
pub shim_idx: u32,
|
||||
pub ret: Descriptor,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Closure {
|
||||
pub shim_idx: u32,
|
||||
pub dtor_idx: u32,
|
||||
@ -146,9 +146,9 @@ impl Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unwrap_function(&self) -> &Function {
|
||||
match *self {
|
||||
Descriptor::Function(ref f) => f,
|
||||
pub fn unwrap_function(self) -> Function {
|
||||
match self {
|
||||
Descriptor::Function(f) => *f,
|
||||
_ => panic!("not a function"),
|
||||
}
|
||||
}
|
||||
@ -199,10 +199,10 @@ impl Descriptor {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn closure(&self) -> Option<&Closure> {
|
||||
match *self {
|
||||
Descriptor::Closure(ref s) => Some(s),
|
||||
_ => None,
|
||||
pub fn unwrap_closure(self) -> Closure {
|
||||
match self {
|
||||
Descriptor::Closure(s) => *s,
|
||||
_ => panic!("not a closure"),
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,6 +292,83 @@ impl Descriptor {
|
||||
_ => 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 {
|
||||
|
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
|
||||
/// function.
|
||||
ret_ty: String,
|
||||
pub ret_ty: String,
|
||||
|
||||
/// Expression used to generate the return value. The string "RET" in this
|
||||
/// 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
|
||||
/// for.
|
||||
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> {
|
||||
@ -92,31 +83,36 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
ret_ty: String::new(),
|
||||
ret_expr: String::new(),
|
||||
constructor: None,
|
||||
anyref_args: Vec::new(),
|
||||
ret_anyref: false,
|
||||
}
|
||||
}
|
||||
|
||||
/// Generates all bindings necessary for the signature in `Function`,
|
||||
/// creating necessary argument conversions and return value processing.
|
||||
pub fn process<'c, I>(
|
||||
pub fn process(
|
||||
&mut self,
|
||||
function: &Function,
|
||||
opt_arg_names: I,
|
||||
) -> Result<&mut Self, Error>
|
||||
where
|
||||
I: Into<Option<&'c Vec<String>>>,
|
||||
{
|
||||
if let Some(arg_names) = opt_arg_names.into() {
|
||||
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
||||
self.argument(arg, arg_name.as_str())?;
|
||||
}
|
||||
} else {
|
||||
for arg in function.arguments.iter() {
|
||||
self.argument(arg, None)?;
|
||||
}
|
||||
opt_arg_names: &Option<Vec<String>>,
|
||||
) -> Result<&mut Self, Error> {
|
||||
let arg_names = match opt_arg_names {
|
||||
Some(arg_names) => arg_names.iter().map(|s| Some(s.as_str())).collect(),
|
||||
None => vec![None; function.arguments.len()],
|
||||
};
|
||||
assert_eq!(arg_names.len(), function.arguments.len());
|
||||
for (arg, arg_name) in function.arguments.iter().zip(arg_names) {
|
||||
// Process the function argument and assert that the metadata about
|
||||
// the number of arguments on the Rust side required is correct.
|
||||
let before = self.rust_arguments.len();
|
||||
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)?;
|
||||
function
|
||||
.ret
|
||||
.assert_abi_return_correct(before, self.rust_arguments.len());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@ -183,12 +179,9 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
ret
|
||||
}
|
||||
|
||||
pub fn argument<'c, I>(&mut self, arg: &Descriptor, opt_arg_name: I) -> Result<&mut Self, Error>
|
||||
where
|
||||
I: Into<Option<&'c str>>,
|
||||
{
|
||||
fn argument(&mut self, arg: &Descriptor, arg_name: Option<&str>) -> Result<&mut Self, Error> {
|
||||
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 {
|
||||
Descriptor::Option(t) => (&**t, true),
|
||||
@ -254,7 +247,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : addToAnyrefTable({0})", name));
|
||||
} else {
|
||||
self.anyref_args.push((self.rust_arguments.len(), true));
|
||||
self.rust_arguments.push(name);
|
||||
}
|
||||
} else {
|
||||
@ -451,7 +443,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
self.js_arguments
|
||||
.push(JsArgument::required(name.clone(), "any".to_string()));
|
||||
if self.cx.config.anyref {
|
||||
self.anyref_args.push((self.rust_arguments.len(), false));
|
||||
self.rust_arguments.push(name);
|
||||
} else {
|
||||
// the "stack-ful" nature means that we're always popping from the
|
||||
@ -494,7 +485,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
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() {
|
||||
match &self.constructor {
|
||||
Some(class) if class == name => {
|
||||
@ -568,7 +559,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
if ty.is_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
||||
self.ret_anyref = true;
|
||||
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
|
||||
/// generated function shim and the second is a TypeScript signature of the
|
||||
/// JS expression.
|
||||
pub fn finish(
|
||||
&mut self,
|
||||
prefix: &str,
|
||||
invoc: &str,
|
||||
exported_shim: ExportedShim,
|
||||
) -> (String, String, String) {
|
||||
pub fn finish(&mut self, prefix: &str, invoc: &str) -> (String, String, String) {
|
||||
let js_args = self
|
||||
.js_arguments
|
||||
.iter()
|
||||
@ -856,23 +841,6 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
}
|
||||
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())
|
||||
}
|
||||
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -1,12 +1,13 @@
|
||||
use crate::descriptor::{Descriptor, Function};
|
||||
use crate::js::js2rust::ExportedShim;
|
||||
use crate::js::{Context, ImportTarget, Js2Rust};
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::intrinsic::Intrinsic;
|
||||
use crate::js::{Context, Js2Rust};
|
||||
use crate::webidl::{AuxImport, AuxValue, ImportBinding};
|
||||
use failure::{bail, Error};
|
||||
|
||||
/// Helper struct for manufacturing a shim in JS used to translate Rust types to
|
||||
/// JS, then invoking an imported JS function.
|
||||
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
|
||||
/// 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.
|
||||
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
|
||||
/// or not.
|
||||
pub anyref_args: Vec<(usize, bool)>,
|
||||
pub ret_anyref: bool,
|
||||
anyref_args: Vec<(usize, 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> {
|
||||
@ -63,6 +81,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
variadic: false,
|
||||
anyref_args: Vec::new(),
|
||||
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`,
|
||||
/// 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() {
|
||||
// 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)?;
|
||||
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)?;
|
||||
function
|
||||
.ret
|
||||
.assert_abi_return_correct(before, self.shim_arguments.len());
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
@ -274,7 +317,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
|
||||
if let Some((f, mutable)) = arg.stack_closure() {
|
||||
let arg2 = self.shim_argument();
|
||||
let mut shim = f.shim_idx;
|
||||
let (js, _ts, _js_doc) = {
|
||||
let mut builder = Js2Rust::new("", self.cx);
|
||||
if mutable {
|
||||
@ -286,13 +328,12 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
} else {
|
||||
builder.rust_argument("this.a");
|
||||
}
|
||||
builder.rust_argument("this.b").process(f, None)?.finish(
|
||||
"function",
|
||||
"this.f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
)
|
||||
builder
|
||||
.rust_argument("this.b")
|
||||
.process(f, &None)?
|
||||
.finish("function", "this.f")
|
||||
};
|
||||
self.cx.function_table_needed = true;
|
||||
self.cx.export_function_table()?;
|
||||
self.global_idx();
|
||||
self.prelude(&format!(
|
||||
"\
|
||||
@ -304,7 +345,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
abi,
|
||||
arg2,
|
||||
js = js,
|
||||
idx = shim,
|
||||
idx = f.shim_idx,
|
||||
));
|
||||
self.finally(&format!("cb{0}.a = cb{0}.b = 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
|
||||
/// possible and avoid a shim in some circumstances.
|
||||
pub fn is_noop(&self) -> bool {
|
||||
fn is_noop(&self) -> bool {
|
||||
let Rust2Js {
|
||||
// fields which may affect whether we do nontrivial work
|
||||
catch,
|
||||
@ -594,6 +635,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
global_idx: _,
|
||||
anyref_args: _,
|
||||
ret_anyref: _,
|
||||
style,
|
||||
} = self;
|
||||
|
||||
!catch &&
|
||||
@ -607,96 +649,277 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
// similarly we want to make sure that all the arguments are simply
|
||||
// forwarded from the shim we would generate to the import,
|
||||
// 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> {
|
||||
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);
|
||||
|
||||
pub fn finish(&mut self, target: &AuxImport) -> Result<String, Error> {
|
||||
let variadic = self.variadic;
|
||||
let ret_expr = &self.ret_expr;
|
||||
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
||||
let ret = if variadic {
|
||||
let variadic_args = |js_arguments: &[String]| {
|
||||
Ok(if !variadic {
|
||||
format!("{}", js_arguments.join(", "))
|
||||
} else {
|
||||
let (last_arg, args) = match js_arguments.split_last() {
|
||||
Some(pair) => pair,
|
||||
None => bail!("a function with no arguments cannot be variadic"),
|
||||
};
|
||||
if args.len() > 0 {
|
||||
ret_expr.replace(
|
||||
"JS",
|
||||
&format!("{}({}, ...{})", invoc, args.join(", "), last_arg),
|
||||
)
|
||||
format!("{}, ...{}", args.join(", "), last_arg)
|
||||
} 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 fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
||||
if variadic {
|
||||
bail!("{} cannot be variadic", desc);
|
||||
}
|
||||
match (class, js_arguments.len()) {
|
||||
(None, n) if n == amt + 1 => Ok((js_arguments[0].clone(), &js_arguments[1..])),
|
||||
(None, _) => bail!("setters must have {} arguments", amt + 1),
|
||||
(Some(class), n) if n == amt => Ok((class.clone(), &js_arguments[..])),
|
||||
(Some(_), _) => bail!("static setters must have {} arguments", amt),
|
||||
}
|
||||
};
|
||||
let invoc = match target {
|
||||
AuxImport::Value(val) => match self.style {
|
||||
Style::Constructor => {
|
||||
let js = match val {
|
||||
AuxValue::Bare(js) => self.cx.import_name(js)?,
|
||||
_ => bail!("invalid import set for constructor"),
|
||||
};
|
||||
format!("new {}({})", js, variadic_args(&self.js_arguments)?)
|
||||
}
|
||||
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 {
|
||||
ImportTarget::Function(f) => handle_variadic(&f, &self.js_arguments)?,
|
||||
ImportTarget::Constructor(c) => {
|
||||
handle_variadic(&format!("new {}", c), &self.js_arguments)?
|
||||
AuxImport::Instanceof(js) => {
|
||||
let js = self.cx.import_name(js)?;
|
||||
assert!(self.style == Style::Function);
|
||||
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() {
|
||||
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)?;
|
||||
let expr = format!("{}.{}", receiver, field);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralGetter(field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
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)?;
|
||||
let expr = format!("{}.{} = {}", receiver, field, val[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralClassGetter(class, field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
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)?;
|
||||
let expr = format!("{}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralSetter(field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
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)?;
|
||||
let expr = format!("{}[{}] = {}", receiver, field[0], field[1]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::StructuralClassSetter(class, field) => {
|
||||
assert!(self.style == Style::Function);
|
||||
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)?;
|
||||
let expr = format!("delete {}[{}]", receiver, field[0]);
|
||||
self.ret_expr.replace("JS", &expr)
|
||||
|
||||
AuxImport::IndexingGetterOfClass(class) => {
|
||||
assert!(self.style == Style::Function);
|
||||
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 {
|
||||
self.cx.expose_handle_error()?;
|
||||
@ -718,20 +941,20 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
}} catch (e) {{\n\
|
||||
let error = (function () {{
|
||||
try {{
|
||||
return e instanceof Error
|
||||
? `${{e.message}}\n\nStack:\n${{e.stack}}`
|
||||
return e instanceof Error \
|
||||
? `${{e.message}}\\n\\nStack:\\n${{e.stack}}` \
|
||||
: e.toString();
|
||||
}} catch(_) {{
|
||||
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:\", \
|
||||
error);
|
||||
throw e;
|
||||
}}\
|
||||
",
|
||||
&invoc, shim,
|
||||
&invoc,
|
||||
);
|
||||
}
|
||||
|
||||
@ -747,35 +970,20 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
&invoc, &self.finally
|
||||
);
|
||||
}
|
||||
ret.push_str(&invoc);
|
||||
|
||||
ret.push_str("\n}\n");
|
||||
|
||||
if self.ret_anyref || self.anyref_args.len() > 0 {
|
||||
// Some return values go at the the beginning of the argument list
|
||||
// (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");
|
||||
}
|
||||
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(", ")
|
||||
}
|
||||
self.cx.anyref.import_xform(
|
||||
"__wbindgen_placeholder__",
|
||||
shim,
|
||||
&self.anyref_args,
|
||||
self.ret_anyref,
|
||||
);
|
||||
ret.push_str("exnptr");
|
||||
}
|
||||
ret.push_str(") {\n");
|
||||
ret.push_str(&self.prelude);
|
||||
|
||||
ret.push_str(&invoc);
|
||||
ret.push_str("\n}\n");
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
@ -801,4 +1009,213 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
}
|
||||
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 walrus::Module;
|
||||
|
||||
mod anyref;
|
||||
mod decode;
|
||||
mod intrinsic;
|
||||
mod descriptor;
|
||||
mod descriptors;
|
||||
mod js;
|
||||
pub mod wasm2es6js;
|
||||
mod webidl;
|
||||
|
||||
pub struct Bindgen {
|
||||
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 {
|
||||
cfg.run(&mut module)
|
||||
.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 {
|
||||
demangle(&mut module);
|
||||
}
|
||||
unexported_unused_lld_things(&mut module);
|
||||
|
||||
// Here we're actually instantiating the module we've parsed above for
|
||||
// execution. Why, you might be asking, are we executing wasm code? A
|
||||
// good question!
|
||||
//
|
||||
// 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)?;
|
||||
// We're making quite a few changes, list ourselves as a producer.
|
||||
module
|
||||
.producers
|
||||
.add_processed_by("wasm-bindgen", &wasm_bindgen_shared::version());
|
||||
|
||||
let mut memories = module.memories.iter().map(|m| m.id());
|
||||
let memory = memories.next();
|
||||
if memories.next().is_some() {
|
||||
bail!("multiple memories currently not supported");
|
||||
// Learn about the type signatures of all wasm-bindgen imports and
|
||||
// exports by executing `__wbindgen_describe_*` functions. This'll
|
||||
// effectively move all the descriptor functions to their own custom
|
||||
// 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 mut cx = js::Context {
|
||||
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 mut cx = js::Context::new(&mut module, self)?;
|
||||
|
||||
let offset = cx
|
||||
.snippet_offsets
|
||||
.entry(program.unique_crate_identifier)
|
||||
.or_insert(0);
|
||||
for js in program.inline_js.iter() {
|
||||
let name = format!("inline{}.js", *offset);
|
||||
*offset += 1;
|
||||
let aux = cx.module.customs.delete_typed::<webidl::WasmBindgenAux>()
|
||||
.expect("aux section should be present");
|
||||
cx.generate(&aux)?;
|
||||
|
||||
// Write out all local JS snippets to the final destination now that
|
||||
// we've collected them from all the programs.
|
||||
for (identifier, list) in aux.snippets.iter() {
|
||||
for (i, js) in list.iter().enumerate() {
|
||||
let name = format!("inline{}.js", i);
|
||||
let path = out_dir
|
||||
.join("snippets")
|
||||
.join(program.unique_crate_identifier)
|
||||
.join(identifier)
|
||||
.join(name);
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(&path, js)
|
||||
.with_context(|_| format!("failed to write `{}`", path.display()))?;
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
for (path, contents) in aux.local_modules.iter() {
|
||||
let path = out_dir.join("snippets").join(path);
|
||||
fs::create_dir_all(path.parent().unwrap())?;
|
||||
fs::write(&path, contents)
|
||||
@ -394,6 +375,8 @@ impl Bindgen {
|
||||
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() {
|
||||
"mjs"
|
||||
} 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 {
|
||||
let mut indent: u32 = 0;
|
||||
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_derive = "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-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
|
||||
// to execute.
|
||||
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();
|
||||
|
||||
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
|
||||
// to read later on.
|
||||
let mut node = true;
|
||||
for (_id, custom) in wasm.customs.iter() {
|
||||
if custom.name() != "__wasm_bindgen_test_unstable" {
|
||||
continue;
|
||||
}
|
||||
node = !custom.data().contains(&0x01);
|
||||
if let Some(section) = wasm.customs.remove_raw("__wasm_bindgen_test_unstable") {
|
||||
node = !section.data.contains(&0x01);
|
||||
}
|
||||
let headless = env::var("NO_HEADLESS").is_err();
|
||||
let debug = env::var("WASM_BINDGEN_NO_DEBUG").is_err();
|
||||
|
@ -22,10 +22,10 @@ pub fn execute(
|
||||
const og = console[method];
|
||||
const on_method = `on_console_${{method}}`;
|
||||
console[method] = function (...args) {{
|
||||
og.apply(this, args);
|
||||
if (handlers[on_method]) {{
|
||||
handlers[on_method](args);
|
||||
}}
|
||||
og.apply(this, args);
|
||||
}};
|
||||
}};
|
||||
|
||||
|
@ -22,10 +22,15 @@ fn no_modules_rejects_npm() {
|
||||
.file("package.json", "")
|
||||
.wasm_bindgen("--no-modules");
|
||||
cmd.assert()
|
||||
.stderr("\
|
||||
error: failed to generate bindings for JS import `foo`
|
||||
caused by: import from `foo` module not allowed with `--target no-modules`; use `nodejs`, `web`, or `bundler` target instead
|
||||
")
|
||||
.stderr(
|
||||
str::is_match(
|
||||
"\
|
||||
error: NPM dependencies have been specified in `.*` but this is only \
|
||||
compatible with the `bundler` and `nodejs` targets
|
||||
",
|
||||
)
|
||||
.unwrap(),
|
||||
)
|
||||
.failure();
|
||||
}
|
||||
|
||||
|
@ -13,4 +13,4 @@ edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1"
|
||||
walrus = "0.7.0"
|
||||
walrus = "0.8.0"
|
||||
|
@ -14,7 +14,7 @@ edition = '2018'
|
||||
[dependencies]
|
||||
failure = "0.1"
|
||||
log = "0.4"
|
||||
walrus = "0.7.0"
|
||||
walrus = "0.8.0"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3"
|
||||
|
@ -127,12 +127,7 @@ impl Interpreter {
|
||||
///
|
||||
/// Returns `Some` if `func` was found in the `module` and `None` if it was
|
||||
/// not found in the `module`.
|
||||
pub fn interpret_descriptor(&mut self, func: &str, 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]> {
|
||||
pub fn interpret_descriptor(&mut self, id: FunctionId, module: &Module) -> Option<&[u32]> {
|
||||
self.descriptor.truncate(0);
|
||||
|
||||
// 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);
|
||||
|
||||
// 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`
|
||||
|
@ -17,7 +17,17 @@ fn interpret(wat: &str, name: &str, result: Option<&[u32]>) {
|
||||
assert!(status.success());
|
||||
let module = walrus::Module::from_file(output.path()).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]
|
||||
@ -30,7 +40,6 @@ fn smoke() {
|
||||
)
|
||||
"#;
|
||||
interpret(wat, "foo", Some(&[]));
|
||||
interpret(wat, "bar", None);
|
||||
|
||||
let wat = r#"
|
||||
(module
|
||||
|
@ -116,6 +116,6 @@ function render(scene) {
|
||||
rendering.stop();
|
||||
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
|
||||
}
|
||||
|
@ -109,7 +109,7 @@ impl Slab {
|
||||
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::mem;
|
||||
use core::ops::{Deref, DerefMut};
|
||||
use core::ptr;
|
||||
|
||||
use crate::convert::FromWasmAbi;
|
||||
|
||||
@ -171,9 +170,13 @@ impl JsValue {
|
||||
/// JS object corresponding to the symbol created.
|
||||
pub fn symbol(description: Option<&str>) -> JsValue {
|
||||
unsafe {
|
||||
let ptr = description.map(|s| s.as_ptr()).unwrap_or(ptr::null());
|
||||
let len = description.map(|s| s.len()).unwrap_or(0);
|
||||
JsValue::_new(__wbindgen_symbol_new(ptr, len))
|
||||
match description {
|
||||
Some(description) => JsValue::_new(__wbindgen_symbol_named_new(
|
||||
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>,
|
||||
{
|
||||
unsafe {
|
||||
let mut ptr = ptr::null_mut();
|
||||
let len = __wbindgen_json_serialize(self.idx, &mut ptr);
|
||||
let s = Vec::from_raw_parts(ptr, len, len);
|
||||
let mut ret = [0usize; 2];
|
||||
__wbindgen_json_serialize(&mut ret, self.idx);
|
||||
let s = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||
let s = String::from_utf8_unchecked(s);
|
||||
serde_json::from_str(&s)
|
||||
}
|
||||
@ -333,14 +336,10 @@ impl JsValue {
|
||||
#[cfg(feature = "std")]
|
||||
fn as_debug_string(&self) -> String {
|
||||
unsafe {
|
||||
let mut len = 0;
|
||||
let ptr = __wbindgen_debug_string(self.idx, &mut len);
|
||||
if ptr.is_null() {
|
||||
unreachable!("`__wbindgen_debug_string` must return a valid string")
|
||||
} else {
|
||||
let data = Vec::from_raw_parts(ptr, len, len);
|
||||
String::from_utf8_unchecked(data)
|
||||
}
|
||||
let mut ret = [0; 2];
|
||||
__wbindgen_debug_string(&mut ret, self.idx);
|
||||
let data = Vec::from_raw_parts(ret[0] as *mut u8, ret[1], ret[1]);
|
||||
String::from_utf8_unchecked(data)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -492,7 +491,8 @@ externs! {
|
||||
|
||||
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> 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;
|
||||
|
||||
@ -507,7 +507,7 @@ externs! {
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
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_rethrow(a: u32) -> !;
|
||||
@ -519,7 +519,7 @@ externs! {
|
||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
||||
|
||||
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_json_serialize(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_memory() -> u32;
|
||||
@ -557,7 +557,7 @@ impl Drop for JsValue {
|
||||
fn drop(&mut self) {
|
||||
unsafe {
|
||||
// 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,
|
||||
// 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 => {
|
||||
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),
|
||||
);
|
||||
fn call_destroyed(a: &JsValue);
|
||||
|
||||
fn js_store_forgotten_closure(closure: &Closure<Fn()>);
|
||||
fn js_call_forgotten_closure();
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
@ -573,3 +576,11 @@ fn call_destroyed_doesnt_segfault() {
|
||||
drop(a);
|
||||
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]
|
||||
#[cfg(ignored)] // TODO: fix this before landing
|
||||
fn switch_methods() {
|
||||
assert!(!switch_methods_called());
|
||||
SwitchMethods::a();
|
||||
@ -158,7 +159,7 @@ fn switch_methods() {
|
||||
|
||||
assert!(!switch_methods_called());
|
||||
SwitchMethods::new().b();
|
||||
assert!(switch_methods_called());
|
||||
assert!(!switch_methods_called());
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@ -213,3 +213,12 @@ fn string_roundtrip() {
|
||||
pub fn do_string_roundtrip(s: String) -> String {
|
||||
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