Alex Crichton f66d83ff70
Store richer adapter types, don't use instructions for TypeScript (#1945)
This commit updates how TypeScript signature are generated from adapters
in wasm-bindgen. A richer set of `AdapterType` types are now stored
which record information about optional types and such. These direct
`AdapterType` values are then used to calculate the TypeScript
signature, rather than following the instructions in an adapter function
(which only works anyway for wasm-bindgen generated adapters).

This should be more robust since it reads the actual true signature of
the adapter to generate the TypeScript signature, rather than attempting
to ad-hoc-ly infer it from the various instructions, which was already
broken.

A number of refactorings were involved here, but the main pieces are:

* The `AdapterType` type is a bit more rich now to describe more
  Rust-like types.
* The `TypescriptArg` structure is now gone and instead return values
  are directly inferred from type signatures of adapters.
* The `typescript_{required,optional}` methods are no longer needed.
* The return of `JsBuilder::process` was enhanced to return more values,
  rather than storing some return values on the structure itself.

Closes #1926
2020-01-07 11:34:02 -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() {}
}