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:
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(¶ms[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, ¶ms[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);
}