commit cf6f708e3c566f50f7f625d7e08d6718f0e2fdda
parent 0b6eaea8eb3bdcdbc9a6ce0e51d95c3da1385ff3
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Tue, 12 May 2026 20:58:19 -0700
Implement CfreeCg public codegen API driving CGTarget directly
Add full value-stack-based codegen implementation (CfreeCg) that drives
CGTarget vtable calls directly, bypassing the internal CG layer. Covers
function lifecycle with C-symbol mangling, local/param slots, push ops,
load/addr/store (with toy frontend stack convention), stack manipulation,
arithmetic/compare/convert with public-to-internal enum mapping, labels
and conditional branches, structured scopes with break/continue, and
ABI-classified call/return. Remaining APIs (float, bytes, alloca, va_*,
atomics, intrinsics, inline asm, data defs, memcpy/memset, index,
field_addr, tail_call) are stubbed for future extension.
Diffstat:
| M | lang/toy/toy.c | | | 12 | +++++++----- |
| M | src/api/cg.c | | | 1405 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
2 files changed, 1412 insertions(+), 5 deletions(-)
diff --git a/lang/toy/toy.c b/lang/toy/toy.c
@@ -9,16 +9,18 @@
/* ============================================================
* CG API coverage checklist — APIs NOT yet exercised by toy:
*
- * Types:
+ * Types (query):
+ * cfree_cg_type_is_ptr / ptr_pointee
+ * cfree_cg_type_is_func / func_ret / func_nparams / func_param
+ * cfree_cg_type_is_record / record_nfields / record_field
+ * cfree_cg_type_size / type_align
+ *
+ * Types (constructors):
* cfree_cg_type_array
* cfree_cg_type_qualified
* cfree_cg_type_alias
* cfree_cg_type_record
* cfree_cg_type_enum
- * cfree_cg_type_is_ptr / ptr_pointee
- * cfree_cg_type_is_func / func_ret / func_nparams / func_param
- * cfree_cg_type_is_record / record_nfields / record_field
- * cfree_cg_type_size / type_align
*
* Functions & data:
* cfree_cg_func_decl
diff --git a/src/api/cg.c b/src/api/cg.c
@@ -4,9 +4,11 @@
#include "api/cg_api.h"
#include "abi/abi.h"
+#include "arch/arch.h"
#include "core/arena.h"
#include "core/heap.h"
#include "core/segvec.h"
+#include "obj/obj.h"
#include "type/type.h"
typedef enum CgApiTypeKind {
@@ -462,3 +464,1406 @@ void cg_api_fini(Compiler* c) {
c->cg_api = NULL;
c->cg_api_free = NULL;
}
+
+/* ============================================================
+ * CfreeCg: public codegen API implementation
+ *
+ * Drives CGTarget directly with its own value stack, mirroring
+ * the internal CG in src/cg/cg.c but without depending on it.
+ * ============================================================ */
+
+typedef enum SResidency {
+ RES_INHERENT,
+ RES_REG,
+ RES_SPILLED,
+} SResidency;
+
+typedef struct ApiSValue {
+ Operand op;
+ const Type* type;
+ u8 res;
+ u8 pinned;
+ u8 pad[2];
+ FrameSlot spill_slot;
+} ApiSValue;
+
+#define API_CG_STACK_INITIAL 16u
+
+typedef struct ApiCgScope {
+ Label break_lbl;
+ Label continue_lbl;
+ const Type* result_type;
+} ApiCgScope;
+
+#define API_CG_MAX_SCOPES 64
+
+struct CfreeCg {
+ Compiler* c;
+ ObjBuilder* obj;
+ CGTarget* target;
+ MCEmitter* mc;
+
+ ApiSValue* stack;
+ u32 sp;
+ u32 cap;
+
+ struct {
+ FrameSlot* free;
+ u32 n;
+ u32 cap;
+ } slot_pools[3];
+
+ CGABIValue* avs_in_flight;
+ u32 avs_in_flight_n;
+
+ const Type* fn_ret_type;
+ const ABIFuncInfo* fn_abi;
+ SrcLoc cur_loc;
+
+ CGFuncDesc fn_desc;
+ CGParamDesc fn_params[64];
+
+ ApiCgScope scopes[API_CG_MAX_SCOPES];
+ u32 nscopes;
+};
+
+/* ---- value stack helpers ---- */
+
+static u8 api_type_class(const Type* ty) {
+ if (ty && (ty->kind == TY_FLOAT || ty->kind == TY_DOUBLE ||
+ ty->kind == TY_LDOUBLE)) {
+ return RC_FP;
+ }
+ return RC_INT;
+}
+
+static Operand api_op_imm(i64 v, const Type* ty) {
+ Operand o;
+ memset(&o, 0, sizeof o);
+ o.kind = OPK_IMM;
+ o.cls = api_type_class(ty);
+ o.type = ty;
+ o.v.imm = v;
+ return o;
+}
+
+static Operand api_op_reg(Reg r, const Type* ty) {
+ Operand o;
+ memset(&o, 0, sizeof o);
+ o.kind = OPK_REG;
+ o.cls = api_type_class(ty);
+ o.type = ty;
+ o.v.reg = r;
+ return o;
+}
+
+static Operand api_op_local(FrameSlot s, const Type* ty) {
+ Operand o;
+ memset(&o, 0, sizeof o);
+ o.kind = OPK_LOCAL;
+ o.cls = RC_INT;
+ o.type = ty;
+ o.v.frame_slot = s;
+ return o;
+}
+
+static Operand api_op_global(ObjSymId sym, i64 addend, const Type* ty) {
+ Operand o;
+ memset(&o, 0, sizeof o);
+ o.kind = OPK_GLOBAL;
+ o.cls = RC_INT;
+ o.type = ty;
+ o.v.global.sym = sym;
+ o.v.global.addend = addend;
+ return o;
+}
+
+static u8 api_residency_for(const Operand* o) {
+ if (o->kind == OPK_REG || o->kind == OPK_INDIRECT) return RES_REG;
+ return RES_INHERENT;
+}
+
+static ApiSValue api_make_sv(Operand op, const Type* ty) {
+ ApiSValue sv;
+ memset(&sv, 0, sizeof sv);
+ sv.op = op;
+ sv.type = ty;
+ sv.res = api_residency_for(&op);
+ sv.spill_slot = FRAME_SLOT_NONE;
+ return sv;
+}
+
+static const Type* api_sv_type(const ApiSValue* sv) {
+ return sv->type ? sv->type : sv->op.type;
+}
+
+static int api_is_lvalue(const Operand* o) {
+ return o->kind == OPK_LOCAL || o->kind == OPK_GLOBAL ||
+ o->kind == OPK_INDIRECT;
+}
+
+static void api_stack_grow(CfreeCg* g, u32 want) {
+ Heap* h = g->c->env->heap;
+ u32 cap = g->cap;
+ ApiSValue* nb;
+ if (cap >= want) return;
+ while (cap < want) cap = cap ? cap * 2u : API_CG_STACK_INITIAL;
+ nb = (ApiSValue*)h->alloc(h, sizeof(ApiSValue) * cap, _Alignof(ApiSValue));
+ if (g->stack) {
+ memcpy(nb, g->stack, sizeof(ApiSValue) * g->sp);
+ h->free(h, g->stack, sizeof(ApiSValue) * g->cap);
+ }
+ g->stack = nb;
+ g->cap = cap;
+}
+
+static void api_push(CfreeCg* g, ApiSValue v) {
+ api_stack_grow(g, g->sp + 1);
+ g->stack[g->sp++] = v;
+}
+
+static ApiSValue api_pop(CfreeCg* g) {
+ if (g->sp == 0) {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: stack underflow");
+ }
+ return g->stack[--g->sp];
+}
+
+/* ---- register class helpers ---- */
+
+static u8 api_class_of_sv(const ApiSValue* sv) {
+ if (sv->op.kind == OPK_IMM || sv->op.kind == OPK_REG)
+ return sv->op.cls;
+ return RC_INT;
+}
+
+static Reg api_reg_of_sv(const ApiSValue* sv) {
+ if (sv->op.kind == OPK_REG) return sv->op.v.reg;
+ if (sv->op.kind == OPK_INDIRECT) return sv->op.v.ind.base;
+ return (Reg)REG_NONE;
+}
+
+static void api_set_owned_reg(ApiSValue* sv, Reg r) {
+ if (sv->op.kind == OPK_REG) sv->op.v.reg = r;
+ else if (sv->op.kind == OPK_INDIRECT) sv->op.v.ind.base = r;
+}
+
+/* ---- spill slot management ---- */
+
+static void api_take_spill_slot_alloc(CfreeCg* g, u8 cls, FrameSlot* out) {
+ CGTarget* T = g->target;
+ FrameSlotDesc fsd;
+ memset(&fsd, 0, sizeof fsd);
+ fsd.kind = FS_SPILL;
+ fsd.size = (cls == RC_FP) ? 8 : 8;
+ fsd.align = (cls == RC_FP) ? 8 : 8;
+ *out = T->frame_slot(T, &fsd);
+}
+
+static FrameSlot api_take_spill_slot(CfreeCg* g, u8 cls) {
+ if (cls < 3 && g->slot_pools[cls].n > 0) {
+ return g->slot_pools[cls].free[--g->slot_pools[cls].n];
+ }
+ FrameSlot s;
+ api_take_spill_slot_alloc(g, cls, &s);
+ return s;
+}
+
+static void api_return_spill_slot(CfreeCg* g, FrameSlot s, u8 cls) {
+ Heap* h;
+ if (cls >= 3) return;
+ h = g->c->env->heap;
+ if (g->slot_pools[cls].n >= g->slot_pools[cls].cap) {
+ u32 new_cap = g->slot_pools[cls].cap ? g->slot_pools[cls].cap * 2 : 8;
+ FrameSlot* nb = (FrameSlot*)h->alloc(h, sizeof(FrameSlot) * new_cap, _Alignof(FrameSlot));
+ if (g->slot_pools[cls].free) {
+ memcpy(nb, g->slot_pools[cls].free, sizeof(FrameSlot) * g->slot_pools[cls].n);
+ h->free(h, g->slot_pools[cls].free, sizeof(FrameSlot) * g->slot_pools[cls].cap);
+ }
+ g->slot_pools[cls].free = nb;
+ g->slot_pools[cls].cap = new_cap;
+ }
+ g->slot_pools[cls].free[g->slot_pools[cls].n++] = s;
+}
+
+/* ---- register allocation / spill ---- */
+
+static ApiSValue* api_pick_victim(CfreeCg* g, u8 cls) {
+ for (u32 i = 0; i < g->sp; ++i) {
+ ApiSValue* sv = &g->stack[i];
+ if (sv->res != RES_REG || sv->pinned) continue;
+ if (api_class_of_sv(sv) != cls) continue;
+ return sv;
+ }
+ return NULL;
+}
+
+static int api_spill_avs_victim(CfreeCg* g, u8 cls) {
+ CGTarget* T = g->target;
+ for (u32 i = 0; i < g->avs_in_flight_n; ++i) {
+ CGABIValue* av = &g->avs_in_flight[i];
+ if (av->storage.kind != OPK_REG) continue;
+ if (av->storage.cls != cls) continue;
+ FrameSlot slot = api_take_spill_slot(g, cls);
+ MemAccess ma;
+ memset(&ma, 0, sizeof ma);
+ ma.type = av->type;
+ ma.size = av->type ? abi_sizeof(g->c->abi, av->type) : 8;
+ ma.align = av->type ? abi_alignof(g->c->abi, av->type) : 8;
+ T->spill_reg(T, av->storage, slot, ma);
+ Operand local = api_op_local(slot, av->type);
+ local.cls = cls;
+ av->storage = local;
+ return 1;
+ }
+ return 0;
+}
+
+static MemAccess api_mem_for_lvalue(CfreeCg* g, const Operand* lv, const Type* ty) {
+ MemAccess m;
+ memset(&m, 0, sizeof m);
+ m.type = ty;
+ m.size = ty ? abi_sizeof(g->c->abi, ty) : 0;
+ m.align = ty ? abi_alignof(g->c->abi, ty) : 0;
+ m.flags = MF_NONE;
+ if (ty && (ty->qual & Q_VOLATILE)) m.flags |= MF_VOLATILE;
+ if (lv->kind == OPK_LOCAL) {
+ m.alias.kind = (u8)ALIAS_LOCAL;
+ m.alias.v.local_id = (i32)lv->v.frame_slot;
+ } else if (lv->kind == OPK_GLOBAL) {
+ m.alias.kind = (u8)ALIAS_GLOBAL;
+ } else {
+ m.alias.kind = (u8)ALIAS_UNKNOWN;
+ }
+ return m;
+}
+
+static MemAccess api_mem_for_spill(CfreeCg* g, const ApiSValue* sv) {
+ const Type* ty = api_sv_type(sv);
+ MemAccess m;
+ memset(&m, 0, sizeof m);
+ m.type = ty;
+ m.size = ty ? abi_sizeof(g->c->abi, ty) : 8;
+ m.align = ty ? abi_alignof(g->c->abi, ty) : 8;
+ m.alias.kind = (u8)ALIAS_UNKNOWN;
+ return m;
+}
+
+static Reg api_alloc_reg_or_spill(CfreeCg* g, u8 cls, const Type* ty) {
+ CGTarget* T = g->target;
+ Reg r = T->alloc_reg(T, cls, ty);
+ if (r != (Reg)REG_NONE) return r;
+
+ ApiSValue* victim = api_pick_victim(g, cls);
+ if (victim) {
+ FrameSlot slot = api_take_spill_slot(g, cls);
+ Operand victim_reg = api_op_reg((Reg)api_reg_of_sv(victim), victim->type);
+ T->spill_reg(T, victim_reg, slot, api_mem_for_spill(g, victim));
+ victim->spill_slot = slot;
+ victim->res = RES_SPILLED;
+ api_set_owned_reg(victim, (Reg)REG_NONE);
+ } else if (!api_spill_avs_victim(g, cls)) {
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: regalloc - no spillable victim (class %u)",
+ (unsigned)cls);
+ }
+
+ r = T->alloc_reg(T, cls, ty);
+ if (r == (Reg)REG_NONE) {
+ compiler_panic(g->c, g->cur_loc,
+ "CfreeCg: regalloc - class %u still empty after spill",
+ (unsigned)cls);
+ }
+ return r;
+}
+
+static void api_ensure_reg(CfreeCg* g, ApiSValue* sv) {
+ if (sv->res != RES_SPILLED) return;
+ CGTarget* T = g->target;
+ u8 cls = api_class_of_sv(sv);
+ const Type* ty = api_sv_type(sv);
+ Reg r = api_alloc_reg_or_spill(g, cls, ty ? ty : type_prim(g->c->global, TY_INT));
+ T->reload_reg(T, api_op_reg(r, ty), sv->spill_slot, api_mem_for_spill(g, sv));
+ api_return_spill_slot(g, sv->spill_slot, cls);
+ sv->spill_slot = FRAME_SLOT_NONE;
+ if (sv->op.kind == OPK_INDIRECT) {
+ sv->op.v.ind.base = r;
+ } else {
+ sv->op = api_op_reg(r, api_sv_type(sv));
+ }
+ sv->res = RES_REG;
+}
+
+static Operand api_force_reg(CfreeCg* g, ApiSValue* v, const Type* ty) {
+ CGTarget* T = g->target;
+ api_ensure_reg(g, v);
+ if (v->op.kind == OPK_REG) return v->op;
+ Reg r = api_alloc_reg_or_spill(g, api_type_class(ty), ty);
+ Operand dst = api_op_reg(r, ty);
+ if (v->op.kind == OPK_IMM) {
+ T->load_imm(T, dst, v->op.v.imm);
+ } else if (api_is_lvalue(&v->op)) {
+ T->load(T, dst, v->op, api_mem_for_lvalue(g, &v->op, ty));
+ if (v->op.kind == OPK_INDIRECT) {
+ T->free_reg(T, v->op.v.ind.base, RC_INT);
+ }
+ } else {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: cannot force operand to register");
+ }
+ v->op = dst;
+ v->res = RES_REG;
+ return dst;
+}
+
+static Operand api_force_reg_unless_imm(CfreeCg* g, ApiSValue* v, const Type* ty) {
+ if (v->op.kind == OPK_IMM) return v->op;
+ return api_force_reg(g, v, ty);
+}
+
+static void api_release(CfreeCg* g, ApiSValue* sv) {
+ if (sv->res == RES_REG) {
+ g->target->free_reg(g->target, (Reg)api_reg_of_sv(sv), api_class_of_sv(sv));
+ } else if (sv->res == RES_SPILLED) {
+ api_return_spill_slot(g, sv->spill_slot, api_class_of_sv(sv));
+ sv->spill_slot = FRAME_SLOT_NONE;
+ }
+ sv->res = RES_INHERENT;
+}
+
+static void api_release_arg_storage(CfreeCg* g, Operand* storage) {
+ if (storage->kind == OPK_REG) {
+ g->target->free_reg(g->target, storage->v.reg, storage->cls);
+ } else if (storage->kind == OPK_LOCAL && storage->cls < 3) {
+ api_return_spill_slot(g, storage->v.frame_slot, storage->cls);
+ }
+}
+
+/* ---- BinOp / UnOp / CmpOp mapping ---- */
+
+static BinOp api_map_binop(CfreeCgBinOp op) {
+ switch (op) {
+ case CFREE_CG_ADD: return BO_IADD;
+ case CFREE_CG_SUB: return BO_ISUB;
+ case CFREE_CG_MUL: return BO_IMUL;
+ case CFREE_CG_SDIV: return BO_SDIV;
+ case CFREE_CG_UDIV: return BO_UDIV;
+ case CFREE_CG_SREM: return BO_SREM;
+ case CFREE_CG_UREM: return BO_UREM;
+ case CFREE_CG_AND: return BO_AND;
+ case CFREE_CG_OR: return BO_OR;
+ case CFREE_CG_XOR: return BO_XOR;
+ case CFREE_CG_SHL: return BO_SHL;
+ case CFREE_CG_SHR_S: return BO_SHR_S;
+ case CFREE_CG_SHR_U: return BO_SHR_U;
+ }
+ return BO_IADD;
+}
+
+static UnOp api_map_unop(CfreeCgUnOp op) {
+ switch (op) {
+ case CFREE_CG_NEG: return UO_NEG;
+ case CFREE_CG_NOT: return UO_NOT;
+ case CFREE_CG_BNOT: return UO_BNOT;
+ }
+ return UO_NEG;
+}
+
+static CmpOp api_map_cmp(CfreeCgCmpOp op) {
+ switch (op) {
+ case CFREE_CG_EQ: return CMP_EQ;
+ case CFREE_CG_NE: return CMP_NE;
+ case CFREE_CG_LT_S: return CMP_LT_S;
+ case CFREE_CG_LE_S: return CMP_LE_S;
+ case CFREE_CG_GT_S: return CMP_GT_S;
+ case CFREE_CG_GE_S: return CMP_GE_S;
+ case CFREE_CG_LT_U: return CMP_LT_U;
+ case CFREE_CG_LE_U: return CMP_LE_U;
+ case CFREE_CG_GT_U: return CMP_GT_U;
+ case CFREE_CG_GE_U: return CMP_GE_U;
+ }
+ return CMP_EQ;
+}
+
+/* ---- C-symbol mangling ---- */
+
+static Sym api_c_mangle(Compiler* c, CfreeSym name) {
+ size_t len;
+ const char* str = pool_str(c->global, (Sym)name, &len);
+ if (!str) return 0;
+ return obj_format_c_mangle(c, str);
+}
+
+static SymBind api_map_bind(CfreeSymBind b) {
+ switch (b) {
+ case CFREE_SB_LOCAL: return SB_LOCAL;
+ case CFREE_SB_GLOBAL: return SB_GLOBAL;
+ case CFREE_SB_WEAK: return SB_WEAK;
+ }
+ return SB_LOCAL;
+}
+
+/* ============================================================
+ * Public API: CfreeCg lifecycle
+ * ============================================================ */
+
+CfreeCg* cfree_cg_new(CfreeCompiler* c, CfreeObjBuilder* out) {
+ Heap* h;
+ CfreeCg* g;
+ MCEmitter* mc;
+ CGTarget* target;
+ if (!c || !out) return NULL;
+ h = (Heap*)c->env->heap;
+ mc = mc_new((Compiler*)c, (ObjBuilder*)out);
+ if (!mc) return NULL;
+ target = cgtarget_new((Compiler*)c, (ObjBuilder*)out, mc);
+ if (!target) {
+ mc_free(mc);
+ return NULL;
+ }
+ g = (CfreeCg*)h->alloc(h, sizeof(CfreeCg), _Alignof(CfreeCg));
+ if (!g) {
+ cgtarget_free(target);
+ mc_free(mc);
+ return NULL;
+ }
+ memset(g, 0, sizeof *g);
+ g->c = (Compiler*)c;
+ g->obj = (ObjBuilder*)out;
+ g->target = target;
+ g->mc = mc;
+ return g;
+}
+
+void cfree_cg_free(CfreeCg* g) {
+ Heap* h;
+ if (!g) return;
+ cgtarget_finalize(g->target);
+ cgtarget_free(g->target);
+ mc_free(g->mc);
+ h = g->c->env->heap;
+ if (g->stack) h->free(h, g->stack, sizeof(ApiSValue) * g->cap);
+ for (u32 c = 0; c < 3; ++c) {
+ if (g->slot_pools[c].free) {
+ h->free(h, g->slot_pools[c].free, sizeof(FrameSlot) * g->slot_pools[c].cap);
+ }
+ }
+ h->free(h, g, sizeof *g);
+}
+
+/* ============================================================
+ * Source location
+ * ============================================================ */
+
+void cfree_cg_set_loc(CfreeCg* g, CfreeSrcLoc loc) {
+ if (!g) return;
+ g->cur_loc = *(SrcLoc*)&loc;
+ if (g->target->set_loc) g->target->set_loc(g->target, *(SrcLoc*)&loc);
+}
+
+/* ============================================================
+ * Function lifecycle
+ * ============================================================ */
+
+void cfree_cg_func_decl(CfreeCg* g, CfreeSym name, CfreeCgTypeId fn_type,
+ CfreeCgDeclAttrs attrs) {
+ Compiler* c;
+ ObjBuilder* ob;
+ Sym mangled;
+ ObjSymId sym;
+ const Type* fty;
+ if (!g) return;
+ c = g->c;
+ ob = g->obj;
+ fty = resolve_type(c, fn_type);
+ if (!fty) return;
+ mangled = api_c_mangle(c, name);
+ sym = obj_symbol_find(ob, mangled);
+ if (sym == OBJ_SYM_NONE) {
+ sym = obj_symbol(ob, mangled, api_map_bind(attrs.bind), SK_FUNC,
+ OBJ_SEC_NONE, 0, 0);
+ }
+ if (attrs.flags & CFREE_CG_DECL_DEFINED) {
+ ObjSecId text_sec = obj_section(ob, pool_intern_cstr(c->global, ".text"),
+ SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
+ obj_symbol_define(ob, sym, text_sec, 0, 0);
+ }
+ (void)fn_type;
+}
+
+void cfree_cg_func_begin(CfreeCg* g, CfreeSym name, CfreeCgTypeId fn_type,
+ CfreeCgDeclAttrs attrs) {
+ Compiler* c;
+ ObjBuilder* ob;
+ CGTarget* T;
+ Sym mangled;
+ ObjSymId sym;
+ ObjSecId text_sec;
+ const Type* fty;
+ const ABIFuncInfo* abi;
+ if (!g) return;
+ c = g->c;
+ ob = g->obj;
+ T = g->target;
+ fty = resolve_type(c, fn_type);
+ if (!fty) return;
+ abi = abi_func_info(c->abi, fty);
+
+ mangled = api_c_mangle(c, name);
+ sym = obj_symbol_find(ob, mangled);
+ text_sec = obj_section(ob, pool_intern_cstr(c->global, ".text"),
+ SEC_TEXT, SF_EXEC | SF_ALLOC, 4);
+
+ if (sym == OBJ_SYM_NONE) {
+ sym = obj_symbol(ob, mangled, api_map_bind(attrs.bind), SK_FUNC,
+ text_sec, 0, 0);
+ } else {
+ obj_symbol_define(ob, sym, text_sec, 0, 0);
+ }
+
+ memset(&g->fn_desc, 0, sizeof g->fn_desc);
+ g->fn_desc.sym = sym;
+ g->fn_desc.text_section_id = text_sec;
+ g->fn_desc.group_id = OBJ_GROUP_NONE;
+ g->fn_desc.fn_type = fty;
+ g->fn_desc.abi = abi;
+ g->fn_desc.loc = g->cur_loc;
+ if (attrs.flags & CFREE_CG_DECL_NORETURN) g->fn_desc.flags |= CGFD_NORETURN;
+
+ g->fn_ret_type = fty->fn.ret;
+ g->fn_abi = abi;
+ g->sp = 0;
+ for (u32 i = 0; i < 3; ++i) g->slot_pools[i].n = 0;
+ g->avs_in_flight = NULL;
+ g->avs_in_flight_n = 0;
+
+ T->func_begin(T, &g->fn_desc);
+}
+
+void cfree_cg_func_end(CfreeCg* g) {
+ if (!g) return;
+ g->target->func_end(g->target);
+ g->fn_abi = NULL;
+ g->fn_ret_type = NULL;
+}
+
+/* ============================================================
+ * Local / param slots
+ * ============================================================ */
+
+CfreeCgSlot cfree_cg_local_slot(CfreeCg* g, CfreeCgTypeId type, CfreeSym name) {
+ const Type* ty;
+ FrameSlotDesc fsd;
+ if (!g) return 0;
+ ty = resolve_type(g->c, type);
+ if (!ty) return 0;
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = ty;
+ fsd.name = (Sym)name;
+ fsd.loc = g->cur_loc;
+ fsd.size = abi_sizeof(g->c->abi, ty);
+ fsd.align = abi_alignof(g->c->abi, ty);
+ fsd.kind = FS_LOCAL;
+ return (CfreeCgSlot)g->target->frame_slot(g->target, &fsd);
+}
+
+CfreeCgSlot cfree_cg_param_slot(CfreeCg* g, uint32_t index, CfreeCgTypeId type,
+ CfreeSym name) {
+ const Type* ty;
+ FrameSlot slot;
+ CGParamDesc pd;
+ FrameSlotDesc fsd;
+ if (!g) return 0;
+ ty = resolve_type(g->c, type);
+ if (!ty) return 0;
+
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = ty;
+ fsd.name = (Sym)name;
+ fsd.loc = g->cur_loc;
+ fsd.size = abi_sizeof(g->c->abi, ty);
+ fsd.align = abi_alignof(g->c->abi, ty);
+ fsd.kind = FS_PARAM;
+ slot = g->target->frame_slot(g->target, &fsd);
+
+ memset(&pd, 0, sizeof pd);
+ pd.index = index;
+ pd.name = (Sym)name;
+ pd.type = ty;
+ pd.slot = slot;
+ if (g->fn_abi && index < g->fn_abi->nparams) {
+ pd.abi = &g->fn_abi->params[index];
+ }
+ pd.loc = g->cur_loc;
+ g->target->param(g->target, &pd);
+
+ return (CfreeCgSlot)slot;
+}
+
+/* ============================================================
+ * Push operations
+ * ============================================================ */
+
+void cfree_cg_push_int(CfreeCg* g, uint64_t value, CfreeCgTypeId type) {
+ const Type* ty;
+ if (!g) return;
+ ty = resolve_type(g->c, type);
+ if (!ty) return;
+ api_push(g, api_make_sv(api_op_imm((i64)value, ty), ty));
+}
+
+void cfree_cg_push_float(CfreeCg* g, double value, CfreeCgTypeId type) {
+ (void)g; (void)value; (void)type;
+}
+
+void cfree_cg_push_bytes(CfreeCg* g, const uint8_t* str, size_t len,
+ CfreeCgTypeId pointee_type) {
+ (void)g; (void)str; (void)len; (void)pointee_type;
+}
+
+void cfree_cg_push_local(CfreeCg* g, CfreeCgSlot slot) {
+ if (!g) return;
+ api_push(g, api_make_sv(api_op_local((FrameSlot)slot, NULL), NULL));
+}
+
+void cfree_cg_push_symbol(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
+ CfreeCgSymbolRefKind kind, int64_t addend) {
+ Sym mangled;
+ ObjSymId sym;
+ const Type* ty;
+ if (!g) return;
+ ty = resolve_type(g->c, type);
+ if (!ty) return;
+ mangled = api_c_mangle(g->c, name);
+ sym = obj_symbol_find(g->obj, mangled);
+ if (sym == OBJ_SYM_NONE) {
+ sym = obj_symbol(g->obj, mangled, SB_GLOBAL, SK_FUNC,
+ OBJ_SEC_NONE, 0, 0);
+ }
+ (void)kind;
+ api_push(g, api_make_sv(api_op_global(sym, addend, ty), ty));
+}
+
+/* ============================================================
+ * Load / addr / store
+ * ============================================================ */
+
+void cfree_cg_load(CfreeCg* g) {
+ ApiSValue v;
+ const Type* ty;
+ Operand dst;
+ if (!g) return;
+ v = api_pop(g);
+ api_ensure_reg(g, &v);
+ if (!api_is_lvalue(&v.op)) {
+ api_push(g, v);
+ return;
+ }
+ ty = api_sv_type(&v);
+ dst = api_force_reg(g, &v, ty);
+ api_push(g, api_make_sv(dst, ty));
+}
+
+void cfree_cg_addr(CfreeCg* g) {
+ ApiSValue v;
+ CGTarget* T;
+ const Type* pty;
+ Reg r;
+ Operand dst;
+ if (!g) return;
+ T = g->target;
+ v = api_pop(g);
+ api_ensure_reg(g, &v);
+ if (!api_is_lvalue(&v.op)) {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: addr operand is not an lvalue");
+ return;
+ }
+ pty = type_ptr(g->c->global, api_sv_type(&v));
+ r = api_alloc_reg_or_spill(g, RC_INT, pty);
+ dst = api_op_reg(r, pty);
+ T->addr_of(T, dst, v.op);
+ api_release(g, &v);
+ api_push(g, api_make_sv(dst, pty));
+}
+
+void cfree_cg_store(CfreeCg* g) {
+ ApiSValue lv, rv;
+ CGTarget* T;
+ const Type* ty;
+ Operand src;
+ if (!g) return;
+ T = g->target;
+ /* Toy frontend pushes rvalue then lvalue: stack is [..., rv, lv].
+ * Pop lv first (TOS), then rv. */
+ lv = api_pop(g);
+ rv = api_pop(g);
+ api_ensure_reg(g, &lv);
+ api_ensure_reg(g, &rv);
+ if (!api_is_lvalue(&lv.op)) {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: store destination is not an lvalue");
+ return;
+ }
+ ty = api_sv_type(&lv);
+ if (rv.op.kind == OPK_IMM || rv.op.kind == OPK_REG) {
+ src = rv.op;
+ } else {
+ src = api_force_reg(g, &rv, api_sv_type(&rv));
+ }
+ T->store(T, lv.op, src, api_mem_for_lvalue(g, &lv.op, ty));
+ api_release(g, &lv);
+ api_push(g, api_make_sv(src, ty));
+}
+
+/* ============================================================
+ * Stack manipulation
+ * ============================================================ */
+
+void cfree_cg_dup(CfreeCg* g) {
+ ApiSValue v;
+ if (!g || g->sp == 0) return;
+ v = g->stack[g->sp - 1];
+ if (v.op.kind == OPK_REG && v.res == RES_REG) {
+ Reg r = api_alloc_reg_or_spill(g, v.op.cls, v.type);
+ Operand dst = api_op_reg(r, v.type);
+ g->target->copy(g->target, dst, v.op);
+ api_push(g, api_make_sv(dst, v.type));
+ } else {
+ api_push(g, v);
+ }
+}
+
+void cfree_cg_swap(CfreeCg* g) {
+ ApiSValue tmp;
+ if (!g || g->sp < 2) return;
+ tmp = g->stack[g->sp - 1];
+ g->stack[g->sp - 1] = g->stack[g->sp - 2];
+ g->stack[g->sp - 2] = tmp;
+}
+
+void cfree_cg_drop(CfreeCg* g) {
+ ApiSValue v;
+ if (!g) return;
+ v = api_pop(g);
+ api_release(g, &v);
+}
+
+void cfree_cg_rot3(CfreeCg* g) {
+ ApiSValue a, b, c;
+ if (!g || g->sp < 3) return;
+ a = g->stack[g->sp - 3];
+ b = g->stack[g->sp - 2];
+ c = g->stack[g->sp - 1];
+ g->stack[g->sp - 3] = b;
+ g->stack[g->sp - 2] = c;
+ g->stack[g->sp - 1] = a;
+}
+
+/* ============================================================
+ * Arithmetic / compare / convert
+ * ============================================================ */
+
+void cfree_cg_binop(CfreeCg* g, CfreeCgBinOp op) {
+ ApiSValue b, a;
+ CGTarget* T;
+ const Type* ty;
+ BinOp iop;
+ Operand ra, rb;
+ Reg rr;
+ Operand dst;
+ if (!g) return;
+ T = g->target;
+ b = api_pop(g);
+ a = api_pop(g);
+ ty = a.type ? a.type : b.type;
+ iop = api_map_binop(op);
+
+ ra = api_force_reg_unless_imm(g, &a, ty);
+ rb = api_force_reg_unless_imm(g, &b, ty);
+ rr = api_alloc_reg_or_spill(g, api_type_class(ty), ty);
+ dst = api_op_reg(rr, ty);
+ T->binop(T, iop, dst, ra, rb);
+ api_release(g, &a);
+ api_release(g, &b);
+ api_push(g, api_make_sv(dst, ty));
+}
+
+void cfree_cg_unop(CfreeCg* g, CfreeCgUnOp op) {
+ ApiSValue a;
+ CGTarget* T;
+ const Type* ty;
+ UnOp iop;
+ Operand ra;
+ Reg rr;
+ Operand dst;
+ if (!g) return;
+ T = g->target;
+ a = api_pop(g);
+ ty = a.type ? a.type : a.op.type;
+ iop = api_map_unop(op);
+
+ ra = api_force_reg_unless_imm(g, &a, ty);
+ rr = api_alloc_reg_or_spill(g, api_type_class(ty), ty);
+ dst = api_op_reg(rr, ty);
+ T->unop(T, iop, dst, ra);
+ api_release(g, &a);
+ api_push(g, api_make_sv(dst, ty));
+}
+
+void cfree_cg_cmp(CfreeCg* g, CfreeCgCmpOp op) {
+ ApiSValue b, a;
+ CGTarget* T;
+ const Type* opty;
+ const Type* i32;
+ CmpOp cop;
+ Operand ra, rb;
+ Reg rr;
+ Operand dst;
+ if (!g) return;
+ T = g->target;
+ b = api_pop(g);
+ a = api_pop(g);
+ opty = a.type ? a.type : b.type;
+ i32 = type_prim(g->c->global, TY_INT);
+ cop = api_map_cmp(op);
+
+ ra = api_force_reg_unless_imm(g, &a, opty);
+ rb = api_force_reg_unless_imm(g, &b, opty);
+ rr = api_alloc_reg_or_spill(g, RC_INT, i32);
+ dst = api_op_reg(rr, i32);
+ T->cmp(T, cop, dst, ra, rb);
+ api_release(g, &a);
+ api_release(g, &b);
+ api_push(g, api_make_sv(dst, i32));
+}
+
+void cfree_cg_convert(CfreeCg* g, CfreeCgTypeId dst_type) {
+ ApiSValue v;
+ CGTarget* T;
+ const Type* sty;
+ const Type* dty;
+ ConvKind ck;
+ Operand src;
+ Reg rr;
+ Operand dst;
+ int s_int, d_int, s_flt, d_flt, s_ptr, d_ptr;
+ u32 s_sz, d_sz;
+ int s_signed;
+ if (!g) return;
+ T = g->target;
+ dty = resolve_type(g->c, dst_type);
+ if (!dty) return;
+ v = api_pop(g);
+ sty = v.type ? v.type : v.op.type;
+ if (sty == dty) {
+ api_push(g, v);
+ return;
+ }
+
+ s_int = type_is_int(sty);
+ d_int = type_is_int(dty);
+ s_flt = sty && (sty->kind == TY_FLOAT || sty->kind == TY_DOUBLE);
+ d_flt = dty && (dty->kind == TY_FLOAT || dty->kind == TY_DOUBLE);
+ s_sz = sty ? abi_sizeof(g->c->abi, sty) : 0;
+ d_sz = dty ? abi_sizeof(g->c->abi, dty) : 0;
+ s_signed = sty ? abi_type_info(g->c->abi, sty).signed_ : 0;
+ s_ptr = type_is_ptr(sty);
+ d_ptr = type_is_ptr(dty);
+
+ {
+ int s_int_or_ptr = s_int || s_ptr;
+ int d_int_or_ptr = d_int || d_ptr;
+ if (s_int_or_ptr && d_int_or_ptr) {
+ if (d_sz < s_sz) {
+ ck = CV_TRUNC;
+ } else if (d_sz > s_sz) {
+ ck = (s_int && s_signed) ? CV_SEXT : CV_ZEXT;
+ } else {
+ v.type = dty;
+ v.op.type = dty;
+ api_push(g, v);
+ return;
+ }
+ } else if (s_int && d_flt) {
+ ck = s_signed ? CV_ITOF_S : CV_ITOF_U;
+ } else if (s_flt && d_int) {
+ int d_signed = abi_type_info(g->c->abi, dty).signed_;
+ ck = d_signed ? CV_FTOI_S : CV_FTOI_U;
+ } else if (s_flt && d_flt) {
+ ck = (d_sz > s_sz) ? CV_FEXT : CV_FTRUNC;
+ } else {
+ ck = CV_BITCAST;
+ }
+ }
+
+ src = api_force_reg(g, &v, sty);
+ rr = api_alloc_reg_or_spill(g, api_type_class(dty), dty);
+ dst = api_op_reg(rr, dty);
+ T->convert(T, ck, dst, src);
+ api_release(g, &v);
+ api_push(g, api_make_sv(dst, dty));
+}
+
+/* ============================================================
+ * Intrinsics (stub)
+ * ============================================================ */
+
+void cfree_cg_intrinsic(CfreeCg* g, CfreeCgIntrinsic intrin, uint32_t nargs,
+ CfreeCgTypeId result_type) {
+ (void)g; (void)intrin; (void)nargs; (void)result_type;
+}
+
+/* ============================================================
+ * Atomics (stub)
+ * ============================================================ */
+
+void cfree_cg_atomic_load(CfreeCg* g, CfreeCgMemOrder order) {
+ (void)g; (void)order;
+}
+
+void cfree_cg_atomic_store(CfreeCg* g, CfreeCgMemOrder order) {
+ (void)g; (void)order;
+}
+
+void cfree_cg_atomic_rmw(CfreeCg* g, CfreeCgAtomicOp op, CfreeCgMemOrder order) {
+ (void)g; (void)op; (void)order;
+}
+
+void cfree_cg_atomic_cmpxchg(CfreeCg* g, CfreeCgMemOrder success,
+ CfreeCgMemOrder failure) {
+ (void)g; (void)success; (void)failure;
+}
+
+void cfree_cg_atomic_fence(CfreeCg* g, CfreeCgMemOrder order) {
+ (void)g; (void)order;
+}
+
+/* ============================================================
+ * Inline asm (stub)
+ * ============================================================ */
+
+void cfree_cg_inline_asm(CfreeCg* g, CfreeSym tmpl,
+ const CfreeCgAsmOperand* outputs, uint32_t noutputs,
+ const CfreeCgAsmOperand* inputs, uint32_t ninputs,
+ const CfreeSym* clobbers, uint32_t nclobbers,
+ uint32_t flags) {
+ (void)g; (void)tmpl; (void)outputs; (void)noutputs;
+ (void)inputs; (void)ninputs; (void)clobbers; (void)nclobbers; (void)flags;
+}
+
+/* ============================================================
+ * Labels / branches
+ * ============================================================ */
+
+CfreeCgLabel cfree_cg_label_new(CfreeCg* g) {
+ if (!g) return CFREE_CG_LABEL_NONE;
+ return (CfreeCgLabel)g->target->label_new(g->target);
+}
+
+void cfree_cg_label_place(CfreeCg* g, CfreeCgLabel label) {
+ if (!g) return;
+ g->target->label_place(g->target, (Label)label);
+}
+
+void cfree_cg_jump(CfreeCg* g, CfreeCgLabel label) {
+ if (!g) return;
+ g->target->jump(g->target, (Label)label);
+}
+
+void cfree_cg_branch_true(CfreeCg* g, CfreeCgLabel label) {
+ ApiSValue v;
+ CGTarget* T;
+ const Type* ty;
+ if (!g) return;
+ T = g->target;
+ v = api_pop(g);
+ ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
+ if (v.op.kind == OPK_IMM) {
+ if (v.op.v.imm != 0) T->jump(T, (Label)label);
+ api_release(g, &v);
+ return;
+ }
+ {
+ Operand a = api_force_reg(g, &v, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_NE, a, zero, (Label)label);
+ api_release(g, &v);
+ }
+}
+
+void cfree_cg_branch_false(CfreeCg* g, CfreeCgLabel label) {
+ ApiSValue v;
+ CGTarget* T;
+ const Type* ty;
+ if (!g) return;
+ T = g->target;
+ v = api_pop(g);
+ ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
+ if (v.op.kind == OPK_IMM) {
+ if (v.op.v.imm == 0) T->jump(T, (Label)label);
+ api_release(g, &v);
+ return;
+ }
+ {
+ Operand a = api_force_reg(g, &v, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_EQ, a, zero, (Label)label);
+ api_release(g, &v);
+ }
+}
+
+/* ============================================================
+ * Scopes / structured control flow
+ * ============================================================ */
+
+CfreeCgScope cfree_cg_scope_begin(CfreeCg* g, CfreeCgTypeId result_type) {
+ Label break_lbl, cont_lbl;
+ CGScopeDesc d;
+ ApiCgScope* s;
+ if (!g) return 0;
+ break_lbl = g->target->label_new(g->target);
+ cont_lbl = g->target->label_new(g->target);
+ g->target->label_place(g->target, cont_lbl);
+
+ if (g->nscopes >= API_CG_MAX_SCOPES) {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: too many nested scopes");
+ return 0;
+ }
+ s = &g->scopes[g->nscopes];
+ s->break_lbl = break_lbl;
+ s->continue_lbl = cont_lbl;
+ s->result_type = resolve_type(g->c, result_type);
+ g->nscopes++;
+
+ memset(&d, 0, sizeof d);
+ d.kind = (u8)SCOPE_LOOP;
+ d.break_label = break_lbl;
+ d.continue_label = cont_lbl;
+ d.result_type = s->result_type;
+ (void)g->target->scope_begin(g->target, &d);
+
+ return (CfreeCgScope)g->nscopes;
+}
+
+void cfree_cg_scope_end(CfreeCg* g, CfreeCgScope scope) {
+ u32 idx;
+ if (!g || scope == 0) return;
+ idx = (u32)scope - 1;
+ if (idx >= g->nscopes) return;
+ g->target->label_place(g->target, g->scopes[idx].break_lbl);
+ g->target->scope_end(g->target, (CGScope)scope);
+}
+
+void cfree_cg_break(CfreeCg* g, CfreeCgScope scope) {
+ u32 idx;
+ if (!g || scope == 0) return;
+ idx = (u32)scope - 1;
+ if (idx >= g->nscopes) return;
+ g->target->jump(g->target, g->scopes[idx].break_lbl);
+}
+
+void cfree_cg_break_true(CfreeCg* g, CfreeCgScope scope) {
+ u32 idx;
+ ApiSValue v;
+ CGTarget* T;
+ const Type* ty;
+ if (!g || scope == 0) return;
+ idx = (u32)scope - 1;
+ if (idx >= g->nscopes) return;
+ T = g->target;
+ v = api_pop(g);
+ ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
+
+ if (g->scopes[idx].result_type && g->scopes[idx].result_type->kind != TY_VOID) {
+ cfree_cg_swap(g);
+ {
+ ApiSValue val = api_pop(g);
+ api_ensure_reg(g, &val);
+ Operand a = api_force_reg(g, &val, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_NE, a, zero, g->scopes[idx].break_lbl);
+ api_release(g, &val);
+ }
+ {
+ ApiSValue result = api_pop(g);
+ api_push(g, result);
+ }
+ } else {
+ if (v.op.kind == OPK_IMM) {
+ if (v.op.v.imm != 0) T->jump(T, g->scopes[idx].break_lbl);
+ api_release(g, &v);
+ } else {
+ Operand a = api_force_reg(g, &v, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_NE, a, zero, g->scopes[idx].break_lbl);
+ api_release(g, &v);
+ }
+ }
+}
+
+void cfree_cg_break_false(CfreeCg* g, CfreeCgScope scope) {
+ u32 idx;
+ ApiSValue v;
+ CGTarget* T;
+ const Type* ty;
+ if (!g || scope == 0) return;
+ idx = (u32)scope - 1;
+ if (idx >= g->nscopes) return;
+ T = g->target;
+ v = api_pop(g);
+ ty = v.type ? v.type : type_prim(g->c->global, TY_INT);
+
+ if (g->scopes[idx].result_type && g->scopes[idx].result_type->kind != TY_VOID) {
+ cfree_cg_swap(g);
+ {
+ ApiSValue val = api_pop(g);
+ api_ensure_reg(g, &val);
+ Operand a = api_force_reg(g, &val, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_EQ, a, zero, g->scopes[idx].break_lbl);
+ api_release(g, &val);
+ }
+ {
+ ApiSValue result = api_pop(g);
+ api_push(g, result);
+ }
+ } else {
+ if (v.op.kind == OPK_IMM) {
+ if (v.op.v.imm == 0) T->jump(T, g->scopes[idx].break_lbl);
+ api_release(g, &v);
+ } else {
+ Operand a = api_force_reg(g, &v, ty);
+ Operand zero = api_op_imm(0, ty);
+ T->cmp_branch(T, CMP_EQ, a, zero, g->scopes[idx].break_lbl);
+ api_release(g, &v);
+ }
+ }
+}
+
+void cfree_cg_continue(CfreeCg* g, CfreeCgScope scope) {
+ u32 idx;
+ if (!g || scope == 0) return;
+ idx = (u32)scope - 1;
+ if (idx >= g->nscopes) return;
+ g->target->jump(g->target, g->scopes[idx].continue_lbl);
+}
+
+void cfree_cg_continue_true(CfreeCg* g, CfreeCgScope scope) {
+ (void)g; (void)scope;
+}
+
+void cfree_cg_continue_false(CfreeCg* g, CfreeCgScope scope) {
+ (void)g; (void)scope;
+}
+
+/* ============================================================
+ * Dynamic stack allocation / variadics (stubs)
+ * ============================================================ */
+
+void cfree_cg_alloca(CfreeCg* g, CfreeCgTypeId result_ptr_type, uint32_t align) {
+ (void)g; (void)result_ptr_type; (void)align;
+}
+
+void cfree_cg_va_start(CfreeCg* g) { (void)g; }
+void cfree_cg_va_arg(CfreeCg* g, CfreeCgTypeId type) { (void)g; (void)type; }
+void cfree_cg_va_end(CfreeCg* g) { (void)g; }
+void cfree_cg_va_copy(CfreeCg* g) { (void)g; }
+
+/* ============================================================
+ * Memory operations (stubs)
+ * ============================================================ */
+
+void cfree_cg_memcpy(CfreeCg* g, uint32_t size, uint32_t align) {
+ (void)g; (void)size; (void)align;
+}
+
+void cfree_cg_memset(CfreeCg* g, uint8_t val, uint32_t size, uint32_t align) {
+ (void)g; (void)val; (void)size; (void)align;
+}
+
+void cfree_cg_index(CfreeCg* g, uint32_t offset) {
+ (void)g; (void)offset;
+}
+
+void cfree_cg_field_addr(CfreeCg* g, uint32_t field_index) {
+ (void)g; (void)field_index;
+}
+
+/* ============================================================
+ * Calls / return
+ * ============================================================ */
+
+void cfree_cg_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) {
+ CGTarget* T;
+ const Type* fty;
+ const ABIFuncInfo* abi;
+ const Type* ret_ty;
+ int has_result;
+ CGABIValue* avs;
+ CGCallDesc desc;
+ ApiSValue callee;
+ if (!g) return;
+ T = g->target;
+ fty = resolve_type(g->c, fn_type);
+ if (!fty) return;
+ abi = abi_func_info(g->c->abi, fty);
+ ret_ty = fty->fn.ret;
+ has_result = ret_ty && ret_ty->kind != TY_VOID;
+
+ if (g->sp < (u32)nargs + 1u) {
+ compiler_panic(g->c, g->cur_loc, "CfreeCg: call stack underflow");
+ return;
+ }
+
+ avs = NULL;
+ if (nargs) {
+ avs = arena_array(g->c->tu, CGABIValue, nargs);
+ memset(avs, 0, sizeof(CGABIValue) * nargs);
+ }
+
+ g->avs_in_flight = avs;
+ g->avs_in_flight_n = nargs;
+
+ for (u32 i = 0; i < nargs; ++i) {
+ u32 idx = nargs - 1u - i;
+ ApiSValue arg = api_pop(g);
+ api_ensure_reg(g, &arg);
+ int is_vararg = (idx >= abi->nparams);
+ const Type* aty;
+ if (is_vararg) {
+ aty = arg.type ? arg.type : api_sv_type(&arg);
+ } else {
+ aty = fty->fn.params ? fty->fn.params[idx] : arg.type;
+ }
+ avs[idx].type = aty;
+ avs[idx].abi = is_vararg ? NULL : &abi->params[idx];
+ int is_aggregate = aty && (aty->kind == TY_STRUCT || aty->kind == TY_UNION);
+ if (is_aggregate) {
+ Operand st = arg.op;
+ st.type = aty;
+ avs[idx].storage = st;
+ avs[idx].size = abi_sizeof(g->c->abi, aty);
+ } else {
+ avs[idx].storage =
+ api_is_lvalue(&arg.op) ? api_force_reg(g, &arg, aty) : arg.op;
+ }
+ }
+
+ callee = api_pop(g);
+ api_ensure_reg(g, &callee);
+ Operand callee_op = (callee.op.kind == OPK_GLOBAL)
+ ? callee.op
+ : api_force_reg(g, &callee, fty);
+
+ memset(&desc, 0, sizeof desc);
+ desc.fn_type = fty;
+ desc.abi = abi;
+ desc.callee = callee_op;
+ desc.args = avs;
+ desc.nargs = nargs;
+ desc.flags = CG_CALL_NONE;
+ desc.ret.type = ret_ty;
+ desc.ret.abi = &abi->ret;
+
+ if (has_result) {
+ int ret_is_aggregate =
+ ret_ty->kind == TY_STRUCT || ret_ty->kind == TY_UNION;
+ if (ret_is_aggregate) {
+ FrameSlotDesc fsd;
+ memset(&fsd, 0, sizeof fsd);
+ fsd.type = ret_ty;
+ fsd.size = abi_sizeof(g->c->abi, ret_ty);
+ fsd.align = abi_alignof(g->c->abi, ret_ty);
+ fsd.kind = FS_LOCAL;
+ fsd.flags = FSF_ADDR_TAKEN;
+ FrameSlot ret_slot = T->frame_slot(T, &fsd);
+ desc.ret.storage = api_op_local(ret_slot, ret_ty);
+ } else {
+ Reg r = api_alloc_reg_or_spill(g, api_type_class(ret_ty), ret_ty);
+ desc.ret.storage = api_op_reg(r, ret_ty);
+ }
+ }
+
+ T->call(T, &desc);
+
+ for (u32 i = 0; i < nargs; ++i) {
+ api_release_arg_storage(g, &avs[i].storage);
+ }
+ g->avs_in_flight = NULL;
+ g->avs_in_flight_n = 0;
+
+ if (callee.op.kind != OPK_GLOBAL) {
+ T->free_reg(T, callee_op.v.reg, RC_INT);
+ }
+
+ if (has_result) {
+ api_push(g, api_make_sv(desc.ret.storage, ret_ty));
+ }
+}
+
+void cfree_cg_tail_call(CfreeCg* g, uint32_t nargs, CfreeCgTypeId fn_type) {
+ (void)g; (void)nargs; (void)fn_type;
+}
+
+void cfree_cg_ret(CfreeCg* g) {
+ ApiSValue v;
+ CGTarget* T;
+ const Type* rty;
+ CGABIValue av;
+ Operand ret_op;
+ if (!g) return;
+ T = g->target;
+ rty = g->fn_ret_type;
+ if (!rty || rty->kind == TY_VOID) {
+ T->ret(T, NULL);
+ return;
+ }
+ v = api_pop(g);
+ memset(&av, 0, sizeof av);
+ av.type = rty;
+ av.abi = &g->fn_abi->ret;
+ int is_aggregate = rty->kind == TY_STRUCT || rty->kind == TY_UNION;
+ if (is_aggregate) {
+ av.storage = v.op;
+ av.storage.type = rty;
+ av.size = abi_sizeof(g->c->abi, rty);
+ T->ret(T, &av);
+ return;
+ }
+ ret_op = api_force_reg(g, &v, rty);
+ av.storage = ret_op;
+ T->ret(T, &av);
+ api_release(g, &v);
+}
+
+void cfree_cg_ret_void(CfreeCg* g) {
+ if (!g) return;
+ g->target->ret(g->target, NULL);
+}
+
+/* ============================================================
+ * Data definitions (stubs)
+ * ============================================================ */
+
+void cfree_cg_data_decl(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
+ CfreeCgDeclAttrs attrs) {
+ (void)g; (void)name; (void)type; (void)attrs;
+}
+
+void cfree_cg_data_begin(CfreeCg* g, CfreeSym name, CfreeCgTypeId type,
+ CfreeCgDeclAttrs attrs) {
+ (void)g; (void)name; (void)type; (void)attrs;
+}
+
+void cfree_cg_data_bytes(CfreeCg* g, const uint8_t* data, size_t len) {
+ (void)g; (void)data; (void)len;
+}
+
+void cfree_cg_data_zero(CfreeCg* g, uint64_t size) {
+ (void)g; (void)size;
+}
+
+void cfree_cg_data_symbol(CfreeCg* g, CfreeCgSymbolRefKind kind,
+ CfreeSym target, int64_t addend, uint32_t nbytes) {
+ (void)g; (void)kind; (void)target; (void)addend; (void)nbytes;
+}
+
+void cfree_cg_data_end(CfreeCg* g) { (void)g; }