kit

kit
git clone https://git.ryansepassi.com/git/kit.git
Log | Files | Refs | README

commit 19d5d73f838dcb2dfede65d2724fc9a0c891a6c8
parent 74448fa2dade4a34e8795349a31271586f84dbe4
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 26 May 2026 15:41:34 -0700

wasm: convert target to semantic CG via IR recorder

Replace the large per-method CGTarget vtable in wasm/target.c with a thin
CgIrRecorderConfig adapter.  The wasm target now records into CgIrModule and
replays through a WTarget emitter during finalize.

Add local_static_data_begin support and data_label_addr_unsupported_msg /
tail_call_unrealizable_reason hooks so the wasm backend can participate in
the semantic local-static-data and tail-call queries.

Move the per-instruction emit helpers (wasm_emit.c) to operate on the
recorded CgIrFunc representation instead of the old direct-target form.

Diffstat:
Msrc/arch/wasm/emit.c | 33++++++++++++++++++++++++++++++---
Msrc/arch/wasm/internal.h | 24++++++++++++++++++++++--
Asrc/arch/wasm/ir_emit.c | 641+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/wasm/target.c | 387++++++++++---------------------------------------------------------------------
4 files changed, 740 insertions(+), 345 deletions(-)

diff --git a/src/arch/wasm/emit.c b/src/arch/wasm/emit.c @@ -3,7 +3,18 @@ * Records CGTarget operations into a per-function WIR list, then linearizes * to a WasmFunc body at func_end. Each SSA Reg becomes a Wasm local; control * flow that fits the cfree_cg_if_begin/else/end pattern lowers to - * if/else/end, while CG scopes (SCOPE_LOOP) lower to (block (loop ...)). */ + * if/else/end, while CG scopes (SCOPE_LOOP) lower to (block (loop ...)). + * + * TODO: complete IR-to-WASM coverage for: + * - bitfield load/store + * - multiple call/return results + * - address-taken parameters + * - ABI multipart params + * - dynamic memcpy/memset via memory.* calls + * - file-scope asm + * - atomics + * - intrinsics + */ #include <stdarg.h> #include <string.h> @@ -1252,7 +1263,7 @@ void wasm_ret(CGTarget* tg, const CGABIValue* v) { * pointed to by the hidden sret parameter, then a void return. */ w->addr = v->storage; w->type = v->type; - w->agg.size = v->size ? v->size : (u32)abi_cg_sizeof(t->c->abi, v->type); + w->agg.size = (u32)abi_cg_sizeof(t->c->abi, v->type); w->agg.align = 1u; w->cgop = 1; /* tag: sret copy */ w->dst = REG_NONE; @@ -2039,7 +2050,7 @@ static void emit_push_operand_reg(WTarget* t, Reg r) { if (r == REG_NONE) wfail(t, "wasm: push of REG_NONE"); /* The reg must already have a local. */ if (r >= t->reg_cap || t->reg_to_local[r] == 0xffffffffu) { - wfail(t, "wasm: reg used before being defined"); + wfail(t, "wasm: reg %u used before being defined", (unsigned)r); } emit_insn(t, WASM_INSN_LOCAL_GET, (i64)t->reg_to_local[r]); } @@ -4531,6 +4542,22 @@ static void wasm_module_freefn(Compiler* c, void* p) { h->free(h, m, sizeof *m); } +WTarget* wasm_emit_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { + Heap* h; + WTarget* t; + if (!c) return NULL; + h = (Heap*)c->ctx->heap; + t = (WTarget*)h->alloc(h, sizeof *t, _Alignof(WTarget)); + if (!t) return NULL; + memset(t, 0, sizeof *t); + t->base.c = c; + t->base.obj = o; + t->c = c; + t->obj = o; + (void)mc; + return t; +} + void wasm_destroy(CGTarget* tg) { WTarget* t = (WTarget*)tg; Heap* h = t->c->ctx->heap; diff --git a/src/arch/wasm/internal.h b/src/arch/wasm/internal.h @@ -24,8 +24,24 @@ #include "arch/arch.h" #include "core/core.h" +#include "opt/ir.h" #include "obj/obj.h" +typedef CgTarget CGTarget; +typedef struct CgIrModule CgIrModule; + +typedef struct WasmOptCGSwitchDesc { + Operand selector; + CfreeCgTypeId selector_type; + Label default_label; + const CGSwitchCase* cases; + u32 ncases; + u8 hint; + u8 opt_level; + u8 pad[2]; +} WasmOptCGSwitchDesc; +#define CGSwitchDesc WasmOptCGSwitchDesc + /* Forward references into the shared src/wasm module representation. The * target reuses that model so emit_wasm can flush through wasm_encode. */ struct WasmModule; @@ -252,7 +268,7 @@ typedef struct WFuncTableFixup { } WFuncTableFixup; typedef struct WTarget { - CGTarget base; + CgTarget base; Compiler* c; ObjBuilder* obj; @@ -383,7 +399,11 @@ typedef struct WTarget { u8 dead; } WTarget; -CGTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); +CgTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); +WTarget* wasm_emit_target_new(Compiler* c, ObjBuilder* o, MCEmitter* mc); +void wasm_emit_ir_module(WTarget* t, const CgIrModule* module); +void wasm_finalize(CGTarget*); +void wasm_destroy(CGTarget*); /* CFG structurer (src/arch/wasm/structure.c). Rewrites the recorded WIR * list so every free WIR_LABEL becomes the break/continue of a synthetic diff --git a/src/arch/wasm/ir_emit.c b/src/arch/wasm/ir_emit.c @@ -0,0 +1,641 @@ +#include "cg/ir.h" + +#include <string.h> + +typedef Operand CgSemOperand; +typedef CGCallDesc CgSemCallDesc; +typedef CGFuncDesc CgSemFuncDesc; +typedef CGParamDesc CgSemParamDesc; +typedef CGScopeDesc CgSemScopeDesc; + +#include "arch/wasm/internal.h" +#include "cg/type.h" +#include "core/heap.h" + +void wasm_func_begin(CGTarget*, const CGFuncDesc*); +void wasm_func_end(CGTarget*); +void wasm_alias(CGTarget*, ObjSymId, ObjSymId, CfreeCgTypeId); +CGLocalStorage wasm_param(CGTarget*, const CGParamDesc*); +CGLocalStorage wasm_local(CGTarget*, const CGLocalDesc*); +Label wasm_label_new(CGTarget*); +void wasm_label_place(CGTarget*, Label); +void wasm_jump(CGTarget*, Label); +void wasm_cmp_branch(CGTarget*, CmpOp, Operand, Operand, Label); +void wasm_switch(CGTarget*, const CGSwitchDesc*); +CGScope wasm_scope_begin(CGTarget*, const CGScopeDesc*); +void wasm_scope_else(CGTarget*, CGScope); +void wasm_scope_end(CGTarget*, CGScope); +void wasm_break_to(CGTarget*, CGScope); +void wasm_continue_to(CGTarget*, CGScope); +void wasm_set_loc(CGTarget*, SrcLoc); +void wasm_load_imm(CGTarget*, Operand, i64); +void wasm_load_const(CGTarget*, Operand, ConstBytes); +void wasm_copy(CGTarget*, Operand, Operand); +void wasm_load(CGTarget*, Operand, Operand, MemAccess); +void wasm_store(CGTarget*, Operand, Operand, MemAccess); +void wasm_addr_of(CGTarget*, Operand, Operand); +void wasm_copy_bytes(CGTarget*, Operand, Operand, AggregateAccess); +void wasm_set_bytes(CGTarget*, Operand, Operand, AggregateAccess); +void wasm_binop(CGTarget*, BinOp, Operand, Operand, Operand); +void wasm_unop(CGTarget*, UnOp, Operand, Operand); +void wasm_cmp(CGTarget*, CmpOp, Operand, Operand, Operand); +void wasm_convert(CGTarget*, ConvKind, Operand, Operand); +void wasm_call(CGTarget*, const CGCallDesc*); +void wasm_ret(CGTarget*, const CGABIValue*); +void wasm_alloca(CGTarget*, Operand, Operand, u32); +void wasm_va_start(CGTarget*, Operand); +void wasm_va_arg(CGTarget*, Operand, Operand, CfreeCgTypeId); +void wasm_va_end(CGTarget*, Operand); +void wasm_va_copy(CGTarget*, Operand, Operand); +void wasm_atomic_load(CGTarget*, Operand, Operand, MemAccess, MemOrder); +void wasm_atomic_store(CGTarget*, Operand, Operand, MemAccess, MemOrder); +void wasm_atomic_rmw(CGTarget*, AtomicOp, Operand, Operand, Operand, MemAccess, + MemOrder); +void wasm_atomic_cas(CGTarget*, Operand, Operand, Operand, Operand, Operand, + MemAccess, MemOrder, MemOrder); +void wasm_fence(CGTarget*, MemOrder); +void wasm_intrinsic(CGTarget*, IntrinKind, Operand*, u32, const Operand*, u32); +void wasm_asm_block(CGTarget*, const char*, const AsmConstraint*, u32, Operand*, + const AsmConstraint*, u32, const Operand*, const Sym*, u32); + +typedef struct WasmIrEmitter { + WTarget* target; + FrameSlot* local_slots; + u32 local_slots_n; + Reg next_temp_reg; + CGScope* scope_map; + u32 scope_map_n; +} WasmIrEmitter; + +static void wasm_ir_fail(WasmIrEmitter* e, SrcLoc loc, const char* msg) { + compiler_panic(e->target->c, loc, "%s", msg); +} + +static RegClass wasm_ir_class_for_type(WTarget* t, CfreeCgTypeId type) { + ABITypeInfo info; + if (!type) return RC_INT; + info = abi_cg_type_info(t->c->abi, type); + return info.scalar_kind == ABI_SC_FLOAT ? RC_FP : RC_INT; +} + +static void wasm_ir_bind_reg(WasmIrEmitter* e, Reg reg, u32 wasm_local, + CfreeCgTypeId type) { + WTarget* t = e->target; + Heap* h = t->c->ctx->heap; + if (reg == REG_NONE) return; + if (reg >= t->reg_cap) { + u32 nc = t->reg_cap ? t->reg_cap : 64u; + while (nc <= reg) nc *= 2u; + u32* regs = (u32*)h->realloc(h, t->reg_to_local, + sizeof(u32) * t->reg_cap, + sizeof(u32) * nc, _Alignof(u32)); + CfreeCgTypeId* types = (CfreeCgTypeId*)h->realloc( + h, t->reg_type, sizeof(CfreeCgTypeId) * t->reg_cap, + sizeof(CfreeCgTypeId) * nc, _Alignof(CfreeCgTypeId)); + u8* cls = (u8*)h->realloc(h, t->reg_cls, t->reg_cap, nc, 1); + if (!regs || !types || !cls) + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "wasm IR emit: out of memory"); + for (u32 i = t->reg_cap; i < nc; ++i) { + regs[i] = 0xffffffffu; + types[i] = 0; + cls[i] = 0; + } + t->reg_to_local = regs; + t->reg_type = types; + t->reg_cls = cls; + t->reg_cap = nc; + } + t->reg_to_local[reg] = wasm_local; + t->reg_type[reg] = type; + t->reg_cls[reg] = (u8)wasm_ir_class_for_type(t, type); +} + +static void wasm_ir_bind_value_local(WasmIrEmitter* e, CGLocal local, + CfreeCgTypeId type, FrameSlot slot) { + WSlot* s; + if (slot == FRAME_SLOT_NONE) return; + s = &e->target->slots[slot - 1u]; + if (s->kind == W_SLOT_LOCAL) + wasm_ir_bind_reg(e, (Reg)local, s->wasm_local, type); +} + +static Operand wasm_ir_value_op(WasmIrEmitter* e, CgSemOperand in) { + Operand out; + memset(&out, 0, sizeof out); + out.kind = in.kind; + out.type = in.type; + out.cls = (u8)wasm_ir_class_for_type(e->target, in.type); + switch ((OpKind)in.kind) { + case OPK_IMM: + out.v.imm = in.v.imm; + return out; + case OPK_LOCAL: + out.kind = OPK_REG; + out.v.reg = (Reg)in.v.local; + return out; + case OPK_GLOBAL: + out.v.global.sym = in.v.global.sym; + out.v.global.addend = in.v.global.addend; + return out; + case OPK_INDIRECT: + out.v.ind.base = (Reg)in.v.ind.base; + out.v.ind.index = + in.v.ind.index == CG_LOCAL_NONE ? REG_NONE : (Reg)in.v.ind.index; + out.v.ind.log2_scale = in.v.ind.log2_scale; + out.v.ind.ofs = in.v.ind.ofs; + return out; + } + return out; +} + +static Reg wasm_ir_temp_reg(WasmIrEmitter* e) { return e->next_temp_reg++; } + +static Operand wasm_ir_source_op(WasmIrEmitter* e, CgSemOperand in, + SrcLoc loc) { + Operand out; + if (in.kind == OPK_LOCAL && in.v.local < e->local_slots_n) { + FrameSlot slot = e->local_slots[in.v.local]; + if (slot != FRAME_SLOT_NONE) { + WSlot* s = &e->target->slots[slot - 1u]; + if (s->kind == W_SLOT_LOCAL) { + wasm_ir_bind_value_local(e, in.v.local, in.type, slot); + } else { + MemAccess mem; + Operand addr; + memset(&mem, 0, sizeof mem); + memset(&addr, 0, sizeof addr); + out = wasm_ir_value_op(e, in); + out.v.reg = wasm_ir_temp_reg(e); + addr.kind = OPK_LOCAL; + addr.type = in.type; + addr.v.frame_slot = slot; + mem.type = in.type; + mem.size = s->size; + mem.align = s->align; + wasm_load((CGTarget*)&e->target->base, out, addr, mem); + return out; + } + } + } + (void)loc; + return wasm_ir_value_op(e, in); +} + +static Operand wasm_ir_addr_op(WasmIrEmitter* e, CgSemOperand in, SrcLoc loc) { + Operand out; + if (in.kind != OPK_LOCAL) return wasm_ir_value_op(e, in); + if (in.v.local == CG_LOCAL_NONE || in.v.local >= e->local_slots_n || + e->local_slots[in.v.local] == FRAME_SLOT_NONE) { + wasm_ir_fail(e, loc, "wasm IR emit: unknown local address"); + } + memset(&out, 0, sizeof out); + out.kind = OPK_LOCAL; + out.type = in.type; + out.v.frame_slot = e->local_slots[in.v.local]; + return out; +} + +static CGScope wasm_ir_scope_lookup(WasmIrEmitter* e, CGScope recorded, + SrcLoc loc) { + if ((u32)recorded >= e->scope_map_n || !e->scope_map[recorded]) + wasm_ir_fail(e, loc, "wasm IR emit: unknown recorded scope"); + return e->scope_map[recorded]; +} + +static void wasm_ir_bind_scope(WasmIrEmitter* e, CGScope recorded, + CGScope emitted, SrcLoc loc) { + if ((u32)recorded >= e->scope_map_n) + wasm_ir_fail(e, loc, "wasm IR emit: recorded scope out of range"); + e->scope_map[recorded] = emitted; +} + +static const ABIArgInfo* wasm_ir_param_abi(const ABIFuncInfo* abi, u32 index) { + return abi && index < abi->nparams ? &abi->params[index] : NULL; +} + +static CGABIValue wasm_ir_abi_value(WasmIrEmitter* e, CGLocal local, + CfreeCgTypeId type, + const ABIArgInfo* abi, int address, + int source, SrcLoc loc) { + CGABIValue out; + CgSemOperand sem; + memset(&out, 0, sizeof out); + memset(&sem, 0, sizeof sem); + sem.kind = OPK_LOCAL; + sem.type = type; + sem.v.local = local; + out.type = type; + out.abi = abi; + out.storage = address ? wasm_ir_addr_op(e, sem, loc) + : source ? wasm_ir_source_op(e, sem, loc) + : wasm_ir_value_op(e, sem); + return out; +} + +static void wasm_ir_emit_call(WasmIrEmitter* e, const CgIrInst* in) { + const CgIrCallAux* aux = (const CgIrCallAux*)in->extra.aux; + const CgSemCallDesc* src = &aux->desc; + Heap* h = e->target->c->ctx->heap; + const ABIFuncInfo* abi = abi_cg_func_info(e->target->c->abi, src->fn_type); + CGCallDesc d; + CGABIValue* args = NULL; + CfreeCgTypeId ret_type = cg_type_func_ret_id(e->target->c, src->fn_type); + memset(&d, 0, sizeof d); + d.fn_type = src->fn_type; + d.callee = wasm_ir_value_op(e, src->callee); + d.nargs = src->nargs; + d.flags = src->flags; + d.tail_policy = src->tail_policy; + d.inline_policy = src->inline_policy; + d.abi = abi; + if (src->nargs) { + args = (CGABIValue*)h->alloc(h, sizeof(*args) * src->nargs, + _Alignof(CGABIValue)); + if (!args) wasm_ir_fail(e, in->loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < src->nargs; ++i) { + const ABIArgInfo* ai = wasm_ir_param_abi(abi, i); + CfreeCgTypeId ty = CFREE_CG_TYPE_NONE; + const CgType* fty = cg_type_get(e->target->c, src->fn_type); + if (fty && fty->kind == CFREE_CG_TYPE_FUNC && i < fty->func.nparams) + ty = fty->func.params[i].type; + args[i] = + wasm_ir_abi_value(e, src->args[i], ty, ai, + ai && ai->kind == ABI_ARG_INDIRECT, 1, in->loc); + } + } + d.args = args; + if (src->nresults > 1) + wasm_ir_fail(e, in->loc, "wasm: multiple call results not yet supported"); + if (src->nresults == 1) { + d.ret = wasm_ir_abi_value(e, src->results[0], ret_type, + abi ? &abi->ret : NULL, abi && abi->has_sret, 0, + in->loc); + } + wasm_call((CGTarget*)&e->target->base, &d); + if (args) h->free(h, args, sizeof(*args) * src->nargs); +} + +static void wasm_ir_emit_ret(WasmIrEmitter* e, const CgIrFunc* f, + const CgIrInst* in) { + const CgIrRetAux* aux = (const CgIrRetAux*)in->extra.aux; + const ABIFuncInfo* abi = abi_cg_func_info(e->target->c->abi, f->desc.fn_type); + CfreeCgTypeId ret_type = cg_type_func_ret_id(e->target->c, f->desc.fn_type); + CGABIValue ret; + if (!aux || aux->nvalues == 0) { + wasm_ret((CGTarget*)&e->target->base, NULL); + return; + } + if (aux->nvalues > 1) + wasm_ir_fail(e, in->loc, "wasm: multiple return values not yet supported"); + ret = wasm_ir_abi_value(e, aux->values[0], ret_type, abi ? &abi->ret : NULL, + abi && abi->has_sret, 1, in->loc); + wasm_ret((CGTarget*)&e->target->base, &ret); +} + +static void wasm_ir_emit_switch(WasmIrEmitter* e, const CgIrInst* in) { + const CgIrSwitchAux* aux = (const CgIrSwitchAux*)in->extra.aux; + CGSwitchDesc d; + memset(&d, 0, sizeof d); + d.selector = wasm_ir_value_op(e, in->opnds[0]); + d.selector_type = aux->selector_type; + d.default_label = aux->default_label; + d.cases = aux->cases; + d.ncases = aux->ncases; + d.hint = aux->hint; + d.opt_level = aux->opt_level; + wasm_switch((CGTarget*)&e->target->base, &d); +} + +static void wasm_ir_emit_inst(WasmIrEmitter* e, const CgIrFunc* f, + const CgIrInst* in) { + CGTarget* t = (CGTarget*)&e->target->base; + wasm_set_loc(t, in->loc); + switch ((CgIrOp)in->op) { + case CG_IR_NOP: + return; + case CG_IR_LABEL: + wasm_label_place(t, (Label)in->extra.imm); + return; + case CG_IR_LOAD_IMM: + wasm_load_imm(t, wasm_ir_value_op(e, in->opnds[0]), in->extra.imm); + return; + case CG_IR_LOAD_CONST: + wasm_load_const(t, wasm_ir_value_op(e, in->opnds[0]), in->extra.cbytes); + return; + case CG_IR_COPY: + wasm_copy(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1])); + return; + case CG_IR_LOAD: + wasm_load(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), in->extra.mem); + return; + case CG_IR_STORE: + wasm_store(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), in->extra.mem); + return; + case CG_IR_ADDR_OF: + wasm_addr_of(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_addr_op(e, in->opnds[1], in->loc)); + return; + case CG_IR_TLS_ADDR_OF: + wasm_ir_fail(e, in->loc, "wasm target: tls_addr_of not yet implemented"); + return; + case CG_IR_AGG_COPY: { + const CgIrAggAux* aux = (const CgIrAggAux*)in->extra.aux; + wasm_copy_bytes(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), aux->access); + return; + } + case CG_IR_AGG_SET: { + const CgIrAggAux* aux = (const CgIrAggAux*)in->extra.aux; + wasm_set_bytes(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), aux->access); + return; + } + case CG_IR_BITFIELD_LOAD: + wasm_ir_fail(e, in->loc, "wasm target: bitfield_load not yet implemented"); + return; + case CG_IR_BITFIELD_STORE: + wasm_ir_fail(e, in->loc, + "wasm target: bitfield_store not yet implemented"); + return; + case CG_IR_BINOP: + wasm_binop(t, (BinOp)in->extra.imm, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), + wasm_ir_value_op(e, in->opnds[2])); + return; + case CG_IR_UNOP: + wasm_unop(t, (UnOp)in->extra.imm, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1])); + return; + case CG_IR_CMP: + wasm_cmp(t, (CmpOp)in->extra.imm, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), + wasm_ir_value_op(e, in->opnds[2])); + return; + case CG_IR_CONVERT: + wasm_convert(t, (ConvKind)in->extra.imm, + wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1])); + return; + case CG_IR_CALL: + wasm_ir_emit_call(e, in); + return; + case CG_IR_RET: + wasm_ir_emit_ret(e, f, in); + return; + case CG_IR_BR: + wasm_jump(t, (Label)in->extra.imm); + return; + case CG_IR_CMP_BRANCH: { + const CgIrCmpBranchAux* aux = (const CgIrCmpBranchAux*)in->extra.aux; + wasm_cmp_branch(t, aux->op, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), aux->target); + return; + } + case CG_IR_SWITCH: + wasm_ir_emit_switch(e, in); + return; + case CG_IR_INDIRECT_BRANCH: + wasm_ir_fail(e, in->loc, + "wasm target: indirect_branch (computed goto) not yet " + "implemented"); + return; + case CG_IR_LOAD_LABEL_ADDR: + wasm_ir_fail(e, in->loc, + "wasm target: load_label_addr (&&label) not yet " + "implemented"); + return; + case CG_IR_LOCAL_STATIC_DATA_BEGIN: + case CG_IR_LOCAL_STATIC_DATA_WRITE: + case CG_IR_LOCAL_STATIC_DATA_LABEL_ADDR: + case CG_IR_LOCAL_STATIC_DATA_END: + wasm_ir_fail(e, in->loc, + "wasm target: function-local static data not yet " + "implemented"); + return; + case CG_IR_SCOPE_BEGIN: { + const CgIrScopeAux* aux = (const CgIrScopeAux*)in->extra.aux; + CGScopeDesc d; + memset(&d, 0, sizeof d); + d.kind = aux->desc.kind; + d.break_label = aux->desc.break_label; + d.continue_label = aux->desc.continue_label; + d.result_type = aux->desc.result_type; + d.cond = wasm_ir_value_op(e, aux->desc.cond); + wasm_ir_bind_scope(e, aux->scope, wasm_scope_begin(t, &d), in->loc); + return; + } + case CG_IR_SCOPE_ELSE: + wasm_scope_else(t, wasm_ir_scope_lookup(e, (CGScope)in->extra.imm, + in->loc)); + return; + case CG_IR_SCOPE_END: + wasm_scope_end(t, wasm_ir_scope_lookup(e, (CGScope)in->extra.imm, + in->loc)); + return; + case CG_IR_BREAK_TO: + wasm_break_to(t, wasm_ir_scope_lookup(e, (CGScope)in->extra.imm, + in->loc)); + return; + case CG_IR_CONTINUE_TO: + wasm_continue_to(t, wasm_ir_scope_lookup(e, (CGScope)in->extra.imm, + in->loc)); + return; + case CG_IR_ALLOCA: + wasm_alloca(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), (u32)in->extra.imm); + return; + case CG_IR_VA_START: + wasm_va_start(t, wasm_ir_value_op(e, in->opnds[0])); + return; + case CG_IR_VA_ARG: + wasm_va_arg(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), + (CfreeCgTypeId)in->extra.imm); + return; + case CG_IR_VA_END: + wasm_va_end(t, wasm_ir_value_op(e, in->opnds[0])); + return; + case CG_IR_VA_COPY: + wasm_va_copy(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1])); + return; + case CG_IR_ATOMIC_LOAD: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + wasm_atomic_load(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), aux->mem, + aux->order); + return; + } + case CG_IR_ATOMIC_STORE: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + wasm_atomic_store(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), aux->mem, + aux->order); + return; + } + case CG_IR_ATOMIC_RMW: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + wasm_atomic_rmw(t, aux->op, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), + wasm_ir_value_op(e, in->opnds[2]), aux->mem, + aux->order); + return; + } + case CG_IR_ATOMIC_CAS: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + wasm_atomic_cas(t, wasm_ir_value_op(e, in->opnds[0]), + wasm_ir_value_op(e, in->opnds[1]), + wasm_ir_value_op(e, in->opnds[2]), + wasm_ir_value_op(e, in->opnds[3]), + wasm_ir_value_op(e, in->opnds[4]), aux->mem, + aux->order, aux->failure); + return; + } + case CG_IR_FENCE: + wasm_fence(t, (MemOrder)in->extra.imm); + return; + case CG_IR_INTRINSIC: { + const CgIrIntrinsicAux* aux = (const CgIrIntrinsicAux*)in->extra.aux; + Heap* h = e->target->c->ctx->heap; + Operand* dsts = NULL; + Operand* args = NULL; + if (aux->ndst) { + dsts = (Operand*)h->alloc(h, sizeof(*dsts) * aux->ndst, + _Alignof(Operand)); + if (!dsts) wasm_ir_fail(e, in->loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < aux->ndst; ++i) + dsts[i] = wasm_ir_value_op(e, aux->dsts[i]); + } + if (aux->narg) { + args = (Operand*)h->alloc(h, sizeof(*args) * aux->narg, + _Alignof(Operand)); + if (!args) wasm_ir_fail(e, in->loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < aux->narg; ++i) + args[i] = wasm_ir_value_op(e, aux->args[i]); + } + wasm_intrinsic(t, aux->kind, dsts, aux->ndst, args, aux->narg); + if (dsts) h->free(h, dsts, sizeof(*dsts) * aux->ndst); + if (args) h->free(h, args, sizeof(*args) * aux->narg); + return; + } + case CG_IR_ASM_BLOCK: { + const CgIrAsmAux* aux = (const CgIrAsmAux*)in->extra.aux; + Heap* h = e->target->c->ctx->heap; + Operand* outs = NULL; + Operand* ins = NULL; + if (aux->nout) { + outs = (Operand*)h->alloc(h, sizeof(*outs) * aux->nout, + _Alignof(Operand)); + if (!outs) wasm_ir_fail(e, in->loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < aux->nout; ++i) + outs[i] = wasm_ir_value_op(e, aux->out_ops[i]); + } + if (aux->nin) { + ins = (Operand*)h->alloc(h, sizeof(*ins) * aux->nin, + _Alignof(Operand)); + if (!ins) wasm_ir_fail(e, in->loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < aux->nin; ++i) { + ins[i] = wasm_ir_value_op(e, aux->in_ops[i]); + } + } + wasm_asm_block(t, aux->tmpl, aux->outs, aux->nout, outs, aux->ins, + aux->nin, ins, aux->clobbers, aux->nclob); + if (outs) h->free(h, outs, sizeof(*outs) * aux->nout); + if (ins) h->free(h, ins, sizeof(*ins) * aux->nin); + return; + } + } + wasm_ir_fail(e, in->loc, "wasm IR emit: unknown op"); +} + +static void wasm_ir_emit_func(WTarget* t, const CgIrFunc* f) { + Heap* h = t->c->ctx->heap; + WasmIrEmitter e; + CGFuncDesc fd; + CGParamDesc* params = NULL; + memset(&e, 0, sizeof e); + e.target = t; + e.next_temp_reg = (Reg)(f->nlocals + 1u); + e.local_slots_n = f->nlocals + 1u; + e.local_slots = (FrameSlot*)h->alloc(h, sizeof(FrameSlot) * e.local_slots_n, + _Alignof(FrameSlot)); + if (!e.local_slots) + compiler_panic(t->c, f->desc.loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < e.local_slots_n; ++i) e.local_slots[i] = FRAME_SLOT_NONE; + e.scope_map_n = f->nscopes + 1u; + if (e.scope_map_n) { + e.scope_map = (CGScope*)h->alloc(h, sizeof(CGScope) * e.scope_map_n, + _Alignof(CGScope)); + if (!e.scope_map) + compiler_panic(t->c, f->desc.loc, "wasm IR emit: out of memory"); + memset(e.scope_map, 0, sizeof(CGScope) * e.scope_map_n); + } + + memset(&fd, 0, sizeof fd); + fd.sym = f->desc.sym; + fd.text_section_id = f->desc.text_section_id; + fd.group_id = f->desc.group_id; + fd.fn_type = f->desc.fn_type; + fd.result_types = f->desc.result_types; + fd.nresults = f->desc.nresults; + fd.nparams = f->desc.nparams; + fd.loc = f->desc.loc; + fd.flags = f->desc.flags; + fd.inline_policy = f->desc.inline_policy; + fd.atomize = f->desc.atomize; + fd.abi = abi_cg_func_info(t->c->abi, f->desc.fn_type); + if (f->nparams) { + params = (CGParamDesc*)h->alloc(h, sizeof(*params) * f->nparams, + _Alignof(CGParamDesc)); + if (!params) compiler_panic(t->c, f->desc.loc, "wasm IR emit: out of memory"); + for (u32 i = 0; i < f->nparams; ++i) { + const CgIrParam* src = &f->params[i]; + memset(&params[i], 0, sizeof params[i]); + params[i].index = src->desc.index; + params[i].name = src->desc.name; + params[i].type = src->desc.type; + params[i].size = src->desc.size; + params[i].align = src->desc.align; + params[i].flags = src->desc.flags; + params[i].loc = src->desc.loc; + params[i].abi = wasm_ir_param_abi(fd.abi, src->desc.index); + } + fd.params = params; + } + + wasm_func_begin((CGTarget*)&t->base, &fd); + for (u32 i = 0; i < f->nlabels; ++i) (void)wasm_label_new((CGTarget*)&t->base); + for (u32 i = 0; i < f->nparams; ++i) { + const CgIrParam* p = &f->params[i]; + CGLocalStorage st = wasm_param((CGTarget*)&t->base, &params[i]); + if (p->local < e.local_slots_n) e.local_slots[p->local] = st.v.frame_slot; + wasm_ir_bind_value_local(&e, p->local, p->desc.type, st.v.frame_slot); + } + for (u32 i = 0; i < f->nlocals; ++i) { + const CgIrLocal* l = &f->locals[i]; + if (l->is_param) continue; + CGLocalStorage st = wasm_local((CGTarget*)&t->base, &l->desc); + if (l->id < e.local_slots_n) e.local_slots[l->id] = st.v.frame_slot; + wasm_ir_bind_value_local(&e, l->id, l->desc.type, st.v.frame_slot); + } + for (u32 i = 0; i < f->ninsts; ++i) wasm_ir_emit_inst(&e, f, &f->insts[i]); + wasm_func_end((CGTarget*)&t->base); + + if (params) h->free(h, params, sizeof(*params) * f->nparams); + if (e.scope_map) h->free(h, e.scope_map, sizeof(CGScope) * e.scope_map_n); + h->free(h, e.local_slots, sizeof(FrameSlot) * e.local_slots_n); +} + +void wasm_emit_ir_module(WTarget* t, const CgIrModule* module) { + if (!t || !module) return; + for (u32 i = 0; i < module->nfuncs; ++i) { + wasm_ir_emit_func(t, module->funcs[i]); + } + for (u32 i = 0; i < module->naliases; ++i) { + const CgIrAlias* a = &module->aliases[i]; + wasm_alias((CGTarget*)&t->base, a->alias_sym, a->target_sym, a->type); + } +} diff --git a/src/arch/wasm/target.c b/src/arch/wasm/target.c @@ -1,357 +1,64 @@ -/* Wasm CGTarget construction and method wiring. +/* Wasm backend registration. * - * See doc/WASM.md ยง"Wasm as Target". The target writes a WasmModule attached - * to the ObjBuilder under OBJ_EXT_WASM; emit_wasm flushes it via wasm_encode. - * Methods that are not yet implemented panic with a precise message so users - * see exactly which Wasm-target feature is still pending. */ + * The public CGTarget path records semantic CG into CgIrModule. Finalize + * replays that IR into the private WIR/module emitter in emit.c. */ #include <string.h> -#include "arch/wasm/internal.h" -#include "core/heap.h" +#include "cg/ir_recorder.h" -/* Real implementations in emit.c. */ -void wasm_func_begin(CGTarget*, const CGFuncDesc*); -void wasm_func_end(CGTarget*); -void wasm_alias(CGTarget*, ObjSymId, ObjSymId, CfreeCgTypeId); -void wasm_ret(CGTarget*, const CGABIValue*); -void wasm_load_imm(CGTarget*, Operand, i64); -void wasm_load_const(CGTarget*, Operand, ConstBytes); -void wasm_copy(CGTarget*, Operand, Operand); -void wasm_binop(CGTarget*, BinOp, Operand, Operand, Operand); -void wasm_unop(CGTarget*, UnOp, Operand, Operand); -void wasm_cmp(CGTarget*, CmpOp, Operand, Operand, Operand); -void wasm_convert(CGTarget*, ConvKind, Operand, Operand); -void wasm_call(CGTarget*, const CGCallDesc*); -const char* wasm_tail_call_unrealizable_reason(CGTarget*, const CGCallDesc*); -CGLocalStorage wasm_param(CGTarget*, const CGParamDesc*); -CGLocalStorage wasm_local(CGTarget*, const CGLocalDesc*); -FrameSlot wasm_frame_slot(CGTarget*, const FrameSlotDesc*); -void wasm_load(CGTarget*, Operand, Operand, MemAccess); -void wasm_store(CGTarget*, Operand, Operand, MemAccess); -void wasm_addr_of(CGTarget*, Operand, Operand); -void wasm_alloca(CGTarget*, Operand, Operand, u32); -void wasm_copy_bytes(CGTarget*, Operand, Operand, AggregateAccess); -void wasm_set_bytes(CGTarget*, Operand, Operand, AggregateAccess); -void wasm_va_start(CGTarget*, Operand); -void wasm_va_arg(CGTarget*, Operand, Operand, CfreeCgTypeId); -void wasm_va_end(CGTarget*, Operand); -void wasm_va_copy(CGTarget*, Operand, Operand); -void wasm_atomic_load(CGTarget*, Operand, Operand, MemAccess, MemOrder); -void wasm_atomic_store(CGTarget*, Operand, Operand, MemAccess, MemOrder); -void wasm_atomic_rmw(CGTarget*, AtomicOp, Operand, Operand, Operand, MemAccess, - MemOrder); -void wasm_atomic_cas(CGTarget*, Operand, Operand, Operand, Operand, Operand, - MemAccess, MemOrder, MemOrder); -void wasm_fence(CGTarget*, MemOrder); -void wasm_intrinsic(CGTarget*, IntrinKind, Operand*, u32, const Operand*, u32); -void wasm_asm_block(CGTarget*, const char*, const AsmConstraint*, u32, Operand*, - const AsmConstraint*, u32, const Operand*, const Sym*, u32); -void wasm_file_scope_asm(CGTarget*, const char*, size_t); -Label wasm_label_new(CGTarget*); -void wasm_label_place(CGTarget*, Label); -void wasm_jump(CGTarget*, Label); -void wasm_cmp_branch(CGTarget*, CmpOp, Operand, Operand, Label); -void wasm_switch(CGTarget*, const CGSwitchDesc*); -CGScope wasm_scope_begin(CGTarget*, const CGScopeDesc*); -void wasm_scope_else(CGTarget*, CGScope); -void wasm_scope_end(CGTarget*, CGScope); -void wasm_break_to(CGTarget*, CGScope); -void wasm_continue_to(CGTarget*, CGScope); -void wasm_set_loc(CGTarget*, SrcLoc); -void wasm_finalize(CGTarget*); -void wasm_destroy(CGTarget*); +typedef CGFuncDesc WasmSemFuncDesc; +typedef CGCallDesc WasmSemCallDesc; -#define WASM_UNIMPL(name) \ - compiler_panic(((WTarget*)t)->c, \ - ((WTarget*)t)->cur_fn_desc ? ((WTarget*)t)->cur_fn_desc->loc \ - : (SrcLoc){0, 0, 0}, \ - "wasm target: " name " not yet implemented") +#include "arch/wasm/internal.h" -static void unimpl_known_frame(CGTarget* t, const CGFuncDesc* f, - const CGKnownFrameDesc* k, FrameSlot* out) { - (void)f; - (void)k; - (void)out; - WASM_UNIMPL("func_begin_known_frame"); -} -static void unimpl_local_addr(CGTarget* t, Operand op, const CGLocalDesc* d, - CGLocalStorage s) { - (void)op; - (void)d; - (void)s; - WASM_UNIMPL("local_addr (address-taken locals)"); -} -static void unimpl_spill_reg(CGTarget* t, Operand a, FrameSlot s, MemAccess m) { - (void)a; - (void)s; - (void)m; - WASM_UNIMPL("spill_reg"); -} -static void unimpl_reload_reg(CGTarget* t, Operand a, FrameSlot s, - MemAccess m) { - (void)a; - (void)s; - (void)m; - WASM_UNIMPL("reload_reg"); -} -static void no_regs(CGTarget* t, RegClass cls, const Reg** out, u32* n) { - (void)t; - (void)cls; - *out = NULL; - *n = 0; -} -static void no_phys_regs(CGTarget* t, RegClass cls, const CGPhysRegInfo** out, - u32* n) { - (void)t; - (void)cls; - *out = NULL; - *n = 0; -} -static int no_caller_saved(CGTarget* t, RegClass cls, Reg r) { - (void)t; - (void)cls; - (void)r; - return 0; -} -static u32 zero_mask(CGTarget* t, const CGCallDesc* d, RegClass cls) { - (void)t; - (void)d; - (void)cls; - return 0; -} -static u32 zero_ret_mask(CGTarget* t, const ABIFuncInfo* f, RegClass cls) { - (void)t; - (void)f; - (void)cls; - return 0; -} -static u32 zero_cs_mask(CGTarget* t, RegClass cls) { - (void)t; - (void)cls; - return 0; -} -static void noop_plan_regs(CGTarget* t, RegClass cls, const Reg* r, u32 n) { - (void)t; - (void)cls; - (void)r; - (void)n; -} -static void noop_reserve_regs(CGTarget* t, RegClass cls, const Reg* r, u32 n) { - (void)t; - (void)cls; - (void)r; - (void)n; -} -static u32 zero_call_stack(CGTarget* t, const CGCallDesc* d) { - (void)t; - (void)d; - return 0; +static void wasm_ir_finalize(void* user, const CgIrModule* module) { + WTarget* t = (WTarget*)user; + wasm_emit_ir_module(t, module); + wasm_finalize((CGTarget*)&t->base); } -static void unimpl_plan_call(CGTarget* t, const CGCallDesc* d, CGCallPlan* p) { - (void)d; - (void)p; - WASM_UNIMPL("plan_call"); -} -static void unimpl_load_call_arg(CGTarget* t, Operand d, - const CGCallPlanMove* m) { - (void)d; - (void)m; - WASM_UNIMPL("load_call_arg"); -} -static void unimpl_store_call_arg(CGTarget* t, const CGCallPlanMove* m) { - (void)m; - WASM_UNIMPL("store_call_arg"); -} -static void unimpl_store_call_ret(CGTarget* t, const CGCallPlanRet* r, - Operand s) { - (void)r; - (void)s; - WASM_UNIMPL("store_call_ret"); -} -static void unimpl_emit_call_plan(CGTarget* t, const CGCallPlan* p) { - (void)p; - WASM_UNIMPL("emit_call_plan"); +static void wasm_ir_destroy(void* user) { + WTarget* t = (WTarget*)user; + if (t) wasm_destroy((CGTarget*)&t->base); } -static void unimpl_tls_addr_of(CGTarget* t, Operand d, ObjSymId s, i64 a) { - (void)d; - (void)s; - (void)a; - WASM_UNIMPL("tls_addr_of"); -} -static void unimpl_bf_load(CGTarget* t, Operand d, Operand r, - BitFieldAccess a) { - (void)d; - (void)r; - (void)a; - WASM_UNIMPL("bitfield_load"); -} -static void unimpl_bf_store(CGTarget* t, Operand r, Operand s, - BitFieldAccess a) { - (void)r; - (void)s; - (void)a; - WASM_UNIMPL("bitfield_store"); -} -static void unimpl_indirect_branch(CGTarget* t, Operand a, const Label* l, - u32 n) { - (void)a; - (void)l; - (void)n; - WASM_UNIMPL("indirect_branch (computed goto)"); +static int wasm_ir_local_static_data_begin( + void* user, const CGLocalStaticDataDesc* desc) { + (void)user; + (void)desc; + return 0; /* opt out: CG falls back to TU-wide data emission. */ } -static void unimpl_load_label_addr(CGTarget* t, Operand d, Label l) { - (void)d; - (void)l; - WASM_UNIMPL("load_label_addr (&&label)"); -} -static int unimpl_lsdb(CGTarget* t, const CGLocalStaticDataDesc* d) { - (void)t; - (void)d; - return 0; /* opt out โ€” CG falls back to TU-wide data emission */ -} -/* The TU-wide data path (cg/data.c) routes function-local-label addresses - * inside static-data initializers through the MCEmitter. The Wasm backend - * has no native concept of an absolute label address resolvable at link - * time the way ELF/Mach-O do, so we short-circuit before MCEmitter with a - * wasm-prefixed diagnostic that the test runner's SKIP regex recognizes. */ -static const char* wasm_data_label_addr_unsupported_msg(CGTarget* t) { - (void)t; + +static const char* wasm_ir_data_label_addr_unsupported_msg(void* user) { + (void)user; return "wasm target: &&label addresses in static-data initializers are " "not yet supported"; } -static void unimpl_lsdw(CGTarget* t, const u8* d, u64 n) { - (void)d; - (void)n; - WASM_UNIMPL("local_static_data_write"); -} -static void unimpl_lsdl(CGTarget* t, Label l, i64 a, u32 w, u32 sp) { - (void)l; - (void)a; - (void)w; - (void)sp; - WASM_UNIMPL("local_static_data_label_addr"); -} -static void unimpl_lsde(CGTarget* t) { WASM_UNIMPL("local_static_data_end"); } -/* wasm_intrinsic, wasm_asm_block, wasm_file_scope_asm live in emit.c โ€” they - * need wir_push / wir_capture_operand and the linearization context. */ - -static MCLabel cg_label_to_mc_label_identity(CGTarget* t, Label l) { - (void)t; - return (MCLabel)l; -} - -static void cleanup_target(void* arg) { cgtarget_free((CGTarget*)arg); } - -CGTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { - Heap* h = (Heap*)c->ctx->heap; - WTarget* x = (WTarget*)h->alloc(h, sizeof *x, _Alignof(WTarget)); - if (!x) return NULL; - memset(x, 0, sizeof *x); - x->c = c; - x->obj = o; - /* MCEmitter is allocated by cg_session but unused by the wasm target โ€” we - * keep it on the base so CGTarget invariants (mc set) hold for callers - * that read CGTarget.mc, but never write through it. */ - (void)mc; - - CGTarget* t = &x->base; - t->c = c; - t->obj = o; - t->mc = mc; - t->virtual_regs = 1; - - t->func_begin = wasm_func_begin; - t->func_begin_known_frame = unimpl_known_frame; - t->func_end = wasm_func_end; - t->alias = wasm_alias; - - t->frame_slot = wasm_frame_slot; - t->local = wasm_local; - t->local_addr = unimpl_local_addr; - t->param = wasm_param; - t->spill_reg = unimpl_spill_reg; - t->reload_reg = unimpl_reload_reg; - - t->get_allocable_regs = no_regs; - t->get_phys_regs = no_phys_regs; - t->get_scratch_regs = no_regs; - t->is_caller_saved = no_caller_saved; - t->call_clobber_mask = zero_mask; - t->return_reg_mask = zero_ret_mask; - t->callee_save_mask = zero_cs_mask; - t->plan_hard_regs = noop_plan_regs; - t->reserve_hard_regs = noop_reserve_regs; - t->call_stack_size = zero_call_stack; - - t->label_new = wasm_label_new; - t->label_place = wasm_label_place; - t->cg_label_to_mc_label = cg_label_to_mc_label_identity; - t->jump = wasm_jump; - t->cmp_branch = wasm_cmp_branch; - t->switch_ = wasm_switch; - t->indirect_branch = unimpl_indirect_branch; - t->load_label_addr = unimpl_load_label_addr; - t->local_static_data_begin = unimpl_lsdb; - t->local_static_data_write = unimpl_lsdw; - t->local_static_data_label_addr = unimpl_lsdl; - t->local_static_data_end = unimpl_lsde; - t->data_label_addr_unsupported_msg = wasm_data_label_addr_unsupported_msg; - t->scope_begin = wasm_scope_begin; - t->scope_else = wasm_scope_else; - t->scope_end = wasm_scope_end; - t->break_to = wasm_break_to; - t->continue_to = wasm_continue_to; - - t->load_imm = wasm_load_imm; - t->load_const = wasm_load_const; - t->copy = wasm_copy; - t->load = wasm_load; - t->store = wasm_store; - t->addr_of = wasm_addr_of; - t->tls_addr_of = unimpl_tls_addr_of; - t->copy_bytes = wasm_copy_bytes; - t->set_bytes = wasm_set_bytes; - t->bitfield_load = unimpl_bf_load; - t->bitfield_store = unimpl_bf_store; - - t->binop = wasm_binop; - t->unop = wasm_unop; - t->cmp = wasm_cmp; - t->convert = wasm_convert; - - t->call = wasm_call; - t->tail_call_unrealizable_reason = wasm_tail_call_unrealizable_reason; - t->plan_call = unimpl_plan_call; - t->load_call_arg = unimpl_load_call_arg; - t->store_call_arg = unimpl_store_call_arg; - t->store_call_ret = unimpl_store_call_ret; - t->emit_call_plan = unimpl_emit_call_plan; - t->ret = wasm_ret; - - t->alloca_ = wasm_alloca; - t->va_start_ = wasm_va_start; - t->va_arg_ = wasm_va_arg; - t->va_end_ = wasm_va_end; - t->va_copy_ = wasm_va_copy; - - t->atomic_load = wasm_atomic_load; - t->atomic_store = wasm_atomic_store; - t->atomic_rmw = wasm_atomic_rmw; - t->atomic_cas = wasm_atomic_cas; - t->fence = wasm_fence; - - t->intrinsic = wasm_intrinsic; - t->asm_block = wasm_asm_block; - t->file_scope_asm = wasm_file_scope_asm; - t->resolve_reg_name = NULL; - - t->set_loc = wasm_set_loc; - t->finalize = wasm_finalize; - t->destroy = wasm_destroy; - compiler_defer(c, cleanup_target, t); - return t; +static const char* wasm_ir_tail_call_unrealizable_reason( + void* user, const WasmSemFuncDesc* caller, const WasmSemCallDesc* call) { + WTarget* t = (WTarget*)user; + const ABIFuncInfo* abi; + (void)caller; + if (!t || !call || !call->fn_type) return NULL; + abi = abi_cg_func_info(t->c->abi, call->fn_type); + if (abi && abi->variadic) + return "wasm cannot tail-call a variadic function (its vararg buffer " + "lives in the frame a sibling call tears down)"; + return NULL; +} + +CgTarget* wasm_cgtarget_new(Compiler* c, ObjBuilder* o, MCEmitter* mc) { + WTarget* emitter = wasm_emit_target_new(c, o, mc); + CgIrRecorderConfig cfg; + if (!emitter) return NULL; + memset(&cfg, 0, sizeof cfg); + cfg.finalize = wasm_ir_finalize; + cfg.destroy = wasm_ir_destroy; + cfg.local_static_data_begin = wasm_ir_local_static_data_begin; + cfg.data_label_addr_unsupported_msg = wasm_ir_data_label_addr_unsupported_msg; + cfg.tail_call_unrealizable_reason = wasm_ir_tail_call_unrealizable_reason; + cfg.user = emitter; + return cg_ir_recorder_new(c, o, &cfg); }