kit

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

commit 684dd8591fd698225fd8ed3f148cddb1e337d63c
parent 6042aecbf851abe32a7a0428bda3d13c803fb0cb
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Tue, 26 May 2026 12:02:20 -0700

Add IR recorder C backend path

Diffstat:
Asrc/arch/c_target/c_emit.c | 3827+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/c_target/c_emit.h | 256+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/arch/c_target/cbuf.c | 2+-
Dsrc/arch/c_target/emit.c | 3830-------------------------------------------------------------------------------
Dsrc/arch/c_target/internal.h | 193-------------------------------------------------------------------------------
Asrc/arch/c_target/ir_emit.c | 281+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/arch/c_target/ir_emit.h | 9+++++++++
Msrc/arch/c_target/target.c | 197++++++++++++++-----------------------------------------------------------------
Asrc/cg/ir.c | 272+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cg/ir.h | 255+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cg/ir_recorder.c | 645+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asrc/cg/ir_recorder.h | 24++++++++++++++++++++++++
Atest/cg/ir_recorder_test.c | 343+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtest/test.mk | 9+++++++++
14 files changed, 5956 insertions(+), 4187 deletions(-)

diff --git a/src/arch/c_target/c_emit.c b/src/arch/c_target/c_emit.c @@ -0,0 +1,3827 @@ +/* C-source emission core. See doc/CBACKEND.md. + * + * Output strategy + * --------------- + * Each function buffers two CBufs while CG walks the body: + * decls — variable declarations: " long long v3;\n" + * body — TU-wide running output; we accumulate signature/body/closing-brace + * across all functions; func_end splices decls in after the open + * brace using the recorded fn_body_start bookmark. + * + * c_emit_finalize flushes a tiny prologue + body to the writer. + * + * Local declaration is lazy: every operand emit goes through c_ensure_local, + * which appends one declaration for each semantic local. */ + +#include "arch/c_target/c_emit.h" + +#include <stdio.h> +#include <string.h> + +#include "cg/type.h" +#include "core/arena.h" +#include "core/core.h" +#include "core/heap.h" +#include "core/pool.h" +#include "core/slice.h" +#include "obj/obj.h" + +/* Forward decls. */ +static void c_ensure_typedef(CTarget* t, CfreeCgTypeId tid); +static const char* c_typedef_name(CTarget* t, CfreeCgTypeId tid); +static const char* c_typename(CTarget* t, CfreeCgTypeId type); +static CfreeCgTypeId c_local_type_or_panic(CTarget* t, CGLocal local); +static void c_ensure_tuple_typedef(CTarget* t, const CfreeCgTypeId* types, + u32 ntypes); +static void c_emit_tuple_type_name(CTarget* t, CBuf* b, + const CfreeCgTypeId* types, u32 ntypes); +static Operand c_op_local(CGLocal local, CfreeCgTypeId type); +static int c_type_is_aggregate(CTarget* t, CfreeCgTypeId type); +static int c_type_is_bool(CTarget* t, CfreeCgTypeId type); +static int c_type_is_ptr(CTarget* t, CfreeCgTypeId type); +static int c_operand_is_ptr_typed(CTarget* t, Operand op); +static void c_emit_addr_deref(CTarget* t, Operand addr, + CfreeCgTypeId access_type); +static void c_emit_copy_addr(CTarget* t, Operand addr); +CGLocal c_emit_local(CTarget* t, const CGLocalDesc* d); +static void c_ensure_forward_decl_with_results( + CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type, + const CfreeCgTypeId* result_types, u32 nresults); +/* Private accessor on ObjBuilder (defined in obj/obj.c, not in obj.h). + * Same forward-decl trick as obj_tls.c uses. */ +ObjSymId obj_tlv_bootstrap_get(const ObjBuilder*); + +/* === Target state === */ + +void c_emit_target_init(CTarget* t, Compiler* c, ObjBuilder* o, + CfreeWriter* w) { + memset(t, 0, sizeof *t); + t->c = c; + t->obj = o; + t->w = w; + cbuf_init(&t->forwards, c->ctx->heap); + cbuf_init(&t->typedefs, c->ctx->heap); + cbuf_init(&t->data_defs, c->ctx->heap); + cbuf_init(&t->decls, c->ctx->heap); + cbuf_init(&t->body, c->ctx->heap); +} + +CTarget* c_emit_target_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { + CTarget* t = arena_new(c->tu, CTarget); + if (!t) return NULL; + c_emit_target_init(t, c, o, w); + return t; +} + +/* === Writer helpers === */ + +void c_writer_write(CTarget* t, const void* data, size_t n) { + CfreeStatus st = cfree_writer_write(t->w, data, n); + if (st != CFREE_OK) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + compiler_panic(t->c, loc, "C target: writer error %d", (int)st); + } +} + +void c_writer_puts(CTarget* t, const char* s) { + size_t n = 0; + while (s[n]) ++n; + c_writer_write(t, s, n); +} + +/* === CLocal / type emission === */ + +static const char* c_int_type_name_for_width(u32 width, int signed_) { + switch (width) { + case 1: + case 8: + return signed_ ? "int8_t" : "uint8_t"; + case 16: + return signed_ ? "int16_t" : "uint16_t"; + case 32: + return signed_ ? "int32_t" : "uint32_t"; + case 64: + return signed_ ? "int64_t" : "uint64_t"; + case 128: + return signed_ ? "__int128" : "unsigned __int128"; + default: + return NULL; + } +} + +/* Returns the integer width for sign-aware emission. 0 if the type isn't a + * fixed-width integer (float, ptr, void, aggregate). */ +static u32 c_int_width_for_signedness(CTarget* t, CfreeCgTypeId type) { + if (type == CFREE_CG_TYPE_NONE) return 0; + CfreeCgTypeId u = api_unalias_type(t->c, type); + const CgType* ty = cg_type_get(t->c, u); + if (!ty) return 0; + if (ty->kind == CFREE_CG_TYPE_INT) return ty->integer.width; + if (ty->kind == CFREE_CG_TYPE_BOOL) return 32; /* bool maps to int32_t */ + return 0; +} + +/* === Typedef machinery === + * + * Composite types (records, arrays, function types) are emitted as opaque + * byte-storage typedefs in a TU-wide typedefs section. The typedef name is + * `__ty_<id>` keyed on the unaliased type id; this is stable for the + * compiler instance. + * + * For records and arrays the typedef wraps a single `_Alignas(A) uint8_t + * raw[N];` member, so all field/element access is mediated by the existing + * `(*(T*)((char*)addr + ofs))` path. This sidesteps any ABI ambiguity (C + * bitfield rules, array decay, packed/aligned attribute interactions) and + * keeps types orthogonal to access patterns. + * + * For function types we emit a function-pointer typedef `R (*__ty_N)(...)`, + * used for indirect calls and function-pointer-typed values. */ + +static void c_grow_type_state(CTarget* t, u32 needed) { + Heap* h = t->c->ctx->heap; + u32 newcap = t->type_state_cap ? t->type_state_cap : 32; + while (newcap < needed) newcap *= 2; + u8* nd = (u8*)h->realloc(h, t->type_state, t->type_state_cap, newcap, 1); + if (!nd && newcap) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); + } + for (u32 i = t->type_state_cap; i < newcap; ++i) nd[i] = 0; + t->type_state = nd; + t->type_state_cap = newcap; +} + +static const char* c_typedef_name(CTarget* t, CfreeCgTypeId tid) { + CfreeCgTypeId u = api_unalias_type(t->c, tid); + char buf[32]; + int n = snprintf(buf, sizeof buf, "__ty_%u", (unsigned)u); + Sym s = pool_intern_slice(t->c->global, (Slice){.s = buf, .len = (size_t)n}); + return pool_slice(t->c->global, s).s; +} + +/* Forward decl. */ +static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, + const CgType* ty); + +static void c_ensure_typedef(CTarget* t, CfreeCgTypeId tid) { + CfreeCgTypeId u = api_unalias_type(t->c, tid); + if ((u32)u >= t->type_state_cap) c_grow_type_state(t, (u32)u + 1u); + if (t->type_state[u] >= 2) return; + if (t->type_state[u] == 1) return; /* cyclic — emit forward-only */ + t->type_state[u] = 1; + const CgType* ty = cg_type_get(t->c, u); + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!ty) + compiler_panic(t->c, loc, "C target: unknown type id %u", (unsigned)u); + switch (ty->kind) { + case CFREE_CG_TYPE_FUNC: + c_emit_typedef_for_func(t, u, ty); + break; + case CFREE_CG_TYPE_RECORD: { + /* Recurse on field types so any composite-typed field has its + * typedef emitted first. (Records-by-value are accessed only via + * pointer arithmetic in cfree CG, but emitting deps first keeps the + * output readable and stable.) */ + for (u32 i = 0; i < ty->record.nfields; ++i) { + if (!(ty->record.fields[i].flags & CFREE_CG_FIELD_BITFIELD)) { + CfreeCgTypeId ft = ty->record.fields[i].type; + CfreeCgTypeId ftu = api_unalias_type(t->c, ft); + const CgType* fty = cg_type_get(t->c, ftu); + if (fty && (fty->kind == CFREE_CG_TYPE_RECORD || + fty->kind == CFREE_CG_TYPE_ARRAY || + fty->kind == CFREE_CG_TYPE_FUNC)) { + c_ensure_typedef(t, ftu); + } + } + } + cbuf_puts(&t->typedefs, "typedef struct { _Alignas("); + cbuf_put_u64(&t->typedefs, (u64)cg_type_align(t->c, u)); + cbuf_puts(&t->typedefs, ") uint8_t raw["); + cbuf_put_u64(&t->typedefs, cg_type_size(t->c, u)); + cbuf_puts(&t->typedefs, "]; } __ty_"); + cbuf_put_u64(&t->typedefs, (u64)u); + cbuf_puts(&t->typedefs, ";\n"); + break; + } + case CFREE_CG_TYPE_ARRAY: { + CfreeCgTypeId eu = api_unalias_type(t->c, ty->array.elem); + const CgType* ety = cg_type_get(t->c, eu); + if (ety && (ety->kind == CFREE_CG_TYPE_RECORD || + ety->kind == CFREE_CG_TYPE_ARRAY || + ety->kind == CFREE_CG_TYPE_FUNC)) { + c_ensure_typedef(t, eu); + } + cbuf_puts(&t->typedefs, "typedef struct { _Alignas("); + cbuf_put_u64(&t->typedefs, (u64)cg_type_align(t->c, u)); + cbuf_puts(&t->typedefs, ") uint8_t raw["); + cbuf_put_u64(&t->typedefs, cg_type_size(t->c, u)); + cbuf_puts(&t->typedefs, "]; } __ty_"); + cbuf_put_u64(&t->typedefs, (u64)u); + cbuf_puts(&t->typedefs, ";\n"); + break; + } + default: + compiler_panic(t->c, loc, + "C target: c_ensure_typedef on non-composite kind %d", + (int)ty->kind); + } + t->type_state[u] = 2; +} + +static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, + const CgType* ty) { + /* Emit recursively for return and param types if they're composites. */ + CfreeCgTypeId ret = api_unalias_type(t->c, ty->func.ret); + const CgType* rty = cg_type_get(t->c, ret); + if (rty && + (rty->kind == CFREE_CG_TYPE_RECORD || rty->kind == CFREE_CG_TYPE_ARRAY || + rty->kind == CFREE_CG_TYPE_FUNC)) { + c_ensure_typedef(t, ret); + } + for (u32 i = 0; i < ty->func.nparams; ++i) { + CfreeCgTypeId pt = api_unalias_type(t->c, ty->func.params[i].type); + const CgType* pty = cg_type_get(t->c, pt); + if (pty && + (pty->kind == CFREE_CG_TYPE_RECORD || + pty->kind == CFREE_CG_TYPE_ARRAY || pty->kind == CFREE_CG_TYPE_FUNC)) { + c_ensure_typedef(t, pt); + } + } + cbuf_puts(&t->typedefs, "typedef "); + cbuf_puts(&t->typedefs, c_typename(t, ty->func.ret)); + cbuf_puts(&t->typedefs, " (*__ty_"); + cbuf_put_u64(&t->typedefs, (u64)tid); + cbuf_puts(&t->typedefs, ")("); + if (ty->func.nparams == 0 && !ty->func.abi_variadic) { + cbuf_puts(&t->typedefs, "void"); + } else { + for (u32 i = 0; i < ty->func.nparams; ++i) { + if (i > 0) cbuf_puts(&t->typedefs, ", "); + cbuf_puts(&t->typedefs, c_typename(t, ty->func.params[i].type)); + } + if (ty->func.abi_variadic) { + if (ty->func.nparams > 0) cbuf_puts(&t->typedefs, ", "); + cbuf_puts(&t->typedefs, "..."); + } + } + cbuf_puts(&t->typedefs, ");\n"); +} + +static const char* c_int_type_for_width_panic(CTarget* t, u32 width, + int signed_) { + const char* s = c_int_type_name_for_width(width, signed_); + if (!s) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + compiler_panic(t->c, loc, "C target: int width %u not yet supported", + (unsigned)width); + } + return s; +} + +static const char* c_float_type_name(u32 width) { + switch (width) { + case 32: + return "float"; + case 64: + return "double"; + case 80: + case 128: + return "long double"; + default: + return NULL; + } +} + +/* Returns the C type name for a CG type id. Scalars map to fixed-width + * <stdint.h> types or float/double/long double; pointers collapse to void*; + * composites (records/arrays/funcs) emit an opaque-storage typedef on first + * sighting and return the typedef name. */ +static const char* c_typename(CTarget* t, CfreeCgTypeId type) { + CfreeCgTypeId resolved = api_unalias_type(t->c, type); + const CgType* ty = cg_type_get(t->c, resolved); + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!ty) { + compiler_panic(t->c, loc, "C target: unknown type id %u", (unsigned)type); + } + switch (ty->kind) { + case CFREE_CG_TYPE_VOID: + return "void"; + case CFREE_CG_TYPE_BOOL: + return "int32_t"; + case CFREE_CG_TYPE_INT: + return c_int_type_for_width_panic(t, ty->integer.width, 1); + case CFREE_CG_TYPE_FLOAT: { + const char* s = c_float_type_name(ty->fp.width); + if (!s) { + compiler_panic(t->c, loc, "C target: fp width %u not yet supported", + (unsigned)ty->fp.width); + } + return s; + } + case CFREE_CG_TYPE_PTR: + return "void*"; + case CFREE_CG_TYPE_ENUM: + /* CG enums are width-only; treat as their underlying integer base. */ + return c_typename(t, ty->enum_.base); + case CFREE_CG_TYPE_VARARG_STATE: + t->need_stdarg = 1; + return "va_list"; + case CFREE_CG_TYPE_RECORD: + case CFREE_CG_TYPE_ARRAY: + case CFREE_CG_TYPE_FUNC: + c_ensure_typedef(t, resolved); + return c_typedef_name(t, resolved); + default: + compiler_panic(t->c, loc, "C target: type kind %d not yet supported", + (int)ty->kind); + } +} + +void c_emit_type(CTarget* t, CBuf* b, CfreeCgTypeId type) { + cbuf_puts(b, c_typename(t, type)); +} + +static CfreeCgTypeId c_local_type_or_panic(CTarget* t, CGLocal local) { + if ((u32)local < t->local_cap && t->local_declared[local] && + t->local_type[local]) { + return t->local_type[local]; + } + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: unknown local type for v%u", (unsigned)local); + return CFREE_CG_TYPE_NONE; +} + +static void c_emit_tuple_type_name(CTarget* t, CBuf* b, + const CfreeCgTypeId* types, u32 ntypes) { + (void)t; + cbuf_puts(b, "__cfree_tuple"); + cbuf_put_u64(b, (u64)ntypes); + for (u32 i = 0; i < ntypes; ++i) { + cbuf_putc(b, '_'); + cbuf_put_u64(b, (u64)api_unalias_type(t->c, types[i])); + } +} + +static void c_ensure_tuple_typedef(CTarget* t, const CfreeCgTypeId* types, + u32 ntypes) { + if (ntypes <= 1) return; + cbuf_puts(&t->typedefs, "#ifndef __cfree_tuple_guard_"); + c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); + cbuf_puts(&t->typedefs, "\n#define __cfree_tuple_guard_"); + c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); + cbuf_puts(&t->typedefs, "\ntypedef struct "); + c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); + cbuf_puts(&t->typedefs, " {"); + for (u32 i = 0; i < ntypes; ++i) { + cbuf_putc(&t->typedefs, ' '); + c_emit_type(t, &t->typedefs, types[i]); + cbuf_puts(&t->typedefs, " f"); + cbuf_put_u64(&t->typedefs, (u64)i); + cbuf_putc(&t->typedefs, ';'); + } + cbuf_puts(&t->typedefs, " } "); + c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); + cbuf_puts(&t->typedefs, ";\n#endif\n"); +} + +static Operand c_op_local(CGLocal local, CfreeCgTypeId type) { + Operand op; + memset(&op, 0, sizeof op); + op.kind = OPK_LOCAL; + op.type = type; + op.v.local = local; + return op; +} + +void c_local_name(CLocal r, char* out, size_t cap) { + size_t i = 0; + if (cap == 0) return; + if (cap > 1) out[i++] = 'v'; + char tmp[16]; + size_t n = 0; + u32 v = (u32)r; + if (v == 0) { + tmp[n++] = '0'; + } else { + while (v) { + tmp[n++] = (char)('0' + (v % 10)); + v /= 10; + } + } + while (n && i + 1 < cap) out[i++] = tmp[--n]; + out[i] = '\0'; +} + +static void c_grow_local_table(CTarget* t, u32 needed) { + Heap* h = t->c->ctx->heap; + u32 newcap = t->local_cap ? t->local_cap : 16; + while (newcap < needed) newcap *= 2; + u8* nd = (u8*)h->realloc(h, t->local_declared, t->local_cap, newcap, 1); + CfreeCgTypeId* nt = (CfreeCgTypeId*)h->realloc( + h, t->local_type, t->local_cap * sizeof(CfreeCgTypeId), + newcap * sizeof(CfreeCgTypeId), _Alignof(CfreeCgTypeId)); + if ((!nd && newcap) || (!nt && newcap)) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); + } + for (u32 i = t->local_cap; i < newcap; ++i) { + nd[i] = 0; + nt[i] = 0; + } + t->local_declared = nd; + t->local_type = nt; + t->local_cap = newcap; +} + +/* Emit the trailing `__attribute__((unused)) = INIT;` for a local decl of + * type `ty`. Scalars get `= 0` (readable); aggregates get `= {0}` (which is + * the only form that compiles for record/array). */ +static void c_emit_zero_init(CTarget* t, CfreeCgTypeId ty) { + const CgType* cgt = ty ? cg_type_get(t->c, api_unalias_type(t->c, ty)) : NULL; + int is_aggregate = cgt && (cgt->kind == CFREE_CG_TYPE_RECORD || + cgt->kind == CFREE_CG_TYPE_ARRAY); + cbuf_puts(&t->decls, is_aggregate ? " __attribute__((unused)) = {0};\n" + : " __attribute__((unused)) = 0;\n"); +} + +void c_ensure_local(CTarget* t, CLocal r, CfreeCgTypeId type) { + if (r == (CLocal)CG_LOCAL_NONE) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: CG_LOCAL_NONE reached emission"); + } + if ((u32)r >= t->local_cap) c_grow_local_table(t, (u32)r + 1u); + if (t->local_declared[r]) { + if (type && api_unalias_type(t->c, t->local_type[r]) != + api_unalias_type(t->c, type)) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: local v%u used with inconsistent type " + "(declared %u, used %u)", + (unsigned)r, + (unsigned)api_unalias_type(t->c, t->local_type[r]), + (unsigned)api_unalias_type(t->c, type)); + } + return; + } + t->local_declared[r] = 1; + t->local_type[r] = type; + cbuf_puts(&t->decls, " "); + c_emit_type(t, &t->decls, type); + cbuf_puts(&t->decls, " "); + char buf[24]; + c_local_name(r, buf, sizeof buf); + cbuf_puts(&t->decls, buf); + /* Zero-init kills -Wsometimes-uninitialized for control flow clang can't + * reason through; the host C compiler DSEs the init when a real + * assignment dominates. Scalars get `= 0`, aggregates `= {0}`. */ + c_emit_zero_init(t, type); +} + +/* Emit a signed-int64 literal. INT64_MIN can't be written directly: clang + * treats `-9223372036854775808` as `-(9223372036854775808)` with the inner + * literal too large for any signed type, which trips + * -Wimplicitly-unsigned-literal. The standard workaround is + * `(-9223372036854775807LL - 1)`. */ +static void c_emit_imm_literal(CTarget* t, i64 v) { + if (v == (i64)((u64)1u << 63u)) { + cbuf_puts(&t->body, "(-9223372036854775807LL - 1)"); + return; + } + cbuf_put_i64(&t->body, v); +} + +/* Address-mode tuple decoded from an OPK_INDIRECT operand. Mirrors the + * `addr_mode` helper in the machine-code backends so all targets share a + * single in-backend view of `base [+ index << log2_scale] + ofs`. */ +typedef struct CAddrMode { + CLocal base; + CLocal index; /* CG_LOCAL_NONE when no index operand */ + u8 log2_scale; /* meaningful only when index != CG_LOCAL_NONE */ + i32 ofs; +} CAddrMode; + +static CAddrMode c_addr_mode(Operand addr) { + CAddrMode m; + m.base = addr.v.ind.base; + m.index = addr.v.ind.index; + m.log2_scale = addr.v.ind.log2_scale; + m.ofs = addr.v.ind.ofs; + return m; +} + +/* Emit `(char*)base [+ (uintptr_t)index * (1u << log2_scale)] [+ ofs]` into + * the body, with each optional term suppressed when absent. Used by every + * OPK_INDIRECT renderer; the caller wraps it with the appropriate + * `(*(T*)(...))` or `((T)(...))` cast. */ +static void c_emit_indirect_addr_expr(CTarget* t, CAddrMode m) { + char rbuf[24]; + cbuf_puts(&t->body, "(char*)"); + c_local_name(m.base, rbuf, sizeof rbuf); + cbuf_puts(&t->body, rbuf); + if (m.index != CG_LOCAL_NONE) { + cbuf_puts(&t->body, " + (uintptr_t)"); + c_local_name(m.index, rbuf, sizeof rbuf); + cbuf_puts(&t->body, rbuf); + cbuf_puts(&t->body, " * "); + /* Spell as the explicit 1/2/4/8 literal corresponding to log2_scale. + * log2_scale is normalized to {0,1,2,3} by cg. */ + cbuf_put_u64(&t->body, (u64)(1u << m.log2_scale)); + } + if (m.ofs != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, (i64)m.ofs); + } +} + +/* Assert that `addr`, if OPK_INDIRECT, has no index operand. Used by paths + * the cg layer guarantees never carry the indexed shape (bitfield, atomics, + * copy_bytes/set_bytes, inline asm). */ +static void c_assert_no_index(CTarget* t, Operand addr, const char* where) { + if (addr.kind != OPK_INDIRECT) return; + if (addr.v.ind.index == CG_LOCAL_NONE) return; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + compiler_panic(t->c, loc, + "C target: %.*s: indexed OPK_INDIRECT not allowed here", + SLICE_ARG(slice_from_cstr(where))); +} + +void c_emit_operand(CTarget* t, Operand op) { + char buf[24]; + switch (op.kind) { + case OPK_IMM: + if (op.type == CFREE_CG_TYPE_NONE) { + /* Untyped IMM (e.g. memset byte value): emit the literal raw. */ + cbuf_putc(&t->body, '('); + c_emit_imm_literal(t, op.v.imm); + cbuf_putc(&t->body, ')'); + } else { + cbuf_puts(&t->body, "(("); + c_emit_type(t, &t->body, op.type); + cbuf_puts(&t->body, ")"); + c_emit_imm_literal(t, op.v.imm); + cbuf_puts(&t->body, ")"); + } + return; + case OPK_LOCAL: { + c_ensure_local(t, op.v.local, op.type); + c_local_name(op.v.local, buf, sizeof buf); + cbuf_puts(&t->body, buf); + return; + } + case OPK_INDIRECT: { + /* Used by call paths to pass aggregates by-address: the operand's type + * is the aggregate, the storage is `base + index*scale + ofs`. Emit the + * deref as a value expression. */ + cbuf_puts(&t->body, "(*("); + c_emit_type(t, &t->body, op.type); + cbuf_puts(&t->body, "*)("); + c_emit_indirect_addr_expr(t, c_addr_mode(op)); + cbuf_puts(&t->body, "))"); + return; + } + case OPK_GLOBAL: { + /* OPK_GLOBAL carries `&sym + addend`. How we spell it depends on + * op.type: + * - pointer/scalar/void: the value IS the address, so cast through + * `((T)((char*)sym + addend))`. + * - aggregate (RECORD/ARRAY): the symbol's storage is an aggregate + * value; emit `(*(T*)((char*)sym + addend))` so the deref reads + * the aggregate value (used by call args that pass struct + * by-value via a global initialized buffer). */ + obj_sym_mark_referenced(t->obj, op.v.global.sym); + const char* nm = c_sym_name(t, op.v.global.sym); + const CgType* gty = + (op.type != CFREE_CG_TYPE_NONE) + ? cg_type_get(t->c, api_unalias_type(t->c, op.type)) + : NULL; + int is_aggregate = gty && (gty->kind == CFREE_CG_TYPE_RECORD || + gty->kind == CFREE_CG_TYPE_ARRAY); + if (is_aggregate) { + cbuf_puts(&t->body, "(*("); + c_emit_type(t, &t->body, op.type); + cbuf_puts(&t->body, "*)((char*)&"); + cbuf_puts(&t->body, nm); + if (op.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, op.v.global.addend); + } + cbuf_puts(&t->body, "))"); + } else { + cbuf_puts(&t->body, "(("); + if (op.type != CFREE_CG_TYPE_NONE) { + c_emit_type(t, &t->body, op.type); + } else { + cbuf_puts(&t->body, "void*"); + } + cbuf_puts(&t->body, ")((char*)&"); + cbuf_puts(&t->body, nm); + if (op.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, op.v.global.addend); + } + cbuf_puts(&t->body, "))"); + } + return; + } + default: { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + compiler_panic(t->c, loc, "C target: operand kind %d not yet supported", + (int)op.kind); + } + } +} + +static int c_type_is_float(CTarget* t, CfreeCgTypeId type) { + if (type == CFREE_CG_TYPE_NONE) return 0; + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); + return ty && ty->kind == CFREE_CG_TYPE_FLOAT; +} + +/* True iff a and b name the same CG type after alias resolution. */ +static int c_types_equiv(CTarget* t, CfreeCgTypeId a, CfreeCgTypeId b) { + if (a == 0 || b == 0) return 0; + return api_unalias_type(t->c, a) == api_unalias_type(t->c, b); +} + +/* Emit " vN = " plus any cast needed for a C assignment expression. + * Caller must then emit the RHS expression and call c_emit_local_assign_close. + * + * `rhs_ty` is the CG type the RHS expression will produce (or 0 if unknown). + * Pointer/int crossings bridge through uintptr_t to keep host-C diagnostics + * quiet. The outer `(...)` parens are kept so the closer's `);` stays + * balanced. */ +static void c_emit_local_assign_open(CTarget* t, CLocal r, + CfreeCgTypeId rhs_ty) { + if ((u32)r >= t->local_cap || !t->local_declared[r]) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: assign to undeclared local v%u", (unsigned)r); + } + CfreeCgTypeId decl = t->local_type[r]; + char buf[24]; + c_local_name(r, buf, sizeof buf); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, " = "); + if (!c_types_equiv(t, rhs_ty, decl)) { + cbuf_putc(&t->body, '('); + c_emit_type(t, &t->body, decl); + cbuf_putc(&t->body, ')'); + if (!c_type_is_float(t, decl) && + (!rhs_ty || c_type_is_ptr(t, decl) || c_type_is_ptr(t, rhs_ty))) { + cbuf_puts(&t->body, "(uintptr_t)"); + } + } + cbuf_puts(&t->body, "("); +} + +static void c_emit_local_assign_close(CTarget* t) { + cbuf_puts(&t->body, ");\n"); +} + +void c_emit_operand_signed(CTarget* t, Operand op, int signed_) { + u32 w = c_int_width_for_signedness(t, op.type); + if (w == 0) { + /* Not an integer — emit without sign cast. */ + c_emit_operand(t, op); + return; + } + const char* tn = c_int_type_name_for_width(w, signed_); + if (!tn) { + c_emit_operand(t, op); + return; + } + int via_uptr = c_operand_is_ptr_typed(t, op); + /* CG ints are width-only; the C target declares every int local/IMM + * as the signed `int{W}_t` of its width. So when `signed_` is true and + * the operand's emit-width matches `w`, the explicit cast is redundant + * with what c_emit_operand already produces. Skipping it cuts the + * ubiquitous `((int32_t)((int32_t)23))` double-cast down to one. */ + if (!via_uptr && signed_) { + CfreeCgTypeId et = op.type; + if (c_int_width_for_signedness(t, et) == w) { + c_emit_operand(t, op); + return; + } + } + cbuf_puts(&t->body, "(("); + cbuf_puts(&t->body, tn); + cbuf_puts(&t->body, ")"); + if (via_uptr) { + cbuf_puts(&t->body, "(uintptr_t)"); + } + c_emit_operand(t, op); + cbuf_puts(&t->body, ")"); +} + +/* Returns 1 if `type` is a pointer (or void*). */ +static int c_type_is_ptr(CTarget* t, CfreeCgTypeId type) { + if (type == CFREE_CG_TYPE_NONE) return 0; + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); + return ty && ty->kind == CFREE_CG_TYPE_PTR; +} + +static int c_type_is_bool(CTarget* t, CfreeCgTypeId type) { + if (type == CFREE_CG_TYPE_NONE) return 0; + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); + return ty && ty->kind == CFREE_CG_TYPE_BOOL; +} + +static int c_type_is_aggregate(CTarget* t, CfreeCgTypeId type) { + if (type == CFREE_CG_TYPE_NONE) return 0; + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); + return ty && + (ty->kind == CFREE_CG_TYPE_RECORD || ty->kind == CFREE_CG_TYPE_ARRAY); +} + +static int c_operand_is_ptr_typed(CTarget* t, Operand op) { + if (c_type_is_ptr(t, op.type)) return 1; + return 0; +} + +/* Emit `(target_ty)(uintptr_t)(op)` (or `(target_ty)(op)` for float + * target_ty). Used when the caller needs a specific C expression type. + * Pointer/int crossings bridge through uintptr_t. */ +static void c_emit_operand_as(CTarget* t, Operand op, CfreeCgTypeId target_ty) { + if (c_types_equiv(t, op.type, target_ty)) { + c_emit_operand(t, op); + return; + } + cbuf_puts(&t->body, "("); + c_emit_type(t, &t->body, target_ty); + cbuf_puts(&t->body, ")"); + if (!c_type_is_float(t, target_ty) && + (!op.type || c_type_is_ptr(t, op.type) || c_type_is_ptr(t, target_ty))) { + cbuf_puts(&t->body, "(uintptr_t)"); + } + cbuf_puts(&t->body, "("); + c_emit_operand(t, op); + cbuf_puts(&t->body, ")"); +} + +/* Emit an operand for use in a C binary arithmetic expression. Pointer-typed + * operands are cast to uintptr_t so C arithmetic semantics apply uniformly + * (cfree IR carries byte offsets, not C-pointer-arith scaled indices). */ +static void c_emit_operand_arith(CTarget* t, Operand op) { + if (c_operand_is_ptr_typed(t, op)) { + cbuf_puts(&t->body, "((uintptr_t)"); + if (op.kind == OPK_IMM) { + c_emit_imm_literal(t, op.v.imm); + } else { + c_emit_operand(t, op); + } + cbuf_puts(&t->body, ")"); + return; + } + c_emit_operand(t, op); +} + +/* Same, but applies the requested signedness when the operand is an integer + * (used for SDIV/UDIV/SREM/UREM/SHR_S/SHR_U). Pointer operands always go + * through the uintptr_t cast regardless of the requested signedness. */ +static void c_emit_operand_arith_signed(CTarget* t, Operand op, int signed_) { + if (c_operand_is_ptr_typed(t, op)) { + cbuf_puts(&t->body, "((uintptr_t)"); + if (op.kind == OPK_IMM) { + c_emit_imm_literal(t, op.v.imm); + } else { + c_emit_operand(t, op); + } + cbuf_puts(&t->body, ")"); + return; + } + c_emit_operand_signed(t, op, signed_); +} + +/* Emit a C lvalue expression for an addr operand (OPK_LOCAL / OPK_GLOBAL / + * OPK_INDIRECT) using `access_type` as the access type. The result is the + * full `*(T*)(...)` dereference, or the C variable directly when the access + * type matches the underlying local/global object. */ +static void c_emit_addr_deref(CTarget* t, Operand addr, + CfreeCgTypeId access_type) { + char buf[24]; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + switch (addr.kind) { + case OPK_LOCAL: { + c_ensure_local(t, addr.v.local, addr.type); + c_local_name(addr.v.local, buf, sizeof buf); + if (access_type == 0 || addr.type == 0 || + api_unalias_type(t->c, access_type) == + api_unalias_type(t->c, addr.type)) { + cbuf_puts(&t->body, buf); + } else { + cbuf_puts(&t->body, "(*("); + c_emit_type(t, &t->body, access_type); + cbuf_puts(&t->body, "*)&"); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ")"); + } + return; + } + case OPK_GLOBAL: { + obj_sym_mark_referenced(t->obj, addr.v.global.sym); + const char* nm = c_sym_name(t, addr.v.global.sym); + cbuf_puts(&t->body, "(*("); + c_emit_type(t, &t->body, access_type); + cbuf_puts(&t->body, "*)((char*)&"); + cbuf_puts(&t->body, nm); + if (addr.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, addr.v.global.addend); + } + cbuf_puts(&t->body, "))"); + return; + } + case OPK_INDIRECT: { + CAddrMode m = c_addr_mode(addr); + if ((u32)m.base >= t->local_cap || !t->local_declared[m.base]) { + compiler_panic(t->c, loc, + "C target: indirect on undeclared base local v%u", + (unsigned)m.base); + } + if (m.index != CG_LOCAL_NONE && + ((u32)m.index >= t->local_cap || !t->local_declared[m.index])) { + compiler_panic(t->c, loc, + "C target: indirect on undeclared index local v%u", + (unsigned)m.index); + } + cbuf_puts(&t->body, "(*("); + c_emit_type(t, &t->body, access_type); + cbuf_puts(&t->body, "*)("); + c_emit_indirect_addr_expr(t, m); + cbuf_puts(&t->body, "))"); + return; + } + default: + compiler_panic(t->c, loc, + "C target: addr-deref on operand kind %d not supported", + (int)addr.kind); + } +} + +/* Emit a C address-of expression for a lvalue operand. Output is a pointer + * value (cast to dst_type). */ +static void c_emit_lvalue_addr(CTarget* t, Operand lv, CfreeCgTypeId dst_type) { + char buf[24]; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + switch (lv.kind) { + case OPK_LOCAL: + cbuf_puts(&t->body, "(("); + c_emit_type(t, &t->body, dst_type); + cbuf_puts(&t->body, ")"); + cbuf_puts(&t->body, "&"); + c_ensure_local(t, lv.v.local, lv.type); + c_local_name(lv.v.local, buf, sizeof buf); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ")"); + return; + case OPK_GLOBAL: { + obj_sym_mark_referenced(t->obj, lv.v.global.sym); + const char* nm = c_sym_name(t, lv.v.global.sym); + cbuf_puts(&t->body, "(("); + c_emit_type(t, &t->body, dst_type); + cbuf_puts(&t->body, ")((char*)&"); + cbuf_puts(&t->body, nm); + if (lv.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, lv.v.global.addend); + } + cbuf_puts(&t->body, ")"); + cbuf_puts(&t->body, ")"); + return; + } + case OPK_INDIRECT: { + cbuf_puts(&t->body, "(("); + c_emit_type(t, &t->body, dst_type); + cbuf_puts(&t->body, ")("); + c_emit_indirect_addr_expr(t, c_addr_mode(lv)); + cbuf_puts(&t->body, "))"); + return; + } + default: + compiler_panic(t->c, loc, + "C target: addr-of on operand kind %d not supported", + (int)lv.kind); + } +} + +/* === Symbol name lookup === */ + +const char* c_sym_name(CTarget* t, ObjSymId sym) { + const ObjSym* os = obj_symbol_get(t->obj, sym); + if (!os) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: unknown ObjSymId %u", + (unsigned)sym); + } + Slice nm = pool_slice(t->c->global, os->name); + const char* s = nm.s; + size_t n = nm.len; + /* Mach-O linker symbols are mangled with a leading underscore; the host + * C compiler will re-add it on its own, so strip when re-emitting source. */ + if (t->c->target.obj == CFREE_OBJ_MACHO && s && n > 0 && s[0] == '_') { + s += 1; + n -= 1; + } + /* Sanitize for C identifier rules: assemblers accept '.', '$', etc. in + * symbol names; C does not. Replace each illegal byte with '_' and prepend + * '_' if the first char isn't alpha/underscore. Local syms (SB_LOCAL) get + * renamed freely since they have no cross-TU contract. Globals are assumed + * to come in with C-safe names; if they don't, we still rewrite — the + * resulting symbol won't link against other TUs that use the asm spelling, + * but cfree-produced code uses the rewritten spelling consistently. */ + int needs_rewrite = 0; + if (n == 0) { + return s; + } + if (!((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z') || + s[0] == '_')) { + needs_rewrite = 1; + } else { + for (size_t i = 0; i < n; ++i) { + char ch = s[i]; + if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch == '_')) { + needs_rewrite = 1; + break; + } + } + } + if (!needs_rewrite) return s; + char buf[256]; + size_t cap = sizeof(buf) - 1u; + size_t out = 0; + int first_alpha = (s[0] >= 'a' && s[0] <= 'z') || + (s[0] >= 'A' && s[0] <= 'Z') || s[0] == '_'; + if (!first_alpha && out < cap) buf[out++] = '_'; + for (size_t i = 0; i < n && out < cap; ++i) { + char ch = s[i]; + int ok = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + (ch >= '0' && ch <= '9') || ch == '_'; + buf[out++] = ok ? ch : '_'; + } + buf[out] = '\0'; + Sym interned = pool_intern_slice(t->c->global, (Slice){.s = buf, .len = out}); + return pool_slice(t->c->global, interned).s; +} + +/* === Prologue / finalize === */ + +void c_emit_prologue(CTarget* t) { + if (t->prologue_emitted) return; + t->prologue_emitted = 1; + c_writer_puts(t, + "/* generated by cfree --emit=c */\n" + "#include <stdint.h>\n" + "#include <stdalign.h>\n"); + /* Other headers are decided at finalize so include lines remain + * deterministic regardless of when the type was first referenced. + * Writer flushes are not stream-buffered, so we keep prologue compact and + * tack the rest on at c_emit_finalize. */ + c_writer_puts(t, "\n"); +} + +/* === func_begin / func_end === */ + +/* Write `RetT name(P0, P1, ...)` (without trailing `;` or `{`) to `b`. */ +static void c_emit_func_signature(CTarget* t, CBuf* b, const char* name, + CfreeCgTypeId fn_type, + const CfreeCgTypeId* result_types, + u32 nresults) { + CfreeCgTypeId ret_type = cg_type_func_ret_id(t->c, fn_type); + const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, fn_type)); + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { + compiler_panic(t->c, loc, "C target: fn_type is not a function type"); + } + if (!result_types) { + nresults = cg_type_is_void(t->c, ret_type) ? 0u : 1u; + result_types = &ret_type; + } + if (nresults == 0) { + cbuf_puts(b, "void"); + } else if (nresults == 1) { + c_emit_type(t, b, result_types[0]); + } else { + c_ensure_tuple_typedef(t, result_types, nresults); + c_emit_tuple_type_name(t, b, result_types, nresults); + } + cbuf_puts(b, " "); + cbuf_puts(b, name); + cbuf_puts(b, "("); + if (fty->func.nparams == 0 && !fty->func.abi_variadic) { + cbuf_puts(b, "void"); + } else { + for (u32 i = 0; i < fty->func.nparams; ++i) { + if (i > 0) cbuf_puts(b, ", "); + c_emit_type(t, b, fty->func.params[i].type); + cbuf_puts(b, " p"); + cbuf_put_u64(b, (u64)i); + } + if (fty->func.abi_variadic) { + if (fty->func.nparams > 0) cbuf_puts(b, ", "); + cbuf_puts(b, "..."); + } + } + cbuf_puts(b, ")"); +} + +void c_emit_func_begin(CTarget* t, const CGFuncDesc* fd) { + c_emit_prologue(t); + + t->cur_fn = fd; + cbuf_reset(&t->decls); + for (u32 i = 0; i < t->local_cap; ++i) { + t->local_declared[i] = 0; + t->local_type[i] = 0; + } + t->next_label = 0; + t->next_local = 0; + t->next_tmp = 0; + t->nscopes = 0; + t->last_was_terminator = 0; + t->have_emitted_loc = 0; + t->emitted_loc = (SrcLoc){0, 0, 0}; + + const char* name = c_sym_name(t, fd->sym); + + /* Forward-declare so out-of-order callers and same-TU references find the + * prototype regardless of definition order. */ + c_ensure_forward_decl_with_results(t, fd->sym, fd->fn_type, fd->result_types, + fd->nresults); + + c_emit_func_signature(t, &t->body, name, fd->fn_type, fd->result_types, + fd->nresults); + cbuf_puts(&t->body, " {\n"); + t->fn_body_start = t->body.len; +} + +static void c_ensure_forward_decl_with_results( + CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type, + const CfreeCgTypeId* result_types, u32 nresults) { + Heap* h = t->c->ctx->heap; + if ((u32)sym >= t->sym_forwarded_cap) { + u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; + while (newcap <= (u32)sym) newcap *= 2; + u8* nd = + (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); + if (!nd && newcap) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); + } + for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; + t->sym_forwarded = nd; + t->sym_forwarded_cap = newcap; + } + if (t->sym_forwarded[sym]) return; + t->sym_forwarded[sym] = 1; + const char* name = c_sym_name(t, sym); + const ObjSym* os = obj_symbol_get(t->obj, sym); + if ((os && (os->kind == SK_FUNC || os->kind == SK_IFUNC)) || fn_type != 0) { + c_emit_func_signature(t, &t->forwards, name, fn_type, result_types, + nresults); + cbuf_puts(&t->forwards, ";\n"); + } else { + if (os && os->bind == SB_LOCAL) + cbuf_puts(&t->forwards, "static "); + else + cbuf_puts(&t->forwards, "extern "); + if (os && os->section_id != OBJ_SEC_NONE) { + const Section* sec = obj_section_get(t->obj, os->section_id); + if (sec->kind == SEC_RODATA) cbuf_puts(&t->forwards, "const "); + } + cbuf_puts(&t->forwards, "struct __cfree_data_"); + cbuf_puts(&t->forwards, name); + cbuf_puts(&t->forwards, " "); + cbuf_puts(&t->forwards, name); + cbuf_puts(&t->forwards, ";\n"); + } +} + +void c_ensure_forward_decl(CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type) { + c_ensure_forward_decl_with_results(t, sym, fn_type, NULL, 0); +} + +void c_emit_func_end(CTarget* t) { + size_t splice_at = t->fn_body_start; + size_t body_after = t->body.len; + size_t fn_body_len = body_after - splice_at; + Heap* h = t->c->ctx->heap; + + u8* tmp = NULL; + if (fn_body_len) { + tmp = (u8*)h->alloc(h, fn_body_len, 1); + if (!tmp) { + compiler_panic(t->c, t->cur_fn->loc, "C target: out of memory"); + } + for (size_t i = 0; i < fn_body_len; ++i) { + tmp[i] = t->body.data[splice_at + i]; + } + } + + t->body.len = splice_at; + if (t->decls.len) + cbuf_putn(&t->body, (const char*)t->decls.data, t->decls.len); + if (tmp) { + cbuf_putn(&t->body, (const char*)tmp, fn_body_len); + h->free(h, tmp, fn_body_len); + } + cbuf_puts(&t->body, "}\n\n"); + + t->cur_fn = NULL; +} + +/* === locals, params === */ + +void c_emit_param_bind(CTarget* t, CGLocal local, CfreeCgTypeId type, + u32 index) { + char buf[24]; + c_ensure_local(t, local, type); + c_local_name(local, buf, sizeof buf); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, " = p"); + cbuf_put_u64(&t->body, (u64)index); + cbuf_puts(&t->body, ";\n"); +} + +CGLocal c_emit_param(CTarget* t, const CGParamDesc* pd) { + CGLocalDesc d; + memset(&d, 0, sizeof d); + d.type = pd->type; + d.name = pd->name; + d.loc = pd->loc; + d.size = pd->size; + d.align = pd->align; + d.flags = pd->flags; + CGLocal local = c_emit_local(t, &d); + c_emit_param_bind(t, local, pd->type, pd->index); + return local; +} + +/* === load_imm, copy, binop === */ + +void c_emit_load_imm(CTarget* t, Operand dst, i64 imm) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: load_imm dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + /* The literal is emitted bare; its C type is `long long`. We can drop + * the bridge cast iff the bare assignment compiles cleanly: + * - integer dst: imm must fit in dst's signed range (else + * -Wconstant-conversion). 64-bit dst always fits. + * - pointer dst: only `0` (null pointer constant) is safe; any other + * literal trips -Wint-conversion. + * Otherwise keep the bridge. */ + u32 w = c_int_width_for_signedness(t, dst.type); + int can_drop_bridge; + if (w > 0) { + can_drop_bridge = (w >= 64) || (imm >= -((i64)1 << (w - 1)) && + imm <= (((i64)1 << (w - 1)) - 1)); + } else if (c_type_is_ptr(t, dst.type)) { + can_drop_bridge = (imm == 0); + } else { + can_drop_bridge = 0; + } + c_emit_local_assign_open(t, dst.v.local, + can_drop_bridge ? dst.type : (CfreeCgTypeId)0); + c_emit_imm_literal(t, imm); + c_emit_local_assign_close(t); +} + +void c_emit_copy(CTarget* t, Operand dst, Operand src) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: copy dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + c_emit_local_assign_open(t, dst.v.local, src.type); + c_emit_operand(t, src); + c_emit_local_assign_close(t); +} + +static const char* binop_to_c(BinOp op) { + switch (op) { + case BO_IADD: + case BO_FADD: + return "+"; + case BO_ISUB: + case BO_FSUB: + return "-"; + case BO_IMUL: + case BO_FMUL: + return "*"; + case BO_SDIV: + case BO_UDIV: + case BO_FDIV: + return "/"; + case BO_SREM: + case BO_UREM: + return "%"; + case BO_AND: + return "&"; + case BO_OR: + return "|"; + case BO_XOR: + return "^"; + case BO_SHL: + return "<<"; + case BO_SHR_S: + case BO_SHR_U: + return ">>"; + } + return NULL; +} + +/* For BinOp `op`, decide how to sign-cast the operands. Returns 0 for "no + * cast", 1 for "cast both to signed", 2 for "cast both to unsigned", 3 for + * "cast lhs only (signedness `lhs_signed`)" (used for shifts). */ +typedef enum { BSC_NONE, BSC_SIGNED, BSC_UNSIGNED, BSC_SHIFT_LHS } BinSignCast; + +static BinSignCast binop_sign_kind(BinOp op, int* lhs_signed_out) { + *lhs_signed_out = 1; + switch (op) { + case BO_SDIV: + case BO_SREM: + return BSC_SIGNED; + case BO_UDIV: + case BO_UREM: + return BSC_UNSIGNED; + case BO_SHR_S: + *lhs_signed_out = 1; + return BSC_SHIFT_LHS; + case BO_SHR_U: + *lhs_signed_out = 0; + return BSC_SHIFT_LHS; + default: + return BSC_NONE; + } +} + +void c_emit_binop(CTarget* t, BinOp op, Operand dst, Operand a, Operand b) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + const char* sym = binop_to_c(op); + if (!sym) { + compiler_panic(t->c, loc, "C target: unknown binop %d", (int)op); + } + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: binop dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + /* Pointer operands get cast to uintptr_t inside c_emit_operand_arith, + * so the binop's C result type is `uintptr_t`, not the original pointer + * type. Keep the bridge when dst or either operand is pointer-typed so + * the assignment back to a pointer dst doesn't trip -Wint-conversion. */ + int has_ptr = c_operand_is_ptr_typed(t, dst) || + c_operand_is_ptr_typed(t, a) || c_operand_is_ptr_typed(t, b); + c_emit_local_assign_open(t, dst.v.local, + has_ptr ? (CfreeCgTypeId)0 : dst.type); + int lhs_signed = 1; + BinSignCast bsc = binop_sign_kind(op, &lhs_signed); + switch (bsc) { + case BSC_NONE: + c_emit_operand_arith(t, a); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, sym); + cbuf_puts(&t->body, " "); + c_emit_operand_arith(t, b); + break; + case BSC_SIGNED: + c_emit_operand_arith_signed(t, a, 1); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, sym); + cbuf_puts(&t->body, " "); + c_emit_operand_arith_signed(t, b, 1); + break; + case BSC_UNSIGNED: + c_emit_operand_arith_signed(t, a, 0); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, sym); + cbuf_puts(&t->body, " "); + c_emit_operand_arith_signed(t, b, 0); + break; + case BSC_SHIFT_LHS: + c_emit_operand_arith_signed(t, a, lhs_signed); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, sym); + cbuf_puts(&t->body, " "); + c_emit_operand(t, b); + break; + } + c_emit_local_assign_close(t); +} + +/* ===== unop ===== */ + +void c_emit_unop(CTarget* t, UnOp op, Operand dst, Operand a) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: unop dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + const char* sym = NULL; + switch (op) { + case UO_NEG: + case UO_FNEG: + sym = "-"; + break; + case UO_NOT: + sym = "!"; + break; + case UO_BNOT: + sym = "~"; + break; + default: + compiler_panic(t->c, loc, "C target: unknown unop %d", (int)op); + } + c_emit_local_assign_open(t, dst.v.local, dst.type); + cbuf_puts(&t->body, sym); + c_emit_operand(t, a); + c_emit_local_assign_close(t); +} + +/* ===== compare ops ===== */ + +static const char* cmp_to_c(CmpOp op) { + switch (op) { + case CMP_EQ: + return "=="; + case CMP_NE: + return "!="; + case CMP_LT_S: + case CMP_LT_U: + case CMP_LT_F: + return "<"; + case CMP_LE_S: + case CMP_LE_U: + case CMP_LE_F: + return "<="; + case CMP_GT_S: + case CMP_GT_U: + case CMP_GT_F: + return ">"; + case CMP_GE_S: + case CMP_GE_U: + case CMP_GE_F: + return ">="; + } + return NULL; +} + +/* Returns 1 if cmp op needs unsigned operand cast. -1 if signed. 0 if no cast + * (EQ/NE — sign doesn't matter for integer equality at the same width — and + * float compares). */ +static int cmp_signedness(CmpOp op) { + switch (op) { + case CMP_LT_S: + case CMP_LE_S: + case CMP_GT_S: + case CMP_GE_S: + return -1; + case CMP_LT_U: + case CMP_LE_U: + case CMP_GT_U: + case CMP_GE_U: + return 1; + default: + return 0; + } +} + +static void c_emit_cmp_operands(CTarget* t, CmpOp op, Operand a, Operand b) { + int sg = cmp_signedness(op); + if (sg == 0) { + c_emit_operand_arith(t, a); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, cmp_to_c(op)); + cbuf_puts(&t->body, " "); + c_emit_operand_arith(t, b); + } else { + int signed_ = (sg < 0); + c_emit_operand_arith_signed(t, a, signed_); + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, cmp_to_c(op)); + cbuf_puts(&t->body, " "); + c_emit_operand_arith_signed(t, b, signed_); + } +} + +void c_emit_cmp(CTarget* t, CmpOp op, Operand dst, Operand a, Operand b) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: cmp dst must be LOCAL"); + } + if (!cmp_to_c(op)) { + compiler_panic(t->c, loc, "C target: unknown cmp %d", (int)op); + } + c_ensure_local(t, dst.v.local, dst.type); + /* Compare result is C `int` (0/1); assigning to integer dst.type narrows + * implicitly without -Wall complaint. */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + c_emit_cmp_operands(t, op, a, b); + c_emit_local_assign_close(t); +} + +/* ===== labels, jump, cmp_branch ===== */ + +static void c_label_name(Label l, char* out, size_t cap) { + size_t i = 0; + if (cap == 0) return; + const char* p = "L"; + while (*p && i + 1 < cap) out[i++] = *p++; + char tmp[16]; + size_t n = 0; + u32 v = (u32)l; + if (v == 0) { + tmp[n++] = '0'; + } else { + while (v) { + tmp[n++] = (char)('0' + (v % 10)); + v /= 10; + } + } + while (n && i + 1 < cap) out[i++] = tmp[--n]; + out[i] = '\0'; +} + +Label c_emit_label_new(CTarget* t) { + t->next_label += 1; + return (Label)t->next_label; +} + +void c_emit_label_place(CTarget* t, Label l) { + char buf[24]; + c_label_name(l, buf, sizeof buf); + /* `Lk: __attribute__((unused));` — empty stmt keeps it valid at end-of-block, + * and the attribute silences -Wunused-label when the goto got folded away. */ + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ": __attribute__((unused));\n"); + t->last_was_terminator = 0; +} + +/* If `l` is the innermost structured scope's break/continue label, return + * the C keyword that exits/iterates that scope (a literal `break` or + * `continue`). NULL means "fall back to goto." Matches only the innermost + * scope because C `break`/`continue` only escape the nearest enclosing + * loop/switch — outer-scope targets must stay as goto. */ +static const char* c_scope_kw_for_label(CTarget* t, Label l) { + if (t->nscopes == 0) return NULL; + const CScopeInfo* s = &t->scopes[t->nscopes - 1u]; + if (!s->structured) return NULL; + if (l == s->break_label) return "break"; + if (l == s->continue_label) return "continue"; + return NULL; +} + +void c_emit_jump(CTarget* t, Label l) { + if (t->last_was_terminator) return; + const char* kw = c_scope_kw_for_label(t, l); + if (kw) { + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, kw); + cbuf_puts(&t->body, ";\n"); + } else { + char buf[24]; + c_label_name(l, buf, sizeof buf); + cbuf_puts(&t->body, " goto "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ";\n"); + } + t->last_was_terminator = 1; +} + +void c_emit_cmp_branch(CTarget* t, CmpOp op, Operand a, Operand b, Label l) { + if (t->last_was_terminator) return; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!cmp_to_c(op)) { + compiler_panic(t->c, loc, "C target: unknown cmp %d", (int)op); + } + const char* kw = c_scope_kw_for_label(t, l); + cbuf_puts(&t->body, " if ("); + c_emit_cmp_operands(t, op, a, b); + if (kw) { + cbuf_puts(&t->body, ") "); + cbuf_puts(&t->body, kw); + cbuf_puts(&t->body, ";\n"); + } else { + char buf[24]; + c_label_name(l, buf, sizeof buf); + cbuf_puts(&t->body, ") goto "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ";\n"); + } +} + +/* ===== scopes ===== + * + * SCOPE_LOOP maps to C's `for (;;) { ... }`. CG places the continue label + * just before `scope_begin` and the break label just before `scope_end` + * (see src/cg/control.c:208,253). The C target leaves those label + * placements in the body — they sit just before `for (;;) {` and just + * after `}` respectively, so any outer-scope `goto continue_lbl` or + * `goto break_lbl` (e.g. a nested loop's `continue` targeting this + * outer loop) still resolves. Inside the `for` body, `c_jump` and + * `c_cmp_branch` translate jumps whose target is the *innermost* scope's + * break/continue label into `break;` / `continue;`; outer-scope targets + * fall back to `goto`. The redundant `Lk: ;` adjacent to the `for` is + * cosmetic; gcc/clang fold it. */ + +static void c_grow_scopes(CTarget* t, u32 needed) { + Heap* h = t->c->ctx->heap; + u32 newcap = t->scopes_cap ? t->scopes_cap : 8; + while (newcap < needed) newcap *= 2; + CScopeInfo* ns = (CScopeInfo*)h->realloc( + h, t->scopes, t->scopes_cap * sizeof(CScopeInfo), + newcap * sizeof(CScopeInfo), _Alignof(CScopeInfo)); + if (!ns && newcap) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); + } + t->scopes = ns; + t->scopes_cap = newcap; +} + +CGScope c_emit_scope_begin(CTarget* t, const CGScopeDesc* d) { + if (t->nscopes + 1u >= t->scopes_cap) c_grow_scopes(t, t->nscopes + 2u); + u32 idx = t->nscopes; + t->scopes[idx].kind = d->kind; + t->scopes[idx].break_label = d->break_label; + t->scopes[idx].continue_label = d->continue_label; + t->scopes[idx].structured = 0; + t->nscopes += 1u; + if (d->kind == SCOPE_LOOP) { + cbuf_puts(&t->body, " for (;;) {\n"); + t->scopes[idx].structured = 1; + t->last_was_terminator = 0; + return (CGScope)(idx + 1u); + } + /* SCOPE_IF carries a cond consumed here. The public CfreeCg API always + * emits SCOPE_LOOP, so this branch only fires for internal callers. */ + if (d->kind == SCOPE_IF) { + char buf[24]; + c_label_name(d->break_label, buf, sizeof buf); + cbuf_puts(&t->body, " if (!("); + c_emit_operand(t, d->cond); + cbuf_puts(&t->body, ")) goto "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ";\n"); + } + return (CGScope)(idx + 1u); +} + +void c_emit_scope_else(CTarget* t, CGScope s) { + (void)t; + (void)s; + /* Public API doesn't emit SCOPE_IF; if it ever does, the frontend is + * responsible for placing the else label and the break_label itself. */ +} + +void c_emit_scope_end(CTarget* t, CGScope s) { + if (s == 0 || (u32)s > t->nscopes) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: scope_end on invalid handle"); + } + u32 idx = (u32)s - 1u; + if (t->scopes[idx].structured) { + /* CG places break_label just before scope_end, so the label sits + * inside the for-body. Anything that lands on it (including a + * `goto break_lbl` from a nested scope's labeled break) needs to + * exit the for — without an explicit `break;`, fall-through would + * iterate again. Always emit; if the body already terminated the + * defensive break is dead but harmless. */ + cbuf_puts(&t->body, " break;\n"); + cbuf_puts(&t->body, " }\n"); + /* The closing brace is not a terminator; control can fall through it + * (e.g., off the end of a void function). */ + t->last_was_terminator = 0; + } + t->nscopes -= 1u; +} + +void c_emit_break_to(CTarget* t, CGScope s) { + if (s == 0 || (u32)s > t->nscopes) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: break_to on invalid handle"); + } + c_emit_jump(t, t->scopes[s - 1u].break_label); +} + +void c_emit_continue_to(CTarget* t, CGScope s) { + if (s == 0 || (u32)s > t->nscopes) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: continue_to on invalid handle"); + } + c_emit_jump(t, t->scopes[s - 1u].continue_label); +} + +/* ===== switch dispatch ===== */ + +/* Emit `case <value>:`. For an int32_t selector the bare literal is + * already the right type, so we skip the cast; for wider/narrower + * integers we wrap in `(T)` so the case constant matches the switch + * value's promoted type (avoids -Wswitch warnings on narrower + * selectors). */ +static void c_emit_case_value(CTarget* t, CfreeCgTypeId sel_ty, u64 v) { + u32 w = c_int_width_for_signedness(t, sel_ty); + cbuf_puts(&t->body, " case "); + if (w != 0 && w != 32) { + cbuf_putc(&t->body, '('); + c_emit_type(t, &t->body, sel_ty); + cbuf_puts(&t->body, ")"); + } + c_emit_imm_literal(t, (i64)v); + cbuf_puts(&t->body, ":"); +} + +void c_emit_switch_( + CTarget* t, const CGSwitchDesc* d) { /* gcc/clang ignore strategy hints and + pick their own dispatch shape. */ + (void)d->hint; + if (t->last_was_terminator) return; + cbuf_puts(&t->body, " switch ("); + c_emit_operand(t, d->selector); + cbuf_puts(&t->body, ") {\n"); + for (u32 i = 0; i < d->ncases; ++i) { + char buf[24]; + c_label_name(d->cases[i].label, buf, sizeof buf); + c_emit_case_value(t, d->selector.type, d->cases[i].value); + cbuf_puts(&t->body, " goto "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ";\n"); + } + cbuf_puts(&t->body, " default: "); + if (d->default_label != (Label)LABEL_NONE) { + char buf[24]; + c_label_name(d->default_label, buf, sizeof buf); + cbuf_puts(&t->body, "goto "); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ";\n"); + } else { + /* No default supplied — the cfree IR's contract for that case is + * "if no case matches, fall through." `break;` does exactly that + * inside the for-wrapper around structured scopes. */ + cbuf_puts(&t->body, "break;\n"); + } + cbuf_puts(&t->body, " }\n"); + /* The switch always transfers control (every arm jumps or breaks). + * Mark as terminator so any frontend-emitted defensive jump after + * dispatch is dropped. */ + t->last_was_terminator = 1; +} + +/* ===== load_label_addr / indirect_branch ===== + * GCC computed-goto extension: `&&L` is the address of label L within + * the current function, and `goto *p;` jumps to such an address. This + * is the lowering every cc1-like backend uses (and what the toy + * frontend ultimately compiles to via the C target). */ +void c_emit_load_label_addr(CTarget* t, Operand dst, Label l) { + char buf[24]; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: load_label_addr dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "(void*)&&"); + c_label_name(l, buf, sizeof buf); + cbuf_puts(&t->body, buf); + c_emit_local_assign_close(t); +} + +void c_emit_indirect_branch(CTarget* t, Operand addr, + const Label* valid_targets, u32 ntargets) { + (void)valid_targets; + (void)ntargets; + if (t->last_was_terminator) return; + cbuf_puts(&t->body, " goto *"); + c_emit_operand(t, addr); + cbuf_puts(&t->body, ";\n"); + t->last_was_terminator = 1; +} + +/* ===== function-local static label-address data ===== */ + +static int c_is_local_static_sym(CTarget* t, ObjSymId sym) { + for (u32 i = 0; i < t->local_static_nsyms; ++i) { + if (t->local_static_syms[i] == sym) return 1; + } + return 0; +} + +static void c_mark_local_static_sym(CTarget* t, ObjSymId sym) { + Heap* h = t->c->ctx->heap; + if (sym == OBJ_SYM_NONE || c_is_local_static_sym(t, sym)) return; + if (t->local_static_nsyms + 1u > t->local_static_syms_cap) { + u32 oldcap = t->local_static_syms_cap; + u32 newcap = oldcap ? oldcap * 2u : 16u; + ObjSymId* ns = (ObjSymId*)h->realloc( + h, t->local_static_syms, oldcap * sizeof(*t->local_static_syms), + newcap * sizeof(*t->local_static_syms), _Alignof(ObjSymId)); + if (!ns) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: out of memory"); + } + t->local_static_syms = ns; + t->local_static_syms_cap = newcap; + } + t->local_static_syms[t->local_static_nsyms++] = sym; +} + +static void c_grow_local_static_entries(CTarget* t, u32 want) { + Heap* h = t->c->ctx->heap; + if (want <= t->local_static_entries_cap) return; + u32 oldcap = t->local_static_entries_cap; + u32 newcap = oldcap ? oldcap * 2u : 8u; + while (newcap < want) newcap *= 2u; + CLocalStaticLabelEntry* ne = (CLocalStaticLabelEntry*)h->realloc( + h, t->local_static_entries, oldcap * sizeof(*t->local_static_entries), + newcap * sizeof(*t->local_static_entries), + _Alignof(CLocalStaticLabelEntry)); + if (!ne) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: out of memory"); + } + t->local_static_entries = ne; + t->local_static_entries_cap = newcap; +} + +int c_emit_can_local_static_data(CTarget* t, + const CGLocalStaticDataDesc* desc) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, desc->type)); + if (!ty) { + compiler_panic(t->c, loc, "C target: unknown local static type %u", + (unsigned)desc->type); + } + if (ty->kind == CFREE_CG_TYPE_ARRAY) { + ty = cg_type_get(t->c, api_unalias_type(t->c, ty->array.elem)); + } + return ty && ty->kind == CFREE_CG_TYPE_PTR; +} + +int c_emit_local_static_data_begin(CTarget* t, + const CGLocalStaticDataDesc* desc) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!t->cur_fn) { + compiler_panic(t->c, loc, + "C target: function-local static data outside function"); + } + if (t->local_static_active) { + compiler_panic(t->c, loc, + "C target: nested function-local static data definition"); + } + const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, desc->type)); + if (!ty) { + compiler_panic(t->c, loc, "C target: unknown local static type %u", + (unsigned)desc->type); + } + + u64 count = 1; + int is_array = 0; + CfreeCgTypeId elem = desc->type; + if (ty->kind == CFREE_CG_TYPE_ARRAY) { + is_array = 1; + count = ty->array.count; + elem = ty->array.elem; + ty = cg_type_get(t->c, api_unalias_type(t->c, elem)); + } + if (!c_emit_can_local_static_data(t, desc)) { + return 0; + } + if (count > UINT32_MAX) { + compiler_panic(t->c, loc, "C target: local static pointer table too large"); + } + + c_grow_local_static_entries(t, (u32)count); + for (u32 i = 0; i < (u32)count; ++i) { + t->local_static_entries[i].label = LABEL_NONE; + t->local_static_entries[i].addend = 0; + t->local_static_entries[i].has_label = 0; + } + t->local_static_nentries = (u32)count; + t->local_static_sym = desc->sym; + t->local_static_type = desc->type; + t->local_static_count = count; + t->local_static_offset = 0; + t->local_static_ptr_width = (u32)cg_type_size(t->c, elem); + t->local_static_align = + desc->align ? desc->align : cg_type_align(t->c, desc->type); + t->local_static_active = 1; + t->local_static_is_array = (u8)is_array; + t->local_static_readonly = + (desc->attrs.flags & CFREE_CG_DATADEF_READONLY) ? 1u : 0u; + c_mark_local_static_sym(t, desc->sym); + return 1; +} + +void c_emit_local_static_data_write(CTarget* t, const u8* data, u64 len) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!t->local_static_active || !len) return; + if (data) { + for (u64 i = 0; i < len; ++i) { + if (data[i] != 0) { + compiler_panic(t->c, loc, + "C target: function-local static label table supports " + "only zero bytes and label addresses"); + } + } + } + t->local_static_offset += len; +} + +void c_emit_local_static_data_label_addr(CTarget* t, Label target, i64 addend, + u32 width, u32 address_space) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + (void)address_space; + if (!t->local_static_active) { + compiler_panic(t->c, loc, + "C target: label address outside local static data"); + } + if (width != t->local_static_ptr_width) { + compiler_panic(t->c, loc, + "C target: label address width %u does not match pointer " + "width %u", + (unsigned)width, (unsigned)t->local_static_ptr_width); + } + if ((t->local_static_offset % t->local_static_ptr_width) != 0) { + compiler_panic(t->c, loc, + "C target: unaligned label address in local static data"); + } + u64 idx = t->local_static_offset / t->local_static_ptr_width; + if (idx >= t->local_static_count) { + compiler_panic(t->c, loc, + "C target: too many local static label table entries"); + } + CLocalStaticLabelEntry* e = &t->local_static_entries[(u32)idx]; + if (e->has_label) { + compiler_panic(t->c, loc, + "C target: duplicate local static label table entry"); + } + e->label = target; + e->addend = addend; + e->has_label = 1; + t->local_static_offset += width; +} + +static void c_emit_local_static_label_expr(CTarget* t, + const CLocalStaticLabelEntry* e) { + char lbuf[24]; + if (!e->has_label) { + cbuf_puts(&t->decls, "(void*)0"); + return; + } + if (e->addend == 0) { + cbuf_puts(&t->decls, "&&"); + c_label_name(e->label, lbuf, sizeof lbuf); + cbuf_puts(&t->decls, lbuf); + return; + } + cbuf_puts(&t->decls, "(void*)((char*)&&"); + c_label_name(e->label, lbuf, sizeof lbuf); + cbuf_puts(&t->decls, lbuf); + cbuf_puts(&t->decls, " + "); + cbuf_put_i64(&t->decls, e->addend); + cbuf_puts(&t->decls, ")"); +} + +void c_emit_local_static_data_end(CTarget* t) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (!t->local_static_active) return; + u64 total_size = t->local_static_count * t->local_static_ptr_width; + if (t->local_static_offset > total_size) { + compiler_panic(t->c, loc, + "C target: local static initializer exceeds object size"); + } + const char* nm = c_sym_name(t, t->local_static_sym); + cbuf_puts(&t->decls, " static __attribute__((unused)) "); + cbuf_puts(&t->decls, "_Alignas("); + cbuf_put_u64(&t->decls, t->local_static_align ? t->local_static_align : 1); + cbuf_puts(&t->decls, ") void* "); + if (t->local_static_readonly) cbuf_puts(&t->decls, "const "); + cbuf_puts(&t->decls, nm); + if (t->local_static_is_array) { + cbuf_puts(&t->decls, "["); + cbuf_put_u64(&t->decls, t->local_static_count); + cbuf_puts(&t->decls, "]"); + } + cbuf_puts(&t->decls, " = {"); + for (u32 i = 0; i < t->local_static_nentries; ++i) { + if (i > 0) cbuf_putc(&t->decls, ','); + if ((i & 3u) == 0) cbuf_puts(&t->decls, "\n "); + c_emit_local_static_label_expr(t, &t->local_static_entries[i]); + } + cbuf_puts(&t->decls, "\n };\n"); + + t->local_static_active = 0; + t->local_static_sym = OBJ_SYM_NONE; + t->local_static_type = CFREE_CG_TYPE_NONE; + t->local_static_count = 0; + t->local_static_offset = 0; + t->local_static_ptr_width = 0; + t->local_static_align = 0; + t->local_static_nentries = 0; + t->local_static_is_array = 0; + t->local_static_readonly = 0; +} + +/* ===== local, local_addr ===== */ + +CGLocal c_emit_local(CTarget* t, const CGLocalDesc* d) { + t->next_local += 1u; + if (t->next_local == CG_LOCAL_NONE) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: semantic local id exhausted"); + return CG_LOCAL_NONE; + } + c_ensure_local(t, (CGLocal)t->next_local, d->type); + return (CGLocal)t->next_local; +} + +void c_emit_local_addr(CTarget* t, Operand dst, const CGLocalDesc* d, + CGLocal s) { + (void)d; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: local_addr dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + c_ensure_local(t, s, d->type); + char buf[24]; + c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "&"); + c_local_name(s, buf, sizeof buf); + cbuf_puts(&t->body, buf); + c_emit_local_assign_close(t); +} + +/* ===== convert ===== */ + +void c_emit_convert(CTarget* t, ConvKind k, Operand dst, Operand src) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: convert dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + char buf[24]; + c_local_name(dst.v.local, buf, sizeof buf); + + if (k == CV_BITCAST) { + /* Same-size reinterpretation. Use __builtin_memcpy through a temp so + * neither aliasing nor representation assumptions creep in. The temp + * lives in its own `{ ... }` block, so no name collision tracking. */ + u32 id = ++t->next_tmp; + cbuf_puts(&t->body, " { "); + c_emit_type(t, &t->body, src.type); + cbuf_puts(&t->body, " __bc"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, " = "); + c_emit_operand(t, src); + cbuf_puts(&t->body, "; __builtin_memcpy(&"); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ", &__bc"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, ", sizeof __bc"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, "); }\n"); + return; + } + + if (c_type_is_bool(t, dst.type)) { + c_emit_local_assign_open(t, dst.v.local, dst.type); + cbuf_puts(&t->body, "("); + c_emit_type(t, &t->body, dst.type); + cbuf_puts(&t->body, ")("); + c_emit_operand(t, src); + cbuf_puts(&t->body, " != 0)"); + c_emit_local_assign_close(t); + return; + } + + /* Integer and float conversions: a C cast does the right thing once the + * source is first cast to the appropriate signedness (for SEXT/ZEXT and + * ITOF_S/U / FTOI_S/U). */ + int src_signed = 1; + switch (k) { + case CV_ZEXT: + case CV_ITOF_U: + case CV_FTOI_U: + src_signed = 0; + break; + default: + src_signed = 1; + break; + } + + /* The cast `(dst.type)(src)` produces a value of dst.type. */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + cbuf_puts(&t->body, "("); + c_emit_type(t, &t->body, dst.type); + cbuf_puts(&t->body, ")"); + if (k == CV_SEXT || k == CV_ZEXT) { + c_emit_operand_signed(t, src, src_signed); + } else if (k == CV_TRUNC && c_operand_is_ptr_typed(t, src)) { + /* Casting a pointer directly to a narrower integer trips + * -Wvoid-pointer-to-int-cast (and -Wpointer-to-int-cast). Bridge + * through uintptr_t. */ + cbuf_puts(&t->body, "((uintptr_t)"); + c_emit_operand(t, src); + cbuf_puts(&t->body, ")"); + } else { + /* TRUNC / FTOI / ITOF / FEXT / FTRUNC: rely on C cast semantics. */ + c_emit_operand(t, src); + } + c_emit_local_assign_close(t); +} + +/* === call === */ + +static CfreeCgTypeId c_call_arg_type(CTarget* t, const CgType* fty, + const CGCallDesc* d, u32 i) { + if (i < fty->func.nparams) return fty->func.params[i].type; + return c_local_type_or_panic(t, d->args[i]); +} + +static void c_emit_call_arg(CTarget* t, const CgType* fty, const CGCallDesc* d, + u32 i) { + CfreeCgTypeId ty = c_call_arg_type(t, fty, d, i); + c_ensure_local(t, d->args[i], ty); + c_emit_operand(t, c_op_local(d->args[i], ty)); +} + +static void c_emit_call_expr(CTarget* t, const CgType* fty, const CGCallDesc* d, + const CfreeCgTypeId* result_types) { + if (d->callee.kind == OPK_GLOBAL) { + c_ensure_forward_decl_with_results(t, d->callee.v.global.sym, d->fn_type, + d->nresults > 1 ? result_types : NULL, + d->nresults); + cbuf_puts(&t->body, c_sym_name(t, d->callee.v.global.sym)); + } else if (d->callee.kind == OPK_LOCAL) { + cbuf_puts(&t->body, "(("); + if (d->nresults > 1) { + c_emit_tuple_type_name(t, &t->body, result_types, d->nresults); + cbuf_puts(&t->body, " (*)("); + if (fty->func.nparams == 0 && !fty->func.abi_variadic) { + cbuf_puts(&t->body, "void"); + } else { + for (u32 i = 0; i < fty->func.nparams; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_type(t, &t->body, fty->func.params[i].type); + } + if (fty->func.abi_variadic) { + if (fty->func.nparams > 0) cbuf_puts(&t->body, ", "); + cbuf_puts(&t->body, "..."); + } + } + cbuf_puts(&t->body, ")"); + } else { + const char* fp = c_typedef_name(t, d->fn_type); + c_ensure_typedef(t, d->fn_type); + cbuf_puts(&t->body, fp); + } + cbuf_puts(&t->body, ")"); + c_emit_operand(t, d->callee); + cbuf_puts(&t->body, ")"); + } else { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: callee kind %d not supported", + (int)d->callee.kind); + } + + cbuf_puts(&t->body, "("); + for (u32 i = 0; i < d->nargs; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_call_arg(t, fty, d, i); + } + cbuf_puts(&t->body, ")"); +} + +const char* c_emit_tail_call_unrealizable_reason(CTarget* t, + const CGCallDesc* d) { + return c_emit_tail_call_unrealizable_reason_for(t, t->cur_fn, d); +} + +const char* c_emit_tail_call_unrealizable_reason_for( + CTarget* t, const CGFuncDesc* caller_fd, const CGCallDesc* d) { + SrcLoc loc = caller_fd ? caller_fd->loc : (SrcLoc){0, 0, 0}; + const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, d->fn_type)); + if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { + compiler_panic(t->c, loc, "C target: tail call: bad fn_type"); + } + const CgType* caller = + caller_fd ? cg_type_get(t->c, api_unalias_type(t->c, caller_fd->fn_type)) + : NULL; + if (!caller || caller->kind != CFREE_CG_TYPE_FUNC) { + compiler_panic(t->c, loc, "C target: tail call outside function"); + } + if (caller->func.abi_variadic) { + return "C target: caller variadic tail call not yet supported by clang " + "musttail"; + } + if (fty->func.abi_variadic) { + return "C target: variadic tail call not yet supported by clang musttail"; + } + if (caller->func.nparams != fty->func.nparams) { + return "C target: tail call with differing parameter counts not yet " + "supported by clang musttail"; + } + return NULL; +} + +void c_emit_call(CTarget* t, const CGCallDesc* d) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + Heap* h = t->c->ctx->heap; + + const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, d->fn_type)); + if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { + compiler_panic(t->c, loc, "C target: call: bad fn_type"); + } + CfreeCgTypeId ret_type = fty->func.ret; + int is_tail = (d->flags & CG_CALL_TAIL) != 0; + CfreeCgTypeId* result_types = NULL; + if (d->nresults > 1) { + result_types = (CfreeCgTypeId*)h->alloc( + h, sizeof(CfreeCgTypeId) * d->nresults, _Alignof(CfreeCgTypeId)); + if (!result_types) { + compiler_panic(t->c, loc, "C target: out of memory"); + return; + } + for (u32 i = 0; i < d->nresults; ++i) + result_types[i] = c_local_type_or_panic(t, d->results[i]); + c_ensure_tuple_typedef(t, result_types, d->nresults); + } + + if (is_tail) { + cbuf_puts(&t->body, " __attribute__((musttail)) return "); + c_emit_call_expr(t, fty, d, result_types); + cbuf_puts(&t->body, ";\n"); + t->last_was_terminator = 1; + } else if (d->nresults == 0) { + cbuf_puts(&t->body, " "); + c_emit_call_expr(t, fty, d, result_types); + cbuf_puts(&t->body, ";\n"); + } else if (d->nresults == 1) { + c_ensure_local(t, d->results[0], ret_type); + c_emit_local_assign_open(t, d->results[0], ret_type); + c_emit_call_expr(t, fty, d, result_types); + c_emit_local_assign_close(t); + } else { + char tmp[32]; + u32 tmp_id = t->next_tmp++; + snprintf(tmp, sizeof tmp, "__cfree_call_%u", (unsigned)tmp_id); + cbuf_puts(&t->body, " "); + c_emit_tuple_type_name(t, &t->body, result_types, d->nresults); + cbuf_putc(&t->body, ' '); + cbuf_puts(&t->body, tmp); + cbuf_puts(&t->body, " = "); + c_emit_call_expr(t, fty, d, result_types); + cbuf_puts(&t->body, ";\n"); + for (u32 i = 0; i < d->nresults; ++i) { + c_ensure_local(t, d->results[i], result_types[i]); + c_emit_local_assign_open(t, d->results[i], result_types[i]); + cbuf_puts(&t->body, tmp); + cbuf_puts(&t->body, ".f"); + cbuf_put_u64(&t->body, (u64)i); + c_emit_local_assign_close(t); + } + } + if (result_types) + h->free(h, result_types, sizeof(CfreeCgTypeId) * d->nresults); +} + +/* === load / store === */ + +void c_emit_load(CTarget* t, Operand dst, Operand addr, MemAccess m) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: load dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + CfreeCgTypeId access_ty = m.type ? m.type : dst.type; + if (c_type_is_aggregate(t, access_ty) && !c_type_is_aggregate(t, dst.type)) + access_ty = dst.type; + /* The deref `*(access_ty*)addr` produces a value of access_ty. */ + c_emit_local_assign_open(t, dst.v.local, access_ty); + c_emit_addr_deref(t, addr, access_ty); + c_emit_local_assign_close(t); +} + +void c_emit_store(CTarget* t, Operand addr, Operand src, MemAccess m) { + CfreeCgTypeId access_ty = m.type ? m.type : src.type; + if (c_type_is_aggregate(t, access_ty) && !c_type_is_aggregate(t, src.type)) + access_ty = src.type; + cbuf_puts(&t->body, " "); + c_emit_addr_deref(t, addr, access_ty); + /* c_emit_operand_as bridges int/ptr crossings through uintptr_t so + * roundtrips don't trip `-Wint-conversion`. */ + cbuf_puts(&t->body, " = "); + c_emit_operand_as(t, src, access_ty); + cbuf_puts(&t->body, ";\n"); +} + +void c_emit_addr_of(CTarget* t, Operand dst, Operand lv) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: addr_of dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + /* `c_emit_lvalue_addr` casts its output to dst.type already. */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + c_emit_lvalue_addr(t, lv, dst.type); + c_emit_local_assign_close(t); +} + +void c_emit_ret( + CTarget* t, const CGLocal* values, + u32 nvalues) { /* Already-terminated block: this ret is unreachable (the + * frontend's defensive `return 0;` epilogue lands here right + * after a user return). */ + if (t->last_was_terminator) return; + /* CG emits a defensive ret_void epilogue at the end of every function. For + * a non-void function that's unreachable; emitting a bare `return;` would + * trip -Wreturn-type. Spell it as `__builtin_unreachable()` so the host C + * compiler sees the path is dead without us inventing a fake value. */ + if (nvalues == 0 && t->cur_fn) { + if (t->cur_fn->nresults != 0) { + cbuf_puts(&t->body, " __builtin_unreachable();\n"); + t->last_was_terminator = 1; + return; + } + } + cbuf_puts(&t->body, " return"); + if (nvalues == 1) { + cbuf_puts(&t->body, " "); + CfreeCgTypeId ret_type = + t->cur_fn ? t->cur_fn->result_types[0] : (CfreeCgTypeId)0; + const CgType* rty = + ret_type ? cg_type_get(t->c, api_unalias_type(t->c, ret_type)) : NULL; + int is_aggregate = rty && (rty->kind == CFREE_CG_TYPE_RECORD || + rty->kind == CFREE_CG_TYPE_ARRAY); + if (ret_type && !is_aggregate) { + CfreeCgTypeId value_ty = c_local_type_or_panic(t, values[0]); + c_emit_operand_as(t, c_op_local(values[0], value_ty), ret_type); + } else { + c_emit_operand(t, c_op_local(values[0], ret_type)); + } + } else if (nvalues > 1) { + const CfreeCgTypeId* result_types = + t->cur_fn ? t->cur_fn->result_types : NULL; + if (!result_types || t->cur_fn->nresults != nvalues) { + compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, + "C target: multi-return shape mismatch"); + return; + } + c_ensure_tuple_typedef(t, result_types, nvalues); + cbuf_puts(&t->body, " ("); + c_emit_tuple_type_name(t, &t->body, result_types, nvalues); + cbuf_puts(&t->body, "){"); + for (u32 i = 0; i < nvalues; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_operand(t, c_op_local(values[i], result_types[i])); + } + cbuf_puts(&t->body, "}"); + } + cbuf_puts(&t->body, ";\n"); + t->last_was_terminator = 1; +} + +/* === alias === + * `cfree_cg_alias` makes alias_sym refer to target_sym's body. In obj-file + * land that's two ObjSyms sharing a (section_id, value); in C source we + * have to spell it out: + * + * ELF/PE → `Ret alias(args) __attribute__((alias("target")));` + * Single definition, true aliasing, &alias == &target. + * Mach-O → emit a thunk `Ret alias(args) { return target(args); }`. + * Clang on Darwin rejects __attribute__((alias)) outright, + * so we fall back to a wrapper. Loses the `&alias==&target` + * identity but preserves call-through semantics, which is + * all the cfree-emitted code path needs. + * + * The emitted decl serves as the alias definition AND a forward prototype + * for callers, so we mark sym_forwarded to dedup against a later c_call. */ +void c_emit_alias(CTarget* t, ObjSymId alias_sym, ObjSymId target_sym, + CfreeCgTypeId type) { + Heap* h = t->c->ctx->heap; + if ((u32)alias_sym >= t->sym_forwarded_cap) { + u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; + while (newcap <= (u32)alias_sym) newcap *= 2; + u8* nd = + (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); + if (!nd && newcap) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); + } + for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; + t->sym_forwarded = nd; + t->sym_forwarded_cap = newcap; + } + if (t->sym_forwarded[alias_sym]) return; + t->sym_forwarded[alias_sym] = 1; + const char* alias_name = c_sym_name(t, alias_sym); + const char* target_name = c_sym_name(t, target_sym); + const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, type)); + int is_func = fty && fty->kind == CFREE_CG_TYPE_FUNC; + + if (t->c->target.obj != CFREE_OBJ_MACHO) { + /* Attribute form. Works for both function and object aliases on ELF + * and PE/COFF. */ + c_emit_func_signature(t, &t->forwards, alias_name, type, NULL, 0); + cbuf_puts(&t->forwards, " __attribute__((alias(\""); + cbuf_puts(&t->forwards, target_name); + cbuf_puts(&t->forwards, "\")));\n"); + return; + } + + /* Mach-O thunk fallback. Functions only for v1 — object aliases on + * Darwin would need a more elaborate scheme (see doc/CBACKEND.md). */ + if (!is_func) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: object alias on Mach-O not yet supported"); + } + /* Forward prototype for the target (its full definition lands separately + * via c_func_begin). Also dedup that. */ + c_ensure_forward_decl(t, target_sym, type); + /* `static`? No — alias must be externally visible. */ + c_emit_func_signature(t, &t->forwards, alias_name, type, NULL, 0); + cbuf_puts(&t->forwards, " { "); + CfreeCgTypeId ret_type = cg_type_func_ret_id(t->c, type); + if (!cg_type_is_void(t->c, ret_type)) cbuf_puts(&t->forwards, "return "); + cbuf_puts(&t->forwards, target_name); + cbuf_puts(&t->forwards, "("); + for (u32 i = 0; i < fty->func.nparams; ++i) { + if (i > 0) cbuf_puts(&t->forwards, ", "); + cbuf_puts(&t->forwards, "p"); + cbuf_put_u64(&t->forwards, (u64)i); + } + cbuf_puts(&t->forwards, "); }\n"); +} + +/* === intrinsic === + * + * All cfree IntrinKinds map onto gcc/clang `__builtin_*` builtins, which + * the host C compiler then turns into the appropriate sequence (inline op, + * libcall, runtime CAS, etc.). This is exactly the seam the doc described: + * cfree records intent, the downstream toolchain picks the mechanism. + * + * Operand shapes follow arch.h §IntrinKind. */ + +static const char* c_bitop_builtin(IntrinKind k, u32 width) { + switch (k) { + case INTRIN_POPCOUNT: + if (width == 32) return "__builtin_popcount"; + if (width == 64) return "__builtin_popcountll"; + if (width == 16 || width == 8) return "__builtin_popcount"; + return NULL; + case INTRIN_CTZ: + if (width == 32) return "__builtin_ctz"; + if (width == 64) return "__builtin_ctzll"; + if (width == 16 || width == 8) return "__builtin_ctz"; + return NULL; + case INTRIN_CLZ: + if (width == 32) return "__builtin_clz"; + if (width == 64) return "__builtin_clzll"; + if (width == 16 || width == 8) return "__builtin_clz"; + return NULL; + case INTRIN_BSWAP16: + return "__builtin_bswap16"; + case INTRIN_BSWAP32: + return "__builtin_bswap32"; + case INTRIN_BSWAP64: + return "__builtin_bswap64"; + default: + return NULL; + } +} + +static const char* c_overflow_builtin(IntrinKind k) { + switch (k) { + case INTRIN_SADD_OVERFLOW: + case INTRIN_UADD_OVERFLOW: + return "__builtin_add_overflow"; + case INTRIN_SSUB_OVERFLOW: + case INTRIN_USUB_OVERFLOW: + return "__builtin_sub_overflow"; + case INTRIN_SMUL_OVERFLOW: + case INTRIN_UMUL_OVERFLOW: + return "__builtin_mul_overflow"; + default: + return NULL; + } +} + +void c_emit_intrinsic(CTarget* t, IntrinKind k, Operand* dsts, u32 ndst, + const Operand* args, u32 narg) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + switch (k) { + case INTRIN_UNREACHABLE: + cbuf_puts(&t->body, " __builtin_unreachable();\n"); + return; + case INTRIN_TRAP: + cbuf_puts(&t->body, " __builtin_trap();\n"); + return; + case INTRIN_PREFETCH: { + cbuf_puts(&t->body, " __builtin_prefetch("); + for (u32 i = 0; i < narg; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_operand(t, args[i]); + } + cbuf_puts(&t->body, ");\n"); + return; + } + case INTRIN_ASSUME_ALIGNED: { + /* dsts[0] is the result local (pointer); args = (ptr, align [, ofs]) */ + if (ndst != 1) { + compiler_panic(t->c, loc, + "C target: assume_aligned: expected 1 dst, got %u", + (unsigned)ndst); + } + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + /* Returns void*; bridge to dst pointer type. */ + c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "__builtin_assume_aligned("); + for (u32 i = 0; i < narg; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_operand(t, args[i]); + } + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); + return; + } + case INTRIN_EXPECT: { + /* dsts[0] = __builtin_expect(args[0], args[1]) but typed via long. */ + if (ndst != 1 || narg != 2) { + compiler_panic(t->c, loc, + "C target: expect: bad shape (ndst=%u narg=%u)", + (unsigned)ndst, (unsigned)narg); + } + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + /* Returns `long`; dst.type may be a narrower int — keep the bridge. */ + c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "__builtin_expect((long)"); + c_emit_operand(t, args[0]); + cbuf_puts(&t->body, ", (long)"); + c_emit_operand(t, args[1]); + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); + return; + } + case INTRIN_POPCOUNT: + case INTRIN_CTZ: + case INTRIN_CLZ: + case INTRIN_BSWAP16: + case INTRIN_BSWAP32: + case INTRIN_BSWAP64: { + if (ndst != 1 || narg != 1) { + compiler_panic(t->c, loc, + "C target: bit-intrin: bad shape (ndst=%u narg=%u)", + (unsigned)ndst, (unsigned)narg); + } + u32 w = c_int_width_for_signedness(t, args[0].type); + const char* fn = c_bitop_builtin(k, w); + if (!fn) { + compiler_panic(t->c, loc, "C target: bit-intrin width %u unsupported", + (unsigned)w); + } + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + /* __builtin_popcount/ctz/clz return `int`; bswap returns its input + * type. Narrow to dst.type via the bridge. */ + c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, fn); + cbuf_puts(&t->body, "("); + c_emit_operand(t, args[0]); + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); + return; + } + case INTRIN_MEMCPY: + case INTRIN_MEMMOVE: + case INTRIN_MEMSET: { + const char* fn = (k == INTRIN_MEMCPY) ? "__builtin_memcpy" + : (k == INTRIN_MEMMOVE) ? "__builtin_memmove" + : "__builtin_memset"; + cbuf_puts(&t->body, " "); + cbuf_puts(&t->body, fn); + cbuf_puts(&t->body, "("); + for (u32 i = 0; i < narg; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + /* The pointer operands (dst for all three; src for mem{cpy,move}) + * may be typed as a plain integer local when they come from address + * arithmetic, which the C target declares as int64_t. The builtins + * take void*, so cast explicitly to avoid -Wint-conversion. */ + int is_ptr_arg = + (i == 0) || (i == 1 && (k == INTRIN_MEMCPY || k == INTRIN_MEMMOVE)); + if (is_ptr_arg) cbuf_puts(&t->body, "(void*)"); + c_emit_operand(t, args[i]); + } + cbuf_puts(&t->body, ");\n"); + return; + } + case INTRIN_SADD_OVERFLOW: + case INTRIN_UADD_OVERFLOW: + case INTRIN_SSUB_OVERFLOW: + case INTRIN_USUB_OVERFLOW: + case INTRIN_SMUL_OVERFLOW: + case INTRIN_UMUL_OVERFLOW: { + /* dsts[0] = value local, dsts[1] = i1 overflow flag. + * + * Signedness comes from the intrinsic kind, but cfree's CG int type + * is width-only and the C target declares every result as a signed + * fixed-width (int{8,16,32,64}_t). __builtin_*_overflow keys its + * overflow check on the result type, so passing the signed local + * directly makes a UADD test as if it were signed and miss true + * unsigned overflow. Wrap the call in a block with a scratch result + * of the right signedness and copy it back through the int/uint + * bridge. */ + if (ndst != 2 || narg != 2) { + compiler_panic(t->c, loc, "C target: overflow-intrin: bad shape"); + } + int is_unsigned = + (k == INTRIN_UADD_OVERFLOW || k == INTRIN_USUB_OVERFLOW || + k == INTRIN_UMUL_OVERFLOW); + const char* fn = c_overflow_builtin(k); + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + c_ensure_local(t, dsts[1].v.local, dsts[1].type); + char vbuf[24], obuf[24]; + c_local_name(dsts[0].v.local, vbuf, sizeof vbuf); + c_local_name(dsts[1].v.local, obuf, sizeof obuf); + u32 w = c_int_width_for_signedness(t, dsts[0].type); + const char* sty = c_int_type_name_for_width(w, !is_unsigned); + if (!sty) { + compiler_panic(t->c, loc, + "C target: overflow-intrin: unsupported width %u", + (unsigned)w); + } + cbuf_puts(&t->body, " { "); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, " __ovsc; "); + cbuf_puts(&t->body, obuf); + cbuf_puts(&t->body, " = ("); + c_emit_type(t, &t->body, dsts[1].type); + cbuf_puts(&t->body, ")"); + cbuf_puts(&t->body, fn); + cbuf_puts(&t->body, "(("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")"); + c_emit_operand(t, args[0]); + cbuf_puts(&t->body, ", ("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")"); + c_emit_operand(t, args[1]); + cbuf_puts(&t->body, ", &__ovsc); "); + cbuf_puts(&t->body, vbuf); + cbuf_puts(&t->body, " = ("); + c_emit_type(t, &t->body, dsts[0].type); + cbuf_puts(&t->body, ")__ovsc; }\n"); + return; + } + case INTRIN_SETJMP: { + t->need_setjmp = 1; + if (ndst != 1 || narg != 1) { + compiler_panic(t->c, loc, "C target: setjmp: bad shape"); + } + c_ensure_local(t, dsts[0].v.local, dsts[0].type); + /* setjmp returns `int`; bridge to dst.type. */ + c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "setjmp(*(jmp_buf*)("); + c_emit_operand(t, args[0]); + cbuf_puts(&t->body, "))"); + c_emit_local_assign_close(t); + return; + } + case INTRIN_LONGJMP: { + t->need_setjmp = 1; + cbuf_puts(&t->body, " longjmp(*(jmp_buf*)("); + c_emit_operand(t, args[0]); + cbuf_puts(&t->body, "), (int)"); + c_emit_operand(t, args[1]); + cbuf_puts(&t->body, ");\n"); + return; + } + case INTRIN_NONE: + default: + compiler_panic(t->c, loc, "C target: intrinsic kind %d not handled", + (int)k); + } +} + +/* === alloca === */ + +void c_emit_alloca(CTarget* t, Operand dst, Operand size, u32 align) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: alloca dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + /* __builtin_alloca returns `void*`; dst.type is typically void* too. */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + if (align > 1) { + /* gcc has __builtin_alloca_with_align taking bits, not bytes. */ + cbuf_puts(&t->body, "__builtin_alloca_with_align("); + c_emit_operand(t, size); + cbuf_puts(&t->body, ", "); + cbuf_put_u64(&t->body, (u64)align * 8u); + cbuf_puts(&t->body, ")"); + } else { + cbuf_puts(&t->body, "__builtin_alloca("); + c_emit_operand(t, size); + cbuf_puts(&t->body, ")"); + } + c_emit_local_assign_close(t); +} + +/* === varargs === + * + * The C-target va_list is the host toolchain's `va_list` from <stdarg.h>. + * The first arg of all va_* is `ap_addr` - the address of the va_list local. + * We deref to get the va_list lvalue C's macros expect. */ + +void c_emit_va_start(CTarget* t, Operand ap_addr) { + t->need_stdarg = 1; + /* va_start needs the "last named parameter". CG doesn't pass that to the + * backend; gcc/clang accept any non-modified ident here for variadic + * compatibility — feed the synthesized parameter name `p<nparams-1>` from + * the enclosing function. */ + const CGFuncDesc* fd = t->cur_fn; + SrcLoc loc = fd ? fd->loc : (SrcLoc){0, 0, 0}; + if (!fd) compiler_panic(t->c, loc, "C target: va_start outside function"); + const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, fd->fn_type)); + if (!fty || fty->kind != CFREE_CG_TYPE_FUNC || fty->func.nparams == 0) { + compiler_panic(t->c, loc, + "C target: va_start in non-variadic function shape"); + } + cbuf_puts(&t->body, " __builtin_va_start(*(va_list*)("); + c_emit_operand(t, ap_addr); + cbuf_puts(&t->body, "), p"); + cbuf_put_u64(&t->body, (u64)(fty->func.nparams - 1u)); + cbuf_puts(&t->body, ");\n"); +} + +void c_emit_va_end(CTarget* t, Operand ap_addr) { + cbuf_puts(&t->body, " __builtin_va_end(*(va_list*)("); + c_emit_operand(t, ap_addr); + cbuf_puts(&t->body, "));\n"); +} + +void c_emit_va_copy(CTarget* t, Operand dst_addr, Operand src_addr) { + cbuf_puts(&t->body, " __builtin_va_copy(*(va_list*)("); + c_emit_operand(t, dst_addr); + cbuf_puts(&t->body, "), *(va_list*)("); + c_emit_operand(t, src_addr); + cbuf_puts(&t->body, "));\n"); +} + +void c_emit_va_arg(CTarget* t, Operand dst, Operand ap_addr, CfreeCgTypeId ty) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: va_arg dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + /* __builtin_va_arg yields a value of `ty`. */ + c_emit_local_assign_open(t, dst.v.local, ty); + cbuf_puts(&t->body, "__builtin_va_arg(*(va_list*)("); + c_emit_operand(t, ap_addr); + cbuf_puts(&t->body, "), "); + c_emit_type(t, &t->body, ty); + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); +} + +/* === copy_bytes / set_bytes === */ + +void c_emit_copy_bytes(CTarget* t, Operand dst_addr, Operand src_addr, + AggregateAccess m) { + c_assert_no_index(t, dst_addr, "copy_bytes dst"); + c_assert_no_index(t, src_addr, "copy_bytes src"); + /* dst/src may be plain integer regs from address arithmetic (declared + * int64_t); __builtin_memcpy takes void*, so cast to avoid + * -Wint-conversion. */ + cbuf_puts(&t->body, " __builtin_memcpy((void*)"); + c_emit_copy_addr(t, dst_addr); + cbuf_puts(&t->body, ", (void*)"); + c_emit_copy_addr(t, src_addr); + cbuf_puts(&t->body, ", "); + cbuf_put_u64(&t->body, (u64)m.size); + cbuf_puts(&t->body, ");\n"); +} + +static void c_emit_copy_addr(CTarget* t, Operand addr) { + char buf[24]; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + switch (addr.kind) { + case OPK_LOCAL: + c_ensure_local(t, addr.v.local, addr.type); + if (c_operand_is_ptr_typed(t, addr)) { + c_emit_operand(t, addr); + } else { + cbuf_putc(&t->body, '&'); + c_local_name(addr.v.local, buf, sizeof buf); + cbuf_puts(&t->body, buf); + } + return; + case OPK_GLOBAL: { + obj_sym_mark_referenced(t->obj, addr.v.global.sym); + cbuf_puts(&t->body, "((char*)&"); + cbuf_puts(&t->body, c_sym_name(t, addr.v.global.sym)); + if (addr.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, addr.v.global.addend); + } + cbuf_putc(&t->body, ')'); + return; + } + case OPK_INDIRECT: + c_emit_indirect_addr_expr(t, c_addr_mode(addr)); + return; + default: + compiler_panic(t->c, loc, + "C target: copy_bytes address operand kind %d not " + "supported", + (int)addr.kind); + } +} + +void c_emit_set_bytes(CTarget* t, Operand dst_addr, Operand byte_value, + AggregateAccess m) { + c_assert_no_index(t, dst_addr, "set_bytes dst"); + /* dst may be a plain integer local from address arithmetic (declared + * int64_t); __builtin_memset takes void*, so cast to avoid + * -Wint-conversion. */ + cbuf_puts(&t->body, " __builtin_memset((void*)"); + c_emit_copy_addr(t, dst_addr); + cbuf_puts(&t->body, ", (int)"); + c_emit_operand(t, byte_value); + cbuf_puts(&t->body, ", "); + cbuf_put_u64(&t->body, (u64)m.size); + cbuf_puts(&t->body, ");\n"); +} + +/* === TLS === + * + * Thread-local data is emitted as `_Thread_local _Alignas(A) uint8_t name[N];` + * during c_emit_data, and tls_addr_of spells `((char*)&name + addend)` with + * the requested pointer type. The host C compiler picks the TLS model. */ + +void c_emit_tls_addr_of(CTarget* t, Operand dst, ObjSymId sym, i64 addend); + +void c_emit_tls_addr_of(CTarget* t, Operand dst, ObjSymId sym, i64 addend) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: tls_addr_of dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + const char* nm = c_sym_name(t, sym); + /* RHS spells `(char*)&sym + addend` — pointer type that may not match + * dst.type; keep the bridge to cast through cleanly. */ + c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); + cbuf_puts(&t->body, "((char*)&"); + cbuf_puts(&t->body, nm); + if (addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, addend); + } + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); +} + +/* === bitfields === + * + * cfree CG flattens bitfields to (storage_type, byte_offset, bit_offset, + * bit_width) at the access boundary, so the C target never sees a C-level + * bitfield declaration. We extract/insert via explicit mask+shift on the + * underlying storage unit (a fixed-width unsigned int loaded through the + * usual address-deref path), which sidesteps the C bitfield ABI ambiguity + * entirely. */ + +void c_emit_bitfield_load(CTarget* t, Operand dst, Operand addr, + BitFieldAccess bf); +void c_emit_bitfield_store(CTarget* t, Operand addr, Operand src, + BitFieldAccess bf); + +/* Returns the unsigned C integer type matching the storage-unit byte size. */ +static const char* c_bf_storage_type(u32 size) { + switch (size) { + case 1: + return "uint8_t"; + case 2: + return "uint16_t"; + case 4: + return "uint32_t"; + case 8: + return "uint64_t"; + default: + return NULL; + } +} + +/* Spell an address expression for a backend-addressable lvalue operand. + * Unlike c_emit_operand, this never reads the object value; it materializes + * the address of the local/global/indirect storage itself. */ +static void c_emit_lvalue_addr_expr_raw(CTarget* t, Operand addr) { + char buf[24]; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + switch (addr.kind) { + case OPK_LOCAL: + cbuf_putc(&t->body, '&'); + c_ensure_local(t, addr.v.local, addr.type); + c_local_name(addr.v.local, buf, sizeof buf); + cbuf_puts(&t->body, buf); + return; + case OPK_GLOBAL: { + obj_sym_mark_referenced(t->obj, addr.v.global.sym); + const char* nm = c_sym_name(t, addr.v.global.sym); + cbuf_puts(&t->body, "((char*)&"); + cbuf_puts(&t->body, nm); + if (addr.v.global.addend != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_i64(&t->body, addr.v.global.addend); + } + cbuf_putc(&t->body, ')'); + return; + } + case OPK_INDIRECT: { + CAddrMode m = c_addr_mode(addr); + if ((u32)m.base >= t->local_cap || !t->local_declared[m.base]) { + compiler_panic(t->c, loc, + "C target: bitfield on undeclared base local v%u", + (unsigned)m.base); + } + cbuf_putc(&t->body, '('); + c_emit_indirect_addr_expr(t, m); + cbuf_putc(&t->body, ')'); + return; + } + default: + compiler_panic(t->c, loc, + "C target: bitfield address on operand kind %d not " + "supported", + (int)addr.kind); + } +} + +/* Spell `*(uintN_t*)((char*)addr + bf.storage_offset)` into the body. */ +static void c_bf_storage_lvalue(CTarget* t, Operand addr, BitFieldAccess bf, + const char* storage_ty) { + cbuf_puts(&t->body, "(*("); + cbuf_puts(&t->body, storage_ty); + cbuf_puts(&t->body, "*)((char*)"); + c_emit_lvalue_addr_expr_raw(t, addr); + if (bf.storage_offset != 0) { + cbuf_puts(&t->body, " + "); + cbuf_put_u64(&t->body, (u64)bf.storage_offset); + } + cbuf_puts(&t->body, "))"); +} + +void c_emit_bitfield_load(CTarget* t, Operand dst, Operand addr, + BitFieldAccess bf) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: bitfield_load dst must be LOCAL"); + } + c_assert_no_index(t, addr, "bitfield_load"); + if (bf.bit_width == 0) { + /* Zero-width — layout barrier only; nothing to load. Emit a no-op + * assignment so the dst local still gets a defined value. */ + c_ensure_local(t, dst.v.local, dst.type); + /* RHS is the literal 0 (int); narrowing to dst.type is fine. */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + cbuf_puts(&t->body, "0"); + c_emit_local_assign_close(t); + return; + } + const char* sty = c_bf_storage_type(bf.storage.size); + if (!sty) { + compiler_panic(t->c, loc, "C target: bitfield storage size %u unsupported", + (unsigned)bf.storage.size); + } + c_ensure_local(t, dst.v.local, dst.type); + /* RHS is the storage-width int from the mask/shift expression; bridge + * to dst.type so any signedness/width adjustment is explicit. */ + c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); + /* For signed bitfields, sign-extend via the standard shift-up / arith-shift- + * down trick on a signed integer of the storage width. For unsigned, mask + * the extracted bits. + * + * Storage is little-endian-bit-indexed on every cfree-supported target + * (LSB-first within a storage unit on x86_64/aarch64/rv64). */ + u32 sw = bf.storage.size * 8u; + if (bf.signed_) { + /* (int_storage_t)((storage << shl) >> shr) where: + * shl = sw - bit_width - bit_offset + * shr = sw - bit_width + * Then cast to dst type. */ + u32 shl = sw - (u32)bf.bit_width - (u32)bf.bit_offset; + u32 shr = sw - (u32)bf.bit_width; + cbuf_puts(&t->body, "(((int"); + cbuf_put_u64(&t->body, (u64)sw); + cbuf_puts(&t->body, "_t)("); + c_bf_storage_lvalue(t, addr, bf, sty); + cbuf_puts(&t->body, " << "); + cbuf_put_u64(&t->body, (u64)shl); + cbuf_puts(&t->body, ")) >> "); + cbuf_put_u64(&t->body, (u64)shr); + cbuf_puts(&t->body, ")"); + } else { + /* ((storage >> bit_offset) & ((1u << bit_width) - 1)) */ + u64 mask = (bf.bit_width >= 64) ? ~(u64)0 : (((u64)1 << bf.bit_width) - 1u); + cbuf_puts(&t->body, "(("); + c_bf_storage_lvalue(t, addr, bf, sty); + cbuf_puts(&t->body, " >> "); + cbuf_put_u64(&t->body, (u64)bf.bit_offset); + cbuf_puts(&t->body, ") & ("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")0x"); + static const char hex[] = "0123456789abcdef"; + int started = 0; + for (int sh = 60; sh >= 0; sh -= 4) { + u32 nib = (u32)((mask >> sh) & 0xfu); + if (nib || started || sh == 0) { + cbuf_putc(&t->body, hex[nib]); + started = 1; + } + } + cbuf_puts(&t->body, ")"); + } + c_emit_local_assign_close(t); +} + +void c_emit_bitfield_store(CTarget* t, Operand addr, Operand src, + BitFieldAccess bf) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + c_assert_no_index(t, addr, "bitfield_store"); + if (bf.bit_width == 0) return; /* zero-width: no-op */ + const char* sty = c_bf_storage_type(bf.storage.size); + if (!sty) { + compiler_panic(t->c, loc, "C target: bitfield storage size %u unsupported", + (unsigned)bf.storage.size); + } + u64 mask = (bf.bit_width >= 64) ? ~(u64)0 : (((u64)1 << bf.bit_width) - 1u); + /* *(uintN_t*)p = (*(uintN_t*)p & ~(mask << bit_offset)) | + * (((uintN_t)src & mask) << bit_offset); */ + cbuf_puts(&t->body, " "); + c_bf_storage_lvalue(t, addr, bf, sty); + cbuf_puts(&t->body, " = ("); + c_bf_storage_lvalue(t, addr, bf, sty); + cbuf_puts(&t->body, " & ~(("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")0x"); + static const char hex[] = "0123456789abcdef"; + int started = 0; + for (int sh = 60; sh >= 0; sh -= 4) { + u32 nib = (u32)((mask >> sh) & 0xfu); + if (nib || started || sh == 0) { + cbuf_putc(&t->body, hex[nib]); + started = 1; + } + } + cbuf_puts(&t->body, " << "); + cbuf_put_u64(&t->body, (u64)bf.bit_offset); + cbuf_puts(&t->body, ")) | (((("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")"); + c_emit_operand(t, src); + cbuf_puts(&t->body, ") & ("); + cbuf_puts(&t->body, sty); + cbuf_puts(&t->body, ")0x"); + started = 0; + for (int sh = 60; sh >= 0; sh -= 4) { + u32 nib = (u32)((mask >> sh) & 0xfu); + if (nib || started || sh == 0) { + cbuf_putc(&t->body, hex[nib]); + started = 1; + } + } + cbuf_puts(&t->body, ") << "); + cbuf_put_u64(&t->body, (u64)bf.bit_offset); + cbuf_puts(&t->body, ");\n"); +} + +/* === inline asm === + * + * Re-serialize cfree's asm-block IR (template + constraint-bound operands + + * clobbers) as GCC extended asm. The cfree CG already speaks GCC-style + * constraint strings ("r", "=r", "+m", "[name]constraint", matching "0"...), + * so we pass the template through and emit the constraint+operand pairs in + * order. */ + +void c_emit_asm_block(CTarget* t, const char* tmpl, const AsmConstraint* outs, + u32 no, Operand* oo, const AsmConstraint* ins, u32 ni, + const Operand* io, const Sym* clobs, u32 nc); + +static void c_emit_c_string_literal(CBuf* b, const char* s) { + cbuf_putc(b, '"'); + for (; *s; ++s) { + char ch = *s; + if (ch == '"' || ch == '\\') { + cbuf_putc(b, '\\'); + cbuf_putc(b, ch); + } else if (ch == '\n') { + cbuf_puts(b, "\\n"); + } else if (ch == '\r') { + cbuf_puts(b, "\\r"); + } else if (ch == '\t') { + cbuf_puts(b, "\\t"); + } else if ((unsigned char)ch < 0x20 || (unsigned char)ch >= 0x7f) { + static const char hex[] = "0123456789abcdef"; + cbuf_puts(b, "\\x"); + cbuf_putc(b, hex[((unsigned char)ch >> 4) & 0xfu]); + cbuf_putc(b, hex[(unsigned char)ch & 0xfu]); + } else { + cbuf_putc(b, ch); + } + } + cbuf_putc(b, '"'); +} + +void c_emit_asm_block(CTarget* t, const char* tmpl, const AsmConstraint* outs, + u32 no, Operand* oo, const AsmConstraint* ins, u32 ni, + const Operand* io, const Sym* clobs, u32 nc) { + for (u32 i = 0; i < no; ++i) c_assert_no_index(t, oo[i], "asm_block out"); + for (u32 i = 0; i < ni; ++i) c_assert_no_index(t, io[i], "asm_block in"); + cbuf_puts(&t->body, " __asm__ __volatile__ ("); + c_emit_c_string_literal(&t->body, tmpl ? tmpl : ""); + /* Outputs. */ + cbuf_puts(&t->body, " : "); + for (u32 i = 0; i < no; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + if (outs[i].name) { + cbuf_puts(&t->body, "["); + cbuf_puts(&t->body, pool_slice(t->c->global, outs[i].name).s); + cbuf_puts(&t->body, "] "); + } + c_emit_c_string_literal(&t->body, outs[i].str ? outs[i].str : ""); + cbuf_puts(&t->body, "("); + /* Outputs must be an lvalue. OPK_LOCAL is a plain C local; this + * works directly. OPK_LOCAL / OPK_INDIRECT also produce lvalues. */ + if (oo[i].kind == OPK_LOCAL) { + c_ensure_local(t, oo[i].v.local, oo[i].type); + char rb[24]; + c_local_name(oo[i].v.local, rb, sizeof rb); + cbuf_puts(&t->body, rb); + } else { + c_emit_addr_deref(t, oo[i], oo[i].type); + } + cbuf_puts(&t->body, ")"); + } + /* Inputs. cfree synthesizes a matching `"N"` input for every ASM_INOUT + * output (so its IR sees a fresh read), but gcc treats `+r` outputs as + * already serving the read role and rejects a redundant matching input. + * Drop those synthesized matches when the referenced output is `+`-tied. */ + cbuf_puts(&t->body, " : "); + int emitted_any = 0; + for (u32 i = 0; i < ni; ++i) { + const char* cs = ins[i].str ? ins[i].str : ""; + if (cs[0] >= '0' && cs[0] <= '9') { + u32 idx = (u32)(cs[0] - '0'); + if (idx < no && outs[idx].str && outs[idx].str[0] == '+') continue; + } + if (emitted_any) cbuf_puts(&t->body, ", "); + emitted_any = 1; + if (ins[i].name) { + cbuf_puts(&t->body, "["); + cbuf_puts(&t->body, pool_slice(t->c->global, ins[i].name).s); + cbuf_puts(&t->body, "] "); + } + c_emit_c_string_literal(&t->body, cs); + cbuf_puts(&t->body, "("); + c_emit_operand(t, io[i]); + cbuf_puts(&t->body, ")"); + } + /* Clobbers. */ + cbuf_puts(&t->body, " : "); + for (u32 i = 0; i < nc; ++i) { + if (i > 0) cbuf_puts(&t->body, ", "); + c_emit_c_string_literal(&t->body, pool_slice(t->c->global, clobs[i]).s); + } + cbuf_puts(&t->body, ");\n"); +} + +/* === load_const === + * + * Used by CG for non-integer literal pushes (mainly floats — + * `cfree_cg_push_float`). Bytes are the target's ABI encoding of the value; we + * copy them into the dst local via a static const byte array and + * __builtin_memcpy so any host C compiler sees the same bit pattern. */ + +void c_emit_load_const(CTarget* t, Operand dst, ConstBytes cb); + +void c_emit_load_const(CTarget* t, Operand dst, ConstBytes cb) { + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + if (dst.kind != OPK_LOCAL) { + compiler_panic(t->c, loc, "C target: load_const dst must be LOCAL"); + } + c_ensure_local(t, dst.v.local, dst.type); + char buf[24]; + c_local_name(dst.v.local, buf, sizeof buf); + u32 id = ++t->next_tmp; + cbuf_puts(&t->body, " { static const uint8_t __k"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, "["); + cbuf_put_u64(&t->body, (u64)cb.size); + cbuf_puts(&t->body, "] = {"); + static const char hex[] = "0123456789abcdef"; + for (u32 i = 0; i < cb.size; ++i) { + if (i > 0) cbuf_putc(&t->body, ','); + cbuf_puts(&t->body, "0x"); + cbuf_putc(&t->body, hex[(cb.bytes[i] >> 4) & 0xfu]); + cbuf_putc(&t->body, hex[cb.bytes[i] & 0xfu]); + } + cbuf_puts(&t->body, "}; __builtin_memcpy(&"); + cbuf_puts(&t->body, buf); + cbuf_puts(&t->body, ", __k"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, ", "); + cbuf_put_u64(&t->body, (u64)cb.size); + cbuf_puts(&t->body, "); }\n"); +} + +/* === atomics === + * + * Lowered to gcc/clang's `__atomic_*` generic builtins. The host compiler + * picks the inline sequence vs. libcall and applies the requested memory + * order. cfree's MemOrder enum aligns 1-1 with the `__ATOMIC_*` constants. */ + +static const char* c_memorder_token(MemOrder o) { + switch (o) { + case MO_RELAXED: + return "__ATOMIC_RELAXED"; + case MO_CONSUME: + return "__ATOMIC_CONSUME"; + case MO_ACQUIRE: + return "__ATOMIC_ACQUIRE"; + case MO_RELEASE: + return "__ATOMIC_RELEASE"; + case MO_ACQ_REL: + return "__ATOMIC_ACQ_REL"; + case MO_SEQ_CST: + return "__ATOMIC_SEQ_CST"; + } + return "__ATOMIC_SEQ_CST"; +} + +void c_emit_atomic_load(CTarget* t, Operand dst, Operand addr, MemAccess m, + MemOrder o); +void c_emit_atomic_store(CTarget* t, Operand addr, Operand src, MemAccess m, + MemOrder o); +void c_emit_atomic_rmw(CTarget* t, AtomicOp op, Operand dst, Operand addr, + Operand val, MemAccess m, MemOrder o); +void c_emit_atomic_cas(CTarget* t, Operand prior, Operand ok, Operand addr, + Operand expected, Operand desired, MemAccess m, + MemOrder so, MemOrder fo); +void c_emit_fence(CTarget* t, MemOrder o); + +void c_emit_atomic_load(CTarget* t, Operand dst, Operand addr, MemAccess m, + MemOrder o) { + (void)m; + c_assert_no_index(t, addr, "atomic_load"); + c_ensure_local(t, dst.v.local, dst.type); + /* __atomic_load_n returns a value of the pointed-to type (dst.type). */ + c_emit_local_assign_open(t, dst.v.local, dst.type); + cbuf_puts(&t->body, "__atomic_load_n(("); + c_emit_type(t, &t->body, dst.type); + cbuf_puts(&t->body, "*)"); + c_emit_operand(t, addr); + cbuf_puts(&t->body, ", "); + cbuf_puts(&t->body, c_memorder_token(o)); + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); +} + +void c_emit_atomic_store(CTarget* t, Operand addr, Operand src, MemAccess m, + MemOrder o) { + (void)m; + c_assert_no_index(t, addr, "atomic_store"); + cbuf_puts(&t->body, " __atomic_store_n(("); + c_emit_type(t, &t->body, src.type); + cbuf_puts(&t->body, "*)"); + c_emit_operand(t, addr); + cbuf_puts(&t->body, ", "); + c_emit_operand_as(t, src, src.type); + cbuf_puts(&t->body, ", "); + cbuf_puts(&t->body, c_memorder_token(o)); + cbuf_puts(&t->body, ");\n"); +} + +static const char* c_atomic_op_builtin(AtomicOp op) { + switch (op) { + case AO_XCHG: + return "__atomic_exchange_n"; + case AO_ADD: + return "__atomic_fetch_add"; + case AO_SUB: + return "__atomic_fetch_sub"; + case AO_AND: + return "__atomic_fetch_and"; + case AO_OR: + return "__atomic_fetch_or"; + case AO_XOR: + return "__atomic_fetch_xor"; + case AO_NAND: + return "__atomic_fetch_nand"; + } + return NULL; +} + +void c_emit_atomic_rmw(CTarget* t, AtomicOp op, Operand dst, Operand addr, + Operand val, MemAccess m, MemOrder o) { + (void)m; + SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; + c_assert_no_index(t, addr, "atomic_rmw"); + const char* fn = c_atomic_op_builtin(op); + if (!fn) { + compiler_panic(t->c, loc, "C target: unknown atomic op %d", (int)op); + } + c_ensure_local(t, dst.v.local, dst.type); + /* __atomic_fetch_* returns the prior value of the pointed-to type. */ + c_emit_local_assign_open(t, dst.v.local, val.type); + cbuf_puts(&t->body, fn); + cbuf_puts(&t->body, "(("); + c_emit_type(t, &t->body, val.type); + cbuf_puts(&t->body, "*)"); + c_emit_operand(t, addr); + cbuf_puts(&t->body, ", "); + c_emit_operand_as(t, val, val.type); + cbuf_puts(&t->body, ", "); + cbuf_puts(&t->body, c_memorder_token(o)); + cbuf_puts(&t->body, ")"); + c_emit_local_assign_close(t); +} + +void c_emit_atomic_cas(CTarget* t, Operand prior, Operand ok, Operand addr, + Operand expected, Operand desired, MemAccess m, + MemOrder so, MemOrder fo) { + (void)m; + c_assert_no_index(t, addr, "atomic_cas"); + /* gcc's __atomic_compare_exchange_n needs a real lvalue holding the + * expected value because it is updated on failure. Materialize a scratch + * local with the compare type, then copy it out to the prior result. */ + c_ensure_local(t, prior.v.local, prior.type); + c_ensure_local(t, ok.v.local, ok.type); + u32 id = ++t->next_tmp; + cbuf_puts(&t->body, " { "); + c_emit_type(t, &t->body, prior.type); + cbuf_puts(&t->body, " __cas"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, " = "); + c_emit_operand_as(t, expected, prior.type); + cbuf_puts(&t->body, "; "); + char ok_name[24], prior_name[24]; + c_local_name(ok.v.local, ok_name, sizeof ok_name); + c_local_name(prior.v.local, prior_name, sizeof prior_name); + cbuf_puts(&t->body, ok_name); + cbuf_puts(&t->body, " = ("); + c_emit_type(t, &t->body, ok.type); + cbuf_puts(&t->body, ")__atomic_compare_exchange_n(("); + c_emit_type(t, &t->body, prior.type); + cbuf_puts(&t->body, "*)"); + c_emit_operand(t, addr); + cbuf_puts(&t->body, ", &__cas"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, ", "); + c_emit_operand_as(t, desired, prior.type); + cbuf_puts(&t->body, ", 0, "); + cbuf_puts(&t->body, c_memorder_token(so)); + cbuf_puts(&t->body, ", "); + cbuf_puts(&t->body, c_memorder_token(fo)); + cbuf_puts(&t->body, "); "); + /* prior local = __cas; */ + cbuf_puts(&t->body, prior_name); + cbuf_puts(&t->body, " = __cas"); + cbuf_put_u64(&t->body, (u64)id); + cbuf_puts(&t->body, "; }\n"); +} + +void c_emit_fence(CTarget* t, MemOrder o) { + cbuf_puts(&t->body, " __atomic_thread_fence("); + cbuf_puts(&t->body, c_memorder_token(o)); + cbuf_puts(&t->body, ");\n"); +} + +/* === set_loc === */ + +static void cbuf_put_line_filename(CBuf* b, CfreeSlice s) { + size_t i; + cbuf_putc(b, '"'); + for (i = 0; i < s.len; ++i) { + { + unsigned char ch = (unsigned char)s.s[i]; + switch (ch) { + case '\\': + case '"': + cbuf_putc(b, '\\'); + cbuf_putc(b, (char)ch); + break; + case '\n': + cbuf_puts(b, "\\n"); + break; + case '\r': + cbuf_puts(b, "\\r"); + break; + case '\t': + cbuf_puts(b, "\\t"); + break; + default: + cbuf_putc(b, (char)ch); + break; + } + } + } + cbuf_putc(b, '"'); +} + +void c_emit_set_loc(CTarget* t, SrcLoc l) { + CfreeSlice file; + + if (!t->cur_fn || l.file_id == 0 || l.line == 0) return; + if (t->have_emitted_loc && t->emitted_loc.file_id == l.file_id && + t->emitted_loc.line == l.line) { + return; + } + + file = cfree_compiler_file_name(t->c, l.file_id); + if (!file.len) return; + + cbuf_puts(&t->body, "#line "); + cbuf_put_u64(&t->body, (u64)l.line); + cbuf_putc(&t->body, ' '); + cbuf_put_line_filename(&t->body, file); + cbuf_putc(&t->body, '\n'); + + t->emitted_loc = l; + t->have_emitted_loc = 1; +} + +/* === data emission === + * + * Walks the ObjBuilder's symbol table at finalize and emits a C declaration + * for every data object — defined or extern. Bytes are emitted verbatim as a + * `uint8_t name[N] = { 0x.., ... }` initializer. Relocations targeting bytes + * inside a defined symbol are spelled as runtime fixups in a constructor; this + * covers both same-TU and cross-TU references uniformly and avoids the C + * static-initializer restrictions on non-constant addresses. + * + * The host C compiler re-applies the Mach-O leading-underscore on link, so the + * C source uses the cfree linker name minus the `_` prefix (matching + * c_sym_name elsewhere in this file). */ + +static int c_is_data_section(const Section* sec) { + if (!sec) return 0; + switch (sec->kind) { + case SEC_DATA: + case SEC_RODATA: + case SEC_BSS: + return 1; + case SEC_OTHER: + /* User-named sections holding allocated data (e.g. `.text.hot` would + * be EXEC, but a custom data section is just SF_ALLOC). */ + return (sec->flags & SF_ALLOC) && !(sec->flags & SF_EXEC); + default: + return 0; + } +} + +static void c_emit_link_attrs(CBuf* b, const ObjSym* os) { + if (os->bind == SB_WEAK) cbuf_puts(b, "__attribute__((weak)) "); + if (os->vis == SV_HIDDEN) { + cbuf_puts(b, "__attribute__((visibility(\"hidden\"))) "); + } else if (os->vis == SV_PROTECTED) { + cbuf_puts(b, "__attribute__((visibility(\"protected\"))) "); + } +} + +/* Reads `len` bytes starting at `ofs` from the section's byte buffer. The + * Section uses a chunked Buf; buf_read does the splice for us. */ +static void c_read_section_bytes(const Section* sec, u32 ofs, u8* out, + size_t len) { + buf_read(&sec->bytes, ofs, out, len); +} + +static void c_emit_data_bytes(CBuf* b, const u8* bytes, size_t n) { + cbuf_puts(b, " = {"); + for (size_t i = 0; i < n; ++i) { + if (i > 0) cbuf_putc(b, ','); + if ((i & 15u) == 0) cbuf_puts(b, "\n "); + cbuf_puts(b, "0x"); + static const char hex[] = "0123456789abcdef"; + cbuf_putc(b, hex[(bytes[i] >> 4) & 0xfu]); + cbuf_putc(b, hex[bytes[i] & 0xfu]); + } + cbuf_puts(b, "\n }"); +} + +/* Mach-O TLS support: the user-visible SK_TLS symbol is a 24-byte TLV + * descriptor in __DATA,__thread_vars, and the actual initial bytes live in + * a synthesized `<name>$tlv$init` sym in __thread_data (or __thread_bss). + * The descriptor carries an R_ABS64 reloc at offset +16 pointing at that + * init sym. For C-source emission we don't care about the descriptor at all + * — we just emit `_Thread_local` with the init sym's bytes and let the host + * C compiler synthesize whatever TLV plumbing it needs. */ + +/* Find the data init sym referenced by a Mach-O TLS descriptor at + * `desc_base` in section `desc_sec`. Looks for an R_ABS64 reloc at + * `desc_base + 16`. Returns OBJ_SYM_NONE if not found. */ +static ObjSymId c_macho_tls_find_init(CTarget* t, ObjSecId desc_sec, + u32 desc_base) { + u32 total = obj_reloc_total(t->obj); + for (u32 i = 0; i < total; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + if (r->section_id != desc_sec) continue; + if (r->offset != desc_base + 16u) continue; + return r->sym; + } + return OBJ_SYM_NONE; +} + +/* Returns 1 if the section is __DATA,__thread_vars (the descriptor section + * on Mach-O). Compared by interned Sym id. */ +static int c_sec_name_is_macho_tvars(CTarget* t, const Section* sec) { + if (!sec) return 0; + Sym tvars = + pool_intern_slice(t->c->global, SLICE_LIT("__DATA,__thread_vars")); + return sec->name == tvars; +} + +/* Returns 1 if any relocation falls into the half-open range [base, base+size) + * of section `sec_id` (i.e. patches the bytes of this symbol). */ +static int c_sym_has_relocs(CTarget* t, ObjSecId sec_id, u32 base, u32 size) { + u32 total = obj_reloc_total(t->obj); + for (u32 i = 0; i < total; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + if (r->section_id != sec_id) continue; + if (r->offset >= base && r->offset < base + size) return 1; + } + return 0; +} + +/* Emit one data symbol: extern declaration if undef, otherwise the full + * definition with bytes. Function symbols are skipped — those go through the + * forwards path. */ +static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) { + if (c_is_local_static_sym(t, id)) return; + if (os->kind == SK_FUNC || os->kind == SK_IFUNC) return; + if (os->kind == SK_SECTION || os->kind == SK_FILE) return; + /* On Mach-O, obj_tls.c synthesizes `__tlv_bootstrap` as an SK_UNDEF + * extern for the TLV descriptor's first field. The C target delegates all + * TLS lowering to the host compiler via `_Thread_local`, so this + * descriptor-time-only symbol has no place in the emitted source. */ + if (os->kind == SK_UNDEF && t->c->target.obj == CFREE_OBJ_MACHO) { + const ObjBuilder* ob = t->obj; + if (id == obj_tlv_bootstrap_get(ob)) return; + } + const char* nm = c_sym_name(t, id); + CBuf* b = &t->data_defs; + /* SK_TLS user-visible syms need a _Thread_local prefix. On ELF the sym + * lives in .tdata/.tbss with the right bytes/size and our normal data + * path handles them once we set the qualifier. On Mach-O the user-visible + * sym is the 24-byte TLV descriptor — its bytes are not the user's data, + * so we can't faithfully reproduce it in C; bail to a SKIP. */ + int is_tls = (os->kind == SK_TLS); + + /* Extern (undefined) data — only declare if referenced. We can't readily + * distinguish "referenced as data" from "referenced as func address" here, + * so declare it as `extern uint8_t name[];` only if it was actually + * referenced from somewhere; otherwise it'd produce unused warnings. The + * obj symbol's `referenced` bit is exactly the right signal. */ + /* SK_TLS with no defining section = extern TLS — falls through to the + * undef branch below with the `_Thread_local` qualifier. */ + /* Extern: SK_UNDEF, or any other defined-kind sym that the producer + * marked as having no defining section (the C frontend uses SK_OBJ + + * section=NONE for `extern T x __attribute__((weak));`). */ + int is_extern = (os->kind == SK_UNDEF) || + (os->kind != SK_COMMON && os->section_id == OBJ_SEC_NONE); + if (is_extern) { + /* Always declare extern data syms in C source: the host cc tolerates + * unused externs, and the ObjSym::referenced bit isn't reliably set on + * syms the C target only addresses by writing the name into the source + * (no relocation gets emitted against them). + * + * Weak externs need different attributes per object format: on Mach-O + * the `weak` attribute requires a definition; the right spelling for an + * undefined weak ref is `__attribute__((weak_import))`. On ELF/PE the + * existing `weak` attribute works as expected. */ + if (os->bind == SB_WEAK) { + if (t->c->target.obj == CFREE_OBJ_MACHO) { + cbuf_puts(b, "__attribute__((weak_import)) "); + } else { + cbuf_puts(b, "__attribute__((weak)) "); + } + } + if (os->vis == SV_HIDDEN) { + cbuf_puts(b, "__attribute__((visibility(\"hidden\"))) "); + } else if (os->vis == SV_PROTECTED) { + cbuf_puts(b, "__attribute__((visibility(\"protected\"))) "); + } + cbuf_puts(b, "extern "); + if (is_tls) cbuf_puts(b, "_Thread_local "); + cbuf_puts(b, "uint8_t "); + cbuf_puts(b, nm); + cbuf_puts(b, "[];\n"); + return; + } + if (is_tls && t->c->target.obj == CFREE_OBJ_MACHO) { + /* Mach-O splits TLS across two object-file symbols (see obj_tls.c): the + * user-visible sym is a 24-byte TLV descriptor in + * __DATA,__thread_vars; the actual initial bytes live in a synthesized + * `<name>$tlv$init` sym in __DATA,__thread_data (or __thread_bss). For + * C source emission we don't need either of those — `_Thread_local` + * delegates to the host C compiler, which builds its own descriptor. + * + * We use the descriptor sym as the carrier (its name is what user code + * references) and pull the initial bytes/size/alignment from the init + * sym, found via the R_ABS64 reloc at descriptor offset +16. The init + * sym is skipped in its own iteration. */ + const Section* desc_sec = obj_section_get(t->obj, os->section_id); + if (c_sec_name_is_macho_tvars(t, desc_sec)) { + ObjSymId init_id = + c_macho_tls_find_init(t, os->section_id, (u32)os->value); + if (init_id == OBJ_SYM_NONE) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: Mach-O TLS descriptor missing init reloc"); + } + const ObjSym* init_os = obj_symbol_get(t->obj, init_id); + if (!init_os || init_os->section_id == OBJ_SEC_NONE) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: Mach-O TLS init sym not defined"); + } + const Section* init_sec = obj_section_get(t->obj, init_os->section_id); + u32 init_base = (u32)init_os->value; + u32 init_size = (u32)init_os->size; + /* TLS data with relocations would need the constructor-fixup path + * (and we'd have to rewrite the reloc target's section/offset to + * the descriptor's name in the emitted C). No test currently + * exercises this; surface it as a clear panic-as-skip if we hit it. */ + if (c_sym_has_relocs(t, init_os->section_id, init_base, init_size)) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: Mach-O TLS with pointer init not yet " + "supported"); + } + if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); + cbuf_puts(b, "_Thread_local "); + c_emit_link_attrs(b, os); + cbuf_puts(b, "__attribute__((unused)) "); + cbuf_puts(b, "_Alignas("); + cbuf_put_u64(b, init_sec->align ? init_sec->align : 1); + cbuf_puts(b, ") uint8_t "); + cbuf_puts(b, nm); + cbuf_puts(b, "["); + cbuf_put_u64(b, init_size ? init_size : 1); + cbuf_puts(b, "]"); + if (init_sec->kind == SEC_BSS || init_sec->sem == SSEM_NOBITS || + init_size == 0) { + cbuf_puts(b, ";\n"); + } else { + Heap* h = t->c->ctx->heap; + u8* bytes = (u8*)h->alloc(h, init_size, 1); + if (!bytes) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: oom on TLS init bytes"); + } + c_read_section_bytes(init_sec, init_base, bytes, init_size); + c_emit_data_bytes(b, bytes, init_size); + h->free(h, bytes, init_size); + cbuf_puts(b, ";\n"); + } + return; + } + /* Not the descriptor: this is the synthesized `<name>$tlv$init` data + * sym (or a __thread_ptrs entry). The descriptor case above already + * emitted the user-facing _Thread_local; nothing more to do. */ + return; + } + if (os->kind == SK_COMMON) { + /* Common — uninitialized, with explicit alignment. Emit as + * tentative-definition (`uint8_t name[size];` at file scope), which C + * treats as a common-style definition under -fcommon. */ + cbuf_puts(b, "__attribute__((unused)) _Alignas("); + cbuf_put_u64(b, os->common_align ? os->common_align : 1); + cbuf_puts(b, ") uint8_t "); + cbuf_puts(b, nm); + cbuf_puts(b, "["); + cbuf_put_u64(b, os->size); + cbuf_puts(b, "];\n"); + return; + } + if (os->section_id == OBJ_SEC_NONE) return; + const Section* sec = obj_section_get(t->obj, os->section_id); + if (!c_is_data_section(sec)) return; + u32 base = (u32)os->value; + u32 size = (u32)os->size; + u32 nrelocs = 0; + u32 total_relocs = obj_reloc_total(t->obj); + for (u32 i = 0; i < total_relocs; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + if (r->section_id == os->section_id && r->offset >= base && + r->offset < base + size) { + nrelocs++; + } + } + + Heap* h = t->c->ctx->heap; + const Reloc** rs = NULL; + if (nrelocs) { + rs = (const Reloc**)h->alloc(h, nrelocs * sizeof(const Reloc*), 1); + u32 j = 0; + for (u32 i = 0; i < total_relocs; ++i) { + const Reloc* r = obj_reloc_at(t->obj, i); + if (r->section_id == os->section_id && r->offset >= base && + r->offset < base + size) { + rs[j++] = r; + } + } + for (u32 i = 1; i < nrelocs; ++i) { + const Reloc* tmp = rs[i]; + u32 k = i; + while (k > 0 && rs[k - 1]->offset > tmp->offset) { + rs[k] = rs[k - 1]; + k--; + } + rs[k] = tmp; + } + } + + cbuf_puts(b, "struct "); + if (nrelocs > 0) cbuf_puts(b, "__attribute__((packed)) "); + cbuf_puts(b, "__cfree_data_"); + cbuf_puts(b, nm); + cbuf_puts(b, " {\n"); + + if (nrelocs == 0) { + cbuf_puts(b, " uint8_t raw["); + cbuf_put_u64(b, size ? size : 1); + cbuf_puts(b, "];\n"); + } else { + u32 cur = base; + for (u32 i = 0; i < nrelocs; ++i) { + const Reloc* r = rs[i]; + if (r->offset > cur) { + cbuf_puts(b, " uint8_t chunk_"); + cbuf_put_u64(b, i); + cbuf_puts(b, "["); + cbuf_put_u64(b, r->offset - cur); + cbuf_puts(b, "];\n"); + } + u32 width = (r->kind == R_ABS32) ? 4 : 8; + const char* ty = (width == 4) ? "uint32_t" : "void*"; + cbuf_puts(b, " "); + cbuf_puts(b, ty); + cbuf_puts(b, " ptr_"); + cbuf_put_u64(b, i); + cbuf_puts(b, ";\n"); + cur = r->offset + width; + } + if (cur < base + size) { + cbuf_puts(b, " uint8_t chunk_"); + cbuf_put_u64(b, nrelocs); + cbuf_puts(b, "["); + cbuf_put_u64(b, base + size - cur); + cbuf_puts(b, "];\n"); + } + } + cbuf_puts(b, "};\n"); + + if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); + if (is_tls) cbuf_puts(b, "_Thread_local "); + c_emit_link_attrs(b, os); + cbuf_puts(b, "__attribute__((unused)) "); + + int is_ro = (sec->kind == SEC_RODATA); + if (is_ro) cbuf_puts(b, "const "); + + cbuf_puts(b, "_Alignas("); + cbuf_put_u64(b, sec->align ? sec->align : 1); + cbuf_puts(b, ") struct __cfree_data_"); + cbuf_puts(b, nm); + cbuf_puts(b, " "); + cbuf_puts(b, nm); + + if (sec->kind == SEC_BSS || sec->sem == SSEM_NOBITS) { + cbuf_puts(b, ";\n"); + } else if (size == 0) { + cbuf_puts(b, " = {{0}};\n"); + } else { + cbuf_puts(b, " = {\n"); + u8* bytes = (u8*)h->alloc(h, size, 1); + c_read_section_bytes(sec, base, bytes, size); + + if (nrelocs == 0) { + cbuf_puts(b, " .raw = {"); + for (u32 i = 0; i < size; ++i) { + if (i > 0) cbuf_puts(b, ", "); + cbuf_put_u64(b, bytes[i]); + } + cbuf_puts(b, "}\n"); + } else { + u32 cur = base; + for (u32 i = 0; i < nrelocs; ++i) { + const Reloc* r = rs[i]; + if (r->offset > cur) { + cbuf_puts(b, " .chunk_"); + cbuf_put_u64(b, i); + cbuf_puts(b, " = {"); + for (u32 k = 0; k < r->offset - cur; ++k) { + if (k > 0) cbuf_puts(b, ", "); + cbuf_put_u64(b, bytes[cur - base + k]); + } + cbuf_puts(b, "},\n"); + } + + u32 width = (r->kind == R_ABS32) ? 4 : 8; + c_ensure_forward_decl(t, r->sym, 0); + const char* tgt = c_sym_name(t, r->sym); + const char* cast = (width == 4) ? "(uint32_t)(uintptr_t)" : "(void*)"; + + cbuf_puts(b, " .ptr_"); + cbuf_put_u64(b, i); + cbuf_puts(b, " = "); + cbuf_puts(b, cast); + cbuf_puts(b, "((char*)&"); + cbuf_puts(b, tgt); + if (r->addend != 0) { + cbuf_puts(b, " + "); + cbuf_put_i64(b, r->addend); + } + cbuf_puts(b, "),\n"); + cur = r->offset + width; + } + if (cur < base + size) { + cbuf_puts(b, " .chunk_"); + cbuf_put_u64(b, nrelocs); + cbuf_puts(b, " = {"); + for (u32 k = 0; k < base + size - cur; ++k) { + if (k > 0) cbuf_puts(b, ", "); + cbuf_put_u64(b, bytes[cur - base + k]); + } + cbuf_puts(b, "}\n"); + } + } + h->free(h, bytes, size); + cbuf_puts(b, "};\n"); + } + + if (nrelocs) h->free(h, (void*)rs, nrelocs * sizeof(const Reloc*)); +} + +static void c_emit_data(CTarget* t) { + ObjSymIter* it = obj_symiter_new(t->obj); + if (!it) return; + ObjSymEntry e; + while (obj_symiter_next(it, &e)) { + if (!e.sym) continue; + c_emit_data_symbol(t, e.id, e.sym); + } + obj_symiter_free(it); +} + +/* === finalize / destroy === */ + +void c_emit_finalize(CTarget* t) { + if (t->finalized) return; + t->finalized = 1; + c_emit_prologue(t); + if (t->need_stdarg) c_writer_puts(t, "#include <stdarg.h>\n"); + if (t->need_setjmp) c_writer_puts(t, "#include <setjmp.h>\n"); + if (t->need_stdarg || t->need_setjmp) c_writer_puts(t, "\n"); + if (t->typedefs.len) { + c_writer_write(t, t->typedefs.data, t->typedefs.len); + c_writer_puts(t, "\n"); + } + c_emit_data(t); + if (t->forwards.len) { + c_writer_write(t, t->forwards.data, t->forwards.len); + c_writer_puts(t, "\n"); + } + if (t->data_defs.len) { + c_writer_write(t, t->data_defs.data, t->data_defs.len); + c_writer_puts(t, "\n"); + } + if (t->body.len) c_writer_write(t, t->body.data, t->body.len); +} + +void c_emit_destroy(CTarget* t) { + Heap* h = t->c->ctx->heap; + cbuf_fini(&t->forwards); + cbuf_fini(&t->typedefs); + cbuf_fini(&t->data_defs); + cbuf_fini(&t->decls); + cbuf_fini(&t->body); + if (t->sym_forwarded) h->free(h, t->sym_forwarded, t->sym_forwarded_cap); + t->sym_forwarded = NULL; + t->sym_forwarded_cap = 0; + if (t->local_static_syms) { + h->free(h, t->local_static_syms, + t->local_static_syms_cap * sizeof(*t->local_static_syms)); + } + if (t->local_static_entries) { + h->free(h, t->local_static_entries, + t->local_static_entries_cap * sizeof(*t->local_static_entries)); + } + if (t->local_declared) h->free(h, t->local_declared, t->local_cap); + if (t->local_type) + h->free(h, t->local_type, t->local_cap * sizeof(CfreeCgTypeId)); + if (t->scopes) h->free(h, t->scopes, t->scopes_cap * sizeof(CScopeInfo)); + t->local_declared = NULL; + t->local_type = NULL; + t->scopes = NULL; + t->local_static_syms = NULL; + t->local_static_entries = NULL; + t->local_cap = 0; + t->scopes_cap = 0; + t->local_static_syms_cap = 0; + t->local_static_entries_cap = 0; + t->local_static_nsyms = 0; + t->local_static_nentries = 0; +} diff --git a/src/arch/c_target/c_emit.h b/src/arch/c_target/c_emit.h @@ -0,0 +1,256 @@ +#ifndef CFREE_C_TARGET_C_EMIT_H +#define CFREE_C_TARGET_C_EMIT_H + +/* C-source emission core. See doc/CBACKEND.md. + * + * This target is selected when CodeOptions.emit_c_source is set. It writes + * target-locked C source to CodeOptions.c_source_writer instead of object + * bytes. Operates with semantic temporary locals minted by CG. */ + +#include <cfree/core.h> + +#include "cg/cgtarget.h" +#include "core/core.h" + +typedef CGLocal CLocal; + +/* Heap-backed growable byte buffer. Used for the per-function declaration + * and body buffers; CG needs decls at function top but doesn't surface them + * before body emission, so we accumulate both and flush at func_end. */ +typedef struct CBuf { + Heap* heap; + u8* data; + size_t len; + size_t cap; +} CBuf; + +void cbuf_init(CBuf* b, Heap* h); +void cbuf_fini(CBuf* b); +void cbuf_reset(CBuf* b); +void cbuf_putc(CBuf* b, char c); +void cbuf_puts(CBuf* b, const char* s); +void cbuf_putn(CBuf* b, const char* s, size_t n); +void cbuf_put_i64(CBuf* b, i64 v); +void cbuf_put_u64(CBuf* b, u64 v); + +typedef struct CLocalStaticLabelEntry { + Label label; + i64 addend; + u8 has_label; + u8 pad[3]; +} CLocalStaticLabelEntry; + +typedef struct CTarget { + Compiler* c; + ObjBuilder* obj; + CfreeWriter* w; + + /* TU prologue (e.g. #include <stdint.h>) emitted once, on first + * function or finalize, whichever first. */ + u8 prologue_emitted; + u8 finalized; + u8 pad[2]; + + /* TU-wide forward declarations: one `RetT name(params);` line per function + * we've seen a definition or call for. Emitted just after the prologue so + * callers defined earlier in the TU still see the prototype, and calls to + * undefined-in-TU externs get a declaration too. */ + CBuf forwards; + /* Forward-decl dedup: which ObjSymIds have we already declared. Lazily + * grown bitmap indexed by ObjSymId. */ + u8* sym_forwarded; + u32 sym_forwarded_cap; + + /* TU-wide typedefs (records, arrays, function pointers, opaque storage). + * Emitted between prologue and forwards. */ + CBuf typedefs; + /* TU-wide data definitions and extern data declarations. Emitted between + * forwards and function bodies so functions can reference data symbols and + * data initializers can reference function forward decls. Populated at + * finalize by walking the ObjBuilder's data sections. */ + CBuf data_defs; + /* Per-type id state: 0 = not seen, 1 = inflight, 2 = emitted. */ + u8* type_state; + u32 type_state_cap; + /* Tracks whether <stdarg.h> needs to be emitted in the prologue. Set when + * c_typename first encounters VARARG_STATE. Flushed lazily at finalize. */ + u8 need_stdarg; + u8 need_setjmp; + u8 pad2[2]; + + /* Per-function buffers. Reset on func_end. */ + CBuf decls; + CBuf body; + + /* Function-local static data consumed from CG's narrow source-backend data + * hook. These symbols are emitted inside the owning function and skipped by + * the TU-wide object-data walker. */ + ObjSymId* local_static_syms; + u32 local_static_nsyms; + u32 local_static_syms_cap; + + CLocalStaticLabelEntry* local_static_entries; + u32 local_static_nentries; + u32 local_static_entries_cap; + ObjSymId local_static_sym; + CfreeCgTypeId local_static_type; + u64 local_static_count; + u64 local_static_offset; + u32 local_static_ptr_width; + u32 local_static_align; + u8 local_static_active; + u8 local_static_is_array; + u8 local_static_readonly; + u8 pad_local_static; + + /* Per-function local-decl tracking: for each local id seen, mark whether we + * have already emitted a declaration into `decls`. Sized by local_cap. + * Grown lazily as new local ids appear. */ + u8* local_declared; + CfreeCgTypeId* local_type; /* declared type for each semantic local */ + u32 local_cap; + + /* Splice bookmark: byte offset into body where the current function's body + * region starts (right after the open brace). func_end uses this to insert + * the per-function declarations between the signature and the body. */ + size_t fn_body_start; + + const CGFuncDesc* cur_fn; + + /* Most recent source line directive emitted into the current function body. + * Reset on func_begin; used by c_set_loc to avoid noisy duplicate #line + * directives. */ + SrcLoc emitted_loc; + u8 have_emitted_loc; + u8 pad_cur_loc[3]; + + /* Label minting: ids 1..next_label. 0 is reserved as LABEL_NONE. */ + u32 next_label; + u32 next_local; + + /* Stack of active scopes. CGScope handles are (scope_index + 1). */ + struct CScopeInfo* scopes; + u32 scopes_cap; + u32 nscopes; + + /* Monotone counter for synthesizing unique temporary names within a + * function (e.g. bitcast scratch). Reset on func_begin. */ + u32 next_tmp; + + /* Tracks whether the last emitted body statement was an unconditional + * terminator (`return`, `goto`, etc.) with no intervening label. When + * set, subsequent ret/jump/cmp_branch emissions are dropped — they're + * unreachable, and emitting them produces dead C that distracts from + * the live code. Reset on func_begin and label_place. */ + u8 last_was_terminator; + u8 pad3[3]; +} CTarget; + +typedef struct CScopeInfo { + u8 kind; /* ScopeKind */ + /* Set when the C target emitted a `for (;;) { ... }` wrapper around + * this scope. Drives jump/cmp_branch's translation of break/continue + * labels into C `break;`/`continue;` and scope_end's `}`. */ + u8 structured; + u8 pad[2]; + Label break_label; + Label continue_label; +} CScopeInfo; + +void c_emit_target_init(CTarget* t, Compiler* c, ObjBuilder* o, CfreeWriter* w); +CTarget* c_emit_target_new(Compiler* c, ObjBuilder* o, CfreeWriter* w); + +void c_emit_prologue(CTarget* t); +/* Ensure local `r` (typed `type`) has been declared. */ +void c_ensure_local(CTarget* t, CLocal r, CfreeCgTypeId type); +/* Get a stable C identifier for local r. Writes into caller-supplied buf. */ +void c_local_name(CLocal r, char* out, size_t cap); +/* Write the C type for a CG int/float/ptr type to `b`. */ +void c_emit_type(CTarget* t, CBuf* b, CfreeCgTypeId type); +/* Write operand expression to body (e.g. "v3", "(int32_t)42"). Supports + * OPK_LOCAL / OPK_IMM / OPK_GLOBAL. INDIRECT is only valid in + * lvalue positions and is emitted via load/store/addr_of paths. */ +void c_emit_operand(CTarget* t, Operand op); +/* Like c_emit_operand but wraps in an explicit signed/unsigned cast of the + * operand's type width. For integer ops where signedness affects semantics + * (UDIV/UREM, SHR_U, unsigned compares). Falls back to c_emit_operand when + * the operand is not integer-typed. */ +void c_emit_operand_signed(CTarget* t, Operand op, int signed_); + +/* Lookup the C linker name for an ObjSymId. Returns interned string. */ +const char* c_sym_name(CTarget* t, ObjSymId sym); + +/* Emit a forward declaration for `sym` (of function type `fn_type`) into + * the TU forwards buffer if not already done. Idempotent per sym. */ +void c_ensure_forward_decl(CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type); + +/* Write `n` bytes to t->w; panic on error. */ +void c_writer_write(CTarget* t, const void* data, size_t n); +void c_writer_puts(CTarget* t, const char* s); + +void c_emit_func_begin(CTarget*, const CGFuncDesc*); +void c_emit_func_end(CTarget*); +void c_emit_alias(CTarget*, ObjSymId, ObjSymId, CfreeCgTypeId); +void c_emit_ret(CTarget*, const CGLocal*, u32); +void c_emit_load_imm(CTarget*, Operand, i64); +void c_emit_load_const(CTarget*, Operand, ConstBytes); +void c_emit_copy(CTarget*, Operand, Operand); +void c_emit_binop(CTarget*, BinOp, Operand, Operand, Operand); +void c_emit_unop(CTarget*, UnOp, Operand, Operand); +void c_emit_cmp(CTarget*, CmpOp, Operand, Operand, Operand); +void c_emit_convert(CTarget*, ConvKind, Operand, Operand); +void c_emit_call(CTarget*, const CGCallDesc*); +const char* c_emit_tail_call_unrealizable_reason(CTarget*, const CGCallDesc*); +const char* c_emit_tail_call_unrealizable_reason_for(CTarget*, + const CGFuncDesc*, + const CGCallDesc*); +void c_emit_load(CTarget*, Operand, Operand, MemAccess); +void c_emit_store(CTarget*, Operand, Operand, MemAccess); +void c_emit_addr_of(CTarget*, Operand, Operand); +CGLocal c_emit_param(CTarget*, const CGParamDesc*); +CGLocal c_emit_local(CTarget*, const CGLocalDesc*); +void c_emit_param_bind(CTarget*, CGLocal, CfreeCgTypeId, u32); +void c_emit_local_addr(CTarget*, Operand, const CGLocalDesc*, CGLocal); +Label c_emit_label_new(CTarget*); +void c_emit_label_place(CTarget*, Label); +void c_emit_jump(CTarget*, Label); +void c_emit_cmp_branch(CTarget*, CmpOp, Operand, Operand, Label); +void c_emit_switch_(CTarget*, const CGSwitchDesc*); +void c_emit_indirect_branch(CTarget*, Operand, const Label*, u32); +void c_emit_load_label_addr(CTarget*, Operand, Label); +int c_emit_local_static_data_begin(CTarget*, const CGLocalStaticDataDesc*); +int c_emit_can_local_static_data(CTarget*, const CGLocalStaticDataDesc*); +void c_emit_local_static_data_write(CTarget*, const u8*, u64); +void c_emit_local_static_data_label_addr(CTarget*, Label, i64, u32, u32); +void c_emit_local_static_data_end(CTarget*); +CGScope c_emit_scope_begin(CTarget*, const CGScopeDesc*); +void c_emit_scope_else(CTarget*, CGScope); +void c_emit_scope_end(CTarget*, CGScope); +void c_emit_break_to(CTarget*, CGScope); +void c_emit_continue_to(CTarget*, CGScope); +void c_emit_set_loc(CTarget*, SrcLoc); +void c_emit_finalize(CTarget*); +void c_emit_destroy(CTarget*); +void c_emit_intrinsic(CTarget*, IntrinKind, Operand*, u32, const Operand*, u32); +void c_emit_alloca(CTarget*, Operand, Operand, u32); +void c_emit_va_start(CTarget*, Operand); +void c_emit_va_arg(CTarget*, Operand, Operand, CfreeCgTypeId); +void c_emit_va_end(CTarget*, Operand); +void c_emit_va_copy(CTarget*, Operand, Operand); +void c_emit_copy_bytes(CTarget*, Operand, Operand, AggregateAccess); +void c_emit_set_bytes(CTarget*, Operand, Operand, AggregateAccess); +void c_emit_asm_block(CTarget*, const char*, const AsmConstraint*, u32, + Operand*, const AsmConstraint*, u32, const Operand*, + const Sym*, u32); +void c_emit_bitfield_load(CTarget*, Operand, Operand, BitFieldAccess); +void c_emit_bitfield_store(CTarget*, Operand, Operand, BitFieldAccess); +void c_emit_tls_addr_of(CTarget*, Operand, ObjSymId, i64); +void c_emit_atomic_load(CTarget*, Operand, Operand, MemAccess, MemOrder); +void c_emit_atomic_store(CTarget*, Operand, Operand, MemAccess, MemOrder); +void c_emit_atomic_rmw(CTarget*, AtomicOp, Operand, Operand, Operand, MemAccess, + MemOrder); +void c_emit_atomic_cas(CTarget*, Operand, Operand, Operand, Operand, Operand, + MemAccess, MemOrder, MemOrder); +void c_emit_fence(CTarget*, MemOrder); + +#endif diff --git a/src/arch/c_target/cbuf.c b/src/arch/c_target/cbuf.c @@ -3,7 +3,7 @@ * declarations and body emission interleaved; we accumulate both and flush * at func_end. */ -#include "arch/c_target/internal.h" +#include "arch/c_target/c_emit.h" #include "core/heap.h" enum { CBUF_MIN_CAP = 256 }; diff --git a/src/arch/c_target/emit.c b/src/arch/c_target/emit.c @@ -1,3830 +0,0 @@ -/* C-source emission for the CgTarget vtable. See doc/CBACKEND.md. - * - * Output strategy - * --------------- - * Each function buffers two CBufs while CG walks the body: - * decls — variable declarations: " long long v3;\n" - * body — TU-wide running output; we accumulate signature/body/closing-brace - * across all functions; func_end splices decls in after the open - * brace using the recorded fn_body_start bookmark. - * - * c_finalize flushes a tiny prologue + body to the writer. - * - * Local declaration is lazy: every operand emit goes through c_ensure_local, - * which appends one declaration for each semantic local. */ - -#include <stdio.h> -#include <string.h> - -#include "arch/c_target/internal.h" -#include "cg/type.h" -#include "core/core.h" -#include "core/heap.h" -#include "core/pool.h" -#include "core/slice.h" -#include "obj/obj.h" - -/* Forward decls. */ -static void c_ensure_typedef(CTarget* t, CfreeCgTypeId tid); -static const char* c_typedef_name(CTarget* t, CfreeCgTypeId tid); -static const char* c_typename(CTarget* t, CfreeCgTypeId type); -static CfreeCgTypeId c_local_type_or_panic(CTarget* t, CGLocal local); -static void c_ensure_tuple_typedef(CTarget* t, const CfreeCgTypeId* types, - u32 ntypes); -static void c_emit_tuple_type_name(CTarget* t, CBuf* b, - const CfreeCgTypeId* types, u32 ntypes); -static Operand c_op_local(CGLocal local, CfreeCgTypeId type); -static int c_type_is_aggregate(CTarget* t, CfreeCgTypeId type); -static int c_type_is_bool(CTarget* t, CfreeCgTypeId type); -static int c_type_is_ptr(CTarget* t, CfreeCgTypeId type); -static int c_operand_is_ptr_typed(CTarget* t, Operand op); -static void c_emit_addr_deref(CTarget* t, Operand addr, - CfreeCgTypeId access_type); -static void c_emit_copy_addr(CTarget* t, Operand addr); -CGLocal c_local(CgTarget* T, const CGLocalDesc* d); -static void c_ensure_forward_decl_with_results(CTarget* t, ObjSymId sym, - CfreeCgTypeId fn_type, - const CfreeCgTypeId* result_types, - u32 nresults); -/* Private accessor on ObjBuilder (defined in obj/obj.c, not in obj.h). - * Same forward-decl trick as obj_tls.c uses. */ -ObjSymId obj_tlv_bootstrap_get(const ObjBuilder*); - -/* === Writer helpers === */ - -void c_writer_write(CTarget* t, const void* data, size_t n) { - CfreeStatus st = cfree_writer_write(t->w, data, n); - if (st != CFREE_OK) { - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - compiler_panic(t->c, loc, "C target: writer error %d", (int)st); - } -} - -void c_writer_puts(CTarget* t, const char* s) { - size_t n = 0; - while (s[n]) ++n; - c_writer_write(t, s, n); -} - -/* === CLocal / type emission === */ - -static const char* c_int_type_name_for_width(u32 width, int signed_) { - switch (width) { - case 1: - case 8: - return signed_ ? "int8_t" : "uint8_t"; - case 16: - return signed_ ? "int16_t" : "uint16_t"; - case 32: - return signed_ ? "int32_t" : "uint32_t"; - case 64: - return signed_ ? "int64_t" : "uint64_t"; - case 128: - return signed_ ? "__int128" : "unsigned __int128"; - default: - return NULL; - } -} - -/* Returns the integer width for sign-aware emission. 0 if the type isn't a - * fixed-width integer (float, ptr, void, aggregate). */ -static u32 c_int_width_for_signedness(CTarget* t, CfreeCgTypeId type) { - if (type == CFREE_CG_TYPE_NONE) return 0; - CfreeCgTypeId u = api_unalias_type(t->c, type); - const CgType* ty = cg_type_get(t->c, u); - if (!ty) return 0; - if (ty->kind == CFREE_CG_TYPE_INT) return ty->integer.width; - if (ty->kind == CFREE_CG_TYPE_BOOL) return 32; /* bool maps to int32_t */ - return 0; -} - -/* === Typedef machinery === - * - * Composite types (records, arrays, function types) are emitted as opaque - * byte-storage typedefs in a TU-wide typedefs section. The typedef name is - * `__ty_<id>` keyed on the unaliased type id; this is stable for the - * compiler instance. - * - * For records and arrays the typedef wraps a single `_Alignas(A) uint8_t - * raw[N];` member, so all field/element access is mediated by the existing - * `(*(T*)((char*)addr + ofs))` path. This sidesteps any ABI ambiguity (C - * bitfield rules, array decay, packed/aligned attribute interactions) and - * keeps types orthogonal to access patterns. - * - * For function types we emit a function-pointer typedef `R (*__ty_N)(...)`, - * used for indirect calls and function-pointer-typed values. */ - -static void c_grow_type_state(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->type_state_cap ? t->type_state_cap : 32; - while (newcap < needed) newcap *= 2; - u8* nd = (u8*)h->realloc(h, t->type_state, t->type_state_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->type_state_cap; i < newcap; ++i) nd[i] = 0; - t->type_state = nd; - t->type_state_cap = newcap; -} - -static const char* c_typedef_name(CTarget* t, CfreeCgTypeId tid) { - CfreeCgTypeId u = api_unalias_type(t->c, tid); - char buf[32]; - int n = snprintf(buf, sizeof buf, "__ty_%u", (unsigned)u); - Sym s = pool_intern_slice(t->c->global, (Slice){.s = buf, .len = (size_t)n}); - return pool_slice(t->c->global, s).s; -} - -/* Forward decl. */ -static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, - const CgType* ty); - -static void c_ensure_typedef(CTarget* t, CfreeCgTypeId tid) { - CfreeCgTypeId u = api_unalias_type(t->c, tid); - if ((u32)u >= t->type_state_cap) c_grow_type_state(t, (u32)u + 1u); - if (t->type_state[u] >= 2) return; - if (t->type_state[u] == 1) return; /* cyclic — emit forward-only */ - t->type_state[u] = 1; - const CgType* ty = cg_type_get(t->c, u); - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!ty) - compiler_panic(t->c, loc, "C target: unknown type id %u", (unsigned)u); - switch (ty->kind) { - case CFREE_CG_TYPE_FUNC: - c_emit_typedef_for_func(t, u, ty); - break; - case CFREE_CG_TYPE_RECORD: { - /* Recurse on field types so any composite-typed field has its - * typedef emitted first. (Records-by-value are accessed only via - * pointer arithmetic in cfree CG, but emitting deps first keeps the - * output readable and stable.) */ - for (u32 i = 0; i < ty->record.nfields; ++i) { - if (!(ty->record.fields[i].flags & CFREE_CG_FIELD_BITFIELD)) { - CfreeCgTypeId ft = ty->record.fields[i].type; - CfreeCgTypeId ftu = api_unalias_type(t->c, ft); - const CgType* fty = cg_type_get(t->c, ftu); - if (fty && (fty->kind == CFREE_CG_TYPE_RECORD || - fty->kind == CFREE_CG_TYPE_ARRAY || - fty->kind == CFREE_CG_TYPE_FUNC)) { - c_ensure_typedef(t, ftu); - } - } - } - cbuf_puts(&t->typedefs, "typedef struct { _Alignas("); - cbuf_put_u64(&t->typedefs, (u64)cg_type_align(t->c, u)); - cbuf_puts(&t->typedefs, ") uint8_t raw["); - cbuf_put_u64(&t->typedefs, cg_type_size(t->c, u)); - cbuf_puts(&t->typedefs, "]; } __ty_"); - cbuf_put_u64(&t->typedefs, (u64)u); - cbuf_puts(&t->typedefs, ";\n"); - break; - } - case CFREE_CG_TYPE_ARRAY: { - CfreeCgTypeId eu = api_unalias_type(t->c, ty->array.elem); - const CgType* ety = cg_type_get(t->c, eu); - if (ety && (ety->kind == CFREE_CG_TYPE_RECORD || - ety->kind == CFREE_CG_TYPE_ARRAY || - ety->kind == CFREE_CG_TYPE_FUNC)) { - c_ensure_typedef(t, eu); - } - cbuf_puts(&t->typedefs, "typedef struct { _Alignas("); - cbuf_put_u64(&t->typedefs, (u64)cg_type_align(t->c, u)); - cbuf_puts(&t->typedefs, ") uint8_t raw["); - cbuf_put_u64(&t->typedefs, cg_type_size(t->c, u)); - cbuf_puts(&t->typedefs, "]; } __ty_"); - cbuf_put_u64(&t->typedefs, (u64)u); - cbuf_puts(&t->typedefs, ";\n"); - break; - } - default: - compiler_panic(t->c, loc, - "C target: c_ensure_typedef on non-composite kind %d", - (int)ty->kind); - } - t->type_state[u] = 2; -} - -static void c_emit_typedef_for_func(CTarget* t, CfreeCgTypeId tid, - const CgType* ty) { - /* Emit recursively for return and param types if they're composites. */ - CfreeCgTypeId ret = api_unalias_type(t->c, ty->func.ret); - const CgType* rty = cg_type_get(t->c, ret); - if (rty && - (rty->kind == CFREE_CG_TYPE_RECORD || rty->kind == CFREE_CG_TYPE_ARRAY || - rty->kind == CFREE_CG_TYPE_FUNC)) { - c_ensure_typedef(t, ret); - } - for (u32 i = 0; i < ty->func.nparams; ++i) { - CfreeCgTypeId pt = api_unalias_type(t->c, ty->func.params[i].type); - const CgType* pty = cg_type_get(t->c, pt); - if (pty && - (pty->kind == CFREE_CG_TYPE_RECORD || - pty->kind == CFREE_CG_TYPE_ARRAY || pty->kind == CFREE_CG_TYPE_FUNC)) { - c_ensure_typedef(t, pt); - } - } - cbuf_puts(&t->typedefs, "typedef "); - cbuf_puts(&t->typedefs, c_typename(t, ty->func.ret)); - cbuf_puts(&t->typedefs, " (*__ty_"); - cbuf_put_u64(&t->typedefs, (u64)tid); - cbuf_puts(&t->typedefs, ")("); - if (ty->func.nparams == 0 && !ty->func.abi_variadic) { - cbuf_puts(&t->typedefs, "void"); - } else { - for (u32 i = 0; i < ty->func.nparams; ++i) { - if (i > 0) cbuf_puts(&t->typedefs, ", "); - cbuf_puts(&t->typedefs, c_typename(t, ty->func.params[i].type)); - } - if (ty->func.abi_variadic) { - if (ty->func.nparams > 0) cbuf_puts(&t->typedefs, ", "); - cbuf_puts(&t->typedefs, "..."); - } - } - cbuf_puts(&t->typedefs, ");\n"); -} - -static const char* c_int_type_for_width_panic(CTarget* t, u32 width, - int signed_) { - const char* s = c_int_type_name_for_width(width, signed_); - if (!s) { - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - compiler_panic(t->c, loc, "C target: int width %u not yet supported", - (unsigned)width); - } - return s; -} - -static const char* c_float_type_name(u32 width) { - switch (width) { - case 32: - return "float"; - case 64: - return "double"; - case 80: - case 128: - return "long double"; - default: - return NULL; - } -} - -/* Returns the C type name for a CG type id. Scalars map to fixed-width - * <stdint.h> types or float/double/long double; pointers collapse to void*; - * composites (records/arrays/funcs) emit an opaque-storage typedef on first - * sighting and return the typedef name. */ -static const char* c_typename(CTarget* t, CfreeCgTypeId type) { - CfreeCgTypeId resolved = api_unalias_type(t->c, type); - const CgType* ty = cg_type_get(t->c, resolved); - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!ty) { - compiler_panic(t->c, loc, "C target: unknown type id %u", (unsigned)type); - } - switch (ty->kind) { - case CFREE_CG_TYPE_VOID: - return "void"; - case CFREE_CG_TYPE_BOOL: - return "int32_t"; - case CFREE_CG_TYPE_INT: - return c_int_type_for_width_panic(t, ty->integer.width, 1); - case CFREE_CG_TYPE_FLOAT: { - const char* s = c_float_type_name(ty->fp.width); - if (!s) { - compiler_panic(t->c, loc, "C target: fp width %u not yet supported", - (unsigned)ty->fp.width); - } - return s; - } - case CFREE_CG_TYPE_PTR: - return "void*"; - case CFREE_CG_TYPE_ENUM: - /* CG enums are width-only; treat as their underlying integer base. */ - return c_typename(t, ty->enum_.base); - case CFREE_CG_TYPE_VARARG_STATE: - t->need_stdarg = 1; - return "va_list"; - case CFREE_CG_TYPE_RECORD: - case CFREE_CG_TYPE_ARRAY: - case CFREE_CG_TYPE_FUNC: - c_ensure_typedef(t, resolved); - return c_typedef_name(t, resolved); - default: - compiler_panic(t->c, loc, "C target: type kind %d not yet supported", - (int)ty->kind); - } -} - -void c_emit_type(CTarget* t, CBuf* b, CfreeCgTypeId type) { - cbuf_puts(b, c_typename(t, type)); -} - -static CfreeCgTypeId c_local_type_or_panic(CTarget* t, CGLocal local) { - if ((u32)local < t->local_cap && t->local_declared[local] && - t->local_type[local]) { - return t->local_type[local]; - } - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: unknown local type for v%u", (unsigned)local); - return CFREE_CG_TYPE_NONE; -} - -static void c_emit_tuple_type_name(CTarget* t, CBuf* b, - const CfreeCgTypeId* types, u32 ntypes) { - (void)t; - cbuf_puts(b, "__cfree_tuple"); - cbuf_put_u64(b, (u64)ntypes); - for (u32 i = 0; i < ntypes; ++i) { - cbuf_putc(b, '_'); - cbuf_put_u64(b, (u64)api_unalias_type(t->c, types[i])); - } -} - -static void c_ensure_tuple_typedef(CTarget* t, const CfreeCgTypeId* types, - u32 ntypes) { - if (ntypes <= 1) return; - cbuf_puts(&t->typedefs, "#ifndef __cfree_tuple_guard_"); - c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); - cbuf_puts(&t->typedefs, "\n#define __cfree_tuple_guard_"); - c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); - cbuf_puts(&t->typedefs, "\ntypedef struct "); - c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); - cbuf_puts(&t->typedefs, " {"); - for (u32 i = 0; i < ntypes; ++i) { - cbuf_putc(&t->typedefs, ' '); - c_emit_type(t, &t->typedefs, types[i]); - cbuf_puts(&t->typedefs, " f"); - cbuf_put_u64(&t->typedefs, (u64)i); - cbuf_putc(&t->typedefs, ';'); - } - cbuf_puts(&t->typedefs, " } "); - c_emit_tuple_type_name(t, &t->typedefs, types, ntypes); - cbuf_puts(&t->typedefs, ";\n#endif\n"); -} - -static Operand c_op_local(CGLocal local, CfreeCgTypeId type) { - Operand op; - memset(&op, 0, sizeof op); - op.kind = OPK_LOCAL; - op.type = type; - op.v.local = local; - return op; -} - -void c_local_name(CLocal r, char* out, size_t cap) { - size_t i = 0; - if (cap == 0) return; - if (cap > 1) out[i++] = 'v'; - char tmp[16]; - size_t n = 0; - u32 v = (u32)r; - if (v == 0) { - tmp[n++] = '0'; - } else { - while (v) { - tmp[n++] = (char)('0' + (v % 10)); - v /= 10; - } - } - while (n && i + 1 < cap) out[i++] = tmp[--n]; - out[i] = '\0'; -} - -static void c_grow_local_table(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->local_cap ? t->local_cap : 16; - while (newcap < needed) newcap *= 2; - u8* nd = (u8*)h->realloc(h, t->local_declared, t->local_cap, newcap, 1); - CfreeCgTypeId* nt = (CfreeCgTypeId*)h->realloc( - h, t->local_type, t->local_cap * sizeof(CfreeCgTypeId), - newcap * sizeof(CfreeCgTypeId), _Alignof(CfreeCgTypeId)); - if ((!nd && newcap) || (!nt && newcap)) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->local_cap; i < newcap; ++i) { - nd[i] = 0; - nt[i] = 0; - } - t->local_declared = nd; - t->local_type = nt; - t->local_cap = newcap; -} - -/* Emit the trailing `__attribute__((unused)) = INIT;` for a local decl of - * type `ty`. Scalars get `= 0` (readable); aggregates get `= {0}` (which is - * the only form that compiles for record/array). */ -static void c_emit_zero_init(CTarget* t, CfreeCgTypeId ty) { - const CgType* cgt = ty ? cg_type_get(t->c, api_unalias_type(t->c, ty)) : NULL; - int is_aggregate = cgt && (cgt->kind == CFREE_CG_TYPE_RECORD || - cgt->kind == CFREE_CG_TYPE_ARRAY); - cbuf_puts(&t->decls, is_aggregate ? " __attribute__((unused)) = {0};\n" - : " __attribute__((unused)) = 0;\n"); -} - -void c_ensure_local(CTarget* t, CLocal r, CfreeCgTypeId type) { - if (r == (CLocal)CG_LOCAL_NONE) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: CG_LOCAL_NONE reached emission"); - } - if ((u32)r >= t->local_cap) c_grow_local_table(t, (u32)r + 1u); - if (t->local_declared[r]) { - if (type && api_unalias_type(t->c, t->local_type[r]) != - api_unalias_type(t->c, type)) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: local v%u used with inconsistent type " - "(declared %u, used %u)", - (unsigned)r, (unsigned)api_unalias_type(t->c, t->local_type[r]), - (unsigned)api_unalias_type(t->c, type)); - } - return; - } - t->local_declared[r] = 1; - t->local_type[r] = type; - cbuf_puts(&t->decls, " "); - c_emit_type(t, &t->decls, type); - cbuf_puts(&t->decls, " "); - char buf[24]; - c_local_name(r, buf, sizeof buf); - cbuf_puts(&t->decls, buf); - /* Zero-init kills -Wsometimes-uninitialized for control flow clang can't - * reason through; the host C compiler DSEs the init when a real - * assignment dominates. Scalars get `= 0`, aggregates `= {0}`. */ - c_emit_zero_init(t, type); -} - -/* Emit a signed-int64 literal. INT64_MIN can't be written directly: clang - * treats `-9223372036854775808` as `-(9223372036854775808)` with the inner - * literal too large for any signed type, which trips - * -Wimplicitly-unsigned-literal. The standard workaround is - * `(-9223372036854775807LL - 1)`. */ -static void c_emit_imm_literal(CTarget* t, i64 v) { - if (v == (i64)((u64)1u << 63u)) { - cbuf_puts(&t->body, "(-9223372036854775807LL - 1)"); - return; - } - cbuf_put_i64(&t->body, v); -} - -/* Address-mode tuple decoded from an OPK_INDIRECT operand. Mirrors the - * `addr_mode` helper in the machine-code backends so all targets share a - * single in-backend view of `base [+ index << log2_scale] + ofs`. */ -typedef struct CAddrMode { - CLocal base; - CLocal index; /* CG_LOCAL_NONE when no index operand */ - u8 log2_scale; /* meaningful only when index != CG_LOCAL_NONE */ - i32 ofs; -} CAddrMode; - -static CAddrMode c_addr_mode(Operand addr) { - CAddrMode m; - m.base = addr.v.ind.base; - m.index = addr.v.ind.index; - m.log2_scale = addr.v.ind.log2_scale; - m.ofs = addr.v.ind.ofs; - return m; -} - -/* Emit `(char*)base [+ (uintptr_t)index * (1u << log2_scale)] [+ ofs]` into - * the body, with each optional term suppressed when absent. Used by every - * OPK_INDIRECT renderer; the caller wraps it with the appropriate - * `(*(T*)(...))` or `((T)(...))` cast. */ -static void c_emit_indirect_addr_expr(CTarget* t, CAddrMode m) { - char rbuf[24]; - cbuf_puts(&t->body, "(char*)"); - c_local_name(m.base, rbuf, sizeof rbuf); - cbuf_puts(&t->body, rbuf); - if (m.index != CG_LOCAL_NONE) { - cbuf_puts(&t->body, " + (uintptr_t)"); - c_local_name(m.index, rbuf, sizeof rbuf); - cbuf_puts(&t->body, rbuf); - cbuf_puts(&t->body, " * "); - /* Spell as the explicit 1/2/4/8 literal corresponding to log2_scale. - * log2_scale is normalized to {0,1,2,3} by cg. */ - cbuf_put_u64(&t->body, (u64)(1u << m.log2_scale)); - } - if (m.ofs != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, (i64)m.ofs); - } -} - -/* Assert that `addr`, if OPK_INDIRECT, has no index operand. Used by paths - * the cg layer guarantees never carry the indexed shape (bitfield, atomics, - * copy_bytes/set_bytes, inline asm). */ -static void c_assert_no_index(CTarget* t, Operand addr, const char* where) { - if (addr.kind != OPK_INDIRECT) return; - if (addr.v.ind.index == CG_LOCAL_NONE) return; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - compiler_panic(t->c, loc, - "C target: %.*s: indexed OPK_INDIRECT not allowed here", - SLICE_ARG(slice_from_cstr(where))); -} - -void c_emit_operand(CTarget* t, Operand op) { - char buf[24]; - switch (op.kind) { - case OPK_IMM: - if (op.type == CFREE_CG_TYPE_NONE) { - /* Untyped IMM (e.g. memset byte value): emit the literal raw. */ - cbuf_putc(&t->body, '('); - c_emit_imm_literal(t, op.v.imm); - cbuf_putc(&t->body, ')'); - } else { - cbuf_puts(&t->body, "(("); - c_emit_type(t, &t->body, op.type); - cbuf_puts(&t->body, ")"); - c_emit_imm_literal(t, op.v.imm); - cbuf_puts(&t->body, ")"); - } - return; - case OPK_LOCAL: { - c_ensure_local(t, op.v.local, op.type); - c_local_name(op.v.local, buf, sizeof buf); - cbuf_puts(&t->body, buf); - return; - } - case OPK_INDIRECT: { - /* Used by call paths to pass aggregates by-address: the operand's type - * is the aggregate, the storage is `base + index*scale + ofs`. Emit the - * deref as a value expression. */ - cbuf_puts(&t->body, "(*("); - c_emit_type(t, &t->body, op.type); - cbuf_puts(&t->body, "*)("); - c_emit_indirect_addr_expr(t, c_addr_mode(op)); - cbuf_puts(&t->body, "))"); - return; - } - case OPK_GLOBAL: { - /* OPK_GLOBAL carries `&sym + addend`. How we spell it depends on - * op.type: - * - pointer/scalar/void: the value IS the address, so cast through - * `((T)((char*)sym + addend))`. - * - aggregate (RECORD/ARRAY): the symbol's storage is an aggregate - * value; emit `(*(T*)((char*)sym + addend))` so the deref reads - * the aggregate value (used by call args that pass struct - * by-value via a global initialized buffer). */ - obj_sym_mark_referenced(t->obj, op.v.global.sym); - const char* nm = c_sym_name(t, op.v.global.sym); - const CgType* gty = - (op.type != CFREE_CG_TYPE_NONE) - ? cg_type_get(t->c, api_unalias_type(t->c, op.type)) - : NULL; - int is_aggregate = gty && (gty->kind == CFREE_CG_TYPE_RECORD || - gty->kind == CFREE_CG_TYPE_ARRAY); - if (is_aggregate) { - cbuf_puts(&t->body, "(*("); - c_emit_type(t, &t->body, op.type); - cbuf_puts(&t->body, "*)((char*)&"); - cbuf_puts(&t->body, nm); - if (op.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, op.v.global.addend); - } - cbuf_puts(&t->body, "))"); - } else { - cbuf_puts(&t->body, "(("); - if (op.type != CFREE_CG_TYPE_NONE) { - c_emit_type(t, &t->body, op.type); - } else { - cbuf_puts(&t->body, "void*"); - } - cbuf_puts(&t->body, ")((char*)&"); - cbuf_puts(&t->body, nm); - if (op.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, op.v.global.addend); - } - cbuf_puts(&t->body, "))"); - } - return; - } - default: { - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - compiler_panic(t->c, loc, "C target: operand kind %d not yet supported", - (int)op.kind); - } - } -} - -static int c_type_is_float(CTarget* t, CfreeCgTypeId type) { - if (type == CFREE_CG_TYPE_NONE) return 0; - const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); - return ty && ty->kind == CFREE_CG_TYPE_FLOAT; -} - -/* True iff a and b name the same CG type after alias resolution. */ -static int c_types_equiv(CTarget* t, CfreeCgTypeId a, CfreeCgTypeId b) { - if (a == 0 || b == 0) return 0; - return api_unalias_type(t->c, a) == api_unalias_type(t->c, b); -} - -/* Emit " vN = " plus any cast needed for a C assignment expression. - * Caller must then emit the RHS expression and call c_emit_local_assign_close. - * - * `rhs_ty` is the CG type the RHS expression will produce (or 0 if unknown). - * Pointer/int crossings bridge through uintptr_t to keep host-C diagnostics - * quiet. The outer `(...)` parens are kept so the closer's `);` stays - * balanced. */ -static void c_emit_local_assign_open(CTarget* t, CLocal r, CfreeCgTypeId rhs_ty) { - if ((u32)r >= t->local_cap || !t->local_declared[r]) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: assign to undeclared local v%u", (unsigned)r); - } - CfreeCgTypeId decl = t->local_type[r]; - char buf[24]; - c_local_name(r, buf, sizeof buf); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, " = "); - if (!c_types_equiv(t, rhs_ty, decl)) { - cbuf_putc(&t->body, '('); - c_emit_type(t, &t->body, decl); - cbuf_putc(&t->body, ')'); - if (!c_type_is_float(t, decl) && - (!rhs_ty || c_type_is_ptr(t, decl) || c_type_is_ptr(t, rhs_ty))) { - cbuf_puts(&t->body, "(uintptr_t)"); - } - } - cbuf_puts(&t->body, "("); -} - -static void c_emit_local_assign_close(CTarget* t) { cbuf_puts(&t->body, ");\n"); } - -void c_emit_operand_signed(CTarget* t, Operand op, int signed_) { - u32 w = c_int_width_for_signedness(t, op.type); - if (w == 0) { - /* Not an integer — emit without sign cast. */ - c_emit_operand(t, op); - return; - } - const char* tn = c_int_type_name_for_width(w, signed_); - if (!tn) { - c_emit_operand(t, op); - return; - } - int via_uptr = c_operand_is_ptr_typed(t, op); - /* CG ints are width-only; the C target declares every int local/IMM - * as the signed `int{W}_t` of its width. So when `signed_` is true and - * the operand's emit-width matches `w`, the explicit cast is redundant - * with what c_emit_operand already produces. Skipping it cuts the - * ubiquitous `((int32_t)((int32_t)23))` double-cast down to one. */ - if (!via_uptr && signed_) { - CfreeCgTypeId et = op.type; - if (c_int_width_for_signedness(t, et) == w) { - c_emit_operand(t, op); - return; - } - } - cbuf_puts(&t->body, "(("); - cbuf_puts(&t->body, tn); - cbuf_puts(&t->body, ")"); - if (via_uptr) { - cbuf_puts(&t->body, "(uintptr_t)"); - } - c_emit_operand(t, op); - cbuf_puts(&t->body, ")"); -} - -/* Returns 1 if `type` is a pointer (or void*). */ -static int c_type_is_ptr(CTarget* t, CfreeCgTypeId type) { - if (type == CFREE_CG_TYPE_NONE) return 0; - const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); - return ty && ty->kind == CFREE_CG_TYPE_PTR; -} - -static int c_type_is_bool(CTarget* t, CfreeCgTypeId type) { - if (type == CFREE_CG_TYPE_NONE) return 0; - const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); - return ty && ty->kind == CFREE_CG_TYPE_BOOL; -} - -static int c_type_is_aggregate(CTarget* t, CfreeCgTypeId type) { - if (type == CFREE_CG_TYPE_NONE) return 0; - const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, type)); - return ty && - (ty->kind == CFREE_CG_TYPE_RECORD || ty->kind == CFREE_CG_TYPE_ARRAY); -} - -static int c_operand_is_ptr_typed(CTarget* t, Operand op) { - if (c_type_is_ptr(t, op.type)) return 1; - return 0; -} - -/* Emit `(target_ty)(uintptr_t)(op)` (or `(target_ty)(op)` for float - * target_ty). Used when the caller needs a specific C expression type. - * Pointer/int crossings bridge through uintptr_t. */ -static void c_emit_operand_as(CTarget* t, Operand op, CfreeCgTypeId target_ty) { - if (c_types_equiv(t, op.type, target_ty)) { - c_emit_operand(t, op); - return; - } - cbuf_puts(&t->body, "("); - c_emit_type(t, &t->body, target_ty); - cbuf_puts(&t->body, ")"); - if (!c_type_is_float(t, target_ty) && - (!op.type || c_type_is_ptr(t, op.type) || c_type_is_ptr(t, target_ty))) { - cbuf_puts(&t->body, "(uintptr_t)"); - } - cbuf_puts(&t->body, "("); - c_emit_operand(t, op); - cbuf_puts(&t->body, ")"); -} - -/* Emit an operand for use in a C binary arithmetic expression. Pointer-typed - * operands are cast to uintptr_t so C arithmetic semantics apply uniformly - * (cfree IR carries byte offsets, not C-pointer-arith scaled indices). */ -static void c_emit_operand_arith(CTarget* t, Operand op) { - if (c_operand_is_ptr_typed(t, op)) { - cbuf_puts(&t->body, "((uintptr_t)"); - if (op.kind == OPK_IMM) { - c_emit_imm_literal(t, op.v.imm); - } else { - c_emit_operand(t, op); - } - cbuf_puts(&t->body, ")"); - return; - } - c_emit_operand(t, op); -} - -/* Same, but applies the requested signedness when the operand is an integer - * (used for SDIV/UDIV/SREM/UREM/SHR_S/SHR_U). Pointer operands always go - * through the uintptr_t cast regardless of the requested signedness. */ -static void c_emit_operand_arith_signed(CTarget* t, Operand op, int signed_) { - if (c_operand_is_ptr_typed(t, op)) { - cbuf_puts(&t->body, "((uintptr_t)"); - if (op.kind == OPK_IMM) { - c_emit_imm_literal(t, op.v.imm); - } else { - c_emit_operand(t, op); - } - cbuf_puts(&t->body, ")"); - return; - } - c_emit_operand_signed(t, op, signed_); -} - -/* Emit a C lvalue expression for an addr operand (OPK_LOCAL / OPK_GLOBAL / - * OPK_INDIRECT) using `access_type` as the access type. The result is the - * full `*(T*)(...)` dereference, or the C variable directly when the access - * type matches the underlying local/global object. */ -static void c_emit_addr_deref(CTarget* t, Operand addr, - CfreeCgTypeId access_type) { - char buf[24]; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - switch (addr.kind) { - case OPK_LOCAL: { - c_ensure_local(t, addr.v.local, addr.type); - c_local_name(addr.v.local, buf, sizeof buf); - if (access_type == 0 || addr.type == 0 || - api_unalias_type(t->c, access_type) == - api_unalias_type(t->c, addr.type)) { - cbuf_puts(&t->body, buf); - } else { - cbuf_puts(&t->body, "(*("); - c_emit_type(t, &t->body, access_type); - cbuf_puts(&t->body, "*)&"); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ")"); - } - return; - } - case OPK_GLOBAL: { - obj_sym_mark_referenced(t->obj, addr.v.global.sym); - const char* nm = c_sym_name(t, addr.v.global.sym); - cbuf_puts(&t->body, "(*("); - c_emit_type(t, &t->body, access_type); - cbuf_puts(&t->body, "*)((char*)&"); - cbuf_puts(&t->body, nm); - if (addr.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, addr.v.global.addend); - } - cbuf_puts(&t->body, "))"); - return; - } - case OPK_INDIRECT: { - CAddrMode m = c_addr_mode(addr); - if ((u32)m.base >= t->local_cap || !t->local_declared[m.base]) { - compiler_panic(t->c, loc, - "C target: indirect on undeclared base local v%u", - (unsigned)m.base); - } - if (m.index != CG_LOCAL_NONE && - ((u32)m.index >= t->local_cap || !t->local_declared[m.index])) { - compiler_panic(t->c, loc, - "C target: indirect on undeclared index local v%u", - (unsigned)m.index); - } - cbuf_puts(&t->body, "(*("); - c_emit_type(t, &t->body, access_type); - cbuf_puts(&t->body, "*)("); - c_emit_indirect_addr_expr(t, m); - cbuf_puts(&t->body, "))"); - return; - } - default: - compiler_panic(t->c, loc, - "C target: addr-deref on operand kind %d not supported", - (int)addr.kind); - } -} - -/* Emit a C address-of expression for a lvalue operand. Output is a pointer - * value (cast to dst_type). */ -static void c_emit_lvalue_addr(CTarget* t, Operand lv, CfreeCgTypeId dst_type) { - char buf[24]; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - switch (lv.kind) { - case OPK_LOCAL: - cbuf_puts(&t->body, "(("); - c_emit_type(t, &t->body, dst_type); - cbuf_puts(&t->body, ")"); - cbuf_puts(&t->body, "&"); - c_ensure_local(t, lv.v.local, lv.type); - c_local_name(lv.v.local, buf, sizeof buf); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ")"); - return; - case OPK_GLOBAL: { - obj_sym_mark_referenced(t->obj, lv.v.global.sym); - const char* nm = c_sym_name(t, lv.v.global.sym); - cbuf_puts(&t->body, "(("); - c_emit_type(t, &t->body, dst_type); - cbuf_puts(&t->body, ")((char*)&"); - cbuf_puts(&t->body, nm); - if (lv.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, lv.v.global.addend); - } - cbuf_puts(&t->body, ")"); - cbuf_puts(&t->body, ")"); - return; - } - case OPK_INDIRECT: { - cbuf_puts(&t->body, "(("); - c_emit_type(t, &t->body, dst_type); - cbuf_puts(&t->body, ")("); - c_emit_indirect_addr_expr(t, c_addr_mode(lv)); - cbuf_puts(&t->body, "))"); - return; - } - default: - compiler_panic(t->c, loc, - "C target: addr-of on operand kind %d not supported", - (int)lv.kind); - } -} - -/* === Symbol name lookup === */ - -const char* c_sym_name(CTarget* t, ObjSymId sym) { - const ObjSym* os = obj_symbol_get(t->obj, sym); - if (!os) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: unknown ObjSymId %u", - (unsigned)sym); - } - Slice nm = pool_slice(t->c->global, os->name); - const char* s = nm.s; - size_t n = nm.len; - /* Mach-O linker symbols are mangled with a leading underscore; the host - * C compiler will re-add it on its own, so strip when re-emitting source. */ - if (t->c->target.obj == CFREE_OBJ_MACHO && s && n > 0 && s[0] == '_') { - s += 1; - n -= 1; - } - /* Sanitize for C identifier rules: assemblers accept '.', '$', etc. in - * symbol names; C does not. Replace each illegal byte with '_' and prepend - * '_' if the first char isn't alpha/underscore. Local syms (SB_LOCAL) get - * renamed freely since they have no cross-TU contract. Globals are assumed - * to come in with C-safe names; if they don't, we still rewrite — the - * resulting symbol won't link against other TUs that use the asm spelling, - * but cfree-produced code uses the rewritten spelling consistently. */ - int needs_rewrite = 0; - if (n == 0) { - return s; - } - if (!((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z') || - s[0] == '_')) { - needs_rewrite = 1; - } else { - for (size_t i = 0; i < n; ++i) { - char ch = s[i]; - if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || - (ch >= '0' && ch <= '9') || ch == '_')) { - needs_rewrite = 1; - break; - } - } - } - if (!needs_rewrite) return s; - char buf[256]; - size_t cap = sizeof(buf) - 1u; - size_t out = 0; - int first_alpha = (s[0] >= 'a' && s[0] <= 'z') || - (s[0] >= 'A' && s[0] <= 'Z') || s[0] == '_'; - if (!first_alpha && out < cap) buf[out++] = '_'; - for (size_t i = 0; i < n && out < cap; ++i) { - char ch = s[i]; - int ok = (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || - (ch >= '0' && ch <= '9') || ch == '_'; - buf[out++] = ok ? ch : '_'; - } - buf[out] = '\0'; - Sym interned = pool_intern_slice(t->c->global, (Slice){.s = buf, .len = out}); - return pool_slice(t->c->global, interned).s; -} - -/* === Prologue / finalize === */ - -void c_emit_prologue(CTarget* t) { - if (t->prologue_emitted) return; - t->prologue_emitted = 1; - c_writer_puts(t, - "/* generated by cfree --emit=c */\n" - "#include <stdint.h>\n" - "#include <stdalign.h>\n"); - /* Other headers are decided at finalize so include lines remain - * deterministic regardless of when the type was first referenced. - * Writer flushes are not stream-buffered, so we keep prologue compact and - * tack the rest on at c_finalize. */ - c_writer_puts(t, "\n"); -} - -/* === func_begin / func_end === */ - -/* Write `RetT name(P0, P1, ...)` (without trailing `;` or `{`) to `b`. */ -static void c_emit_func_signature(CTarget* t, CBuf* b, const char* name, - CfreeCgTypeId fn_type, - const CfreeCgTypeId* result_types, - u32 nresults) { - CfreeCgTypeId ret_type = cg_type_func_ret_id(t->c, fn_type); - const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, fn_type)); - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { - compiler_panic(t->c, loc, "C target: fn_type is not a function type"); - } - if (!result_types) { - nresults = cg_type_is_void(t->c, ret_type) ? 0u : 1u; - result_types = &ret_type; - } - if (nresults == 0) { - cbuf_puts(b, "void"); - } else if (nresults == 1) { - c_emit_type(t, b, result_types[0]); - } else { - c_ensure_tuple_typedef(t, result_types, nresults); - c_emit_tuple_type_name(t, b, result_types, nresults); - } - cbuf_puts(b, " "); - cbuf_puts(b, name); - cbuf_puts(b, "("); - if (fty->func.nparams == 0 && !fty->func.abi_variadic) { - cbuf_puts(b, "void"); - } else { - for (u32 i = 0; i < fty->func.nparams; ++i) { - if (i > 0) cbuf_puts(b, ", "); - c_emit_type(t, b, fty->func.params[i].type); - cbuf_puts(b, " p"); - cbuf_put_u64(b, (u64)i); - } - if (fty->func.abi_variadic) { - if (fty->func.nparams > 0) cbuf_puts(b, ", "); - cbuf_puts(b, "..."); - } - } - cbuf_puts(b, ")"); -} - -void c_func_begin(CgTarget* T, const CGFuncDesc* fd) { - CTarget* t = (CTarget*)T; - - c_emit_prologue(t); - - t->cur_fn = fd; - cbuf_reset(&t->decls); - for (u32 i = 0; i < t->local_cap; ++i) { - t->local_declared[i] = 0; - t->local_type[i] = 0; - } - t->next_label = 0; - t->next_local = 0; - t->next_tmp = 0; - t->nscopes = 0; - t->last_was_terminator = 0; - t->have_emitted_loc = 0; - t->emitted_loc = (SrcLoc){0, 0, 0}; - - const char* name = c_sym_name(t, fd->sym); - - /* Forward-declare so out-of-order callers and same-TU references find the - * prototype regardless of definition order. */ - c_ensure_forward_decl_with_results(t, fd->sym, fd->fn_type, fd->result_types, - fd->nresults); - - c_emit_func_signature(t, &t->body, name, fd->fn_type, fd->result_types, - fd->nresults); - cbuf_puts(&t->body, " {\n"); - t->fn_body_start = t->body.len; -} - -static void c_ensure_forward_decl_with_results(CTarget* t, ObjSymId sym, - CfreeCgTypeId fn_type, - const CfreeCgTypeId* result_types, - u32 nresults) { - Heap* h = t->c->ctx->heap; - if ((u32)sym >= t->sym_forwarded_cap) { - u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; - while (newcap <= (u32)sym) newcap *= 2; - u8* nd = - (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; - t->sym_forwarded = nd; - t->sym_forwarded_cap = newcap; - } - if (t->sym_forwarded[sym]) return; - t->sym_forwarded[sym] = 1; - const char* name = c_sym_name(t, sym); - const ObjSym* os = obj_symbol_get(t->obj, sym); - if ((os && (os->kind == SK_FUNC || os->kind == SK_IFUNC)) || fn_type != 0) { - c_emit_func_signature(t, &t->forwards, name, fn_type, result_types, - nresults); - cbuf_puts(&t->forwards, ";\n"); - } else { - if (os && os->bind == SB_LOCAL) - cbuf_puts(&t->forwards, "static "); - else - cbuf_puts(&t->forwards, "extern "); - if (os && os->section_id != OBJ_SEC_NONE) { - const Section* sec = obj_section_get(t->obj, os->section_id); - if (sec->kind == SEC_RODATA) cbuf_puts(&t->forwards, "const "); - } - cbuf_puts(&t->forwards, "struct __cfree_data_"); - cbuf_puts(&t->forwards, name); - cbuf_puts(&t->forwards, " "); - cbuf_puts(&t->forwards, name); - cbuf_puts(&t->forwards, ";\n"); - } -} - -void c_ensure_forward_decl(CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type) { - c_ensure_forward_decl_with_results(t, sym, fn_type, NULL, 0); -} - -void c_func_end(CgTarget* T) { - CTarget* t = (CTarget*)T; - size_t splice_at = t->fn_body_start; - size_t body_after = t->body.len; - size_t fn_body_len = body_after - splice_at; - Heap* h = t->c->ctx->heap; - - u8* tmp = NULL; - if (fn_body_len) { - tmp = (u8*)h->alloc(h, fn_body_len, 1); - if (!tmp) { - compiler_panic(t->c, t->cur_fn->loc, "C target: out of memory"); - } - for (size_t i = 0; i < fn_body_len; ++i) { - tmp[i] = t->body.data[splice_at + i]; - } - } - - t->body.len = splice_at; - if (t->decls.len) - cbuf_putn(&t->body, (const char*)t->decls.data, t->decls.len); - if (tmp) { - cbuf_putn(&t->body, (const char*)tmp, fn_body_len); - h->free(h, tmp, fn_body_len); - } - cbuf_puts(&t->body, "}\n\n"); - - t->cur_fn = NULL; -} - -/* === locals, params === */ - -CGLocal c_param(CgTarget* T, const CGParamDesc* pd) { - CTarget* t = (CTarget*)T; - CGLocalDesc d; - memset(&d, 0, sizeof d); - d.type = pd->type; - d.name = pd->name; - d.loc = pd->loc; - d.size = pd->size; - d.align = pd->align; - d.flags = pd->flags; - CGLocal local = c_local(T, &d); - c_ensure_local(t, local, pd->type); - - char buf[24]; - c_local_name(local, buf, sizeof buf); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, " = p"); - cbuf_put_u64(&t->body, (u64)pd->index); - cbuf_puts(&t->body, ";\n"); - - return local; -} - -/* === load_imm, copy, binop === */ - -void c_load_imm(CgTarget* T, Operand dst, i64 imm) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: load_imm dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - /* The literal is emitted bare; its C type is `long long`. We can drop - * the bridge cast iff the bare assignment compiles cleanly: - * - integer dst: imm must fit in dst's signed range (else - * -Wconstant-conversion). 64-bit dst always fits. - * - pointer dst: only `0` (null pointer constant) is safe; any other - * literal trips -Wint-conversion. - * Otherwise keep the bridge. */ - u32 w = c_int_width_for_signedness(t, dst.type); - int can_drop_bridge; - if (w > 0) { - can_drop_bridge = (w >= 64) || (imm >= -((i64)1 << (w - 1)) && - imm <= (((i64)1 << (w - 1)) - 1)); - } else if (c_type_is_ptr(t, dst.type)) { - can_drop_bridge = (imm == 0); - } else { - can_drop_bridge = 0; - } - c_emit_local_assign_open(t, dst.v.local, - can_drop_bridge ? dst.type : (CfreeCgTypeId)0); - c_emit_imm_literal(t, imm); - c_emit_local_assign_close(t); -} - -void c_copy(CgTarget* T, Operand dst, Operand src) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: copy dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - c_emit_local_assign_open(t, dst.v.local, src.type); - c_emit_operand(t, src); - c_emit_local_assign_close(t); -} - -static const char* binop_to_c(BinOp op) { - switch (op) { - case BO_IADD: - case BO_FADD: - return "+"; - case BO_ISUB: - case BO_FSUB: - return "-"; - case BO_IMUL: - case BO_FMUL: - return "*"; - case BO_SDIV: - case BO_UDIV: - case BO_FDIV: - return "/"; - case BO_SREM: - case BO_UREM: - return "%"; - case BO_AND: - return "&"; - case BO_OR: - return "|"; - case BO_XOR: - return "^"; - case BO_SHL: - return "<<"; - case BO_SHR_S: - case BO_SHR_U: - return ">>"; - } - return NULL; -} - -/* For BinOp `op`, decide how to sign-cast the operands. Returns 0 for "no - * cast", 1 for "cast both to signed", 2 for "cast both to unsigned", 3 for - * "cast lhs only (signedness `lhs_signed`)" (used for shifts). */ -typedef enum { BSC_NONE, BSC_SIGNED, BSC_UNSIGNED, BSC_SHIFT_LHS } BinSignCast; - -static BinSignCast binop_sign_kind(BinOp op, int* lhs_signed_out) { - *lhs_signed_out = 1; - switch (op) { - case BO_SDIV: - case BO_SREM: - return BSC_SIGNED; - case BO_UDIV: - case BO_UREM: - return BSC_UNSIGNED; - case BO_SHR_S: - *lhs_signed_out = 1; - return BSC_SHIFT_LHS; - case BO_SHR_U: - *lhs_signed_out = 0; - return BSC_SHIFT_LHS; - default: - return BSC_NONE; - } -} - -void c_binop(CgTarget* T, BinOp op, Operand dst, Operand a, Operand b) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - const char* sym = binop_to_c(op); - if (!sym) { - compiler_panic(t->c, loc, "C target: unknown binop %d", (int)op); - } - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: binop dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - /* Pointer operands get cast to uintptr_t inside c_emit_operand_arith, - * so the binop's C result type is `uintptr_t`, not the original pointer - * type. Keep the bridge when dst or either operand is pointer-typed so - * the assignment back to a pointer dst doesn't trip -Wint-conversion. */ - int has_ptr = c_operand_is_ptr_typed(t, dst) || - c_operand_is_ptr_typed(t, a) || c_operand_is_ptr_typed(t, b); - c_emit_local_assign_open(t, dst.v.local, has_ptr ? (CfreeCgTypeId)0 : dst.type); - int lhs_signed = 1; - BinSignCast bsc = binop_sign_kind(op, &lhs_signed); - switch (bsc) { - case BSC_NONE: - c_emit_operand_arith(t, a); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, sym); - cbuf_puts(&t->body, " "); - c_emit_operand_arith(t, b); - break; - case BSC_SIGNED: - c_emit_operand_arith_signed(t, a, 1); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, sym); - cbuf_puts(&t->body, " "); - c_emit_operand_arith_signed(t, b, 1); - break; - case BSC_UNSIGNED: - c_emit_operand_arith_signed(t, a, 0); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, sym); - cbuf_puts(&t->body, " "); - c_emit_operand_arith_signed(t, b, 0); - break; - case BSC_SHIFT_LHS: - c_emit_operand_arith_signed(t, a, lhs_signed); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, sym); - cbuf_puts(&t->body, " "); - c_emit_operand(t, b); - break; - } - c_emit_local_assign_close(t); -} - -/* ===== unop ===== */ - -void c_unop(CgTarget* T, UnOp op, Operand dst, Operand a) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: unop dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - const char* sym = NULL; - switch (op) { - case UO_NEG: - case UO_FNEG: - sym = "-"; - break; - case UO_NOT: - sym = "!"; - break; - case UO_BNOT: - sym = "~"; - break; - default: - compiler_panic(t->c, loc, "C target: unknown unop %d", (int)op); - } - c_emit_local_assign_open(t, dst.v.local, dst.type); - cbuf_puts(&t->body, sym); - c_emit_operand(t, a); - c_emit_local_assign_close(t); -} - -/* ===== compare ops ===== */ - -static const char* cmp_to_c(CmpOp op) { - switch (op) { - case CMP_EQ: - return "=="; - case CMP_NE: - return "!="; - case CMP_LT_S: - case CMP_LT_U: - case CMP_LT_F: - return "<"; - case CMP_LE_S: - case CMP_LE_U: - case CMP_LE_F: - return "<="; - case CMP_GT_S: - case CMP_GT_U: - case CMP_GT_F: - return ">"; - case CMP_GE_S: - case CMP_GE_U: - case CMP_GE_F: - return ">="; - } - return NULL; -} - -/* Returns 1 if cmp op needs unsigned operand cast. -1 if signed. 0 if no cast - * (EQ/NE — sign doesn't matter for integer equality at the same width — and - * float compares). */ -static int cmp_signedness(CmpOp op) { - switch (op) { - case CMP_LT_S: - case CMP_LE_S: - case CMP_GT_S: - case CMP_GE_S: - return -1; - case CMP_LT_U: - case CMP_LE_U: - case CMP_GT_U: - case CMP_GE_U: - return 1; - default: - return 0; - } -} - -static void c_emit_cmp_operands(CTarget* t, CmpOp op, Operand a, Operand b) { - int sg = cmp_signedness(op); - if (sg == 0) { - c_emit_operand_arith(t, a); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, cmp_to_c(op)); - cbuf_puts(&t->body, " "); - c_emit_operand_arith(t, b); - } else { - int signed_ = (sg < 0); - c_emit_operand_arith_signed(t, a, signed_); - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, cmp_to_c(op)); - cbuf_puts(&t->body, " "); - c_emit_operand_arith_signed(t, b, signed_); - } -} - -void c_cmp(CgTarget* T, CmpOp op, Operand dst, Operand a, Operand b) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: cmp dst must be LOCAL"); - } - if (!cmp_to_c(op)) { - compiler_panic(t->c, loc, "C target: unknown cmp %d", (int)op); - } - c_ensure_local(t, dst.v.local, dst.type); - /* Compare result is C `int` (0/1); assigning to integer dst.type narrows - * implicitly without -Wall complaint. */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - c_emit_cmp_operands(t, op, a, b); - c_emit_local_assign_close(t); -} - -/* ===== labels, jump, cmp_branch ===== */ - -static void c_label_name(Label l, char* out, size_t cap) { - size_t i = 0; - if (cap == 0) return; - const char* p = "L"; - while (*p && i + 1 < cap) out[i++] = *p++; - char tmp[16]; - size_t n = 0; - u32 v = (u32)l; - if (v == 0) { - tmp[n++] = '0'; - } else { - while (v) { - tmp[n++] = (char)('0' + (v % 10)); - v /= 10; - } - } - while (n && i + 1 < cap) out[i++] = tmp[--n]; - out[i] = '\0'; -} - -Label c_label_new(CgTarget* T) { - CTarget* t = (CTarget*)T; - t->next_label += 1; - return (Label)t->next_label; -} - -void c_label_place(CgTarget* T, Label l) { - CTarget* t = (CTarget*)T; - char buf[24]; - c_label_name(l, buf, sizeof buf); - /* `Lk: __attribute__((unused));` — empty stmt keeps it valid at end-of-block, - * and the attribute silences -Wunused-label when the goto got folded away. */ - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ": __attribute__((unused));\n"); - t->last_was_terminator = 0; -} - -/* If `l` is the innermost structured scope's break/continue label, return - * the C keyword that exits/iterates that scope (a literal `break` or - * `continue`). NULL means "fall back to goto." Matches only the innermost - * scope because C `break`/`continue` only escape the nearest enclosing - * loop/switch — outer-scope targets must stay as goto. */ -static const char* c_scope_kw_for_label(CTarget* t, Label l) { - if (t->nscopes == 0) return NULL; - const CScopeInfo* s = &t->scopes[t->nscopes - 1u]; - if (!s->structured) return NULL; - if (l == s->break_label) return "break"; - if (l == s->continue_label) return "continue"; - return NULL; -} - -void c_jump(CgTarget* T, Label l) { - CTarget* t = (CTarget*)T; - if (t->last_was_terminator) return; - const char* kw = c_scope_kw_for_label(t, l); - if (kw) { - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, kw); - cbuf_puts(&t->body, ";\n"); - } else { - char buf[24]; - c_label_name(l, buf, sizeof buf); - cbuf_puts(&t->body, " goto "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ";\n"); - } - t->last_was_terminator = 1; -} - -void c_cmp_branch(CgTarget* T, CmpOp op, Operand a, Operand b, Label l) { - CTarget* t = (CTarget*)T; - if (t->last_was_terminator) return; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!cmp_to_c(op)) { - compiler_panic(t->c, loc, "C target: unknown cmp %d", (int)op); - } - const char* kw = c_scope_kw_for_label(t, l); - cbuf_puts(&t->body, " if ("); - c_emit_cmp_operands(t, op, a, b); - if (kw) { - cbuf_puts(&t->body, ") "); - cbuf_puts(&t->body, kw); - cbuf_puts(&t->body, ";\n"); - } else { - char buf[24]; - c_label_name(l, buf, sizeof buf); - cbuf_puts(&t->body, ") goto "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ";\n"); - } -} - -/* ===== scopes ===== - * - * SCOPE_LOOP maps to C's `for (;;) { ... }`. CG places the continue label - * just before `scope_begin` and the break label just before `scope_end` - * (see src/cg/control.c:208,253). The C target leaves those label - * placements in the body — they sit just before `for (;;) {` and just - * after `}` respectively, so any outer-scope `goto continue_lbl` or - * `goto break_lbl` (e.g. a nested loop's `continue` targeting this - * outer loop) still resolves. Inside the `for` body, `c_jump` and - * `c_cmp_branch` translate jumps whose target is the *innermost* scope's - * break/continue label into `break;` / `continue;`; outer-scope targets - * fall back to `goto`. The redundant `Lk: ;` adjacent to the `for` is - * cosmetic; gcc/clang fold it. */ - -static void c_grow_scopes(CTarget* t, u32 needed) { - Heap* h = t->c->ctx->heap; - u32 newcap = t->scopes_cap ? t->scopes_cap : 8; - while (newcap < needed) newcap *= 2; - CScopeInfo* ns = (CScopeInfo*)h->realloc( - h, t->scopes, t->scopes_cap * sizeof(CScopeInfo), - newcap * sizeof(CScopeInfo), _Alignof(CScopeInfo)); - if (!ns && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - t->scopes = ns; - t->scopes_cap = newcap; -} - -CGScope c_scope_begin(CgTarget* T, const CGScopeDesc* d) { - CTarget* t = (CTarget*)T; - if (t->nscopes + 1u >= t->scopes_cap) c_grow_scopes(t, t->nscopes + 2u); - u32 idx = t->nscopes; - t->scopes[idx].kind = d->kind; - t->scopes[idx].break_label = d->break_label; - t->scopes[idx].continue_label = d->continue_label; - t->scopes[idx].structured = 0; - t->nscopes += 1u; - if (d->kind == SCOPE_LOOP) { - cbuf_puts(&t->body, " for (;;) {\n"); - t->scopes[idx].structured = 1; - t->last_was_terminator = 0; - return (CGScope)(idx + 1u); - } - /* SCOPE_IF carries a cond consumed here. The public CfreeCg API always - * emits SCOPE_LOOP, so this branch only fires for internal callers. */ - if (d->kind == SCOPE_IF) { - char buf[24]; - c_label_name(d->break_label, buf, sizeof buf); - cbuf_puts(&t->body, " if (!("); - c_emit_operand(t, d->cond); - cbuf_puts(&t->body, ")) goto "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ";\n"); - } - return (CGScope)(idx + 1u); -} - -void c_scope_else(CgTarget* T, CGScope s) { - (void)T; - (void)s; - /* Public API doesn't emit SCOPE_IF; if it ever does, the frontend is - * responsible for placing the else label and the break_label itself. */ -} - -void c_scope_end(CgTarget* T, CGScope s) { - CTarget* t = (CTarget*)T; - if (s == 0 || (u32)s > t->nscopes) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: scope_end on invalid handle"); - } - u32 idx = (u32)s - 1u; - if (t->scopes[idx].structured) { - /* CG places break_label just before scope_end, so the label sits - * inside the for-body. Anything that lands on it (including a - * `goto break_lbl` from a nested scope's labeled break) needs to - * exit the for — without an explicit `break;`, fall-through would - * iterate again. Always emit; if the body already terminated the - * defensive break is dead but harmless. */ - cbuf_puts(&t->body, " break;\n"); - cbuf_puts(&t->body, " }\n"); - /* The closing brace is not a terminator; control can fall through it - * (e.g., off the end of a void function). */ - t->last_was_terminator = 0; - } - t->nscopes -= 1u; -} - -void c_break_to(CgTarget* T, CGScope s) { - CTarget* t = (CTarget*)T; - if (s == 0 || (u32)s > t->nscopes) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: break_to on invalid handle"); - } - c_jump(T, t->scopes[s - 1u].break_label); -} - -void c_continue_to(CgTarget* T, CGScope s) { - CTarget* t = (CTarget*)T; - if (s == 0 || (u32)s > t->nscopes) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: continue_to on invalid handle"); - } - c_jump(T, t->scopes[s - 1u].continue_label); -} - -/* ===== switch dispatch ===== */ - -/* Emit `case <value>:`. For an int32_t selector the bare literal is - * already the right type, so we skip the cast; for wider/narrower - * integers we wrap in `(T)` so the case constant matches the switch - * value's promoted type (avoids -Wswitch warnings on narrower - * selectors). */ -static void c_emit_case_value(CTarget* t, CfreeCgTypeId sel_ty, u64 v) { - u32 w = c_int_width_for_signedness(t, sel_ty); - cbuf_puts(&t->body, " case "); - if (w != 0 && w != 32) { - cbuf_putc(&t->body, '('); - c_emit_type(t, &t->body, sel_ty); - cbuf_puts(&t->body, ")"); - } - c_emit_imm_literal(t, (i64)v); - cbuf_puts(&t->body, ":"); -} - -void c_switch_(CgTarget* T, const CGSwitchDesc* d) { - CTarget* t = (CTarget*)T; - /* gcc/clang ignore strategy hints and pick their own dispatch shape. */ - (void)d->hint; - if (t->last_was_terminator) return; - cbuf_puts(&t->body, " switch ("); - c_emit_operand(t, d->selector); - cbuf_puts(&t->body, ") {\n"); - for (u32 i = 0; i < d->ncases; ++i) { - char buf[24]; - c_label_name(d->cases[i].label, buf, sizeof buf); - c_emit_case_value(t, d->selector.type, d->cases[i].value); - cbuf_puts(&t->body, " goto "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ";\n"); - } - cbuf_puts(&t->body, " default: "); - if (d->default_label != (Label)LABEL_NONE) { - char buf[24]; - c_label_name(d->default_label, buf, sizeof buf); - cbuf_puts(&t->body, "goto "); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ";\n"); - } else { - /* No default supplied — the cfree IR's contract for that case is - * "if no case matches, fall through." `break;` does exactly that - * inside the for-wrapper around structured scopes. */ - cbuf_puts(&t->body, "break;\n"); - } - cbuf_puts(&t->body, " }\n"); - /* The switch always transfers control (every arm jumps or breaks). - * Mark as terminator so any frontend-emitted defensive jump after - * dispatch is dropped. */ - t->last_was_terminator = 1; -} - -/* ===== load_label_addr / indirect_branch ===== - * GCC computed-goto extension: `&&L` is the address of label L within - * the current function, and `goto *p;` jumps to such an address. This - * is the lowering every cc1-like backend uses (and what the toy - * frontend ultimately compiles to via the C target). */ -void c_load_label_addr(CgTarget* T, Operand dst, Label l) { - CTarget* t = (CTarget*)T; - char buf[24]; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: load_label_addr dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "(void*)&&"); - c_label_name(l, buf, sizeof buf); - cbuf_puts(&t->body, buf); - c_emit_local_assign_close(t); -} - -void c_indirect_branch(CgTarget* T, Operand addr, const Label* valid_targets, - u32 ntargets) { - CTarget* t = (CTarget*)T; - (void)valid_targets; - (void)ntargets; - if (t->last_was_terminator) return; - cbuf_puts(&t->body, " goto *"); - c_emit_operand(t, addr); - cbuf_puts(&t->body, ";\n"); - t->last_was_terminator = 1; -} - -/* ===== function-local static label-address data ===== */ - -static int c_is_local_static_sym(CTarget* t, ObjSymId sym) { - for (u32 i = 0; i < t->local_static_nsyms; ++i) { - if (t->local_static_syms[i] == sym) return 1; - } - return 0; -} - -static void c_mark_local_static_sym(CTarget* t, ObjSymId sym) { - Heap* h = t->c->ctx->heap; - if (sym == OBJ_SYM_NONE || c_is_local_static_sym(t, sym)) return; - if (t->local_static_nsyms + 1u > t->local_static_syms_cap) { - u32 oldcap = t->local_static_syms_cap; - u32 newcap = oldcap ? oldcap * 2u : 16u; - ObjSymId* ns = (ObjSymId*)h->realloc( - h, t->local_static_syms, oldcap * sizeof(*t->local_static_syms), - newcap * sizeof(*t->local_static_syms), _Alignof(ObjSymId)); - if (!ns) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: out of memory"); - } - t->local_static_syms = ns; - t->local_static_syms_cap = newcap; - } - t->local_static_syms[t->local_static_nsyms++] = sym; -} - -static void c_grow_local_static_entries(CTarget* t, u32 want) { - Heap* h = t->c->ctx->heap; - if (want <= t->local_static_entries_cap) return; - u32 oldcap = t->local_static_entries_cap; - u32 newcap = oldcap ? oldcap * 2u : 8u; - while (newcap < want) newcap *= 2u; - CLocalStaticLabelEntry* ne = (CLocalStaticLabelEntry*)h->realloc( - h, t->local_static_entries, oldcap * sizeof(*t->local_static_entries), - newcap * sizeof(*t->local_static_entries), - _Alignof(CLocalStaticLabelEntry)); - if (!ne) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: out of memory"); - } - t->local_static_entries = ne; - t->local_static_entries_cap = newcap; -} - -int c_local_static_data_begin(CgTarget* T, const CGLocalStaticDataDesc* desc) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!t->cur_fn) { - compiler_panic(t->c, loc, - "C target: function-local static data outside function"); - } - if (t->local_static_active) { - compiler_panic(t->c, loc, - "C target: nested function-local static data definition"); - } - const CgType* ty = cg_type_get(t->c, api_unalias_type(t->c, desc->type)); - if (!ty) { - compiler_panic(t->c, loc, "C target: unknown local static type %u", - (unsigned)desc->type); - } - - u64 count = 1; - int is_array = 0; - CfreeCgTypeId elem = desc->type; - if (ty->kind == CFREE_CG_TYPE_ARRAY) { - is_array = 1; - count = ty->array.count; - elem = ty->array.elem; - ty = cg_type_get(t->c, api_unalias_type(t->c, elem)); - } - if (!ty || ty->kind != CFREE_CG_TYPE_PTR) { - return 0; - } - if (count > UINT32_MAX) { - compiler_panic(t->c, loc, "C target: local static pointer table too large"); - } - - c_grow_local_static_entries(t, (u32)count); - for (u32 i = 0; i < (u32)count; ++i) { - t->local_static_entries[i].label = LABEL_NONE; - t->local_static_entries[i].addend = 0; - t->local_static_entries[i].has_label = 0; - } - t->local_static_nentries = (u32)count; - t->local_static_sym = desc->sym; - t->local_static_type = desc->type; - t->local_static_count = count; - t->local_static_offset = 0; - t->local_static_ptr_width = (u32)cg_type_size(t->c, elem); - t->local_static_align = - desc->align ? desc->align : cg_type_align(t->c, desc->type); - t->local_static_active = 1; - t->local_static_is_array = (u8)is_array; - t->local_static_readonly = - (desc->attrs.flags & CFREE_CG_DATADEF_READONLY) ? 1u : 0u; - c_mark_local_static_sym(t, desc->sym); - return 1; -} - -void c_local_static_data_write(CgTarget* T, const u8* data, u64 len) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!t->local_static_active || !len) return; - if (data) { - for (u64 i = 0; i < len; ++i) { - if (data[i] != 0) { - compiler_panic(t->c, loc, - "C target: function-local static label table supports " - "only zero bytes and label addresses"); - } - } - } - t->local_static_offset += len; -} - -void c_local_static_data_label_addr(CgTarget* T, Label target, i64 addend, - u32 width, u32 address_space) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - (void)address_space; - if (!t->local_static_active) { - compiler_panic(t->c, loc, - "C target: label address outside local static data"); - } - if (width != t->local_static_ptr_width) { - compiler_panic(t->c, loc, - "C target: label address width %u does not match pointer " - "width %u", - (unsigned)width, (unsigned)t->local_static_ptr_width); - } - if ((t->local_static_offset % t->local_static_ptr_width) != 0) { - compiler_panic(t->c, loc, - "C target: unaligned label address in local static data"); - } - u64 idx = t->local_static_offset / t->local_static_ptr_width; - if (idx >= t->local_static_count) { - compiler_panic(t->c, loc, - "C target: too many local static label table entries"); - } - CLocalStaticLabelEntry* e = &t->local_static_entries[(u32)idx]; - if (e->has_label) { - compiler_panic(t->c, loc, - "C target: duplicate local static label table entry"); - } - e->label = target; - e->addend = addend; - e->has_label = 1; - t->local_static_offset += width; -} - -static void c_emit_local_static_label_expr(CTarget* t, - const CLocalStaticLabelEntry* e) { - char lbuf[24]; - if (!e->has_label) { - cbuf_puts(&t->decls, "(void*)0"); - return; - } - if (e->addend == 0) { - cbuf_puts(&t->decls, "&&"); - c_label_name(e->label, lbuf, sizeof lbuf); - cbuf_puts(&t->decls, lbuf); - return; - } - cbuf_puts(&t->decls, "(void*)((char*)&&"); - c_label_name(e->label, lbuf, sizeof lbuf); - cbuf_puts(&t->decls, lbuf); - cbuf_puts(&t->decls, " + "); - cbuf_put_i64(&t->decls, e->addend); - cbuf_puts(&t->decls, ")"); -} - -void c_local_static_data_end(CgTarget* T) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (!t->local_static_active) return; - u64 total_size = t->local_static_count * t->local_static_ptr_width; - if (t->local_static_offset > total_size) { - compiler_panic(t->c, loc, - "C target: local static initializer exceeds object size"); - } - const char* nm = c_sym_name(t, t->local_static_sym); - cbuf_puts(&t->decls, " static __attribute__((unused)) "); - cbuf_puts(&t->decls, "_Alignas("); - cbuf_put_u64(&t->decls, t->local_static_align ? t->local_static_align : 1); - cbuf_puts(&t->decls, ") void* "); - if (t->local_static_readonly) cbuf_puts(&t->decls, "const "); - cbuf_puts(&t->decls, nm); - if (t->local_static_is_array) { - cbuf_puts(&t->decls, "["); - cbuf_put_u64(&t->decls, t->local_static_count); - cbuf_puts(&t->decls, "]"); - } - cbuf_puts(&t->decls, " = {"); - for (u32 i = 0; i < t->local_static_nentries; ++i) { - if (i > 0) cbuf_putc(&t->decls, ','); - if ((i & 3u) == 0) cbuf_puts(&t->decls, "\n "); - c_emit_local_static_label_expr(t, &t->local_static_entries[i]); - } - cbuf_puts(&t->decls, "\n };\n"); - - t->local_static_active = 0; - t->local_static_sym = OBJ_SYM_NONE; - t->local_static_type = CFREE_CG_TYPE_NONE; - t->local_static_count = 0; - t->local_static_offset = 0; - t->local_static_ptr_width = 0; - t->local_static_align = 0; - t->local_static_nentries = 0; - t->local_static_is_array = 0; - t->local_static_readonly = 0; -} - -/* ===== local, local_addr ===== */ - -CGLocal c_local(CgTarget* T, const CGLocalDesc* d) { - CTarget* t = (CTarget*)T; - t->next_local += 1u; - if (t->next_local == CG_LOCAL_NONE) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: semantic local id exhausted"); - return CG_LOCAL_NONE; - } - c_ensure_local(t, (CGLocal)t->next_local, d->type); - return (CGLocal)t->next_local; -} - -void c_local_addr(CgTarget* T, Operand dst, const CGLocalDesc* d, - CGLocal s) { - CTarget* t = (CTarget*)T; - (void)d; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: local_addr dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - c_ensure_local(t, s, d->type); - char buf[24]; - c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "&"); - c_local_name(s, buf, sizeof buf); - cbuf_puts(&t->body, buf); - c_emit_local_assign_close(t); -} - -/* ===== convert ===== */ - -void c_convert(CgTarget* T, ConvKind k, Operand dst, Operand src) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: convert dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - char buf[24]; - c_local_name(dst.v.local, buf, sizeof buf); - - if (k == CV_BITCAST) { - /* Same-size reinterpretation. Use __builtin_memcpy through a temp so - * neither aliasing nor representation assumptions creep in. The temp - * lives in its own `{ ... }` block, so no name collision tracking. */ - u32 id = ++t->next_tmp; - cbuf_puts(&t->body, " { "); - c_emit_type(t, &t->body, src.type); - cbuf_puts(&t->body, " __bc"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, " = "); - c_emit_operand(t, src); - cbuf_puts(&t->body, "; __builtin_memcpy(&"); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ", &__bc"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, ", sizeof __bc"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, "); }\n"); - return; - } - - if (c_type_is_bool(t, dst.type)) { - c_emit_local_assign_open(t, dst.v.local, dst.type); - cbuf_puts(&t->body, "("); - c_emit_type(t, &t->body, dst.type); - cbuf_puts(&t->body, ")("); - c_emit_operand(t, src); - cbuf_puts(&t->body, " != 0)"); - c_emit_local_assign_close(t); - return; - } - - /* Integer and float conversions: a C cast does the right thing once the - * source is first cast to the appropriate signedness (for SEXT/ZEXT and - * ITOF_S/U / FTOI_S/U). */ - int src_signed = 1; - switch (k) { - case CV_ZEXT: - case CV_ITOF_U: - case CV_FTOI_U: - src_signed = 0; - break; - default: - src_signed = 1; - break; - } - - /* The cast `(dst.type)(src)` produces a value of dst.type. */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - cbuf_puts(&t->body, "("); - c_emit_type(t, &t->body, dst.type); - cbuf_puts(&t->body, ")"); - if (k == CV_SEXT || k == CV_ZEXT) { - c_emit_operand_signed(t, src, src_signed); - } else if (k == CV_TRUNC && c_operand_is_ptr_typed(t, src)) { - /* Casting a pointer directly to a narrower integer trips - * -Wvoid-pointer-to-int-cast (and -Wpointer-to-int-cast). Bridge - * through uintptr_t. */ - cbuf_puts(&t->body, "((uintptr_t)"); - c_emit_operand(t, src); - cbuf_puts(&t->body, ")"); - } else { - /* TRUNC / FTOI / ITOF / FEXT / FTRUNC: rely on C cast semantics. */ - c_emit_operand(t, src); - } - c_emit_local_assign_close(t); -} - -/* === call === */ - -static CfreeCgTypeId c_call_arg_type(CTarget* t, const CgType* fty, - const CGCallDesc* d, u32 i) { - if (i < fty->func.nparams) return fty->func.params[i].type; - return c_local_type_or_panic(t, d->args[i]); -} - -static void c_emit_call_arg(CTarget* t, const CgType* fty, - const CGCallDesc* d, u32 i) { - CfreeCgTypeId ty = c_call_arg_type(t, fty, d, i); - c_ensure_local(t, d->args[i], ty); - c_emit_operand(t, c_op_local(d->args[i], ty)); -} - -static void c_emit_call_expr(CTarget* t, const CgType* fty, - const CGCallDesc* d, - const CfreeCgTypeId* result_types) { - if (d->callee.kind == OPK_GLOBAL) { - c_ensure_forward_decl_with_results( - t, d->callee.v.global.sym, d->fn_type, - d->nresults > 1 ? result_types : NULL, d->nresults); - cbuf_puts(&t->body, c_sym_name(t, d->callee.v.global.sym)); - } else if (d->callee.kind == OPK_LOCAL) { - cbuf_puts(&t->body, "(("); - if (d->nresults > 1) { - c_emit_tuple_type_name(t, &t->body, result_types, d->nresults); - cbuf_puts(&t->body, " (*)("); - if (fty->func.nparams == 0 && !fty->func.abi_variadic) { - cbuf_puts(&t->body, "void"); - } else { - for (u32 i = 0; i < fty->func.nparams; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_type(t, &t->body, fty->func.params[i].type); - } - if (fty->func.abi_variadic) { - if (fty->func.nparams > 0) cbuf_puts(&t->body, ", "); - cbuf_puts(&t->body, "..."); - } - } - cbuf_puts(&t->body, ")"); - } else { - const char* fp = c_typedef_name(t, d->fn_type); - c_ensure_typedef(t, d->fn_type); - cbuf_puts(&t->body, fp); - } - cbuf_puts(&t->body, ")"); - c_emit_operand(t, d->callee); - cbuf_puts(&t->body, ")"); - } else { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: callee kind %d not supported", - (int)d->callee.kind); - } - - cbuf_puts(&t->body, "("); - for (u32 i = 0; i < d->nargs; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_call_arg(t, fty, d, i); - } - cbuf_puts(&t->body, ")"); -} - -const char* c_tail_call_unrealizable_reason(CgTarget* T, - const CGCallDesc* d) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, d->fn_type)); - if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { - compiler_panic(t->c, loc, "C target: tail call: bad fn_type"); - } - const CgType* caller = - t->cur_fn - ? cg_type_get(t->c, api_unalias_type(t->c, t->cur_fn->fn_type)) - : NULL; - if (!caller || caller->kind != CFREE_CG_TYPE_FUNC) { - compiler_panic(t->c, loc, "C target: tail call outside function"); - } - if (caller->func.abi_variadic) { - return "C target: caller variadic tail call not yet supported by clang " - "musttail"; - } - if (fty->func.abi_variadic) { - return "C target: variadic tail call not yet supported by clang musttail"; - } - if (caller->func.nparams != fty->func.nparams) { - return "C target: tail call with differing parameter counts not yet " - "supported by clang musttail"; - } - return NULL; -} - -void c_call(CgTarget* T, const CGCallDesc* d) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - Heap* h = t->c->ctx->heap; - - const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, d->fn_type)); - if (!fty || fty->kind != CFREE_CG_TYPE_FUNC) { - compiler_panic(t->c, loc, "C target: call: bad fn_type"); - } - CfreeCgTypeId ret_type = fty->func.ret; - int is_tail = (d->flags & CG_CALL_TAIL) != 0; - CfreeCgTypeId* result_types = NULL; - if (d->nresults > 1) { - result_types = (CfreeCgTypeId*)h->alloc( - h, sizeof(CfreeCgTypeId) * d->nresults, _Alignof(CfreeCgTypeId)); - if (!result_types) { - compiler_panic(t->c, loc, "C target: out of memory"); - return; - } - for (u32 i = 0; i < d->nresults; ++i) - result_types[i] = c_local_type_or_panic(t, d->results[i]); - c_ensure_tuple_typedef(t, result_types, d->nresults); - } - - if (is_tail) { - cbuf_puts(&t->body, " __attribute__((musttail)) return "); - c_emit_call_expr(t, fty, d, result_types); - cbuf_puts(&t->body, ";\n"); - t->last_was_terminator = 1; - } else if (d->nresults == 0) { - cbuf_puts(&t->body, " "); - c_emit_call_expr(t, fty, d, result_types); - cbuf_puts(&t->body, ";\n"); - } else if (d->nresults == 1) { - c_ensure_local(t, d->results[0], ret_type); - c_emit_local_assign_open(t, d->results[0], ret_type); - c_emit_call_expr(t, fty, d, result_types); - c_emit_local_assign_close(t); - } else { - char tmp[32]; - u32 tmp_id = t->next_tmp++; - snprintf(tmp, sizeof tmp, "__cfree_call_%u", (unsigned)tmp_id); - cbuf_puts(&t->body, " "); - c_emit_tuple_type_name(t, &t->body, result_types, d->nresults); - cbuf_putc(&t->body, ' '); - cbuf_puts(&t->body, tmp); - cbuf_puts(&t->body, " = "); - c_emit_call_expr(t, fty, d, result_types); - cbuf_puts(&t->body, ";\n"); - for (u32 i = 0; i < d->nresults; ++i) { - c_ensure_local(t, d->results[i], result_types[i]); - c_emit_local_assign_open(t, d->results[i], result_types[i]); - cbuf_puts(&t->body, tmp); - cbuf_puts(&t->body, ".f"); - cbuf_put_u64(&t->body, (u64)i); - c_emit_local_assign_close(t); - } - } - if (result_types) h->free(h, result_types, - sizeof(CfreeCgTypeId) * d->nresults); -} - -/* === load / store === */ - -void c_load(CgTarget* T, Operand dst, Operand addr, MemAccess m) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: load dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - CfreeCgTypeId access_ty = m.type ? m.type : dst.type; - if (c_type_is_aggregate(t, access_ty) && !c_type_is_aggregate(t, dst.type)) - access_ty = dst.type; - /* The deref `*(access_ty*)addr` produces a value of access_ty. */ - c_emit_local_assign_open(t, dst.v.local, access_ty); - c_emit_addr_deref(t, addr, access_ty); - c_emit_local_assign_close(t); -} - -void c_store(CgTarget* T, Operand addr, Operand src, MemAccess m) { - CTarget* t = (CTarget*)T; - CfreeCgTypeId access_ty = m.type ? m.type : src.type; - if (c_type_is_aggregate(t, access_ty) && !c_type_is_aggregate(t, src.type)) - access_ty = src.type; - cbuf_puts(&t->body, " "); - c_emit_addr_deref(t, addr, access_ty); - /* c_emit_operand_as bridges int/ptr crossings through uintptr_t so - * roundtrips don't trip `-Wint-conversion`. */ - cbuf_puts(&t->body, " = "); - c_emit_operand_as(t, src, access_ty); - cbuf_puts(&t->body, ";\n"); -} - -void c_addr_of(CgTarget* T, Operand dst, Operand lv) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: addr_of dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - /* `c_emit_lvalue_addr` casts its output to dst.type already. */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - c_emit_lvalue_addr(t, lv, dst.type); - c_emit_local_assign_close(t); -} - -void c_ret(CgTarget* T, const CGLocal* values, u32 nvalues) { - CTarget* t = (CTarget*)T; - /* Already-terminated block: this ret is unreachable (the frontend's - * defensive `return 0;` epilogue lands here right after a user return). */ - if (t->last_was_terminator) return; - /* CG emits a defensive ret_void epilogue at the end of every function. For - * a non-void function that's unreachable; emitting a bare `return;` would - * trip -Wreturn-type. Spell it as `__builtin_unreachable()` so the host C - * compiler sees the path is dead without us inventing a fake value. */ - if (nvalues == 0 && t->cur_fn) { - if (t->cur_fn->nresults != 0) { - cbuf_puts(&t->body, " __builtin_unreachable();\n"); - t->last_was_terminator = 1; - return; - } - } - cbuf_puts(&t->body, " return"); - if (nvalues == 1) { - cbuf_puts(&t->body, " "); - CfreeCgTypeId ret_type = t->cur_fn - ? t->cur_fn->result_types[0] - : (CfreeCgTypeId)0; - const CgType* rty = - ret_type ? cg_type_get(t->c, api_unalias_type(t->c, ret_type)) : NULL; - int is_aggregate = rty && (rty->kind == CFREE_CG_TYPE_RECORD || - rty->kind == CFREE_CG_TYPE_ARRAY); - if (ret_type && !is_aggregate) { - CfreeCgTypeId value_ty = c_local_type_or_panic(t, values[0]); - c_emit_operand_as(t, c_op_local(values[0], value_ty), ret_type); - } else { - c_emit_operand(t, c_op_local(values[0], ret_type)); - } - } else if (nvalues > 1) { - const CfreeCgTypeId* result_types = - t->cur_fn ? t->cur_fn->result_types : NULL; - if (!result_types || t->cur_fn->nresults != nvalues) { - compiler_panic(t->c, t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}, - "C target: multi-return shape mismatch"); - return; - } - c_ensure_tuple_typedef(t, result_types, nvalues); - cbuf_puts(&t->body, " ("); - c_emit_tuple_type_name(t, &t->body, result_types, nvalues); - cbuf_puts(&t->body, "){"); - for (u32 i = 0; i < nvalues; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_operand(t, c_op_local(values[i], result_types[i])); - } - cbuf_puts(&t->body, "}"); - } - cbuf_puts(&t->body, ";\n"); - t->last_was_terminator = 1; -} - -/* === alias === - * `cfree_cg_alias` makes alias_sym refer to target_sym's body. In obj-file - * land that's two ObjSyms sharing a (section_id, value); in C source we - * have to spell it out: - * - * ELF/PE → `Ret alias(args) __attribute__((alias("target")));` - * Single definition, true aliasing, &alias == &target. - * Mach-O → emit a thunk `Ret alias(args) { return target(args); }`. - * Clang on Darwin rejects __attribute__((alias)) outright, - * so we fall back to a wrapper. Loses the `&alias==&target` - * identity but preserves call-through semantics, which is - * all the cfree-emitted code path needs. - * - * The emitted decl serves as the alias definition AND a forward prototype - * for callers, so we mark sym_forwarded to dedup against a later c_call. */ -void c_alias(CgTarget* T, ObjSymId alias_sym, ObjSymId target_sym, - CfreeCgTypeId type) { - CTarget* t = (CTarget*)T; - Heap* h = t->c->ctx->heap; - if ((u32)alias_sym >= t->sym_forwarded_cap) { - u32 newcap = t->sym_forwarded_cap ? t->sym_forwarded_cap : 16; - while (newcap <= (u32)alias_sym) newcap *= 2; - u8* nd = - (u8*)h->realloc(h, t->sym_forwarded, t->sym_forwarded_cap, newcap, 1); - if (!nd && newcap) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: out of memory"); - } - for (u32 i = t->sym_forwarded_cap; i < newcap; ++i) nd[i] = 0; - t->sym_forwarded = nd; - t->sym_forwarded_cap = newcap; - } - if (t->sym_forwarded[alias_sym]) return; - t->sym_forwarded[alias_sym] = 1; - const char* alias_name = c_sym_name(t, alias_sym); - const char* target_name = c_sym_name(t, target_sym); - const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, type)); - int is_func = fty && fty->kind == CFREE_CG_TYPE_FUNC; - - if (t->c->target.obj != CFREE_OBJ_MACHO) { - /* Attribute form. Works for both function and object aliases on ELF - * and PE/COFF. */ - c_emit_func_signature(t, &t->forwards, alias_name, type, NULL, 0); - cbuf_puts(&t->forwards, " __attribute__((alias(\""); - cbuf_puts(&t->forwards, target_name); - cbuf_puts(&t->forwards, "\")));\n"); - return; - } - - /* Mach-O thunk fallback. Functions only for v1 — object aliases on - * Darwin would need a more elaborate scheme (see doc/CBACKEND.md). */ - if (!is_func) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: object alias on Mach-O not yet supported"); - } - /* Forward prototype for the target (its full definition lands separately - * via c_func_begin). Also dedup that. */ - c_ensure_forward_decl(t, target_sym, type); - /* `static`? No — alias must be externally visible. */ - c_emit_func_signature(t, &t->forwards, alias_name, type, NULL, 0); - cbuf_puts(&t->forwards, " { "); - CfreeCgTypeId ret_type = cg_type_func_ret_id(t->c, type); - if (!cg_type_is_void(t->c, ret_type)) cbuf_puts(&t->forwards, "return "); - cbuf_puts(&t->forwards, target_name); - cbuf_puts(&t->forwards, "("); - for (u32 i = 0; i < fty->func.nparams; ++i) { - if (i > 0) cbuf_puts(&t->forwards, ", "); - cbuf_puts(&t->forwards, "p"); - cbuf_put_u64(&t->forwards, (u64)i); - } - cbuf_puts(&t->forwards, "); }\n"); -} - -/* === intrinsic === - * - * All cfree IntrinKinds map onto gcc/clang `__builtin_*` builtins, which - * the host C compiler then turns into the appropriate sequence (inline op, - * libcall, runtime CAS, etc.). This is exactly the seam the doc described: - * cfree records intent, the downstream toolchain picks the mechanism. - * - * Operand shapes follow arch.h §IntrinKind. */ - -static const char* c_bitop_builtin(IntrinKind k, u32 width) { - switch (k) { - case INTRIN_POPCOUNT: - if (width == 32) return "__builtin_popcount"; - if (width == 64) return "__builtin_popcountll"; - if (width == 16 || width == 8) return "__builtin_popcount"; - return NULL; - case INTRIN_CTZ: - if (width == 32) return "__builtin_ctz"; - if (width == 64) return "__builtin_ctzll"; - if (width == 16 || width == 8) return "__builtin_ctz"; - return NULL; - case INTRIN_CLZ: - if (width == 32) return "__builtin_clz"; - if (width == 64) return "__builtin_clzll"; - if (width == 16 || width == 8) return "__builtin_clz"; - return NULL; - case INTRIN_BSWAP16: - return "__builtin_bswap16"; - case INTRIN_BSWAP32: - return "__builtin_bswap32"; - case INTRIN_BSWAP64: - return "__builtin_bswap64"; - default: - return NULL; - } -} - -static const char* c_overflow_builtin(IntrinKind k) { - switch (k) { - case INTRIN_SADD_OVERFLOW: - case INTRIN_UADD_OVERFLOW: - return "__builtin_add_overflow"; - case INTRIN_SSUB_OVERFLOW: - case INTRIN_USUB_OVERFLOW: - return "__builtin_sub_overflow"; - case INTRIN_SMUL_OVERFLOW: - case INTRIN_UMUL_OVERFLOW: - return "__builtin_mul_overflow"; - default: - return NULL; - } -} - -void c_intrinsic(CgTarget* T, IntrinKind k, Operand* dsts, u32 ndst, - const Operand* args, u32 narg) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - switch (k) { - case INTRIN_UNREACHABLE: - cbuf_puts(&t->body, " __builtin_unreachable();\n"); - return; - case INTRIN_TRAP: - cbuf_puts(&t->body, " __builtin_trap();\n"); - return; - case INTRIN_PREFETCH: { - cbuf_puts(&t->body, " __builtin_prefetch("); - for (u32 i = 0; i < narg; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_operand(t, args[i]); - } - cbuf_puts(&t->body, ");\n"); - return; - } - case INTRIN_ASSUME_ALIGNED: { - /* dsts[0] is the result local (pointer); args = (ptr, align [, ofs]) */ - if (ndst != 1) { - compiler_panic(t->c, loc, - "C target: assume_aligned: expected 1 dst, got %u", - (unsigned)ndst); - } - c_ensure_local(t, dsts[0].v.local, dsts[0].type); - /* Returns void*; bridge to dst pointer type. */ - c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "__builtin_assume_aligned("); - for (u32 i = 0; i < narg; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_operand(t, args[i]); - } - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); - return; - } - case INTRIN_EXPECT: { - /* dsts[0] = __builtin_expect(args[0], args[1]) but typed via long. */ - if (ndst != 1 || narg != 2) { - compiler_panic(t->c, loc, - "C target: expect: bad shape (ndst=%u narg=%u)", - (unsigned)ndst, (unsigned)narg); - } - c_ensure_local(t, dsts[0].v.local, dsts[0].type); - /* Returns `long`; dst.type may be a narrower int — keep the bridge. */ - c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "__builtin_expect((long)"); - c_emit_operand(t, args[0]); - cbuf_puts(&t->body, ", (long)"); - c_emit_operand(t, args[1]); - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); - return; - } - case INTRIN_POPCOUNT: - case INTRIN_CTZ: - case INTRIN_CLZ: - case INTRIN_BSWAP16: - case INTRIN_BSWAP32: - case INTRIN_BSWAP64: { - if (ndst != 1 || narg != 1) { - compiler_panic(t->c, loc, - "C target: bit-intrin: bad shape (ndst=%u narg=%u)", - (unsigned)ndst, (unsigned)narg); - } - u32 w = c_int_width_for_signedness(t, args[0].type); - const char* fn = c_bitop_builtin(k, w); - if (!fn) { - compiler_panic(t->c, loc, "C target: bit-intrin width %u unsupported", - (unsigned)w); - } - c_ensure_local(t, dsts[0].v.local, dsts[0].type); - /* __builtin_popcount/ctz/clz return `int`; bswap returns its input - * type. Narrow to dst.type via the bridge. */ - c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, fn); - cbuf_puts(&t->body, "("); - c_emit_operand(t, args[0]); - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); - return; - } - case INTRIN_MEMCPY: - case INTRIN_MEMMOVE: - case INTRIN_MEMSET: { - const char* fn = (k == INTRIN_MEMCPY) ? "__builtin_memcpy" - : (k == INTRIN_MEMMOVE) ? "__builtin_memmove" - : "__builtin_memset"; - cbuf_puts(&t->body, " "); - cbuf_puts(&t->body, fn); - cbuf_puts(&t->body, "("); - for (u32 i = 0; i < narg; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - /* The pointer operands (dst for all three; src for mem{cpy,move}) - * may be typed as a plain integer local when they come from address - * arithmetic, which the C target declares as int64_t. The builtins - * take void*, so cast explicitly to avoid -Wint-conversion. */ - int is_ptr_arg = - (i == 0) || (i == 1 && (k == INTRIN_MEMCPY || k == INTRIN_MEMMOVE)); - if (is_ptr_arg) cbuf_puts(&t->body, "(void*)"); - c_emit_operand(t, args[i]); - } - cbuf_puts(&t->body, ");\n"); - return; - } - case INTRIN_SADD_OVERFLOW: - case INTRIN_UADD_OVERFLOW: - case INTRIN_SSUB_OVERFLOW: - case INTRIN_USUB_OVERFLOW: - case INTRIN_SMUL_OVERFLOW: - case INTRIN_UMUL_OVERFLOW: { - /* dsts[0] = value local, dsts[1] = i1 overflow flag. - * - * Signedness comes from the intrinsic kind, but cfree's CG int type - * is width-only and the C target declares every result as a signed - * fixed-width (int{8,16,32,64}_t). __builtin_*_overflow keys its - * overflow check on the result type, so passing the signed local - * directly makes a UADD test as if it were signed and miss true - * unsigned overflow. Wrap the call in a block with a scratch result - * of the right signedness and copy it back through the int/uint - * bridge. */ - if (ndst != 2 || narg != 2) { - compiler_panic(t->c, loc, "C target: overflow-intrin: bad shape"); - } - int is_unsigned = - (k == INTRIN_UADD_OVERFLOW || k == INTRIN_USUB_OVERFLOW || - k == INTRIN_UMUL_OVERFLOW); - const char* fn = c_overflow_builtin(k); - c_ensure_local(t, dsts[0].v.local, dsts[0].type); - c_ensure_local(t, dsts[1].v.local, dsts[1].type); - char vbuf[24], obuf[24]; - c_local_name(dsts[0].v.local, vbuf, sizeof vbuf); - c_local_name(dsts[1].v.local, obuf, sizeof obuf); - u32 w = c_int_width_for_signedness(t, dsts[0].type); - const char* sty = c_int_type_name_for_width(w, !is_unsigned); - if (!sty) { - compiler_panic(t->c, loc, - "C target: overflow-intrin: unsupported width %u", - (unsigned)w); - } - cbuf_puts(&t->body, " { "); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, " __ovsc; "); - cbuf_puts(&t->body, obuf); - cbuf_puts(&t->body, " = ("); - c_emit_type(t, &t->body, dsts[1].type); - cbuf_puts(&t->body, ")"); - cbuf_puts(&t->body, fn); - cbuf_puts(&t->body, "(("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")"); - c_emit_operand(t, args[0]); - cbuf_puts(&t->body, ", ("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")"); - c_emit_operand(t, args[1]); - cbuf_puts(&t->body, ", &__ovsc); "); - cbuf_puts(&t->body, vbuf); - cbuf_puts(&t->body, " = ("); - c_emit_type(t, &t->body, dsts[0].type); - cbuf_puts(&t->body, ")__ovsc; }\n"); - return; - } - case INTRIN_SETJMP: { - t->need_setjmp = 1; - if (ndst != 1 || narg != 1) { - compiler_panic(t->c, loc, "C target: setjmp: bad shape"); - } - c_ensure_local(t, dsts[0].v.local, dsts[0].type); - /* setjmp returns `int`; bridge to dst.type. */ - c_emit_local_assign_open(t, dsts[0].v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "setjmp(*(jmp_buf*)("); - c_emit_operand(t, args[0]); - cbuf_puts(&t->body, "))"); - c_emit_local_assign_close(t); - return; - } - case INTRIN_LONGJMP: { - t->need_setjmp = 1; - cbuf_puts(&t->body, " longjmp(*(jmp_buf*)("); - c_emit_operand(t, args[0]); - cbuf_puts(&t->body, "), (int)"); - c_emit_operand(t, args[1]); - cbuf_puts(&t->body, ");\n"); - return; - } - case INTRIN_NONE: - default: - compiler_panic(t->c, loc, "C target: intrinsic kind %d not handled", - (int)k); - } -} - -/* === alloca === */ - -void c_alloca(CgTarget* T, Operand dst, Operand size, u32 align) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: alloca dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - /* __builtin_alloca returns `void*`; dst.type is typically void* too. */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - if (align > 1) { - /* gcc has __builtin_alloca_with_align taking bits, not bytes. */ - cbuf_puts(&t->body, "__builtin_alloca_with_align("); - c_emit_operand(t, size); - cbuf_puts(&t->body, ", "); - cbuf_put_u64(&t->body, (u64)align * 8u); - cbuf_puts(&t->body, ")"); - } else { - cbuf_puts(&t->body, "__builtin_alloca("); - c_emit_operand(t, size); - cbuf_puts(&t->body, ")"); - } - c_emit_local_assign_close(t); -} - -/* === varargs === - * - * The C-target va_list is the host toolchain's `va_list` from <stdarg.h>. - * The first arg of all va_* is `ap_addr` - the address of the va_list local. - * We deref to get the va_list lvalue C's macros expect. */ - -void c_va_start(CgTarget* T, Operand ap_addr) { - CTarget* t = (CTarget*)T; - t->need_stdarg = 1; - /* va_start needs the "last named parameter". CG doesn't pass that to the - * backend; gcc/clang accept any non-modified ident here for variadic - * compatibility — feed the synthesized parameter name `p<nparams-1>` from - * the enclosing function. */ - const CGFuncDesc* fd = t->cur_fn; - SrcLoc loc = fd ? fd->loc : (SrcLoc){0, 0, 0}; - if (!fd) compiler_panic(t->c, loc, "C target: va_start outside function"); - const CgType* fty = cg_type_get(t->c, api_unalias_type(t->c, fd->fn_type)); - if (!fty || fty->kind != CFREE_CG_TYPE_FUNC || fty->func.nparams == 0) { - compiler_panic(t->c, loc, - "C target: va_start in non-variadic function shape"); - } - cbuf_puts(&t->body, " __builtin_va_start(*(va_list*)("); - c_emit_operand(t, ap_addr); - cbuf_puts(&t->body, "), p"); - cbuf_put_u64(&t->body, (u64)(fty->func.nparams - 1u)); - cbuf_puts(&t->body, ");\n"); -} - -void c_va_end(CgTarget* T, Operand ap_addr) { - CTarget* t = (CTarget*)T; - cbuf_puts(&t->body, " __builtin_va_end(*(va_list*)("); - c_emit_operand(t, ap_addr); - cbuf_puts(&t->body, "));\n"); -} - -void c_va_copy(CgTarget* T, Operand dst_addr, Operand src_addr) { - CTarget* t = (CTarget*)T; - cbuf_puts(&t->body, " __builtin_va_copy(*(va_list*)("); - c_emit_operand(t, dst_addr); - cbuf_puts(&t->body, "), *(va_list*)("); - c_emit_operand(t, src_addr); - cbuf_puts(&t->body, "));\n"); -} - -void c_va_arg(CgTarget* T, Operand dst, Operand ap_addr, CfreeCgTypeId ty) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: va_arg dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - /* __builtin_va_arg yields a value of `ty`. */ - c_emit_local_assign_open(t, dst.v.local, ty); - cbuf_puts(&t->body, "__builtin_va_arg(*(va_list*)("); - c_emit_operand(t, ap_addr); - cbuf_puts(&t->body, "), "); - c_emit_type(t, &t->body, ty); - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); -} - -/* === copy_bytes / set_bytes === */ - -void c_copy_bytes(CgTarget* T, Operand dst_addr, Operand src_addr, - AggregateAccess m) { - CTarget* t = (CTarget*)T; - c_assert_no_index(t, dst_addr, "copy_bytes dst"); - c_assert_no_index(t, src_addr, "copy_bytes src"); - /* dst/src may be plain integer regs from address arithmetic (declared - * int64_t); __builtin_memcpy takes void*, so cast to avoid - * -Wint-conversion. */ - cbuf_puts(&t->body, " __builtin_memcpy((void*)"); - c_emit_copy_addr(t, dst_addr); - cbuf_puts(&t->body, ", (void*)"); - c_emit_copy_addr(t, src_addr); - cbuf_puts(&t->body, ", "); - cbuf_put_u64(&t->body, (u64)m.size); - cbuf_puts(&t->body, ");\n"); -} - -static void c_emit_copy_addr(CTarget* t, Operand addr) { - char buf[24]; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - switch (addr.kind) { - case OPK_LOCAL: - c_ensure_local(t, addr.v.local, addr.type); - if (c_operand_is_ptr_typed(t, addr)) { - c_emit_operand(t, addr); - } else { - cbuf_putc(&t->body, '&'); - c_local_name(addr.v.local, buf, sizeof buf); - cbuf_puts(&t->body, buf); - } - return; - case OPK_GLOBAL: { - obj_sym_mark_referenced(t->obj, addr.v.global.sym); - cbuf_puts(&t->body, "((char*)&"); - cbuf_puts(&t->body, c_sym_name(t, addr.v.global.sym)); - if (addr.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, addr.v.global.addend); - } - cbuf_putc(&t->body, ')'); - return; - } - case OPK_INDIRECT: - c_emit_indirect_addr_expr(t, c_addr_mode(addr)); - return; - default: - compiler_panic(t->c, loc, - "C target: copy_bytes address operand kind %d not " - "supported", - (int)addr.kind); - } -} - -void c_set_bytes(CgTarget* T, Operand dst_addr, Operand byte_value, - AggregateAccess m) { - CTarget* t = (CTarget*)T; - c_assert_no_index(t, dst_addr, "set_bytes dst"); - /* dst may be a plain integer local from address arithmetic (declared - * int64_t); __builtin_memset takes void*, so cast to avoid - * -Wint-conversion. */ - cbuf_puts(&t->body, " __builtin_memset((void*)"); - c_emit_copy_addr(t, dst_addr); - cbuf_puts(&t->body, ", (int)"); - c_emit_operand(t, byte_value); - cbuf_puts(&t->body, ", "); - cbuf_put_u64(&t->body, (u64)m.size); - cbuf_puts(&t->body, ");\n"); -} - -/* === TLS === - * - * Thread-local data is emitted as `_Thread_local _Alignas(A) uint8_t name[N];` - * during c_emit_data, and tls_addr_of spells `((char*)&name + addend)` with - * the requested pointer type. The host C compiler picks the TLS model. */ - -void c_tls_addr_of(CgTarget* T, Operand dst, ObjSymId sym, i64 addend); - -void c_tls_addr_of(CgTarget* T, Operand dst, ObjSymId sym, i64 addend) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: tls_addr_of dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - const char* nm = c_sym_name(t, sym); - /* RHS spells `(char*)&sym + addend` — pointer type that may not match - * dst.type; keep the bridge to cast through cleanly. */ - c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); - cbuf_puts(&t->body, "((char*)&"); - cbuf_puts(&t->body, nm); - if (addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, addend); - } - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); -} - -/* === bitfields === - * - * cfree CG flattens bitfields to (storage_type, byte_offset, bit_offset, - * bit_width) at the access boundary, so the C target never sees a C-level - * bitfield declaration. We extract/insert via explicit mask+shift on the - * underlying storage unit (a fixed-width unsigned int loaded through the - * usual address-deref path), which sidesteps the C bitfield ABI ambiguity - * entirely. */ - -void c_bitfield_load(CgTarget* T, Operand dst, Operand addr, BitFieldAccess bf); -void c_bitfield_store(CgTarget* T, Operand addr, Operand src, - BitFieldAccess bf); - -/* Returns the unsigned C integer type matching the storage-unit byte size. */ -static const char* c_bf_storage_type(u32 size) { - switch (size) { - case 1: - return "uint8_t"; - case 2: - return "uint16_t"; - case 4: - return "uint32_t"; - case 8: - return "uint64_t"; - default: - return NULL; - } -} - -/* Spell an address expression for a backend-addressable lvalue operand. - * Unlike c_emit_operand, this never reads the object value; it materializes - * the address of the local/global/indirect storage itself. */ -static void c_emit_lvalue_addr_expr_raw(CTarget* t, Operand addr) { - char buf[24]; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - switch (addr.kind) { - case OPK_LOCAL: - cbuf_putc(&t->body, '&'); - c_ensure_local(t, addr.v.local, addr.type); - c_local_name(addr.v.local, buf, sizeof buf); - cbuf_puts(&t->body, buf); - return; - case OPK_GLOBAL: { - obj_sym_mark_referenced(t->obj, addr.v.global.sym); - const char* nm = c_sym_name(t, addr.v.global.sym); - cbuf_puts(&t->body, "((char*)&"); - cbuf_puts(&t->body, nm); - if (addr.v.global.addend != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_i64(&t->body, addr.v.global.addend); - } - cbuf_putc(&t->body, ')'); - return; - } - case OPK_INDIRECT: { - CAddrMode m = c_addr_mode(addr); - if ((u32)m.base >= t->local_cap || !t->local_declared[m.base]) { - compiler_panic(t->c, loc, - "C target: bitfield on undeclared base local v%u", - (unsigned)m.base); - } - cbuf_putc(&t->body, '('); - c_emit_indirect_addr_expr(t, m); - cbuf_putc(&t->body, ')'); - return; - } - default: - compiler_panic(t->c, loc, - "C target: bitfield address on operand kind %d not " - "supported", - (int)addr.kind); - } -} - -/* Spell `*(uintN_t*)((char*)addr + bf.storage_offset)` into the body. */ -static void c_bf_storage_lvalue(CTarget* t, Operand addr, BitFieldAccess bf, - const char* storage_ty) { - cbuf_puts(&t->body, "(*("); - cbuf_puts(&t->body, storage_ty); - cbuf_puts(&t->body, "*)((char*)"); - c_emit_lvalue_addr_expr_raw(t, addr); - if (bf.storage_offset != 0) { - cbuf_puts(&t->body, " + "); - cbuf_put_u64(&t->body, (u64)bf.storage_offset); - } - cbuf_puts(&t->body, "))"); -} - -void c_bitfield_load(CgTarget* T, Operand dst, Operand addr, - BitFieldAccess bf) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: bitfield_load dst must be LOCAL"); - } - c_assert_no_index(t, addr, "bitfield_load"); - if (bf.bit_width == 0) { - /* Zero-width — layout barrier only; nothing to load. Emit a no-op - * assignment so the dst local still gets a defined value. */ - c_ensure_local(t, dst.v.local, dst.type); - /* RHS is the literal 0 (int); narrowing to dst.type is fine. */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - cbuf_puts(&t->body, "0"); - c_emit_local_assign_close(t); - return; - } - const char* sty = c_bf_storage_type(bf.storage.size); - if (!sty) { - compiler_panic(t->c, loc, "C target: bitfield storage size %u unsupported", - (unsigned)bf.storage.size); - } - c_ensure_local(t, dst.v.local, dst.type); - /* RHS is the storage-width int from the mask/shift expression; bridge - * to dst.type so any signedness/width adjustment is explicit. */ - c_emit_local_assign_open(t, dst.v.local, (CfreeCgTypeId)0); - /* For signed bitfields, sign-extend via the standard shift-up / arith-shift- - * down trick on a signed integer of the storage width. For unsigned, mask - * the extracted bits. - * - * Storage is little-endian-bit-indexed on every cfree-supported target - * (LSB-first within a storage unit on x86_64/aarch64/rv64). */ - u32 sw = bf.storage.size * 8u; - if (bf.signed_) { - /* (int_storage_t)((storage << shl) >> shr) where: - * shl = sw - bit_width - bit_offset - * shr = sw - bit_width - * Then cast to dst type. */ - u32 shl = sw - (u32)bf.bit_width - (u32)bf.bit_offset; - u32 shr = sw - (u32)bf.bit_width; - cbuf_puts(&t->body, "(((int"); - cbuf_put_u64(&t->body, (u64)sw); - cbuf_puts(&t->body, "_t)("); - c_bf_storage_lvalue(t, addr, bf, sty); - cbuf_puts(&t->body, " << "); - cbuf_put_u64(&t->body, (u64)shl); - cbuf_puts(&t->body, ")) >> "); - cbuf_put_u64(&t->body, (u64)shr); - cbuf_puts(&t->body, ")"); - } else { - /* ((storage >> bit_offset) & ((1u << bit_width) - 1)) */ - u64 mask = (bf.bit_width >= 64) ? ~(u64)0 : (((u64)1 << bf.bit_width) - 1u); - cbuf_puts(&t->body, "(("); - c_bf_storage_lvalue(t, addr, bf, sty); - cbuf_puts(&t->body, " >> "); - cbuf_put_u64(&t->body, (u64)bf.bit_offset); - cbuf_puts(&t->body, ") & ("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")0x"); - static const char hex[] = "0123456789abcdef"; - int started = 0; - for (int sh = 60; sh >= 0; sh -= 4) { - u32 nib = (u32)((mask >> sh) & 0xfu); - if (nib || started || sh == 0) { - cbuf_putc(&t->body, hex[nib]); - started = 1; - } - } - cbuf_puts(&t->body, ")"); - } - c_emit_local_assign_close(t); -} - -void c_bitfield_store(CgTarget* T, Operand addr, Operand src, - BitFieldAccess bf) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - c_assert_no_index(t, addr, "bitfield_store"); - if (bf.bit_width == 0) return; /* zero-width: no-op */ - const char* sty = c_bf_storage_type(bf.storage.size); - if (!sty) { - compiler_panic(t->c, loc, "C target: bitfield storage size %u unsupported", - (unsigned)bf.storage.size); - } - u64 mask = (bf.bit_width >= 64) ? ~(u64)0 : (((u64)1 << bf.bit_width) - 1u); - /* *(uintN_t*)p = (*(uintN_t*)p & ~(mask << bit_offset)) | - * (((uintN_t)src & mask) << bit_offset); */ - cbuf_puts(&t->body, " "); - c_bf_storage_lvalue(t, addr, bf, sty); - cbuf_puts(&t->body, " = ("); - c_bf_storage_lvalue(t, addr, bf, sty); - cbuf_puts(&t->body, " & ~(("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")0x"); - static const char hex[] = "0123456789abcdef"; - int started = 0; - for (int sh = 60; sh >= 0; sh -= 4) { - u32 nib = (u32)((mask >> sh) & 0xfu); - if (nib || started || sh == 0) { - cbuf_putc(&t->body, hex[nib]); - started = 1; - } - } - cbuf_puts(&t->body, " << "); - cbuf_put_u64(&t->body, (u64)bf.bit_offset); - cbuf_puts(&t->body, ")) | (((("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")"); - c_emit_operand(t, src); - cbuf_puts(&t->body, ") & ("); - cbuf_puts(&t->body, sty); - cbuf_puts(&t->body, ")0x"); - started = 0; - for (int sh = 60; sh >= 0; sh -= 4) { - u32 nib = (u32)((mask >> sh) & 0xfu); - if (nib || started || sh == 0) { - cbuf_putc(&t->body, hex[nib]); - started = 1; - } - } - cbuf_puts(&t->body, ") << "); - cbuf_put_u64(&t->body, (u64)bf.bit_offset); - cbuf_puts(&t->body, ");\n"); -} - -/* === inline asm === - * - * Re-serialize cfree's asm-block IR (template + constraint-bound operands + - * clobbers) as GCC extended asm. The cfree CG already speaks GCC-style - * constraint strings ("r", "=r", "+m", "[name]constraint", matching "0"...), - * so we pass the template through and emit the constraint+operand pairs in - * order. */ - -void c_asm_block(CgTarget* T, const char* tmpl, const AsmConstraint* outs, - u32 no, Operand* oo, const AsmConstraint* ins, u32 ni, - const Operand* io, const Sym* clobs, u32 nc); - -static void c_emit_c_string_literal(CBuf* b, const char* s) { - cbuf_putc(b, '"'); - for (; *s; ++s) { - char ch = *s; - if (ch == '"' || ch == '\\') { - cbuf_putc(b, '\\'); - cbuf_putc(b, ch); - } else if (ch == '\n') { - cbuf_puts(b, "\\n"); - } else if (ch == '\r') { - cbuf_puts(b, "\\r"); - } else if (ch == '\t') { - cbuf_puts(b, "\\t"); - } else if ((unsigned char)ch < 0x20 || (unsigned char)ch >= 0x7f) { - static const char hex[] = "0123456789abcdef"; - cbuf_puts(b, "\\x"); - cbuf_putc(b, hex[((unsigned char)ch >> 4) & 0xfu]); - cbuf_putc(b, hex[(unsigned char)ch & 0xfu]); - } else { - cbuf_putc(b, ch); - } - } - cbuf_putc(b, '"'); -} - -void c_asm_block(CgTarget* T, const char* tmpl, const AsmConstraint* outs, - u32 no, Operand* oo, const AsmConstraint* ins, u32 ni, - const Operand* io, const Sym* clobs, u32 nc) { - CTarget* t = (CTarget*)T; - for (u32 i = 0; i < no; ++i) c_assert_no_index(t, oo[i], "asm_block out"); - for (u32 i = 0; i < ni; ++i) c_assert_no_index(t, io[i], "asm_block in"); - cbuf_puts(&t->body, " __asm__ __volatile__ ("); - c_emit_c_string_literal(&t->body, tmpl ? tmpl : ""); - /* Outputs. */ - cbuf_puts(&t->body, " : "); - for (u32 i = 0; i < no; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - if (outs[i].name) { - cbuf_puts(&t->body, "["); - cbuf_puts(&t->body, pool_slice(t->c->global, outs[i].name).s); - cbuf_puts(&t->body, "] "); - } - c_emit_c_string_literal(&t->body, outs[i].str ? outs[i].str : ""); - cbuf_puts(&t->body, "("); - /* Outputs must be an lvalue. OPK_LOCAL is a plain C local; this - * works directly. OPK_LOCAL / OPK_INDIRECT also produce lvalues. */ - if (oo[i].kind == OPK_LOCAL) { - c_ensure_local(t, oo[i].v.local, oo[i].type); - char rb[24]; - c_local_name(oo[i].v.local, rb, sizeof rb); - cbuf_puts(&t->body, rb); - } else { - c_emit_addr_deref(t, oo[i], oo[i].type); - } - cbuf_puts(&t->body, ")"); - } - /* Inputs. cfree synthesizes a matching `"N"` input for every ASM_INOUT - * output (so its IR sees a fresh read), but gcc treats `+r` outputs as - * already serving the read role and rejects a redundant matching input. - * Drop those synthesized matches when the referenced output is `+`-tied. */ - cbuf_puts(&t->body, " : "); - int emitted_any = 0; - for (u32 i = 0; i < ni; ++i) { - const char* cs = ins[i].str ? ins[i].str : ""; - if (cs[0] >= '0' && cs[0] <= '9') { - u32 idx = (u32)(cs[0] - '0'); - if (idx < no && outs[idx].str && outs[idx].str[0] == '+') continue; - } - if (emitted_any) cbuf_puts(&t->body, ", "); - emitted_any = 1; - if (ins[i].name) { - cbuf_puts(&t->body, "["); - cbuf_puts(&t->body, pool_slice(t->c->global, ins[i].name).s); - cbuf_puts(&t->body, "] "); - } - c_emit_c_string_literal(&t->body, cs); - cbuf_puts(&t->body, "("); - c_emit_operand(t, io[i]); - cbuf_puts(&t->body, ")"); - } - /* Clobbers. */ - cbuf_puts(&t->body, " : "); - for (u32 i = 0; i < nc; ++i) { - if (i > 0) cbuf_puts(&t->body, ", "); - c_emit_c_string_literal(&t->body, pool_slice(t->c->global, clobs[i]).s); - } - cbuf_puts(&t->body, ");\n"); -} - -/* === load_const === - * - * Used by CG for non-integer literal pushes (mainly floats — - * `cfree_cg_push_float`). Bytes are the target's ABI encoding of the value; we - * copy them into the dst local via a static const byte array and __builtin_memcpy - * so any host C compiler sees the same bit pattern. */ - -void c_load_const(CgTarget* T, Operand dst, ConstBytes cb); - -void c_load_const(CgTarget* T, Operand dst, ConstBytes cb) { - CTarget* t = (CTarget*)T; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - if (dst.kind != OPK_LOCAL) { - compiler_panic(t->c, loc, "C target: load_const dst must be LOCAL"); - } - c_ensure_local(t, dst.v.local, dst.type); - char buf[24]; - c_local_name(dst.v.local, buf, sizeof buf); - u32 id = ++t->next_tmp; - cbuf_puts(&t->body, " { static const uint8_t __k"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, "["); - cbuf_put_u64(&t->body, (u64)cb.size); - cbuf_puts(&t->body, "] = {"); - static const char hex[] = "0123456789abcdef"; - for (u32 i = 0; i < cb.size; ++i) { - if (i > 0) cbuf_putc(&t->body, ','); - cbuf_puts(&t->body, "0x"); - cbuf_putc(&t->body, hex[(cb.bytes[i] >> 4) & 0xfu]); - cbuf_putc(&t->body, hex[cb.bytes[i] & 0xfu]); - } - cbuf_puts(&t->body, "}; __builtin_memcpy(&"); - cbuf_puts(&t->body, buf); - cbuf_puts(&t->body, ", __k"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, ", "); - cbuf_put_u64(&t->body, (u64)cb.size); - cbuf_puts(&t->body, "); }\n"); -} - -/* === atomics === - * - * Lowered to gcc/clang's `__atomic_*` generic builtins. The host compiler - * picks the inline sequence vs. libcall and applies the requested memory - * order. cfree's MemOrder enum aligns 1-1 with the `__ATOMIC_*` constants. */ - -static const char* c_memorder_token(MemOrder o) { - switch (o) { - case MO_RELAXED: - return "__ATOMIC_RELAXED"; - case MO_CONSUME: - return "__ATOMIC_CONSUME"; - case MO_ACQUIRE: - return "__ATOMIC_ACQUIRE"; - case MO_RELEASE: - return "__ATOMIC_RELEASE"; - case MO_ACQ_REL: - return "__ATOMIC_ACQ_REL"; - case MO_SEQ_CST: - return "__ATOMIC_SEQ_CST"; - } - return "__ATOMIC_SEQ_CST"; -} - -void c_atomic_load(CgTarget* T, Operand dst, Operand addr, MemAccess m, - MemOrder o); -void c_atomic_store(CgTarget* T, Operand addr, Operand src, MemAccess m, - MemOrder o); -void c_atomic_rmw(CgTarget* T, AtomicOp op, Operand dst, Operand addr, - Operand val, MemAccess m, MemOrder o); -void c_atomic_cas(CgTarget* T, Operand prior, Operand ok, Operand addr, - Operand expected, Operand desired, MemAccess m, MemOrder so, - MemOrder fo); -void c_fence(CgTarget* T, MemOrder o); - -void c_atomic_load(CgTarget* T, Operand dst, Operand addr, MemAccess m, - MemOrder o) { - CTarget* t = (CTarget*)T; - (void)m; - c_assert_no_index(t, addr, "atomic_load"); - c_ensure_local(t, dst.v.local, dst.type); - /* __atomic_load_n returns a value of the pointed-to type (dst.type). */ - c_emit_local_assign_open(t, dst.v.local, dst.type); - cbuf_puts(&t->body, "__atomic_load_n(("); - c_emit_type(t, &t->body, dst.type); - cbuf_puts(&t->body, "*)"); - c_emit_operand(t, addr); - cbuf_puts(&t->body, ", "); - cbuf_puts(&t->body, c_memorder_token(o)); - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); -} - -void c_atomic_store(CgTarget* T, Operand addr, Operand src, MemAccess m, - MemOrder o) { - CTarget* t = (CTarget*)T; - (void)m; - c_assert_no_index(t, addr, "atomic_store"); - cbuf_puts(&t->body, " __atomic_store_n(("); - c_emit_type(t, &t->body, src.type); - cbuf_puts(&t->body, "*)"); - c_emit_operand(t, addr); - cbuf_puts(&t->body, ", "); - c_emit_operand_as(t, src, src.type); - cbuf_puts(&t->body, ", "); - cbuf_puts(&t->body, c_memorder_token(o)); - cbuf_puts(&t->body, ");\n"); -} - -static const char* c_atomic_op_builtin(AtomicOp op) { - switch (op) { - case AO_XCHG: - return "__atomic_exchange_n"; - case AO_ADD: - return "__atomic_fetch_add"; - case AO_SUB: - return "__atomic_fetch_sub"; - case AO_AND: - return "__atomic_fetch_and"; - case AO_OR: - return "__atomic_fetch_or"; - case AO_XOR: - return "__atomic_fetch_xor"; - case AO_NAND: - return "__atomic_fetch_nand"; - } - return NULL; -} - -void c_atomic_rmw(CgTarget* T, AtomicOp op, Operand dst, Operand addr, - Operand val, MemAccess m, MemOrder o) { - CTarget* t = (CTarget*)T; - (void)m; - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - c_assert_no_index(t, addr, "atomic_rmw"); - const char* fn = c_atomic_op_builtin(op); - if (!fn) { - compiler_panic(t->c, loc, "C target: unknown atomic op %d", (int)op); - } - c_ensure_local(t, dst.v.local, dst.type); - /* __atomic_fetch_* returns the prior value of the pointed-to type. */ - c_emit_local_assign_open(t, dst.v.local, val.type); - cbuf_puts(&t->body, fn); - cbuf_puts(&t->body, "(("); - c_emit_type(t, &t->body, val.type); - cbuf_puts(&t->body, "*)"); - c_emit_operand(t, addr); - cbuf_puts(&t->body, ", "); - c_emit_operand_as(t, val, val.type); - cbuf_puts(&t->body, ", "); - cbuf_puts(&t->body, c_memorder_token(o)); - cbuf_puts(&t->body, ")"); - c_emit_local_assign_close(t); -} - -void c_atomic_cas(CgTarget* T, Operand prior, Operand ok, Operand addr, - Operand expected, Operand desired, MemAccess m, MemOrder so, - MemOrder fo) { - CTarget* t = (CTarget*)T; - (void)m; - c_assert_no_index(t, addr, "atomic_cas"); - /* gcc's __atomic_compare_exchange_n needs a real lvalue holding the - * expected value because it is updated on failure. Materialize a scratch - * local with the compare type, then copy it out to the prior result. */ - c_ensure_local(t, prior.v.local, prior.type); - c_ensure_local(t, ok.v.local, ok.type); - u32 id = ++t->next_tmp; - cbuf_puts(&t->body, " { "); - c_emit_type(t, &t->body, prior.type); - cbuf_puts(&t->body, " __cas"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, " = "); - c_emit_operand_as(t, expected, prior.type); - cbuf_puts(&t->body, "; "); - char ok_name[24], prior_name[24]; - c_local_name(ok.v.local, ok_name, sizeof ok_name); - c_local_name(prior.v.local, prior_name, sizeof prior_name); - cbuf_puts(&t->body, ok_name); - cbuf_puts(&t->body, " = ("); - c_emit_type(t, &t->body, ok.type); - cbuf_puts(&t->body, ")__atomic_compare_exchange_n(("); - c_emit_type(t, &t->body, prior.type); - cbuf_puts(&t->body, "*)"); - c_emit_operand(t, addr); - cbuf_puts(&t->body, ", &__cas"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, ", "); - c_emit_operand_as(t, desired, prior.type); - cbuf_puts(&t->body, ", 0, "); - cbuf_puts(&t->body, c_memorder_token(so)); - cbuf_puts(&t->body, ", "); - cbuf_puts(&t->body, c_memorder_token(fo)); - cbuf_puts(&t->body, "); "); - /* prior local = __cas; */ - cbuf_puts(&t->body, prior_name); - cbuf_puts(&t->body, " = __cas"); - cbuf_put_u64(&t->body, (u64)id); - cbuf_puts(&t->body, "; }\n"); -} - -void c_fence(CgTarget* T, MemOrder o) { - CTarget* t = (CTarget*)T; - cbuf_puts(&t->body, " __atomic_thread_fence("); - cbuf_puts(&t->body, c_memorder_token(o)); - cbuf_puts(&t->body, ");\n"); -} - -/* === set_loc === */ - -static void cbuf_put_line_filename(CBuf* b, CfreeSlice s) { - size_t i; - cbuf_putc(b, '"'); - for (i = 0; i < s.len; ++i) { - { - unsigned char ch = (unsigned char)s.s[i]; - switch (ch) { - case '\\': - case '"': - cbuf_putc(b, '\\'); - cbuf_putc(b, (char)ch); - break; - case '\n': - cbuf_puts(b, "\\n"); - break; - case '\r': - cbuf_puts(b, "\\r"); - break; - case '\t': - cbuf_puts(b, "\\t"); - break; - default: - cbuf_putc(b, (char)ch); - break; - } - } - } - cbuf_putc(b, '"'); -} - -void c_set_loc(CgTarget* T, SrcLoc l) { - CTarget* t = (CTarget*)T; - CfreeSlice file; - - if (!t->cur_fn || l.file_id == 0 || l.line == 0) return; - if (t->have_emitted_loc && t->emitted_loc.file_id == l.file_id && - t->emitted_loc.line == l.line) { - return; - } - - file = cfree_compiler_file_name(t->c, l.file_id); - if (!file.len) return; - - cbuf_puts(&t->body, "#line "); - cbuf_put_u64(&t->body, (u64)l.line); - cbuf_putc(&t->body, ' '); - cbuf_put_line_filename(&t->body, file); - cbuf_putc(&t->body, '\n'); - - t->emitted_loc = l; - t->have_emitted_loc = 1; -} - -/* === data emission === - * - * Walks the ObjBuilder's symbol table at finalize and emits a C declaration - * for every data object — defined or extern. Bytes are emitted verbatim as a - * `uint8_t name[N] = { 0x.., ... }` initializer. Relocations targeting bytes - * inside a defined symbol are spelled as runtime fixups in a constructor; this - * covers both same-TU and cross-TU references uniformly and avoids the C - * static-initializer restrictions on non-constant addresses. - * - * The host C compiler re-applies the Mach-O leading-underscore on link, so the - * C source uses the cfree linker name minus the `_` prefix (matching - * c_sym_name elsewhere in this file). */ - -static int c_is_data_section(const Section* sec) { - if (!sec) return 0; - switch (sec->kind) { - case SEC_DATA: - case SEC_RODATA: - case SEC_BSS: - return 1; - case SEC_OTHER: - /* User-named sections holding allocated data (e.g. `.text.hot` would - * be EXEC, but a custom data section is just SF_ALLOC). */ - return (sec->flags & SF_ALLOC) && !(sec->flags & SF_EXEC); - default: - return 0; - } -} - -static void c_emit_link_attrs(CBuf* b, const ObjSym* os) { - if (os->bind == SB_WEAK) cbuf_puts(b, "__attribute__((weak)) "); - if (os->vis == SV_HIDDEN) { - cbuf_puts(b, "__attribute__((visibility(\"hidden\"))) "); - } else if (os->vis == SV_PROTECTED) { - cbuf_puts(b, "__attribute__((visibility(\"protected\"))) "); - } -} - -/* Reads `len` bytes starting at `ofs` from the section's byte buffer. The - * Section uses a chunked Buf; buf_read does the splice for us. */ -static void c_read_section_bytes(const Section* sec, u32 ofs, u8* out, - size_t len) { - buf_read(&sec->bytes, ofs, out, len); -} - -static void c_emit_data_bytes(CBuf* b, const u8* bytes, size_t n) { - cbuf_puts(b, " = {"); - for (size_t i = 0; i < n; ++i) { - if (i > 0) cbuf_putc(b, ','); - if ((i & 15u) == 0) cbuf_puts(b, "\n "); - cbuf_puts(b, "0x"); - static const char hex[] = "0123456789abcdef"; - cbuf_putc(b, hex[(bytes[i] >> 4) & 0xfu]); - cbuf_putc(b, hex[bytes[i] & 0xfu]); - } - cbuf_puts(b, "\n }"); -} - -/* Mach-O TLS support: the user-visible SK_TLS symbol is a 24-byte TLV - * descriptor in __DATA,__thread_vars, and the actual initial bytes live in - * a synthesized `<name>$tlv$init` sym in __thread_data (or __thread_bss). - * The descriptor carries an R_ABS64 reloc at offset +16 pointing at that - * init sym. For C-source emission we don't care about the descriptor at all - * — we just emit `_Thread_local` with the init sym's bytes and let the host - * C compiler synthesize whatever TLV plumbing it needs. */ - -/* Find the data init sym referenced by a Mach-O TLS descriptor at - * `desc_base` in section `desc_sec`. Looks for an R_ABS64 reloc at - * `desc_base + 16`. Returns OBJ_SYM_NONE if not found. */ -static ObjSymId c_macho_tls_find_init(CTarget* t, ObjSecId desc_sec, - u32 desc_base) { - u32 total = obj_reloc_total(t->obj); - for (u32 i = 0; i < total; ++i) { - const Reloc* r = obj_reloc_at(t->obj, i); - if (r->section_id != desc_sec) continue; - if (r->offset != desc_base + 16u) continue; - return r->sym; - } - return OBJ_SYM_NONE; -} - -/* Returns 1 if the section is __DATA,__thread_vars (the descriptor section - * on Mach-O). Compared by interned Sym id. */ -static int c_sec_name_is_macho_tvars(CTarget* t, const Section* sec) { - if (!sec) return 0; - Sym tvars = - pool_intern_slice(t->c->global, SLICE_LIT("__DATA,__thread_vars")); - return sec->name == tvars; -} - -/* Returns 1 if any relocation falls into the half-open range [base, base+size) - * of section `sec_id` (i.e. patches the bytes of this symbol). */ -static int c_sym_has_relocs(CTarget* t, ObjSecId sec_id, u32 base, u32 size) { - u32 total = obj_reloc_total(t->obj); - for (u32 i = 0; i < total; ++i) { - const Reloc* r = obj_reloc_at(t->obj, i); - if (r->section_id != sec_id) continue; - if (r->offset >= base && r->offset < base + size) return 1; - } - return 0; -} - -/* Emit one data symbol: extern declaration if undef, otherwise the full - * definition with bytes. Function symbols are skipped — those go through the - * forwards path. */ -static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) { - if (c_is_local_static_sym(t, id)) return; - if (os->kind == SK_FUNC || os->kind == SK_IFUNC) return; - if (os->kind == SK_SECTION || os->kind == SK_FILE) return; - /* On Mach-O, obj_tls.c synthesizes `__tlv_bootstrap` as an SK_UNDEF - * extern for the TLV descriptor's first field. The C target delegates all - * TLS lowering to the host compiler via `_Thread_local`, so this - * descriptor-time-only symbol has no place in the emitted source. */ - if (os->kind == SK_UNDEF && t->c->target.obj == CFREE_OBJ_MACHO) { - const ObjBuilder* ob = t->obj; - if (id == obj_tlv_bootstrap_get(ob)) return; - } - const char* nm = c_sym_name(t, id); - CBuf* b = &t->data_defs; - /* SK_TLS user-visible syms need a _Thread_local prefix. On ELF the sym - * lives in .tdata/.tbss with the right bytes/size and our normal data - * path handles them once we set the qualifier. On Mach-O the user-visible - * sym is the 24-byte TLV descriptor — its bytes are not the user's data, - * so we can't faithfully reproduce it in C; bail to a SKIP. */ - int is_tls = (os->kind == SK_TLS); - - /* Extern (undefined) data — only declare if referenced. We can't readily - * distinguish "referenced as data" from "referenced as func address" here, - * so declare it as `extern uint8_t name[];` only if it was actually - * referenced from somewhere; otherwise it'd produce unused warnings. The - * obj symbol's `referenced` bit is exactly the right signal. */ - /* SK_TLS with no defining section = extern TLS — falls through to the - * undef branch below with the `_Thread_local` qualifier. */ - /* Extern: SK_UNDEF, or any other defined-kind sym that the producer - * marked as having no defining section (the C frontend uses SK_OBJ + - * section=NONE for `extern T x __attribute__((weak));`). */ - int is_extern = (os->kind == SK_UNDEF) || - (os->kind != SK_COMMON && os->section_id == OBJ_SEC_NONE); - if (is_extern) { - /* Always declare extern data syms in C source: the host cc tolerates - * unused externs, and the ObjSym::referenced bit isn't reliably set on - * syms the C target only addresses by writing the name into the source - * (no relocation gets emitted against them). - * - * Weak externs need different attributes per object format: on Mach-O - * the `weak` attribute requires a definition; the right spelling for an - * undefined weak ref is `__attribute__((weak_import))`. On ELF/PE the - * existing `weak` attribute works as expected. */ - if (os->bind == SB_WEAK) { - if (t->c->target.obj == CFREE_OBJ_MACHO) { - cbuf_puts(b, "__attribute__((weak_import)) "); - } else { - cbuf_puts(b, "__attribute__((weak)) "); - } - } - if (os->vis == SV_HIDDEN) { - cbuf_puts(b, "__attribute__((visibility(\"hidden\"))) "); - } else if (os->vis == SV_PROTECTED) { - cbuf_puts(b, "__attribute__((visibility(\"protected\"))) "); - } - cbuf_puts(b, "extern "); - if (is_tls) cbuf_puts(b, "_Thread_local "); - cbuf_puts(b, "uint8_t "); - cbuf_puts(b, nm); - cbuf_puts(b, "[];\n"); - return; - } - if (is_tls && t->c->target.obj == CFREE_OBJ_MACHO) { - /* Mach-O splits TLS across two object-file symbols (see obj_tls.c): the - * user-visible sym is a 24-byte TLV descriptor in - * __DATA,__thread_vars; the actual initial bytes live in a synthesized - * `<name>$tlv$init` sym in __DATA,__thread_data (or __thread_bss). For - * C source emission we don't need either of those — `_Thread_local` - * delegates to the host C compiler, which builds its own descriptor. - * - * We use the descriptor sym as the carrier (its name is what user code - * references) and pull the initial bytes/size/alignment from the init - * sym, found via the R_ABS64 reloc at descriptor offset +16. The init - * sym is skipped in its own iteration. */ - const Section* desc_sec = obj_section_get(t->obj, os->section_id); - if (c_sec_name_is_macho_tvars(t, desc_sec)) { - ObjSymId init_id = - c_macho_tls_find_init(t, os->section_id, (u32)os->value); - if (init_id == OBJ_SYM_NONE) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: Mach-O TLS descriptor missing init reloc"); - } - const ObjSym* init_os = obj_symbol_get(t->obj, init_id); - if (!init_os || init_os->section_id == OBJ_SEC_NONE) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: Mach-O TLS init sym not defined"); - } - const Section* init_sec = obj_section_get(t->obj, init_os->section_id); - u32 init_base = (u32)init_os->value; - u32 init_size = (u32)init_os->size; - /* TLS data with relocations would need the constructor-fixup path - * (and we'd have to rewrite the reloc target's section/offset to - * the descriptor's name in the emitted C). No test currently - * exercises this; surface it as a clear panic-as-skip if we hit it. */ - if (c_sym_has_relocs(t, init_os->section_id, init_base, init_size)) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: Mach-O TLS with pointer init not yet " - "supported"); - } - if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); - cbuf_puts(b, "_Thread_local "); - c_emit_link_attrs(b, os); - cbuf_puts(b, "__attribute__((unused)) "); - cbuf_puts(b, "_Alignas("); - cbuf_put_u64(b, init_sec->align ? init_sec->align : 1); - cbuf_puts(b, ") uint8_t "); - cbuf_puts(b, nm); - cbuf_puts(b, "["); - cbuf_put_u64(b, init_size ? init_size : 1); - cbuf_puts(b, "]"); - if (init_sec->kind == SEC_BSS || init_sec->sem == SSEM_NOBITS || - init_size == 0) { - cbuf_puts(b, ";\n"); - } else { - Heap* h = t->c->ctx->heap; - u8* bytes = (u8*)h->alloc(h, init_size, 1); - if (!bytes) { - compiler_panic(t->c, (SrcLoc){0, 0, 0}, - "C target: oom on TLS init bytes"); - } - c_read_section_bytes(init_sec, init_base, bytes, init_size); - c_emit_data_bytes(b, bytes, init_size); - h->free(h, bytes, init_size); - cbuf_puts(b, ";\n"); - } - return; - } - /* Not the descriptor: this is the synthesized `<name>$tlv$init` data - * sym (or a __thread_ptrs entry). The descriptor case above already - * emitted the user-facing _Thread_local; nothing more to do. */ - return; - } - if (os->kind == SK_COMMON) { - /* Common — uninitialized, with explicit alignment. Emit as - * tentative-definition (`uint8_t name[size];` at file scope), which C - * treats as a common-style definition under -fcommon. */ - cbuf_puts(b, "__attribute__((unused)) _Alignas("); - cbuf_put_u64(b, os->common_align ? os->common_align : 1); - cbuf_puts(b, ") uint8_t "); - cbuf_puts(b, nm); - cbuf_puts(b, "["); - cbuf_put_u64(b, os->size); - cbuf_puts(b, "];\n"); - return; - } - if (os->section_id == OBJ_SEC_NONE) return; - const Section* sec = obj_section_get(t->obj, os->section_id); - if (!c_is_data_section(sec)) return; - u32 base = (u32)os->value; - u32 size = (u32)os->size; - u32 nrelocs = 0; - u32 total_relocs = obj_reloc_total(t->obj); - for (u32 i = 0; i < total_relocs; ++i) { - const Reloc* r = obj_reloc_at(t->obj, i); - if (r->section_id == os->section_id && r->offset >= base && - r->offset < base + size) { - nrelocs++; - } - } - - Heap* h = t->c->ctx->heap; - const Reloc** rs = NULL; - if (nrelocs) { - rs = (const Reloc**)h->alloc(h, nrelocs * sizeof(const Reloc*), 1); - u32 j = 0; - for (u32 i = 0; i < total_relocs; ++i) { - const Reloc* r = obj_reloc_at(t->obj, i); - if (r->section_id == os->section_id && r->offset >= base && - r->offset < base + size) { - rs[j++] = r; - } - } - for (u32 i = 1; i < nrelocs; ++i) { - const Reloc* tmp = rs[i]; - u32 k = i; - while (k > 0 && rs[k - 1]->offset > tmp->offset) { - rs[k] = rs[k - 1]; - k--; - } - rs[k] = tmp; - } - } - - cbuf_puts(b, "struct "); - if (nrelocs > 0) cbuf_puts(b, "__attribute__((packed)) "); - cbuf_puts(b, "__cfree_data_"); - cbuf_puts(b, nm); - cbuf_puts(b, " {\n"); - - if (nrelocs == 0) { - cbuf_puts(b, " uint8_t raw["); - cbuf_put_u64(b, size ? size : 1); - cbuf_puts(b, "];\n"); - } else { - u32 cur = base; - for (u32 i = 0; i < nrelocs; ++i) { - const Reloc* r = rs[i]; - if (r->offset > cur) { - cbuf_puts(b, " uint8_t chunk_"); - cbuf_put_u64(b, i); - cbuf_puts(b, "["); - cbuf_put_u64(b, r->offset - cur); - cbuf_puts(b, "];\n"); - } - u32 width = (r->kind == R_ABS32) ? 4 : 8; - const char* ty = (width == 4) ? "uint32_t" : "void*"; - cbuf_puts(b, " "); - cbuf_puts(b, ty); - cbuf_puts(b, " ptr_"); - cbuf_put_u64(b, i); - cbuf_puts(b, ";\n"); - cur = r->offset + width; - } - if (cur < base + size) { - cbuf_puts(b, " uint8_t chunk_"); - cbuf_put_u64(b, nrelocs); - cbuf_puts(b, "["); - cbuf_put_u64(b, base + size - cur); - cbuf_puts(b, "];\n"); - } - } - cbuf_puts(b, "};\n"); - - if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); - if (is_tls) cbuf_puts(b, "_Thread_local "); - c_emit_link_attrs(b, os); - cbuf_puts(b, "__attribute__((unused)) "); - - int is_ro = (sec->kind == SEC_RODATA); - if (is_ro) cbuf_puts(b, "const "); - - cbuf_puts(b, "_Alignas("); - cbuf_put_u64(b, sec->align ? sec->align : 1); - cbuf_puts(b, ") struct __cfree_data_"); - cbuf_puts(b, nm); - cbuf_puts(b, " "); - cbuf_puts(b, nm); - - if (sec->kind == SEC_BSS || sec->sem == SSEM_NOBITS) { - cbuf_puts(b, ";\n"); - } else if (size == 0) { - cbuf_puts(b, " = {{0}};\n"); - } else { - cbuf_puts(b, " = {\n"); - u8* bytes = (u8*)h->alloc(h, size, 1); - c_read_section_bytes(sec, base, bytes, size); - - if (nrelocs == 0) { - cbuf_puts(b, " .raw = {"); - for (u32 i = 0; i < size; ++i) { - if (i > 0) cbuf_puts(b, ", "); - cbuf_put_u64(b, bytes[i]); - } - cbuf_puts(b, "}\n"); - } else { - u32 cur = base; - for (u32 i = 0; i < nrelocs; ++i) { - const Reloc* r = rs[i]; - if (r->offset > cur) { - cbuf_puts(b, " .chunk_"); - cbuf_put_u64(b, i); - cbuf_puts(b, " = {"); - for (u32 k = 0; k < r->offset - cur; ++k) { - if (k > 0) cbuf_puts(b, ", "); - cbuf_put_u64(b, bytes[cur - base + k]); - } - cbuf_puts(b, "},\n"); - } - - u32 width = (r->kind == R_ABS32) ? 4 : 8; - c_ensure_forward_decl(t, r->sym, 0); - const char* tgt = c_sym_name(t, r->sym); - const char* cast = (width == 4) ? "(uint32_t)(uintptr_t)" : "(void*)"; - - cbuf_puts(b, " .ptr_"); - cbuf_put_u64(b, i); - cbuf_puts(b, " = "); - cbuf_puts(b, cast); - cbuf_puts(b, "((char*)&"); - cbuf_puts(b, tgt); - if (r->addend != 0) { - cbuf_puts(b, " + "); - cbuf_put_i64(b, r->addend); - } - cbuf_puts(b, "),\n"); - cur = r->offset + width; - } - if (cur < base + size) { - cbuf_puts(b, " .chunk_"); - cbuf_put_u64(b, nrelocs); - cbuf_puts(b, " = {"); - for (u32 k = 0; k < base + size - cur; ++k) { - if (k > 0) cbuf_puts(b, ", "); - cbuf_put_u64(b, bytes[cur - base + k]); - } - cbuf_puts(b, "}\n"); - } - } - h->free(h, bytes, size); - cbuf_puts(b, "};\n"); - } - - if (nrelocs) h->free(h, (void*)rs, nrelocs * sizeof(const Reloc*)); -} - -static void c_emit_data(CTarget* t) { - ObjSymIter* it = obj_symiter_new(t->obj); - if (!it) return; - ObjSymEntry e; - while (obj_symiter_next(it, &e)) { - if (!e.sym) continue; - c_emit_data_symbol(t, e.id, e.sym); - } - obj_symiter_free(it); -} - -/* === finalize / destroy === */ - -void c_finalize(CgTarget* T) { - CTarget* t = (CTarget*)T; - if (t->finalized) return; - t->finalized = 1; - c_emit_prologue(t); - if (t->need_stdarg) c_writer_puts(t, "#include <stdarg.h>\n"); - if (t->need_setjmp) c_writer_puts(t, "#include <setjmp.h>\n"); - if (t->need_stdarg || t->need_setjmp) c_writer_puts(t, "\n"); - if (t->typedefs.len) { - c_writer_write(t, t->typedefs.data, t->typedefs.len); - c_writer_puts(t, "\n"); - } - c_emit_data(t); - if (t->forwards.len) { - c_writer_write(t, t->forwards.data, t->forwards.len); - c_writer_puts(t, "\n"); - } - if (t->data_defs.len) { - c_writer_write(t, t->data_defs.data, t->data_defs.len); - c_writer_puts(t, "\n"); - } - if (t->body.len) c_writer_write(t, t->body.data, t->body.len); -} - -void c_destroy(CgTarget* T) { - CTarget* t = (CTarget*)T; - Heap* h = t->c->ctx->heap; - cbuf_fini(&t->forwards); - cbuf_fini(&t->typedefs); - cbuf_fini(&t->data_defs); - cbuf_fini(&t->decls); - cbuf_fini(&t->body); - if (t->sym_forwarded) h->free(h, t->sym_forwarded, t->sym_forwarded_cap); - t->sym_forwarded = NULL; - t->sym_forwarded_cap = 0; - if (t->local_static_syms) { - h->free(h, t->local_static_syms, - t->local_static_syms_cap * sizeof(*t->local_static_syms)); - } - if (t->local_static_entries) { - h->free(h, t->local_static_entries, - t->local_static_entries_cap * sizeof(*t->local_static_entries)); - } - if (t->local_declared) h->free(h, t->local_declared, t->local_cap); - if (t->local_type) h->free(h, t->local_type, t->local_cap * sizeof(CfreeCgTypeId)); - if (t->scopes) h->free(h, t->scopes, t->scopes_cap * sizeof(CScopeInfo)); - t->local_declared = NULL; - t->local_type = NULL; - t->scopes = NULL; - t->local_static_syms = NULL; - t->local_static_entries = NULL; - t->local_cap = 0; - t->scopes_cap = 0; - t->local_static_syms_cap = 0; - t->local_static_entries_cap = 0; - t->local_static_nsyms = 0; - t->local_static_nentries = 0; -} diff --git a/src/arch/c_target/internal.h b/src/arch/c_target/internal.h @@ -1,193 +0,0 @@ -#ifndef CFREE_C_TARGET_INTERNAL_H -#define CFREE_C_TARGET_INTERNAL_H - -/* C-source emission CgTarget. See doc/CBACKEND.md. - * - * This target is selected when CodeOptions.emit_c_source is set. It writes - * target-locked C source to CodeOptions.c_source_writer instead of object - * bytes. Operates with semantic temporary locals minted by CG. */ - -#include <cfree/core.h> - -#include "cg/cgtarget.h" -#include "core/core.h" - -typedef CGLocal CLocal; - -/* Heap-backed growable byte buffer. Used for the per-function declaration - * and body buffers; CG needs decls at function top but doesn't surface them - * before body emission, so we accumulate both and flush at func_end. */ -typedef struct CBuf { - Heap* heap; - u8* data; - size_t len; - size_t cap; -} CBuf; - -void cbuf_init(CBuf* b, Heap* h); -void cbuf_fini(CBuf* b); -void cbuf_reset(CBuf* b); -void cbuf_putc(CBuf* b, char c); -void cbuf_puts(CBuf* b, const char* s); -void cbuf_putn(CBuf* b, const char* s, size_t n); -void cbuf_put_i64(CBuf* b, i64 v); -void cbuf_put_u64(CBuf* b, u64 v); - -typedef struct CLocalStaticLabelEntry { - Label label; - i64 addend; - u8 has_label; - u8 pad[3]; -} CLocalStaticLabelEntry; - -typedef struct CTarget { - CgTarget base; - - Compiler* c; - ObjBuilder* obj; - CfreeWriter* w; - - /* TU prologue (e.g. #include <stdint.h>) emitted once, on first - * function or finalize, whichever first. */ - u8 prologue_emitted; - u8 finalized; - u8 pad[2]; - - /* TU-wide forward declarations: one `RetT name(params);` line per function - * we've seen a definition or call for. Emitted just after the prologue so - * callers defined earlier in the TU still see the prototype, and calls to - * undefined-in-TU externs get a declaration too. */ - CBuf forwards; - /* Forward-decl dedup: which ObjSymIds have we already declared. Lazily - * grown bitmap indexed by ObjSymId. */ - u8* sym_forwarded; - u32 sym_forwarded_cap; - - /* TU-wide typedefs (records, arrays, function pointers, opaque storage). - * Emitted between prologue and forwards. */ - CBuf typedefs; - /* TU-wide data definitions and extern data declarations. Emitted between - * forwards and function bodies so functions can reference data symbols and - * data initializers can reference function forward decls. Populated at - * finalize by walking the ObjBuilder's data sections. */ - CBuf data_defs; - /* Per-type id state: 0 = not seen, 1 = inflight, 2 = emitted. */ - u8* type_state; - u32 type_state_cap; - /* Tracks whether <stdarg.h> needs to be emitted in the prologue. Set when - * c_typename first encounters VARARG_STATE. Flushed lazily at finalize. */ - u8 need_stdarg; - u8 need_setjmp; - u8 pad2[2]; - - /* Per-function buffers. Reset on func_end. */ - CBuf decls; - CBuf body; - - /* Function-local static data consumed from CG's narrow source-backend data - * hook. These symbols are emitted inside the owning function and skipped by - * the TU-wide object-data walker. */ - ObjSymId* local_static_syms; - u32 local_static_nsyms; - u32 local_static_syms_cap; - - CLocalStaticLabelEntry* local_static_entries; - u32 local_static_nentries; - u32 local_static_entries_cap; - ObjSymId local_static_sym; - CfreeCgTypeId local_static_type; - u64 local_static_count; - u64 local_static_offset; - u32 local_static_ptr_width; - u32 local_static_align; - u8 local_static_active; - u8 local_static_is_array; - u8 local_static_readonly; - u8 pad_local_static; - - /* Per-function local-decl tracking: for each local id seen, mark whether we - * have already emitted a declaration into `decls`. Sized by local_cap. - * Grown lazily as new local ids appear. */ - u8* local_declared; - CfreeCgTypeId* local_type; /* declared type for each semantic local */ - u32 local_cap; - - /* Splice bookmark: byte offset into body where the current function's body - * region starts (right after the open brace). func_end uses this to insert - * the per-function declarations between the signature and the body. */ - size_t fn_body_start; - - const CGFuncDesc* cur_fn; - - /* Most recent source line directive emitted into the current function body. - * Reset on func_begin; used by c_set_loc to avoid noisy duplicate #line - * directives. */ - SrcLoc emitted_loc; - u8 have_emitted_loc; - u8 pad_cur_loc[3]; - - /* Label minting: ids 1..next_label. 0 is reserved as LABEL_NONE. */ - u32 next_label; - u32 next_local; - - /* Stack of active scopes. CGScope handles are (scope_index + 1). */ - struct CScopeInfo* scopes; - u32 scopes_cap; - u32 nscopes; - - /* Monotone counter for synthesizing unique temporary names within a - * function (e.g. bitcast scratch). Reset on func_begin. */ - u32 next_tmp; - - /* Tracks whether the last emitted body statement was an unconditional - * terminator (`return`, `goto`, etc.) with no intervening label. When - * set, subsequent ret/jump/cmp_branch emissions are dropped — they're - * unreachable, and emitting them produces dead C that distracts from - * the live code. Reset on func_begin and label_place. */ - u8 last_was_terminator; - u8 pad3[3]; -} CTarget; - -typedef struct CScopeInfo { - u8 kind; /* ScopeKind */ - /* Set when the C target emitted a `for (;;) { ... }` wrapper around - * this scope. Drives jump/cmp_branch's translation of break/continue - * labels into C `break;`/`continue;` and scope_end's `}`. */ - u8 structured; - u8 pad[2]; - Label break_label; - Label continue_label; -} CScopeInfo; - -CgTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w); - -/* Helpers shared across emit.c. */ -void c_emit_prologue(CTarget* t); -/* Ensure local `r` (typed `type`) has been declared. */ -void c_ensure_local(CTarget* t, CLocal r, CfreeCgTypeId type); -/* Get a stable C identifier for local r. Writes into caller-supplied buf. */ -void c_local_name(CLocal r, char* out, size_t cap); -/* Write the C type for a CG int/float/ptr type to `b`. */ -void c_emit_type(CTarget* t, CBuf* b, CfreeCgTypeId type); -/* Write operand expression to body (e.g. "v3", "(int32_t)42"). Supports - * OPK_LOCAL / OPK_IMM / OPK_GLOBAL. INDIRECT is only valid in - * lvalue positions and is emitted via load/store/addr_of paths. */ -void c_emit_operand(CTarget* t, Operand op); -/* Like c_emit_operand but wraps in an explicit signed/unsigned cast of the - * operand's type width. For integer ops where signedness affects semantics - * (UDIV/UREM, SHR_U, unsigned compares). Falls back to c_emit_operand when - * the operand is not integer-typed. */ -void c_emit_operand_signed(CTarget* t, Operand op, int signed_); - -/* Lookup the C linker name for an ObjSymId. Returns interned string. */ -const char* c_sym_name(CTarget* t, ObjSymId sym); - -/* Emit a forward declaration for `sym` (of function type `fn_type`) into - * the TU forwards buffer if not already done. Idempotent per sym. */ -void c_ensure_forward_decl(CTarget* t, ObjSymId sym, CfreeCgTypeId fn_type); - -/* Write `n` bytes to t->w; panic on error. */ -void c_writer_write(CTarget* t, const void* data, size_t n); -void c_writer_puts(CTarget* t, const char* s); - -#endif diff --git a/src/arch/c_target/ir_emit.c b/src/arch/c_target/ir_emit.c @@ -0,0 +1,281 @@ +#include "arch/c_target/ir_emit.h" + +#include <string.h> + +typedef struct CIrEmitter { + CTarget* target; + CGScope* scope_map; + u32 scope_map_n; +} CIrEmitter; + +static CGScope ir_scope_lookup(CIrEmitter* e, CGScope recorded, SrcLoc loc) { + if ((u32)recorded >= e->scope_map_n || !e->scope_map[recorded]) { + compiler_panic(e->target->c, loc, + "C target IR emit: unknown recorded scope %u", + (unsigned)recorded); + } + return e->scope_map[recorded]; +} + +static void ir_scope_bind(CIrEmitter* e, CGScope recorded, CGScope emitted, + SrcLoc loc) { + if ((u32)recorded >= e->scope_map_n) { + compiler_panic(e->target->c, loc, + "C target IR emit: recorded scope %u out of range", + (unsigned)recorded); + } + e->scope_map[recorded] = emitted; +} + +static void ir_emit_param_bind(CIrEmitter* e, const CgIrParam* p) { + c_emit_param_bind(e->target, p->local, p->desc.type, p->desc.index); +} + +static void ir_declare_locals(CIrEmitter* e, const CgIrFunc* f) { + for (u32 i = 0; i < f->nlocals; ++i) { + const CgIrLocal* l = &f->locals[i]; + c_ensure_local(e->target, l->id, l->desc.type); + } + for (u32 i = 0; i < f->nparams; ++i) ir_emit_param_bind(e, &f->params[i]); +} + +static void ir_emit_switch(CIrEmitter* e, const CgIrInst* in) { + const CgIrSwitchAux* aux = (const CgIrSwitchAux*)in->extra.aux; + CGSwitchDesc d; + memset(&d, 0, sizeof d); + d.selector = 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; + c_emit_switch_(e->target, &d); +} + +static void ir_emit_inst(CIrEmitter* e, const CgIrInst* in) { + CTarget* t = e->target; + c_emit_set_loc(t, in->loc); + switch ((CgIrOp)in->op) { + case CG_IR_NOP: + return; + case CG_IR_LABEL: + c_emit_label_place(t, (Label)in->extra.imm); + return; + case CG_IR_LOAD_IMM: + c_emit_load_imm(t, in->opnds[0], in->extra.imm); + return; + case CG_IR_LOAD_CONST: + c_emit_load_const(t, in->opnds[0], in->extra.cbytes); + return; + case CG_IR_COPY: + c_emit_copy(t, in->opnds[0], in->opnds[1]); + return; + case CG_IR_LOAD: + c_emit_load(t, in->opnds[0], in->opnds[1], in->extra.mem); + return; + case CG_IR_STORE: + c_emit_store(t, in->opnds[0], in->opnds[1], in->extra.mem); + return; + case CG_IR_ADDR_OF: + c_emit_addr_of(t, in->opnds[0], in->opnds[1]); + return; + case CG_IR_TLS_ADDR_OF: { + const CgIrTlsAux* aux = (const CgIrTlsAux*)in->extra.aux; + c_emit_tls_addr_of(t, in->opnds[0], aux->sym, aux->addend); + return; + } + case CG_IR_AGG_COPY: { + const CgIrAggAux* aux = (const CgIrAggAux*)in->extra.aux; + c_emit_copy_bytes(t, in->opnds[0], in->opnds[1], aux->access); + return; + } + case CG_IR_AGG_SET: { + const CgIrAggAux* aux = (const CgIrAggAux*)in->extra.aux; + c_emit_set_bytes(t, in->opnds[0], in->opnds[1], aux->access); + return; + } + case CG_IR_BITFIELD_LOAD: { + const CgIrBitFieldAux* aux = (const CgIrBitFieldAux*)in->extra.aux; + c_emit_bitfield_load(t, in->opnds[0], in->opnds[1], aux->access); + return; + } + case CG_IR_BITFIELD_STORE: { + const CgIrBitFieldAux* aux = (const CgIrBitFieldAux*)in->extra.aux; + c_emit_bitfield_store(t, in->opnds[0], in->opnds[1], aux->access); + return; + } + case CG_IR_BINOP: + c_emit_binop(t, (BinOp)in->extra.imm, in->opnds[0], in->opnds[1], + in->opnds[2]); + return; + case CG_IR_UNOP: + c_emit_unop(t, (UnOp)in->extra.imm, in->opnds[0], in->opnds[1]); + return; + case CG_IR_CMP: + c_emit_cmp(t, (CmpOp)in->extra.imm, in->opnds[0], in->opnds[1], + in->opnds[2]); + return; + case CG_IR_CONVERT: + c_emit_convert(t, (ConvKind)in->extra.imm, in->opnds[0], in->opnds[1]); + return; + case CG_IR_CALL: { + const CgIrCallAux* aux = (const CgIrCallAux*)in->extra.aux; + c_emit_call(t, &aux->desc); + return; + } + case CG_IR_RET: { + const CgIrRetAux* aux = (const CgIrRetAux*)in->extra.aux; + c_emit_ret(t, aux->values, aux->nvalues); + return; + } + case CG_IR_BR: + c_emit_jump(t, (Label)in->extra.imm); + return; + case CG_IR_CMP_BRANCH: { + const CgIrCmpBranchAux* aux = (const CgIrCmpBranchAux*)in->extra.aux; + c_emit_cmp_branch(t, aux->op, in->opnds[0], in->opnds[1], aux->target); + return; + } + case CG_IR_SWITCH: + ir_emit_switch(e, in); + return; + case CG_IR_INDIRECT_BRANCH: { + const CgIrIndirectAux* aux = (const CgIrIndirectAux*)in->extra.aux; + c_emit_indirect_branch(t, in->opnds[0], aux->targets, aux->ntargets); + return; + } + case CG_IR_LOAD_LABEL_ADDR: + c_emit_load_label_addr(t, in->opnds[0], (Label)in->extra.imm); + return; + case CG_IR_LOCAL_STATIC_DATA_BEGIN: { + const CgIrLocalStaticBeginAux* aux = + (const CgIrLocalStaticBeginAux*)in->extra.aux; + (void)c_emit_local_static_data_begin(t, &aux->desc); + return; + } + case CG_IR_LOCAL_STATIC_DATA_WRITE: { + const CgIrLocalStaticWriteAux* aux = + (const CgIrLocalStaticWriteAux*)in->extra.aux; + c_emit_local_static_data_write(t, aux->has_data ? aux->data : NULL, + aux->len); + return; + } + case CG_IR_LOCAL_STATIC_DATA_LABEL_ADDR: { + const CgIrLocalStaticLabelAux* aux = + (const CgIrLocalStaticLabelAux*)in->extra.aux; + c_emit_local_static_data_label_addr(t, aux->target, aux->addend, + aux->width, aux->address_space); + return; + } + case CG_IR_LOCAL_STATIC_DATA_END: + c_emit_local_static_data_end(t); + return; + case CG_IR_SCOPE_BEGIN: { + const CgIrScopeAux* aux = (const CgIrScopeAux*)in->extra.aux; + CGScope emitted = c_emit_scope_begin(t, &aux->desc); + ir_scope_bind(e, aux->scope, emitted, in->loc); + return; + } + case CG_IR_SCOPE_ELSE: + c_emit_scope_else(t, ir_scope_lookup(e, (CGScope)in->extra.imm, in->loc)); + return; + case CG_IR_SCOPE_END: + c_emit_scope_end(t, ir_scope_lookup(e, (CGScope)in->extra.imm, in->loc)); + return; + case CG_IR_BREAK_TO: + c_emit_break_to(t, ir_scope_lookup(e, (CGScope)in->extra.imm, in->loc)); + return; + case CG_IR_CONTINUE_TO: + c_emit_continue_to(t, + ir_scope_lookup(e, (CGScope)in->extra.imm, in->loc)); + return; + case CG_IR_ALLOCA: + c_emit_alloca(t, in->opnds[0], in->opnds[1], (u32)in->extra.imm); + return; + case CG_IR_VA_START: + c_emit_va_start(t, in->opnds[0]); + return; + case CG_IR_VA_ARG: + c_emit_va_arg(t, in->opnds[0], in->opnds[1], + (CfreeCgTypeId)in->extra.imm); + return; + case CG_IR_VA_END: + c_emit_va_end(t, in->opnds[0]); + return; + case CG_IR_VA_COPY: + c_emit_va_copy(t, in->opnds[0], in->opnds[1]); + return; + case CG_IR_ATOMIC_LOAD: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + c_emit_atomic_load(t, in->opnds[0], in->opnds[1], aux->mem, aux->order); + return; + } + case CG_IR_ATOMIC_STORE: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + c_emit_atomic_store(t, in->opnds[0], in->opnds[1], aux->mem, aux->order); + return; + } + case CG_IR_ATOMIC_RMW: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + c_emit_atomic_rmw(t, aux->op, in->opnds[0], in->opnds[1], in->opnds[2], + aux->mem, aux->order); + return; + } + case CG_IR_ATOMIC_CAS: { + const CgIrAtomicAux* aux = (const CgIrAtomicAux*)in->extra.aux; + c_emit_atomic_cas(t, in->opnds[0], in->opnds[1], in->opnds[2], + in->opnds[3], in->opnds[4], aux->mem, aux->order, + aux->failure); + return; + } + case CG_IR_FENCE: + c_emit_fence(t, (MemOrder)in->extra.imm); + return; + case CG_IR_INTRINSIC: { + const CgIrIntrinsicAux* aux = (const CgIrIntrinsicAux*)in->extra.aux; + c_emit_intrinsic(t, aux->kind, aux->dsts, aux->ndst, aux->args, + aux->narg); + return; + } + case CG_IR_ASM_BLOCK: { + const CgIrAsmAux* aux = (const CgIrAsmAux*)in->extra.aux; + c_emit_asm_block(t, aux->tmpl, aux->outs, aux->nout, aux->out_ops, + aux->ins, aux->nin, aux->in_ops, aux->clobbers, + aux->nclob); + return; + } + } + compiler_panic(t->c, in->loc, "C target IR emit: unknown op %u", + (unsigned)in->op); +} + +static void ir_emit_func(CTarget* t, const CgIrFunc* f) { + CIrEmitter e; + Heap* h = t->c->ctx->heap; + memset(&e, 0, sizeof e); + e.target = t; + 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, "C target IR emit: out of memory"); + } + memset(e.scope_map, 0, sizeof(CGScope) * e.scope_map_n); + } + + c_emit_func_begin(t, &f->desc); + ir_declare_locals(&e, f); + for (u32 i = 0; i < f->ninsts; ++i) ir_emit_inst(&e, &f->insts[i]); + c_emit_func_end(t); + + if (e.scope_map) h->free(h, e.scope_map, sizeof(CGScope) * e.scope_map_n); +} + +void c_emit_ir_module(CTarget* t, const CgIrModule* module) { + if (!t || !module) return; + for (u32 i = 0; i < module->nfuncs; ++i) { + ir_emit_func(t, module->funcs[i]); + } +} diff --git a/src/arch/c_target/ir_emit.h b/src/arch/c_target/ir_emit.h @@ -0,0 +1,9 @@ +#ifndef CFREE_C_TARGET_IR_EMIT_H +#define CFREE_C_TARGET_IR_EMIT_H + +#include "arch/c_target/c_emit.h" +#include "cg/ir.h" + +void c_emit_ir_module(CTarget* t, const CgIrModule* module); + +#endif diff --git a/src/arch/c_target/target.c b/src/arch/c_target/target.c @@ -1,183 +1,54 @@ -/* C-source CgTarget construction and vtable wiring. +/* C-source backend registration. * - * See doc/CBACKEND.md. The C target writes portable, target-locked C source - * text to the CfreeWriter passed via CodeOptions.c_source_writer. CG operates - * with semantic temporary locals. */ + * The public backend path records CG into semantic IR, then emits C from that + * IR. The legacy CgTarget adapter lives in cg_emit.c only so it can be deleted + * whole once all callers use the IR path directly. */ #include <string.h> -#include "arch/c_target/internal.h" -#include "core/arena.h" +#include "arch/c_target/c_emit.h" +#include "arch/c_target/ir_emit.h" +#include "cg/ir_recorder.h" #include "core/core.h" -#include "core/heap.h" -/* Forward declarations for all CgTarget methods. Implementations either land - * in emit.c (working) or are stubs that compiler_panic. */ -void c_func_begin(CgTarget*, const CGFuncDesc*); -void c_func_end(CgTarget*); -void c_alias(CgTarget*, ObjSymId, ObjSymId, CfreeCgTypeId); -void c_ret(CgTarget*, const CGLocal*, u32); -void c_load_imm(CgTarget*, Operand, i64); -void c_copy(CgTarget*, Operand, Operand); -void c_binop(CgTarget*, BinOp, Operand, Operand, Operand); -void c_unop(CgTarget*, UnOp, Operand, Operand); -void c_cmp(CgTarget*, CmpOp, Operand, Operand, Operand); -void c_convert(CgTarget*, ConvKind, Operand, Operand); -void c_call(CgTarget*, const CGCallDesc*); -const char* c_tail_call_unrealizable_reason(CgTarget*, const CGCallDesc*); -void c_load(CgTarget*, Operand, Operand, MemAccess); -void c_store(CgTarget*, Operand, Operand, MemAccess); -void c_addr_of(CgTarget*, Operand, Operand); -CGLocal c_param(CgTarget*, const CGParamDesc*); -CGLocal c_local(CgTarget*, const CGLocalDesc*); -void c_local_addr(CgTarget*, Operand, const CGLocalDesc*, CGLocal); -Label c_label_new(CgTarget*); -void c_label_place(CgTarget*, Label); -void c_jump(CgTarget*, Label); -void c_cmp_branch(CgTarget*, CmpOp, Operand, Operand, Label); -void c_switch_(CgTarget*, const CGSwitchDesc*); -void c_indirect_branch(CgTarget*, Operand, const Label*, u32); -void c_load_label_addr(CgTarget*, Operand, Label); -int c_local_static_data_begin(CgTarget*, const CGLocalStaticDataDesc*); -void c_local_static_data_write(CgTarget*, const u8*, u64); -void c_local_static_data_label_addr(CgTarget*, Label, i64, u32, u32); -void c_local_static_data_end(CgTarget*); -CGScope c_scope_begin(CgTarget*, const CGScopeDesc*); -void c_scope_else(CgTarget*, CGScope); -void c_scope_end(CgTarget*, CGScope); -void c_break_to(CgTarget*, CGScope); -void c_continue_to(CgTarget*, CGScope); -void c_set_loc(CgTarget*, SrcLoc); -void c_finalize(CgTarget*); -void c_destroy(CgTarget*); -void c_intrinsic(CgTarget*, IntrinKind, Operand*, u32, const Operand*, u32); -void c_alloca(CgTarget*, Operand, Operand, u32); -void c_va_start(CgTarget*, Operand); -void c_va_arg(CgTarget*, Operand, Operand, CfreeCgTypeId); -void c_va_end(CgTarget*, Operand); -void c_va_copy(CgTarget*, Operand, Operand); -void c_copy_bytes(CgTarget*, Operand, Operand, AggregateAccess); -void c_set_bytes(CgTarget*, Operand, Operand, AggregateAccess); -void c_load_const(CgTarget*, Operand, ConstBytes); -void c_asm_block(CgTarget*, const char*, const AsmConstraint*, u32, Operand*, - const AsmConstraint*, u32, const Operand*, const Sym*, u32); -void c_bitfield_load(CgTarget*, Operand, Operand, BitFieldAccess); -void c_bitfield_store(CgTarget*, Operand, Operand, BitFieldAccess); -void c_tls_addr_of(CgTarget*, Operand, ObjSymId, i64); -void c_atomic_load(CgTarget*, Operand, Operand, MemAccess, MemOrder); -void c_atomic_store(CgTarget*, Operand, Operand, MemAccess, MemOrder); -void c_atomic_rmw(CgTarget*, AtomicOp, Operand, Operand, Operand, MemAccess, - MemOrder); -void c_atomic_cas(CgTarget*, Operand, Operand, Operand, Operand, Operand, - MemAccess, MemOrder, MemOrder); -void c_fence(CgTarget*, MemOrder); - -static void cgt_cleanup(void* arg) { cgtarget_free((CgTarget*)arg); } - -CgTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { - CTarget* x = arena_new(c->tu, CTarget); - memset(x, 0, sizeof *x); - - x->c = c; - x->obj = o; - x->w = w; - cbuf_init(&x->forwards, c->ctx->heap); - cbuf_init(&x->typedefs, c->ctx->heap); - cbuf_init(&x->data_defs, c->ctx->heap); - cbuf_init(&x->decls, c->ctx->heap); - cbuf_init(&x->body, c->ctx->heap); - - CgTarget* t = &x->base; - t->c = c; - t->obj = o; - - /* ---- function lifecycle ---- */ - t->func_begin = c_func_begin; - t->func_end = c_func_end; - t->alias = c_alias; - - /* ---- locals ---- */ - t->local = c_local; - t->local_addr = c_local_addr; - t->param = c_param; - - /* ---- labels and control flow ---- */ - t->label_new = c_label_new; - t->label_place = c_label_place; - t->jump = c_jump; - t->cmp_branch = c_cmp_branch; - t->switch_ = c_switch_; - t->indirect_branch = c_indirect_branch; - t->load_label_addr = c_load_label_addr; - t->local_static_data_begin = c_local_static_data_begin; - t->local_static_data_write = c_local_static_data_write; - t->local_static_data_label_addr = c_local_static_data_label_addr; - t->local_static_data_end = c_local_static_data_end; - t->scope_begin = c_scope_begin; - t->scope_else = c_scope_else; - t->scope_end = c_scope_end; - t->break_to = c_break_to; - t->continue_to = c_continue_to; - - /* ---- data movement ---- */ - t->load_imm = c_load_imm; - t->load_const = c_load_const; - t->copy = c_copy; - t->load = c_load; - t->store = c_store; - t->addr_of = c_addr_of; - t->tls_addr_of = c_tls_addr_of; - t->copy_bytes = c_copy_bytes; - t->set_bytes = c_set_bytes; - t->bitfield_load = c_bitfield_load; - t->bitfield_store = c_bitfield_store; - - /* ---- arithmetic, compare, convert ---- */ - t->binop = c_binop; - t->unop = c_unop; - t->cmp = c_cmp; - t->convert = c_convert; - - /* ---- calls / return ---- */ - t->call = c_call; - t->tail_call_unrealizable_reason = c_tail_call_unrealizable_reason; - t->ret = c_ret; - - /* ---- alloca / varargs ---- */ - t->alloca_ = c_alloca; - t->va_start_ = c_va_start; - t->va_arg_ = c_va_arg; - t->va_end_ = c_va_end; - t->va_copy_ = c_va_copy; - - /* ---- atomics ---- */ - t->atomic_load = c_atomic_load; - t->atomic_store = c_atomic_store; - t->atomic_rmw = c_atomic_rmw; - t->atomic_cas = c_atomic_cas; - t->fence = c_fence; +static void c_ir_finalize(void* user, const CgIrModule* module) { + CTarget* t = (CTarget*)user; + c_emit_ir_module(t, module); + c_emit_finalize(t); +} - /* ---- intrinsics / asm ---- */ - t->intrinsic = c_intrinsic; - t->asm_block = c_asm_block; +static void c_ir_destroy(void* user) { + CTarget* t = (CTarget*)user; + if (t) c_emit_destroy(t); +} - t->set_loc = c_set_loc; - t->finalize = c_finalize; - t->destroy = c_destroy; +static int c_ir_local_static_data_begin(void* user, + const CGLocalStaticDataDesc* desc) { + return c_emit_can_local_static_data((CTarget*)user, desc); +} - compiler_defer(c, cgt_cleanup, t); - return t; +static const char* c_ir_tail_call_unrealizable_reason(void* user, + const CGFuncDesc* caller, + const CGCallDesc* call) { + return c_emit_tail_call_unrealizable_reason_for((CTarget*)user, caller, call); } static CgTarget* c_target_backend_make(Compiler* c, ObjBuilder* o, const CfreeCodeOptions* opts) { - /* c_target ignores opt_level and debug_info entirely — see - * doc/CBACKEND.md §"Sequencing with opt". It only needs the writer. */ if (!opts || !opts->c_source_writer) { compiler_panic(c, (SrcLoc){0, 0, 0}, "c_target backend: emit_c_source requires c_source_writer"); } - return c_cgtarget_new(c, o, opts->c_source_writer); + + CTarget* ctarget = c_emit_target_new(c, o, opts->c_source_writer); + CgIrRecorderConfig cfg; + memset(&cfg, 0, sizeof cfg); + cfg.finalize = c_ir_finalize; + cfg.destroy = c_ir_destroy; + cfg.local_static_data_begin = c_ir_local_static_data_begin; + cfg.tail_call_unrealizable_reason = c_ir_tail_call_unrealizable_reason; + cfg.user = ctarget; + return cg_ir_recorder_new(c, o, &cfg); } const CGBackend cg_backend_c_target = { diff --git a/src/cg/ir.c b/src/cg/ir.c @@ -0,0 +1,272 @@ +#include "cg/ir.h" + +#include <string.h> + +static _Noreturn void ir_panic(Compiler* c, SrcLoc loc, const char* what) { + compiler_panic(c, loc, "cg ir: %s", what); +} + +static void* ir_zalloc_or_panic(Compiler* c, Arena* a, size_t size, + size_t align) { + void* p = arena_zalloc(a, size, align); + if (!p) ir_panic(c, (SrcLoc){0, 0, 0}, "out of memory"); + return p; +} + +char* cg_ir_dup_cstr(Arena* a, const char* s) { + size_t n; + if (!s) return NULL; + n = strlen(s); + return arena_strdup(a, s, n); +} + +static void module_grow(CgIrModule* m, u32 want) { + CgIrFunc** next; + u32 cap; + if (m->funcs_cap >= want) return; + cap = m->funcs_cap ? m->funcs_cap : 8u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(m->c, m->arena, sizeof(*next) * cap, + _Alignof(CgIrFunc*)); + if (m->funcs) memcpy(next, m->funcs, sizeof(*next) * m->nfuncs); + m->funcs = next; + m->funcs_cap = cap; +} + +CgIrModule* cg_ir_module_new(Compiler* c) { + CgIrModule* m = arena_znew(c->tu, CgIrModule); + if (!m) return NULL; + m->arena = c->tu; + m->c = c; + return m; +} + +void cg_ir_module_add_func(CgIrModule* m, CgIrFunc* f) { + if (!m || !f) return; + module_grow(m, m->nfuncs + 1u); + m->funcs[m->nfuncs++] = f; +} + +static CGFuncDesc dup_func_desc(Arena* a, const CGFuncDesc* in) { + CGFuncDesc out = *in; + if (in->nresults) { + CfreeCgTypeId* r = arena_array(a, CfreeCgTypeId, in->nresults); + memcpy(r, in->result_types, sizeof(*r) * in->nresults); + out.result_types = r; + } + if (in->nparams) { + CGParamDesc* p = arena_array(a, CGParamDesc, in->nparams); + memcpy(p, in->params, sizeof(*p) * in->nparams); + out.params = p; + } + return out; +} + +CgIrFunc* cg_ir_func_new(Compiler* c, const CGFuncDesc* desc) { + CgIrFunc* f = arena_znew(c->tu, CgIrFunc); + if (!f) return NULL; + f->arena = c->tu; + f->c = c; + f->desc = dup_func_desc(f->arena, desc); + f->next_inst_id = 1; + return f; +} + +static void inst_grow(CgIrFunc* f, u32 want) { + CgIrInst* next; + u32 cap; + if (f->insts_cap >= want) return; + cap = f->insts_cap ? f->insts_cap : 32u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(f->c, f->arena, sizeof(*next) * cap, + _Alignof(CgIrInst)); + if (f->insts) memcpy(next, f->insts, sizeof(*next) * f->ninsts); + f->insts = next; + f->insts_cap = cap; +} + +CgIrInst* cg_ir_emit(CgIrFunc* f, CgIrOp op, SrcLoc loc) { + CgIrInst* in; + inst_grow(f, f->ninsts + 1u); + in = &f->insts[f->ninsts++]; + memset(in, 0, sizeof *in); + in->id = f->next_inst_id++; + in->op = (u16)op; + in->loc = loc; + return in; +} + +static void local_grow(CgIrFunc* f, u32 want) { + CgIrLocal* next; + u32 cap; + if (f->locals_cap >= want) return; + cap = f->locals_cap ? f->locals_cap : 32u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(f->c, f->arena, sizeof(*next) * cap, + _Alignof(CgIrLocal)); + if (f->locals) memcpy(next, f->locals, sizeof(*next) * f->nlocals); + f->locals = next; + f->locals_cap = cap; +} + +CGLocal cg_ir_func_add_local(CgIrFunc* f, const CGLocalDesc* desc, int is_param, + u32 param_index) { + CGLocal id; + CgIrLocal* l; + local_grow(f, f->nlocals + 1u); + id = f->nlocals + 1u; + l = &f->locals[f->nlocals++]; + memset(l, 0, sizeof *l); + l->id = id; + l->desc = *desc; + l->is_param = is_param ? 1u : 0u; + l->param_index = param_index; + l->address_taken = (desc->flags & CG_LOCAL_ADDR_TAKEN) != 0; + return id; +} + +void cg_ir_func_mark_local_address_taken(CgIrFunc* f, CGLocal id) { + CgIrLocal* l; + if (!f || id == CG_LOCAL_NONE || id > f->nlocals) return; + l = &f->locals[id - 1u]; + l->address_taken = 1; + l->desc.flags |= CG_LOCAL_ADDR_TAKEN; +} + +static void param_grow(CgIrFunc* f, u32 want) { + CgIrParam* next; + u32 cap; + if (f->params_cap >= want) return; + cap = f->params_cap ? f->params_cap : 8u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(f->c, f->arena, sizeof(*next) * cap, + _Alignof(CgIrParam)); + if (f->params) memcpy(next, f->params, sizeof(*next) * f->nparams); + f->params = next; + f->params_cap = cap; +} + +void cg_ir_func_add_param(CgIrFunc* f, CGLocal local, const CGParamDesc* desc) { + CgIrParam* p; + param_grow(f, f->nparams + 1u); + p = &f->params[f->nparams++]; + memset(p, 0, sizeof *p); + p->local = local; + p->desc = *desc; +} + +static void label_grow(CgIrFunc* f, u32 want) { + CgIrLabel* next; + u32 cap; + if (f->labels_cap >= want) return; + cap = f->labels_cap ? f->labels_cap : 16u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(f->c, f->arena, sizeof(*next) * cap, + _Alignof(CgIrLabel)); + if (f->labels) memcpy(next, f->labels, sizeof(*next) * f->nlabels); + f->labels = next; + f->labels_cap = cap; +} + +Label cg_ir_func_add_label(CgIrFunc* f) { + Label id; + CgIrLabel* l; + label_grow(f, f->nlabels + 1u); + id = f->nlabels + 1u; + l = &f->labels[f->nlabels++]; + memset(l, 0, sizeof *l); + l->id = id; + return id; +} + +void cg_ir_func_note_label_place(CgIrFunc* f, Label id, SrcLoc loc) { + CgIrLabel* l; + if (!f || id == LABEL_NONE || id > f->nlabels) return; + l = &f->labels[id - 1u]; + if (!l->nplaces) l->first_place_loc = loc; + ++l->nplaces; +} + +static void scope_grow(CgIrFunc* f, u32 want) { + CgIrScope* next; + u32 cap; + if (f->scopes_cap >= want) return; + cap = f->scopes_cap ? f->scopes_cap : 16u; + while (cap < want) cap *= 2u; + next = ir_zalloc_or_panic(f->c, f->arena, sizeof(*next) * cap, + _Alignof(CgIrScope)); + if (f->scopes) memcpy(next, f->scopes, sizeof(*next) * f->nscopes); + f->scopes = next; + f->scopes_cap = cap; +} + +CGScope cg_ir_func_add_scope(CgIrFunc* f, const CGScopeDesc* desc) { + CGScope id; + CgIrScope* s; + scope_grow(f, f->nscopes + 1u); + id = f->nscopes + 1u; + s = &f->scopes[f->nscopes++]; + memset(s, 0, sizeof *s); + s->id = id; + s->desc = *desc; + return id; +} + +Operand* cg_ir_dup_operands(Arena* a, const Operand* src, u32 n) { + Operand* out; + if (!n) return NULL; + out = arena_array(a, Operand, n); + memcpy(out, src, sizeof(*out) * n); + return out; +} + +CGLocal* cg_ir_dup_locals(Arena* a, const CGLocal* src, u32 n) { + CGLocal* out; + if (!n) return NULL; + out = arena_array(a, CGLocal, n); + memcpy(out, src, sizeof(*out) * n); + return out; +} + +Label* cg_ir_dup_labels(Arena* a, const Label* src, u32 n) { + Label* out; + if (!n) return NULL; + out = arena_array(a, Label, n); + memcpy(out, src, sizeof(*out) * n); + return out; +} + +CGSwitchCase* cg_ir_dup_switch_cases(Arena* a, const CGSwitchCase* src, u32 n) { + CGSwitchCase* out; + if (!n) return NULL; + out = arena_array(a, CGSwitchCase, n); + memcpy(out, src, sizeof(*out) * n); + return out; +} + +ConstBytes cg_ir_dup_const_bytes(Arena* a, ConstBytes in) { + ConstBytes out = in; + if (in.size) { + u8* bytes = arena_array(a, u8, in.size); + memcpy(bytes, in.bytes, in.size); + out.bytes = bytes; + } + return out; +} + +CGCallDesc cg_ir_dup_call_desc(Arena* a, const CGCallDesc* in) { + CGCallDesc out = *in; + out.args = cg_ir_dup_locals(a, in->args, in->nargs); + out.results = cg_ir_dup_locals(a, in->results, in->nresults); + return out; +} + +AsmConstraint* cg_ir_dup_asm_constraints(Arena* a, const AsmConstraint* src, + u32 n) { + AsmConstraint* out; + if (!n) return NULL; + out = arena_array(a, AsmConstraint, n); + memcpy(out, src, sizeof(*out) * n); + for (u32 i = 0; i < n; ++i) out[i].str = cg_ir_dup_cstr(a, src[i].str); + return out; +} diff --git a/src/cg/ir.h b/src/cg/ir.h @@ -0,0 +1,255 @@ +#ifndef CFREE_CG_IR_H +#define CFREE_CG_IR_H + +#include "cg/cgtarget.h" +#include "core/arena.h" + +typedef enum CgIrOp { + CG_IR_NOP, + CG_IR_LABEL, + + CG_IR_LOAD_IMM, + CG_IR_LOAD_CONST, + CG_IR_COPY, + CG_IR_LOAD, + CG_IR_STORE, + CG_IR_ADDR_OF, + CG_IR_TLS_ADDR_OF, + CG_IR_AGG_COPY, + CG_IR_AGG_SET, + CG_IR_BITFIELD_LOAD, + CG_IR_BITFIELD_STORE, + + CG_IR_BINOP, + CG_IR_UNOP, + CG_IR_CMP, + CG_IR_CONVERT, + + CG_IR_CALL, + CG_IR_RET, + + CG_IR_BR, + CG_IR_CMP_BRANCH, + CG_IR_SWITCH, + CG_IR_INDIRECT_BRANCH, + CG_IR_LOAD_LABEL_ADDR, + CG_IR_LOCAL_STATIC_DATA_BEGIN, + CG_IR_LOCAL_STATIC_DATA_WRITE, + CG_IR_LOCAL_STATIC_DATA_LABEL_ADDR, + CG_IR_LOCAL_STATIC_DATA_END, + CG_IR_SCOPE_BEGIN, + CG_IR_SCOPE_ELSE, + CG_IR_SCOPE_END, + CG_IR_BREAK_TO, + CG_IR_CONTINUE_TO, + + CG_IR_ALLOCA, + CG_IR_VA_START, + CG_IR_VA_ARG, + CG_IR_VA_END, + CG_IR_VA_COPY, + + CG_IR_ATOMIC_LOAD, + CG_IR_ATOMIC_STORE, + CG_IR_ATOMIC_RMW, + CG_IR_ATOMIC_CAS, + CG_IR_FENCE, + + CG_IR_INTRINSIC, + CG_IR_ASM_BLOCK, +} CgIrOp; + +typedef struct CgIrLocal { + CGLocal id; + CGLocalDesc desc; + u32 param_index; + u8 is_param; + u8 address_taken; + u8 pad[2]; +} CgIrLocal; + +typedef struct CgIrParam { + CGLocal local; + CGParamDesc desc; +} CgIrParam; + +typedef struct CgIrLabel { + Label id; + SrcLoc first_place_loc; + u32 nplaces; +} CgIrLabel; + +typedef struct CgIrScope { + CGScope id; + CGScopeDesc desc; +} CgIrScope; + +typedef struct CgIrTlsAux { + ObjSymId sym; + i64 addend; +} CgIrTlsAux; + +typedef struct CgIrAggAux { + AggregateAccess access; +} CgIrAggAux; + +typedef struct CgIrBitFieldAux { + BitFieldAccess access; +} CgIrBitFieldAux; + +typedef struct CgIrCallAux { + CGCallDesc desc; +} CgIrCallAux; + +typedef struct CgIrRetAux { + CGLocal* values; + u32 nvalues; +} CgIrRetAux; + +typedef struct CgIrCmpBranchAux { + CmpOp op; + Label target; +} CgIrCmpBranchAux; + +typedef struct CgIrSwitchAux { + CfreeCgTypeId selector_type; + Label default_label; + CGSwitchCase* cases; + u32 ncases; + u8 hint; + u8 opt_level; + u8 pad[2]; +} CgIrSwitchAux; + +typedef struct CgIrIndirectAux { + Label* targets; + u32 ntargets; +} CgIrIndirectAux; + +typedef struct CgIrLocalStaticBeginAux { + CGLocalStaticDataDesc desc; +} CgIrLocalStaticBeginAux; + +typedef struct CgIrLocalStaticWriteAux { + u8* data; + u64 len; + u8 has_data; + u8 pad[7]; +} CgIrLocalStaticWriteAux; + +typedef struct CgIrLocalStaticLabelAux { + Label target; + i64 addend; + u32 width; + u32 address_space; +} CgIrLocalStaticLabelAux; + +typedef struct CgIrScopeAux { + CGScope scope; + CGScopeDesc desc; +} CgIrScopeAux; + +typedef struct CgIrAtomicAux { + MemAccess mem; + MemOrder order; + AtomicOp op; + MemOrder failure; +} CgIrAtomicAux; + +typedef struct CgIrAsmAux { + char* tmpl; + AsmConstraint* outs; + Operand* out_ops; + AsmConstraint* ins; + Operand* in_ops; + Sym* clobbers; + u32 nout; + u32 nin; + u32 nclob; +} CgIrAsmAux; + +typedef struct CgIrIntrinsicAux { + IntrinKind kind; + Operand* dsts; + Operand* args; + u32 ndst; + u32 narg; +} CgIrIntrinsicAux; + +typedef struct CgIrInst { + u32 id; + u16 op; + u16 flags; + SrcLoc loc; + u32 nopnds; + Operand* opnds; + union { + i64 imm; + ConstBytes cbytes; + MemAccess mem; + void* aux; + } extra; +} CgIrInst; + +typedef struct CgIrFunc { + Arena* arena; + Compiler* c; + CGFuncDesc desc; + + CgIrInst* insts; + u32 ninsts; + u32 insts_cap; + + CgIrLocal* locals; + u32 nlocals; + u32 locals_cap; + + CgIrParam* params; + u32 nparams; + u32 params_cap; + + CgIrLabel* labels; + u32 nlabels; + u32 labels_cap; + + CgIrScope* scopes; + u32 nscopes; + u32 scopes_cap; + + u32 next_inst_id; + u8 complete; + u8 pad[3]; +} CgIrFunc; + +typedef struct CgIrModule { + Arena* arena; + Compiler* c; + CgIrFunc** funcs; + u32 nfuncs; + u32 funcs_cap; +} CgIrModule; + +CgIrModule* cg_ir_module_new(Compiler*); +CgIrFunc* cg_ir_func_new(Compiler*, const CGFuncDesc*); +void cg_ir_module_add_func(CgIrModule*, CgIrFunc*); + +CGLocal cg_ir_func_add_local(CgIrFunc*, const CGLocalDesc*, int is_param, + u32 param_index); +void cg_ir_func_mark_local_address_taken(CgIrFunc*, CGLocal); +void cg_ir_func_add_param(CgIrFunc*, CGLocal, const CGParamDesc*); +Label cg_ir_func_add_label(CgIrFunc*); +void cg_ir_func_note_label_place(CgIrFunc*, Label, SrcLoc); +CGScope cg_ir_func_add_scope(CgIrFunc*, const CGScopeDesc*); + +CgIrInst* cg_ir_emit(CgIrFunc*, CgIrOp, SrcLoc); + +Operand* cg_ir_dup_operands(Arena*, const Operand*, u32 n); +CGLocal* cg_ir_dup_locals(Arena*, const CGLocal*, u32 n); +Label* cg_ir_dup_labels(Arena*, const Label*, u32 n); +CGSwitchCase* cg_ir_dup_switch_cases(Arena*, const CGSwitchCase*, u32 n); +ConstBytes cg_ir_dup_const_bytes(Arena*, ConstBytes); +CGCallDesc cg_ir_dup_call_desc(Arena*, const CGCallDesc*); +AsmConstraint* cg_ir_dup_asm_constraints(Arena*, const AsmConstraint*, u32 n); +char* cg_ir_dup_cstr(Arena*, const char*); + +#endif diff --git a/src/cg/ir_recorder.c b/src/cg/ir_recorder.c @@ -0,0 +1,645 @@ +#include "cg/ir_recorder.h" + +#include <string.h> + +struct CgIrRecorder { + CgTarget base; + u32 magic; + CgIrModule* module; + CgIrFunc* cur; + SrcLoc loc; + void (*func_recorded)(void*, CgIrFunc*); + void (*finalize_recorded)(void*, const CgIrModule*); + void (*destroy_user)(void*); + int (*local_static_data_begin)(void*, const CGLocalStaticDataDesc*); + const char* (*tail_call_unrealizable_reason)(void*, const CGFuncDesc*, + const CGCallDesc*); + void* user; +}; + +#define CG_IR_RECORDER_MAGIC 0x43524952u + +static CgIrRecorder* rec_of(CgTarget* t) { return (CgIrRecorder*)t; } + +static void rec_panic(CgIrRecorder* r, const char* what) { + compiler_panic(r->base.c, r->loc, "cg ir recorder: %s", what); +} + +static CgIrFunc* require_func(CgIrRecorder* r) { + if (!r->cur) rec_panic(r, "operation outside function"); + return r->cur; +} + +static void* aux_alloc(CgIrRecorder* r, size_t size, size_t align) { + void* p = arena_zalloc(require_func(r)->arena, size, align); + if (!p) rec_panic(r, "out of memory"); + return p; +} + +#define AUX_NEW(r, T) ((T*)aux_alloc((r), sizeof(T), _Alignof(T))) + +static CgIrInst* emit(CgIrRecorder* r, CgIrOp op) { + return cg_ir_emit(require_func(r), op, r->loc); +} + +static void set_ops(CgIrRecorder* r, CgIrInst* in, const Operand* ops, u32 n) { + in->opnds = cg_ir_dup_operands(require_func(r)->arena, ops, n); + in->nopnds = n; +} + +static void rec_func_begin(CgTarget* t, const CGFuncDesc* desc) { + CgIrRecorder* r = rec_of(t); + if (r->cur) rec_panic(r, "nested function begin"); + r->cur = cg_ir_func_new(t->c, desc); + if (!r->cur) rec_panic(r, "out of memory"); + cg_ir_module_add_func(r->module, r->cur); + r->loc = desc ? desc->loc : (SrcLoc){0, 0, 0}; +} + +static void rec_func_end(CgTarget* t) { + CgIrRecorder* r = rec_of(t); + CgIrFunc* f = require_func(r); + f->complete = 1; + if (r->func_recorded) r->func_recorded(r->user, f); + r->cur = NULL; +} + +static void rec_alias(CgTarget* t, ObjSymId alias_sym, ObjSymId target_sym, + CfreeCgTypeId type) { + (void)t; + (void)alias_sym; + (void)target_sym; + (void)type; +} + +static CGLocal rec_local(CgTarget* t, const CGLocalDesc* desc) { + CgIrRecorder* r = rec_of(t); + return cg_ir_func_add_local(require_func(r), desc, 0, 0); +} + +static void rec_local_addr(CgTarget* t, Operand dst, const CGLocalDesc* desc, + CGLocal local) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_ADDR_OF); + Operand ops[2]; + (void)desc; + cg_ir_func_mark_local_address_taken(require_func(r), local); + memset(ops, 0, sizeof ops); + ops[0] = dst; + ops[1].kind = OPK_LOCAL; + ops[1].type = desc ? desc->type : dst.type; + ops[1].v.local = local; + set_ops(r, in, ops, 2); +} + +static CGLocal rec_param(CgTarget* t, const CGParamDesc* desc) { + CgIrRecorder* r = rec_of(t); + CgIrFunc* f = require_func(r); + CGLocalDesc ld; + CGLocal local; + memset(&ld, 0, sizeof ld); + ld.type = desc->type; + ld.name = desc->name; + ld.loc = desc->loc; + ld.size = desc->size; + ld.align = desc->align; + ld.flags = desc->flags; + local = cg_ir_func_add_local(f, &ld, 1, desc->index); + cg_ir_func_add_param(f, local, desc); + return local; +} + +static Label rec_label_new(CgTarget* t) { + return cg_ir_func_add_label(require_func(rec_of(t))); +} + +static void rec_label_place(CgTarget* t, Label label) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_LABEL); + in->extra.imm = (i64)label; + cg_ir_func_note_label_place(require_func(r), label, r->loc); +} + +static void rec_jump(CgTarget* t, Label label) { + CgIrInst* in = emit(rec_of(t), CG_IR_BR); + in->extra.imm = (i64)label; +} + +static void rec_cmp_branch(CgTarget* t, CmpOp op, Operand a, Operand b, + Label label) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_CMP_BRANCH); + CgIrCmpBranchAux* aux = AUX_NEW(r, CgIrCmpBranchAux); + Operand ops[2] = {a, b}; + aux->op = op; + aux->target = label; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_switch(CgTarget* t, const CGSwitchDesc* desc) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_SWITCH); + CgIrSwitchAux* aux = AUX_NEW(r, CgIrSwitchAux); + Operand op = desc->selector; + aux->selector_type = desc->selector_type; + aux->default_label = desc->default_label; + aux->cases = + cg_ir_dup_switch_cases(require_func(r)->arena, desc->cases, desc->ncases); + aux->ncases = desc->ncases; + aux->hint = desc->hint; + aux->opt_level = desc->opt_level; + set_ops(r, in, &op, 1); + in->extra.aux = aux; +} + +static void rec_indirect_branch(CgTarget* t, Operand addr, + const Label* valid_targets, u32 ntargets) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_INDIRECT_BRANCH); + CgIrIndirectAux* aux = AUX_NEW(r, CgIrIndirectAux); + aux->targets = + cg_ir_dup_labels(require_func(r)->arena, valid_targets, ntargets); + aux->ntargets = ntargets; + set_ops(r, in, &addr, 1); + in->extra.aux = aux; +} + +static void rec_load_label_addr(CgTarget* t, Operand dst, Label label) { + CgIrInst* in = emit(rec_of(t), CG_IR_LOAD_LABEL_ADDR); + set_ops(rec_of(t), in, &dst, 1); + in->extra.imm = (i64)label; +} + +static int rec_local_static_data_begin(CgTarget* t, + const CGLocalStaticDataDesc* desc) { + CgIrRecorder* r = rec_of(t); + if (r->local_static_data_begin && + !r->local_static_data_begin(r->user, desc)) { + return 0; + } + CgIrInst* in = emit(r, CG_IR_LOCAL_STATIC_DATA_BEGIN); + CgIrLocalStaticBeginAux* aux = AUX_NEW(r, CgIrLocalStaticBeginAux); + aux->desc = *desc; + in->extra.aux = aux; + return 1; +} + +static void rec_local_static_data_write(CgTarget* t, const u8* data, u64 len) { + CgIrRecorder* r = rec_of(t); + CgIrFunc* f = require_func(r); + CgIrInst* in = emit(r, CG_IR_LOCAL_STATIC_DATA_WRITE); + CgIrLocalStaticWriteAux* aux = AUX_NEW(r, CgIrLocalStaticWriteAux); + aux->len = len; + if (data && len) { + if (len > UINT32_MAX) rec_panic(r, "local static data chunk too large"); + aux->data = arena_array(f->arena, u8, (u32)len); + memcpy(aux->data, data, (size_t)len); + aux->has_data = 1; + } + in->extra.aux = aux; +} + +static void rec_local_static_data_label_addr(CgTarget* t, Label target, + i64 addend, u32 width, + u32 address_space) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_LOCAL_STATIC_DATA_LABEL_ADDR); + CgIrLocalStaticLabelAux* aux = AUX_NEW(r, CgIrLocalStaticLabelAux); + aux->target = target; + aux->addend = addend; + aux->width = width; + aux->address_space = address_space; + in->extra.aux = aux; +} + +static void rec_local_static_data_end(CgTarget* t) { + (void)emit(rec_of(t), CG_IR_LOCAL_STATIC_DATA_END); +} + +static const char* rec_data_label_addr_unsupported_msg(CgTarget* t) { + (void)t; + return "IR recorder supports function-local label address data"; +} + +static CGScope rec_scope_begin(CgTarget* t, const CGScopeDesc* desc) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_SCOPE_BEGIN); + CgIrScopeAux* aux = AUX_NEW(r, CgIrScopeAux); + CGScope scope = cg_ir_func_add_scope(require_func(r), desc); + aux->scope = scope; + aux->desc = *desc; + in->extra.aux = aux; + return scope; +} + +static void rec_scope_id_op(CgTarget* t, CgIrOp op, CGScope scope) { + CgIrInst* in = emit(rec_of(t), op); + in->extra.imm = (i64)scope; +} + +static void rec_scope_else(CgTarget* t, CGScope scope) { + rec_scope_id_op(t, CG_IR_SCOPE_ELSE, scope); +} + +static void rec_scope_end(CgTarget* t, CGScope scope) { + rec_scope_id_op(t, CG_IR_SCOPE_END, scope); +} + +static void rec_break_to(CgTarget* t, CGScope scope) { + rec_scope_id_op(t, CG_IR_BREAK_TO, scope); +} + +static void rec_continue_to(CgTarget* t, CGScope scope) { + rec_scope_id_op(t, CG_IR_CONTINUE_TO, scope); +} + +static void rec_load_imm(CgTarget* t, Operand dst, i64 imm) { + CgIrInst* in = emit(rec_of(t), CG_IR_LOAD_IMM); + set_ops(rec_of(t), in, &dst, 1); + in->extra.imm = imm; +} + +static void rec_load_const(CgTarget* t, Operand dst, ConstBytes cbytes) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_LOAD_CONST); + set_ops(r, in, &dst, 1); + in->extra.cbytes = cg_ir_dup_const_bytes(require_func(r)->arena, cbytes); +} + +static void rec_copy(CgTarget* t, Operand dst, Operand src) { + CgIrInst* in = emit(rec_of(t), CG_IR_COPY); + Operand ops[2] = {dst, src}; + set_ops(rec_of(t), in, ops, 2); +} + +static void rec_load(CgTarget* t, Operand dst, Operand addr, MemAccess mem) { + CgIrInst* in = emit(rec_of(t), CG_IR_LOAD); + Operand ops[2] = {dst, addr}; + set_ops(rec_of(t), in, ops, 2); + in->extra.mem = mem; +} + +static void rec_store(CgTarget* t, Operand addr, Operand src, MemAccess mem) { + CgIrInst* in = emit(rec_of(t), CG_IR_STORE); + Operand ops[2] = {addr, src}; + set_ops(rec_of(t), in, ops, 2); + in->extra.mem = mem; +} + +static void rec_addr_of(CgTarget* t, Operand dst, Operand lv) { + CgIrInst* in = emit(rec_of(t), CG_IR_ADDR_OF); + Operand ops[2] = {dst, lv}; + set_ops(rec_of(t), in, ops, 2); + if (lv.kind == OPK_LOCAL) + cg_ir_func_mark_local_address_taken(require_func(rec_of(t)), lv.v.local); +} + +static void rec_tls_addr_of(CgTarget* t, Operand dst, ObjSymId sym, + i64 addend) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_TLS_ADDR_OF); + CgIrTlsAux* aux = AUX_NEW(r, CgIrTlsAux); + aux->sym = sym; + aux->addend = addend; + set_ops(r, in, &dst, 1); + in->extra.aux = aux; +} + +static void rec_copy_bytes(CgTarget* t, Operand dst_addr, Operand src_addr, + AggregateAccess access) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_AGG_COPY); + CgIrAggAux* aux = AUX_NEW(r, CgIrAggAux); + Operand ops[2] = {dst_addr, src_addr}; + aux->access = access; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_set_bytes(CgTarget* t, Operand dst_addr, Operand byte_value, + AggregateAccess access) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_AGG_SET); + CgIrAggAux* aux = AUX_NEW(r, CgIrAggAux); + Operand ops[2] = {dst_addr, byte_value}; + aux->access = access; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_bitfield_load(CgTarget* t, Operand dst, Operand record_addr, + BitFieldAccess access) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_BITFIELD_LOAD); + CgIrBitFieldAux* aux = AUX_NEW(r, CgIrBitFieldAux); + Operand ops[2] = {dst, record_addr}; + aux->access = access; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_bitfield_store(CgTarget* t, Operand record_addr, Operand src, + BitFieldAccess access) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_BITFIELD_STORE); + CgIrBitFieldAux* aux = AUX_NEW(r, CgIrBitFieldAux); + Operand ops[2] = {record_addr, src}; + aux->access = access; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_binop(CgTarget* t, BinOp op, Operand dst, Operand a, + Operand b) { + CgIrInst* in = emit(rec_of(t), CG_IR_BINOP); + Operand ops[3] = {dst, a, b}; + set_ops(rec_of(t), in, ops, 3); + in->extra.imm = (i64)op; +} + +static void rec_unop(CgTarget* t, UnOp op, Operand dst, Operand a) { + CgIrInst* in = emit(rec_of(t), CG_IR_UNOP); + Operand ops[2] = {dst, a}; + set_ops(rec_of(t), in, ops, 2); + in->extra.imm = (i64)op; +} + +static void rec_cmp(CgTarget* t, CmpOp op, Operand dst, Operand a, Operand b) { + CgIrInst* in = emit(rec_of(t), CG_IR_CMP); + Operand ops[3] = {dst, a, b}; + set_ops(rec_of(t), in, ops, 3); + in->extra.imm = (i64)op; +} + +static void rec_convert(CgTarget* t, ConvKind op, Operand dst, Operand src) { + CgIrInst* in = emit(rec_of(t), CG_IR_CONVERT); + Operand ops[2] = {dst, src}; + set_ops(rec_of(t), in, ops, 2); + in->extra.imm = (i64)op; +} + +static void rec_call(CgTarget* t, const CGCallDesc* desc) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_CALL); + CgIrCallAux* aux = AUX_NEW(r, CgIrCallAux); + aux->desc = cg_ir_dup_call_desc(require_func(r)->arena, desc); + in->extra.aux = aux; +} + +static const char* rec_tail_call_unrealizable_reason(CgTarget* t, + const CGCallDesc* desc) { + CgIrRecorder* r = rec_of(t); + if (r->tail_call_unrealizable_reason) { + CgIrFunc* f = require_func(r); + return r->tail_call_unrealizable_reason(r->user, &f->desc, desc); + } + return NULL; +} + +static void rec_ret(CgTarget* t, const CGLocal* values, u32 nvalues) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_RET); + CgIrRetAux* aux = AUX_NEW(r, CgIrRetAux); + aux->values = cg_ir_dup_locals(require_func(r)->arena, values, nvalues); + aux->nvalues = nvalues; + in->extra.aux = aux; +} + +static void rec_alloca(CgTarget* t, Operand dst, Operand size, u32 align) { + CgIrInst* in = emit(rec_of(t), CG_IR_ALLOCA); + Operand ops[2] = {dst, size}; + set_ops(rec_of(t), in, ops, 2); + in->extra.imm = (i64)align; +} + +static void rec_va_start(CgTarget* t, Operand ap_addr) { + CgIrInst* in = emit(rec_of(t), CG_IR_VA_START); + set_ops(rec_of(t), in, &ap_addr, 1); +} + +static void rec_va_arg(CgTarget* t, Operand dst, Operand ap_addr, + CfreeCgTypeId type) { + CgIrInst* in = emit(rec_of(t), CG_IR_VA_ARG); + Operand ops[2] = {dst, ap_addr}; + set_ops(rec_of(t), in, ops, 2); + in->extra.imm = (i64)type; +} + +static void rec_va_end(CgTarget* t, Operand ap_addr) { + CgIrInst* in = emit(rec_of(t), CG_IR_VA_END); + set_ops(rec_of(t), in, &ap_addr, 1); +} + +static void rec_va_copy(CgTarget* t, Operand dst_ap_addr, Operand src_ap_addr) { + CgIrInst* in = emit(rec_of(t), CG_IR_VA_COPY); + Operand ops[2] = {dst_ap_addr, src_ap_addr}; + set_ops(rec_of(t), in, ops, 2); +} + +static void rec_atomic_load(CgTarget* t, Operand dst, Operand addr, + MemAccess mem, MemOrder order) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_ATOMIC_LOAD); + CgIrAtomicAux* aux = AUX_NEW(r, CgIrAtomicAux); + Operand ops[2] = {dst, addr}; + aux->mem = mem; + aux->order = order; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_atomic_store(CgTarget* t, Operand addr, Operand src, + MemAccess mem, MemOrder order) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_ATOMIC_STORE); + CgIrAtomicAux* aux = AUX_NEW(r, CgIrAtomicAux); + Operand ops[2] = {addr, src}; + aux->mem = mem; + aux->order = order; + set_ops(r, in, ops, 2); + in->extra.aux = aux; +} + +static void rec_atomic_rmw(CgTarget* t, AtomicOp op, Operand dst, Operand addr, + Operand val, MemAccess mem, MemOrder order) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_ATOMIC_RMW); + CgIrAtomicAux* aux = AUX_NEW(r, CgIrAtomicAux); + Operand ops[3] = {dst, addr, val}; + aux->mem = mem; + aux->order = order; + aux->op = op; + set_ops(r, in, ops, 3); + in->extra.aux = aux; +} + +static void rec_atomic_cas(CgTarget* t, Operand prior, Operand ok, Operand addr, + Operand expected, Operand desired, MemAccess mem, + MemOrder success, MemOrder failure) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_ATOMIC_CAS); + CgIrAtomicAux* aux = AUX_NEW(r, CgIrAtomicAux); + Operand ops[5] = {prior, ok, addr, expected, desired}; + aux->mem = mem; + aux->order = success; + aux->failure = failure; + set_ops(r, in, ops, 5); + in->extra.aux = aux; +} + +static void rec_fence(CgTarget* t, MemOrder order) { + CgIrInst* in = emit(rec_of(t), CG_IR_FENCE); + in->extra.imm = (i64)order; +} + +static void rec_intrinsic(CgTarget* t, IntrinKind kind, Operand* dsts, u32 ndst, + const Operand* args, u32 narg) { + CgIrRecorder* r = rec_of(t); + CgIrInst* in = emit(r, CG_IR_INTRINSIC); + CgIrIntrinsicAux* aux = AUX_NEW(r, CgIrIntrinsicAux); + aux->kind = kind; + aux->dsts = cg_ir_dup_operands(require_func(r)->arena, dsts, ndst); + aux->args = cg_ir_dup_operands(require_func(r)->arena, args, narg); + aux->ndst = ndst; + aux->narg = narg; + in->extra.aux = aux; +} + +static void rec_asm_block(CgTarget* t, const char* tmpl, + const AsmConstraint* outs, u32 nout, Operand* out_ops, + const AsmConstraint* ins, u32 nin, + const Operand* in_ops, const Sym* clobbers, + u32 nclob) { + CgIrRecorder* r = rec_of(t); + CgIrFunc* f = require_func(r); + CgIrInst* in = emit(r, CG_IR_ASM_BLOCK); + CgIrAsmAux* aux = AUX_NEW(r, CgIrAsmAux); + aux->tmpl = cg_ir_dup_cstr(f->arena, tmpl); + aux->outs = cg_ir_dup_asm_constraints(f->arena, outs, nout); + aux->out_ops = cg_ir_dup_operands(f->arena, out_ops, nout); + aux->ins = cg_ir_dup_asm_constraints(f->arena, ins, nin); + aux->in_ops = cg_ir_dup_operands(f->arena, in_ops, nin); + if (nclob) { + aux->clobbers = arena_array(f->arena, Sym, nclob); + memcpy(aux->clobbers, clobbers, sizeof(*aux->clobbers) * nclob); + } + aux->nout = nout; + aux->nin = nin; + aux->nclob = nclob; + in->extra.aux = aux; +} + +static void rec_file_scope_asm(CgTarget* t, const char* src, size_t len) { + (void)t; + (void)src; + (void)len; +} + +static void rec_set_loc(CgTarget* t, SrcLoc loc) { rec_of(t)->loc = loc; } + +static void rec_finalize(CgTarget* t) { + CgIrRecorder* r = rec_of(t); + if (r->cur) rec_panic(r, "finalize with open function"); + if (r->finalize_recorded) r->finalize_recorded(r->user, r->module); +} + +static void rec_destroy(CgTarget* t) { + CgIrRecorder* r = rec_of(t); + if (r->destroy_user) r->destroy_user(r->user); +} + +CgTarget* cg_ir_recorder_new(Compiler* c, ObjBuilder* obj, + const CgIrRecorderConfig* cfg) { + CgIrRecorder* r; + if (!c) return NULL; + r = arena_znew(c->tu, CgIrRecorder); + if (!r) return NULL; + r->base.c = c; + r->base.obj = obj; + r->magic = CG_IR_RECORDER_MAGIC; + r->module = cg_ir_module_new(c); + if (!r->module) return NULL; + if (cfg) { + r->func_recorded = cfg->func_recorded; + r->finalize_recorded = cfg->finalize; + r->destroy_user = cfg->destroy; + r->local_static_data_begin = cfg->local_static_data_begin; + r->tail_call_unrealizable_reason = cfg->tail_call_unrealizable_reason; + r->user = cfg->user; + } + + r->base.func_begin = rec_func_begin; + r->base.func_end = rec_func_end; + r->base.alias = rec_alias; + r->base.local = rec_local; + r->base.local_addr = rec_local_addr; + r->base.param = rec_param; + r->base.label_new = rec_label_new; + r->base.label_place = rec_label_place; + r->base.jump = rec_jump; + r->base.cmp_branch = rec_cmp_branch; + r->base.switch_ = rec_switch; + r->base.indirect_branch = rec_indirect_branch; + r->base.load_label_addr = rec_load_label_addr; + r->base.local_static_data_begin = rec_local_static_data_begin; + r->base.local_static_data_write = rec_local_static_data_write; + r->base.local_static_data_label_addr = rec_local_static_data_label_addr; + r->base.local_static_data_end = rec_local_static_data_end; + r->base.data_label_addr_unsupported_msg = rec_data_label_addr_unsupported_msg; + r->base.scope_begin = rec_scope_begin; + r->base.scope_else = rec_scope_else; + r->base.scope_end = rec_scope_end; + r->base.break_to = rec_break_to; + r->base.continue_to = rec_continue_to; + r->base.load_imm = rec_load_imm; + r->base.load_const = rec_load_const; + r->base.copy = rec_copy; + r->base.load = rec_load; + r->base.store = rec_store; + r->base.addr_of = rec_addr_of; + r->base.tls_addr_of = rec_tls_addr_of; + r->base.copy_bytes = rec_copy_bytes; + r->base.set_bytes = rec_set_bytes; + r->base.bitfield_load = rec_bitfield_load; + r->base.bitfield_store = rec_bitfield_store; + r->base.binop = rec_binop; + r->base.unop = rec_unop; + r->base.cmp = rec_cmp; + r->base.convert = rec_convert; + r->base.call = rec_call; + r->base.tail_call_unrealizable_reason = rec_tail_call_unrealizable_reason; + r->base.ret = rec_ret; + r->base.alloca_ = rec_alloca; + r->base.va_start_ = rec_va_start; + r->base.va_arg_ = rec_va_arg; + r->base.va_end_ = rec_va_end; + r->base.va_copy_ = rec_va_copy; + r->base.atomic_load = rec_atomic_load; + r->base.atomic_store = rec_atomic_store; + r->base.atomic_rmw = rec_atomic_rmw; + r->base.atomic_cas = rec_atomic_cas; + r->base.fence = rec_fence; + r->base.intrinsic = rec_intrinsic; + r->base.asm_block = rec_asm_block; + r->base.file_scope_asm = rec_file_scope_asm; + r->base.set_loc = rec_set_loc; + r->base.finalize = rec_finalize; + r->base.destroy = rec_destroy; + return &r->base; +} + +CgIrRecorder* cg_ir_recorder_from_target(CgTarget* t) { + CgIrRecorder* r = t ? rec_of(t) : NULL; + return r && r->magic == CG_IR_RECORDER_MAGIC ? r : NULL; +} + +const CgIrModule* cg_ir_recorder_module(const CgTarget* t) { + const CgIrRecorder* r = (const CgIrRecorder*)t; + return r && r->magic == CG_IR_RECORDER_MAGIC ? r->module : NULL; +} + +CgIrFunc* cg_ir_recorder_current_func(const CgTarget* t) { + const CgIrRecorder* r = (const CgIrRecorder*)t; + return r && r->magic == CG_IR_RECORDER_MAGIC ? r->cur : NULL; +} diff --git a/src/cg/ir_recorder.h b/src/cg/ir_recorder.h @@ -0,0 +1,24 @@ +#ifndef CFREE_CG_IR_RECORDER_H +#define CFREE_CG_IR_RECORDER_H + +#include "cg/ir.h" + +typedef struct CgIrRecorder CgIrRecorder; + +typedef struct CgIrRecorderConfig { + void (*func_recorded)(void* user, CgIrFunc* func); + void (*finalize)(void* user, const CgIrModule* module); + void (*destroy)(void* user); + int (*local_static_data_begin)(void* user, const CGLocalStaticDataDesc* desc); + const char* (*tail_call_unrealizable_reason)(void* user, + const CGFuncDesc* caller, + const CGCallDesc* call); + void* user; +} CgIrRecorderConfig; + +CgTarget* cg_ir_recorder_new(Compiler*, ObjBuilder*, const CgIrRecorderConfig*); +CgIrRecorder* cg_ir_recorder_from_target(CgTarget*); +const CgIrModule* cg_ir_recorder_module(const CgTarget*); +CgIrFunc* cg_ir_recorder_current_func(const CgTarget*); + +#endif diff --git a/test/cg/ir_recorder_test.c b/test/cg/ir_recorder_test.c @@ -0,0 +1,343 @@ +#include "cg/ir_recorder.h" + +#include <cfree/core.h> +#include <stdarg.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "core/pool.h" + +static void* h_alloc(CfreeHeap* h, size_t n, size_t a) { + (void)h; + (void)a; + return n ? malloc(n) : NULL; +} + +static void* h_realloc(CfreeHeap* h, void* p, size_t o, size_t n, size_t a) { + (void)h; + (void)o; + (void)a; + return realloc(p, n); +} + +static void h_free(CfreeHeap* h, void* p, size_t n) { + (void)h; + (void)n; + free(p); +} + +static CfreeHeap g_heap = {h_alloc, h_realloc, h_free, NULL}; +static int g_fails; +static int g_checks; + +static void diag_emit(CfreeDiagSink* s, CfreeDiagKind k, CfreeSrcLoc loc, + const char* fmt, va_list ap) { + static const char* names[] = {"note", "warning", "error", "fatal"}; + (void)s; + (void)loc; + fprintf(stderr, "%s: ", names[k]); + vfprintf(stderr, fmt, ap); + fputc('\n', stderr); +} + +static CfreeDiagSink g_diag = {diag_emit, NULL, 0, 0}; + +#define EXPECT(cond, ...) \ + do { \ + ++g_checks; \ + if (!(cond)) { \ + ++g_fails; \ + fprintf(stderr, "FAIL %s:%d: ", __FILE__, __LINE__); \ + fprintf(stderr, __VA_ARGS__); \ + fputc('\n', stderr); \ + } \ + } while (0) + +typedef struct TestCtx { + CfreeContext ctx; + Compiler* c; + CfreeCgTypeId i32; + CfreeCgTypeId ptr; +} TestCtx; + +static void tc_init(TestCtx* tc) { + CfreeTarget target; + CfreeCgBuiltinTypes b; + memset(tc, 0, sizeof *tc); + tc->ctx.heap = &g_heap; + tc->ctx.diag = &g_diag; + tc->ctx.now = -1; + memset(&target, 0, sizeof target); + target.arch = CFREE_ARCH_X86_64; + target.os = CFREE_OS_LINUX; + target.obj = CFREE_OBJ_ELF; + target.ptr_size = 8; + target.ptr_align = 8; + if (cfree_compiler_new(target, &tc->ctx, (CfreeCompiler**)&tc->c) != + CFREE_OK || + !tc->c) { + fprintf(stderr, "fatal: compiler allocation failed\n"); + abort(); + } + b = cfree_cg_builtin_types(tc->c); + tc->i32 = b.id[CFREE_CG_BUILTIN_I32]; + tc->ptr = cfree_cg_type_ptr(tc->c, b.id[CFREE_CG_BUILTIN_VOID], 0); +} + +static void tc_fini(TestCtx* tc) { + cfree_compiler_free(tc->c); + tc->c = NULL; +} + +static Operand op_local(CGLocal local, CfreeCgTypeId type) { + Operand o; + memset(&o, 0, sizeof o); + o.kind = OPK_LOCAL; + o.type = type; + o.v.local = local; + return o; +} + +static Operand op_imm(i64 value, CfreeCgTypeId type) { + Operand o; + memset(&o, 0, sizeof o); + o.kind = OPK_IMM; + o.type = type; + o.v.imm = value; + return o; +} + +static Operand op_global(ObjSymId sym, CfreeCgTypeId type) { + Operand o; + memset(&o, 0, sizeof o); + o.kind = OPK_GLOBAL; + o.type = type; + o.v.global.sym = sym; + return o; +} + +static CGLocal local_new(CgTarget* t, CfreeCgTypeId type, const char* name) { + CGLocalDesc d; + memset(&d, 0, sizeof d); + d.type = type; + d.name = name ? pool_intern_slice(t->c->global, cfree_slice_cstr(name)) : 0; + d.size = 4; + d.align = 4; + return t->local(t, &d); +} + +static CGFuncDesc fn_desc(TestCtx* tc) { + CGFuncDesc fd; + CfreeCgFuncSig sig; + memset(&fd, 0, sizeof fd); + memset(&sig, 0, sizeof sig); + sig.ret = tc->i32; + sig.call_conv = CFREE_CG_CC_TARGET_C; + fd.fn_type = cfree_cg_type_func(tc->c, sig); + fd.loc.line = 3; + fd.loc.col = 1; + return fd; +} + +typedef struct CallbackState { + u32 count; + CgIrFunc* last; +} CallbackState; + +static void on_func(void* user, CgIrFunc* func) { + CallbackState* s = user; + ++s->count; + s->last = func; +} + +static CgTarget* make_recorder(TestCtx* tc, CallbackState* cb) { + CgIrRecorderConfig cfg; + memset(&cfg, 0, sizeof cfg); + cfg.func_recorded = on_func; + cfg.user = cb; + return cg_ir_recorder_new(tc->c, NULL, &cfg); +} + +static void test_records_basic_function_shape(void) { + TestCtx tc; + CallbackState cb; + CgTarget* t; + CGFuncDesc fd; + CGLocal a, b, dst; + const CgIrModule* m; + CgIrFunc* f; + CgIrRetAux* ret; + memset(&cb, 0, sizeof cb); + tc_init(&tc); + t = make_recorder(&tc, &cb); + fd = fn_desc(&tc); + t->func_begin(t, &fd); + t->set_loc(t, (SrcLoc){.file_id = 9, .line = 7, .col = 5}); + a = local_new(t, tc.i32, "a"); + b = local_new(t, tc.i32, "b"); + dst = local_new(t, tc.i32, "dst"); + t->load_imm(t, op_local(a, tc.i32), 40); + t->load_imm(t, op_local(b, tc.i32), 2); + t->binop(t, BO_IADD, op_local(dst, tc.i32), op_local(a, tc.i32), + op_local(b, tc.i32)); + t->ret(t, &dst, 1); + t->func_end(t); + + m = cg_ir_recorder_module(t); + EXPECT(m && m->nfuncs == 1, "expected one recorded function"); + f = m->funcs[0]; + EXPECT(f == cb.last && cb.count == 1, "callback should observe func_end"); + EXPECT(f->complete, "function should be complete"); + EXPECT(f->nlocals == 3, "expected 3 locals, got %u", f->nlocals); + EXPECT(f->ninsts == 4, "expected 4 insts, got %u", f->ninsts); + EXPECT(f->insts[0].op == CG_IR_LOAD_IMM && f->insts[0].extra.imm == 40, + "first inst should be load_imm 40"); + EXPECT(f->insts[2].op == CG_IR_BINOP && f->insts[2].opnds[0].v.local == dst && + f->insts[2].opnds[1].v.local == a && + f->insts[2].opnds[2].v.local == b && + f->insts[2].extra.imm == BO_IADD, + "binop should preserve semantic local operands"); + EXPECT(f->insts[2].loc.file_id == 9 && f->insts[2].loc.line == 7, + "sticky source location should be stamped on insts"); + ret = (CgIrRetAux*)f->insts[3].extra.aux; + EXPECT(ret && ret->nvalues == 1 && ret->values[0] == dst, + "return should preserve semantic result locals"); + tc_fini(&tc); +} + +static void test_deep_copies_call_switch_and_const_payloads(void) { + TestCtx tc; + CallbackState cb; + CgTarget* t; + CGFuncDesc fd; + CGLocal arg, result; + CGCallDesc call; + CGLocal call_args[1]; + CGLocal call_results[1]; + CGSwitchDesc sw; + CGSwitchCase cases[2]; + u8 bytes[4] = {1, 2, 3, 4}; + ConstBytes cbv; + Label l0, l1; + CgIrFunc* f; + CgIrCallAux* call_aux; + CgIrSwitchAux* switch_aux; + memset(&cb, 0, sizeof cb); + tc_init(&tc); + t = make_recorder(&tc, &cb); + fd = fn_desc(&tc); + t->func_begin(t, &fd); + arg = local_new(t, tc.i32, "arg"); + result = local_new(t, tc.i32, "result"); + l0 = t->label_new(t); + l1 = t->label_new(t); + + memset(&call, 0, sizeof call); + call_args[0] = arg; + call_results[0] = result; + call.fn_type = fd.fn_type; + call.callee = op_global(12, tc.ptr); + call.args = call_args; + call.results = call_results; + call.nargs = 1; + call.nresults = 1; + t->call(t, &call); + call_args[0] = 999; + call_results[0] = 1000; + + memset(&sw, 0, sizeof sw); + cases[0].value = 4; + cases[0].label = l0; + cases[1].value = 9; + cases[1].label = l1; + sw.selector = op_local(arg, tc.i32); + sw.selector_type = tc.i32; + sw.default_label = l1; + sw.cases = cases; + sw.ncases = 2; + sw.hint = 7; + t->switch_(t, &sw); + cases[0].value = 400; + cases[0].label = 400; + + memset(&cbv, 0, sizeof cbv); + cbv.type = tc.i32; + cbv.bytes = bytes; + cbv.size = sizeof bytes; + cbv.align = 4; + t->load_const(t, op_local(result, tc.i32), cbv); + bytes[0] = 99; + t->func_end(t); + + f = cb.last; + EXPECT(f->ninsts == 3, "expected call, switch, load_const"); + call_aux = (CgIrCallAux*)f->insts[0].extra.aux; + EXPECT(call_aux && call_aux->desc.args[0] == arg && + call_aux->desc.results[0] == result, + "call descriptor should be deep-copied"); + switch_aux = (CgIrSwitchAux*)f->insts[1].extra.aux; + EXPECT(switch_aux && switch_aux->ncases == 2 && + switch_aux->cases[0].value == 4 && + switch_aux->cases[0].label == l0 && + switch_aux->cases[1].label == l1, + "switch cases should be deep-copied"); + EXPECT(f->insts[2].extra.cbytes.bytes[0] == 1 && + f->insts[2].extra.cbytes.size == 4, + "const bytes should be deep-copied"); + tc_fini(&tc); +} + +static void test_labels_scopes_and_address_taken_locals(void) { + TestCtx tc; + CallbackState cb; + CgTarget* t; + CGFuncDesc fd; + CGLocal ptr, value; + Label label; + CGScope scope; + CGScopeDesc sd; + CgIrFunc* f; + CgIrScopeAux* scope_aux; + memset(&cb, 0, sizeof cb); + tc_init(&tc); + t = make_recorder(&tc, &cb); + fd = fn_desc(&tc); + t->func_begin(t, &fd); + ptr = local_new(t, tc.ptr, "ptr"); + value = local_new(t, tc.i32, "value"); + label = t->label_new(t); + t->label_place(t, label); + t->load_label_addr(t, op_local(ptr, tc.ptr), label); + t->addr_of(t, op_local(ptr, tc.ptr), op_local(value, tc.i32)); + memset(&sd, 0, sizeof sd); + sd.kind = SCOPE_IF; + sd.cond = op_imm(1, tc.i32); + scope = t->scope_begin(t, &sd); + t->break_to(t, scope); + t->scope_end(t, scope); + t->func_end(t); + + f = cb.last; + EXPECT(f->nlabels == 1 && f->labels[0].nplaces == 1, + "label placement should be tracked"); + EXPECT(f->insts[0].op == CG_IR_LABEL && f->insts[0].extra.imm == label, + "label placement should be a linear IR inst"); + EXPECT(f->locals[value - 1u].address_taken, + "addr_of local should mark the local address-taken"); + EXPECT(f->nscopes == 1 && f->scopes[0].id == scope, + "scope table should preserve semantic scope ids"); + scope_aux = (CgIrScopeAux*)f->insts[3].extra.aux; + EXPECT(scope_aux && scope_aux->scope == scope && + scope_aux->desc.kind == SCOPE_IF, + "scope_begin inst should carry scope metadata"); + tc_fini(&tc); +} + +int main(void) { + test_records_basic_function_shape(); + test_deep_copies_call_switch_and_const_payloads(); + test_labels_scopes_and_address_taken_locals(); + fprintf(stderr, "ir-recorder: %d checks, %d failures\n", g_checks, g_fails); + return g_fails ? 1 : 0; +} diff --git a/test/test.mk b/test/test.mk @@ -51,6 +51,7 @@ TEST_TARGETS = \ test-dwarf \ test-elf \ test-emu \ + test-ir-recorder \ test-isa \ test-lib-deps \ test-libc \ @@ -231,6 +232,7 @@ $(EMU_RV64_TEST_BIN): test/emu/rv64_smoke_test.c $(LIB_AR) CG_API_TEST_BIN = build/test/cg_api_test CG_SWITCH_TEST_BIN = build/test/cg_switch_test ABI_CLASSIFY_TEST_BIN = build/test/abi_classify_test +IR_RECORDER_TEST_BIN = build/test/ir_recorder_test test-cg-api: $(CG_API_TEST_BIN) $(CG_SWITCH_TEST_BIN) $(CG_API_TEST_BIN) @@ -251,6 +253,13 @@ $(ABI_CLASSIFY_TEST_BIN): test/api/abi_classify_test.c $(LIB_OBJS) @mkdir -p $(dir $@) $(CC) $(TEST_HOST_CFLAGS) -Isrc test/api/abi_classify_test.c $(LIB_OBJS) -o $@ +test-ir-recorder: $(IR_RECORDER_TEST_BIN) + $(IR_RECORDER_TEST_BIN) + +$(IR_RECORDER_TEST_BIN): test/cg/ir_recorder_test.c $(LIB_OBJS) + @mkdir -p $(dir $@) + $(CC) $(TEST_HOST_CFLAGS) -Isrc test/cg/ir_recorder_test.c $(LIB_OBJS) -o $@ + test-toy: bin @CFREE=$(abspath $(BIN)) test/toy/run.sh