Files
wasm-bindgen/crates/cli-support/src/wit/incoming.rs

386 lines
14 KiB
Rust
Raw Normal View History

Rewrite wasm-bindgen with updated interface types proposal (#1882) This commit is a pretty large scale rewrite of the internals of wasm-bindgen. No user-facing changes are expected as a result of this PR, but due to the scale of changes here it's likely inevitable that at least something will break. I'm hoping to get more testing in though before landing! The purpose of this PR is to update wasm-bindgen to the current state of the interface types proposal. The wasm-bindgen tool was last updated when it was still called "WebIDL bindings" so it's been awhile! All support is now based on https://github.com/bytecodealliance/wasm-interface-types which defines parsers/binary format/writers/etc for wasm-interface types. This is a pretty massive PR and unfortunately can't really be split up any more afaik. I don't really expect realistic review of all the code here (or commits), but some high-level changes are: * Interface types now consists of a set of "adapter functions". The IR in wasm-bindgen is modeled the same way not. * Each adapter function has a list of instructions, and these instructions work at a higher level than wasm itself, for example with strings. * The wasm-bindgen tool has a suite of instructions which are specific to it and not present in the standard. (like before with webidl bindings) * The anyref/multi-value transformations are now greatly simplified. They're simply "optimization passes" over adapter functions, removing instructions that are otherwise present. This way we don't have to juggle so much all over the place, and instructions always have the same meaning.
2019-12-03 11:16:44 -06:00
//! Definition of how to convert Rust types (`Description`) into wasm types
//! through adapter functions.
//!
//! Note that many Rust types use "nonstandard" instructions which only work in
//! the JS output, not for the "pure wasm interface types" output.
//!
//! Note that the mirror operation, going from WebAssembly to JS, is found in
//! the `outgoing.rs` module.
use crate::descriptor::Descriptor;
use crate::wit::InstructionData;
use crate::wit::{AdapterType, Instruction, InstructionBuilder, StackChange};
use anyhow::{bail, format_err, Error};
use walrus::ValType;
impl InstructionBuilder<'_, '_> {
/// Process a `Descriptor` as if it's being passed from JS to Rust. This
/// will skip `Unit` and otherwise internally add instructions necessary to
/// convert the foreign type into the Rust bits.
pub fn incoming(&mut self, arg: &Descriptor) -> Result<(), Error> {
if let Descriptor::Unit = arg {
return Ok(());
}
// This is a wrapper around `_incoming` to have a number of sanity checks
// that we don't forget things. We should always produce at least one
// wasm arge and exactly one webidl arg. Additionally the number of
// bindings should always match the number of webidl types for now.
let input_before = self.input.len();
let output_before = self.output.len();
self._incoming(arg)?;
assert_eq!(
input_before + 1,
self.input.len(),
"didn't push an input {:?}",
arg
);
assert!(
output_before < self.output.len(),
"didn't push more outputs {:?}",
arg
);
Ok(())
}
fn _incoming(&mut self, arg: &Descriptor) -> Result<(), Error> {
use walrus::ValType as WasmVT;
use wit_walrus::ValType as WitVT;
match arg {
Descriptor::Boolean => {
self.instruction(
&[AdapterType::Bool],
Instruction::I32FromBool,
&[AdapterType::I32],
);
}
Descriptor::Char => {
self.instruction(
&[AdapterType::String],
Instruction::I32FromStringFirstChar,
&[AdapterType::I32],
);
}
Descriptor::Anyref => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromAnyrefOwned,
&[AdapterType::I32],
);
}
Descriptor::RustStruct(class) => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromAnyrefRustOwned {
class: class.clone(),
},
&[AdapterType::I32],
);
}
Descriptor::I8 => self.number(WitVT::S8, WasmVT::I32),
Descriptor::U8 => self.number(WitVT::U8, WasmVT::I32),
Descriptor::I16 => self.number(WitVT::S16, WasmVT::I32),
Descriptor::U16 => self.number(WitVT::U16, WasmVT::I32),
Descriptor::I32 => self.number(WitVT::S32, WasmVT::I32),
Descriptor::U32 => self.number(WitVT::U32, WasmVT::I32),
Descriptor::I64 => self.number64(true),
Descriptor::U64 => self.number64(false),
Descriptor::F32 => {
self.get(AdapterType::F32);
self.output.push(AdapterType::F32);
}
Descriptor::F64 => {
self.get(AdapterType::F64);
self.output.push(AdapterType::F64);
}
Descriptor::Enum { .. } => self.number(WitVT::U32, WasmVT::I32),
Descriptor::Ref(d) => self.incoming_ref(false, d)?,
Descriptor::RefMut(d) => self.incoming_ref(true, d)?,
Descriptor::Option(d) => self.incoming_option(d)?,
Descriptor::String | Descriptor::CachedString => {
let std = wit_walrus::Instruction::StringToMemory {
malloc: self.cx.malloc()?,
mem: self.cx.memory()?,
};
self.instruction(
&[AdapterType::String],
Instruction::Standard(std),
&[AdapterType::I32, AdapterType::I32],
);
}
Descriptor::Vector(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!("unsupported argument type for calling Rust function from JS {:?}", arg)
})?;
self.instruction(
&[AdapterType::Vector(kind)],
Instruction::VectorToMemory {
kind,
malloc: self.cx.malloc()?,
mem: self.cx.memory()?,
},
&[AdapterType::I32, AdapterType::I32],
);
}
// Can't be passed from JS to Rust yet
Descriptor::Function(_) |
Descriptor::Closure(_) |
// Always behind a `Ref`
Descriptor::Slice(_) => bail!(
"unsupported argument type for calling Rust function from JS: {:?}",
arg
),
// nothing to do
Descriptor::Unit => {}
// Largely synthetic and can't show up
Descriptor::ClampedU8 => unreachable!(),
}
Ok(())
}
fn incoming_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::RustStruct(class) => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromAnyrefRustBorrow {
class: class.clone(),
},
&[AdapterType::I32],
);
}
Descriptor::Anyref => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromAnyrefBorrow,
&[AdapterType::I32],
);
}
Descriptor::String | Descriptor::CachedString => {
// This allocation is cleaned up once it's received in Rust.
let std = wit_walrus::Instruction::StringToMemory {
malloc: self.cx.malloc()?,
mem: self.cx.memory()?,
};
self.instruction(
&[AdapterType::String],
Instruction::Standard(std),
&[AdapterType::I32, AdapterType::I32],
);
}
Descriptor::Slice(_) => {
// like strings, this allocation is cleaned up after being
// received in Rust.
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported argument type for calling Rust function from JS {:?}",
arg
)
})?;
if mutable {
self.instruction(
&[AdapterType::Vector(kind)],
Instruction::MutableSliceToMemory {
kind,
malloc: self.cx.malloc()?,
mem: self.cx.memory()?,
free: self.cx.free()?,
},
&[AdapterType::I32, AdapterType::I32],
);
} else {
self.instruction(
&[AdapterType::Vector(kind)],
Instruction::VectorToMemory {
kind,
malloc: self.cx.malloc()?,
mem: self.cx.memory()?,
},
&[AdapterType::I32, AdapterType::I32],
);
}
}
_ => bail!(
"unsupported reference argument type for calling Rust function from JS: {:?}",
arg
),
}
Ok(())
}
fn incoming_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Anyref => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionAnyref,
&[AdapterType::I32],
);
}
Descriptor::I8 => self.in_option_sentinel(),
Descriptor::U8 => self.in_option_sentinel(),
Descriptor::I16 => self.in_option_sentinel(),
Descriptor::U16 => self.in_option_sentinel(),
Descriptor::I32 => self.in_option_native(ValType::I32),
Descriptor::U32 => self.in_option_native(ValType::I32),
Descriptor::F32 => self.in_option_native(ValType::F32),
Descriptor::F64 => self.in_option_native(ValType::F64),
Descriptor::I64 | Descriptor::U64 => {
let signed = match arg {
Descriptor::I64 => true,
_ => false,
};
self.instruction(
&[AdapterType::Anyref],
Instruction::I32SplitOption64 { signed },
&[AdapterType::I32; 3],
);
}
Descriptor::Boolean => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionBool,
&[AdapterType::I32],
);
}
Descriptor::Char => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionChar,
&[AdapterType::I32],
);
}
Descriptor::Enum { hole } => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionEnum { hole: *hole },
&[AdapterType::I32],
);
}
Descriptor::RustStruct(name) => {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionRust {
class: name.to_string(),
},
&[AdapterType::I32],
);
}
Descriptor::String | Descriptor::CachedString | Descriptor::Vector(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported optional slice type for calling Rust function from JS {:?}",
arg
)
})?;
let malloc = self.cx.malloc()?;
let mem = self.cx.memory()?;
self.instruction(
&[AdapterType::Anyref],
Instruction::OptionVector { kind, malloc, mem },
&[AdapterType::I32; 2],
);
}
_ => bail!(
"unsupported optional argument type for calling Rust function from JS: {:?}",
arg
),
}
Ok(())
}
pub fn get(&mut self, ty: AdapterType) {
self.input.push(ty);
// If we're generating instructions in the return position then the
// arguments are already on the stack to consume, otherwise we need to
// fetch them from the parameters.
if !self.return_position {
let idx = self.input.len() as u32 - 1;
let std = wit_walrus::Instruction::ArgGet(idx);
self.instructions.push(InstructionData {
instr: Instruction::Standard(std),
stack_change: StackChange::Modified {
pushed: 1,
popped: 0,
},
});
}
}
pub fn instruction(
&mut self,
inputs: &[AdapterType],
instr: Instruction,
outputs: &[AdapterType],
) {
// If we're generating instructions in the return position then the
// arguments are already on the stack to consume, otherwise we need to
// fetch them from the parameters.
if !self.return_position {
for input in inputs {
self.get(*input);
}
} else {
self.input.extend_from_slice(inputs);
}
self.instructions.push(InstructionData {
instr,
stack_change: StackChange::Modified {
popped: inputs.len(),
pushed: outputs.len(),
},
});
self.output.extend_from_slice(outputs);
}
fn number(&mut self, input: wit_walrus::ValType, output: walrus::ValType) {
let std = wit_walrus::Instruction::IntToWasm {
input,
output,
trap: false,
};
self.instruction(
&[AdapterType::from_wit(input)],
Instruction::Standard(std),
&[AdapterType::from_wasm(output).unwrap()],
);
}
fn number64(&mut self, signed: bool) {
self.instruction(
&[if signed {
AdapterType::S64
} else {
AdapterType::U64
}],
Instruction::I32Split64 { signed },
&[AdapterType::I32, AdapterType::I32],
);
}
fn in_option_native(&mut self, wasm: ValType) {
self.instruction(
&[AdapterType::Anyref],
Instruction::FromOptionNative { ty: wasm },
&[AdapterType::I32, AdapterType::from_wasm(wasm).unwrap()],
);
}
fn in_option_sentinel(&mut self) {
self.instruction(
&[AdapterType::Anyref],
Instruction::I32FromOptionU32Sentinel,
&[AdapterType::I32],
);
}
}