kit

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

commit 88093f7a2ed4025863be2de9409e7f42934cedee
parent 0c5222c1d9651c72759c16bd0e19caa8806a50c0
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Wed, 20 May 2026 13:15:09 -0700

c_target: emit C that compiles under -Wall -Wextra -Werror

Six warning classes the host cc flagged on emitted source:
- unused-but-set-variable / unused-variable: vN and slot_N decls now
  get __attribute__((unused)) = {0}. The zero-init also silences
  -Wsometimes-uninitialized on paths clang can't reason through.
- unused-label: label placement adds __attribute__((unused)).
- unused-const-variable: static data definitions get
  __attribute__((unused)).
- implicitly-unsigned-literal: INT64_MIN is now spelled
  (-9223372036854775807LL - 1) via a c_emit_imm_literal helper.
- void-pointer-to-int-cast: CV_TRUNC and CV_SEXT/ZEXT through a
  pointer-typed reg (CG reuses reg ids across types) now bridge
  through (uintptr_t).

Tighten the host-cc compile in test/parse/run.sh and test/toy/run.sh
to -Wall -Wextra -Werror so any regression in emitter quality fails
the suite. 432/432 parse cases and 124/124 toy cases that emit
successfully now pass; the 31 + 3 SKIPs are pre-existing
phased-rollout gaps (i128/ldbl128/Mach-O TLS).

Diffstat:
Msrc/arch/c_target/emit.c | 53+++++++++++++++++++++++++++++++++++++++++++++--------
Mtest/parse/run.sh | 2+-
Mtest/toy/run.sh | 7++++---
3 files changed, 50 insertions(+), 12 deletions(-)

diff --git a/src/arch/c_target/emit.c b/src/arch/c_target/emit.c @@ -29,6 +29,7 @@ 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 int c_operand_is_ptr_typed(CTarget* t, Operand op); /* === Writer helpers === */ @@ -385,7 +386,23 @@ void c_ensure_reg(CTarget* t, Reg r, CfreeCgTypeId type, RegClass cls) { char buf[24]; c_reg_name(r, buf, sizeof buf); cbuf_puts(&t->decls, buf); - cbuf_puts(&t->decls, ";\n"); + /* `= {0}` zeroes scalars and aggregates uniformly. Kills + * -Wsometimes-uninitialized for control flow clang can't reason through; + * the host C compiler DSEs the init when a real assignment dominates. */ + cbuf_puts(&t->decls, " __attribute__((unused)) = {0};\n"); +} + +/* 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); } void c_emit_operand(CTarget* t, Operand op) { @@ -400,13 +417,13 @@ void c_emit_operand(CTarget* t, Operand op) { if (op.type == CFREE_CG_TYPE_NONE) { /* Untyped IMM (e.g. memset byte value): emit the literal raw. */ cbuf_putc(&t->body, '('); - cbuf_put_i64(&t->body, op.v.imm); + 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, ")"); - cbuf_put_i64(&t->body, op.v.imm); + c_emit_imm_literal(t, op.v.imm); cbuf_puts(&t->body, ")"); } return; @@ -551,9 +568,16 @@ void c_emit_operand_signed(CTarget* t, Operand op, int signed_) { c_emit_operand(t, op); return; } + /* If the operand's C declaration is pointer-typed (reg-id reuse across + * types), bridge through uintptr_t so the narrow int cast doesn't trip + * -Wvoid-pointer-to-int-cast. */ + int via_uptr = c_operand_is_ptr_typed(t, op); 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, ")"); } @@ -951,7 +975,8 @@ FrameSlot c_frame_slot(CGTarget* T, const FrameSlotDesc* fsd) { char buf[24]; c_slot_name(id, buf, sizeof buf); cbuf_puts(&t->decls, buf); - cbuf_puts(&t->decls, ";\n"); + /* See c_ensure_reg — same `= {0}` reasoning. */ + cbuf_puts(&t->decls, " __attribute__((unused)) = {0};\n"); return id; } @@ -994,7 +1019,7 @@ void c_load_imm(CGTarget* T, Operand dst, i64 imm) { } c_ensure_reg(t, dst.v.reg, dst.type, (RegClass)dst.cls); c_emit_reg_assign_open(t, dst.v.reg); - cbuf_put_i64(&t->body, imm); + c_emit_imm_literal(t, imm); c_emit_reg_assign_close(t); } @@ -1252,10 +1277,11 @@ void c_label_place(CGTarget* T, Label l) { CTarget* t = (CTarget*)T; char buf[24]; c_label_name(l, buf, sizeof buf); - /* `Lk: ;` — the empty statement keeps it valid even at end-of-block. */ + /* `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, ": ;\n"); + cbuf_puts(&t->body, ": __attribute__((unused));\n"); } void c_jump(CGTarget* T, Label l) { @@ -1459,6 +1485,13 @@ void c_convert(CGTarget* T, ConvKind k, Operand dst, Operand src) { 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); @@ -2764,7 +2797,7 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) { /* 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_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); @@ -2782,6 +2815,10 @@ static void c_emit_data_symbol(CTarget* t, ObjSymId id, const ObjSym* os) { if (os->bind == SB_LOCAL) cbuf_puts(b, "static "); if (is_tls) cbuf_puts(b, "_Thread_local "); c_emit_link_attrs(b, os); + /* `__attribute__((unused))` so -Wunused-const-variable doesn't fire when + * the upstream use got folded away (CG can emit the data sym before the + * frontend decides to inline its load). */ + cbuf_puts(b, "__attribute__((unused)) "); /* `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). */ diff --git a/test/parse/run.sh b/test/parse/run.sh @@ -602,7 +602,7 @@ run_parse_case() { else emit_event "$event" FAIL "$name/C (parse-runner --emit-c failed; see $work/c.emit.err)" fi - elif ! $CC -std=c11 "$c_src" "$C_WRAPPER_OBJ" -o "$c_bin" \ + elif ! $CC -std=c11 -Wall -Wextra -Werror "$c_src" "$C_WRAPPER_OBJ" -o "$c_bin" \ >"$work/c.cc.out" 2>"$work/c.cc.err"; then dt=$(( $(now_ms) - t0 )) emit_event "$event" TIME C "$dt" diff --git a/test/toy/run.sh b/test/toy/run.sh @@ -8,8 +8,9 @@ # C cfree cc --emit=c case.toy -> host cc -> native exec. Exercises the # --emit=c C-source backend driven by a non-C frontend (validates that # the CGTarget seam is frontend-agnostic). Phased-rollout panics from -# the C target report as SKIP. Host cc runs without -Werror so the -# i64 toy main type doesn't trigger -Wmain-return-type as an error. +# the C target report as SKIP. Host cc runs under +# -Wall -Wextra -Werror with -Wno-main-return-type (toy's main returns +# i64, and `int64_t main` would otherwise fire -Wmain-return-type). # # Sidecars: # <name>.expected expected process exit code, default 0 @@ -338,7 +339,7 @@ run_case_emit_c() { # hard error ("'main' must return 'int'"). The emitted C only needs # the stdint typedefs, so we compile under -std=gnu99 + the relax # flag — gnu99's main-return-type check is a warning, not an error. - if ! $HOST_CC -std=gnu99 -Wno-main-return-type "$out_c" -o "$out_bin" \ + if ! $HOST_CC -std=gnu99 -Wall -Wextra -Werror -Wno-main-return-type "$out_c" -o "$out_bin" \ > "$work/c.cc.out" 2> "$cc_err"; then note_fail "$label" printf ' host cc rejected emitted source\n'