use std::borrow::Cow;
use std::collections::HashMap;
use wasm_encoder::{BlockType, Instruction, MemArg, ValType};
use wit_bindgen_core::abi::{Bindgen, Bitcast, Instruction as AbiInst, WasmType};
use wit_parser::{Alignment, ArchitectureSize, FlagsRepr, Resolve, SizeAlign, Type};
use super::super::indices::LocalsBuilder;
use super::compat::{cast, flat_types};
use super::emit::wasm_type_to_val;
pub(crate) struct WasmEncoderBindgen<'a> {
main: Vec<Instruction<'static>>,
block_buffers: Vec<ActiveBlock>,
completed_blocks: Vec<CompletedBlock>,
sizes: &'a SizeAlign,
addr_local: u32,
indices: &'a mut LocalsBuilder,
param_flat_count: u32,
flat_cursor: u32,
store_tmp_by_valtype: HashMap<ValType, u32>,
}
struct ActiveBlock {
buffer: Vec<Instruction<'static>>,
iter_addr_local: Option<u32>,
start_cursor: u32,
is_variant_arm: bool,
}
struct CompletedBlock {
body: Vec<Instruction<'static>>,
iter_addr_local: Option<u32>,
start_cursor: u32,
end_cursor: u32,
}
impl<'a> WasmEncoderBindgen<'a> {
pub fn new(sizes: &'a SizeAlign, addr_local: u32, indices: &'a mut LocalsBuilder) -> Self {
Self {
main: Vec::new(),
block_buffers: Vec::new(),
completed_blocks: Vec::new(),
sizes,
addr_local,
indices,
param_flat_count: 0,
flat_cursor: 0,
store_tmp_by_valtype: HashMap::new(),
}
}
pub fn with_param_flat_count(mut self, count: u32) -> Self {
self.param_flat_count = count;
self
}
pub fn emit_set_addr_const(&mut self, value: i32) {
let addr_local = self.addr_local;
self.emit_one(Instruction::I32Const(value));
self.emit_one(Instruction::LocalSet(addr_local));
}
pub fn into_instructions(self) -> Vec<Instruction<'static>> {
assert!(
self.block_buffers.is_empty(),
"into_instructions called mid-block — push_block/finish_block unbalanced"
);
assert!(
self.completed_blocks.is_empty(),
"into_instructions called with unconsumed completed blocks \
(variant emit missing?)"
);
self.main
}
fn alloc_local(&mut self, ty: ValType) -> u32 {
self.indices.alloc_local(ty)
}
fn emit_one(&mut self, inst: Instruction<'static>) {
self.active_buf().push(inst);
}
fn active_buf(&mut self) -> &mut Vec<Instruction<'static>> {
match self.block_buffers.last_mut() {
Some(block) => &mut block.buffer,
None => &mut self.main,
}
}
fn current_addr_local(&self) -> u32 {
for block in self.block_buffers.iter().rev() {
if let Some(idx) = block.iter_addr_local {
return idx;
}
}
self.addr_local
}
fn emit_load(&mut self, offset: ArchitectureSize, load: LoadKind) {
let off = offset.size_wasm32() as u64;
let mem_arg = MemArg {
offset: off,
align: load.natural_align_log2(),
memory_index: 0,
};
let addr_local = self.current_addr_local();
self.emit_one(Instruction::LocalGet(addr_local));
self.emit_one(load.to_instruction(mem_arg));
}
fn emit_store(&mut self, offset: ArchitectureSize, store: StoreKind) {
let off = offset.size_wasm32() as u64;
let mem_arg = MemArg {
offset: off,
align: store.natural_align_log2(),
memory_index: 0,
};
let value_vt = store.value_valtype();
let tmp = self.alloc_store_tmp(value_vt);
let addr_local = self.current_addr_local();
self.emit_one(Instruction::LocalSet(tmp));
self.emit_one(Instruction::LocalGet(addr_local));
self.emit_one(Instruction::LocalGet(tmp));
self.emit_one(store.to_instruction(mem_arg));
}
fn alloc_store_tmp(&mut self, vt: ValType) -> u32 {
if let Some(&idx) = self.store_tmp_by_valtype.get(&vt) {
return idx;
}
let idx = self.indices.alloc_local(vt);
self.store_tmp_by_valtype.insert(vt, idx);
idx
}
fn emit_get_flat_slot(&mut self) {
let idx = self.flat_cursor;
assert!(
idx < self.param_flat_count,
"lift-to-flat past end of param flat (cursor={idx}, count={count})",
count = self.param_flat_count,
);
self.emit_one(Instruction::LocalGet(idx));
self.flat_cursor = idx + 1;
}
fn emit_bitcast(&mut self, bc: &Bitcast) {
use Bitcast::*;
match bc {
None => {}
I32ToI64 => self.emit_one(Instruction::I64ExtendI32U),
I64ToI32 => self.emit_one(Instruction::I32WrapI64),
F32ToI32 => self.emit_one(Instruction::I32ReinterpretF32),
I32ToF32 => self.emit_one(Instruction::F32ReinterpretI32),
F64ToI64 => self.emit_one(Instruction::I64ReinterpretF64),
I64ToF64 => self.emit_one(Instruction::F64ReinterpretI64),
F32ToI64 => {
self.emit_one(Instruction::I32ReinterpretF32);
self.emit_one(Instruction::I64ExtendI32U);
}
I64ToF32 => {
self.emit_one(Instruction::I32WrapI64);
self.emit_one(Instruction::F32ReinterpretI32);
}
PToI32 | I32ToP | I32ToL | LToI32 | PToL | LToP => {}
I64ToP64 | P64ToI64 => {}
PToP64 | LToI64 => self.emit_one(Instruction::I64ExtendI32U),
P64ToP | I64ToL => self.emit_one(Instruction::I32WrapI64),
Sequence(pair) => {
let [a, b] = pair.as_ref();
self.emit_bitcast(a);
self.emit_bitcast(b);
}
}
}
fn start_block(&mut self) {
self.block_buffers.push(ActiveBlock {
buffer: Vec::new(),
iter_addr_local: None,
start_cursor: self.flat_cursor,
is_variant_arm: false,
});
}
fn finish_block_body(&mut self) {
let active = self
.block_buffers
.pop()
.expect("finish_block without matching push_block");
let end_cursor = self.flat_cursor;
if active.is_variant_arm {
self.flat_cursor = active.start_cursor;
}
self.completed_blocks.push(CompletedBlock {
body: active.buffer,
iter_addr_local: active.iter_addr_local,
start_cursor: active.start_cursor,
end_cursor,
});
}
fn emit_variant_dispatch_for_lower(
&mut self,
resolve: &Resolve,
variant_type: &Type,
arm_types: &[Option<Type>],
) {
let n_arms = arm_types.len();
let joined = flat_types(resolve, variant_type, None).unwrap_or_else(|| {
panic!(
"variant flat must fit in MAX_FLAT_PARAMS ({}) — larger variants are invalid",
Resolve::MAX_FLAT_PARAMS,
)
});
let joined_total = joined.len() as u32;
let start = self
.completed_blocks
.len()
.checked_sub(n_arms)
.expect("fewer captured arm blocks than arms");
let arms: Vec<CompletedBlock> = self.completed_blocks.drain(start..).collect();
let variant_start = arms[0].start_cursor;
let arm_flats: Vec<Vec<WasmType>> = arm_types
.iter()
.map(|at| match at {
None => Vec::new(),
Some(ty) => flat_types(resolve, ty, None).unwrap_or_else(|| {
panic!(
"variant arm flat must fit in MAX_FLAT_PARAMS ({}) — larger arms invalid",
Resolve::MAX_FLAT_PARAMS,
)
}),
})
.collect();
let disc_param_local = variant_start;
let disc_local = self.alloc_local(ValType::I32);
self.emit_one(Instruction::LocalGet(disc_param_local));
self.emit_one(Instruction::LocalSet(disc_local));
self.emit_one(Instruction::Block(BlockType::Empty)); self.emit_one(Instruction::Block(BlockType::Empty)); for _ in 0..n_arms {
self.emit_one(Instruction::Block(BlockType::Empty)); }
self.emit_one(Instruction::LocalGet(disc_local));
let table: Cow<'static, [u32]> = Cow::Owned((0..n_arms as u32).collect());
self.emit_one(Instruction::BrTable(table, n_arms as u32));
self.emit_one(Instruction::End);
for (i, (arm, arm_flat)) in arms.iter().zip(&arm_flats).enumerate() {
let arm_locals: Vec<u32> = arm_flat
.iter()
.map(|wt| self.alloc_local(wasm_type_to_val(*wt)))
.collect();
for (m, &arm_wt) in arm_flat.iter().enumerate() {
let joined_local = variant_start + 1 + m as u32;
self.emit_one(Instruction::LocalGet(joined_local));
self.emit_bitcast(&cast(joined[m + 1], arm_wt));
self.emit_one(Instruction::LocalSet(arm_locals[m]));
}
let block_range = (arm.start_cursor, arm.end_cursor);
self.replay_block_with_arm_locals(&arm.body, block_range, &arm_locals);
let depth = (n_arms - i) as u32;
self.emit_one(Instruction::Br(depth));
self.emit_one(Instruction::End); }
self.emit_one(Instruction::Unreachable);
self.emit_one(Instruction::End);
self.flat_cursor = variant_start + joined_total;
}
fn replay_block_with_arm_locals(
&mut self,
body: &[Instruction<'static>],
block_range: (u32, u32),
arm_locals: &[u32],
) {
let (block_start, _) = block_range;
for inst in body {
let mapped = match inst {
Instruction::LocalGet(k) if *k >= block_range.0 && *k < block_range.1 => {
let pos = (*k - block_start) as usize;
debug_assert!(
pos < arm_locals.len(),
"arm-local pos {pos} >= arm_locals.len() ({})",
arm_locals.len(),
);
Instruction::LocalGet(arm_locals[pos])
}
other => other.clone(),
};
self.emit_one(mapped);
}
}
fn replay_block_remapped(
&mut self,
body: &[Instruction<'static>],
block_range: (u32, u32),
new_base: u32,
) {
let (block_start, block_end) = block_range;
for inst in body {
let mapped = match inst {
Instruction::LocalGet(k) if *k >= block_start && *k < block_end => {
Instruction::LocalGet(new_base + (*k - block_start))
}
other => other.clone(),
};
self.emit_one(mapped);
}
}
fn emit_const_zero(&mut self, wt: WasmType) {
use WasmType::*;
let inst = match wt {
I32 | Pointer | Length => Instruction::I32Const(0),
I64 | PointerOrI64 => Instruction::I64Const(0),
F32 => Instruction::F32Const(0.0f32.into()),
F64 => Instruction::F64Const(0.0f64.into()),
};
self.emit_one(inst);
}
fn emit_variant_dispatch(
&mut self,
resolve: &Resolve,
variant_type: &Type,
arm_types: &[Option<Type>],
) {
let joined = flat_types(resolve, variant_type, None).unwrap_or_else(|| {
panic!(
"variant flat must fit in MAX_FLAT_PARAMS ({}) — larger variants are invalid \
per the canonical ABI spec",
Resolve::MAX_FLAT_PARAMS
)
});
assert!(
!joined.is_empty(),
"variant joined flat must include at least a discriminant"
);
let joined_payload: Vec<WasmType> = joined[1..].to_vec();
let disc_local = self.alloc_local(ValType::I32);
self.emit_one(Instruction::LocalSet(disc_local));
let payload_locals: Vec<u32> = joined_payload
.iter()
.map(|wt| self.alloc_local(wasm_type_to_val(*wt)))
.collect();
let n = arm_types.len();
let start = self
.completed_blocks
.len()
.checked_sub(n)
.expect("fewer captured arm blocks than arms");
let arm_blocks: Vec<CompletedBlock> = self.completed_blocks.drain(start..).collect();
let arm_flats: Vec<Vec<WasmType>> = arm_types
.iter()
.map(|opt_ty| match opt_ty {
None => Vec::new(),
Some(ty) => flat_types(resolve, ty, None).unwrap_or_else(|| {
panic!(
"arm flat must fit in MAX_FLAT_PARAMS ({}) — larger arms are invalid \
per the canonical ABI spec",
Resolve::MAX_FLAT_PARAMS
)
}),
})
.collect();
self.emit_one(Instruction::Block(BlockType::Empty)); self.emit_one(Instruction::Block(BlockType::Empty)); for _ in 0..n {
self.emit_one(Instruction::Block(BlockType::Empty)); }
self.emit_one(Instruction::LocalGet(disc_local));
let table: Cow<'static, [u32]> = Cow::Owned((0..n as u32).collect());
self.emit_one(Instruction::BrTable(table, n as u32));
self.emit_one(Instruction::End);
for (i, arm) in arm_blocks.iter().enumerate() {
let arm_flat = &arm_flats[i];
for inst in &arm.body {
self.emit_one(inst.clone());
}
for j in (0..arm_flat.len()).rev() {
self.emit_bitcast(&cast(arm_flat[j], joined_payload[j]));
self.emit_one(Instruction::LocalSet(payload_locals[j]));
}
for j in arm_flat.len()..joined_payload.len() {
self.emit_const_zero(joined_payload[j]);
self.emit_one(Instruction::LocalSet(payload_locals[j]));
}
let depth = (n - i) as u32;
self.emit_one(Instruction::Br(depth));
self.emit_one(Instruction::End);
}
self.emit_one(Instruction::Unreachable);
self.emit_one(Instruction::End);
self.emit_one(Instruction::LocalGet(disc_local));
for idx in &payload_locals {
self.emit_one(Instruction::LocalGet(*idx));
}
}
}
#[derive(Clone, Copy)]
enum LoadKind {
I32Load,
I32Load8U,
I32Load8S,
I32Load16U,
I32Load16S,
I64Load,
F32Load,
F64Load,
}
impl LoadKind {
fn to_instruction(self, mem_arg: MemArg) -> Instruction<'static> {
match self {
LoadKind::I32Load => Instruction::I32Load(mem_arg),
LoadKind::I32Load8U => Instruction::I32Load8U(mem_arg),
LoadKind::I32Load8S => Instruction::I32Load8S(mem_arg),
LoadKind::I32Load16U => Instruction::I32Load16U(mem_arg),
LoadKind::I32Load16S => Instruction::I32Load16S(mem_arg),
LoadKind::I64Load => Instruction::I64Load(mem_arg),
LoadKind::F32Load => Instruction::F32Load(mem_arg),
LoadKind::F64Load => Instruction::F64Load(mem_arg),
}
}
fn natural_align_log2(self) -> u32 {
match self {
LoadKind::I32Load8U | LoadKind::I32Load8S => 0,
LoadKind::I32Load16U | LoadKind::I32Load16S => 1,
LoadKind::I32Load | LoadKind::F32Load => 2,
LoadKind::I64Load | LoadKind::F64Load => 3,
}
}
}
#[derive(Clone, Copy)]
enum StoreKind {
I32Store,
I32Store8,
I32Store16,
I64Store,
F32Store,
F64Store,
}
impl StoreKind {
fn to_instruction(self, mem_arg: MemArg) -> Instruction<'static> {
match self {
StoreKind::I32Store => Instruction::I32Store(mem_arg),
StoreKind::I32Store8 => Instruction::I32Store8(mem_arg),
StoreKind::I32Store16 => Instruction::I32Store16(mem_arg),
StoreKind::I64Store => Instruction::I64Store(mem_arg),
StoreKind::F32Store => Instruction::F32Store(mem_arg),
StoreKind::F64Store => Instruction::F64Store(mem_arg),
}
}
fn natural_align_log2(self) -> u32 {
match self {
StoreKind::I32Store8 => 0,
StoreKind::I32Store16 => 1,
StoreKind::I32Store | StoreKind::F32Store => 2,
StoreKind::I64Store | StoreKind::F64Store => 3,
}
}
fn value_valtype(self) -> ValType {
match self {
StoreKind::I32Store | StoreKind::I32Store8 | StoreKind::I32Store16 => ValType::I32,
StoreKind::I64Store => ValType::I64,
StoreKind::F32Store => ValType::F32,
StoreKind::F64Store => ValType::F64,
}
}
}
impl Bindgen for WasmEncoderBindgen<'_> {
type Operand = ();
fn emit(
&mut self,
_resolve: &Resolve,
inst: &AbiInst<'_>,
operands: &mut Vec<()>,
results: &mut Vec<()>,
) {
match inst {
AbiInst::I32Load { offset } => {
self.emit_load(*offset, LoadKind::I32Load);
produce_n(results, 1);
}
AbiInst::I32Load8U { offset } => {
self.emit_load(*offset, LoadKind::I32Load8U);
produce_n(results, 1);
}
AbiInst::I32Load8S { offset } => {
self.emit_load(*offset, LoadKind::I32Load8S);
produce_n(results, 1);
}
AbiInst::I32Load16U { offset } => {
self.emit_load(*offset, LoadKind::I32Load16U);
produce_n(results, 1);
}
AbiInst::I32Load16S { offset } => {
self.emit_load(*offset, LoadKind::I32Load16S);
produce_n(results, 1);
}
AbiInst::I64Load { offset } => {
self.emit_load(*offset, LoadKind::I64Load);
produce_n(results, 1);
}
AbiInst::F32Load { offset } => {
self.emit_load(*offset, LoadKind::F32Load);
produce_n(results, 1);
}
AbiInst::F64Load { offset } => {
self.emit_load(*offset, LoadKind::F64Load);
produce_n(results, 1);
}
AbiInst::PointerLoad { offset } => {
self.emit_load(*offset, LoadKind::I32Load);
produce_n(results, 1);
}
AbiInst::LengthLoad { offset } => {
self.emit_load(*offset, LoadKind::I32Load);
produce_n(results, 1);
}
AbiInst::BoolFromI32
| AbiInst::S8FromI32
| AbiInst::U8FromI32
| AbiInst::S16FromI32
| AbiInst::U16FromI32
| AbiInst::S32FromI32
| AbiInst::U32FromI32
| AbiInst::S64FromI64
| AbiInst::U64FromI64
| AbiInst::CharFromI32
| AbiInst::F32FromCoreF32
| AbiInst::F64FromCoreF64 => {
produce_n(results, 1);
}
AbiInst::I32FromBool
| AbiInst::I32FromS8
| AbiInst::I32FromU8
| AbiInst::I32FromS16
| AbiInst::I32FromU16
| AbiInst::I32FromS32
| AbiInst::I32FromU32
| AbiInst::I64FromS64
| AbiInst::I64FromU64
| AbiInst::I32FromChar
| AbiInst::CoreF32FromF32
| AbiInst::CoreF64FromF64 => {
self.emit_get_flat_slot();
produce_n(results, 1);
}
AbiInst::I32Store { offset } => {
self.emit_store(*offset, StoreKind::I32Store);
}
AbiInst::I32Store8 { offset } => {
self.emit_store(*offset, StoreKind::I32Store8);
}
AbiInst::I32Store16 { offset } => {
self.emit_store(*offset, StoreKind::I32Store16);
}
AbiInst::I64Store { offset } => {
self.emit_store(*offset, StoreKind::I64Store);
}
AbiInst::F32Store { offset } => {
self.emit_store(*offset, StoreKind::F32Store);
}
AbiInst::F64Store { offset } => {
self.emit_store(*offset, StoreKind::F64Store);
}
AbiInst::PointerStore { offset } | AbiInst::LengthStore { offset } => {
self.emit_store(*offset, StoreKind::I32Store);
}
AbiInst::RecordLower { record, .. } => {
produce_n(results, record.fields.len());
}
AbiInst::TupleLower { tuple, .. } => {
produce_n(results, tuple.types.len());
}
AbiInst::EnumLower { .. } => {
self.emit_get_flat_slot();
produce_n(results, 1);
}
AbiInst::FlagsLower { flags, .. } => {
let n = match flags.repr() {
FlagsRepr::U8 | FlagsRepr::U16 => 1usize,
FlagsRepr::U32(n) => n,
};
for _ in 0..n {
self.emit_get_flat_slot();
}
produce_n(results, n);
}
AbiInst::StringLower { .. } | AbiInst::ListCanonLower { .. } => {
self.emit_get_flat_slot(); self.emit_get_flat_slot(); produce_n(results, 2);
}
AbiInst::HandleLower { .. }
| AbiInst::ErrorContextLower
| AbiInst::FutureLower { .. }
| AbiInst::StreamLower { .. } => {
self.emit_get_flat_slot();
produce_n(results, 1);
}
AbiInst::MapLower { .. } => {
let block = self
.completed_blocks
.pop()
.expect("MapLower without matching block");
self.flat_cursor = block.start_cursor;
self.emit_get_flat_slot(); self.emit_get_flat_slot(); produce_n(results, 2);
}
AbiInst::IterMapKey { .. } | AbiInst::IterMapValue { .. } => {
produce_n(results, 1);
}
AbiInst::I32Const { val } => {
self.emit_one(Instruction::I32Const(*val));
produce_n(results, 1);
}
AbiInst::IterElem { .. } => {
produce_n(results, 1);
}
AbiInst::VariantPayloadName => {
self.block_buffers
.last_mut()
.expect("VariantPayloadName outside a block")
.is_variant_arm = true;
produce_n(results, 1);
}
AbiInst::FixedLengthListLower { size, .. } => {
produce_n(results, *size as usize);
}
AbiInst::VariantLower {
variant,
ty,
results: r,
..
} => {
debug_assert!(
r.is_empty(),
"VariantLower in lower-flat (non-empty results) context not yet supported",
);
let arms: Vec<Option<Type>> = variant.cases.iter().map(|c| c.ty).collect();
self.emit_variant_dispatch_for_lower(_resolve, &Type::Id(*ty), &arms);
produce_n(results, r.len());
}
AbiInst::OptionLower {
payload,
ty,
results: r,
} => {
debug_assert!(r.is_empty());
let arms = vec![None, Some(**payload)];
self.emit_variant_dispatch_for_lower(_resolve, &Type::Id(*ty), &arms);
produce_n(results, r.len());
}
AbiInst::ResultLower {
result,
ty,
results: r,
} => {
debug_assert!(r.is_empty());
let arms = vec![result.ok, result.err];
self.emit_variant_dispatch_for_lower(_resolve, &Type::Id(*ty), &arms);
produce_n(results, r.len());
}
AbiInst::Bitcasts { casts } => {
for bc in casts.iter() {
self.emit_bitcast(bc);
}
produce_n(results, operands.len());
}
AbiInst::RecordLift { .. }
| AbiInst::TupleLift { .. }
| AbiInst::HandleLift { .. }
| AbiInst::FutureLift { .. }
| AbiInst::StreamLift { .. }
| AbiInst::EnumLift { .. }
| AbiInst::FlagsLift { .. }
| AbiInst::ErrorContextLift
| AbiInst::FixedLengthListLift { .. }
| AbiInst::StringLift
| AbiInst::ListCanonLift { .. }
| AbiInst::ListLift { .. } => {
produce_n(results, 1);
}
AbiInst::MapLift { .. } => {
self.completed_blocks
.pop()
.expect("MapLift without matching block");
produce_n(results, 1);
}
AbiInst::VariantLift { variant, ty, .. } => {
let arms: Vec<Option<Type>> = variant.cases.iter().map(|c| c.ty).collect();
self.emit_variant_dispatch(_resolve, &Type::Id(*ty), &arms);
produce_n(results, 1);
}
AbiInst::OptionLift { payload, ty } => {
let arms = vec![None, Some(**payload)];
self.emit_variant_dispatch(_resolve, &Type::Id(*ty), &arms);
produce_n(results, 1);
}
AbiInst::ResultLift { result, ty } => {
let arms = vec![result.ok, result.err];
self.emit_variant_dispatch(_resolve, &Type::Id(*ty), &arms);
produce_n(results, 1);
}
AbiInst::IterBasePointer => {
let need_alloc = self
.block_buffers
.last()
.expect("IterBasePointer must fire inside a block")
.iter_addr_local
.is_none();
if need_alloc {
let idx = self.indices.alloc_local(ValType::I32);
self.block_buffers
.last_mut()
.expect("IterBasePointer must fire inside a block")
.iter_addr_local = Some(idx);
}
produce_n(results, 1);
}
AbiInst::FixedLengthListLowerToMemory { element, size, .. } => {
let elem_size = self.sizes.size(element).size_wasm32() as u32;
let block = self
.completed_blocks
.pop()
.expect("FixedLengthListLowerToMemory without a matching block");
let iter_addr = block.iter_addr_local.expect(
"fixed-size-list block must have allocated an iter_addr_local via \
IterBasePointer",
);
let parent_addr = self.current_addr_local();
self.emit_one(Instruction::LocalGet(parent_addr));
self.emit_one(Instruction::LocalSet(iter_addr));
let elem_flat = block.end_cursor - block.start_cursor;
let block_range = (block.start_cursor, block.end_cursor);
for i in 0..*size {
if i > 0 {
self.emit_one(Instruction::LocalGet(iter_addr));
self.emit_one(Instruction::I32Const(elem_size as i32));
self.emit_one(Instruction::I32Add);
self.emit_one(Instruction::LocalSet(iter_addr));
}
let new_base = block.start_cursor + i * elem_flat;
self.replay_block_remapped(&block.body, block_range, new_base);
}
self.flat_cursor = block.start_cursor + *size * elem_flat;
produce_n(results, 0);
}
AbiInst::FixedLengthListLiftFromMemory { element, size, .. } => {
let elem_size = self.sizes.size(element).size_wasm32() as u32;
let block = self
.completed_blocks
.pop()
.expect("FixedLengthListLiftFromMemory without a matching block");
let iter_addr = block.iter_addr_local.expect(
"fixed-size-list block must have allocated an iter_addr_local via \
IterBasePointer",
);
let parent_addr = self.current_addr_local();
self.emit_one(Instruction::LocalGet(parent_addr));
self.emit_one(Instruction::LocalSet(iter_addr));
for i in 0..*size {
if i > 0 {
self.emit_one(Instruction::LocalGet(iter_addr));
self.emit_one(Instruction::I32Const(elem_size as i32));
self.emit_one(Instruction::I32Add);
self.emit_one(Instruction::LocalSet(iter_addr));
}
for inst in &block.body {
self.emit_one(inst.clone());
}
}
produce_n(results, 1);
}
other => unimplemented!(
"WasmEncoderBindgen::emit hit unsupported instruction: {:?}. \
This path is only exercised by lift_from_memory; other entry \
points aren't supported.",
other
),
}
}
fn return_pointer(&mut self, _size: ArchitectureSize, _align: Alignment) {
unimplemented!(
"return_pointer is only called on lowering paths; \
lift_from_memory never invokes it"
);
}
fn push_block(&mut self) {
self.start_block();
}
fn finish_block(&mut self, operand: &mut Vec<()>) {
operand.clear();
self.finish_block_body();
}
fn sizes(&self) -> &SizeAlign {
self.sizes
}
fn is_list_canonical(&self, _resolve: &Resolve, _element: &Type) -> bool {
true
}
}
fn produce_n(results: &mut Vec<()>, n: usize) {
for _ in 0..n {
results.push(());
}
}
#[cfg(test)]
impl WasmEncoderBindgen<'_> {
pub(crate) fn instructions(&self) -> &[Instruction<'static>] {
&self.main
}
}
#[cfg(test)]
mod tests {
use super::*;
use wit_bindgen_core::abi::{lift_from_memory, lower_to_memory};
use wit_parser::{Docs, Field, Record, Span, Stability, TypeDef, TypeDefKind, TypeOwner};
fn new_sizes(resolve: &Resolve) -> SizeAlign {
let mut s = SizeAlign::default();
s.fill(resolve);
s
}
fn count<F: Fn(&Instruction<'static>) -> bool>(bg: &WasmEncoderBindgen<'_>, pred: F) -> usize {
bg.instructions().iter().filter(|i| pred(i)).count()
}
#[test]
fn lift_u32_emits_one_load() {
let resolve = Resolve::default();
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::U32);
assert_eq!(count(&bg, |i| matches!(i, Instruction::LocalGet(_))), 1);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load(_))), 1);
}
#[test]
fn lift_u64_emits_i64_load() {
let resolve = Resolve::default();
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(4);
let mut bg = WasmEncoderBindgen::new(&sizes, 3, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::U64);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I64Load(_))), 1);
assert!(bg
.instructions()
.iter()
.any(|i| matches!(i, Instruction::LocalGet(3))));
}
#[test]
fn lift_record_emits_one_load_per_field() {
let mut resolve = Resolve::default();
let record_id = resolve.types.alloc(TypeDef {
name: Some("r".to_string()),
kind: TypeDefKind::Record(Record {
fields: vec![
Field {
name: "a".to_string(),
ty: Type::U32,
docs: Docs::default(),
span: Span::default(),
},
Field {
name: "b".to_string(),
ty: Type::U64,
docs: Docs::default(),
span: Span::default(),
},
Field {
name: "c".to_string(),
ty: Type::U8,
docs: Docs::default(),
span: Span::default(),
},
],
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(record_id));
assert_eq!(count(&bg, |i| matches!(i, Instruction::LocalGet(_))), 3);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load(_))), 1);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I64Load(_))), 1);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load8U(_))), 1);
}
#[test]
fn lift_string_emits_ptr_len_loads() {
let resolve = Resolve::default();
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::String);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load(_))), 2);
}
#[test]
fn lift_homogeneous_result_emits_dispatch_structure() {
let mut resolve = Resolve::default();
let result_id = resolve.types.alloc(TypeDef {
name: Some("r".to_string()),
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: Some(Type::U32),
err: Some(Type::U32),
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(result_id));
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load8U(_))), 1);
assert_eq!(count(&bg, |i| matches!(i, Instruction::Block(_))), 4);
assert_eq!(count(&bg, |i| matches!(i, Instruction::BrTable(_, _))), 1);
assert_eq!(count(&bg, |i| matches!(i, Instruction::Unreachable)), 1);
let _insts = bg.into_instructions();
assert_eq!(indices.freeze().locals, vec![ValType::I32, ValType::I32]);
}
#[test]
fn lift_heterogeneous_result_emits_widening() {
let mut resolve = Resolve::default();
let result_id = resolve.types.alloc(TypeDef {
name: Some("r".to_string()),
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: Some(Type::U8),
err: Some(Type::U64),
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(result_id));
assert_eq!(
count(&bg, |i| matches!(i, Instruction::I64ExtendI32U)),
1,
"ok (u8) arm should widen to i64 to match joined payload"
);
let _insts = bg.into_instructions();
assert_eq!(indices.freeze().locals, vec![ValType::I32, ValType::I64]);
}
#[test]
fn lift_option_pads_none_arm_with_zero() {
let mut resolve = Resolve::default();
let opt_id = resolve.types.alloc(TypeDef {
name: Some("o".to_string()),
kind: TypeDefKind::Option(Type::U32),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(opt_id));
assert!(
bg.instructions()
.iter()
.any(|i| matches!(i, Instruction::I32Const(0))),
"option's None arm should emit i32.const 0 to pad joined payload"
);
}
#[test]
fn lift_result_string_u64_widens_pointer_to_pointer_or_i64() {
let mut resolve = Resolve::default();
let result_id = resolve.types.alloc(TypeDef {
name: Some("r".to_string()),
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: Some(Type::String),
err: Some(Type::U64),
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(result_id));
assert_eq!(
count(&bg, |i| matches!(i, Instruction::I64ExtendI32U)),
1,
"ok (string) arm should widen Pointer to PointerOrI64"
);
let _insts = bg.into_instructions();
assert_eq!(
indices.freeze().locals,
vec![ValType::I32, ValType::I64, ValType::I32]
);
}
#[test]
fn lift_fixed_size_list_unrolls_n_loads() {
let mut resolve = Resolve::default();
let list_id = resolve.types.alloc(TypeDef {
name: Some("l".to_string()),
kind: TypeDefKind::FixedLengthList(Type::U32, 4),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(list_id));
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Load(_))), 4);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Add)), 3);
assert_eq!(count(&bg, |i| matches!(i, Instruction::I32Const(4))), 3);
let _insts = bg.into_instructions();
assert_eq!(indices.freeze().locals, vec![ValType::I32]);
}
#[test]
fn lift_result_list_u64_widens_pointer_to_pointer_or_i64() {
let mut resolve = Resolve::default();
let list_id = resolve.types.alloc(TypeDef {
name: Some("l".to_string()),
kind: TypeDefKind::List(Type::U8),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let result_id = resolve.types.alloc(TypeDef {
name: Some("r".to_string()),
kind: TypeDefKind::Result(wit_parser::Result_ {
ok: Some(Type::Id(list_id)),
err: Some(Type::U64),
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(1);
let mut bg = WasmEncoderBindgen::new(&sizes, 0, &mut indices);
lift_from_memory(&resolve, &mut bg, (), &Type::Id(result_id));
assert_eq!(
count(&bg, |i| matches!(i, Instruction::I64ExtendI32U)),
1,
"ok (list) arm should widen Pointer to PointerOrI64"
);
}
#[test]
fn lower_variant_arms_share_joined_payload_slot() {
let mut resolve = Resolve::default();
let variant_id = resolve.types.alloc(TypeDef {
name: Some("v".to_string()),
kind: TypeDefKind::Variant(wit_parser::Variant {
cases: vec![
wit_parser::Case {
name: "a".to_string(),
ty: Some(Type::U64),
docs: Docs::default(),
span: Span::default(),
},
wit_parser::Case {
name: "b".to_string(),
ty: Some(Type::U64),
docs: Docs::default(),
span: Span::default(),
},
wit_parser::Case {
name: "c".to_string(),
ty: Some(Type::U64),
docs: Docs::default(),
span: Span::default(),
},
],
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(2);
let addr_local = indices.alloc_local(ValType::I32);
let mut bg =
WasmEncoderBindgen::new(&sizes, addr_local, &mut indices).with_param_flat_count(2);
bg.emit_set_addr_const(0);
lower_to_memory(&resolve, &mut bg, (), (), &Type::Id(variant_id));
let insts = bg.into_instructions();
let count_inst =
|pred: fn(&Instruction<'_>) -> bool| insts.iter().filter(|i| pred(i)).count();
assert_eq!(count_inst(|i| matches!(i, Instruction::BrTable(_, _))), 1);
assert_eq!(count_inst(|i| matches!(i, Instruction::LocalGet(0))), 1);
assert_eq!(count_inst(|i| matches!(i, Instruction::LocalGet(1))), 3);
}
#[test]
fn lower_option_emits_dispatch() {
let mut resolve = Resolve::default();
let opt_id = resolve.types.alloc(TypeDef {
name: Some("o".to_string()),
kind: TypeDefKind::Option(Type::U32),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(2);
let addr_local = indices.alloc_local(ValType::I32);
let mut bg =
WasmEncoderBindgen::new(&sizes, addr_local, &mut indices).with_param_flat_count(2);
bg.emit_set_addr_const(0);
lower_to_memory(&resolve, &mut bg, (), (), &Type::Id(opt_id));
let insts = bg.into_instructions();
let count_inst =
|pred: fn(&Instruction<'_>) -> bool| insts.iter().filter(|i| pred(i)).count();
assert_eq!(count_inst(|i| matches!(i, Instruction::BrTable(_, _))), 1);
assert_eq!(count_inst(|i| matches!(i, Instruction::LocalGet(0))), 1);
assert_eq!(count_inst(|i| matches!(i, Instruction::LocalGet(1))), 1);
}
#[test]
fn lower_nested_variant_routes_through_replay() {
let mut resolve = Resolve::default();
let inner_id = resolve.types.alloc(TypeDef {
name: Some("inner".to_string()),
kind: TypeDefKind::Variant(wit_parser::Variant {
cases: ["p", "q", "r"]
.into_iter()
.map(|n| wit_parser::Case {
name: n.to_string(),
ty: Some(Type::U64),
docs: Docs::default(),
span: Span::default(),
})
.collect(),
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let outer_id = resolve.types.alloc(TypeDef {
name: Some("outer".to_string()),
kind: TypeDefKind::Variant(wit_parser::Variant {
cases: vec![
wit_parser::Case {
name: "x".to_string(),
ty: Some(Type::Id(inner_id)),
docs: Docs::default(),
span: Span::default(),
},
wit_parser::Case {
name: "y".to_string(),
ty: Some(Type::U8),
docs: Docs::default(),
span: Span::default(),
},
],
}),
owner: TypeOwner::None,
docs: Docs::default(),
stability: Stability::default(),
span: Span::default(),
});
let sizes = new_sizes(&resolve);
let mut indices = LocalsBuilder::new(3);
let addr_local = indices.alloc_local(ValType::I32);
let mut bg =
WasmEncoderBindgen::new(&sizes, addr_local, &mut indices).with_param_flat_count(3);
bg.emit_set_addr_const(0);
lower_to_memory(&resolve, &mut bg, (), (), &Type::Id(outer_id));
let insts = bg.into_instructions();
let count_inst =
|pred: fn(&Instruction<'_>) -> bool| insts.iter().filter(|i| pred(i)).count();
assert_eq!(count_inst(|i| matches!(i, Instruction::BrTable(_, _))), 2);
assert_eq!(count_inst(|i| matches!(i, Instruction::LocalGet(0))), 1);
}
}