mirror of
https://github.com/fluencelabs/wasm-bindgen
synced 2025-04-30 08:02:23 +00:00
* Add tests for the interface types output of wasm-bindgen This commit expands the test suite with assertions about the output of the interface types pass in wasm-bindgen. The goal here is to actually assert that we produce the right output and have a suite of reference files to show how the interface types output is changing over time. The `reference` test suite added in the previous PR has been updated to work for interface types as well, generating `*.wit` file assertions which are printed via the `wit-printer` crate on crates.io. Along the way a number of bugs were fixed with the interface types output, such as: * Non-determinism in output caused by iteration of a `HashMap` * Avoiding JS generation entirely in interface types mode, ensuring that we don't export extraneous intrinsics that aren't otherwise needed. * Fixing location of the stack pointer for modules where it's GC'd out. It's now rooted in the aux section of wasm-bindgen so it's available to later passes, like the multi-value pass. * Interface types emission now works in debug mode, meaning the `--release` flag is no longer required. This previously did not work because the `__wbindgen_throw` intrinsic was required in debug mode. This comes about because of the `malloc_failure` and `internal_error` functions in the anyref pass. The purpose of these functions is to signal fatal runtime errors, if any, in a way that's usable to the user. For wasm interface types though we can replace calls to these functions with `unreachable` to avoid needing to import the intrinsic. This has the accidental side effect of making `wasm_bindgen::throw_str` "just work" with wasm interface types by aborting the program, but that's not actually entirely intended. It's hoped that a split of a `wasm-bindgen-core` crate would solve this issue for the future. * Run the wasm interface types validator in tests * Add more gc roots for adapter gc * Improve stack pointer detection The stack pointer is never initialized to zero, but some other mutable globals are (TLS, thread ID, etc), so let's filter those out.
107 lines
3.6 KiB
Rust
107 lines
3.6 KiB
Rust
use crate::wit::{Adapter, NonstandardWitSection};
|
|
use crate::wit::{AdapterKind, Instruction, WasmBindgenAux};
|
|
use anyhow::{anyhow, Error};
|
|
use walrus::Module;
|
|
use wasm_bindgen_multi_value_xform as multi_value_xform;
|
|
use wasm_bindgen_wasm_conventions as wasm_conventions;
|
|
|
|
pub fn run(module: &mut Module) -> Result<(), Error> {
|
|
let mut adapters = module
|
|
.customs
|
|
.delete_typed::<NonstandardWitSection>()
|
|
.unwrap();
|
|
let mut to_xform = Vec::new();
|
|
let mut slots = Vec::new();
|
|
|
|
for (_, adapter) in adapters.adapters.iter_mut() {
|
|
extract_xform(module, adapter, &mut to_xform, &mut slots);
|
|
}
|
|
if to_xform.is_empty() {
|
|
// Early exit to avoid failing if we don't have a memory or shadow stack
|
|
// pointer because this is a minimal module that doesn't use linear
|
|
// memory.
|
|
module.customs.add(*adapters);
|
|
return Ok(());
|
|
}
|
|
|
|
let shadow_stack_pointer = module
|
|
.customs
|
|
.get_typed::<WasmBindgenAux>()
|
|
.expect("aux section should be present")
|
|
.shadow_stack_pointer
|
|
.ok_or_else(|| anyhow!("failed to find shadow stack pointer in wasm module"))?;
|
|
let memory = wasm_conventions::get_memory(module)?;
|
|
let wrappers = multi_value_xform::run(module, memory, shadow_stack_pointer, &to_xform)?;
|
|
|
|
for (slot, id) in slots.into_iter().zip(wrappers) {
|
|
match slot {
|
|
Slot::Id(s) => *s = id,
|
|
Slot::Export(e) => module.exports.get_mut(e).item = id.into(),
|
|
}
|
|
}
|
|
|
|
module.customs.add(*adapters);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
enum Slot<'a> {
|
|
Id(&'a mut walrus::FunctionId),
|
|
Export(walrus::ExportId),
|
|
}
|
|
|
|
fn extract_xform<'a>(
|
|
module: &Module,
|
|
adapter: &'a mut Adapter,
|
|
to_xform: &mut Vec<(walrus::FunctionId, usize, Vec<walrus::ValType>)>,
|
|
slots: &mut Vec<Slot<'a>>,
|
|
) {
|
|
let instructions = match &mut adapter.kind {
|
|
AdapterKind::Local { instructions } => instructions,
|
|
AdapterKind::Import { .. } => return,
|
|
};
|
|
|
|
// If the first instruction is a `Retptr`, then this must be an exported
|
|
// adapter which calls a wasm-defined function. Something we'd like to
|
|
// adapt to multi-value!
|
|
if let Some(Instruction::Retptr) = instructions.first().map(|e| &e.instr) {
|
|
instructions.remove(0);
|
|
let mut types = Vec::new();
|
|
instructions.retain(|instruction| match instruction.instr {
|
|
Instruction::LoadRetptr { ty, .. } => {
|
|
types.push(ty.to_wasm().unwrap());
|
|
false
|
|
}
|
|
_ => true,
|
|
});
|
|
let slot = instructions
|
|
.iter_mut()
|
|
.filter_map(|i| match &mut i.instr {
|
|
Instruction::Standard(wit_walrus::Instruction::CallCore(f)) => Some(Slot::Id(f)),
|
|
Instruction::CallExport(e) => Some(Slot::Export(*e)),
|
|
_ => None,
|
|
})
|
|
.next()
|
|
.expect("should have found call-core");
|
|
|
|
// LLVM currently always uses the first parameter for the return
|
|
// pointer. We hard code that here, since we have no better option.
|
|
let id = match &slot {
|
|
Slot::Id(i) => **i,
|
|
Slot::Export(e) => match module.exports.get(*e).item {
|
|
walrus::ExportItem::Function(f) => f,
|
|
_ => panic!("found call to non-function export"),
|
|
},
|
|
};
|
|
to_xform.push((id, 0, types));
|
|
slots.push(slot);
|
|
return;
|
|
}
|
|
|
|
// If the last instruction is a `StoreRetptr`, then this must be an adapter
|
|
// which calls an imported function.
|
|
//
|
|
// FIXME(#1872) handle this
|
|
// if let Some(Instruction::StoreRetptr { .. }) = instructions.last() {}
|
|
}
|