kit

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

commit d8f401b016a594425174caeacea85b888c39a1fa
parent c5025473abe14579fbfda9365b3b7a6d212f1fb9
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 20 May 2026 11:34:23 -0700

c_target: land Phase 4 of the C-source backend

Implements data emission (OPK_GLOBAL, with constructor-time reloc fixups),
atomics via __atomic_* builtins, inline asm re-serialization as GCC extended
asm, bitfield mask/shift on opaque storage, load_const via memcpy from a
static byte array, _Thread_local tls_addr_of, and identifier sanitization
for cfree's assembler-style local symbol names.

Toy under path C goes from 87/40 (pass/skip) to 124/3. The 3 remaining
SKIPs are pcrel and symdiff data relocs, which have no faithful in-language
C-source equivalent and are documented as deferred.

Diffstat:
Mdoc/CBACKEND.md | 66++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--------
Msrc/arch/c_target/emit.c | 856++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---
Msrc/arch/c_target/internal.h | 5+++++
Msrc/arch/c_target/target.c | 97+++++++++++++++++++++++--------------------------------------------------------
4 files changed, 925 insertions(+), 99 deletions(-)

diff --git a/doc/CBACKEND.md b/doc/CBACKEND.md @@ -517,14 +517,64 @@ Other choices that landed in Phase 3: Acceptance met: 87 toy cases under path `C` pass; the remaining 40 SKIPs all blame Phase 4 surface (data emission, atomics, inline asm). -### Phase 4 — atomics, asm, TLS, exotic features - -- Atomic load/store/rmw/cas/fence. -- `asm_block` re-serialization. -- TLS, weak, visibility, `_Alignas`. -- `bitfield_load`/`_store`. -- `data_*` definition emission including const_data. -- `tls_addr_of`, `alloca_`. +### Phase 4 — atomics, asm, TLS, exotic features ✅ landed + +Implemented: `atomic_load`/`atomic_store`/`atomic_rmw`/`atomic_cas`/`fence` +via `__atomic_*` builtins; `asm_block` re-serialized as GCC extended asm; +data-symbol emission with `static`/`__attribute__((weak))`/visibility/ +`_Alignas`; `bitfield_load`/`_store` via mask+shift on opaque storage units; +`load_const` via static-const byte array + `__builtin_memcpy`; +`tls_addr_of` + `_Thread_local` extern TLS data; OPK_GLOBAL operand support +in all use sites; identifier sanitization (cfree's `.L...` assembler-style +local syms become C-safe). + +Other choices that landed in Phase 4: + +- **Data is opaque bytes.** Every data symbol emits as `_Alignas(A) uint8_t + name[N] = { 0x.., ... };` regardless of source type. Field/element access + goes through the existing `(*(T*)((char*)addr + ofs))` path, sidestepping + C's static-initializer constraints. +- **Relocations run at startup.** Cross-symbol references in data + initializers become `__attribute__((constructor)) static void + __cfree_init_<name>(void) { *(uintN_t*)(...) = (uintN_t)(uintptr_t)(...); + }` thunks. Same path covers same-TU and cross-TU targets uniformly. + Cost: rodata-with-relocs loses its `const` qualifier (the constructor + writes through). Reasonable trade for the simplification. +- **pcrel + symdiff are skipped.** Both are link-time concepts with no + faithful in-language C-source equivalent (a constructor fixup can write + the absolute value but not preserve the "section-relative" semantics that + pcrel users actually want — they want shared `.rodata` pages, not dirty + per-process tables). v1 lift would emit format-gated top-level + `__asm__(".long sym - .\n")`; deferred. +- **Atomics via `__atomic_*` builtins.** Memory order maps 1-1 to + `__ATOMIC_*`. `compare_exchange_n` needs a real lvalue for `expected`, + but CG reuses reg ids across types so `&prior_reg` may have the wrong C + type — we materialize a scratch local typed by `prior.type` and copy out. +- **Atomic value-arg cast bridging.** Same reg-id-reuse hazard exists for + store/rmw value operands; `c_emit_operand_as(t, op, target_ty)` bridges + the cast through `(uintptr_t)` (or skips for float dst) so `-Wint- + conversion` doesn't trip. +- **asm_block input filter.** cfree synthesizes a matching `"N"` input for + every ASM_INOUT output to model the read in IR; gcc rejects that as + redundant when the output is `+r`-tied, so we drop synthesized matches. +- **Bitfields don't use C bitfields.** Loads/stores spell explicit shift + + mask on a `uint{8,16,32,64}_t` storage view. Signed loads use the + shift-left-then-arith-shift-right trick on a signed cast. The host C + compiler never sees a `int x : 3;`-style declaration, so its ABI choices + don't enter the picture. +- **Mach-O TLS definitions panic-as-skip.** On Mach-O the user-visible TLS + sym is the 24-byte TLV descriptor; reconstructing the original initial + value from the synthesized `__init.<name>` aux sym is non-trivial. ELF + TLS data definitions (where `.tdata`/`.tbss` carry the user's bytes + directly) work — emit `_Thread_local _Alignas(A) uint8_t name[N] = + {bytes};`. Mach-O TLS externs work too (they're SK_UNDEF, no descriptor). +- **Identifier sanitization.** cfree mints assembler-style names like + `.Ltoy_static_0` for static locals; `c_sym_name` rewrites illegal C + identifier chars to `_`. Safe because such names are SB_LOCAL and have + no cross-TU contract. + +Acceptance: 124 toy cases under path `C` pass. The 3 remaining SKIPs are +pcrel/symdiff data relocs as documented. ### Phase 5 — quality diff --git a/src/arch/c_target/emit.c b/src/arch/c_target/emit.c @@ -1,4 +1,4 @@ -/* Phase 1 C-source emission for the CGTarget vtable. See doc/CBACKEND.md. +/* C-source emission for the CGTarget vtable. See doc/CBACKEND.md. * * Output strategy * --------------- @@ -246,9 +246,10 @@ static const char* c_float_type_name(u32 width) { } } -/* Phase 1/3: void / bool / sized int / pointer / float / record / array / - * enum / vararg-state. Returns interned C type names; for composites lazily - * emits a typedef into t->typedefs and returns the typedef name. */ +/* 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); @@ -449,13 +450,25 @@ void c_emit_operand(CTarget* t, Operand op) { return; } case OPK_GLOBAL: { - /* Data globals need an `extern T name;` declaration AND, in the - * single-TU case, the actual data definition — both belong to data - * emission (Phase 4). Defer with a graceful panic the harness reports - * as SKIP. Function callees go through c_call directly, not here. */ - SrcLoc loc = t->cur_fn ? t->cur_fn->loc : (SrcLoc){0, 0, 0}; - compiler_panic(t->c, loc, - "C target: OPK_GLOBAL data reference not yet supported"); + /* Address expression for `&sym + addend`. The data symbol is declared + * elsewhere (either as a function forward, or as `uint8_t name[N]` + * data emitted at finalize); here we just form the address with the + * right pointer type. */ + const char* nm = c_sym_name(t, op.v.global.sym); + 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}; @@ -547,6 +560,20 @@ static int c_operand_is_ptr_typed(CTarget* t, Operand op) { return 0; } +/* Emit `(target_ty)(uintptr_t)(op)` (or `(target_ty)(op)` for float + * target_ty). Used when the caller knows the C expression type they want and + * the source operand may have been declared with a different type (CG reuses + * reg ids across types). Without the bridge, gcc trips -Wint-conversion. */ +static void c_emit_operand_as(CTarget* t, Operand op, CfreeCgTypeId target_ty) { + cbuf_puts(&t->body, "("); + c_emit_type(t, &t->body, target_ty); + cbuf_puts(&t->body, ")"); + if (!c_type_is_float(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). */ @@ -589,10 +616,17 @@ static void c_emit_addr_deref(CTarget* t, Operand addr, CfreeCgTypeId access_typ cbuf_puts(&t->body, buf); return; case OPK_GLOBAL: { - /* Phase 2 defers data emission to Phase 4. */ - (void)access_type; - compiler_panic(t->c, loc, - "C target: OPK_GLOBAL data reference not yet supported"); + 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: { Operand base_reg; @@ -643,9 +677,18 @@ static void c_emit_lvalue_addr(CTarget* t, Operand lv, CfreeCgTypeId dst_type) { cbuf_puts(&t->body, ")"); return; case OPK_GLOBAL: { - (void)dst_type; - compiler_panic(t->c, loc, - "C target: OPK_GLOBAL data reference not yet supported"); + 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, "(("); @@ -675,11 +718,55 @@ const char* c_sym_name(CTarget* t, ObjSymId sym) { compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: unknown ObjSymId %u", (unsigned)sym); } - const char* s = pool_str(t->c->global, os->name, NULL); + size_t n = 0; + const char* s = pool_str(t->c->global, os->name, &n); /* 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 && s[0] == '_') s += 1; - return s; + 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(t->c->global, buf, out); + return pool_str(t->c->global, interned, NULL); } /* === Prologue / finalize === */ @@ -1471,8 +1558,11 @@ void c_store(CGTarget* T, Operand addr, Operand src, MemAccess m) { CfreeCgTypeId access_ty = m.type ? m.type : src.type; cbuf_puts(&t->body, " "); c_emit_addr_deref(t, addr, access_ty); + /* CG reuses reg ids across types; the src reg's C declaration may differ + * from access_ty. c_emit_operand_as bridges through uintptr_t so int/ptr + * roundtrips don't trip `-Wint-conversion`. */ cbuf_puts(&t->body, " = "); - c_emit_operand(t, src); + c_emit_operand_as(t, src, access_ty); cbuf_puts(&t->body, ";\n"); } @@ -1894,6 +1984,489 @@ void c_set_bytes(CGTarget* T, Operand dst_addr, Operand byte_value, 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_REG) { + compiler_panic(t->c, loc, "C target: tls_addr_of dst must be REG"); + } + c_ensure_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + const char* nm = c_sym_name(t, sym); + c_emit_reg_assign_open(t, dst.v.reg); + 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_reg_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 `*(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_operand(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_REG) { + compiler_panic(t->c, loc, "C target: bitfield_load dst must be REG"); + } + if (bf.bit_width == 0) { + /* Zero-width — layout barrier only; nothing to load. Emit a no-op + * assignment so the dst reg still gets a defined value. */ + c_ensure_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + c_emit_reg_assign_open(t, dst.v.reg); + cbuf_puts(&t->body, "0"); + c_emit_reg_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_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + c_emit_reg_assign_open(t, dst.v.reg); + /* 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_reg_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}; + 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; + 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_str(t->c->global, outs[i].name, NULL)); + 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_REG → the C reg is a plain local; this + * works directly. OPK_LOCAL / OPK_INDIRECT also produce lvalues. */ + if (oo[i].kind == OPK_REG) { + c_ensure_reg(t, oo[i].v.reg, oo[i].type, (RegClass)oo[i].cls); + char rb[24]; + c_reg_name(oo[i].v.reg, 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_str(t->c->global, ins[i].name, NULL)); + 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_str(t->c->global, clobs[i], NULL)); + } + 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 + * reg 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_REG) { + compiler_panic(t->c, loc, "C target: load_const dst must be REG"); + } + c_ensure_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + char buf[24]; + c_reg_name(dst.v.reg, 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_ensure_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + c_emit_reg_assign_open(t, dst.v.reg); + 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_reg_assign_close(t); +} + +void c_atomic_store(CGTarget* T, Operand addr, Operand src, MemAccess m, + MemOrder o) { + CTarget* t = (CTarget*)T; + (void)m; + 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}; + 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_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); + c_emit_reg_assign_open(t, dst.v.reg); + 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_reg_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; + /* gcc's __atomic_compare_exchange_n needs a real lvalue holding the + * expected value (it's updated on failure). We can't use `&prior_reg` + * directly because CG reuses reg ids across types — the C declaration may + * have been locked at a different type. Materialize a fresh scratch local + * typed by `prior.type`, then copy out to the prior reg using the existing + * uintptr_t bridge. */ + c_ensure_reg(t, prior.v.reg, prior.type, (RegClass)prior.cls); + c_ensure_reg(t, ok.v.reg, ok.type, (RegClass)ok.cls); + 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_reg_name(ok.v.reg, ok_name, sizeof ok_name); + c_reg_name(prior.v.reg, 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 reg = (DECL)(uintptr_t)__cas; */ + cbuf_puts(&t->body, prior_name); + cbuf_puts(&t->body, " = ("); + CfreeCgTypeId decl = t->reg_type[prior.v.reg]; + c_emit_type(t, &t->body, decl); + cbuf_puts(&t->body, ")"); + if (!c_type_is_float(t, decl)) cbuf_puts(&t->body, "(uintptr_t)"); + 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 === */ void c_set_loc(CGTarget* T, SrcLoc l) { @@ -1901,6 +2474,238 @@ void c_set_loc(CGTarget* T, SrcLoc l) { (void)l; } +/* === 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 }"); +} + +/* 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; +} + +static void c_emit_sym_relocs_fixup(CTarget* t, const char* nm, + ObjSecId sec_id, u32 base, u32 size) { + u32 total = obj_reloc_total(t->obj); + int first = 1; + 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) continue; + if (first) { + cbuf_puts(&t->data_defs, + "__attribute__((constructor)) static void __cfree_init_"); + cbuf_puts(&t->data_defs, nm); + cbuf_puts(&t->data_defs, "(void) {\n"); + first = 0; + } + /* api_data_reloc_kind only emits R_ABS{32,64} and R_PC{32,64}; pcrel + * has no constructor-time spelling (see doc/CBACKEND.md §pcrel). */ + u32 width = 0; + switch ((int)r->kind) { + case R_ABS32: width = 4; break; + case R_ABS64: width = 8; break; + case R_PC32: + case R_PC64: + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: pcrel data reloc not yet supported"); + default: + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: data reloc kind %d not yet supported", + (int)r->kind); + } + const char* tgt = c_sym_name(t, r->sym); + const char* ity = (width == 8) ? "uint64_t" : + (width == 4) ? "uint32_t" : + (width == 2) ? "uint16_t" : "uint8_t"; + cbuf_puts(&t->data_defs, " *("); + cbuf_puts(&t->data_defs, ity); + cbuf_puts(&t->data_defs, "*)((char*)"); + cbuf_puts(&t->data_defs, nm); + cbuf_puts(&t->data_defs, " + "); + cbuf_put_u64(&t->data_defs, (u64)(r->offset - base)); + cbuf_puts(&t->data_defs, ") = ("); + cbuf_puts(&t->data_defs, ity); + cbuf_puts(&t->data_defs, ")(uintptr_t)((char*)"); + cbuf_puts(&t->data_defs, tgt); + if (r->addend != 0) { + cbuf_puts(&t->data_defs, " + "); + cbuf_put_i64(&t->data_defs, r->addend); + } + cbuf_puts(&t->data_defs, ");\n"); + } + if (!first) cbuf_puts(&t->data_defs, "}\n"); +} + +/* 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 (os->kind == SK_FUNC || os->kind == SK_IFUNC) return; + if (os->kind == SK_SECTION || os->kind == SK_FILE) 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. */ + if (os->kind == SK_UNDEF || + (is_tls && os->section_id == OBJ_SEC_NONE)) { + if (!os->referenced) return; + 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 TLV: the user sym is the descriptor (24 bytes, with relocs to + * __tlv_bootstrap and the synthesized __init.<name> sym). We can't + * faithfully express the descriptor in portable C — `_Thread_local` + * needs the underlying initial value, which lives in a different + * (synthesized) sym. Phase 4 leaves this for a future pass. */ + compiler_panic(t->c, (SrcLoc){0, 0, 0}, + "C target: Mach-O TLS data definition not yet supported"); + } + /* Skip the synthesized `__init.<name>` aux sym on Mach-O — even if we got + * here, it shouldn't be emitted as user-visible data. (Currently + * unreachable because the descriptor path panicked above.) */ + 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, "_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; + /* Storage class: SB_LOCAL → `static`, SB_GLOBAL/WEAK → file scope. */ + if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); + if (is_tls) cbuf_puts(b, "_Thread_local "); + c_emit_link_attrs(b, os); + /* `const` only when the section is RODATA and no relocs patch it (we + * still drop const if there are relocs because the constructor will + * write through this storage). */ + int has_relocs = c_sym_has_relocs(t, os->section_id, base, size); + int is_ro = (sec->kind == SEC_RODATA); + if (is_ro && !has_relocs) cbuf_puts(b, "const "); + cbuf_puts(b, "_Alignas("); + cbuf_put_u64(b, sec->align ? sec->align : 1); + cbuf_puts(b, ") uint8_t "); + cbuf_puts(b, nm); + cbuf_puts(b, "["); + cbuf_put_u64(b, size ? size : 1); + cbuf_puts(b, "]"); + if (sec->kind == SEC_BSS || sec->sem == SSEM_NOBITS) { + /* BSS — no initializer (defaults to zero in C). */ + cbuf_puts(b, ";\n"); + } else if (size == 0) { + cbuf_puts(b, ";\n"); + } else { + Heap* h = t->c->ctx->heap; + u8* bytes = (u8*)h->alloc(h, size, 1); + if (!bytes) { + compiler_panic(t->c, (SrcLoc){0, 0, 0}, "C target: oom on data bytes"); + } + c_read_section_bytes(sec, base, bytes, size); + c_emit_data_bytes(b, bytes, size); + h->free(h, bytes, size); + cbuf_puts(b, ";\n"); + } + if (has_relocs) c_emit_sym_relocs_fixup(t, nm, os->section_id, base, size); +} + +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) { @@ -1919,6 +2724,11 @@ void c_finalize(CGTarget* T) { c_writer_write(t, t->forwards.data, t->forwards.len); c_writer_puts(t, "\n"); } + c_emit_data(t); + 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); } @@ -1926,6 +2736,8 @@ 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); diff --git a/src/arch/c_target/internal.h b/src/arch/c_target/internal.h @@ -58,6 +58,11 @@ typedef struct CTarget { /* 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; diff --git a/src/arch/c_target/target.c b/src/arch/c_target/target.c @@ -52,9 +52,23 @@ 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); - -/* Unimplemented stubs panic specifically per the doc's Phase 0 acceptance - * criterion. The method name is encoded in the panic message. */ +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); + +/* Unimplemented stubs panic with the method name in the message — the toy + * harness recognizes "C target: method <name> not implemented" as a graceful + * SKIP rather than a hard FAIL. */ #define C_UNIMPL(name) \ compiler_panic(((CTarget*)t)->c, ((CTarget*)t)->cur_fn ? \ ((CTarget*)t)->cur_fn->loc : (SrcLoc){0,0,0}, \ @@ -123,26 +137,6 @@ static u32 c_call_stack_size_zero(CGTarget* t, const CGCallDesc* d) { return 0; } -static void c_unimpl_load_const(CGTarget* t, Operand dst, ConstBytes cb) { - (void)dst; (void)cb; - C_UNIMPL("load_const"); -} -static void c_unimpl_tls_addr_of(CGTarget* t, Operand dst, ObjSymId sym, - i64 addend) { - (void)dst; (void)sym; (void)addend; - C_UNIMPL("tls_addr_of"); -} -static void c_unimpl_bitfield_load(CGTarget* t, Operand dst, Operand addr, - BitFieldAccess bf) { - (void)dst; (void)addr; (void)bf; - C_UNIMPL("bitfield_load"); -} -static void c_unimpl_bitfield_store(CGTarget* t, Operand addr, Operand src, - BitFieldAccess bf) { - (void)addr; (void)src; (void)bf; - C_UNIMPL("bitfield_store"); -} - static void c_unimpl_plan_call(CGTarget* t, const CGCallDesc* d, CGCallPlan* p) { (void)d; (void)p; C_UNIMPL("plan_call"); @@ -166,42 +160,6 @@ static void c_unimpl_emit_call_plan(CGTarget* t, const CGCallPlan* p) { C_UNIMPL("emit_call_plan"); } -static void c_unimpl_atomic_load(CGTarget* t, Operand d, Operand a, MemAccess m, - MemOrder o) { - (void)d; (void)a; (void)m; (void)o; - C_UNIMPL("atomic_load"); -} -static void c_unimpl_atomic_store(CGTarget* t, Operand a, Operand s, MemAccess m, - MemOrder o) { - (void)a; (void)s; (void)m; (void)o; - C_UNIMPL("atomic_store"); -} -static void c_unimpl_atomic_rmw(CGTarget* t, AtomicOp op, Operand d, Operand a, - Operand v, MemAccess m, MemOrder o) { - (void)op; (void)d; (void)a; (void)v; (void)m; (void)o; - C_UNIMPL("atomic_rmw"); -} -static void c_unimpl_atomic_cas(CGTarget* t, Operand p, Operand ok, Operand a, - Operand e, Operand de, MemAccess m, MemOrder so, - MemOrder fo) { - (void)p; (void)ok; (void)a; (void)e; (void)de; (void)m; (void)so; (void)fo; - C_UNIMPL("atomic_cas"); -} -static void c_unimpl_fence(CGTarget* t, MemOrder o) { - (void)o; - C_UNIMPL("fence"); -} - -static void c_unimpl_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) { - (void)tmpl; (void)outs; (void)no; (void)oo; - (void)ins; (void)ni; (void)io; - (void)clobs; (void)nc; - C_UNIMPL("asm_block"); -} - static void cgt_cleanup(void* arg) { cgtarget_free((CGTarget*)arg); } CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { @@ -213,6 +171,7 @@ CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { 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); @@ -261,16 +220,16 @@ CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { /* ---- data movement ---- */ t->load_imm = c_load_imm; - t->load_const = c_unimpl_load_const; + 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_unimpl_tls_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_unimpl_bitfield_load; - t->bitfield_store = c_unimpl_bitfield_store; + t->bitfield_load = c_bitfield_load; + t->bitfield_store = c_bitfield_store; /* ---- arithmetic, compare, convert ---- */ t->binop = c_binop; @@ -295,15 +254,15 @@ CGTarget* c_cgtarget_new(Compiler* c, ObjBuilder* o, CfreeWriter* w) { t->va_copy_ = c_va_copy; /* ---- atomics ---- */ - t->atomic_load = c_unimpl_atomic_load; - t->atomic_store = c_unimpl_atomic_store; - t->atomic_rmw = c_unimpl_atomic_rmw; - t->atomic_cas = c_unimpl_atomic_cas; - t->fence = c_unimpl_fence; + 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; /* ---- intrinsics / asm ---- */ t->intrinsic = c_intrinsic; - t->asm_block = c_unimpl_asm_block; + t->asm_block = c_asm_block; t->resolve_reg_name = NULL; t->set_loc = c_set_loc;