use crate::Adapter;
use crate::metadata;
use std::collections::HashMap;
use std::slice;
use wasm_encoder::{BlockType, Function, InstructionSink, MemArg, ValType};
use wit_parser::abi::{AbiVariant, FlatTypes, WasmSignature, WasmType};
use wit_parser::{FlagsRepr, Handle, Int, Resolve, Type, TypeDefKind, TypeId};
macro_rules! intrinsics {
($(
$name:ident : [ $($params:tt)* ] -> [ $($results:tt)* ] = $str:tt,
)*) => (
pub struct WitInterpreterIntrinsics {
$(pub $name: u32,)*
}
impl WitInterpreterIntrinsics {
pub fn new(adapter: &mut crate::Adapter) -> Self {
Self {
$(
$name: {
let ty = adapter.define_ty([$($params)*], [$($results)*]);
adapter.import_func("env", $str, ty)
},
)*
}
}
}
)
}
intrinsics! {
initialize : [ValType::I32] -> [] = "wit_dylib_initialize",
cabi_realloc : [ValType::I32; 4] -> [ValType::I32] = "cabi_realloc",
dealloc_bytes : [ValType::I32; 5] -> [] = "wit_dylib_dealloc_bytes",
export_start : [ValType::I32] -> [ValType::I32] = "wit_dylib_export_start",
export_call : [ValType::I32; 2] -> [] = "wit_dylib_export_call",
export_async_call : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_export_async_call",
export_async_callback : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_export_async_callback",
export_finish : [ValType::I32; 2] -> [] = "wit_dylib_export_finish",
resource_dtor : [ValType::I32; 2] -> [] = "wit_dylib_resource_dtor",
pop_u8 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_u8",
pop_u16 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_u16",
pop_u32 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_u32",
pop_u64 : [ValType::I32] -> [ValType::I64] = "wit_dylib_pop_u64",
pop_s8 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_s8",
pop_s16 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_s16",
pop_s32 : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_s32",
pop_s64 : [ValType::I32] -> [ValType::I64] = "wit_dylib_pop_s64",
pop_bool : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_bool",
pop_char : [ValType::I32] -> [ValType::I32] = "wit_dylib_pop_char",
pop_f32 : [ValType::I32] -> [ValType::F32] = "wit_dylib_pop_f32",
pop_f64 : [ValType::I32] -> [ValType::F64] = "wit_dylib_pop_f64",
pop_flags : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_flags",
pop_enum : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_enum",
pop_borrow : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_borrow",
pop_own : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_own",
pop_future : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_future",
pop_stream : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_stream",
pop_record : [ValType::I32; 2] -> [] = "wit_dylib_pop_record",
pop_tuple : [ValType::I32; 2] -> [] = "wit_dylib_pop_tuple",
pop_variant : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_variant",
pop_option : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_option",
pop_result : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_result",
pop_string : [ValType::I32; 2] -> [ValType::I32] = "wit_dylib_pop_string",
pop_list : [ValType::I32; 3] -> [ValType::I32] = "wit_dylib_pop_list",
pop_iter_next : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter_next",
pop_iter : [ValType::I32; 2] -> [] = "wit_dylib_pop_iter",
push_bool : [ValType::I32; 2] -> [] = "wit_dylib_push_bool",
push_char : [ValType::I32; 2] -> [] = "wit_dylib_push_char",
push_u8 : [ValType::I32; 2] -> [] = "wit_dylib_push_u8",
push_s8 : [ValType::I32; 2] -> [] = "wit_dylib_push_s8",
push_u16 : [ValType::I32; 2] -> [] = "wit_dylib_push_u16",
push_s16 : [ValType::I32; 2] -> [] = "wit_dylib_push_s16",
push_u32 : [ValType::I32; 2] -> [] = "wit_dylib_push_u32",
push_s32 : [ValType::I32; 2] -> [] = "wit_dylib_push_s32",
push_u64 : [ValType::I32, ValType::I64] -> [] = "wit_dylib_push_u64",
push_s64 : [ValType::I32, ValType::I64] -> [] = "wit_dylib_push_s64",
push_f32 : [ValType::I32, ValType::F32] -> [] = "wit_dylib_push_f32",
push_f64 : [ValType::I32, ValType::F64] -> [] = "wit_dylib_push_f64",
push_string : [ValType::I32; 3] -> [] = "wit_dylib_push_string",
push_record : [ValType::I32; 2] -> [] = "wit_dylib_push_record",
push_tuple : [ValType::I32; 2] -> [] = "wit_dylib_push_tuple",
push_flags : [ValType::I32; 3] -> [] = "wit_dylib_push_flags",
push_enum : [ValType::I32; 3] -> [] = "wit_dylib_push_enum",
push_borrow : [ValType::I32; 3] -> [] = "wit_dylib_push_borrow",
push_own : [ValType::I32; 3] -> [] = "wit_dylib_push_own",
push_future : [ValType::I32; 3] -> [] = "wit_dylib_push_future",
push_stream : [ValType::I32; 3] -> [] = "wit_dylib_push_stream",
push_variant : [ValType::I32; 3] -> [] = "wit_dylib_push_variant",
push_option : [ValType::I32; 3] -> [] = "wit_dylib_push_option",
push_result : [ValType::I32; 3] -> [] = "wit_dylib_push_result",
push_list : [ValType::I32; 4] -> [ValType::I32] = "wit_dylib_push_list",
list_append : [ValType::I32; 2] -> [] = "wit_dylib_list_append",
}
pub fn import(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
abi: AbiVariant,
import_idx: u32,
) -> Function {
let sig = resolve.wasm_signature(abi, func);
let initial_locals = if abi.is_async() { 2 } else { 1 };
let mut c = FunctionCompiler::new(adapter, resolve, initial_locals);
let frame = c.stack_frame(func, &sig, abi);
c.allocate_stack_frame(&frame);
c.ctx = Some(TempLocal::new(0, ValType::I32));
let async_retptr_offset = async_import_retptr_offset(c.adapter, resolve, func);
c.lower_import_params(
func,
&sig,
|me| {
if abi.is_async() {
Memory {
addr: TempLocal::new(1, ValType::I32),
offset: 0,
}
} else {
let fp = me.fp.as_ref().unwrap();
Memory {
addr: TempLocal::new(fp.idx, fp.ty),
offset: frame.abi_param_offset.unwrap(),
}
}
},
|me| {
if abi.is_async() {
Memory {
addr: TempLocal::new(1, ValType::I32),
offset: async_retptr_offset.unwrap().try_into().unwrap(),
}
} else {
let fp = me.fp.as_ref().unwrap();
let AbiLoc::Memory(mem) = frame.abi_loc(fp).unwrap() else {
unreachable!()
};
mem
}
},
);
c.ins().call(import_idx);
if !abi.is_async() {
let tmp = if !sig.retptr && func.result.is_some() {
let tmp_ty = c.adapter.map_wasm_type(sig.results[0]);
Some(c.local_set_new_tmp(tmp_ty))
} else {
None
};
c.lift_import_results(
func,
&sig,
|| AbiLoc::Stack(slice::from_ref(tmp.as_ref().unwrap())),
|me| frame.abi_loc(me.fp.as_ref().unwrap()).unwrap(),
);
if let Some(tmp) = tmp {
c.free_temp_local(tmp);
}
}
c.deallocate_stack_frame(&frame);
c.finish()
}
pub fn async_import_abi_area_types<'a>(
resolve: &Resolve,
func: &'a wit_parser::Function,
) -> impl Iterator<Item = &'a Type> {
let sig = resolve.wasm_signature(AbiVariant::GuestImportAsync, func);
let include_params = sig.indirect_params;
func.params
.iter()
.filter_map(move |p| if include_params { Some(&p.ty) } else { None })
.chain(func.result.iter())
}
fn async_import_retptr_offset(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
) -> Option<u32> {
if !func.result.is_some() {
return None;
}
let types = async_import_abi_area_types(resolve, func);
let (size, _ty) = adapter.sizes.field_offsets(types).pop().unwrap();
Some(size.size_wasm32().try_into().unwrap())
}
pub fn lift_async_import_results(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
abi: AbiVariant,
) -> Function {
assert!(abi.is_async());
let sig = resolve.wasm_signature(abi, func);
let mut c = FunctionCompiler::new(adapter, resolve, 2);
c.ctx = Some(TempLocal::new(0, ValType::I32));
let async_retptr_offset = async_import_retptr_offset(c.adapter, resolve, func);
c.lift_import_results(
func,
&sig,
|| unreachable!(),
|_| {
AbiLoc::Memory(Memory {
addr: TempLocal::new(1, ValType::I32),
offset: async_retptr_offset.unwrap(),
})
},
);
c.finish()
}
pub fn export(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
abi: AbiVariant,
func_metadata_index: usize,
) -> Function {
let sig = resolve.wasm_signature(abi, func);
let mut c = FunctionCompiler::new(adapter, resolve, sig.params.len() as u32);
let frame = c.stack_frame(func, &sig, abi);
c.allocate_stack_frame(&frame);
let export_start = c.adapter.intrinsics().export_start;
c.ins().i32_const(func_metadata_index.try_into().unwrap());
c.ins().call(export_start);
c.ctx = Some(c.local_set_new_tmp(ValType::I32));
c.lift_export_params(func, &sig);
if abi.is_async() {
let export_async_call = c.adapter.intrinsics().export_async_call;
c.local_get_ctx();
let l_ctx = c.ctx.take().unwrap();
c.free_temp_local(l_ctx);
c.ins().i32_const(func_metadata_index.try_into().unwrap());
c.ins().call(export_async_call);
c.deallocate_stack_frame(&frame);
} else {
let export_call = c.adapter.intrinsics().export_call;
c.local_get_ctx();
c.ins().i32_const(func_metadata_index.try_into().unwrap());
c.ins().call(export_call);
c.lower_export_results(func, &sig, &frame);
let sp = c.adapter.stack_pointer();
c.ins().global_get(sp);
c.ins().i32_const(8);
c.ins().i32_sub();
let l_scratch = c.local_tee_new_tmp(ValType::I32);
c.ins().global_set(sp);
let l_ctx = c.ctx.take().unwrap();
c.ins().local_get(l_scratch.idx);
c.ins().local_get(l_ctx.idx);
c.ins().i32_store(MemArg {
memory_index: 0,
align: 2,
offset: 0,
});
c.free_temp_local(l_ctx);
c.free_temp_local(l_scratch);
if let Some(fp) = c.fp.take() {
c.free_temp_local(fp);
}
}
c.finish()
}
pub fn post_return(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
abi: AbiVariant,
func_metadata_index: usize,
) -> Function {
assert!(!abi.is_async());
let sig = resolve.wasm_signature(abi, func);
let mut c = FunctionCompiler::new(adapter, resolve, sig.results.len() as u32);
let frame = c.stack_frame(func, &sig, abi);
let sp = c.adapter.stack_pointer();
c.ins().global_get(sp);
let l_sp = c.local_set_new_tmp(ValType::I32);
c.ins().local_get(l_sp.idx);
c.ins().i32_load(MemArg {
memory_index: 0,
align: 2,
offset: 0,
});
c.ctx = Some(c.local_set_new_tmp(ValType::I32));
c.ins().local_get(l_sp.idx);
c.free_temp_local(l_sp);
c.ins().i32_const(8);
c.ins().i32_add();
c.ins().global_set(sp);
let export_finish = c.adapter.intrinsics().export_finish;
c.local_get_ctx();
c.ins().i32_const(func_metadata_index.try_into().unwrap());
c.ins().call(export_finish);
let ctx = c.ctx.take().unwrap();
c.free_temp_local(ctx);
if frame.size > 0 {
c.ins().global_get(sp);
c.fp = Some(c.local_set_new_tmp(ValType::I32));
c.deallocate_stack_frame(&frame);
}
c.finish()
}
pub fn task_return(
adapter: &mut Adapter,
resolve: &Resolve,
func: &wit_parser::Function,
abi: AbiVariant,
task_return_import: u32,
) -> Function {
assert!(abi.is_async());
let mut dummy_func = func.clone();
dummy_func.params = Vec::new();
if let Some(ty) = dummy_func.result.take() {
dummy_func.params.push(wit_parser::Param {
name: "x".to_string(),
ty,
span: Default::default(),
});
}
import(
adapter,
resolve,
&dummy_func,
AbiVariant::GuestImport,
task_return_import,
)
}
pub fn lift_payload(adapter: &mut Adapter, resolve: &Resolve, ty: Type) -> Function {
let mut c = FunctionCompiler::new(adapter, resolve, 2);
c.ctx = Some(TempLocal::new(0, ValType::I32));
c.lift(
&AbiLoc::Memory(Memory {
addr: TempLocal::new(1, ValType::I32),
offset: 0,
}),
ty,
);
c.finish()
}
pub fn lower_payload(adapter: &mut Adapter, resolve: &Resolve, ty: Type) -> Function {
let mut c = FunctionCompiler::new(adapter, resolve, 2);
c.ctx = Some(TempLocal::new(0, ValType::I32));
let frame = StackFrame {
size: 16,
abi_param_offset: None,
retptr_offset: None,
stack_temp_offset: Some(0),
};
c.allocate_stack_frame(&frame);
c.lower(
ty,
&AbiLoc::Memory(Memory {
addr: TempLocal::new(1, ValType::I32),
offset: 0,
}),
);
c.deallocate_stack_frame(&frame);
c.finish()
}
struct FunctionCompiler<'a> {
adapter: &'a mut Adapter,
resolve: &'a Resolve,
bytecode: Vec<u8>,
locals: Vec<(u32, ValType)>,
free_locals: HashMap<ValType, Vec<u32>>,
nlocals: u32,
fp: Option<TempLocal>,
stack_temp_offset: Option<u32>,
ctx: Option<TempLocal>,
}
impl<'a> FunctionCompiler<'a> {
fn new(adapter: &'a mut Adapter, resolve: &'a Resolve, nlocals: u32) -> FunctionCompiler<'a> {
FunctionCompiler {
adapter,
resolve,
bytecode: Vec::new(),
locals: Vec::new(),
free_locals: HashMap::new(),
nlocals,
ctx: None,
fp: None,
stack_temp_offset: None,
}
}
fn ins(&mut self) -> InstructionSink<'_> {
InstructionSink::new(&mut self.bytecode)
}
fn local_tee_new_tmp(&mut self, ty: ValType) -> TempLocal {
let local = self.gen_temp_local(ty);
self.ins().local_tee(local.idx);
local
}
fn local_set_new_tmp(&mut self, ty: ValType) -> TempLocal {
let local = self.gen_temp_local(ty);
self.ins().local_set(local.idx);
local
}
fn local_get_ctx(&mut self) {
let idx = self.ctx.as_ref().unwrap().idx;
self.ins().local_get(idx);
}
fn local_get_stack_temp_addr(&mut self) {
let fp = self.fp.as_ref().unwrap().idx;
self.ins().local_get(fp);
let off = self.stack_temp_offset.unwrap();
self.ins().i32_const(off.try_into().unwrap());
self.ins().i32_add();
}
fn gen_temp_local(&mut self, ty: ValType) -> TempLocal {
if let Some(idx) = self.free_locals.get_mut(&ty).and_then(|v| v.pop()) {
return TempLocal {
ty,
idx,
needs_free: true,
};
}
let locals = &mut self.locals;
match locals.last_mut() {
Some((cnt, prev_ty)) if ty == *prev_ty => *cnt += 1,
_ => locals.push((1, ty)),
}
self.nlocals += 1;
let idx = self.nlocals - 1;
TempLocal {
ty,
idx,
needs_free: true,
}
}
fn free_temp_local(&mut self, mut local: TempLocal) {
assert!(local.needs_free);
self.free_locals
.entry(local.ty)
.or_insert(Vec::new())
.push(local.idx);
local.needs_free = false;
}
fn finish(self) -> Function {
let mut func = Function::new(self.locals);
func.raw(self.bytecode);
func.instructions().end();
func
}
fn deallocate_stack_frame(&mut self, frame: &StackFrame) {
if frame.size == 0 {
return;
}
let fp = self.fp.take().unwrap();
self.stack_temp_offset = None;
let sp = self.adapter.stack_pointer();
self.ins().local_get(fp.idx);
self.ins().i32_const(frame.size as i32);
self.ins().i32_add();
self.ins().global_set(sp);
self.free_temp_local(fp);
}
fn stack_frame(
&self,
func: &wit_parser::Function,
sig: &WasmSignature,
abi: AbiVariant,
) -> StackFrame {
let (ret_size, ret_align) = if sig.retptr && !abi.is_async() {
let info = self.adapter.sizes.record(&func.result);
(
info.size.size_wasm32().try_into().unwrap(),
info.align.align_wasm32().try_into().unwrap(),
)
} else {
(0, 1)
};
let (abi_param_size, abi_param_align) = match abi {
AbiVariant::GuestImport if sig.indirect_params => {
let info = self.adapter.sizes.params(func.params.iter().map(|p| &p.ty));
(
info.size.size_wasm32().try_into().unwrap(),
info.align.align_wasm32().try_into().unwrap(),
)
}
AbiVariant::GuestImport => (0, 1),
AbiVariant::GuestExport => (0, 1),
AbiVariant::GuestImportAsync => (0, 1),
AbiVariant::GuestExportAsync => (0, 1),
AbiVariant::GuestExportAsyncStackful => todo!("async"),
};
let (stack_temp_size, stack_temp_align) = (4, 4);
let mut offset = 0;
let mut bump = |size, align| {
assert!(align <= 16);
if size == 0 {
None
} else {
let ret = align_up(offset, align);
offset = ret + size;
Some(ret.try_into().unwrap())
}
};
let abi_param_offset = bump(abi_param_size, abi_param_align);
let retptr_offset = bump(ret_size, ret_align);
let stack_temp_offset = bump(stack_temp_size, stack_temp_align);
StackFrame {
size: align_up(offset, 16).try_into().unwrap(),
abi_param_offset,
retptr_offset,
stack_temp_offset,
}
}
fn allocate_stack_frame(&mut self, frame: &StackFrame) {
assert!(self.fp.is_none());
assert!(self.stack_temp_offset.is_none());
if frame.size == 0 {
return;
}
assert!(frame.size % 16 == 0);
let sp = self.adapter.stack_pointer();
self.ins().global_get(sp);
self.ins().i32_const(frame.size as i32);
self.ins().i32_sub();
self.fp = Some(self.local_tee_new_tmp(ValType::I32));
self.stack_temp_offset = frame.stack_temp_offset;
self.ins().global_set(sp);
}
fn lower_import_params(
&mut self,
func: &wit_parser::Function,
sig: &WasmSignature,
indirect_params: impl FnOnce(&Self) -> Memory,
retptr: impl FnOnce(&Self) -> Memory,
) {
let tys = func.params.iter().map(|p| &p.ty);
if sig.indirect_params {
let mem = indirect_params(self);
let dsts = self.record_field_locs(&AbiLoc::Memory(mem.bump(0)), tys.clone());
for (param, dst) in tys.zip(dsts) {
self.lower(*param, &dst);
}
self.ins().local_get(mem.addr.idx);
self.ins().i32_const(mem.offset.try_into().unwrap());
self.ins().i32_add();
} else {
let mut locals = sig
.params
.iter()
.map(|ty| self.gen_temp_local(self.adapter.map_wasm_type(*ty)))
.collect::<Vec<_>>();
if sig.retptr {
self.free_temp_local(locals.pop().unwrap());
}
let dsts =
self.record_field_locs(&AbiLoc::Stack(&locals), func.params.iter().map(|p| &p.ty));
for (param, dst) in tys.zip(dsts) {
self.lower(*param, &dst);
}
for local in locals {
self.ins().local_get(local.idx);
self.free_temp_local(local);
}
}
if sig.retptr {
let mem = retptr(self);
self.ins().local_get(mem.addr.idx);
self.ins().i32_const(mem.offset as i32);
self.ins().i32_add();
}
}
fn lift_import_results<'b>(
&mut self,
func: &wit_parser::Function,
sig: &WasmSignature,
stack_src: impl FnOnce() -> AbiLoc<'b>,
retptr_src: impl FnOnce(&Self) -> AbiLoc<'b>,
) {
let ty = match func.result {
Some(ty) => ty,
None => {
assert!(!sig.retptr);
return;
}
};
let src = if sig.retptr {
retptr_src(self)
} else {
assert_eq!(sig.results.len(), 1);
stack_src()
};
self.lift(&src, ty);
}
fn lift_export_params(&mut self, func: &wit_parser::Function, sig: &WasmSignature) {
let temps;
let src = if sig.indirect_params {
AbiLoc::Memory(Memory {
addr: TempLocal::new(0, ValType::I32),
offset: 0,
})
} else {
temps = sig
.params
.iter()
.enumerate()
.map(|(i, ty)| TempLocal::new(i as u32, self.adapter.map_wasm_type(*ty)))
.collect::<Vec<_>>();
AbiLoc::Stack(&temps)
};
let srcs = self.record_field_locs(&src, func.params.iter().map(|p| &p.ty));
for (param, src) in func.params.iter().zip(srcs) {
self.lift(&src, param.ty);
}
if let AbiLoc::Memory(mem) = &src {
assert!(sig.indirect_params);
let info = self.adapter.sizes.record(func.params.iter().map(|p| &p.ty));
self.local_get_ctx();
self.ins().local_get(mem.addr.idx);
assert_eq!(mem.offset, 0);
self.ins()
.i32_const(info.size.size_wasm32().try_into().unwrap());
self.ins()
.i32_const(info.align.align_wasm32().try_into().unwrap());
self.ins().i32_const(0);
let dealloc_bytes = self.adapter.intrinsics().dealloc_bytes;
self.ins().call(dealloc_bytes);
}
}
fn lower_export_results(
&mut self,
func: &wit_parser::Function,
sig: &WasmSignature,
frame: &StackFrame,
) {
let ty = match func.result {
Some(ty) => ty,
None => {
assert!(!sig.retptr);
return;
}
};
if sig.retptr {
assert_eq!(sig.results.len(), 1);
let dst = frame.abi_loc(self.fp.as_ref().unwrap()).unwrap();
self.lower(ty, &dst);
let AbiLoc::Memory(mem) = dst else {
unreachable!()
};
self.ins().local_get(mem.addr.idx);
self.ins().i32_const(mem.offset as i32);
self.ins().i32_add();
} else {
assert_eq!(sig.results.len(), 1);
let tmp_ty = self.adapter.map_wasm_type(sig.results[0]);
let tmp = self.gen_temp_local(tmp_ty);
let dst = AbiLoc::Stack(slice::from_ref(&tmp));
self.lower(ty, &dst);
self.ins().local_get(tmp.idx);
self.free_temp_local(tmp);
}
}
fn lower(&mut self, ty: Type, dest: &AbiLoc<'_>) {
let i = self.adapter.intrinsics();
match ty {
Type::Bool => self.pop_scalar(dest, ValType::I32, i.pop_bool, 0),
Type::Char => self.pop_scalar(dest, ValType::I32, i.pop_char, 2),
Type::U8 => self.pop_scalar(dest, ValType::I32, i.pop_u8, 0),
Type::S8 => self.pop_scalar(dest, ValType::I32, i.pop_s8, 0),
Type::U16 => self.pop_scalar(dest, ValType::I32, i.pop_u16, 1),
Type::S16 => self.pop_scalar(dest, ValType::I32, i.pop_s16, 1),
Type::U32 => self.pop_scalar(dest, ValType::I32, i.pop_u32, 2),
Type::S32 => self.pop_scalar(dest, ValType::I32, i.pop_s32, 2),
Type::U64 => self.pop_scalar(dest, ValType::I64, i.pop_u64, 3),
Type::S64 => self.pop_scalar(dest, ValType::I64, i.pop_s64, 3),
Type::F32 => self.pop_scalar(dest, ValType::F32, i.pop_f32, 2),
Type::F64 => self.pop_scalar(dest, ValType::F64, i.pop_f64, 3),
Type::ErrorContext => todo!("error-context"),
Type::String => {
let (dst_ptr, dst_len) = dest.split_ptr_len();
let pop_string = i.pop_string;
self.local_get_ctx();
self.local_get_stack_temp_addr();
self.ins().call(pop_string);
self.store_scalar_from_top_of_stack(&dst_len, ValType::I32, 2);
self.local_get_stack_temp_addr();
self.ins().i32_load(MemArg {
memory_index: 0,
offset: 0,
align: 2,
});
self.store_scalar_from_top_of_stack(&dst_ptr, ValType::I32, 2);
}
Type::Id(id) => self.lower_id(id, dest),
}
}
fn lower_id(&mut self, id: TypeId, dest: &AbiLoc<'_>) {
let ty = self.adapter.type_map[&id];
let i = self.adapter.intrinsics();
match &self.resolve.types[id].kind {
TypeDefKind::Type(t) => self.lower(*t, dest),
TypeDefKind::Handle(Handle::Borrow(_)) => {
let metadata::Type::Borrow(resource_index) = ty else {
unreachable!()
};
self.pop_typed_scalar(dest, resource_index, i.pop_borrow, 2);
}
TypeDefKind::Handle(Handle::Own(_)) => {
let metadata::Type::Own(resource_index) = ty else {
unreachable!()
};
self.pop_typed_scalar(dest, resource_index, i.pop_own, 2);
}
TypeDefKind::Future(_) => {
let metadata::Type::Future(future_index) = ty else {
unreachable!()
};
self.pop_typed_scalar(dest, future_index, i.pop_future, 2);
}
TypeDefKind::Stream(_) => {
let metadata::Type::Stream(stream_index) = ty else {
unreachable!()
};
self.pop_typed_scalar(dest, stream_index, i.pop_stream, 2);
}
TypeDefKind::Flags(f) => {
let metadata::Type::Flags(flags_index) = ty else {
unreachable!()
};
let size_log2 = match f.repr() {
FlagsRepr::U8 => 0,
FlagsRepr::U16 => 1,
FlagsRepr::U32(1) => 2,
FlagsRepr::U32(_) => unimplemented!(),
};
self.pop_typed_scalar(dest, flags_index, i.pop_flags, size_log2)
}
TypeDefKind::Enum(f) => {
let metadata::Type::Enum(enum_index) = ty else {
unreachable!()
};
let size_log2 = match f.tag() {
Int::U8 => 0,
Int::U16 => 1,
Int::U32 => 2,
Int::U64 => 3,
};
self.pop_typed_scalar(dest, enum_index, i.pop_enum, size_log2)
}
TypeDefKind::Record(r) => {
let metadata::Type::Record(record_index) = ty else {
unreachable!()
};
self.pop_record(
dest,
record_index,
i.pop_record,
r.fields.iter().map(|f| &f.ty),
);
}
TypeDefKind::Tuple(t) => {
let metadata::Type::Tuple(tuple_index) = ty else {
unreachable!()
};
self.pop_record(dest, tuple_index, i.pop_tuple, t.types.iter());
}
TypeDefKind::List(t) => {
let metadata::Type::List(list_index) = ty else {
unreachable!()
};
let pop_list = self.adapter.intrinsics().pop_list;
let pop_iter_next = self.adapter.intrinsics().pop_iter_next;
let pop_iter = self.adapter.intrinsics().pop_iter;
let cabi_realloc = self.adapter.intrinsics().cabi_realloc;
let size = self.adapter.sizes.size(t).size_wasm32();
let align = self.adapter.sizes.align(t).align_wasm32();
let (dst_ptr, dst_len) = dest.split_ptr_len();
self.local_get_ctx();
self.ins().i32_const(list_index.try_into().unwrap());
self.local_get_stack_temp_addr();
self.ins().call(pop_list);
let l_len = self.local_set_new_tmp(ValType::I32);
self.local_get_stack_temp_addr();
self.ins().i32_load(MemArg {
memory_index: 0,
offset: 0,
align: 2,
});
let l_ptr = self.local_set_new_tmp(ValType::I32);
self.ins().local_get(l_ptr.idx);
self.ins().i32_eqz();
self.ins().if_(BlockType::Empty);
{
self.ins().i32_const(0);
let l_index = self.local_set_new_tmp(ValType::I32);
self.ins().i32_const(0);
self.ins().i32_const(0);
self.ins().i32_const(align.try_into().unwrap());
self.ins().local_get(l_len.idx);
self.ins().i32_const(size.try_into().unwrap());
self.ins().i32_mul();
let l_byte_size = self.local_tee_new_tmp(ValType::I32);
self.ins().call(cabi_realloc);
let l_elem_addr = self.local_tee_new_tmp(ValType::I32);
self.ins().local_set(l_ptr.idx);
self.ins().loop_(BlockType::Empty);
{
self.ins().local_get(l_index.idx);
self.ins().local_get(l_len.idx);
self.ins().i32_ne();
self.ins().if_(BlockType::Empty);
{
self.local_get_ctx();
self.ins().i32_const(list_index.try_into().unwrap());
self.ins().call(pop_iter_next);
self.lower(
*t,
&AbiLoc::Memory(Memory {
addr: TempLocal::new(l_elem_addr.idx, ValType::I32),
offset: 0,
}),
);
self.ins().local_get(l_index.idx);
self.ins().i32_const(1);
self.ins().i32_add();
self.ins().local_set(l_index.idx);
self.ins().local_get(l_elem_addr.idx);
self.ins().i32_const(size.try_into().unwrap());
self.ins().i32_add();
self.ins().local_set(l_elem_addr.idx);
self.ins().br(1);
}
self.ins().end();
}
self.ins().end();
self.defer_deallocate_lowered_list(&l_ptr, &l_byte_size, align);
self.local_get_ctx();
self.ins().i32_const(list_index.try_into().unwrap());
self.ins().call(pop_iter);
self.free_temp_local(l_index);
self.free_temp_local(l_elem_addr);
self.free_temp_local(l_byte_size);
}
self.ins().end();
self.ins().local_get(l_ptr.idx);
self.store_scalar_from_top_of_stack(&dst_ptr, ValType::I32, 2);
self.ins().local_get(l_len.idx);
self.store_scalar_from_top_of_stack(&dst_len, ValType::I32, 2);
self.free_temp_local(l_len);
self.free_temp_local(l_ptr);
}
TypeDefKind::Variant(t) => {
let metadata::Type::Variant(variant_index) = ty else {
unreachable!()
};
self.pop_variant(
dest,
variant_index,
i.pop_variant,
t.tag(),
t.cases.iter().map(|c| c.ty.as_ref()),
);
}
TypeDefKind::Option(t) => {
let metadata::Type::Option(option_index) = ty else {
unreachable!()
};
self.pop_variant(dest, option_index, i.pop_option, Int::U8, [None, Some(t)]);
}
TypeDefKind::Result(t) => {
let metadata::Type::Result(result_index) = ty else {
unreachable!()
};
self.pop_variant(
dest,
result_index,
i.pop_result,
Int::U8,
[t.ok.as_ref(), t.err.as_ref()],
);
}
TypeDefKind::FixedLengthList(t, len) => {
let _ = (t, len);
todo!("fixed-length-list")
}
TypeDefKind::Map(k, v) => {
let _ = (k, v);
todo!("map")
}
TypeDefKind::Resource => unreachable!(),
TypeDefKind::Unknown => unreachable!(),
}
}
fn pop_record<'b>(
&mut self,
dest: &AbiLoc<'_>,
type_index: usize,
pop_intrinsic: u32,
types: impl IntoIterator<Item = &'b Type> + Clone,
) {
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().call(pop_intrinsic);
let dsts = self.record_field_locs(dest, types.clone());
for (ty, destination) in types.into_iter().zip(dsts) {
self.lower(*ty, &destination);
}
}
fn pop_variant<'b>(
&mut self,
dest: &AbiLoc<'_>,
type_index: usize,
pop_intrinsic: u32,
tag: Int,
payloads: impl IntoIterator<Item = Option<&'b Type>> + Clone,
) {
let (discr_dst, payload_dst) =
dest.split_discr_payload(self.adapter, tag, payloads.clone());
let tag_size_log2 = match tag {
Int::U8 => 0,
Int::U16 => 1,
Int::U32 => 2,
Int::U64 => 3,
};
let push_zeros = |me: &mut Self, locals: &[TempLocal]| {
for local in locals {
match local.ty {
ValType::I32 => me.ins().i32_const(0),
ValType::I64 => me.ins().i64_const(0),
ValType::F32 => me.ins().f32_const(0.0.into()),
ValType::F64 => me.ins().f64_const(0.0.into()),
_ => unreachable!(),
};
me.ins().local_set(local.idx);
}
};
let lower_payload = |me: &mut Self, ty: Option<&Type>| match ty {
Some(ty) => {
let (dst, zero_locals) = payload_dst.narrow_payload(me.resolve, ty);
me.lower(*ty, &dst);
if let Some(zero_locals) = zero_locals {
push_zeros(me, zero_locals);
}
}
None => match &payload_dst {
AbiLoc::Stack(types) => push_zeros(me, types),
AbiLoc::Memory(_) => {}
},
};
match payloads.clone().into_iter().count() {
2 => {
let mut iter = payloads.clone().into_iter();
let payload0 = iter.next().unwrap();
let payload1 = iter.next().unwrap();
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().call(pop_intrinsic);
self.ins().if_(BlockType::Empty);
self.ins().i32_const(1);
self.store_scalar_from_top_of_stack(&discr_dst, ValType::I32, tag_size_log2);
lower_payload(self, payload1);
self.ins().else_();
self.ins().i32_const(0);
self.store_scalar_from_top_of_stack(&discr_dst, ValType::I32, tag_size_log2);
lower_payload(self, payload0);
self.ins().end();
}
n => {
self.ins().block(BlockType::Empty);
for _ in 0..n {
self.ins().block(BlockType::Empty);
}
self.ins().block(BlockType::Empty);
self.ins().block(BlockType::Empty);
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().call(pop_intrinsic);
self.ins().br_table(1..(n + 1) as u32, 0);
self.ins().end();
self.ins().unreachable();
self.ins().end();
for (i, ty) in payloads.clone().into_iter().enumerate() {
self.ins().i32_const(i.try_into().unwrap());
self.store_scalar_from_top_of_stack(&discr_dst, ValType::I32, tag_size_log2);
lower_payload(self, ty);
self.ins().br((n - i) as u32);
self.ins().end();
}
self.ins().end();
}
}
}
fn pop_typed_scalar(
&mut self,
dest: &AbiLoc<'_>,
type_index: usize,
push_intrinsic: u32,
size_log2: u32,
) {
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().call(push_intrinsic);
self.store_scalar_from_top_of_stack(dest, ValType::I32, size_log2);
}
fn pop_scalar(&mut self, dest: &AbiLoc<'_>, ty: ValType, pop_intrinsic: u32, size_log2: u32) {
self.local_get_ctx();
self.ins().call(pop_intrinsic);
self.store_scalar_from_top_of_stack(dest, ty, size_log2);
}
fn store_scalar_from_top_of_stack(&mut self, dest: &AbiLoc<'_>, ty: ValType, size_log2: u32) {
match dest {
AbiLoc::Stack(locals) => {
assert_eq!(locals.len(), 1);
let local = &locals[0];
let dst_ty = local.ty;
match (ty, dst_ty) {
(ValType::I32, ValType::I32)
| (ValType::I64, ValType::I64)
| (ValType::F32, ValType::F32)
| (ValType::F64, ValType::F64) => {}
(ValType::F32, ValType::I32) => {
self.ins().i32_reinterpret_f32();
}
(ValType::I32, ValType::I64) => {
self.ins().i64_extend_i32_u();
}
(ValType::F64, ValType::I64) => {
self.ins().i64_reinterpret_f64();
}
(ValType::F32, ValType::I64) => {
self.ins().i32_reinterpret_f32();
self.ins().i64_extend_i32_u();
}
(ValType::I64, ValType::I32)
| (ValType::F64, ValType::I32)
| (ValType::I32, ValType::F32)
| (ValType::I64, ValType::F32)
| (ValType::F64, ValType::F32)
| (ValType::I32, ValType::F64)
| (ValType::I64, ValType::F64)
| (ValType::F32, ValType::F64)
| (ValType::Ref(_), _)
| (_, ValType::Ref(_))
| (ValType::V128, _)
| (_, ValType::V128) => {
panic!("cannot get {dst_ty:?} from {ty:?} local");
}
}
self.ins().local_set(local.idx);
}
AbiLoc::Memory(mem) => {
let tmp = self.local_set_new_tmp(ty);
let memarg = mem.memarg(size_log2);
self.ins().local_get(mem.addr.idx);
self.ins().local_get(tmp.idx);
match (ty, size_log2) {
(ValType::I32, 0) => self.ins().i32_store8(memarg),
(ValType::I32, 1) => self.ins().i32_store16(memarg),
(ValType::I32, 2) => self.ins().i32_store(memarg),
(ValType::I64, 3) => self.ins().i64_store(memarg),
(ValType::F32, 2) => self.ins().f32_store(memarg),
(ValType::F64, 3) => self.ins().f64_store(memarg),
other => unimplemented!("{other:?}"),
};
self.free_temp_local(tmp);
}
}
}
fn lift(&mut self, src: &AbiLoc<'_>, ty: Type) {
let i = self.adapter.intrinsics();
match ty {
Type::Bool => self.push_scalar(src, ValType::I32, i.push_bool, 0, false),
Type::Char => self.push_scalar(src, ValType::I32, i.push_char, 2, false),
Type::U8 => self.push_scalar(src, ValType::I32, i.push_u8, 0, false),
Type::S8 => self.push_scalar(src, ValType::I32, i.push_s8, 0, true),
Type::U16 => self.push_scalar(src, ValType::I32, i.push_u16, 1, false),
Type::S16 => self.push_scalar(src, ValType::I32, i.push_s16, 1, true),
Type::U32 => self.push_scalar(src, ValType::I32, i.push_u32, 2, false),
Type::S32 => self.push_scalar(src, ValType::I32, i.push_s32, 2, true),
Type::U64 => self.push_scalar(src, ValType::I64, i.push_u64, 3, false),
Type::S64 => self.push_scalar(src, ValType::I64, i.push_s64, 3, true),
Type::F32 => self.push_scalar(src, ValType::F32, i.push_f32, 2, false),
Type::F64 => self.push_scalar(src, ValType::F64, i.push_f64, 3, false),
Type::String => {
let push_string = i.push_string;
let (src_ptr, src_len) = src.split_ptr_len();
self.local_get_ctx();
self.load_abi_loc(&src_ptr, ValType::I32, 2, false);
self.load_abi_loc(&src_len, ValType::I32, 2, false);
self.ins().call(push_string);
}
Type::ErrorContext => todo!("error-context"),
Type::Id(id) => self.lift_id(src, id),
}
}
fn lift_id(&mut self, src: &AbiLoc<'_>, id: TypeId) {
let ty = self.adapter.type_map[&id];
let i = self.adapter.intrinsics();
match &self.resolve.types[id].kind {
TypeDefKind::Type(t) => self.lift(src, *t),
TypeDefKind::Record(r) => {
let metadata::Type::Record(record_index) = ty else {
unreachable!()
};
self.push_record(
src,
record_index,
i.push_record,
r.fields.iter().map(|f| &f.ty),
);
}
TypeDefKind::Tuple(t) => {
let metadata::Type::Tuple(tuple_index) = ty else {
unreachable!()
};
self.push_record(src, tuple_index, i.push_tuple, t.types.iter());
}
TypeDefKind::Handle(Handle::Borrow(_)) => {
let metadata::Type::Borrow(resource_index) = ty else {
unreachable!()
};
self.push_typed_scalar(src, resource_index, i.push_borrow, 2);
}
TypeDefKind::Handle(Handle::Own(_)) => {
let metadata::Type::Own(resource_index) = ty else {
unreachable!()
};
self.push_typed_scalar(src, resource_index, i.push_own, 2);
}
TypeDefKind::Future(_) => {
let metadata::Type::Future(future_index) = ty else {
unreachable!()
};
self.push_typed_scalar(src, future_index, i.push_future, 2);
}
TypeDefKind::Stream(_) => {
let metadata::Type::Stream(stream_index) = ty else {
unreachable!()
};
self.push_typed_scalar(src, stream_index, i.push_stream, 2);
}
TypeDefKind::Flags(f) => {
let metadata::Type::Flags(flags_index) = ty else {
unreachable!()
};
let size_log2 = match f.repr() {
FlagsRepr::U8 => 0,
FlagsRepr::U16 => 1,
FlagsRepr::U32(1) => 2,
FlagsRepr::U32(_) => unimplemented!(),
};
self.push_typed_scalar(src, flags_index, i.push_flags, size_log2)
}
TypeDefKind::Enum(f) => {
let metadata::Type::Enum(enum_index) = ty else {
unreachable!()
};
let size_log2 = match f.tag() {
Int::U8 => 0,
Int::U16 => 1,
Int::U32 => 2,
Int::U64 => 3,
};
self.push_typed_scalar(src, enum_index, i.push_enum, size_log2)
}
TypeDefKind::FixedLengthList(t, len) => {
let _ = (t, len);
todo!("fixed-length-list")
}
TypeDefKind::Variant(t) => {
let metadata::Type::Variant(variant_index) = ty else {
unreachable!()
};
self.push_variant(
src,
variant_index,
i.push_variant,
t.tag(),
t.cases.iter().map(|c| c.ty.as_ref()),
);
}
TypeDefKind::Option(t) => {
let metadata::Type::Option(option_index) = ty else {
unreachable!()
};
self.push_variant(src, option_index, i.push_option, Int::U8, [None, Some(t)]);
}
TypeDefKind::Result(t) => {
let metadata::Type::Result(result_index) = ty else {
unreachable!()
};
self.push_variant(
src,
result_index,
i.push_result,
Int::U8,
[t.ok.as_ref(), t.err.as_ref()],
);
}
TypeDefKind::List(t) => {
let metadata::Type::List(list_index) = ty else {
unreachable!()
};
let elem_size = self.adapter.sizes.size(t).size_wasm32();
let elem_align = self.adapter.sizes.align(t).align_wasm32();
let list_index = i32::try_from(list_index).unwrap();
let push_list = i.push_list;
let list_append = i.list_append;
let dealloc_bytes = i.dealloc_bytes;
let (src_ptr, src_len) = src.split_ptr_len();
self.local_get_ctx();
self.ins().i32_const(list_index);
self.load_abi_loc(&src_ptr, ValType::I32, 2, false);
let l_ptr = self.local_tee_new_tmp(ValType::I32);
self.load_abi_loc(&src_len, ValType::I32, 2, false);
let l_len = self.local_tee_new_tmp(ValType::I32);
self.ins().call(push_list);
self.ins().i32_eqz();
self.ins().if_(BlockType::Empty);
{
self.ins().local_get(l_len.idx);
self.ins().i32_const(elem_size.try_into().unwrap());
self.ins().i32_mul();
let l_byte_size = self.local_set_new_tmp(ValType::I32);
self.ins().local_get(l_ptr.idx);
let l_ptr_to_free = self.local_set_new_tmp(ValType::I32);
self.ins().loop_(BlockType::Empty);
{
self.ins().local_get(l_len.idx);
self.ins().if_(BlockType::Empty);
{
let src = AbiLoc::Memory(Memory {
addr: TempLocal::new(l_ptr.idx, l_ptr.ty),
offset: 0,
});
self.lift(&src, *t);
self.local_get_ctx();
self.ins().i32_const(list_index);
self.ins().call(list_append);
self.ins().local_get(l_len.idx);
self.ins().i32_const(1);
self.ins().i32_sub();
self.ins().local_set(l_len.idx);
self.ins().local_get(l_ptr.idx);
self.ins().i32_const(elem_size.try_into().unwrap());
self.ins().i32_add();
self.ins().local_set(l_ptr.idx);
self.ins().br(1);
}
self.ins().end();
}
self.ins().end();
self.ins().local_get(l_byte_size.idx);
self.ins().if_(BlockType::Empty);
{
self.local_get_ctx();
self.ins().local_get(l_ptr_to_free.idx);
self.ins().local_get(l_byte_size.idx);
self.ins().i32_const(elem_align.try_into().unwrap());
self.ins().i32_const(0);
self.ins().call(dealloc_bytes);
}
self.ins().end();
self.free_temp_local(l_byte_size);
self.free_temp_local(l_ptr_to_free);
}
self.ins().end();
self.free_temp_local(l_ptr);
self.free_temp_local(l_len);
}
TypeDefKind::Map(k, v) => {
let _ = (k, v);
todo!("map")
}
TypeDefKind::Resource => unreachable!(),
TypeDefKind::Unknown => unreachable!(),
}
}
fn push_record<'b>(
&mut self,
src: &AbiLoc<'_>,
type_index: usize,
push_intrinsic: u32,
types: impl IntoIterator<Item = &'b Type> + Clone,
) {
let srcs = self.record_field_locs(src, types.clone());
for (ty, src) in types.into_iter().zip(srcs) {
self.lift(&src, *ty);
}
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().call(push_intrinsic);
}
fn push_variant<'b>(
&mut self,
src: &AbiLoc<'_>,
type_index: usize,
push_intrinsic: u32,
tag: Int,
payloads: impl IntoIterator<Item = Option<&'b Type>> + Clone,
) {
let (discr_src, payload_src) = src.split_discr_payload(self.adapter, tag, payloads.clone());
let tag_size_log2 = match tag {
Int::U8 => 0,
Int::U16 => 1,
Int::U32 => 2,
Int::U64 => 3,
};
let lift_payload = |me: &mut Self, ty: Option<&Type>| match ty {
Some(ty) => {
let (src, _) = payload_src.narrow_payload(me.resolve, ty);
me.lift(&src, *ty);
}
None => {}
};
match payloads.clone().into_iter().count() {
2 => {
let mut iter = payloads.clone().into_iter();
let payload0 = iter.next().unwrap();
let payload1 = iter.next().unwrap();
self.load_abi_loc(&discr_src, ValType::I32, tag_size_log2, false);
self.ins().if_(BlockType::Empty);
{
lift_payload(self, payload1);
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().i32_const(1);
self.ins().call(push_intrinsic);
}
self.ins().else_();
{
lift_payload(self, payload0);
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().i32_const(0);
self.ins().call(push_intrinsic);
}
self.ins().end();
}
n => {
self.ins().block(BlockType::Empty);
for _ in 0..n {
self.ins().block(BlockType::Empty);
}
self.ins().block(BlockType::Empty);
self.ins().block(BlockType::Empty);
self.load_abi_loc(&discr_src, ValType::I32, tag_size_log2, false);
self.ins().br_table(1..(n + 1) as u32, 0);
self.ins().end();
self.ins().unreachable();
self.ins().end();
for (i, ty) in payloads.clone().into_iter().enumerate() {
lift_payload(self, ty);
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.ins().i32_const(i.try_into().unwrap());
self.ins().call(push_intrinsic);
self.ins().br((n - i) as u32);
self.ins().end();
}
self.ins().end();
}
}
}
fn push_typed_scalar(
&mut self,
src: &AbiLoc<'_>,
type_index: usize,
push_intrinsic: u32,
size_log2: u32,
) {
self.local_get_ctx();
self.ins().i32_const(type_index.try_into().unwrap());
self.load_abi_loc(src, ValType::I32, size_log2, false);
self.ins().call(push_intrinsic);
}
fn push_scalar(
&mut self,
src: &AbiLoc<'_>,
ty: ValType,
push_intrinsic: u32,
size_log2: u32,
signed: bool,
) {
self.local_get_ctx();
self.load_abi_loc(src, ty, size_log2, signed);
self.ins().call(push_intrinsic);
}
fn load_abi_loc(&mut self, src: &AbiLoc<'_>, ty: ValType, size_log2: u32, signed: bool) {
match src {
AbiLoc::Stack([local]) => {
self.ins().local_get(local.idx);
match (local.ty, ty) {
(ValType::I32, ValType::I32)
| (ValType::I64, ValType::I64)
| (ValType::F32, ValType::F32)
| (ValType::F64, ValType::F64) => {}
(ValType::I32, ValType::F32) => {
self.ins().f32_reinterpret_i32();
}
(ValType::I64, ValType::I32) => {
self.ins().i32_wrap_i64();
}
(ValType::I64, ValType::F64) => {
self.ins().f64_reinterpret_i64();
}
(ValType::I64, ValType::F32) => {
self.ins().i32_wrap_i64();
self.ins().f32_reinterpret_i32();
}
(ValType::I32, ValType::I64)
| (ValType::F64, ValType::I32)
| (ValType::F32, ValType::I32)
| (ValType::F32, ValType::I64)
| (ValType::F64, ValType::F32)
| (ValType::I32, ValType::F64)
| (ValType::F64, ValType::I64)
| (ValType::F32, ValType::F64)
| (ValType::Ref(_), _)
| (_, ValType::Ref(_))
| (ValType::V128, _)
| (_, ValType::V128) => {
panic!("cannot convert {:?} to {ty:?}", local.ty)
}
}
}
AbiLoc::Stack(_) => unreachable!(),
AbiLoc::Memory(mem) => {
self.ins().local_get(mem.addr.idx);
let memarg = mem.memarg(size_log2);
match (ty, size_log2) {
(ValType::I32, 0) if signed => self.ins().i32_load8_s(memarg),
(ValType::I32, 0) => self.ins().i32_load8_u(memarg),
(ValType::I32, 1) if signed => self.ins().i32_load16_s(memarg),
(ValType::I32, 1) => self.ins().i32_load16_u(memarg),
(ValType::I32, 2) => self.ins().i32_load(memarg),
(ValType::I64, 3) => self.ins().i64_load(memarg),
(ValType::F32, 2) => self.ins().f32_load(memarg),
(ValType::F64, 3) => self.ins().f64_load(memarg),
other => unimplemented!("{other:?}"),
};
}
}
}
fn record_field_locs<'b, 'c>(
&self,
dst: &AbiLoc<'b>,
fields: impl IntoIterator<Item = &'c Type>,
) -> Vec<AbiLoc<'b>> {
match dst {
AbiLoc::Stack(types) => {
let mut types = &types[..];
fields
.into_iter()
.map(|ty| {
let n = flat_types(self.resolve, ty).unwrap().len();
let (a, b) = types.split_at(n);
types = b;
AbiLoc::Stack(a)
})
.collect()
}
AbiLoc::Memory(mem) => self
.adapter
.sizes
.field_offsets(fields)
.iter()
.map(|(size, _ty)| mem.bump(size.size_wasm32()))
.map(AbiLoc::Memory)
.collect(),
}
}
fn defer_deallocate_lowered_list(
&mut self,
l_ptr: &TempLocal,
l_byte_size: &TempLocal,
align: usize,
) {
let dealloc_bytes = self.adapter.intrinsics().dealloc_bytes;
self.ins().local_get(l_byte_size.idx);
self.ins().if_(BlockType::Empty);
{
self.local_get_ctx();
self.ins().local_get(l_ptr.idx);
self.ins().local_get(l_byte_size.idx);
self.ins().i32_const(align.try_into().unwrap());
self.ins().i32_const(1);
self.ins().call(dealloc_bytes);
}
self.ins().end();
}
}
struct StackFrame {
size: u32,
abi_param_offset: Option<u32>,
retptr_offset: Option<u32>,
stack_temp_offset: Option<u32>,
}
impl StackFrame {
fn abi_loc(&self, base: &TempLocal) -> Option<AbiLoc<'_>> {
let offset = self.retptr_offset?;
Some(AbiLoc::Memory(Memory {
addr: TempLocal::new(base.idx, base.ty),
offset,
}))
}
}
fn flat_types(resolve: &Resolve, ty: &Type) -> Option<Vec<WasmType>> {
let mut storage = [WasmType::I32; Resolve::MAX_FLAT_PARAMS];
let mut flat = FlatTypes::new(&mut storage);
if resolve.push_flat(ty, &mut flat) {
Some(flat.to_vec())
} else {
None
}
}
enum AbiLoc<'a> {
Stack(&'a [TempLocal]),
Memory(Memory),
}
impl<'a> AbiLoc<'a> {
fn split_ptr_len(&self) -> (Self, Self) {
match self {
AbiLoc::Stack([dst_ptr, dst_len]) => (
AbiLoc::Stack(slice::from_ref(dst_ptr)),
AbiLoc::Stack(slice::from_ref(dst_len)),
),
AbiLoc::Stack(_) => unreachable!(),
AbiLoc::Memory(mem) => (AbiLoc::Memory(mem.bump(0)), AbiLoc::Memory(mem.bump(4))),
}
}
fn split_discr_payload<'b>(
&self,
adapter: &Adapter,
tag: Int,
payloads: impl IntoIterator<Item = Option<&'b Type>>,
) -> (Self, Self) {
match self {
AbiLoc::Stack([discr, rest @ ..]) => {
(AbiLoc::Stack(slice::from_ref(discr)), AbiLoc::Stack(rest))
}
AbiLoc::Stack([]) => unreachable!(),
AbiLoc::Memory(mem) => {
let offset = adapter.sizes.payload_offset(tag, payloads);
(
AbiLoc::Memory(mem.bump(0)),
AbiLoc::Memory(mem.bump(offset.size_wasm32())),
)
}
}
}
fn narrow_payload(&self, resolve: &Resolve, ty: &Type) -> (Self, Option<&'a [TempLocal]>) {
match self {
AbiLoc::Stack(locals) => {
let flat_count = flat_types(resolve, ty).unwrap().len();
let (lower_locals, rest) = locals.split_at(flat_count);
(AbiLoc::Stack(lower_locals), Some(rest))
}
AbiLoc::Memory(m) => (AbiLoc::Memory(m.bump(0)), None),
}
}
}
struct Memory {
addr: TempLocal,
offset: u32,
}
impl Memory {
fn memarg(&self, align: u32) -> MemArg {
MemArg {
offset: u64::from(self.offset),
align,
memory_index: 0,
}
}
fn bump(&self, offset: usize) -> Memory {
Memory {
addr: TempLocal::new(self.addr.idx, self.addr.ty),
offset: self.offset + u32::try_from(offset).unwrap(),
}
}
}
struct TempLocal {
idx: u32,
ty: ValType,
needs_free: bool,
}
impl TempLocal {
fn new(idx: u32, ty: ValType) -> TempLocal {
TempLocal {
idx,
ty,
needs_free: false,
}
}
}
impl Drop for TempLocal {
fn drop(&mut self) {
if self.needs_free {
if std::thread::panicking() {
eprintln!("temporary local not free'd");
} else {
panic!("temporary local not free'd");
}
}
}
}
fn align_up(a: usize, align: usize) -> usize {
assert!(align.is_power_of_two());
(a + (align - 1)) & !(align - 1)
}