use crate::descriptor::{Descriptor, Function, SeqValue, SequenceCell, VectorKind};
use crate::wasm_conventions::get_function_table_entry;
use crate::wit::{AdapterType, ClosureDtor, Instruction, InstructionBuilder, OptionKind};
use crate::wit::{InstructionData, StackChange};
use anyhow::{bail, Error};
use walrus::{ExportId, ValType};
use wasm_bindgen_shared::identifier::to_valid_ident;
fn closure_word_descriptor(memory64: bool) -> Descriptor {
if memory64 {
Descriptor::I64AsF64
} else {
Descriptor::I32
}
}
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::I64AsF64 | Descriptor::U64AsF64 => {
self.outgoing_f64();
}
Descriptor::RawPointer => {
if self.cx.memory64() {
self.outgoing_f64();
} else {
self.outgoing_i32(AdapterType::U32);
}
}
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.get(AdapterType::F64);
self.output.push(AdapterType::Enum(name.clone()));
}
Descriptor::StringEnum { name, .. } => self.outgoing_string_enum(name),
Descriptor::DynamicUnion { name, .. } => self.outgoing_dynamic_union(name)?,
Descriptor::Char => {
self.instruction(
&[AdapterType::I32],
Instruction::StringFromChar,
&[AdapterType::String],
);
}
Descriptor::RustStruct { name: class, .. } => {
let ptr_ty = if self.cx.memory64() {
AdapterType::F64
} else {
self.ptr_ty()
};
self.instruction(
&[ptr_ty],
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 => {
let ptr_ty = self.outgoing_internal_word_ty();
self.get(ptr_ty.clone());
self.get(ptr_ty);
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(cell) => {
let kind = match cell.sequence_cell() {
SequenceCell::InPlace(kind) => kind,
SequenceCell::Encoded(elem) => return self.outgoing_encoded_slice(elem, None),
};
let mem = self.cx.memory()?;
let free = self.cx.free()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::VectorLoad {
kind: kind.clone(),
mem,
free,
mode: self.seq_value,
},
&[AdapterType::Vector(kind)],
);
}
Descriptor::Option(inner) => match &**inner {
Descriptor::Nullable(inner) => {
self.outgoing_option(inner, OptionKind::OptionNullable)?
}
inner => self.outgoing_option(inner, OptionKind::Option)?,
},
Descriptor::Nullable(inner) => self.outgoing_option(inner, OptionKind::Nullable)?,
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 => {
if self.cx.memory64() {
self.get(AdapterType::F64);
self.output.push(AdapterType::NonNull);
} else {
self.outgoing_i32(AdapterType::NonNull);
}
}
Descriptor::Closure(d) => {
self.outgoing_function(d.mutable, &d.function, Some(d.owned))?
}
Descriptor::SeqMode(mode, d) => {
if let Err(reason) = d.seq_mode_fit(*mode) {
bail!("`{mode:?}` can't apply to this argument: {reason}");
}
self.seq_value = mode.value;
let result = self.outgoing(d);
self.seq_value = SeqValue::View;
result?;
}
}
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 => {
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::MemoryToString(self.cx.memory()?),
&[AdapterType::String],
);
}
Descriptor::Slice(cell) => {
let kind = match cell.sequence_cell() {
SequenceCell::InPlace(kind) => kind,
SequenceCell::Encoded(elem) => return self.outgoing_encoded_slice(elem, None),
};
let mem = self.cx.memory()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::View {
kind: kind.clone(),
mem,
mode: self.seq_value,
},
&[AdapterType::Vector(kind)],
);
}
Descriptor::Function(descriptor) => {
self.outgoing_function(mutable, descriptor, None)?;
}
Descriptor::Ref(d) => self.outgoing_ref(mutable, d)?,
Descriptor::I8
| Descriptor::U8
| Descriptor::I16
| Descriptor::U16
| Descriptor::I32
| Descriptor::U32
| Descriptor::I64
| Descriptor::U64
| Descriptor::I64AsF64
| Descriptor::U64AsF64
| Descriptor::I128
| Descriptor::U128
| Descriptor::F32
| Descriptor::F64
| Descriptor::Boolean
| Descriptor::Char => self._outgoing(arg)?,
Descriptor::Option(inner) => {
self.outgoing_option_ref(mutable, inner, OptionKind::Option)?
}
Descriptor::Nullable(inner) => {
self.outgoing_option_ref(mutable, inner, OptionKind::Nullable)?
}
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();
let ptr_descriptor = closure_word_descriptor(self.cx.memory64());
descriptor.arguments.insert(0, ptr_descriptor.clone());
descriptor.arguments.insert(0, ptr_descriptor);
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)?;
let ptr_ty = self.ptr_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::Closure {
adapter,
nargs,
mutable,
dtor,
},
&[AdapterType::Function],
);
Ok(())
}
fn outgoing_option(&mut self, arg: &Descriptor, option_kind: OptionKind) -> Result<(), Error> {
match arg {
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::Externref.option_kind(option_kind)],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::NamedExternref(name.clone()).option_kind(option_kind)],
);
}
Descriptor::DynamicUnion { name, .. } => {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::DynamicUnion(name.clone()).option_kind(option_kind)],
);
}
Descriptor::I8 => self.out_option_sentinel32(AdapterType::S8, option_kind),
Descriptor::U8 => self.out_option_sentinel32(AdapterType::U8, option_kind),
Descriptor::I16 => self.out_option_sentinel32(AdapterType::S16, option_kind),
Descriptor::U16 => self.out_option_sentinel32(AdapterType::U16, option_kind),
Descriptor::I32 => self.out_option_sentinel64(AdapterType::S32, option_kind),
Descriptor::U32 => self.out_option_sentinel64(AdapterType::U32, option_kind),
Descriptor::I64AsF64 => self.out_option_sentinel64(AdapterType::I64, option_kind),
Descriptor::U64AsF64 => self.out_option_sentinel64(AdapterType::U64, option_kind),
Descriptor::I64 => self.option_native(true, ValType::I64, option_kind),
Descriptor::U64 => self.option_native(false, ValType::I64, option_kind),
Descriptor::F32 => self.out_option_sentinel64(AdapterType::F32, option_kind),
Descriptor::F64 => self.option_native(true, ValType::F64, option_kind),
Descriptor::I128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 {
signed: true,
kind: option_kind,
},
&[AdapterType::S128.option_kind(option_kind)],
);
}
Descriptor::U128 => {
self.instruction(
&[AdapterType::I32, AdapterType::I64, AdapterType::I64],
Instruction::OptionWasmToInt128 {
signed: false,
kind: option_kind,
},
&[AdapterType::U128.option_kind(option_kind)],
);
}
Descriptor::Boolean => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionBoolFromI32 { kind: option_kind },
&[AdapterType::Bool.option_kind(option_kind)],
);
}
Descriptor::Char => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionCharFromI32 { kind: option_kind },
&[AdapterType::String.option_kind(option_kind)],
);
}
Descriptor::Enum { name, hole, .. } => {
self.instruction(
&[AdapterType::F64],
Instruction::OptionEnumFromF64 {
hole: *hole,
kind: option_kind,
},
&[AdapterType::Enum(name.clone()).option_kind(option_kind)],
);
}
Descriptor::StringEnum { name, hole, .. } => {
self.instruction(
&[AdapterType::I32],
Instruction::OptionWasmToStringEnum {
name: name.clone(),
hole: *hole,
kind: option_kind,
},
&[AdapterType::StringEnum(name.clone()).option_kind(option_kind)],
);
}
Descriptor::RustStruct { name, .. } => {
let ptr_ty = if self.cx.memory64() {
AdapterType::F64
} else {
self.ptr_ty()
};
self.instruction(
&[ptr_ty],
Instruction::OptionRustFromI32 {
class: name.to_string(),
kind: option_kind,
},
&[AdapterType::Struct(name.clone()).option_kind(option_kind)],
);
}
Descriptor::Ref(d) => self.outgoing_option_ref(false, d, option_kind)?,
Descriptor::RefMut(d) => self.outgoing_option_ref(true, d, option_kind)?,
Descriptor::CachedString => self.cached_string(true)?,
Descriptor::String | Descriptor::Vector(_) => {
let kind = match arg {
Descriptor::Vector(cell) => match cell.sequence_cell() {
SequenceCell::InPlace(kind) => kind,
SequenceCell::Encoded(elem) => {
return self.outgoing_encoded_slice(elem, Some(option_kind))
}
},
_ => VectorKind::String,
};
let mem = self.cx.memory()?;
let free = self.cx.free()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::OptionVectorLoad {
kind: kind.clone(),
mem,
free,
mode: self.seq_value,
option_kind,
},
&[AdapterType::Vector(kind).option_kind(option_kind)],
);
}
Descriptor::NonNull if option_kind.nullable() => {
self.out_option_sentinel64(AdapterType::U32, option_kind)
}
Descriptor::NonNull => {
let ptr_ty = if self.cx.memory64() {
AdapterType::F64
} else {
self.ptr_ty()
};
self.instruction(
&[ptr_ty],
Instruction::OptionNonNullFromI32,
&[AdapterType::NonNull.option_kind(option_kind)],
);
}
Descriptor::RawPointer => {
self.out_option_sentinel64(AdapterType::U32, option_kind);
}
Descriptor::Option(_) | Descriptor::Nullable(_) => bail!(
"unsupported nested optional return type for calling Rust function from JS: \
{arg:?}; only `Option<Nullable<T>>` nests"
),
_ => 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::I64AsF64
| Descriptor::U64AsF64
| Descriptor::I128
| Descriptor::U128
| Descriptor::Boolean
| Descriptor::Char
| Descriptor::Enum { .. }
| Descriptor::StringEnum { .. }
| Descriptor::DynamicUnion { .. }
| Descriptor::RustStruct { .. }
| Descriptor::Ref(_)
| Descriptor::RefMut(_)
| Descriptor::CachedString
| Descriptor::Option(_)
| Descriptor::Nullable(_)
| Descriptor::Vector(_)
| Descriptor::Unit
| Descriptor::NonNull
| Descriptor::RawPointer => {
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 => {
let ptr_ty = self.outgoing_internal_word_ty();
self.get(ptr_ty.clone());
self.get(ptr_ty);
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::SeqMode(..)
| Descriptor::Result(_) => {
bail!("unsupported Result type for returning from exported Rust function: {arg:?}")
}
}
Ok(())
}
fn outgoing_option_ref(
&mut self,
_mutable: bool,
arg: &Descriptor,
option_kind: OptionKind,
) -> Result<(), Error> {
match arg {
Descriptor::Externref => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::Externref.option_kind(option_kind)],
);
}
Descriptor::NamedExternref(name) => {
self.instruction(
&[AdapterType::I32],
Instruction::TableGet,
&[AdapterType::NamedExternref(name.clone()).option_kind(option_kind)],
);
}
Descriptor::CachedString => self.cached_string(false)?,
Descriptor::String | Descriptor::Slice(_) => {
let kind = match arg {
Descriptor::Slice(cell) => match cell.sequence_cell() {
SequenceCell::InPlace(kind) => kind,
SequenceCell::Encoded(elem) => {
return self.outgoing_encoded_slice(elem, Some(option_kind))
}
},
_ => VectorKind::String,
};
let mem = self.cx.memory()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::OptionView {
kind: kind.clone(),
mem,
mode: self.seq_value,
option_kind,
},
&[AdapterType::Vector(kind).option_kind(option_kind)],
);
}
Descriptor::Ref(d) => self.outgoing_option_ref(_mutable, d, option_kind)?,
Descriptor::Nullable(d) => {
if option_kind != OptionKind::Option {
bail!("unsupported nested optional type for calling JS function from Rust: {arg:?}");
}
self.outgoing_option_ref(_mutable, d, OptionKind::OptionNullable)?
}
Descriptor::I8
| Descriptor::U8
| Descriptor::I16
| Descriptor::U16
| Descriptor::I32
| Descriptor::U32
| Descriptor::I64
| Descriptor::U64
| Descriptor::I64AsF64
| Descriptor::U64AsF64
| Descriptor::I128
| Descriptor::U128
| Descriptor::F32
| Descriptor::F64
| Descriptor::Boolean
| Descriptor::Char => self.outgoing_option(arg, option_kind)?,
_ => bail!(
"unsupported optional ref argument type for calling JS function from Rust: {arg:?}"
),
}
Ok(())
}
fn outgoing_encoded_slice(
&mut self,
elem: &Descriptor,
optional: Option<OptionKind>,
) -> Result<(), Error> {
let elem = match elem {
Descriptor::Ref(d) => &**d,
d => d,
};
let mut decode = InstructionBuilder {
input: Vec::new(),
output: Vec::new(),
instructions: Vec::new(),
cx: &mut *self.cx,
return_position: true,
seq_value: SeqValue::View,
};
match elem {
Descriptor::String
| Descriptor::CachedString
| Descriptor::Externref
| Descriptor::NamedExternref(_) => decode.outgoing_ref(false, elem)?,
Descriptor::Boolean
| Descriptor::Char
| Descriptor::Enum { .. }
| Descriptor::StringEnum { .. } => decode._outgoing(elem)?,
_ => {
bail!("unsupported slice element type for calling JS function from Rust: {elem:?}")
}
}
let InstructionBuilder {
input: words,
mut output,
instructions: elem,
..
} = decode;
let array = AdapterType::Array(Box::new(output.remove(0)));
let mem = self.cx.memory()?;
let free = self.cx.free()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::EncodedSliceLoad {
words,
elem,
mem,
free,
optional,
},
&[match optional {
Some(kind) => array.option_kind(kind),
None => array,
}],
);
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_dynamic_union(&mut self, name: &str) -> Result<(), Error> {
self.instruction(
&[AdapterType::I32],
Instruction::ExternrefLoadOwned {
table_and_drop: None,
},
&[AdapterType::DynamicUnion(name.to_string())],
);
Ok(())
}
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 outgoing_f64(&mut self) {
self.get(AdapterType::F64);
self.output.push(AdapterType::F64);
}
fn cached_string(&mut self, owned: bool) -> Result<(), Error> {
let mem = self.cx.memory()?;
let free = self.cx.free()?;
let ptr_ty = self.outgoing_internal_word_ty();
self.instruction(
&[ptr_ty.clone(), ptr_ty],
Instruction::CachedStringLoad {
owned,
mem,
free,
table: None,
},
&[AdapterType::String],
);
Ok(())
}
fn option_native(&mut self, signed: bool, ty: ValType, kind: OptionKind) {
let adapter_ty = AdapterType::from_wasm(ty).unwrap();
self.instruction(
&[AdapterType::I32, adapter_ty.clone()],
Instruction::ToOptionNative { signed, ty, kind },
&[adapter_ty.option_kind(kind)],
);
}
fn out_option_sentinel32(&mut self, ty: AdapterType, kind: OptionKind) {
self.instruction(
&[AdapterType::I32],
Instruction::OptionU32Sentinel { kind },
&[ty.option_kind(kind)],
);
}
fn out_option_sentinel64(&mut self, ty: AdapterType, kind: OptionKind) {
self.instruction(
&[AdapterType::F64],
Instruction::OptionF64Sentinel { kind },
&[ty.option_kind(kind)],
);
}
fn outgoing_internal_word_ty(&self) -> AdapterType {
if self.return_position && self.cx.memory64() {
AdapterType::F64
} else {
self.ptr_ty()
}
}
}
#[test]
fn closure_word_descriptor_uses_number_abi_on_memory64() {
assert_eq!(closure_word_descriptor(true), Descriptor::I64AsF64);
assert_eq!(closure_word_descriptor(false), Descriptor::I32);
}