kit

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

commit af1dcc5f631b291e74a78f74af15d39bba6c87b5
parent cb33525798c6427becd139b2632ba02986406d1f
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Sun, 10 May 2026 11:53:03 -0700

parse: Phase 9 — __builtin_* / __atomic_* routing

try_parse_builtin_call dispatches the IDENT before scope_lookup so
__builtin_alloca / _expect / _offsetof / _va_* and __atomic_load_n /
_store_n / _exchange_n / _fetch_{add,sub,and,or,xor} reach cg_alloca,
cg_va_*, cg_atomic_* (or fold to a constant) instead of going through
cg_call. __builtin_va_list resolves through abi_va_list_type as a type
specifier; pp predefines __ATOMIC_RELAXED..SEQ_CST to align with the
MemOrder enum so eval_const_int casts directly. cg_va_* / cg_atomic_*
are still stubs, so the runtime corpus rows (builtin_03/05/06/07,
6_9_06, 6_7_6_08) carry .skip sidecars; alloca/expect/offsetof land ★.

Diffstat:
Mdoc/parser-status.md | 65+++++++++++++++++++++++++++++++++++++++++++++++++++++------------
Msrc/parse/parse.c | 287+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/pp/pp.c | 9+++++++++
Mtest/parse/CORPUS.md | 18+++++++++---------
Atest/parse/cases/6_7_6_08_variadic_decl.skip | 1+
Atest/parse/cases/6_9_06_variadic_func.skip | 1+
Atest/parse/cases/builtin_01_alloca.c | 5+++++
Atest/parse/cases/builtin_01_alloca.expected | 1+
Atest/parse/cases/builtin_02_expect.c | 4++++
Atest/parse/cases/builtin_02_expect.expected | 1+
Atest/parse/cases/builtin_03_va_list.skip | 1+
Atest/parse/cases/builtin_05_va_copy.skip | 1+
Atest/parse/cases/builtin_06_atomic_load.skip | 1+
Atest/parse/cases/builtin_07_atomic_fetch_add.skip | 1+
14 files changed, 375 insertions(+), 21 deletions(-)

diff --git a/doc/parser-status.md b/doc/parser-status.md @@ -396,18 +396,59 @@ diagnostic. --- -## Phase 9 — Builtins ⬜ - -Routes named `__builtin_*` calls to cg's intrinsic / asm machinery -rather than ordinary call lowering. Contract: `doc/builtins.md`. - -- [ ] `__builtin_alloca` -- [ ] `__builtin_expect` -- [ ] `__builtin_va_start` / `va_arg` / `va_end` / `va_copy` -- [ ] `__builtin_offsetof` -- [ ] `__atomic_load_n`, `__atomic_fetch_add` (and friends) - -Unlocks: `builtin_01–07`, `6_9_06`. +## Phase 9 — Builtins ✅ + +Routes named `__builtin_*` / `__atomic_*` calls to cg's intrinsic / asm +machinery rather than ordinary call lowering. Contract: +`doc/builtins.md`. + +- [x] `__builtin_alloca` — lowers via `cg_alloca`; result is `void*`. +- [x] `__builtin_expect` — folds to identity (returns the value + operand; the hint operand is parse-and-drop, no IR hint emitted + — backend can re-introduce one once `cg_intrinsic(EXPECT)` lands). +- [x] `__builtin_va_start` / `va_arg` / `va_end` / `va_copy` — parser + routes through `cg_va_*`; `__builtin_va_list` is recognized as a + type-name (`abi_va_list_type`). +- [x] `__builtin_offsetof` — folds at parse to a `size_t` constant + using `find_field` / `abi_record_layout`; supports nested + `.field` and `[index]` member-designator chains. +- [x] `__atomic_load_n`, `__atomic_store_n`, `__atomic_exchange_n`, + `__atomic_fetch_{add,sub,and,or,xor}` — parser routes through + `cg_atomic_load` / `cg_atomic_store` / `cg_atomic_rmw`. The + memory-order argument must be a constant-folded integer; pp + predefines `__ATOMIC_RELAXED`..`__ATOMIC_SEQ_CST` to match the + `MemOrder` enum so a header-less corpus row resolves the names. + +Phase 9 also added: + - `try_parse_builtin_call` invoked from `parse_primary` ahead of + `scope_lookup`. It dispatches the IDENT against the interned + builtin spellings and consumes the `( ... )` argument list itself, + so the call never goes through `cg_call`. Falls through (returns 0, + no tokens consumed) when the IDENT isn't a known builtin so an + ordinary identifier reference still resolves. + - `__ATOMIC_*` predefined macros in `pp_register_static_predefined` + — values pinned to the `MemOrder` enum so `eval_const_int(order)` + casts directly. Headers can now `#define memory_order_relaxed + __ATOMIC_RELAXED` (matches `rt/include/stdatomic.h`). + - `__builtin_va_list` recognized as a type specifier (and counted by + `starts_type_name`), resolved through `abi_va_list_type` so the + structural shape comes from the ABI vtable + (`abi_aapcs64_va_list_type` is a 32-byte struct; Apple ARM64 is + `char*`). + +Lowering gap: the `cg_va_*` and `cg_atomic_*` primitives panic at the +cg layer ("not in v1 slice"). Parser routing is what Phase 9 owns; +backend wiring is a follow-up. Until that lands, the corpus rows that +exercise the runtime side carry `.skip` sidecars (`6_7_6_08`, +`6_9_06`, `builtin_03`, `builtin_05`, `builtin_06`, `builtin_07`), +treated as failure unless `CFREE_TEST_ALLOW_SKIP=1` — same convention +as `6_7_2_12_long_double`. + +Unlocks (status as landed): `builtin_01_alloca` ★, `builtin_02_expect` +★, `builtin_04_offsetof` ★. `builtin_03_va_list`, `builtin_05_va_copy`, +`builtin_06_atomic_load`, `builtin_07_atomic_fetch_add`, `6_9_06`, +`6_7_6_08` parse end-to-end but `.skip` pending the cg-layer wiring of +`cg_va_*` / `cg_atomic_*`. --- diff --git a/src/parse/parse.c b/src/parse/parse.c @@ -226,6 +226,26 @@ typedef struct Parser { Sym kw_sym[KW_COUNT]; + /* Interned spellings for the __builtin_* / __atomic_* family routed through + * try_parse_builtin_call (Phase 9). __builtin_va_list is recognized as a + * type-name in parse_decl_specs / starts_type_name. */ + Sym sym_b_alloca; + Sym sym_b_expect; + Sym sym_b_offsetof; + Sym sym_b_va_list; + Sym sym_b_va_start; + Sym sym_b_va_arg; + Sym sym_b_va_end; + Sym sym_b_va_copy; + Sym sym_a_load_n; + Sym sym_a_store_n; + Sym sym_a_exchange_n; + Sym sym_a_fetch_add; + Sym sym_a_fetch_sub; + Sym sym_a_fetch_and; + Sym sym_a_fetch_or; + Sym sym_a_fetch_xor; + Scope* scope; /* top of stack; file scope is the root */ ObjSecId text_sec; @@ -727,6 +747,14 @@ static int parse_decl_specs(Parser* p, DeclSpecs* out) { advance(p); seen = 1; } else if (!acc.saw_explicit_type && !tagged_ty && t.kind == TOK_IDENT && ident_kw(p, t.v.ident) == KW_NONE) { + /* `__builtin_va_list` resolves to the per-ABI va_list type. */ + if (t.v.ident == p->sym_b_va_list) { + tagged_ty = abi_va_list_type(p->abi, p->pool); + acc.saw_explicit_type = 1; + advance(p); + seen = 1; + continue; + } /* Typedef-name as a type specifier. Only consumed when no other * type specifier has been seen — otherwise this IDENT is the * declarator name. */ @@ -1237,6 +1265,9 @@ static int starts_type_name(const Parser* p, const Tok* t) { case KW_ALIGNAS: return 1; case KW_NONE: { + /* `__builtin_va_list` is a target-defined type-name (the va_list + * type produced by `abi_va_list_type`). Phase 9. */ + if (t->v.ident == p->sym_b_va_list) return 1; /* Typedef-name. Cast away const for the lookup helper, which only * reads scope state. */ SymEntry* e = scope_lookup((Parser*)p, t->v.ident); @@ -1683,6 +1714,236 @@ static ObjSymId emit_string_to_rodata(Parser* p, const u8* bytes, size_t n) { return sym; } +/* Phase 9 — Builtins. + * + * `__builtin_*` and `__atomic_*` calls are not ordinary function references: + * they don't go through scope_lookup / cg_call. Instead the parser dispatches + * the name to a per-builtin handler that consumes the argument list and + * emits the corresponding cg primitive (cg_alloca, cg_va_*, cg_atomic_*) or + * folds the call to a constant (`__builtin_offsetof`, `__builtin_expect`). + * + * Pre: p->cur is TOK_IDENT for `name`, and peek1() is `(`. (The caller's + * responsibility — try_parse_builtin_call assumes both checks have run.) + * Returns 1 if the name matched a known builtin (token stream advanced + * past the closing `)`); 0 if not (no tokens consumed). + * + * Stack discipline mirrors a normal call: a non-void builtin leaves its + * result rvalue on the stack; a void-returning one (va_start/end/copy, + * atomic_store) pushes the same int-0 sentinel parse_postfix uses for + * void calls so higher levels never underflow. */ +static int try_parse_builtin_call(Parser* p); + +/* Walk a `__builtin_offsetof` member-designator (`.field` / `[index]` chain) + * starting from `rec` (a struct/union/array). Adds offsets into `*off` and + * descends through nested aggregates. Returns the leaf type. The first + * step must be a field name (`.` is implicit per §7.18); subsequent steps + * may be either form. */ +static const Type* offsetof_designator(Parser* p, const Type* base, + u32* off) { + const Type* cur = base; + /* First member name — required, no leading `.` per the macro contract + * but we accept the GCC form `,member-designator` directly here, which + * is `.name` written as just `name` for the leading element. */ + if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + perr(p, "expected member name in __builtin_offsetof"); + } + for (;;) { + if (cur->kind == TY_STRUCT || cur->kind == TY_UNION) { + Sym mname = p->cur.v.ident; + const Type* mty = NULL; + u32 moff = 0; + const Field* mf = NULL; + if (!find_field(p->abi, cur, mname, &mty, &moff, &mf)) { + perr(p, "no such member in __builtin_offsetof"); + } + advance(p); + *off += moff; + cur = mty; + } else if (cur->kind == TY_ARRAY) { + /* `[index]` step — fall through to the bracket branch below. */ + } else { + perr(p, "__builtin_offsetof step into non-aggregate"); + } + /* Optional continuation: `.field` or `[index]`. */ + if (is_punct(&p->cur, '.')) { + advance(p); + if (p->cur.kind != TOK_IDENT || ident_kw(p, p->cur.v.ident) != KW_NONE) { + perr(p, "expected member name after '.'"); + } + continue; + } + if (is_punct(&p->cur, '[')) { + advance(p); + i64 idx = eval_const_int(p, p->cur.loc); + expect_punct(p, ']', "']' in __builtin_offsetof"); + if (cur->kind != TY_ARRAY) { + perr(p, "__builtin_offsetof '[' on non-array"); + } + *off += (u32)((i64)abi_sizeof(p->abi, cur->arr.elem) * idx); + cur = cur->arr.elem; + continue; + } + break; + } + return cur; +} + +static int try_parse_builtin_call(Parser* p) { + Sym name = p->cur.v.ident; + SrcLoc loc = p->cur.loc; + + /* Common dispatch: only the names below match. Falling through means the + * IDENT is not a builtin and parse_primary should resolve it normally. */ + if (name != p->sym_b_alloca && name != p->sym_b_expect && + name != p->sym_b_offsetof && name != p->sym_b_va_start && + name != p->sym_b_va_arg && name != p->sym_b_va_end && + name != p->sym_b_va_copy && name != p->sym_a_load_n && + name != p->sym_a_store_n && name != p->sym_a_exchange_n && + name != p->sym_a_fetch_add && name != p->sym_a_fetch_sub && + name != p->sym_a_fetch_and && name != p->sym_a_fetch_or && + name != p->sym_a_fetch_xor) { + return 0; + } + advance(p); /* IDENT */ + expect_punct(p, '(', "'(' after builtin"); + + if (name == p->sym_b_offsetof) { + /* `__builtin_offsetof(type, member-designator)` — fold to a size_t + * constant. The type is the aggregate root; the designator chain + * accumulates field/element offsets. */ + const Type* root = parse_type_name(p); + expect_punct(p, ',', "',' in __builtin_offsetof"); + u32 off = 0; + (void)offsetof_designator(p, root, &off); + expect_punct(p, ')', "')' after __builtin_offsetof"); + cg_push_int(p->cg, (i64)off, ty_size_t(p)); + return 1; + } + + if (name == p->sym_b_expect) { + /* `__builtin_expect(expr, hint)` — value of the call is `expr`; the + * hint is evaluated for side effects (none in practice) then dropped. + * No backend-side intrinsic is needed for the corpus. */ + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in __builtin_expect"); + parse_assign_expr(p); + cg_drop(p->cg); + expect_punct(p, ')', "')' after __builtin_expect"); + return 1; + } + + if (name == p->sym_b_alloca) { + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ')', "')' after __builtin_alloca"); + cg_set_loc(p->cg, loc); + cg_alloca(p->cg); + return 1; + } + + if (name == p->sym_b_va_start) { + /* `__builtin_va_start(ap, last)` — push &ap, parse-and-drop `last` + * (its name is required by C but the runtime impl ignores it). */ + parse_assign_expr(p); + cg_addr(p->cg); + expect_punct(p, ',', "',' in __builtin_va_start"); + parse_assign_expr(p); + cg_drop(p->cg); + expect_punct(p, ')', "')' after __builtin_va_start"); + cg_set_loc(p->cg, loc); + cg_va_start_(p->cg); + cg_push_int(p->cg, 0, ty_int(p)); /* void-call sentinel */ + return 1; + } + + if (name == p->sym_b_va_end) { + parse_assign_expr(p); + cg_addr(p->cg); + expect_punct(p, ')', "')' after __builtin_va_end"); + cg_set_loc(p->cg, loc); + cg_va_end_(p->cg); + cg_push_int(p->cg, 0, ty_int(p)); + return 1; + } + + if (name == p->sym_b_va_copy) { + parse_assign_expr(p); + cg_addr(p->cg); + expect_punct(p, ',', "',' in __builtin_va_copy"); + parse_assign_expr(p); + cg_addr(p->cg); + expect_punct(p, ')', "')' after __builtin_va_copy"); + cg_set_loc(p->cg, loc); + cg_va_copy_(p->cg); + cg_push_int(p->cg, 0, ty_int(p)); + return 1; + } + + if (name == p->sym_b_va_arg) { + parse_assign_expr(p); + cg_addr(p->cg); + expect_punct(p, ',', "',' in __builtin_va_arg"); + const Type* ty = parse_type_name(p); + expect_punct(p, ')', "')' after __builtin_va_arg"); + cg_set_loc(p->cg, loc); + cg_va_arg_(p->cg, ty); + return 1; + } + + if (name == p->sym_a_load_n) { + /* `__atomic_load_n(ptr, order)`. The order must be a constant + * matching one of the predefined `__ATOMIC_*` values (Phase 9 + * predefines 0–5 to align with the MemOrder enum). */ + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in __atomic_load_n"); + i64 ord = eval_const_int(p, p->cur.loc); + expect_punct(p, ')', "')' after __atomic_load_n"); + cg_set_loc(p->cg, loc); + cg_atomic_load(p->cg, (MemOrder)ord); + return 1; + } + + if (name == p->sym_a_store_n) { + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in __atomic_store_n"); + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in __atomic_store_n"); + i64 ord = eval_const_int(p, p->cur.loc); + expect_punct(p, ')', "')' after __atomic_store_n"); + cg_set_loc(p->cg, loc); + cg_atomic_store(p->cg, (MemOrder)ord); + cg_push_int(p->cg, 0, ty_int(p)); + return 1; + } + + /* The rmw family — exchange / fetch_{add,sub,and,or,xor} share the same + * (ptr, val, order) shape; map name → AtomicOp. */ + AtomicOp op; + if (name == p->sym_a_exchange_n) op = AO_XCHG; + else if (name == p->sym_a_fetch_add) op = AO_ADD; + else if (name == p->sym_a_fetch_sub) op = AO_SUB; + else if (name == p->sym_a_fetch_and) op = AO_AND; + else if (name == p->sym_a_fetch_or) op = AO_OR; + else if (name == p->sym_a_fetch_xor) op = AO_XOR; + else { perr(p, "internal: unhandled builtin"); } + + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in atomic builtin"); + parse_assign_expr(p); + to_rvalue(p); + expect_punct(p, ',', "',' in atomic builtin"); + i64 ord = eval_const_int(p, p->cur.loc); + expect_punct(p, ')', "')' after atomic builtin"); + cg_set_loc(p->cg, loc); + cg_atomic_rmw(p->cg, op, (MemOrder)ord); + return 1; +} + static void parse_primary(Parser* p) { Tok t = p->cur; if (t.kind == TOK_NUM) { @@ -1711,6 +1972,14 @@ static void parse_primary(Parser* p) { if (ident_kw(p, t.v.ident) != KW_NONE) { perr(p, "unexpected keyword in expression"); } + /* Phase 9 — Builtins. Intercepted before scope_lookup because they + * have no SymEntry: `__builtin_*` and `__atomic_*` followed by `(` + * route to dedicated cg primitives (or fold to constants) instead + * of going through cg_call. */ + { + Tok n = peek1(p); + if (is_punct(&n, '(') && try_parse_builtin_call(p)) return; + } e = scope_lookup(p, t.v.ident); if (!e) { size_t nlen = 0; @@ -4659,6 +4928,24 @@ void parse_c(Compiler* c, Pp* pp, DeclTable* decls, CG* cg, Debug* debug) { p.kw_sym[i] = pool_intern_cstr(p.pool, kw_names[i]); } + /* Builtin / atomic spellings — Phase 9. */ + p.sym_b_alloca = pool_intern_cstr(p.pool, "__builtin_alloca"); + p.sym_b_expect = pool_intern_cstr(p.pool, "__builtin_expect"); + p.sym_b_offsetof = pool_intern_cstr(p.pool, "__builtin_offsetof"); + p.sym_b_va_list = pool_intern_cstr(p.pool, "__builtin_va_list"); + p.sym_b_va_start = pool_intern_cstr(p.pool, "__builtin_va_start"); + p.sym_b_va_arg = pool_intern_cstr(p.pool, "__builtin_va_arg"); + p.sym_b_va_end = pool_intern_cstr(p.pool, "__builtin_va_end"); + p.sym_b_va_copy = pool_intern_cstr(p.pool, "__builtin_va_copy"); + p.sym_a_load_n = pool_intern_cstr(p.pool, "__atomic_load_n"); + p.sym_a_store_n = pool_intern_cstr(p.pool, "__atomic_store_n"); + p.sym_a_exchange_n = pool_intern_cstr(p.pool, "__atomic_exchange_n"); + p.sym_a_fetch_add = pool_intern_cstr(p.pool, "__atomic_fetch_add"); + p.sym_a_fetch_sub = pool_intern_cstr(p.pool, "__atomic_fetch_sub"); + p.sym_a_fetch_and = pool_intern_cstr(p.pool, "__atomic_fetch_and"); + p.sym_a_fetch_or = pool_intern_cstr(p.pool, "__atomic_fetch_or"); + p.sym_a_fetch_xor = pool_intern_cstr(p.pool, "__atomic_fetch_xor"); + /* File scope. */ p.scope = scope_new(&p, NULL); diff --git a/src/pp/pp.c b/src/pp/pp.c @@ -2671,6 +2671,15 @@ static void pp_register_static_predefined(Pp* pp) { pp_define(pp, "__STDC__", "1"); pp_define(pp, "__STDC_HOSTED__", "0"); pp_define(pp, "__STDC_VERSION__", "201112L"); + /* C11 memory_order constants used by __atomic_* builtins. Values match the + * `MemOrder` enum in src/arch/arch.h so eval_const_int -> MemOrder is a + * direct cast. */ + pp_define(pp, "__ATOMIC_RELAXED", "0"); + pp_define(pp, "__ATOMIC_CONSUME", "1"); + pp_define(pp, "__ATOMIC_ACQUIRE", "2"); + pp_define(pp, "__ATOMIC_RELEASE", "3"); + pp_define(pp, "__ATOMIC_ACQ_REL", "4"); + pp_define(pp, "__ATOMIC_SEQ_CST", "5"); } Pp* pp_new(Compiler* c) { diff --git a/test/parse/CORPUS.md b/test/parse/CORPUS.md @@ -244,7 +244,7 @@ already exercised in §6.5 and §6.7. | `6_7_6_05_funcptr_returning_ptr` | ★ | helper returns `int*`; `int *(*fp)(int*)=...; return *fp(&x);` | 42 | | `6_7_6_06_array_static_n` | ★ | helper `int rd(int p[static 3]){return p[2];}`; `int a[3]={0,0,42}; return rd(a);` | 42 | | `6_7_6_07_vla_local` | ★ | `int n=7; int a[n]; for(int i=0;i<n;i++) a[i]=i*7; return a[n-1];` | 42 | -| `6_7_6_08_variadic_decl` | · | helper `int sum(int n, ...)` summing two ints; `sum(2, 20, 22)` | 42 | +| `6_7_6_08_variadic_decl` | · | helper `int sum(int n, ...)` summing two ints; `sum(2, 20, 22)` (`.skip` pending cg `cg_va_*`) | 42 | ## §6.7.8 Type definitions @@ -309,7 +309,7 @@ cover compound typedef targets. | `6_9_03_tentative_def` | ★ | file-scope `int g;` (tentative) + use | 0 | | `6_9_04_static_func` | ★ | `static int helper(...)` + caller | 42 | | `6_9_05_proto_then_def` | ★ | forward declaration before body | 42 | -| `6_9_06_variadic_func` | · | `sum(int n, ...)` over `va_arg`; `sum(2,20,22)` (paired with builtin_03) | 42 | +| `6_9_06_variadic_func` | · | `sum(int n, ...)` over `va_arg`; `sum(2,20,22)` (paired with builtin_03; `.skip` pending cg `cg_va_*`) | 42 | | `6_9_07_global_const` | ★ | full TU: `const int g = 42; int test_main(void){return g;}` | 42 | | `6_9_08_global_struct_init` | ★ | full TU: `struct S{int v;} g={42}; int test_main(void){return g.v;}` | 42 | | `6_9_09_static_data_array` | ★ | full TU: `static int g[3] = {0, 0, 42}; int test_main(void){return g[2];}` | 42 | @@ -324,13 +324,13 @@ ordinary calls. | Case | Status | Body | Expected | |---|---|---|---| -| `builtin_01_alloca` | · | `int *p = __builtin_alloca(4); *p=42; return *p;` | 42 | -| `builtin_02_expect` | · | `if (__builtin_expect(1, 1)) return 42; return 0;` | 42 | -| `builtin_03_va_list` | · | uses `__builtin_va_start`/`__builtin_va_arg`/`__builtin_va_end` summing two ints | 42 | -| `builtin_04_offsetof` | · | `struct S {int a, b;}; return (int)__builtin_offsetof(struct S, b) * 10 + 2;` | 42 | -| `builtin_05_va_copy` | · | walks varargs twice via `__builtin_va_copy`; both walks return same sum | 42 | -| `builtin_06_atomic_load` | · | `int x = 42; return __atomic_load_n(&x, __ATOMIC_RELAXED);` | 42 | -| `builtin_07_atomic_fetch_add` | · | `int x = 40; __atomic_fetch_add(&x, 2, __ATOMIC_RELAXED); return x;` | 42 | +| `builtin_01_alloca` | ★ | `int *p = (int *)__builtin_alloca(4); *p=42; return *p;` | 42 | +| `builtin_02_expect` | ★ | `if (__builtin_expect(1, 1)) return 42; return 0;` | 42 | +| `builtin_03_va_list` | · | uses `__builtin_va_start`/`__builtin_va_arg`/`__builtin_va_end` summing three ints (`.skip` pending cg `cg_va_*`) | 42 | +| `builtin_04_offsetof` | ★ | `struct S {int a, b;}; return (int)__builtin_offsetof(struct S, b) * 10 + 2;` | 42 | +| `builtin_05_va_copy` | · | walks varargs twice via `__builtin_va_copy` (`.skip` pending cg `cg_va_copy`) | 42 | +| `builtin_06_atomic_load` | · | `int x = 42; return __atomic_load_n(&x, __ATOMIC_RELAXED);` (`.skip` pending cg `cg_atomic_load`) | 42 | +| `builtin_07_atomic_fetch_add` | · | `int x = 40; __atomic_fetch_add(&x, 2, __ATOMIC_RELAXED); return x;` (`.skip` pending cg `cg_atomic_rmw`) | 42 | | `builtin_08_syscall0` | (deferred) | `__cfree_syscall0` requires linking against the syscall stub; covered in `test/libc` | — | ## Negative cases (cases_err/) diff --git a/test/parse/cases/6_7_6_08_variadic_decl.skip b/test/parse/cases/6_7_6_08_variadic_decl.skip @@ -0,0 +1 @@ +cg va_* primitives are stubs; parser routing for __builtin_va_* landed in Phase 9, lowering needs cg backend wiring diff --git a/test/parse/cases/6_9_06_variadic_func.skip b/test/parse/cases/6_9_06_variadic_func.skip @@ -0,0 +1 @@ +cg va_* primitives are stubs; parser routing for __builtin_va_* landed in Phase 9, lowering needs cg backend wiring diff --git a/test/parse/cases/builtin_01_alloca.c b/test/parse/cases/builtin_01_alloca.c @@ -0,0 +1,5 @@ +int test_main(void) { + int *p = (int *)__builtin_alloca(4); + *p = 42; + return *p; +} diff --git a/test/parse/cases/builtin_01_alloca.expected b/test/parse/cases/builtin_01_alloca.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_02_expect.c b/test/parse/cases/builtin_02_expect.c @@ -0,0 +1,4 @@ +int test_main(void) { + if (__builtin_expect(1, 1)) return 42; + return 0; +} diff --git a/test/parse/cases/builtin_02_expect.expected b/test/parse/cases/builtin_02_expect.expected @@ -0,0 +1 @@ +42 diff --git a/test/parse/cases/builtin_03_va_list.skip b/test/parse/cases/builtin_03_va_list.skip @@ -0,0 +1 @@ +cg va_* primitives are stubs (cg_va_start/_arg/_end panic); parser routing landed in Phase 9, lowering needs cg backend wiring diff --git a/test/parse/cases/builtin_05_va_copy.skip b/test/parse/cases/builtin_05_va_copy.skip @@ -0,0 +1 @@ +cg va_* primitives are stubs (cg_va_copy panics); parser routing landed in Phase 9, lowering needs cg backend wiring diff --git a/test/parse/cases/builtin_06_atomic_load.skip b/test/parse/cases/builtin_06_atomic_load.skip @@ -0,0 +1 @@ +cg_atomic_load is a stub; parser routing for __atomic_load_n landed in Phase 9, lowering needs cg backend wiring diff --git a/test/parse/cases/builtin_07_atomic_fetch_add.skip b/test/parse/cases/builtin_07_atomic_fetch_add.skip @@ -0,0 +1 @@ +cg_atomic_rmw is a stub; parser routing for __atomic_fetch_add landed in Phase 9, lowering needs cg backend wiring