Alex Crichton 203d86f343
Add tests for the interface types output of wasm-bindgen (#1898)
* 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.
2019-12-04 15:19:48 -06:00

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() {}
}