mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-05-23 10:41:22 +00:00
* Add reference output tests for JS operations This commit starts adding a test suite which checks in, to the repository, test assertions for both the JS and wasm file outputs of a Rust crate compiled with `#[wasm_bindgen]`. These aren't intended to be exhaustive or large scale tests, but rather micro-tests to help observe the changes in `wasm-bindgen`'s output over time. The motivation for this commit is basically overhauling how all the GC passes work in `wasm-bindgen` today. The reorganization is also included in this commit as well. Previously `wasm-bindgen` would, in an ad-hoc fashion, run the GC passes of `walrus` in a bunch of places to ensure that less "garbage" was seen by future passes. This not only was a source of slowdown but it also was pretty brittle since `wasm-bindgen` kept breaking if extra iteams leaked through. The strategy taken in this commit is to have one precise location for a GC pass, and everything goes through there. This is achieved by: * All internal exports are removed immediately when generating the nonstandard wasm interface types section. Internal exports, intrinsics, and runtime support are all referenced by the various instructions and/or sections that use them. This means that we now have precise tracking of what an adapter uses. * This in turn enables us to implement the `add_gc_roots` function for `walrus` custom sections, which in turn allows walrus GC passes to do what `unexport_unused_intrinsics` did before. That function is now no longer necessary, but effectively works the same way. All intrinsics are unexported at the beginning and then they're selectively re-imported and re-exported through the JS glue generation pass as necessary and defined by the bindings. * Passes like the `anyref` pass are now much more precise about the intrinsics that they work with. The `anyref` pass also deletes any internal intrinsics found and also does some rewriting of the adapters aftewards now to hook up calls to the heap count import to the heap count intrinsic in the wasm module. * Fix handling of __wbindgen_realloc The final user of the `require_internal_export` function was `__wbindgen_realloc`. This usage has now been removed by updating how we handle usage of the `realloc` function. The wasm interface types standard doesn't have a `realloc` function slot, nor do I think it ever will. This means that as a polyfill for wasm interface types we'll always have to support the lack of `realloc`. For direct Rust to JS, however, we can still optionally handle `realloc`. This is all handled with a few internal changes. * Custom `StringToMemory` instructions now exist. These have an extra `realloc` slot to store an intrinsic, if found. * Our custom instructions are lowered to the standard instructions when generating an interface types section. * The `realloc` function, if present, is passed as an argument like the malloc function when passing strings to wasm. If it's not present we use a slower fallback, but if it's present we use the faster implementation. This should mean that there's little-to-no impact on existing users of `wasm-bindgen`, but this should continue to still work for wasm interface types polyfills and such. Additionally the GC passes now work in that they don't delete `__wbindgen_realloc` which we later try to reference. * Add an empty test for the anyref pass * Precisely track I32FromOptionAnyref's dependencies This depends on the anyref table and a function to allocate an index if the anyref pass is running, so be sure to track that in the instruction itself for GC rooting. * Trim extraneous exports from nop anyref module Or if you're otherwise not using anyref slices, don't force some intrinsics to exist. * Remove globals from reference tests Looks like these values adjust in slight but insignificant ways over time * Update the anyref xform tests
189 lines
6.9 KiB
Rust
189 lines
6.9 KiB
Rust
//! Management of wasm-bindgen descriptor functions.
|
|
//!
|
|
//! The purpose of this module is to basically execute a pass on a raw wasm
|
|
//! module that just came out of the compiler. The pass will execute all
|
|
//! relevant descriptor functions contained in the module which wasm-bindgen
|
|
//! uses to convey type infomation here, to the CLI.
|
|
//!
|
|
//! All descriptor functions are removed after this pass runs and in their stead
|
|
//! a new custom section, defined in this module, is inserted into the
|
|
//! `walrus::Module` which contains all the results of all the descriptor
|
|
//! functions.
|
|
|
|
use crate::descriptor::{Closure, Descriptor};
|
|
use anyhow::Error;
|
|
use std::borrow::Cow;
|
|
use std::collections::{HashMap, HashSet};
|
|
use walrus::ImportId;
|
|
use walrus::{CustomSection, FunctionId, Module, TypedCustomSectionId};
|
|
use wasm_bindgen_wasm_interpreter::Interpreter;
|
|
|
|
#[derive(Default, Debug)]
|
|
pub struct WasmBindgenDescriptorsSection {
|
|
pub descriptors: HashMap<String, Descriptor>,
|
|
pub closure_imports: HashMap<ImportId, Closure>,
|
|
}
|
|
|
|
pub type WasmBindgenDescriptorsSectionId = TypedCustomSectionId<WasmBindgenDescriptorsSection>;
|
|
|
|
/// Execute all `__wbindgen_describe_*` functions in a module, inserting a
|
|
/// custom section which represents the executed value of each descriptor.
|
|
///
|
|
/// Afterwards this will delete all descriptor functions from the module.
|
|
pub fn execute(module: &mut Module) -> Result<WasmBindgenDescriptorsSectionId, Error> {
|
|
let mut section = WasmBindgenDescriptorsSection::default();
|
|
let mut interpreter = Interpreter::new(module)?;
|
|
|
|
section.execute_exports(module, &mut interpreter)?;
|
|
section.execute_closures(module, &mut interpreter)?;
|
|
|
|
Ok(module.customs.add(section))
|
|
}
|
|
|
|
impl WasmBindgenDescriptorsSection {
|
|
fn execute_exports(
|
|
&mut self,
|
|
module: &mut Module,
|
|
interpreter: &mut Interpreter,
|
|
) -> Result<(), Error> {
|
|
let mut to_remove = Vec::new();
|
|
for export in module.exports.iter() {
|
|
let prefix = "__wbindgen_describe_";
|
|
if !export.name.starts_with(prefix) {
|
|
continue;
|
|
}
|
|
let id = match export.item {
|
|
walrus::ExportItem::Function(id) => id,
|
|
_ => panic!("{} export not a function", export.name),
|
|
};
|
|
if let Some(d) = interpreter.interpret_descriptor(id, module) {
|
|
let name = &export.name[prefix.len()..];
|
|
let descriptor = Descriptor::decode(d);
|
|
self.descriptors.insert(name.to_string(), descriptor);
|
|
}
|
|
to_remove.push(export.id());
|
|
}
|
|
|
|
for id in to_remove {
|
|
module.exports.delete(id);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
fn execute_closures(
|
|
&mut self,
|
|
module: &mut Module,
|
|
interpreter: &mut Interpreter,
|
|
) -> Result<(), Error> {
|
|
use walrus::ir::*;
|
|
|
|
// If our describe closure intrinsic isn't present or wasn't linked
|
|
// then there's no closures, so nothing to do!
|
|
let wbindgen_describe_closure = match interpreter.describe_closure_id() {
|
|
Some(i) => i,
|
|
None => return Ok(()),
|
|
};
|
|
|
|
// Find all functions which call `wbindgen_describe_closure`. These are
|
|
// specially codegen'd so we know the rough structure of them. For each
|
|
// one we delegate to the interpreter to figure out the actual result.
|
|
let mut element_removal_list = HashSet::new();
|
|
let mut func_to_descriptor = HashMap::new();
|
|
for (id, local) in module.funcs.iter_local() {
|
|
let mut find = FindDescribeClosure {
|
|
wbindgen_describe_closure,
|
|
found: false,
|
|
};
|
|
dfs_in_order(&mut find, local, local.entry_block());
|
|
if find.found {
|
|
let descriptor = interpreter
|
|
.interpret_closure_descriptor(id, module, &mut element_removal_list)
|
|
.unwrap();
|
|
func_to_descriptor.insert(id, Descriptor::decode(descriptor));
|
|
}
|
|
}
|
|
|
|
// For all indirect functions that were closure descriptors, delete them
|
|
// from the function table since we've executed them and they're not
|
|
// necessary in the final binary.
|
|
let table_id = match interpreter.function_table_id() {
|
|
Some(id) => id,
|
|
None => return Ok(()),
|
|
};
|
|
let table = module.tables.get_mut(table_id);
|
|
let table = match &mut table.kind {
|
|
walrus::TableKind::Function(f) => f,
|
|
_ => unreachable!(),
|
|
};
|
|
for idx in element_removal_list {
|
|
log::trace!("delete element {}", idx);
|
|
assert!(table.elements[idx].is_some());
|
|
table.elements[idx] = None;
|
|
}
|
|
|
|
// And finally replace all calls of `wbindgen_describe_closure` with a
|
|
// freshly manufactured import. Save off the type of this import in
|
|
// ourselves, and then we're good to go.
|
|
let ty = module.funcs.get(wbindgen_describe_closure).ty();
|
|
for (func, descriptor) in func_to_descriptor {
|
|
let import_name = format!("__wbindgen_closure_wrapper{}", func.index());
|
|
let (id, import_id) =
|
|
module.add_import_func("__wbindgen_placeholder__", &import_name, ty);
|
|
module.funcs.get_mut(id).name = Some(import_name);
|
|
|
|
let local = match &mut module.funcs.get_mut(func).kind {
|
|
walrus::FunctionKind::Local(l) => l,
|
|
_ => unreachable!(),
|
|
};
|
|
let entry = local.entry_block();
|
|
dfs_pre_order_mut(
|
|
&mut UpdateDescribeClosure {
|
|
wbindgen_describe_closure,
|
|
replacement: id,
|
|
},
|
|
local,
|
|
entry,
|
|
);
|
|
self.closure_imports
|
|
.insert(import_id, descriptor.unwrap_closure());
|
|
}
|
|
return Ok(());
|
|
|
|
struct FindDescribeClosure {
|
|
wbindgen_describe_closure: FunctionId,
|
|
found: bool,
|
|
}
|
|
|
|
impl<'a> Visitor<'a> for FindDescribeClosure {
|
|
fn visit_call(&mut self, call: &Call) {
|
|
if call.func == self.wbindgen_describe_closure {
|
|
self.found = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
struct UpdateDescribeClosure {
|
|
wbindgen_describe_closure: FunctionId,
|
|
replacement: FunctionId,
|
|
}
|
|
|
|
impl<'a> VisitorMut for UpdateDescribeClosure {
|
|
fn visit_call_mut(&mut self, call: &mut Call) {
|
|
if call.func == self.wbindgen_describe_closure {
|
|
call.func = self.replacement;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl CustomSection for WasmBindgenDescriptorsSection {
|
|
fn name(&self) -> &str {
|
|
"wasm-bindgen descriptors"
|
|
}
|
|
|
|
fn data(&self, _: &walrus::IdsToIndices) -> Cow<[u8]> {
|
|
panic!("shouldn't emit custom sections just yet");
|
|
}
|
|
}
|