kit

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

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:
Mdoc/STAGE2.md | 28++++++++++++++++------------
Msrc/arch/aa64_asm.c | 103++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/decl/decl.c | 8++++++--
Msrc/obj/obj.c | 10++++++++++
Msrc/obj/obj.h | 1+
Msrc/parse/parse_asm.c | 7+++++--
Msrc/pp/pp.c | 3+++
Atest/parse/cases/asm_02_file_scope.c | 19+++++++++++++++++++
Atest/parse/cases/asm_02_file_scope.expected | 1+
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