mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-03-16 02:00:51 +00:00
Merge pull request #1002 from alexcrichton/reference-types
Add experimental support for the `anyref` type
This commit is contained in:
commit
de85d99acd
@ -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…
x
Reference in New Issue
Block a user