mirror of
https://github.com/rustwasm/wasm-bindgen.git
synced 2024-12-14 20:11:37 +03:00
Add experimental support for the anyref
type
This commit adds experimental support to `wasm-bindgen` to emit and leverage the `anyref` native wasm type. This native type is still in a proposal status (the reference-types proposal). The intention of `anyref` is to be able to directly hold JS values in wasm and pass the to imported functions, namely to empower eventual host bindings (now renamed WebIDL bindings) integration where we can skip JS shims altogether for many imports. This commit doesn't actually affect wasm-bindgen's behavior at all as-is, but rather this support requires an opt-in env var to be configured. Once the support is stable in browsers it's intended that this will add a CLI switch for turning on this support, eventually defaulting it to `true` in the far future. The basic strategy here is to take the `stack` and `slab` globals in the generated JS glue and move them into wasm using a table. This new table in wasm is managed at the fringes via injected shims. At `wasm-bindgen`-time the CLI will rewrite exports and imports with shims that actually use `anyref` if needed, performing loads/stores inside the wasm module instead of externally in the wasm module. This should provide a boost over what we have today, but it's not a fantastic strategy long term. We have a more grand vision for `anyref` being a first-class type in the language, but that's on a much longer horizon and this is currently thought to be the best we can do in terms of integration in the near future. The stack/heap JS tables are combined into one wasm table. The stack starts at the end of the table and grows down with a stack pointer (also injected). The heap starts at the end and grows up (state managed in linear memory). The anyref transformation here will hook up various intrinsics in wasm-bindgen to the runtime functionality if the anyref supoprt is enabled. The main tricky treatment here was applied to closures, where we need JS to use a different function pointer than the one Rust gives it to use a JS function pointer empowered with anyref. This works by switching up a bit how descriptors work, embedding the shims to call inside descriptors rather than communicated at runtime. This means that we're accessing constant values in the generated JS and we can just update the constant value accessed.
This commit is contained in:
parent
8f695782fb
commit
4181fb311a
@ -65,6 +65,8 @@ matrix:
|
||||
- cargo test
|
||||
# Run the main body of the test suite
|
||||
- cargo test --target wasm32-unknown-unknown
|
||||
# Make sure the anyref pass at least compiles even if it doesn't run
|
||||
- NODE_ARGS=/dev/null WASM_BINDGEN_ANYREF=1 cargo test --target wasm32-unknown-unknown --test wasm
|
||||
# Rerun the test suite but disable `--debug` in generated JS
|
||||
- WASM_BINDGEN_NO_DEBUG=1 cargo test --target wasm32-unknown-unknown
|
||||
# Make sure our serde tests work
|
||||
|
16
crates/anyref-xform/Cargo.toml
Normal file
16
crates/anyref-xform/Cargo.toml
Normal file
@ -0,0 +1,16 @@
|
||||
[package]
|
||||
name = "wasm-bindgen-anyref-xform"
|
||||
version = "0.2.37"
|
||||
authors = ["The wasm-bindgen Developers"]
|
||||
license = "MIT/Apache-2.0"
|
||||
repository = "https://github.com/rustwasm/wasm-bindgen/tree/master/crates/anyref-xform"
|
||||
homepage = "https://rustwasm.github.io/wasm-bindgen/"
|
||||
documentation = "https://docs.rs/wasm-bindgen-anyref-xform"
|
||||
description = """
|
||||
Internal anyref transformations for wasm-bindgen
|
||||
"""
|
||||
edition = '2018'
|
||||
|
||||
[dependencies]
|
||||
failure = "0.1"
|
||||
walrus = "0.4"
|
730
crates/anyref-xform/src/lib.rs
Normal file
730
crates/anyref-xform/src/lib.rs
Normal file
@ -0,0 +1,730 @@
|
||||
//! Transformation for wasm-bindgen to enable usage of `anyref` in a wasm
|
||||
//! module.
|
||||
//!
|
||||
//! This crate is in charge of enabling code using `wasm-bindgen` to use the
|
||||
//! `anyref` type inside of the wasm module. This transformation pass primarily
|
||||
//! wraps exports and imports in shims which use `anyref`, but quickly turn them
|
||||
//! into `i32` value types. This is all largely a stopgap until Rust has
|
||||
//! first-class support for the `anyref` type, but that's thought to be in the
|
||||
//! far future and will take quite some time to implement. In the meantime, we
|
||||
//! have this!
|
||||
//!
|
||||
//! The pass here works by collecting information during binding generation
|
||||
//! about imports and exports. Afterwards this pass runs in one go against a
|
||||
//! wasm module, updating exports, imports, calls to these functions, etc. The
|
||||
//! goal at least is to have valid wasm modules coming in that don't use
|
||||
//! `anyref` and valid wasm modules going out which use `anyref` at the fringes.
|
||||
|
||||
use failure::{bail, format_err, Error};
|
||||
use std::cmp;
|
||||
use std::collections::{BTreeMap, HashMap, HashSet};
|
||||
use std::mem;
|
||||
use walrus::ir::*;
|
||||
use walrus::{FunctionId, GlobalId, InitExpr, Module, TableId, ValType};
|
||||
|
||||
// must be kept in sync with src/lib.rs and ANYREF_HEAP_START
|
||||
const DEFAULT_MIN: u32 = 32;
|
||||
|
||||
/// State of the anyref pass, used to collect information while bindings are
|
||||
/// generated and used eventually to actually execute the entire pass.
|
||||
#[derive(Default)]
|
||||
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>,
|
||||
elements: BTreeMap<u32, (u32, Function)>,
|
||||
|
||||
// When wrapping closures with new shims, this is the index of the next
|
||||
// table entry that we'll be handing out.
|
||||
next_element: u32,
|
||||
|
||||
// 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> {
|
||||
cx: &'a mut Context,
|
||||
|
||||
// A map of functions to intrinsics that they represent
|
||||
intrinsic_map: HashMap<FunctionId, Intrinsic>,
|
||||
// A map of old import functions to the new internally-defined shims which
|
||||
// call the correct new import functions
|
||||
import_map: HashMap<FunctionId, FunctionId>,
|
||||
// A set of all shims we've created
|
||||
shims: HashSet<FunctionId>,
|
||||
|
||||
// Indices of items that we have injected or found. This state is maintained
|
||||
// during the pass execution.
|
||||
table: TableId,
|
||||
clone_ref: FunctionId,
|
||||
heap_alloc: FunctionId,
|
||||
heap_dealloc: FunctionId,
|
||||
stack_pointer: GlobalId,
|
||||
}
|
||||
|
||||
struct Function {
|
||||
name: String,
|
||||
// A map of argument index to whether it's an owned or borrowed anyref
|
||||
// (owned = true)
|
||||
args: HashMap<usize, bool>,
|
||||
ret_anyref: bool,
|
||||
}
|
||||
|
||||
enum Intrinsic {
|
||||
TableGrow,
|
||||
TableSetNull,
|
||||
DropRef,
|
||||
CloneRef,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Executed first very early over a wasm module, used to learn about how
|
||||
/// 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.
|
||||
let mut tables = module.tables.iter().filter_map(|t| match &t.kind {
|
||||
walrus::TableKind::Function(f) => Some(f),
|
||||
_ => None,
|
||||
});
|
||||
if let Some(t) = tables.next() {
|
||||
if tables.next().is_some() {
|
||||
bail!("more than one function table present")
|
||||
}
|
||||
self.next_element = t.elements.len() as u32;
|
||||
}
|
||||
drop(tables);
|
||||
|
||||
// Add in an anyref table to the module, which we'll be using for
|
||||
// our transform below.
|
||||
let kind = walrus::TableKind::Anyref(Default::default());
|
||||
self.table = Some(module.tables.add_local(DEFAULT_MIN, None, kind));
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Store information about an imported function that needs to be
|
||||
/// transformed. The actual transformation happens later during `run`.
|
||||
pub fn import_xform(
|
||||
&mut self,
|
||||
module: &str,
|
||||
name: &str,
|
||||
anyref: &[(usize, bool)],
|
||||
ret_anyref: bool,
|
||||
) -> &mut Self {
|
||||
if !self.enabled {
|
||||
return self;
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
/// Store information about an exported function that needs to be
|
||||
/// transformed. The actual transformation happens later during `run`.
|
||||
pub fn export_xform(
|
||||
&mut self,
|
||||
name: &str,
|
||||
anyref: &[(usize, bool)],
|
||||
ret_anyref: bool,
|
||||
) -> &mut Self {
|
||||
if !self.enabled {
|
||||
return self;
|
||||
}
|
||||
let f = self.function(name, anyref, ret_anyref);
|
||||
self.exports.insert(name.to_string(), f);
|
||||
self
|
||||
}
|
||||
|
||||
/// Store information about a function pointer that needs to be transformed.
|
||||
/// The actual transformation happens later during `run`. Returns an index
|
||||
/// that the new wrapped function pointer will be injected at.
|
||||
pub fn table_element_xform(
|
||||
&mut self,
|
||||
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
|
||||
}
|
||||
|
||||
fn function(&self, name: &str, anyref: &[(usize, bool)], ret_anyref: bool) -> Function {
|
||||
Function {
|
||||
name: name.to_string(),
|
||||
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
|
||||
// stack on the anyref table.
|
||||
let init = InitExpr::Value(Value::I32(DEFAULT_MIN as i32));
|
||||
let stack_pointer = module.globals.add_local(ValType::I32, true, init);
|
||||
|
||||
let mut heap_alloc = None;
|
||||
let mut heap_dealloc = None;
|
||||
|
||||
// Find exports of some intrinsics which we only need for a runtime
|
||||
// implementation.
|
||||
for export in module.exports.iter() {
|
||||
let f = match export.item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
match export.name.as_str() {
|
||||
"__wbindgen_anyref_table_alloc" => heap_alloc = Some(f),
|
||||
"__wbindgen_anyref_table_dealloc" => heap_dealloc = Some(f),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
let heap_alloc = heap_alloc.ok_or_else(|| format_err!("failed to find heap alloc"))?;
|
||||
let heap_dealloc =
|
||||
heap_dealloc.ok_or_else(|| format_err!("failed to find heap dealloc"))?;
|
||||
|
||||
// Create a shim function that looks like:
|
||||
//
|
||||
// (func __wbindgen_object_clone_ref (param i32) (result i32)
|
||||
// (local i32)
|
||||
// (table.set
|
||||
// (tee_local 1 (call $heap_alloc))
|
||||
// (table.get (local.get 0)))
|
||||
// (local.get 1))
|
||||
let mut builder = walrus::FunctionBuilder::new();
|
||||
let arg = module.locals.add(ValType::I32);
|
||||
let local = module.locals.add(ValType::I32);
|
||||
|
||||
let alloc = builder.call(heap_alloc, Box::new([]));
|
||||
let tee = builder.local_tee(local, alloc);
|
||||
let get_arg = builder.local_get(arg);
|
||||
let get_table = builder.table_get(table, get_arg);
|
||||
let set_table = builder.table_set(table, tee, get_table);
|
||||
let get_local = builder.local_get(local);
|
||||
|
||||
let ty = module.types.add(&[ValType::I32], &[ValType::I32]);
|
||||
let clone_ref = builder.finish(ty, vec![arg], vec![set_table, get_local], module);
|
||||
let name = "__wbindgen_object_clone_ref".to_string();
|
||||
module.funcs.get_mut(clone_ref).name = Some(name);
|
||||
|
||||
// And run the transformation!
|
||||
Transform {
|
||||
cx: self,
|
||||
intrinsic_map: HashMap::new(),
|
||||
import_map: HashMap::new(),
|
||||
shims: HashSet::new(),
|
||||
table,
|
||||
clone_ref,
|
||||
heap_alloc,
|
||||
heap_dealloc,
|
||||
stack_pointer,
|
||||
}
|
||||
.run(module)
|
||||
}
|
||||
}
|
||||
|
||||
impl Transform<'_> {
|
||||
fn run(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||
// Detect all the various intrinsics and such. This will also along the
|
||||
// way inject an intrinsic for cloning an anyref.
|
||||
self.find_intrinsics(module)?;
|
||||
|
||||
// Perform transformations of imports, exports, and function pointers.
|
||||
self.process_imports(module);
|
||||
for m in self.cx.imports.values() {
|
||||
assert!(m.is_empty());
|
||||
}
|
||||
self.process_exports(module);
|
||||
assert!(self.cx.exports.is_empty());
|
||||
self.process_elements(module)?;
|
||||
assert!(self.cx.elements.is_empty());
|
||||
|
||||
// If we didn't actually transform anything, no need to inject or
|
||||
// rewrite anything from below.
|
||||
if self.shims.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Perform all instruction transformations to rewrite calls between
|
||||
// functions and make sure everything is still hooked up right.
|
||||
self.rewrite_calls(module);
|
||||
|
||||
// Inject initialization routine to set up default slots in the table
|
||||
// (things like null/undefined/true/false)
|
||||
self.inject_initialization(module);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn find_intrinsics(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||
// Build up a map of various imported intrinsics to wire them up to
|
||||
// different implementations or different functions.
|
||||
for import in module.imports.iter_mut() {
|
||||
let f = match import.kind {
|
||||
walrus::ImportKind::Function(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
if import.module == "__wbindgen_anyref_xform__" {
|
||||
match import.name.as_str() {
|
||||
"__wbindgen_anyref_table_grow" => {
|
||||
self.intrinsic_map.insert(f, Intrinsic::TableGrow);
|
||||
}
|
||||
"__wbindgen_anyref_table_set_null" => {
|
||||
self.intrinsic_map.insert(f, Intrinsic::TableSetNull);
|
||||
}
|
||||
n => bail!("unknown intrinsic: {}", n),
|
||||
}
|
||||
} else if import.module == "__wbindgen_placeholder__" {
|
||||
match import.name.as_str() {
|
||||
"__wbindgen_object_drop_ref" => {
|
||||
self.intrinsic_map.insert(f, Intrinsic::DropRef);
|
||||
}
|
||||
"__wbindgen_object_clone_ref" => {
|
||||
self.intrinsic_map.insert(f, Intrinsic::CloneRef);
|
||||
}
|
||||
_ => continue,
|
||||
}
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Make sure we don't actually end up using the original import
|
||||
// because any invocation of them should be remapped to something
|
||||
// else.
|
||||
import.name = format!("{}_unused", import.name);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_imports(&mut self, module: &mut Module) {
|
||||
for import in module.imports.iter_mut() {
|
||||
let f = match import.kind {
|
||||
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 shim = self.append_shim(
|
||||
f,
|
||||
import,
|
||||
&mut module.types,
|
||||
&mut module.funcs,
|
||||
&mut module.locals,
|
||||
);
|
||||
self.import_map.insert(f, shim);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_exports(&mut self, module: &mut Module) {
|
||||
let mut new_exports = Vec::new();
|
||||
for export in module.exports.iter() {
|
||||
let f = match export.item {
|
||||
walrus::ExportItem::Function(f) => f,
|
||||
_ => continue,
|
||||
};
|
||||
let function = match self.cx.exports.remove(&export.name) {
|
||||
Some(s) => s,
|
||||
None => continue,
|
||||
};
|
||||
let shim = self.append_shim(
|
||||
f,
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
fn process_elements(&mut self, module: &mut Module) -> Result<(), Error> {
|
||||
let table = match module.tables.main_function_table()? {
|
||||
Some(t) => t,
|
||||
None => return Ok(()),
|
||||
};
|
||||
let table = module.tables.get_mut(table);
|
||||
let kind = match &mut table.kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
_ => unreachable!(),
|
||||
};
|
||||
if kind.relative_elements.len() > 0 {
|
||||
bail!("not compatible with relative element initializers yet");
|
||||
}
|
||||
|
||||
// Create shims for all our functions and append them all to the segment
|
||||
// which places elements at the end.
|
||||
while let Some((idx, function)) = self.cx.elements.remove(&(kind.elements.len() as u32)) {
|
||||
let target = kind.elements[idx as usize].unwrap();
|
||||
let shim = self.append_shim(
|
||||
target,
|
||||
function,
|
||||
&mut module.types,
|
||||
&mut module.funcs,
|
||||
&mut module.locals,
|
||||
);
|
||||
kind.elements.push(Some(shim));
|
||||
}
|
||||
|
||||
// ... and next update the limits of the table in case any are listed.
|
||||
table.initial = cmp::max(table.initial, kind.elements.len() as u32);
|
||||
if let Some(max) = table.maximum {
|
||||
table.maximum = Some(cmp::max(max, kind.elements.len() as u32));
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn append_shim(
|
||||
&mut self,
|
||||
shim_target: FunctionId,
|
||||
mut func: Function,
|
||||
types: &mut walrus::ModuleTypes,
|
||||
funcs: &mut walrus::ModuleFunctions,
|
||||
locals: &mut walrus::ModuleLocals,
|
||||
) -> FunctionId {
|
||||
let target = funcs.get_mut(shim_target);
|
||||
let (is_export, ty) = match &mut target.kind {
|
||||
walrus::FunctionKind::Import(f) => (false, &mut f.ty),
|
||||
walrus::FunctionKind::Local(f) => (true, &mut f.ty),
|
||||
_ => unreachable!()
|
||||
};
|
||||
|
||||
let target_ty = types.get(*ty);
|
||||
|
||||
// Learn about the various operations we're doing up front. Afterwards
|
||||
// we'll have a better idea bout what sort of code we're gonna be
|
||||
// generating.
|
||||
enum Convert {
|
||||
None,
|
||||
Store { owned: bool },
|
||||
Load { owned: bool },
|
||||
}
|
||||
let mut param_tys = Vec::new();
|
||||
let mut param_convert = Vec::new();
|
||||
let mut anyref_stack = 0;
|
||||
|
||||
for (i, old_ty) in target_ty.params().iter().enumerate() {
|
||||
let is_owned = func.args.remove(&i);
|
||||
let new_ty = is_owned
|
||||
.map(|_which| ValType::Anyref)
|
||||
.unwrap_or(old_ty.clone());
|
||||
param_tys.push(new_ty.clone());
|
||||
if new_ty == *old_ty {
|
||||
param_convert.push(Convert::None);
|
||||
} else if is_export {
|
||||
// We're calling an export, so we need to push this anyref into
|
||||
// a table somehow.
|
||||
param_convert.push(Convert::Store {
|
||||
owned: is_owned.unwrap(),
|
||||
});
|
||||
if is_owned == Some(false) {
|
||||
anyref_stack += 1;
|
||||
}
|
||||
} else {
|
||||
// We're calling an import, so we just need to fetch our table
|
||||
// value.
|
||||
param_convert.push(Convert::Load {
|
||||
owned: is_owned.unwrap(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
let new_ret = if func.ret_anyref {
|
||||
assert_eq!(target_ty.results(), &[ValType::I32]);
|
||||
vec![ValType::Anyref]
|
||||
} else {
|
||||
target_ty.results().to_vec()
|
||||
};
|
||||
let anyref_ty = types.add(¶m_tys, &new_ret);
|
||||
|
||||
// If we're an export then our shim is what's actually going to get
|
||||
// exported, and it's going to have the anyref signature.
|
||||
//
|
||||
// If we're an import, then our shim is what the Rust code calls, which
|
||||
// means it'll have the original signature. The existing import's
|
||||
// signature, however, is transformed to be an anyref signature.
|
||||
let shim_ty = if is_export {
|
||||
anyref_ty
|
||||
} else {
|
||||
mem::replace(ty, anyref_ty)
|
||||
};
|
||||
|
||||
let mut builder = walrus::FunctionBuilder::new();
|
||||
let mut before = Vec::new();
|
||||
let params = types.get(shim_ty)
|
||||
.params()
|
||||
.iter()
|
||||
.cloned()
|
||||
.map(|ty| locals.add(ty))
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
// Unconditionally allocate some locals which get cleaned up in later
|
||||
// gc passes if we don't actually end up using them.
|
||||
let fp = locals.add(ValType::I32);
|
||||
let scratch_i32 = locals.add(ValType::I32);
|
||||
let scratch_anyref = locals.add(ValType::Anyref);
|
||||
|
||||
// Update our stack pointer if there's any borrowed anyref objects.
|
||||
if anyref_stack > 0 {
|
||||
let sp = builder.global_get(self.stack_pointer);
|
||||
let size = builder.const_(Value::I32(anyref_stack));
|
||||
let new_sp = builder.binop(BinaryOp::I32Sub, sp, size);
|
||||
let tee = builder.local_tee(fp, new_sp);
|
||||
before.push(builder.global_set(self.stack_pointer, tee));
|
||||
}
|
||||
let mut next_stack_offset = 0;
|
||||
|
||||
let mut args = Vec::new();
|
||||
for (i, convert) in param_convert.iter().enumerate() {
|
||||
let local = builder.local_get(params[i]);
|
||||
args.push(match *convert {
|
||||
Convert::None => local,
|
||||
Convert::Load { owned: true } => {
|
||||
// load the anyref onto the stack, then afterwards
|
||||
// deallocate our index, leaving the anyref on the stack.
|
||||
let get = builder.table_get(self.table, local);
|
||||
let free = builder.call(self.heap_dealloc, Box::new([local]));
|
||||
builder.with_side_effects(Vec::new(), get, vec![free])
|
||||
}
|
||||
Convert::Load { owned: false } => builder.table_get(self.table, local),
|
||||
Convert::Store { owned: true } => {
|
||||
// Allocate space for the anyref, store it, and then leave
|
||||
// the index of the allocated anyref on the stack.
|
||||
let alloc = builder.call(self.heap_alloc, Box::new([]));
|
||||
let tee = builder.local_tee(scratch_i32, alloc);
|
||||
let store = builder.table_set(self.table, tee, local);
|
||||
let get = builder.local_get(scratch_i32);
|
||||
builder.with_side_effects(vec![store], get, Vec::new())
|
||||
}
|
||||
Convert::Store { owned: false } => {
|
||||
// Store an anyref at an offset from our function's stack
|
||||
// pointer frame.
|
||||
let get_fp = builder.local_get(fp);
|
||||
next_stack_offset += 1;
|
||||
let (index, idx_local) = if next_stack_offset == 1 {
|
||||
(get_fp, fp)
|
||||
} else {
|
||||
let rhs = builder.i32_const(next_stack_offset);
|
||||
let add = builder.binop(BinaryOp::I32Add, get_fp, rhs);
|
||||
(builder.local_tee(scratch_i32, add), scratch_i32)
|
||||
};
|
||||
let store = builder.table_set(self.table, index, local);
|
||||
let get = builder.local_get(idx_local);
|
||||
builder.with_side_effects(vec![store], get, Vec::new())
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Now that we've converted all the arguments, call the original
|
||||
// function. This may be either an import or an export which we're
|
||||
// wrapping.
|
||||
let mut result = builder.call(shim_target, args.into_boxed_slice());
|
||||
let mut after = Vec::new();
|
||||
|
||||
// If an anyref value is returned, then we need to be sure to apply
|
||||
// special treatment to convert it to an i32 as well. Note that only
|
||||
// owned anyref values can be returned, so that's all that's handled
|
||||
// here.
|
||||
if func.ret_anyref {
|
||||
if is_export {
|
||||
// We're an export so we have an i32 on the stack and need to
|
||||
// convert it to an anyref, basically by doing the same as an
|
||||
// owned load above: get the value then deallocate our slot.
|
||||
let tee = builder.local_tee(scratch_i32, result);
|
||||
result = builder.table_get(self.table, tee);
|
||||
let get_local = builder.local_get(scratch_i32);
|
||||
after.push(builder.call(self.heap_dealloc, Box::new([get_local])));
|
||||
} else {
|
||||
// Imports are the opposite, we have any anyref on the stack
|
||||
// and convert it to an i32 by allocating space for it and
|
||||
// storing it there.
|
||||
before.push(builder.local_set(scratch_anyref, result));
|
||||
let alloc = builder.call(self.heap_alloc, Box::new([]));
|
||||
let tee = builder.local_tee(scratch_i32, alloc);
|
||||
let get = builder.local_get(scratch_anyref);
|
||||
before.push(builder.table_set(self.table, tee, get));
|
||||
result = builder.local_get(scratch_i32);
|
||||
}
|
||||
}
|
||||
|
||||
// On function exit restore our anyref stack pointer if we decremented
|
||||
// it to start off.
|
||||
//
|
||||
// Note that we pave over all our stack slots with `ref.null` to ensure
|
||||
// that the table doesn't accidentally hold a strong reference to items
|
||||
// no longer in use by our wasm instance.
|
||||
//
|
||||
// TODO: use `table.fill` once that's spec'd
|
||||
if anyref_stack > 0 {
|
||||
for i in 0..anyref_stack {
|
||||
let get_fp = builder.local_get(fp);
|
||||
let index = if i > 0 {
|
||||
let offset = builder.i32_const(i);
|
||||
builder.binop(BinaryOp::I32Add, get_fp, offset)
|
||||
} else {
|
||||
get_fp
|
||||
};
|
||||
let null = builder.ref_null();
|
||||
after.push(builder.table_set(self.table, index, null));
|
||||
}
|
||||
|
||||
let get_fp = builder.local_get(fp);
|
||||
let size = builder.i32_const(anyref_stack);
|
||||
let new_sp = builder.binop(BinaryOp::I32Add, get_fp, size);
|
||||
after.push(builder.global_set(self.stack_pointer, new_sp));
|
||||
}
|
||||
|
||||
// Create the final expression node and then finish the function builder
|
||||
// with a fresh type we've been calculating so far. Give the function a
|
||||
// nice name for debugging and then we're good to go!
|
||||
let 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);
|
||||
funcs.get_mut(id).name = Some(name);
|
||||
self.shims.insert(id);
|
||||
return id;
|
||||
}
|
||||
|
||||
fn rewrite_calls(&mut self, module: &mut Module) {
|
||||
for (id, func) in module.funcs.iter_local_mut() {
|
||||
if self.shims.contains(&id) {
|
||||
continue;
|
||||
}
|
||||
let mut entry = func.entry_block();
|
||||
Rewrite {
|
||||
func,
|
||||
xform: self,
|
||||
replace: None,
|
||||
}
|
||||
.visit_block_id_mut(&mut entry);
|
||||
}
|
||||
|
||||
struct Rewrite<'a, 'b> {
|
||||
func: &'a mut walrus::LocalFunction,
|
||||
xform: &'a Transform<'b>,
|
||||
replace: Option<ExprId>,
|
||||
}
|
||||
|
||||
impl VisitorMut for Rewrite<'_, '_> {
|
||||
fn local_function_mut(&mut self) -> &mut walrus::LocalFunction {
|
||||
self.func
|
||||
}
|
||||
|
||||
fn visit_expr_id_mut(&mut self, expr: &mut ExprId) {
|
||||
expr.visit_mut(self);
|
||||
if let Some(id) = self.replace.take() {
|
||||
*expr = id;
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_call_mut(&mut self, e: &mut Call) {
|
||||
e.visit_mut(self);
|
||||
let intrinsic = match self.xform.intrinsic_map.get(&e.func) {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
// If this wasn't a call of an intrinsic, but it was a
|
||||
// call of one of our old import functions then we
|
||||
// switch the functions we're calling here.
|
||||
if let Some(f) = self.xform.import_map.get(&e.func) {
|
||||
e.func = *f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let builder = self.func.builder_mut();
|
||||
|
||||
match intrinsic {
|
||||
Intrinsic::TableGrow => {
|
||||
assert_eq!(e.args.len(), 1);
|
||||
let delta = e.args[0];
|
||||
let null = builder.ref_null();
|
||||
let grow = builder.table_grow(self.xform.table, delta, null);
|
||||
self.replace = Some(grow);
|
||||
}
|
||||
Intrinsic::TableSetNull => {
|
||||
assert_eq!(e.args.len(), 1);
|
||||
let index = e.args[0];
|
||||
let null = builder.ref_null();
|
||||
let set = builder.table_set(self.xform.table, index, null);
|
||||
self.replace = Some(set);
|
||||
}
|
||||
Intrinsic::DropRef => e.func = self.xform.heap_dealloc,
|
||||
Intrinsic::CloneRef => e.func = self.xform.clone_ref,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the `start` function for this module calls the
|
||||
// `__wbindgen_init_anyref_table` function. This'll ensure that all
|
||||
// instances of this module have the initial slots of the anyref table
|
||||
// initialized correctly.
|
||||
fn inject_initialization(&mut self, module: &mut Module) {
|
||||
let ty = module.types.add(&[], &[]);
|
||||
let import = module.add_import_func(
|
||||
"__wbindgen_placeholder__",
|
||||
"__wbindgen_init_anyref_table",
|
||||
ty,
|
||||
);
|
||||
|
||||
let prev_start = match module.start {
|
||||
Some(f) => f,
|
||||
None => {
|
||||
module.start = Some(import);
|
||||
return;
|
||||
}
|
||||
};
|
||||
|
||||
let mut builder = walrus::FunctionBuilder::new();
|
||||
let call_init = builder.call(import, Box::new([]));
|
||||
let call_prev = builder.call(prev_start, Box::new([]));
|
||||
let new_start = builder.finish(ty, Vec::new(), vec![call_init, call_prev], module);
|
||||
module.start = Some(new_start);
|
||||
}
|
||||
}
|
@ -3,9 +3,9 @@ use std::env;
|
||||
use std::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::iter::FromIterator;
|
||||
use std::sync::atomic::AtomicBool;
|
||||
use std::sync::atomic::AtomicUsize;
|
||||
use std::sync::atomic::Ordering::SeqCst;
|
||||
use std::sync::atomic::{AtomicBool};
|
||||
use std::sync::atomic::{AtomicUsize};
|
||||
|
||||
use ast;
|
||||
use proc_macro2::{self, Ident};
|
||||
|
@ -18,6 +18,7 @@ log = "0.4"
|
||||
rustc-demangle = "0.1.13"
|
||||
tempfile = "3.0"
|
||||
walrus = "0.4.0"
|
||||
wasm-bindgen-anyref-xform = { path = '../anyref-xform', version = '=0.2.37' }
|
||||
wasm-bindgen-shared = { path = "../shared", version = '=0.2.37' }
|
||||
wasm-bindgen-threads-xform = { path = '../threads-xform', version = '=0.2.37' }
|
||||
wasm-bindgen-wasm-interpreter = { path = "../wasm-interpreter", version = '=0.2.37' }
|
||||
|
@ -10,7 +10,7 @@
|
||||
//! this works can be found in the code below.
|
||||
|
||||
use crate::descriptor::Descriptor;
|
||||
use crate::js::js2rust::Js2Rust;
|
||||
use crate::js::js2rust::{ExportedShim, Js2Rust};
|
||||
use crate::js::Context;
|
||||
use failure::Error;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
@ -142,7 +142,7 @@ impl ClosureDescriptors {
|
||||
let table = input.module.tables.get_mut(table_id);
|
||||
let table = match &mut table.kind {
|
||||
walrus::TableKind::Function(f) => f,
|
||||
walrus::TableKind::Anyref(_) => unreachable!(),
|
||||
_ => unreachable!(),
|
||||
};
|
||||
for idx in self.element_removal_list.iter().cloned() {
|
||||
log::trace!("delete element {}", idx);
|
||||
@ -178,6 +178,7 @@ impl ClosureDescriptors {
|
||||
|
||||
let closure = instr.descriptor.closure().unwrap();
|
||||
|
||||
let mut shim = closure.shim_idx;
|
||||
let (js, _ts, _js_doc) = {
|
||||
let mut builder = Js2Rust::new("", input);
|
||||
builder.prelude("this.cnt++;");
|
||||
@ -192,9 +193,12 @@ impl ClosureDescriptors {
|
||||
builder.rust_argument("this.a").rust_argument("b");
|
||||
}
|
||||
builder.finally("if (this.cnt-- == 1) d(this.a, b);");
|
||||
builder.process(&closure.function)?.finish("function", "f")
|
||||
builder.process(&closure.function)?.finish(
|
||||
"function",
|
||||
"f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
)
|
||||
};
|
||||
input.expose_add_heap_object();
|
||||
input.function_table_needed = true;
|
||||
let body = format!(
|
||||
"function(a, b, _ignored) {{
|
||||
@ -205,15 +209,19 @@ impl ClosureDescriptors {
|
||||
cb.cnt = 1;
|
||||
let real = cb.bind(cb);
|
||||
real.original = cb;
|
||||
return addHeapObject(real);
|
||||
return {};
|
||||
}}",
|
||||
closure.shim_idx, closure.dtor_idx, js,
|
||||
shim,
|
||||
closure.dtor_idx,
|
||||
js,
|
||||
input.add_heap_object("real"),
|
||||
);
|
||||
input.export(&import_name, &body, None);
|
||||
|
||||
let id = input
|
||||
.module
|
||||
.add_import_func("__wbindgen_placeholder__", &import_name, ty);
|
||||
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,
|
||||
|
@ -44,6 +44,15 @@ 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> {
|
||||
@ -59,6 +68,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
ret_ty: String::new(),
|
||||
ret_expr: String::new(),
|
||||
constructor: None,
|
||||
anyref_args: Vec::new(),
|
||||
ret_anyref: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -193,13 +204,25 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
|
||||
if arg.is_anyref() {
|
||||
self.js_arguments.push((name.clone(), "any".to_string()));
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name,));
|
||||
if self.cx.config.anyref {
|
||||
if optional {
|
||||
self.cx.expose_add_to_anyref_table()?;
|
||||
self.cx.expose_is_like_none();
|
||||
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 {
|
||||
self.rust_arguments.push(format!("addHeapObject({})", name));
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.rust_arguments
|
||||
.push(format!("isLikeNone({0}) ? 0 : addHeapObject({0})", name));
|
||||
} else {
|
||||
self.rust_arguments.push(format!("addHeapObject({})", name));
|
||||
}
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
@ -383,14 +406,19 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
|
||||
if arg.is_ref_anyref() {
|
||||
self.js_arguments.push((name.clone(), "any".to_string()));
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.cx.expose_global_stack_pointer();
|
||||
// the "stack-ful" nature means that we're always popping from the
|
||||
// stack, and make sure that we actually clear our reference to
|
||||
// allow stale values to get GC'd
|
||||
self.finally("heap[stack_pointer++] = undefined;");
|
||||
self.rust_arguments
|
||||
.push(format!("addBorrowedObject({})", name));
|
||||
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
|
||||
// stack, and make sure that we actually clear our reference to
|
||||
// allow stale values to get GC'd
|
||||
self.cx.expose_borrowed_objects();
|
||||
self.cx.expose_global_stack_pointer();
|
||||
self.finally("heap[stack_pointer++] = undefined;");
|
||||
self.rust_arguments
|
||||
.push(format!("addBorrowedObject({})", name));
|
||||
}
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
@ -462,7 +490,7 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
|
||||
if let Some(ty) = ty.vector_kind() {
|
||||
self.ret_ty = ty.js_ty().to_string();
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty);
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty)?;
|
||||
self.cx.expose_global_argument_ptr()?;
|
||||
self.cx.expose_uint32_memory();
|
||||
self.cx.require_internal_export("__wbindgen_free")?;
|
||||
@ -494,8 +522,8 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
// that `takeObject` will naturally pluck out `undefined`.
|
||||
if ty.is_anyref() {
|
||||
self.ret_ty = "any".to_string();
|
||||
self.cx.expose_take_object();
|
||||
self.ret_expr = format!("return takeObject(RET);");
|
||||
self.ret_expr = format!("return {};", self.cx.take_object("RET"));
|
||||
self.ret_anyref = true;
|
||||
return Ok(self);
|
||||
}
|
||||
|
||||
@ -708,7 +736,12 @@ 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(&self, prefix: &str, invoc: &str) -> (String, String, String) {
|
||||
pub fn finish(
|
||||
&mut self,
|
||||
prefix: &str,
|
||||
invoc: &str,
|
||||
exported_shim: ExportedShim,
|
||||
) -> (String, String, String) {
|
||||
let js_args = self
|
||||
.js_arguments
|
||||
.iter()
|
||||
@ -754,6 +787,24 @@ impl<'a, 'b> Js2Rust<'a, 'b> {
|
||||
ts.push_str(&self.ret_ty);
|
||||
}
|
||||
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,4 +1,5 @@
|
||||
use crate::descriptor::{Descriptor, Function};
|
||||
use crate::js::js2rust::ExportedShim;
|
||||
use crate::js::{Context, ImportTarget, Js2Rust};
|
||||
use failure::{bail, Error};
|
||||
|
||||
@ -39,6 +40,11 @@ pub struct Rust2Js<'a, 'b: 'a> {
|
||||
|
||||
/// Whether or not the last argument is a slice representing variadic arguments.
|
||||
variadic: bool,
|
||||
|
||||
/// 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,
|
||||
}
|
||||
|
||||
impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
@ -55,6 +61,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
catch: false,
|
||||
catch_and_rethrow: false,
|
||||
variadic: false,
|
||||
anyref_args: Vec::new(),
|
||||
ret_anyref: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -101,7 +109,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
|
||||
if let Some(ty) = arg.vector_kind() {
|
||||
let abi2 = self.shim_argument();
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty);
|
||||
let f = self.cx.expose_get_vector_from_wasm(ty)?;
|
||||
self.prelude(&format!(
|
||||
"let v{0} = {prefix}{func}({0}, {1});",
|
||||
abi,
|
||||
@ -141,12 +149,14 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
// No need to special case `optional` here because `takeObject` will
|
||||
// naturally work.
|
||||
if arg.is_anyref() {
|
||||
self.cx.expose_take_object();
|
||||
self.js_arguments.push(format!("takeObject({})", abi));
|
||||
let arg = self.cx.take_object(&abi);
|
||||
self.js_arguments.push(arg);
|
||||
self.anyref_args.push((self.arg_idx - 1, true));
|
||||
return Ok(());
|
||||
} else if arg.is_ref_anyref() {
|
||||
self.cx.expose_get_object();
|
||||
self.js_arguments.push(format!("getObject({})", abi));
|
||||
let arg = self.cx.get_object(&abi);
|
||||
self.js_arguments.push(arg);
|
||||
self.anyref_args.push((self.arg_idx - 1, false));
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -263,6 +273,7 @@ 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 {
|
||||
@ -274,10 +285,11 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
} else {
|
||||
builder.rust_argument("this.a");
|
||||
}
|
||||
builder
|
||||
.rust_argument("this.b")
|
||||
.process(f)?
|
||||
.finish("function", "this.f")
|
||||
builder.rust_argument("this.b").process(f)?.finish(
|
||||
"function",
|
||||
"this.f",
|
||||
ExportedShim::TableElement(&mut shim),
|
||||
)
|
||||
};
|
||||
self.cx.function_table_needed = true;
|
||||
self.global_idx();
|
||||
@ -291,7 +303,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
abi,
|
||||
arg2,
|
||||
js = js,
|
||||
idx = f.shim_idx,
|
||||
idx = shim,
|
||||
));
|
||||
self.finally(&format!("cb{0}.a = cb{0}.b = 0;", abi));
|
||||
self.js_arguments.push(format!("cb{0}.bind(cb{0})", abi));
|
||||
@ -349,16 +361,31 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
return Ok(());
|
||||
}
|
||||
if ty.is_anyref() {
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.ret_expr = "
|
||||
const val = JS;
|
||||
return isLikeNone(val) ? 0 : addHeapObject(val);
|
||||
"
|
||||
.to_string();
|
||||
if self.cx.config.anyref {
|
||||
if optional {
|
||||
self.cx.expose_add_to_anyref_table()?;
|
||||
self.cx.expose_is_like_none();
|
||||
self.ret_expr = "
|
||||
const val = JS;
|
||||
return isLikeNone(val) ? 0 : addToAnyrefTable(val);
|
||||
"
|
||||
.to_string();
|
||||
} else {
|
||||
self.ret_anyref = true;
|
||||
self.ret_expr = "return JS;".to_string()
|
||||
}
|
||||
} else {
|
||||
self.ret_expr = "return addHeapObject(JS);".to_string()
|
||||
self.cx.expose_add_heap_object();
|
||||
if optional {
|
||||
self.cx.expose_is_like_none();
|
||||
self.ret_expr = "
|
||||
const val = JS;
|
||||
return isLikeNone(val) ? 0 : addHeapObject(val);
|
||||
"
|
||||
.to_string();
|
||||
} else {
|
||||
self.ret_expr = "return addHeapObject(JS);".to_string()
|
||||
}
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
@ -565,6 +592,8 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
arg_idx: _,
|
||||
cx: _,
|
||||
global_idx: _,
|
||||
anyref_args: _,
|
||||
ret_anyref: _,
|
||||
} = self;
|
||||
|
||||
!catch &&
|
||||
@ -581,7 +610,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
js_arguments == shim_arguments
|
||||
}
|
||||
|
||||
pub fn finish(&mut self, invoc: &ImportTarget) -> Result<String, Error> {
|
||||
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(", "));
|
||||
@ -596,7 +625,6 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
|
||||
let variadic = self.variadic;
|
||||
let ret_expr = &self.ret_expr;
|
||||
let js_arguments = &self.js_arguments;
|
||||
let handle_variadic = |invoc: &str, js_arguments: &[String]| {
|
||||
let ret = if variadic {
|
||||
let (last_arg, args) = match js_arguments.split_last() {
|
||||
@ -617,6 +645,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
Ok(ret)
|
||||
};
|
||||
|
||||
let js_arguments = &self.js_arguments;
|
||||
let fixed = |desc: &str, class: &Option<String>, amt: usize| {
|
||||
if variadic {
|
||||
bail!("{} cannot be variadic", desc);
|
||||
@ -670,7 +699,7 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
};
|
||||
|
||||
if self.catch {
|
||||
self.cx.expose_handle_error();
|
||||
self.cx.expose_handle_error()?;
|
||||
invoc = format!(
|
||||
"\
|
||||
try {{\n\
|
||||
@ -712,6 +741,33 @@ impl<'a, 'b> Rust2Js<'a, 'b> {
|
||||
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");
|
||||
}
|
||||
}
|
||||
self.cx.anyref.import_xform(
|
||||
"__wbindgen_placeholder__",
|
||||
shim,
|
||||
&self.anyref_args,
|
||||
self.ret_anyref,
|
||||
);
|
||||
}
|
||||
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
|
@ -35,6 +35,7 @@ pub struct Bindgen {
|
||||
// Experimental support for the wasm threads proposal, transforms the wasm
|
||||
// module to be "ready to be instantiated on any thread"
|
||||
threads: Option<wasm_bindgen_threads_xform::Config>,
|
||||
anyref: bool,
|
||||
}
|
||||
|
||||
enum Input {
|
||||
@ -62,6 +63,7 @@ impl Bindgen {
|
||||
emit_start: true,
|
||||
weak_refs: env::var("WASM_BINDGEN_WEAKREF").is_ok(),
|
||||
threads: threads_config(),
|
||||
anyref: env::var("WASM_BINDGEN_ANYREF").is_ok(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -176,6 +178,22 @@ impl Bindgen {
|
||||
(module, stem)
|
||||
}
|
||||
};
|
||||
|
||||
// This isn't the hardest thing in the world too support but we
|
||||
// basically don't know how to rationalize #[wasm_bindgen(start)] and
|
||||
// the actual `start` function if present. Figure this out later if it
|
||||
// comes up, but otherwise we should continue to be compatible with
|
||||
// LLVM's output today.
|
||||
//
|
||||
// Note that start function handling in `js/mod.rs` will need to be
|
||||
// updated as well, because `#[wasm_bindgen(start)]` is inserted *after*
|
||||
// a module's start function, if any, because we assume start functions
|
||||
// only show up when injected on behalf of wasm-bindgen's passes.
|
||||
if module.start.is_some() {
|
||||
bail!("wasm-bindgen is currently incompatible with modules that \
|
||||
already have a start function");
|
||||
}
|
||||
|
||||
let mut program_storage = Vec::new();
|
||||
let programs = extract_programs(&mut module, &mut program_storage)
|
||||
.with_context(|_| "failed to extract wasm-bindgen custom sections")?;
|
||||
@ -233,7 +251,10 @@ impl Bindgen {
|
||||
imported_statics: Default::default(),
|
||||
direct_imports: Default::default(),
|
||||
start: None,
|
||||
anyref: Default::default(),
|
||||
};
|
||||
cx.anyref.enabled = self.anyref;
|
||||
cx.anyref.prepare(cx.module)?;
|
||||
for program in programs.iter() {
|
||||
js::SubContext {
|
||||
program,
|
||||
|
@ -191,7 +191,10 @@ fn set_f64() {
|
||||
|
||||
Reflect::set_f64(&a, 0.0, &JsValue::from_str("Bye!")).unwrap();
|
||||
|
||||
assert_eq!(Reflect::get_f64(&a, 0.0).unwrap(), JsValue::from_str("Bye!"));
|
||||
assert_eq!(
|
||||
Reflect::get_f64(&a, 0.0).unwrap(),
|
||||
JsValue::from_str("Bye!")
|
||||
);
|
||||
}
|
||||
|
||||
#[wasm_bindgen_test]
|
||||
|
@ -27,6 +27,7 @@ const CRATES_TO_PUBLISH: &[&str] = &[
|
||||
"wasm-bindgen-wasm-interpreter",
|
||||
"wasm-bindgen-webidl",
|
||||
"wasm-bindgen-threads-xform",
|
||||
"wasm-bindgen-anyref-xform",
|
||||
"wasm-bindgen-cli-support",
|
||||
"wasm-bindgen-cli",
|
||||
"wasm-bindgen",
|
||||
|
208
src/anyref.rs
Normal file
208
src/anyref.rs
Normal file
@ -0,0 +1,208 @@
|
||||
use std::slice;
|
||||
use std::vec::Vec;
|
||||
use std::ptr;
|
||||
use std::alloc::{self, Layout};
|
||||
use std::mem;
|
||||
|
||||
use JsValue;
|
||||
|
||||
externs! {
|
||||
#[link(wasm_import_module = "__wbindgen_anyref_xform__")]
|
||||
extern "C" {
|
||||
fn __wbindgen_anyref_table_grow(delta: usize) -> i32;
|
||||
fn __wbindgen_anyref_table_set_null(idx: usize) -> ();
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Slab {
|
||||
data: Vec<usize>,
|
||||
head: usize,
|
||||
base: usize,
|
||||
}
|
||||
|
||||
impl Slab {
|
||||
fn new() -> Slab {
|
||||
Slab {
|
||||
data: Vec::new(),
|
||||
head: 0,
|
||||
base: 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn alloc(&mut self) -> usize {
|
||||
let ret = self.head;
|
||||
if ret == self.data.len() {
|
||||
if self.data.len() == self.data.capacity() {
|
||||
let extra = 128;
|
||||
let r = unsafe {
|
||||
__wbindgen_anyref_table_grow(extra)
|
||||
};
|
||||
if r == -1 {
|
||||
internal_error("table grow failure")
|
||||
}
|
||||
if self.base == 0 {
|
||||
self.base = r as usize + (super::JSIDX_RESERVED as usize);
|
||||
} else if self.base + self.data.len() != r as usize {
|
||||
internal_error("someone else allocated table entires?")
|
||||
}
|
||||
|
||||
// poor man's `try_reserve_exact` until that's stable
|
||||
unsafe {
|
||||
let new_cap = self.data.capacity() + extra;
|
||||
let size = mem::size_of::<usize>() * new_cap;
|
||||
let align = mem::align_of::<usize>();
|
||||
let layout = match Layout::from_size_align(size, align) {
|
||||
Ok(l) => l,
|
||||
Err(_) => internal_error("size/align layout failure"),
|
||||
};
|
||||
let ptr = alloc::alloc(layout) as *mut usize;
|
||||
if ptr.is_null() {
|
||||
internal_error("allocation failure");
|
||||
}
|
||||
ptr::copy_nonoverlapping(
|
||||
self.data.as_ptr(),
|
||||
ptr,
|
||||
self.data.len(),
|
||||
);
|
||||
let new_vec = Vec::from_raw_parts(
|
||||
ptr,
|
||||
self.data.len(),
|
||||
new_cap,
|
||||
);
|
||||
let mut old = mem::replace(&mut self.data, new_vec);
|
||||
old.set_len(0);
|
||||
}
|
||||
}
|
||||
|
||||
// custom condition to ensure `push` below doesn't call `reserve` in
|
||||
// optimized builds which pulls in lots of panic infrastructure
|
||||
if self.data.len() >= self.data.capacity() {
|
||||
internal_error("push should be infallible now")
|
||||
}
|
||||
self.data.push(ret + 1);
|
||||
}
|
||||
|
||||
// usage of `get_mut` thwarts panicking infrastructure in optimized
|
||||
// builds
|
||||
match self.data.get_mut(ret) {
|
||||
Some(slot) => self.head = *slot,
|
||||
None => internal_error("ret out of bounds"),
|
||||
}
|
||||
ret + self.base
|
||||
}
|
||||
|
||||
fn dealloc(&mut self, slot: usize) {
|
||||
if slot < self.base {
|
||||
internal_error("free reserved slot");
|
||||
}
|
||||
let slot = slot - self.base;
|
||||
|
||||
// usage of `get_mut` thwarts panicking infrastructure in optimized
|
||||
// builds
|
||||
match self.data.get_mut(slot) {
|
||||
Some(ptr) => {
|
||||
*ptr = self.head;
|
||||
self.head = slot;
|
||||
}
|
||||
None => internal_error("slot out of bounds"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn internal_error(msg: &str) -> ! {
|
||||
let msg = if cfg!(debug_assertions) { msg } else { "" };
|
||||
super::throw_str(msg)
|
||||
}
|
||||
|
||||
// Whoa, there's two `tl` modules here! That's currently intention, but for sort
|
||||
// of a weird reason. The table here is fundamentally thread local, so we want
|
||||
// to use the `thread_local!` macro. The implementation of thread locals (as of
|
||||
// the time of this writing) generates a lot of code as it pulls in panic paths
|
||||
// in libstd (even when using `try_with`). There's a patch to fix that
|
||||
// (rust-lang/rust#55518), but in the meantime the stable/beta channels produce
|
||||
// a lot of code.
|
||||
//
|
||||
// Matters are made worse here because this code is almost never used (it's only
|
||||
// here for an unstable feature). If we were to have panics here, though, then
|
||||
// we couldn't effectively gc away the panic infrastructure, meaning this unused
|
||||
// infrastructure would show up in binaries! That's a no-no for wasm-bindgen.
|
||||
//
|
||||
// In the meantime, if the atomics feature is turned on (which it never is by
|
||||
// default) then we use `thread_local!`, otherwise we use a home-grown
|
||||
// implementation that will be replaced once #55518 lands on stable.
|
||||
#[cfg(target_feature = "atomics")]
|
||||
mod tl {
|
||||
use std::*; // hack to get `thread_local!` to work
|
||||
use super::Slab;
|
||||
use std::cell::Cell;
|
||||
|
||||
thread_local!(pub static HEAP_SLAB: Cell<Slab> = Cell::new(Slab::new()));
|
||||
}
|
||||
|
||||
#[cfg(not(target_feature = "atomics"))]
|
||||
mod tl {
|
||||
use std::alloc::{self, Layout};
|
||||
use std::cell::Cell;
|
||||
use std::ptr;
|
||||
use super::Slab;
|
||||
|
||||
pub struct HeapSlab;
|
||||
pub static HEAP_SLAB: HeapSlab = HeapSlab;
|
||||
static mut SLOT: *mut Cell<Slab> = 0 as *mut Cell<Slab>;
|
||||
|
||||
impl HeapSlab {
|
||||
pub fn try_with<R>(&self, f: impl FnOnce(&Cell<Slab>) -> R) -> Result<R, ()> {
|
||||
unsafe {
|
||||
if SLOT.is_null() {
|
||||
let ptr = alloc::alloc(Layout::new::<Cell<Slab>>());
|
||||
if ptr.is_null() {
|
||||
super::internal_error("allocation failure");
|
||||
}
|
||||
let ptr = ptr as *mut Cell<Slab>;
|
||||
ptr::write(ptr, Cell::new(Slab::new()));
|
||||
SLOT = ptr;
|
||||
}
|
||||
Ok(f(&*SLOT))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn __wbindgen_anyref_table_alloc() -> usize {
|
||||
tl::HEAP_SLAB.try_with(|slot| {
|
||||
let mut slab = slot.replace(Slab::new());
|
||||
let ret = slab.alloc();
|
||||
slot.replace(slab);
|
||||
ret
|
||||
}).unwrap_or_else(|_| internal_error("tls access failure"))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub extern fn __wbindgen_anyref_table_dealloc(idx: usize) {
|
||||
if idx < super::JSIDX_RESERVED as usize {
|
||||
return
|
||||
}
|
||||
// clear this value from the table so while the table slot is un-allocated
|
||||
// we don't keep around a strong reference to a potentially large object
|
||||
unsafe {
|
||||
__wbindgen_anyref_table_set_null(idx);
|
||||
}
|
||||
tl::HEAP_SLAB.try_with(|slot| {
|
||||
let mut slab = slot.replace(Slab::new());
|
||||
slab.dealloc(idx);
|
||||
slot.replace(slab);
|
||||
}).unwrap_or_else(|_| internal_error("tls access failure"))
|
||||
}
|
||||
|
||||
#[no_mangle]
|
||||
pub unsafe extern fn __wbindgen_drop_anyref_slice(ptr: *mut JsValue, len: usize) {
|
||||
for slot in slice::from_raw_parts_mut(ptr, len) {
|
||||
ptr::drop_in_place(slot);
|
||||
}
|
||||
}
|
||||
|
||||
// see comment in module above this in `link_mem_intrinsics`
|
||||
#[inline(never)]
|
||||
pub fn link_intrinsics() {
|
||||
}
|
97
src/lib.rs
97
src/lib.rs
@ -30,6 +30,24 @@ macro_rules! if_std {
|
||||
)*)
|
||||
}
|
||||
|
||||
macro_rules! externs {
|
||||
($(#[$attr:meta])* extern "C" { $(fn $name:ident($($args:tt)*) -> $ret:ty;)* }) => (
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
||||
$(#[$attr])*
|
||||
extern "C" {
|
||||
$(fn $name($($args)*) -> $ret;)*
|
||||
}
|
||||
|
||||
$(
|
||||
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
|
||||
#[allow(unused_variables)]
|
||||
unsafe extern fn $name($($args)*) -> $ret {
|
||||
panic!("function not implemented on non-wasm32 targets")
|
||||
}
|
||||
)*
|
||||
)
|
||||
}
|
||||
|
||||
/// A module which is typically glob imported from:
|
||||
///
|
||||
/// ```
|
||||
@ -57,6 +75,7 @@ if_std! {
|
||||
extern crate std;
|
||||
use std::prelude::v1::*;
|
||||
pub mod closure;
|
||||
mod anyref;
|
||||
}
|
||||
|
||||
/// Representation of an object owned by JS.
|
||||
@ -462,58 +481,44 @@ macro_rules! numbers {
|
||||
|
||||
numbers! { i8 u8 i16 u16 i32 u32 f32 f64 }
|
||||
|
||||
macro_rules! externs {
|
||||
($(fn $name:ident($($args:tt)*) -> $ret:ty;)*) => (
|
||||
#[cfg(all(target_arch = "wasm32", not(target_os = "emscripten")))]
|
||||
#[link(wasm_import_module = "__wbindgen_placeholder__")]
|
||||
extern "C" {
|
||||
$(fn $name($($args)*) -> $ret;)*
|
||||
}
|
||||
|
||||
$(
|
||||
#[cfg(not(all(target_arch = "wasm32", not(target_os = "emscripten"))))]
|
||||
#[allow(unused_variables)]
|
||||
unsafe extern "C" fn $name($($args)*) -> $ret {
|
||||
panic!("function not implemented on non-wasm32 targets")
|
||||
}
|
||||
)*
|
||||
)
|
||||
}
|
||||
|
||||
externs! {
|
||||
fn __wbindgen_object_clone_ref(idx: u32) -> u32;
|
||||
fn __wbindgen_object_drop_ref(idx: u32) -> ();
|
||||
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_number_new(f: f64) -> u32;
|
||||
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
|
||||
fn __wbindgen_is_null(idx: u32) -> u32;
|
||||
fn __wbindgen_is_undefined(idx: u32) -> u32;
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_is_symbol(idx: u32) -> u32;
|
||||
fn __wbindgen_is_object(idx: u32) -> u32;
|
||||
fn __wbindgen_is_function(idx: u32) -> u32;
|
||||
fn __wbindgen_is_string(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_throw(a: *const u8, b: usize) -> !;
|
||||
fn __wbindgen_rethrow(a: u32) -> !;
|
||||
#[link(wasm_import_module = "__wbindgen_placeholder__")]
|
||||
extern "C" {
|
||||
fn __wbindgen_object_clone_ref(idx: u32) -> u32;
|
||||
fn __wbindgen_object_drop_ref(idx: u32) -> ();
|
||||
fn __wbindgen_string_new(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_number_new(f: f64) -> u32;
|
||||
fn __wbindgen_number_get(idx: u32, invalid: *mut u8) -> f64;
|
||||
fn __wbindgen_is_null(idx: u32) -> u32;
|
||||
fn __wbindgen_is_undefined(idx: u32) -> u32;
|
||||
fn __wbindgen_boolean_get(idx: u32) -> u32;
|
||||
fn __wbindgen_symbol_new(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_is_symbol(idx: u32) -> u32;
|
||||
fn __wbindgen_is_object(idx: u32) -> u32;
|
||||
fn __wbindgen_is_function(idx: u32) -> u32;
|
||||
fn __wbindgen_is_string(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_throw(a: *const u8, b: usize) -> !;
|
||||
fn __wbindgen_rethrow(a: u32) -> !;
|
||||
|
||||
fn __wbindgen_cb_drop(idx: u32) -> u32;
|
||||
fn __wbindgen_cb_forget(idx: u32) -> ();
|
||||
fn __wbindgen_cb_drop(idx: u32) -> u32;
|
||||
fn __wbindgen_cb_forget(idx: u32) -> ();
|
||||
|
||||
fn __wbindgen_describe(v: u32) -> ();
|
||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
||||
fn __wbindgen_describe(v: u32) -> ();
|
||||
fn __wbindgen_describe_closure(a: u32, b: u32, c: u32) -> u32;
|
||||
|
||||
fn __wbindgen_json_parse(ptr: *const u8, len: usize) -> u32;
|
||||
fn __wbindgen_json_serialize(idx: u32, ptr: *mut *mut u8) -> usize;
|
||||
fn __wbindgen_jsval_eq(a: u32, b: 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_jsval_eq(a: u32, b: u32) -> u32;
|
||||
|
||||
fn __wbindgen_memory() -> u32;
|
||||
fn __wbindgen_module() -> u32;
|
||||
fn __wbindgen_memory() -> u32;
|
||||
fn __wbindgen_module() -> u32;
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for JsValue {
|
||||
#[inline]
|
||||
fn clone(&self) -> JsValue {
|
||||
unsafe {
|
||||
let idx = __wbindgen_object_clone_ref(self.idx);
|
||||
@ -973,7 +978,9 @@ pub mod __rt {
|
||||
/// in the object file and link the intrinsics.
|
||||
///
|
||||
/// Ideas for how to improve this are most welcome!
|
||||
pub fn link_mem_intrinsics() {}
|
||||
pub fn link_mem_intrinsics() {
|
||||
::anyref::link_intrinsics();
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper type around slices and vectors for binding the `Uint8ClampedArray`
|
||||
|
Loading…
Reference in New Issue
Block a user