kit

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

commit 24d477f4ce2d0de89590909ce1fdbfb2c84f0ffa
parent f7ac167dc65d43ed7b189508db8a2f6a4a82f14a
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 18 May 2026 15:39:38 -0700

Fix macro hideset recursion in fp runtime probe

Diffstat:
Mdoc/RT_CFREERT_CHECKLIST.md | 14++++++++++----
Mlang/c/parse/cg_adapter.c | 2+-
Mlang/c/parse/parse_expr.c | 32++++++++++++++++++++++++++++++--
Mlang/c/pp/pp_directive.c | 6+++---
Mlang/c/pp/pp_expand.c | 21++++++++++++++-------
Mlang/c/pp/pp_priv.h | 26+++++++++++++++++++++++++-
Msrc/api/cg.c | 2++
Mtest/parse/CORPUS.md | 3+++
Atest/parse/cases/6_5_46b_cmp_to_bool_init.c | 4++++
Atest/parse/cases/6_5_46b_cmp_to_bool_init.expected | 1+
Atest/parse/cases/6_6_10_logical_cond_const.c | 6++++++
Atest/parse/cases/6_6_10_logical_cond_const.expected | 1+
Atest/parse/cases/builtin_26_sadd_overflow.c | 7+++++++
Atest/parse/cases/builtin_26_sadd_overflow.expected | 1+
Mtest/pp/CORPUS.md | 1+
Atest/pp/cases/65_rescan_arg_hideset_prescan.c | 5+++++
Atest/pp/cases/65_rescan_arg_hideset_prescan.expected | 1+
17 files changed, 115 insertions(+), 18 deletions(-)

diff --git a/doc/RT_CFREERT_CHECKLIST.md b/doc/RT_CFREERT_CHECKLIST.md @@ -76,13 +76,19 @@ does not build yet because `rt/lib/fp/fp.c` still fails. rewriting the token to an undeclared plain identifier. - [x] Added parser support for `__atomic_fetch_nand`, mapped through the existing target-independent atomic NAND RMW operation. +- [x] Fixed the `rt/lib/fp/fp.c` preprocessor crash. Function-like macro + argument prescan now preserves raw-token hidesets, so self-referential + suffix-renaming macros such as `rep_t -> _FP_NAME(rep_t)` do not recurse + until stack exhaustion. ## Remaining Blockers -- [ ] Fix the `rt/lib/fp/fp.c` compiler crash. - Current symptom: cfree segfaults while compiling this file; lldb stops in - `cfree_arena_alloc`. Reduce to the smallest function or included helper that - reproduces the stack growth / allocator crash. +- [ ] Finish `rt/lib/fp/fp.c`. + Current symptom after the preprocessor crash fix: + `rt/lib/include/common/fp_lib.h:338:7: fatal: undeclared identifier + '__builtin_sadd_overflow'`. The crash reduced to a self-referential macro + argument-prescan case and is covered by + `test/pp/cases/65_rescan_arg_hideset_prescan.c`. - [ ] Lift remaining coroutine file-scope assembly. Current file-scope asm remains in: diff --git a/lang/c/parse/cg_adapter.c b/lang/c/parse/cg_adapter.c @@ -383,7 +383,7 @@ void pcg_cmp(Parser* p, CmpOp op) { if (pcg_emit_enabled(p)) cfree_cg_int_cmp(p->cg, pcg_int_cmp(op)); } pcg_drop_type(p); - pcg_retag_top(p, type_prim(p->pool, TY_BOOL)); + pcg_retag_top(p, type_prim(p->pool, TY_INT)); } void pcg_convert(Parser* p, const Type* dst) { diff --git a/lang/c/parse/parse_expr.c b/lang/c/parse/parse_expr.c @@ -389,6 +389,7 @@ CfreeCgSym emit_string_to_rodata(Parser* p, const u8* bytes, size_t n) { * ============================================================ */ static i64 cexpr_unary(Parser* p, SrcLoc loc); +static i64 cexpr_cond(Parser* p, SrcLoc loc); static const Type* offsetof_designator(Parser* p, const Type* base, u32* off); static i64 cexpr_mul(Parser* p, SrcLoc loc) { @@ -484,6 +485,33 @@ static i64 cexpr_bor(Parser* p, SrcLoc loc) { } return v; } +static i64 cexpr_land(Parser* p, SrcLoc loc) { + i64 v = cexpr_bor(p, loc); + while (accept_punct(p, P_AND)) { + i64 r = cexpr_bor(p, loc); + v = (v != 0 && r != 0) ? 1 : 0; + } + return v; +} +static i64 cexpr_lor(Parser* p, SrcLoc loc) { + i64 v = cexpr_land(p, loc); + while (accept_punct(p, P_OR)) { + i64 r = cexpr_land(p, loc); + v = (v != 0 || r != 0) ? 1 : 0; + } + return v; +} +static i64 cexpr_cond(Parser* p, SrcLoc loc) { + i64 v = cexpr_lor(p, loc); + if (accept_punct(p, '?')) { + i64 then_v = cexpr_cond(p, loc); + i64 else_v; + expect_punct(p, ':', "':' in constant conditional expression"); + else_v = cexpr_cond(p, loc); + v = v ? then_v : else_v; + } + return v; +} static i64 cexpr_unary(Parser* p, SrcLoc loc) { if (accept_punct(p, '+')) return cexpr_unary(p, loc); @@ -555,7 +583,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { } } { - i64 v = cexpr_bor(p, loc); + i64 v = cexpr_cond(p, loc); expect_punct(p, ')', "')' in constant expression"); return v; } @@ -595,7 +623,7 @@ static i64 cexpr_unary(Parser* p, SrcLoc loc) { compiler_panic(p->c, loc, "expected constant expression"); } -i64 eval_const_int(Parser* p, SrcLoc loc) { return cexpr_bor(p, loc); } +i64 eval_const_int(Parser* p, SrcLoc loc) { return cexpr_cond(p, loc); } /* ============================================================ * to_rvalue diff --git a/lang/c/pp/pp_directive.c b/lang/c/pp/pp_directive.c @@ -138,7 +138,7 @@ static void expand_for_if(Pp* pp, const Tok* in, u32 nin, TokVec* out) { if (nin == 0) return; slice = arena_array(pp->arena, Tok, nin); memcpy(slice, in, sizeof(Tok) * nin); - expand_arg_to_eof(pp, slice, nin, out); + expand_arg_to_eof(pp, slice, NULL, nin, out); /* Replace remaining identifiers with `0`. */ { u32 i; @@ -728,7 +728,7 @@ static void parse_include_path(Pp* pp, const Tok* line, u32 n, SrcLoc loc, TokVec exp = {0}; Tok* slice = arena_array(pp->arena, Tok, n); memcpy(slice, line, sizeof(Tok) * n); - expand_arg_to_eof(pp, slice, n, &exp); + expand_arg_to_eof(pp, slice, NULL, n, &exp); if (exp.n == 0) { compiler_panic(pp->c, loc, "#include: empty after macro replacement"); @@ -838,7 +838,7 @@ static void do_line(Pp* pp, const Tok* line, u32 n, SrcLoc loc) { if (n == 0) compiler_panic(pp->c, loc, "#line: missing arguments"); slice = arena_array(pp->arena, Tok, n); memcpy(slice, line, sizeof(Tok) * n); - expand_arg_to_eof(pp, slice, n, &exp); + expand_arg_to_eof(pp, slice, NULL, n, &exp); if (exp.n == 0 || exp.data[0].kind != TOK_NUM) { compiler_panic(pp->c, loc, "#line: expected line number"); diff --git a/lang/c/pp/pp_expand.c b/lang/c/pp/pp_expand.c @@ -391,6 +391,7 @@ static void expand_object_macro(Pp* pp, const Macro* m, const Tok* invoke, * are restored as a buffer source so subsequent reads still see them. */ int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out) { TokVec saved = {0}; + HsVec saved_hs = {0}; int saw_ws = 0; Tok t; HidesetId hs; @@ -400,11 +401,12 @@ int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out) { if (t.kind == TOK_NEWLINE) { saw_ws = 1; tv_push(pp, &saved, t); + hsv_push(pp, &saved_hs, hs); continue; } if (t.kind == TOK_EOF) { /* No '(' — push back saved tokens, leave EOF for next read. */ - if (saved.n) push_buf(pp, saved.data, NULL, saved.n); + if (saved.n) push_buf(pp, saved.data, saved_hs.data, saved.n); *ws_has_space_out = saw_ws; return 0; } @@ -417,7 +419,8 @@ int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out) { } /* Save this non-`(` token too and push back. */ tv_push(pp, &saved, t); - push_buf(pp, saved.data, NULL, saved.n); + hsv_push(pp, &saved_hs, hs); + push_buf(pp, saved.data, saved_hs.data, saved.n); *ws_has_space_out = saw_ws; return 0; } @@ -426,7 +429,7 @@ int peek_for_invoke_paren(Pp* pp, int* ws_has_space_out) { /* Run macro expansion on a fixed token sequence to completion, yielding the * fully-expanded token sequence. Used to pre-expand each function-macro * argument before substitution (§6.10.3.1 ¶1). */ -void expand_arg_to_eof(Pp* pp, Tok* in, u32 nin, TokVec* out) { +void expand_arg_to_eof(Pp* pp, Tok* in, HidesetId* hs, u32 nin, TokVec* out) { TokSrc src; Tok t; @@ -434,7 +437,7 @@ void expand_arg_to_eof(Pp* pp, Tok* in, u32 nin, TokVec* out) { src.kind = SRC_BUF; src.scope_top = 1; src.toks = in; - src.hs = NULL; + src.hs = hs; src.n = nin; src_push(pp, src); @@ -458,6 +461,7 @@ void expand_arg_to_eof(Pp* pp, Tok* in, u32 nin, TokVec* out) { typedef struct ArgList { /* Unexpanded arg tokens (raw as collected from invocation). */ Tok* raw; + HidesetId* raw_hs; u32 raw_n; u32* raw_start; /* size n_args + 1 (sentinel = raw_n) */ /* Pre-expanded tokens. */ @@ -472,6 +476,7 @@ typedef struct ArgList { static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc, ArgList* out) { TokVec raw = {0}; + HsVec raw_hs = {0}; u32* starts; u32 starts_cap = 0; u32 n_args = 0; @@ -552,6 +557,7 @@ static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc, if (m->is_variadic && n_args + 1 >= m->n_params) { /* This comma is part of __VA_ARGS__. Push it. */ tv_push(pp, &raw, t); + hsv_push(pp, &raw_hs, hs); first_token_of_arg = 0; continue; } @@ -571,9 +577,8 @@ static Tok read_invocation_args(Pp* pp, const Macro* m, SrcLoc invoke_loc, } } tv_push(pp, &raw, t); + hsv_push(pp, &raw_hs, hs); first_token_of_arg = 0; - (void)hs; /* hideset of raw arg tokens carried for blue-paint - * propagation in the arg's pre-expansion */ } done: /* Validate arity. */ @@ -612,6 +617,7 @@ done: } } out->raw = raw.data; + out->raw_hs = raw_hs.data; out->raw_n = raw.n; out->raw_start = starts; out->n_args = n_args; @@ -633,7 +639,8 @@ static void preexpand_args(Pp* pp, ArgList* a) { * own it without aliasing. */ Tok* slice = arena_array(pp->arena, Tok, hi - lo); memcpy(slice, &a->raw[lo], sizeof(Tok) * (hi - lo)); - expand_arg_to_eof(pp, slice, hi - lo, &exp); + expand_arg_to_eof(pp, slice, a->raw_hs ? &a->raw_hs[lo] : NULL, + hi - lo, &exp); } exp_start[i + 1] = exp.n; } diff --git a/lang/c/pp/pp_priv.h b/lang/c/pp/pp_priv.h @@ -186,6 +186,12 @@ typedef struct TokVec { u32 cap; } TokVec; +typedef struct HsVec { + HidesetId* data; + u32 n; + u32 cap; +} HsVec; + static inline void tv_grow(Pp* pp, TokVec* v, u32 want) { u32 nc; if (v->cap >= want) return; @@ -204,6 +210,24 @@ static inline void tv_push(Pp* pp, TokVec* v, Tok t) { v->data[v->n++] = t; } +static inline void hsv_grow(Pp* pp, HsVec* v, u32 want) { + u32 nc; + if (v->cap >= want) return; + nc = v->cap ? v->cap * 2 : 8; + while (nc < want) nc *= 2; + { + HidesetId* nb = arena_array(pp->arena, HidesetId, nc); + if (v->n) memcpy(nb, v->data, sizeof(HidesetId) * v->n); + v->data = nb; + v->cap = nc; + } +} + +static inline void hsv_push(Pp* pp, HsVec* v, HidesetId hs) { + hsv_grow(pp, v, v->n + 1); + v->data[v->n++] = hs; +} + /* Growable char buffer (arena-backed). */ typedef struct CharBuf { char* data; @@ -251,7 +275,7 @@ int hs_contains(Pp* pp, HidesetId id, Sym s); Macro* mt_get(Pp* pp, Sym name); void mt_put(Pp* pp, Sym name, Macro* m); void mt_del(Pp* pp, Sym name); -void expand_arg_to_eof(Pp* pp, Tok* in, u32 nin, TokVec* out); +void expand_arg_to_eof(Pp* pp, Tok* in, HidesetId* hs, u32 nin, TokVec* out); /* --- pp_directive.c → pp_expand.c --- */ i64 eval_if_expr(Pp* pp, const Tok* line, u32 n, SrcLoc loc); diff --git a/src/api/cg.c b/src/api/cg.c @@ -3560,6 +3560,8 @@ void cfree_cg_addr(CfreeCg *g) { return; v = api_pop(g); pty = cg_type_ptr_to(g->c, api_sv_type(&v)); + if (v.source_local != CFREE_CG_LOCAL_NONE) + api_local_const_address_taken(g, v.source_local); dst = api_lvalue_addr(g, &v, pty); api_release(g, &v); api_push(g, api_make_sv(dst, pty)); diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -172,6 +172,7 @@ here for completeness once they're real cases. | `6_5_44_relational_all` | ★ | `(3>1)+(1<3)+(2<=2)+(2>=2)+(1!=2)+(2==2)` — covers `>`, `<=`, `>=`, `!=` | 6 | | `6_5_45_ptr_eq_null` | ★ | `(p==0)+(q!=0)+(p!=q)` — pointer equality with null and pointer-vs-pointer `!=` | 3 | | `6_5_46_ptr_relcmp` | ★ | `(p<q)+(q>p)+(p<=p)` — relational comparison of pointers into same array | 3 | +| `6_5_46b_cmp_to_bool_init` | ★ | `_Bool b = (1 != 0);` — comparison result is assignable through `_Bool` conversion | 42 | | `6_5_47_ternary_arith_conv` | ★ | `int c=1; double r = c ? 1 : 2.5; return (int)(r+41.0);` — ternary arms get usual arithmetic conversion to double | 42 | | `6_5_48_ternary_ptr_null` | ★ | `int *p = 1 ? &x : 0; return *p;` — one arm pointer, other null pointer constant | 42 | | `6_5_49_compound_assign_sub` | ★ | `int x=44; x-=2; return x;` — `-=` operator | 42 | @@ -213,6 +214,7 @@ copy (BYVAL). Each row exercises one cell of the (1-reg / 2-reg / copy) | `6_6_07_enum_array_bound` | ★ | file scope: `typedef enum {A,B,C,N} E; static const int marks[N]={10,12,20};` — enum constant as array bound | 42 | | `6_6_08_func_addr_static_init` | ★ | `static int helper(int x){return x;}` + `static const FN g = helper;` — function designator as static-init address constant (no `&`) | 42 | | `6_6_09_func_addr_array_static` | ★ | `static const FN ops[2] = {add1, dbl};` — array of function-pointer static-init constants | 42 | +| `6_6_10_logical_cond_const` | ★ | logical `&&` / `||` and `?:` in integer constant expressions | 42 | ## §6.7 Declarations @@ -499,6 +501,7 @@ ordinary calls. | `builtin_23_atomic_long_literal_convert` | ★ | store/RMW integer literals converted to unsigned long atomic object type | 42 | | `builtin_24_atomic_lock_free` | ★ | target-aware lock-free folding through `if`, `&&`, and `||`; dead 16-byte atomic arms suppress codegen | 42 | | `builtin_25_atomic_fetch_nand` | ★ | `__atomic_fetch_nand` lowers to atomic NAND RMW | 42 | +| `builtin_26_sadd_overflow` | RED | `__builtin_sadd_overflow` stores result and returns overflow flag; currently undeclared | 42 | | `builtin_99_syscall0` | (deferred) | `__cfree_syscall0` requires linking against the syscall stub; covered in `test/libc` | — | ## Variadic coverage diff --git a/test/parse/cases/6_5_46b_cmp_to_bool_init.c b/test/parse/cases/6_5_46b_cmp_to_bool_init.c @@ -0,0 +1,4 @@ +int test_main(void) { + _Bool b = (1 != 0); + return b ? 42 : 0; +} diff --git a/test/parse/cases/6_5_46b_cmp_to_bool_init.expected b/test/parse/cases/6_5_46b_cmp_to_bool_init.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/6_6_10_logical_cond_const.c b/test/parse/cases/6_6_10_logical_cond_const.c @@ -0,0 +1,6 @@ +_Static_assert(0 || (1 && 1), "logical operators are integer constants"); + +int test_main(void) { + int a[0 ? 1 : 7]; + return (int)(sizeof a / sizeof a[0]) * (1 ? 6 : 0); +} diff --git a/test/parse/cases/6_6_10_logical_cond_const.expected b/test/parse/cases/6_6_10_logical_cond_const.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_26_sadd_overflow.c b/test/parse/cases/builtin_26_sadd_overflow.c @@ -0,0 +1,7 @@ +int test_main(void) { + int r = 0; + _Bool ov = __builtin_sadd_overflow(40, 2, &r); + if (ov || r != 42) return 1; + ov = __builtin_sadd_overflow(2147483647, 1, &r); + return ov ? 42 : 2; +} diff --git a/test/parse/cases/builtin_26_sadd_overflow.expected b/test/parse/cases/builtin_26_sadd_overflow.expected @@ -0,0 +1 @@ +42 diff --git a/test/pp/CORPUS.md b/test/pp/CORPUS.md @@ -103,6 +103,7 @@ Both runners pin the values of the otherwise-environmental predefined macros: | 62_rescan_paste_to_macro | 6.10.3.3 ¶3 | post-paste token is available for further replacement | | 63_rescan_not_directive | 6.10.3.4 ¶3 | tokens produced by expansion are not a directive | | 64_rescan_self_in_func | 6.10.3.4 ¶2 | function-like macro does not re-invoke itself in rescan | +| 65_rescan_arg_hideset_prescan | 6.10.3.4 ¶2 | argument prescan keeps no-reexpand state from caller | ### 70–79 Variadic macros (§6.10.3 ¶12, §6.10.3.1 ¶2) diff --git a/test/pp/cases/65_rescan_arg_hideset_prescan.c b/test/pp/cases/65_rescan_arg_hideset_prescan.c @@ -0,0 +1,5 @@ +#define CAT1(a, b) a##b +#define CAT(a, b) CAT1(a, b) +#define SUF x +#define T CAT(T, SUF) +T diff --git a/test/pp/cases/65_rescan_arg_hideset_prescan.expected b/test/pp/cases/65_rescan_arg_hideset_prescan.expected @@ -0,0 +1 @@ +Tx