2018-10-18 08:43:36 -07:00
|
|
|
//! 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 walrus::ir::*;
|
2019-08-12 10:49:00 -07:00
|
|
|
use walrus::{ExportId, ImportId, TypeId};
|
2018-10-18 08:43:36 -07:00
|
|
|
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.
|
2019-05-31 10:54:03 -07:00
|
|
|
imports: HashMap<ImportId, Function>,
|
|
|
|
exports: HashMap<ExportId, Function>,
|
2018-10-18 08:43:36 -07:00
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
// 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> {
|
|
|
|
// 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,
|
2019-05-31 10:54:03 -07:00
|
|
|
id: ImportId,
|
2018-10-18 08:43:36 -07:00
|
|
|
anyref: &[(usize, bool)],
|
|
|
|
ret_anyref: bool,
|
|
|
|
) -> &mut Self {
|
2019-05-31 10:54:03 -07:00
|
|
|
if let Some(f) = self.function(anyref, ret_anyref) {
|
|
|
|
self.imports.insert(id, f);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
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,
|
2019-05-31 10:54:03 -07:00
|
|
|
id: ExportId,
|
2018-10-18 08:43:36 -07:00
|
|
|
anyref: &[(usize, bool)],
|
|
|
|
ret_anyref: bool,
|
|
|
|
) -> &mut Self {
|
2019-05-31 10:54:03 -07:00
|
|
|
if let Some(f) = self.function(anyref, ret_anyref) {
|
|
|
|
self.exports.insert(id, f);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
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,
|
2019-05-31 10:54:03 -07:00
|
|
|
) -> Option<u32> {
|
|
|
|
self.function(anyref, ret_anyref).map(|f| {
|
|
|
|
let ret = self.next_element;
|
|
|
|
self.next_element += 1;
|
|
|
|
self.elements.insert(ret, (idx, f));
|
|
|
|
ret
|
|
|
|
})
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
2019-05-31 10:54:03 -07:00
|
|
|
fn function(&self, anyref: &[(usize, bool)], ret_anyref: bool) -> Option<Function> {
|
|
|
|
if !ret_anyref && anyref.len() == 0 {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
Some(Function {
|
2018-10-18 08:43:36 -07:00
|
|
|
args: anyref.iter().cloned().collect(),
|
|
|
|
ret_anyref,
|
2019-05-31 10:54:03 -07:00
|
|
|
})
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run(&mut self, module: &mut Module) -> Result<(), Error> {
|
|
|
|
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))
|
2019-08-12 10:49:00 -07:00
|
|
|
let mut builder =
|
|
|
|
walrus::FunctionBuilder::new(&mut module.types, &[ValType::I32], &[ValType::I32]);
|
2018-10-18 08:43:36 -07:00
|
|
|
let arg = module.locals.add(ValType::I32);
|
|
|
|
let local = module.locals.add(ValType::I32);
|
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
let mut body = builder.func_body();
|
|
|
|
body.call(heap_alloc)
|
|
|
|
.local_tee(local)
|
|
|
|
.local_get(arg)
|
|
|
|
.table_get(table)
|
|
|
|
.table_set(table)
|
|
|
|
.local_get(local);
|
2018-10-18 08:43:36 -07:00
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
let clone_ref = builder.finish(vec![arg], &mut module.funcs);
|
2018-10-18 08:43:36 -07:00
|
|
|
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);
|
2019-05-31 10:54:03 -07:00
|
|
|
assert!(self.cx.imports.is_empty());
|
2018-10-18 08:43:36 -07:00
|
|
|
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);
|
|
|
|
|
|
|
|
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,
|
|
|
|
};
|
2019-05-31 10:54:03 -07:00
|
|
|
let func = match self.cx.imports.remove(&import.id()) {
|
|
|
|
Some(s) => s,
|
|
|
|
None => continue,
|
2018-10-18 08:43:36 -07:00
|
|
|
};
|
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
let (shim, anyref_ty) = self.append_shim(
|
2018-10-18 08:43:36 -07:00
|
|
|
f,
|
2019-05-31 10:54:03 -07:00
|
|
|
&import.name,
|
|
|
|
func,
|
2018-10-18 08:43:36 -07:00
|
|
|
&mut module.types,
|
|
|
|
&mut module.funcs,
|
|
|
|
&mut module.locals,
|
|
|
|
);
|
|
|
|
self.import_map.insert(f, shim);
|
2019-08-12 10:49:00 -07:00
|
|
|
match &mut module.funcs.get_mut(f).kind {
|
|
|
|
walrus::FunctionKind::Import(f) => f.ty = anyref_ty,
|
|
|
|
_ => unreachable!(),
|
|
|
|
}
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn process_exports(&mut self, module: &mut Module) {
|
2019-05-31 10:54:03 -07:00
|
|
|
// let mut new_exports = Vec::new();
|
|
|
|
for export in module.exports.iter_mut() {
|
2018-10-18 08:43:36 -07:00
|
|
|
let f = match export.item {
|
|
|
|
walrus::ExportItem::Function(f) => f,
|
|
|
|
_ => continue,
|
|
|
|
};
|
2019-05-31 10:54:03 -07:00
|
|
|
let function = match self.cx.exports.remove(&export.id()) {
|
2018-10-18 08:43:36 -07:00
|
|
|
Some(s) => s,
|
|
|
|
None => continue,
|
|
|
|
};
|
2019-08-12 10:49:00 -07:00
|
|
|
let (shim, _anyref_ty) = self.append_shim(
|
2018-10-18 08:43:36 -07:00
|
|
|
f,
|
2019-05-31 10:54:03 -07:00
|
|
|
&export.name,
|
2018-10-18 08:43:36 -07:00
|
|
|
function,
|
|
|
|
&mut module.types,
|
|
|
|
&mut module.funcs,
|
|
|
|
&mut module.locals,
|
|
|
|
);
|
2019-05-31 10:54:03 -07:00
|
|
|
export.item = shim.into();
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2019-08-12 10:49:00 -07:00
|
|
|
let (shim, _anyref_ty) = self.append_shim(
|
2018-10-18 08:43:36 -07:00
|
|
|
target,
|
2019-05-31 10:54:03 -07:00
|
|
|
&format!("closure{}", idx),
|
2018-10-18 08:43:36 -07:00
|
|
|
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,
|
2019-05-31 10:54:03 -07:00
|
|
|
name: &str,
|
2018-10-18 08:43:36 -07:00
|
|
|
mut func: Function,
|
|
|
|
types: &mut walrus::ModuleTypes,
|
|
|
|
funcs: &mut walrus::ModuleFunctions,
|
|
|
|
locals: &mut walrus::ModuleLocals,
|
2019-08-12 10:49:00 -07:00
|
|
|
) -> (FunctionId, TypeId) {
|
2018-10-18 08:43:36 -07:00
|
|
|
let target = funcs.get_mut(shim_target);
|
2019-08-12 10:49:00 -07:00
|
|
|
let (is_export, ty) = match &target.kind {
|
|
|
|
walrus::FunctionKind::Import(f) => (false, f.ty),
|
|
|
|
walrus::FunctionKind::Local(f) => (true, f.ty()),
|
2019-03-26 08:00:16 -07:00
|
|
|
_ => unreachable!(),
|
2018-10-18 08:43:36 -07:00
|
|
|
};
|
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
let target_ty = types.get(ty);
|
|
|
|
let target_ty_params = target_ty.params().to_vec();
|
|
|
|
let target_ty_results = target_ty.results().to_vec();
|
2018-10-18 08:43:36 -07:00
|
|
|
|
|
|
|
// 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.
|
2019-08-12 10:49:00 -07:00
|
|
|
let shim_ty = if is_export { anyref_ty } else { ty };
|
2018-10-18 08:43:36 -07:00
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
let mut builder = walrus::FunctionBuilder::new(
|
|
|
|
types,
|
|
|
|
if is_export {
|
|
|
|
¶m_tys
|
|
|
|
} else {
|
|
|
|
&target_ty_params
|
|
|
|
},
|
|
|
|
if is_export {
|
|
|
|
&new_ret
|
|
|
|
} else {
|
|
|
|
&target_ty_results
|
|
|
|
},
|
|
|
|
);
|
|
|
|
let mut body = builder.func_body();
|
2019-03-26 08:00:16 -07:00
|
|
|
let params = types
|
|
|
|
.get(shim_ty)
|
2018-10-18 08:43:36 -07:00
|
|
|
.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 {
|
2019-08-12 10:49:00 -07:00
|
|
|
body.global_get(self.stack_pointer)
|
|
|
|
.const_(Value::I32(anyref_stack))
|
|
|
|
.binop(BinaryOp::I32Sub)
|
|
|
|
.local_tee(fp)
|
|
|
|
.global_set(self.stack_pointer);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
let mut next_stack_offset = 0;
|
|
|
|
|
|
|
|
for (i, convert) in param_convert.iter().enumerate() {
|
2019-08-12 10:49:00 -07:00
|
|
|
match *convert {
|
|
|
|
Convert::None => {
|
|
|
|
body.local_get(params[i]);
|
|
|
|
}
|
2018-10-18 08:43:36 -07:00
|
|
|
Convert::Load { owned: true } => {
|
|
|
|
// load the anyref onto the stack, then afterwards
|
|
|
|
// deallocate our index, leaving the anyref on the stack.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_get(params[i])
|
|
|
|
.table_get(self.table)
|
|
|
|
.local_get(params[i])
|
|
|
|
.call(self.heap_dealloc);
|
|
|
|
}
|
|
|
|
Convert::Load { owned: false } => {
|
|
|
|
body.local_get(params[i]).table_get(self.table);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
Convert::Store { owned: true } => {
|
|
|
|
// Allocate space for the anyref, store it, and then leave
|
|
|
|
// the index of the allocated anyref on the stack.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.call(self.heap_alloc)
|
|
|
|
.local_tee(scratch_i32)
|
|
|
|
.local_get(params[i])
|
|
|
|
.table_set(self.table)
|
|
|
|
.local_get(scratch_i32);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
Convert::Store { owned: false } => {
|
|
|
|
// Store an anyref at an offset from our function's stack
|
|
|
|
// pointer frame.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_get(fp);
|
|
|
|
let idx_local = if next_stack_offset == 0 {
|
|
|
|
fp
|
2018-10-18 08:43:36 -07:00
|
|
|
} else {
|
2019-08-12 10:49:00 -07:00
|
|
|
body.i32_const(next_stack_offset)
|
|
|
|
.binop(BinaryOp::I32Add)
|
|
|
|
.local_tee(scratch_i32);
|
|
|
|
scratch_i32
|
2018-10-18 08:43:36 -07:00
|
|
|
};
|
2019-08-01 11:58:23 -07:00
|
|
|
next_stack_offset += 1;
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_get(params[i])
|
|
|
|
.table_set(self.table)
|
|
|
|
.local_get(idx_local);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
2019-08-12 10:49:00 -07:00
|
|
|
}
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.call(shim_target);
|
2018-10-18 08:43:36 -07:00
|
|
|
|
|
|
|
// 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.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_tee(scratch_i32)
|
|
|
|
.table_get(self.table)
|
|
|
|
.local_get(scratch_i32)
|
|
|
|
.call(self.heap_dealloc);
|
2018-10-18 08:43:36 -07:00
|
|
|
} 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.
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_set(scratch_anyref)
|
|
|
|
.call(self.heap_alloc)
|
|
|
|
.local_tee(scratch_i32)
|
|
|
|
.local_get(scratch_anyref)
|
|
|
|
.table_set(self.table)
|
|
|
|
.local_get(scratch_i32);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 {
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_get(fp);
|
|
|
|
if i > 0 {
|
|
|
|
body.i32_const(i).binop(BinaryOp::I32Add);
|
|
|
|
}
|
|
|
|
body.ref_null();
|
|
|
|
body.table_set(self.table);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
body.local_get(fp)
|
|
|
|
.i32_const(anyref_stack)
|
|
|
|
.binop(BinaryOp::I32Add)
|
|
|
|
.global_set(self.stack_pointer);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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!
|
2019-08-12 10:49:00 -07:00
|
|
|
let id = builder.finish(params, funcs);
|
2019-05-31 10:54:03 -07:00
|
|
|
let name = format!("{}_anyref_shim", name);
|
2018-10-18 08:43:36 -07:00
|
|
|
funcs.get_mut(id).name = Some(name);
|
|
|
|
self.shims.insert(id);
|
2019-08-12 10:49:00 -07:00
|
|
|
(id, anyref_ty)
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
fn rewrite_calls(&mut self, module: &mut Module) {
|
|
|
|
for (id, func) in module.funcs.iter_local_mut() {
|
|
|
|
if self.shims.contains(&id) {
|
|
|
|
continue;
|
|
|
|
}
|
2019-08-12 10:49:00 -07:00
|
|
|
let entry = func.entry_block();
|
|
|
|
dfs_pre_order_mut(&mut Rewrite { xform: self }, func, entry);
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
struct Rewrite<'a, 'b> {
|
|
|
|
xform: &'a Transform<'b>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl VisitorMut for Rewrite<'_, '_> {
|
2019-08-12 10:49:00 -07:00
|
|
|
fn start_instr_seq_mut(&mut self, seq: &mut InstrSeq) {
|
|
|
|
for i in (0..seq.instrs.len()).rev() {
|
|
|
|
let call = match &mut seq.instrs[i] {
|
|
|
|
Instr::Call(call) => call,
|
|
|
|
_ => continue,
|
|
|
|
};
|
|
|
|
let intrinsic = match self.xform.intrinsic_map.get(&call.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(&call.func) {
|
|
|
|
call.func = *f;
|
|
|
|
}
|
|
|
|
continue;
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
2019-08-12 10:49:00 -07:00
|
|
|
};
|
2018-10-18 08:43:36 -07:00
|
|
|
|
2019-08-12 10:49:00 -07:00
|
|
|
match intrinsic {
|
|
|
|
Intrinsic::TableGrow => {
|
|
|
|
// Switch this to a `table.grow` instruction...
|
|
|
|
seq.instrs[i] = TableGrow {
|
|
|
|
table: self.xform.table,
|
|
|
|
}
|
|
|
|
.into();
|
|
|
|
// ... and then insert a `ref.null` before the
|
|
|
|
// preceding instruction as the value to grow the
|
|
|
|
// table with.
|
|
|
|
seq.instrs.insert(i - 1, RefNull {}.into());
|
|
|
|
}
|
|
|
|
Intrinsic::TableSetNull => {
|
|
|
|
// Switch this to a `table.set` instruction...
|
|
|
|
seq.instrs[i] = TableSet {
|
|
|
|
table: self.xform.table,
|
|
|
|
}
|
|
|
|
.into();
|
|
|
|
// ... and then insert a `ref.null` as the
|
|
|
|
// preceding instruction
|
|
|
|
seq.instrs.insert(i, RefNull {}.into());
|
|
|
|
}
|
|
|
|
Intrinsic::DropRef => call.func = self.xform.heap_dealloc,
|
|
|
|
Intrinsic::CloneRef => call.func = self.xform.clone_ref,
|
2018-10-18 08:43:36 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|