use crate::descriptor::{Descriptor, Function};
use crate::wasm_conventions::get_function_table_entry;
use crate::wit::{AdapterType, ClosureDtor, Instruction, InstructionBuilder};
use crate::wit::{InstructionData, StackChange};
use anyhow::{bail, format_err, Error};
use walrus::{ExportId, ValType};
use wasm_bindgen_shared::identifier::to_valid_ident;
impl InstructionBuilder<'_, '_> {
pub fn outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> {
if let Descriptor::Unit = arg {
return Ok(());
}
let input_before = self.input.len();
let output_before = self.output.len();
self._outgoing(arg)?;
assert!(input_before < self.input.len());
if let Descriptor::Result(arg) = arg {
if let Descriptor::Unit = &**arg {
assert_eq!(output_before, self.output.len());
return Ok(());
}
}
assert_eq!(output_before + 1, self.output.len());
Ok(())
}
fn _outgoing(&mut self, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Boolean => {
self.instruction(
&[AdapterType::I32],
Instruction::BoolFromI32,
&[AdapterType::Bool],
);
}
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::Externref],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::NamedExternref(name.clone())],
);
}
Descriptor::I8 => self.outgoing_i32(AdapterType::S8),
Descriptor::U8 => self.outgoing_i32(AdapterType::U8),
Descriptor::I16 => self.outgoing_i32(AdapterType::S16),
Descriptor::U16 => self.outgoing_i32(AdapterType::U16),
Descriptor::I32 => self.outgoing_i32(AdapterType::S32),
Descriptor::U32 => self.outgoing_i32(AdapterType::U32),
Descriptor::I64 => self.outgoing_i64(AdapterType::I64),
Descriptor::U64 => self.outgoing_i64(AdapterType::U64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::I64, AdapterType::I64],
Instruction::WasmToInt128 { signed: true },
&[AdapterType::S128],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::I64, AdapterType::I64],
Instruction::WasmToInt128 { signed: false },
&[AdapterType::U128],
);
}
Descriptor::F32 => {
self.get(AdapterType::F32);
self.output.push(AdapterType::F32);
}
Descriptor::F64 => {
self.get(AdapterType::F64);
self.output.push(AdapterType::F64);
}
Descriptor::Enum { name, .. } => self.outgoing_i32(AdapterType::Enum(name.clone())),
Descriptor::StringEnum { name, .. } => self.outgoing_string_enum(name),
Descriptor::Char => {
self.instruction(
&[AdapterType::I32],
Instruction::StringFromChar,
&[AdapterType::String],
);
}
Descriptor::RustStruct(class) => {
self.instruction(
&[AdapterType::I32],
Instruction::RustFromI32 {
class: class.to_string(),
},
&[AdapterType::Struct(class.clone())],
);
}
Descriptor::Ref(d) => self.outgoing_ref(false, d)?,
Descriptor::RefMut(d) => self.outgoing_ref(true, d)?,
Descriptor::CachedString => self.cached_string(true)?,
Descriptor::String => {
self.get(AdapterType::I32);
self.get(AdapterType::I32);
let free = self.cx.free()?;
self.instructions.push(InstructionData {
instr: Instruction::DeferFree { free, align: 1 },
stack_change: StackChange::Modified {
popped: 2,
pushed: 2,
},
});
self.instructions.push(InstructionData {
instr: Instruction::MemoryToString(self.cx.memory()?),
stack_change: StackChange::Modified {
popped: 2,
pushed: 1,
},
});
self.output.push(AdapterType::String);
}
Descriptor::Vector(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported argument type for calling JS function from Rust {arg:?}"
)
})?;
let mem = self.cx.memory()?;
let free = self.cx.free()?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::VectorLoad {
kind: kind.clone(),
mem,
free,
},
&[AdapterType::Vector(kind)],
);
}
Descriptor::Option(d) => self.outgoing_option(d)?,
Descriptor::Result(d) => self.outgoing_result(d)?,
Descriptor::Function(descriptor) => {
self.outgoing_function(false, descriptor, None)?;
}
Descriptor::Slice(_) => {
bail!("unsupported argument type for calling JS function from Rust: {arg:?}")
}
Descriptor::Unit => {}
Descriptor::ClampedU8 => unreachable!(),
Descriptor::NonNull => self.outgoing_i32(AdapterType::NonNull),
Descriptor::Closure(d) => {
self.outgoing_function(d.mutable, &d.function, Some(d.owned))?
}
}
Ok(())
}
fn outgoing_ref(&mut self, mutable: bool, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::Externref],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::NamedExternref(name.clone())],
);
}
Descriptor::CachedString => self.cached_string(false)?,
Descriptor::String => {
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::MemoryToString(self.cx.memory()?),
&[AdapterType::String],
);
}
Descriptor::Slice(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported argument type for calling JS function from Rust {arg:?}"
)
})?;
let mem = self.cx.memory()?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::View {
kind: kind.clone(),
mem,
},
&[AdapterType::Vector(kind)],
);
}
Descriptor::Function(descriptor) => {
self.outgoing_function(mutable, descriptor, None)?;
}
Descriptor::RefMut(inner) => match inner.as_ref() {
Descriptor::Function(descriptor) => {
self.outgoing_function(true, descriptor, None)?;
}
_ => bail!(
"unsupported reference argument type for calling JS function from Rust: {arg:?}"
),
},
_ => bail!(
"unsupported reference argument type for calling JS function from Rust: {arg:?}"
),
}
Ok(())
}
fn export_table_element(&mut self, idx: u32) -> ExportId {
let module = &mut *self.cx.module;
let func_id = get_function_table_entry(module, idx).unwrap();
if let Some(export) = module
.exports
.iter()
.find(|e| matches!(e.item, walrus::ExportItem::Function(id) if id == func_id))
{
return export.id();
}
let name = match &module.funcs.get(func_id).name {
Some(name) => to_valid_ident(name),
None => format!("__wasm_bindgen_func_elem_{}", func_id.index()),
};
module.exports.add(&name, func_id)
}
fn outgoing_function(
&mut self,
mutable: bool,
descriptor: &Function,
owned_closure: Option<bool>,
) -> Result<(), Error> {
let mut descriptor = descriptor.clone();
let nargs = descriptor.arguments.len();
descriptor.arguments.insert(0, Descriptor::I32);
descriptor.arguments.insert(0, Descriptor::I32);
let shim = self.export_table_element(descriptor.shim_idx);
let dtor = match owned_closure {
None => ClosureDtor::Immediate,
Some(false) => ClosureDtor::Borrowed,
Some(true) => ClosureDtor::OwnClosure,
};
let adapter = self.cx.export_adapter(shim, descriptor)?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::Closure {
adapter,
nargs,
mutable,
dtor,
},
&[AdapterType::Function],
);
Ok(())
}
fn outgoing_option(&mut self, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::Externref.option()],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::NamedExternref(name.clone()).option()],
);
}
Descriptor::I8 => self.out_option_sentinel32(AdapterType::S8),
Descriptor::U8 => self.out_option_sentinel32(AdapterType::U8),
Descriptor::I16 => self.out_option_sentinel32(AdapterType::S16),
Descriptor::U16 => self.out_option_sentinel32(AdapterType::U16),
Descriptor::I32 => self.out_option_sentinel64(AdapterType::S32),
Descriptor::U32 => self.out_option_sentinel64(AdapterType::U32),
Descriptor::I64 => self.option_native(true, ValType::I64),
Descriptor::U64 => self.option_native(false, ValType::I64),
Descriptor::F32 => self.out_option_sentinel64(AdapterType::F32),
Descriptor::F64 => self.option_native(true, ValType::F64),
Descriptor::I128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 { signed: true },
&[AdapterType::S128.option()],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 { signed: false },
&[AdapterType::U128.option()],
);
}
Descriptor::Boolean => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionBoolFromI32,
&[AdapterType::Bool.option()],
);
}
Descriptor::Char => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionCharFromI32,
&[AdapterType::String.option()],
);
}
Descriptor::Enum { name, hole } => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionEnumFromI32 { hole: *hole },
&[AdapterType::Enum(name.clone()).option()],
);
}
Descriptor::StringEnum { name, .. } => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionWasmToStringEnum { name: name.clone() },
&[AdapterType::StringEnum(name.clone()).option()],
);
}
Descriptor::RustStruct(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionRustFromI32 {
class: name.to_string(),
},
&[AdapterType::Struct(name.clone()).option()],
);
}
Descriptor::Ref(d) => self.outgoing_option_ref(false, d)?,
Descriptor::RefMut(d) => self.outgoing_option_ref(true, d)?,
Descriptor::CachedString => self.cached_string(true)?,
Descriptor::String | Descriptor::Vector(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported optional slice type for calling JS function from Rust {arg:?}"
)
})?;
let mem = self.cx.memory()?;
let free = self.cx.free()?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::OptionVectorLoad {
kind: kind.clone(),
mem,
free,
},
&[AdapterType::Vector(kind).option()],
);
}
Descriptor::NonNull => self.instruction(
&[AdapterType::I32],
Instruction::OptionNonNullFromI32,
&[AdapterType::NonNull.option()],
),
_ => bail!(
"unsupported optional argument type for calling JS function from Rust: {arg:?}"
),
}
Ok(())
}
fn outgoing_result(&mut self, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Externref
| Descriptor::NamedExternref(_)
| Descriptor::I8
| Descriptor::U8
| Descriptor::I16
| Descriptor::U16
| Descriptor::I32
| Descriptor::U32
| Descriptor::F32
| Descriptor::F64
| Descriptor::I64
| Descriptor::U64
| Descriptor::I128
| Descriptor::U128
| Descriptor::Boolean
| Descriptor::Char
| Descriptor::Enum { .. }
| Descriptor::StringEnum { .. }
| Descriptor::RustStruct(_)
| Descriptor::Ref(_)
| Descriptor::RefMut(_)
| Descriptor::CachedString
| Descriptor::Option(_)
| Descriptor::Vector(_)
| Descriptor::Unit
| Descriptor::NonNull => {
self.instructions.push(InstructionData {
instr: Instruction::UnwrapResult {
table_and_drop: None,
},
stack_change: StackChange::Modified {
popped: 2,
pushed: 0,
},
});
let len = self.instructions.len();
self._outgoing(arg)?;
assert!(!self.instructions[len..]
.iter()
.any(|idata| matches!(idata.instr, Instruction::DeferFree { .. })));
self.get(AdapterType::I32);
self.get(AdapterType::I32);
}
Descriptor::String => {
self.get(AdapterType::I32);
self.get(AdapterType::I32);
self.get(AdapterType::I32);
self.get(AdapterType::I32);
self.instructions.push(InstructionData {
instr: Instruction::UnwrapResultString {
table_and_drop: None,
},
stack_change: StackChange::Modified {
popped: 4,
pushed: 2,
},
});
let free = self.cx.free()?;
self.instructions.push(InstructionData {
instr: Instruction::DeferFree { free, align: 1 },
stack_change: StackChange::Modified {
popped: 2,
pushed: 2,
},
});
self.instructions.push(InstructionData {
instr: Instruction::MemoryToString(self.cx.memory()?),
stack_change: StackChange::Modified {
popped: 2,
pushed: 1,
},
});
self.output.push(AdapterType::String);
}
Descriptor::ClampedU8
| Descriptor::Function(_)
| Descriptor::Closure(_)
| Descriptor::Slice(_)
| Descriptor::Result(_) => {
bail!("unsupported Result type for returning from exported Rust function: {arg:?}")
}
}
Ok(())
}
fn outgoing_option_ref(&mut self, _mutable: bool, arg: &Descriptor) -> Result<(), Error> {
match arg {
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::Externref.option()],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::NamedExternref(name.clone()).option()],
);
}
Descriptor::CachedString => self.cached_string(false)?,
Descriptor::String | Descriptor::Slice(_) => {
let kind = arg.vector_kind().ok_or_else(|| {
format_err!(
"unsupported optional slice type for calling JS function from Rust {arg:?}"
)
})?;
let mem = self.cx.memory()?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::OptionView {
kind: kind.clone(),
mem,
},
&[AdapterType::Vector(kind).option()],
);
}
_ => bail!(
"unsupported optional ref argument type for calling JS function from Rust: {arg:?}"
),
}
Ok(())
}
fn outgoing_string_enum(&mut self, name: &str) {
self.instruction(
&[AdapterType::I32],
Instruction::WasmToStringEnum {
name: name.to_string(),
},
&[AdapterType::StringEnum(name.to_string())],
);
}
fn outgoing_i32(&mut self, output: AdapterType) {
let instr = Instruction::WasmToInt32 {
unsigned_32: output == AdapterType::U32 || output == AdapterType::NonNull,
};
self.instruction(&[AdapterType::I32], instr, &[output]);
}
fn outgoing_i64(&mut self, output: AdapterType) {
let instr = Instruction::WasmToInt64 {
unsigned: output == AdapterType::U64,
};
self.instruction(&[AdapterType::I64], instr, &[output]);
}
fn cached_string(&mut self, owned: bool) -> Result<(), Error> {
let mem = self.cx.memory()?;
let free = self.cx.free()?;
self.instruction(
&[AdapterType::I32, AdapterType::I32],
Instruction::CachedStringLoad {
owned,
mem,
free,
table: None,
},
&[AdapterType::String],
);
Ok(())
}
fn option_native(&mut self, signed: bool, ty: ValType) {
let adapter_ty = AdapterType::from_wasm(ty).unwrap();
self.instruction(
&[AdapterType::I32, adapter_ty.clone()],
Instruction::ToOptionNative { signed, ty },
&[adapter_ty.option()],
);
}
fn out_option_sentinel32(&mut self, ty: AdapterType) {
self.instruction(
&[AdapterType::I32],
Instruction::OptionU32Sentinel,
&[ty.option()],
);
}
fn out_option_sentinel64(&mut self, ty: AdapterType) {
self.instruction(
&[AdapterType::F64],
Instruction::OptionF64Sentinel,
&[ty.option()],
);
}
}