use wasm_encoder::{CodeSection, Function};
use wit_parser::{Function as WitFunction, Resolve};
use super::super::abi::canon_async;
use super::super::abi::emit::{
emit_alloc_call_id, emit_borrow_drops, emit_bump_restore, emit_bump_save,
emit_cabi_realloc_call, emit_cabi_realloc_call_runtime, emit_handler_call,
emit_populate_call_id, emit_store_i64_local, emit_store_slice, emit_store_slice_len_runtime,
emit_store_slice_ptr_runtime, emit_wrapper_return, BlobSlice, BumpReset, RecordLayout,
};
use super::super::indices::LocalsBuilder;
use super::lift::plan::LiftPlan;
use super::lift::{
alloc_wrapper_locals, emit_lift_compound_prefix, emit_lift_plan, emit_lift_result,
emit_list_pre_pass, CellSideRefs, FlagsInfoOffsets, HandleInfoOffsets, InfoCounts, LiftEmitCtx,
ListEmitLocals, RecordInfoOffsets, ResultEmitPlan, VariantInfoOffsets, WrapperLocals,
};
use super::schema::{
SchemaLayouts, FIELD_TREE, ON_CALL_ARGS, ON_CALL_CALL, ON_RET_CALL, ON_RET_RESULT, TREE_CELLS,
TREE_FLAGS_INFOS, TREE_HANDLE_INFOS, TREE_RECORD_INFOS, TREE_VARIANT_INFOS,
};
use super::section_emit::FuncIndices;
use super::{FuncDispatch, FuncShape};
pub(super) struct WrapperCtx<'a> {
pub(super) schema: &'a SchemaLayouts,
pub(super) resolve: &'a Resolve,
pub(super) iface_name: BlobSlice,
pub(super) before_hook: Option<BeforeHook<'a>>,
pub(super) after_hook: Option<AfterHook<'a>>,
pub(super) call_id_counter_global: u32,
pub(super) bump_global: u32,
}
pub(super) struct BeforeHook<'a> {
pub(super) idx: u32,
pub(super) layout: &'a RecordLayout,
pub(super) params_ptr: i32,
}
pub(super) struct AfterHook<'a> {
pub(super) idx: u32,
pub(super) layout: &'a RecordLayout,
}
struct OnCallCallSite {
iface_name: BlobSlice,
fn_name: BlobSlice,
args: BlobSlice,
id_local: u32,
}
struct CellsTarget {
fields_base_ptr: i32,
cells_field_off: u32,
static_info_counts: InfoCounts,
}
fn emit_alloc_cells_for_plan(
f: &mut Function,
ctx: &LiftEmitCtx<'_>,
plan: &LiftPlan,
list_locals: &[ListEmitLocals],
local_base: u32,
lcl: &WrapperLocals,
target: CellsTarget,
) {
emit_list_pre_pass(
f,
ctx,
plan,
&target.static_info_counts,
list_locals,
local_base,
lcl,
);
emit_cabi_realloc_call_runtime(
f,
ctx.cabi_realloc_idx,
ctx.cell_layout.align,
lcl.next_cell_idx,
ctx.cell_layout.size,
lcl.cells_base,
);
emit_store_slice_ptr_runtime(
f,
target.fields_base_ptr,
target.cells_field_off,
lcl.cells_base,
);
emit_store_slice_len_runtime(
f,
target.fields_base_ptr,
target.cells_field_off,
lcl.next_cell_idx,
);
}
fn field_tree_slice_off(schema: &SchemaLayouts, tree_field: &str) -> u32 {
schema.field_layout.offset_of(FIELD_TREE) + schema.tree_layout.offset_of(tree_field)
}
fn after_result_tree_slice_off(
schema: &SchemaLayouts,
after_layout: &RecordLayout,
tree_field: &str,
) -> u32 {
after_layout.offset_of(ON_RET_RESULT)
+ schema.option_payload_off
+ schema.tree_layout.offset_of(tree_field)
}
#[derive(Clone, Copy)]
enum InfoKind {
Handle,
Flags,
Record,
Variant,
}
impl InfoKind {
fn name(self) -> &'static str {
match self {
InfoKind::Handle => "handle",
InfoKind::Flags => "flags",
InfoKind::Record => "record",
InfoKind::Variant => "variant",
}
}
fn entry_geometry(self, ctx: &LiftEmitCtx<'_>) -> (u32, u32) {
match self {
InfoKind::Handle => (ctx.handle_info.align, ctx.handle_info.entry_size),
InfoKind::Flags => (ctx.flags_info.align, ctx.flags_info.entry_size),
InfoKind::Record => (ctx.record_info.align, ctx.record_info.entry_size),
InfoKind::Variant => (ctx.variant_info.align, ctx.variant_info.entry_size),
}
}
fn locals(self, lcl: &WrapperLocals) -> (Option<u32>, Option<u32>) {
match self {
InfoKind::Handle => (lcl.handle_info_base, lcl.next_handle_idx),
InfoKind::Flags => (lcl.flags_info_base, lcl.next_flags_idx),
InfoKind::Record => (lcl.record_info_base, lcl.next_record_idx),
InfoKind::Variant => (lcl.variant_info_base, lcl.next_variant_idx),
}
}
}
struct InfoBufferTarget {
kind: InfoKind,
static_count: u32,
runtime_sized: bool,
slice_field_off: u32,
}
fn emit_alloc_info_buffer_for_plan(
f: &mut Function,
ctx: &LiftEmitCtx<'_>,
target: InfoBufferTarget,
base_ptr: i32,
lcl: &WrapperLocals,
) {
let InfoBufferTarget {
kind,
static_count,
runtime_sized,
slice_field_off,
} = target;
let (align, entry_size) = kind.entry_geometry(ctx);
let (base_local_opt, count_local_opt) = kind.locals(lcl);
if runtime_sized {
let count_local = count_local_opt.unwrap_or_else(|| {
panic!(
"fn_has_list_elem_{kind} gate disagrees with plan",
kind = kind.name()
)
});
let base_local = base_local_opt.unwrap_or_else(|| {
panic!(
"fn_has_{kind}_cells gate disagrees with fn_has_list_elem_{kind}",
kind = kind.name()
)
});
emit_cabi_realloc_call_runtime(
f,
ctx.cabi_realloc_idx,
align,
count_local,
entry_size,
base_local,
);
emit_store_slice_ptr_runtime(f, base_ptr, slice_field_off, base_local);
emit_store_slice_len_runtime(f, base_ptr, slice_field_off, count_local);
return;
}
if static_count == 0 {
return;
}
let base_local = base_local_opt.unwrap_or_else(|| {
panic!(
"fn_has_{kind}_cells gate disagrees with {kind}_count",
kind = kind.name()
)
});
let buf_size = static_count
.checked_mul(entry_size)
.unwrap_or_else(|| panic!("{kind}_info buffer size overflowed u32", kind = kind.name()));
emit_cabi_realloc_call(f, ctx.cabi_realloc_idx, align, buf_size, base_local);
emit_store_slice_ptr_runtime(f, base_ptr, slice_field_off, base_local);
}
fn emit_populate_hook_params(
f: &mut Function,
schema: &SchemaLayouts,
before: &BeforeHook<'_>,
site: &OnCallCallSite,
) {
let call_off = before.layout.offset_of(ON_CALL_CALL);
let args_off = before.layout.offset_of(ON_CALL_ARGS);
emit_populate_call_id(
f,
before.params_ptr,
call_off,
&schema.callid_layout,
site.iface_name,
site.fn_name,
site.id_local,
);
emit_store_slice(f, before.params_ptr, args_off, site.args);
}
pub(super) fn emit_wrapper_function(
code: &mut CodeSection,
func_idx: &FuncIndices,
ctx: &WrapperCtx<'_>,
i: usize,
fd: &FuncDispatch,
func: &WitFunction,
) {
let async_funcs = &func_idx.async_funcs;
let schema = ctx.schema;
let nparams = fd.export_sig.params.len() as u32;
let builder = LocalsBuilder::new(nparams);
let (lcl, result_emit, frozen) = alloc_wrapper_locals(
ctx.resolve,
&schema.size_align,
schema.record_field_tuple_layout.size,
builder,
fd,
func,
);
let mut f = Function::new_with_locals_types(frozen.locals);
let bump_reset = BumpReset {
global: ctx.bump_global,
saved_local: lcl.saved_bump,
};
emit_bump_save(&mut f, bump_reset);
emit_alloc_call_id(&mut f, ctx.call_id_counter_global, lcl.id_local);
let lift_ctx = LiftEmitCtx {
cell_layout: &schema.cell_layout,
cabi_realloc_idx: func_idx.cabi_realloc_idx,
handle_info: HandleInfoOffsets::from_layout(&schema.handle_info_layout),
flags_info: FlagsInfoOffsets::from_layout(&schema.flags_info_layout),
record_info: RecordInfoOffsets::from_layout(
&schema.record_info_layout,
&schema.record_field_tuple_layout,
),
variant_info: VariantInfoOffsets::from_layout(
&schema.variant_info_layout,
schema.variant_info_payload_value_off,
),
};
if let Some(before) = ctx.before_hook.as_ref() {
let mut local_base: u32 = 0;
let cells_slice_off = field_tree_slice_off(schema, TREE_CELLS);
let handle_infos_slice_off = field_tree_slice_off(schema, TREE_HANDLE_INFOS);
let flags_infos_slice_off = field_tree_slice_off(schema, TREE_FLAGS_INFOS);
let record_infos_slice_off = field_tree_slice_off(schema, TREE_RECORD_INFOS);
let variant_infos_slice_off = field_tree_slice_off(schema, TREE_VARIANT_INFOS);
for (i, p) in fd.params.iter().enumerate() {
let field_off = i as u32 * schema.field_layout.size;
let list_locals = &lcl.param_list_locals[i];
emit_alloc_cells_for_plan(
&mut f,
&lift_ctx,
&p.lift.plan,
list_locals,
local_base,
&lcl,
CellsTarget {
fields_base_ptr: fd.fields_buf_offset as i32,
cells_field_off: field_off + cells_slice_off,
static_info_counts: p.info_counts,
},
);
for target in [
InfoBufferTarget {
kind: InfoKind::Handle,
static_count: p.info_counts.handle,
runtime_sized: p.lift.plan.has_list_elem_handle(),
slice_field_off: field_off + handle_infos_slice_off,
},
InfoBufferTarget {
kind: InfoKind::Flags,
static_count: p.info_counts.flags,
runtime_sized: p.lift.plan.has_list_elem_flags(),
slice_field_off: field_off + flags_infos_slice_off,
},
InfoBufferTarget {
kind: InfoKind::Record,
static_count: p.info_counts.record,
runtime_sized: p.lift.plan.has_list_elem_record(),
slice_field_off: field_off + record_infos_slice_off,
},
InfoBufferTarget {
kind: InfoKind::Variant,
static_count: p.info_counts.variant,
runtime_sized: p.lift.plan.has_list_elem_variant(),
slice_field_off: field_off + variant_infos_slice_off,
},
] {
emit_alloc_info_buffer_for_plan(
&mut f,
&lift_ctx,
target,
fd.fields_buf_offset as i32,
&lcl,
);
}
emit_lift_plan(
&mut f,
&lift_ctx,
&p.lift.plan,
CellSideRefs {
cell_side: &p.cell_side,
},
local_base,
&lcl,
list_locals,
);
local_base += p.lift.plan.flat_slot_count;
}
let nargs = fd.params.len() as u32;
let args_off = if nargs == 0 { 0 } else { fd.fields_buf_offset };
emit_populate_hook_params(
&mut f,
schema,
before,
&OnCallCallSite {
iface_name: ctx.iface_name,
fn_name: BlobSlice {
off: fd.fn_name_offset as u32,
len: fd.fn_name_len as u32,
},
args: BlobSlice {
off: args_off,
len: nargs,
},
id_local: lcl.id_local,
},
);
f.instructions().i32_const(before.params_ptr);
canon_async::emit_call_and_wait(&mut f, before.idx, lcl.st, lcl.ws, async_funcs);
}
let handler_imp_idx = func_idx.handler_imp_base + i as u32;
if let Some(seq) = lcl.params_lower_seq.as_ref() {
for inst in seq {
f.instruction(inst);
}
f.instructions().i32_const(
fd.params_record_offset
.expect("indirect_params → params_record_offset"),
);
if fd.import_sig.retptr {
f.instructions()
.i32_const(fd.retptr_offset.expect("import_retptr → retptr_offset"));
}
f.instructions().call(handler_imp_idx);
} else {
emit_handler_call(
&mut f,
nparams,
fd.import_sig.retptr,
fd.retptr_offset,
handler_imp_idx,
);
}
match &fd.shape {
FuncShape::Async(_) => {
f.instructions().local_set(lcl.st);
canon_async::emit_wait_loop(&mut f, lcl.st, lcl.ws, async_funcs);
}
FuncShape::Sync => {
if let Some(local) = lcl.result {
f.instructions().local_set(local);
}
}
}
let after_zip = match (ctx.after_hook.as_ref(), fd.after.as_ref()) {
(Some(s), Some(pf)) => Some((s, pf)),
(None, None) => None,
_ => unreachable!("after-hook ctx and per-fn data are wired in lockstep"),
};
if let Some((after_static, after_pf)) = after_zip {
let cells_field_off = after_result_tree_slice_off(schema, after_static.layout, TREE_CELLS);
let handle_infos_field_off =
after_result_tree_slice_off(schema, after_static.layout, TREE_HANDLE_INFOS);
let flags_infos_field_off =
after_result_tree_slice_off(schema, after_static.layout, TREE_FLAGS_INFOS);
let record_infos_field_off =
after_result_tree_slice_off(schema, after_static.layout, TREE_RECORD_INFOS);
let variant_infos_field_off =
after_result_tree_slice_off(schema, after_static.layout, TREE_VARIANT_INFOS);
let result_info_counts = fd
.result_lift
.as_ref()
.map(|rl| rl.info_counts)
.unwrap_or_default();
match &result_emit {
ResultEmitPlan::Compound {
plan,
retptr_offset,
addr_local,
synth_locals,
loads,
side_refs,
list_locals,
} => {
emit_lift_compound_prefix(
&mut f,
plan.flat_slot_count,
*retptr_offset,
loads,
*addr_local,
synth_locals,
);
emit_alloc_cells_for_plan(
&mut f,
&lift_ctx,
plan,
list_locals,
synth_locals[0],
&lcl,
CellsTarget {
fields_base_ptr: after_pf.params_offset,
cells_field_off,
static_info_counts: result_info_counts,
},
);
for target in [
InfoBufferTarget {
kind: InfoKind::Handle,
static_count: result_info_counts.handle,
runtime_sized: plan.has_list_elem_handle(),
slice_field_off: handle_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Flags,
static_count: result_info_counts.flags,
runtime_sized: plan.has_list_elem_flags(),
slice_field_off: flags_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Record,
static_count: result_info_counts.record,
runtime_sized: plan.has_list_elem_record(),
slice_field_off: record_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Variant,
static_count: result_info_counts.variant,
runtime_sized: plan.has_list_elem_variant(),
slice_field_off: variant_infos_field_off,
},
] {
emit_alloc_info_buffer_for_plan(
&mut f,
&lift_ctx,
target,
after_pf.params_offset,
&lcl,
);
}
emit_lift_plan(
&mut f,
&lift_ctx,
plan,
*side_refs,
synth_locals[0],
&lcl,
list_locals,
);
}
ResultEmitPlan::Direct { .. } => {
emit_cabi_realloc_call(
&mut f,
func_idx.cabi_realloc_idx,
schema.cell_layout.align,
schema.cell_layout.size,
lcl.cells_base,
);
emit_store_slice_ptr_runtime(
&mut f,
after_pf.params_offset,
cells_field_off,
lcl.cells_base,
);
for target in [
InfoBufferTarget {
kind: InfoKind::Handle,
static_count: result_info_counts.handle,
runtime_sized: false,
slice_field_off: handle_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Flags,
static_count: result_info_counts.flags,
runtime_sized: false,
slice_field_off: flags_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Record,
static_count: result_info_counts.record,
runtime_sized: false,
slice_field_off: record_infos_field_off,
},
InfoBufferTarget {
kind: InfoKind::Variant,
static_count: result_info_counts.variant,
runtime_sized: false,
slice_field_off: variant_infos_field_off,
},
] {
emit_alloc_info_buffer_for_plan(
&mut f,
&lift_ctx,
target,
after_pf.params_offset,
&lcl,
);
}
f.instructions().local_get(lcl.cells_base);
f.instructions().local_set(lcl.addr);
emit_lift_result(&mut f, &lift_ctx, &result_emit, &lcl);
}
ResultEmitPlan::None => {}
}
let id_field_off =
after_static.layout.offset_of(ON_RET_CALL) + schema.callid_layout.id_off();
emit_store_i64_local(&mut f, after_pf.params_offset, id_field_off, lcl.id_local);
f.instructions().i32_const(after_pf.params_offset);
canon_async::emit_call_and_wait(&mut f, after_static.idx, lcl.st, lcl.ws, async_funcs);
}
emit_borrow_drops(&mut f, &fd.borrow_drops, &func_idx.resource_drop);
emit_bump_restore(&mut f, bump_reset);
match &fd.shape {
FuncShape::Async(_) => {
emit_task_return(&mut f, fd, func_idx, i, &lcl);
}
FuncShape::Sync => {
emit_wrapper_return(&mut f, lcl.result, fd.export_sig.retptr, fd.retptr_offset);
}
}
f.instructions().end();
code.function(&f);
}
fn emit_task_return(
f: &mut Function,
fd: &FuncDispatch,
func_idx: &FuncIndices,
i: usize,
lcl: &WrapperLocals,
) {
let imp_task_return =
func_idx.task_return_idx[i].expect("async func must have task.return import");
let FuncShape::Async(tr) = &fd.shape else {
unreachable!("emit_task_return called only for async funcs")
};
if fd.result_ty.is_none() {
f.instructions().call(imp_task_return);
} else if tr.sig.indirect_params {
f.instructions().i32_const(
fd.retptr_offset
.expect("async non-void result → retptr_offset"),
);
f.instructions().call(imp_task_return);
} else {
let addr_local = lcl.tr_addr.expect("flat loads → tr_addr local");
let task_return_loads = lcl
.task_return_loads
.as_deref()
.expect("flat loads → instruction sequence");
f.instructions().i32_const(
fd.retptr_offset
.expect("async non-void result → retptr_offset"),
);
f.instructions().local_set(addr_local);
for inst in task_return_loads {
f.instruction(inst);
}
f.instructions().call(imp_task_return);
}
}