commit 4820b382e16649610c6a4e1e43e0380bacf3d89b
parent d00d65a937eb4ea15f294fdd58d8de057cf632f5
Author: Ryan Sepassi <rsepassi@gmail.com>
Date: Mon, 11 May 2026 16:55:43 -0700
asm: support file-scope global asm
Diffstat:
9 files changed, 158 insertions(+), 22 deletions(-)
diff --git a/doc/STAGE2.md b/doc/STAGE2.md
@@ -156,8 +156,9 @@ not been switched back on.
Separate from stage-2 self-host: can cfree compile `libcfree_rt.a`?
Probed on the `aarch64-apple-darwin` variant — 8 sources, freestanding,
-no system headers. Result: **5 / 8 clean** today (`fp/fp.c`, `mem/mem.c`,
-`cfree/ifunc_init.c`, `coro/coro.c`, `int/int.c`). Flags must drop
+no system headers. Result: **6 / 8 clean** today (`fp/fp.c`, `mem/mem.c`,
+`cfree/ifunc_init.c`, `coro/coro.c`, `coro/aarch64.c`, `int/int.c`).
+Flags must drop
`-std=c11 -Wpedantic -Wall -Wextra -Werror -ffreestanding -fno-builtin` —
cfree rejects all of these. (`-fno-builtin` is the only one not already
on the stage-2 drop list.)
@@ -197,8 +198,19 @@ on the stage-2 drop list.)
`parse_primary` as a NUL-terminated `char[N+1]` literal in `.rodata`,
using a new `Parser.cur_func_name` field set around
`parse_function_body`. Outside a function body, a clean diagnostic.
-
-After R1–R7, three blockers remain for the 8-source `aarch64-apple-darwin`
+- [x] **R10.** File-scope `__asm__("...")` declarations
+ (a GCC extension, also accepted by clang). `parse_translation_unit`
+ recognizes `__asm__` / `asm` at TU scope, decodes the string-literal
+ payload, and feeds it through `parse_asm` against the current object
+ emitter. The object symbol table now reuses existing symbols by name,
+ so C declarations before/after asm labels bind to the same `ObjSymId`.
+ For `coro/aarch64.c`, the AArch64 assembler also accepts `stp`/`ldp`
+ on `d0..d31`, supports `csinc`, and predefines
+ `__USER_LABEL_PREFIX__` as `""` for ELF-style targets and `"_"` for
+ Mach-O. Verified with:
+ `build/cfree cc -target aarch64-apple-darwin -g -c rt/lib/coro/aarch64.c ...`.
+
+After R1–R10, two blockers remain for the 8-source `aarch64-apple-darwin`
rt probe:
- [ ] **R8.** `__builtin_ctzl` / `__builtin_ctzll` not wired (only
@@ -214,14 +226,6 @@ rt probe:
without folding cfree would emit per-size dispatch that the rt
layout expects to be dead-code-eliminated. Blocks
`atomic/atomic_freestanding.c:77`.
-- [ ] **R10.** File-scope `__asm__("...")` declarations
- (a GCC extension, also accepted by clang). `coro/aarch64.c` uses
- this form to emit raw setjmp/longjmp assembly without going through
- a function-body inline-asm path. Needs a new top-level parser case
- in `parse_translation_unit` that recognizes `__asm__` / `asm` at
- TU scope, parses the string-literal argument, and feeds it to
- `parse_asm` against the current `__TEXT,__text` section. Blocks
- `coro/aarch64.c:120`.
Additionally listed in the larger SDK ingest plan but not yet seen in
the 8-source rt probe: `__builtin_*_overflow` (for `int/int.c`'s
diff --git a/src/arch/aa64_asm.c b/src/arch/aa64_asm.c
@@ -100,7 +100,8 @@ typedef struct AA64Reg {
u32 num;
u8 is64;
u8 is_sp; /* 1 if the spelling was "sp" / "wsp" */
- u8 pad[2];
+ u8 is_fp; /* 1 for SIMD/FP register spellings accepted in FP forms */
+ u8 pad;
} AA64Reg;
static int parse_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
@@ -112,48 +113,56 @@ static int parse_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
out->num = 31;
out->is64 = 1;
out->is_sp = 1;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "wsp")) {
out->num = 31;
out->is64 = 0;
out->is_sp = 1;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "lr")) {
out->num = 30;
out->is64 = 1;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "fp")) {
out->num = 29;
out->is64 = 1;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "ip0")) {
out->num = 16;
out->is64 = 1;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "ip1")) {
out->num = 17;
out->is64 = 1;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "xzr")) {
out->num = 31;
out->is64 = 1;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
if (icase_eq(p, n, "wzr")) {
out->num = 31;
out->is64 = 0;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
/* W/X<num> */
@@ -169,11 +178,30 @@ static int parse_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
out->num = r;
out->is64 = (p[0] == 'x' || p[0] == 'X') ? 1 : 0;
out->is_sp = 0;
+ out->is_fp = 0;
return 1;
}
return 0;
}
+static int parse_fp_d_reg_from_ident(AsmDriver* d, Sym ident, AA64Reg* out) {
+ size_t n = 0;
+ const char* p = pool_str(asm_driver_pool(d), ident, &n);
+ if (!p || n < 2 || (p[0] != 'd' && p[0] != 'D')) return 0;
+ u32 r = 0;
+ for (size_t i = 1; i < n; ++i) {
+ char c = p[i];
+ if (c < '0' || c > '9') return 0;
+ r = r * 10 + (u32)(c - '0');
+ if (r > 31) return 0;
+ }
+ out->num = r;
+ out->is64 = 1;
+ out->is_sp = 0;
+ out->is_fp = 1;
+ return 1;
+}
+
static AA64Reg parse_reg(AsmDriver* d) {
Tok t = asm_driver_next(d);
AA64Reg r;
@@ -183,6 +211,18 @@ static AA64Reg parse_reg(AsmDriver* d) {
return r;
}
+static AA64Reg parse_ldstp_reg(AsmDriver* d) {
+ Tok t = asm_driver_next(d);
+ AA64Reg r;
+ memset(&r, 0, sizeof r);
+ if (t.kind != TOK_IDENT ||
+ (!parse_reg_from_ident(d, t.v.ident, &r) &&
+ !parse_fp_d_reg_from_ident(d, t.v.ident, &r))) {
+ asm_driver_panic(d, "asm: expected register");
+ }
+ return r;
+}
+
/* Parse "#imm" (with optional + / -) or a bare expression — GNU as is
* lenient about the leading hash. Returns an i64. */
static i64 parse_imm_const(AsmDriver* d) {
@@ -207,6 +247,37 @@ static void emit32(AsmDriver* d, u32 word) {
mc->emit_bytes(mc, buf, 4);
}
+static int parse_cond_from_ident(AsmDriver* d, Sym ident, u32* out) {
+ size_t n = 0;
+ const char* s = pool_str(asm_driver_pool(d), ident, &n);
+ if (!s) return 0;
+ if (icase_eq(s, n, "eq")) *out = 0;
+ else if (icase_eq(s, n, "ne")) *out = 1;
+ else if (icase_eq(s, n, "cs") || icase_eq(s, n, "hs")) *out = 2;
+ else if (icase_eq(s, n, "cc") || icase_eq(s, n, "lo")) *out = 3;
+ else if (icase_eq(s, n, "mi")) *out = 4;
+ else if (icase_eq(s, n, "pl")) *out = 5;
+ else if (icase_eq(s, n, "vs")) *out = 6;
+ else if (icase_eq(s, n, "vc")) *out = 7;
+ else if (icase_eq(s, n, "hi")) *out = 8;
+ else if (icase_eq(s, n, "ls")) *out = 9;
+ else if (icase_eq(s, n, "ge")) *out = 10;
+ else if (icase_eq(s, n, "lt")) *out = 11;
+ else if (icase_eq(s, n, "gt")) *out = 12;
+ else if (icase_eq(s, n, "le")) *out = 13;
+ else if (icase_eq(s, n, "al")) *out = 14;
+ else return 0;
+ return 1;
+}
+
+static u32 parse_cond(AsmDriver* d, const char* what) {
+ Tok t = asm_driver_next(d);
+ u32 cond = 0;
+ if (t.kind != TOK_IDENT || !parse_cond_from_ident(d, t.v.ident, &cond))
+ asm_driver_panic(d, "asm: %s: expected condition code", what);
+ return cond;
+}
+
static void expect_comma(AsmDriver* d, const char* what) {
if (!asm_driver_eat_comma(d))
asm_driver_panic(d, "asm: expected ',' (%s)", what);
@@ -525,6 +596,24 @@ static void p_cmp(AsmDriver* d, int is_neg /* cmn flips op */) {
emit32(d, word);
}
+static void p_csinc(AsmDriver* d) {
+ AA64Reg rd = parse_reg(d);
+ expect_comma(d, "csinc");
+ AA64Reg rn = parse_reg(d);
+ expect_comma(d, "csinc");
+ AA64Reg rm = parse_reg(d);
+ expect_comma(d, "csinc");
+ u32 cond = parse_cond(d, "csinc");
+ if (rd.is_sp || rn.is_sp || rm.is_sp)
+ asm_driver_panic(d, "asm: csinc: SP register not allowed");
+ if (rd.is64 != rn.is64 || rd.is64 != rm.is64)
+ asm_driver_panic(d, "asm: csinc: width mismatch");
+ u32 word = 0x1A800400u | ((u32)rd.is64 << 31) | ((rm.num & 0x1fu) << 16) |
+ ((cond & 0xfu) << 12) | ((rn.num & 0x1fu) << 5) |
+ (rd.num & 0x1fu);
+ emit32(d, word);
+}
+
/* neg / negs Rd, Rm → SUB / SUBS Rd, ZR, Rm */
static void p_neg(AsmDriver* d, int set_flags) {
AA64Reg rd = parse_reg(d);
@@ -750,11 +839,11 @@ static void p_ldur_stur(AsmDriver* d, int is_load) {
/* ldp / stp Rt, Rt2, [Xn, #imm] or [Xn, #imm]! */
static void p_ldp_stp(AsmDriver* d, int is_load) {
- AA64Reg rt = parse_reg(d);
+ AA64Reg rt = parse_ldstp_reg(d);
expect_comma(d, "ldp/stp");
- AA64Reg rt2 = parse_reg(d);
+ AA64Reg rt2 = parse_ldstp_reg(d);
expect_comma(d, "ldp/stp");
- if (rt.is64 != rt2.is64)
+ if (rt.is64 != rt2.is64 || rt.is_fp != rt2.is_fp)
asm_driver_panic(d, "asm: ldp/stp: width mismatch");
AA64Mem m = parse_mem(d);
u32 scale = rt.is64 ? 8u : 4u;
@@ -763,8 +852,8 @@ static void p_ldp_stp(AsmDriver* d, int is_load) {
i64 imm7 = m.imm / (i64)scale;
if (imm7 < -64 || imm7 > 63)
asm_driver_panic(d, "asm: ldp/stp: imm7 out of range");
- AA64LdStPPre f = {.opc = rt.is64 ? 2u : 0u,
- .V = 0,
+ AA64LdStPPre f = {.opc = rt.is_fp ? 1u : (rt.is64 ? 2u : 0u),
+ .V = rt.is_fp ? 1u : 0u,
.L = is_load ? 1u : 0u,
.imm7 = (u32)imm7 & 0x7fu,
.Rt2 = rt2.num,
@@ -813,6 +902,7 @@ static void p_addsub_sub(AsmDriver* d) { p_addsub(d, 1, 0); }
static void p_addsub_subs(AsmDriver* d) { p_addsub(d, 1, 1); }
static void p_cmp_w(AsmDriver* d) { p_cmp(d, 0); }
static void p_cmn_w(AsmDriver* d) { p_cmp(d, 1); }
+static void p_csinc_(AsmDriver* d) { p_csinc(d); }
static void p_neg_w(AsmDriver* d) { p_neg(d, 0); }
static void p_negs_w(AsmDriver* d) { p_neg(d, 1); }
static void p_and_w(AsmDriver* d) { p_log_sr(d, AA64_LOG_AND_OPC, 0); }
@@ -891,6 +981,7 @@ static const AA64Mn kTable[] = {
{"subs", p_addsub_subs, 0},
{"cmp", p_cmp_w, 0},
{"cmn", p_cmn_w, 0},
+ {"csinc", p_csinc_, 0},
{"neg", p_neg_w, 0},
{"negs", p_negs_w, 0},
{"and", p_and_w, 0},
diff --git a/src/decl/decl.c b/src/decl/decl.c
@@ -126,8 +126,12 @@ DeclId decl_declare(DeclTable* t, const Decl* in) {
}
}
}
- slot->obj_sym = obj_symbol_ex(t->ob, onwire, bind, (SymVis)slot->visibility,
- k, OBJ_SEC_NONE, 0, 0, 0);
+ slot->obj_sym = obj_symbol_find(t->ob, onwire);
+ if (slot->obj_sym == OBJ_SYM_NONE) {
+ slot->obj_sym = obj_symbol_ex(t->ob, onwire, bind,
+ (SymVis)slot->visibility, k,
+ OBJ_SEC_NONE, 0, 0, 0);
+ }
}
return id;
}
diff --git a/src/obj/obj.c b/src/obj/obj.c
@@ -290,6 +290,16 @@ ObjSymId obj_symbol_ex(ObjBuilder* ob, Sym name, SymBind bind, SymVis vis,
return (ObjSymId)id;
}
+ObjSymId obj_symbol_find(ObjBuilder* ob, Sym name) {
+ if (!ob || !name) return OBJ_SYM_NONE;
+ u32 n = Symbols_count(&ob->symbols);
+ for (u32 i = 1; i < n; ++i) {
+ ObjSym* s = Symbols_at(&ob->symbols, i);
+ if (s && s->name == name) return (ObjSymId)i;
+ }
+ return OBJ_SYM_NONE;
+}
+
void obj_symbol_define(ObjBuilder* ob, ObjSymId id, ObjSecId section_id,
u64 value, u64 size) {
ObjSym* s;
diff --git a/src/obj/obj.h b/src/obj/obj.h
@@ -353,6 +353,7 @@ ObjSymId obj_symbol(ObjBuilder*, Sym name, SymBind, SymKind,
ObjSymId obj_symbol_ex(ObjBuilder*, Sym name, SymBind, SymVis, SymKind,
ObjSecId section_id, u64 value, u64 size,
u64 common_align);
+ObjSymId obj_symbol_find(ObjBuilder*, Sym name);
/* obj_symbol_ex creates a symbol; obj_symbol_define fills in the
* (section_id, value, size) fields of an already-created symbol. The pair
* supports forward references: an undefined ObjSymId is created when first
diff --git a/src/parse/parse_asm.c b/src/parse/parse_asm.c
@@ -160,8 +160,11 @@ static void set_section(AsmDriver* d, Sym name, SecKind kind, u16 flags,
static ObjSymId intern_sym(AsmDriver* d, Sym name) {
ObjSymId* hit = SymSymMap_get(&d->sym_map, name);
if (hit) return *hit;
- ObjSymId id = obj_symbol_ex(d->ob, name, SB_LOCAL, SV_DEFAULT, SK_NOTYPE,
- OBJ_SEC_NONE, 0, 0, 0);
+ ObjSymId id = obj_symbol_find(d->ob, name);
+ if (id == OBJ_SYM_NONE) {
+ id = obj_symbol_ex(d->ob, name, SB_LOCAL, SV_DEFAULT, SK_NOTYPE,
+ OBJ_SEC_NONE, 0, 0, 0);
+ }
SymSymMap_set(&d->sym_map, name, id);
return id;
}
diff --git a/src/pp/pp.c b/src/pp/pp.c
@@ -335,6 +335,9 @@ static void pp_register_static_predefined(Pp* pp) {
static void pp_register_target_predefined(Pp* pp) {
int lp64 = (pp->c->target.ptr_size == 8);
+ pp_define(pp, "__USER_LABEL_PREFIX__",
+ pp->c->target.obj == CFREE_OBJ_MACHO ? "_" : "");
+
/* stddef.h base aliases */
pp_define(pp, "__SIZE_TYPE__", lp64 ? "unsigned long" : "unsigned int");
pp_define(pp, "__PTRDIFF_TYPE__", lp64 ? "long" : "int");
diff --git a/test/parse/cases/asm_02_file_scope.c b/test/parse/cases/asm_02_file_scope.c
@@ -0,0 +1,19 @@
+extern int global_asm_after;
+
+__asm__(".data\n"
+ ".globl global_asm_after\n"
+ "global_asm_after:\n"
+ ".word 40\n"
+ ".text\n");
+
+asm(".data\n"
+ ".globl global_asm_before\n"
+ "global_asm_before:\n"
+ ".word 2\n"
+ ".text\n");
+
+extern int global_asm_before;
+
+int test_main(void) {
+ return global_asm_before + global_asm_after;
+}
diff --git a/test/parse/cases/asm_02_file_scope.expected b/test/parse/cases/asm_02_file_scope.expected
@@ -0,0 +1 @@
+42