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:
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