kit

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

commit 6935690db6378b76c5ee70c35b1438250c6062c2
parent bb761e84894752fec18b51b8b9a914e06ccc2798
Author: Ryan Sepassi <rsepassi@gmail.com>
Date:   Mon, 11 May 2026 09:19:19 -0700

attr: phase-2 W1–W4 — honor packed/aligned/section/used/weak/visibility/alias/noreturn

Merge of four parallel-worker branches off PR-0:

  W1 (abi/abi.c)         honor Type.rec.{packed,align_override} and
                         Field.{packed,align_override} in compute_record_layout.
  W2 (decl/decl_attrs.c) implement attr_list_to_decl: decode every honored
                         AttrKind onto Decl. Added DeclTable* parameter so
                         section("...") can reach the ObjBuilder.
  W3 (decl/decl.c)       decl_declare honors DF_WEAK → SB_WEAK; decl_define_*
                         honor Decl.section_id and set SF_RETAIN on DF_USED.
  W4 (decl/decl.c)       implement decl_define_alias: resolve target's ObjSym
                         and obj_symbol_define self at the same (section,
                         value, size).

All consumer code is dormant until PR-Z wires the parser to populate the new
Decl/Type/Field fields. Baseline parse-tests unchanged: 1126 pass, 18 fail
(attr_p2_* awaiting PR-Z), 1 skip.

Diffstat:
Mdoc/ATTRIBUTE.md | 24+++++++++++++-----------
Msrc/abi/abi.c | 9+++++++++
Msrc/decl/decl.c | 95++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-----
Msrc/decl/decl_attrs.c | 107++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Msrc/decl/decl_attrs.h | 6++++--
5 files changed, 214 insertions(+), 27 deletions(-)

diff --git a/doc/ATTRIBUTE.md b/doc/ATTRIBUTE.md @@ -611,34 +611,36 @@ Inert structural changes that unblock W1–W5. **W1 — Type / ABI layout** (`src/abi/abi.c`) -- [ ] `compute_record_layout`: per-field align clamp under +- [x] `compute_record_layout`: per-field align clamp under `rec.packed`; per-field `align_override` raise; record-level `align_override` raise. **W2 — `attr_list_to_decl` body** (`src/decl/decl_attrs.c`) -- [ ] Decode `ATTR_ALIGNED` → `Decl.align`. -- [ ] Decode `ATTR_SECTION` → intern/create `ObjSecId` → - `Decl.section_id`. -- [ ] Decode `ATTR_USED` / `ATTR_WEAK` / `ATTR_NORETURN` / +- [x] Decode `ATTR_ALIGNED` → `Decl.align`. +- [x] Decode `ATTR_SECTION` → intern/create `ObjSecId` → + `Decl.section_id`. (Required adding a `DeclTable*` param to + `attr_list_to_decl` so it can reach the `ObjBuilder`.) +- [x] Decode `ATTR_USED` / `ATTR_WEAK` / `ATTR_NORETURN` / `ATTR_ALWAYS_INLINE` / `ATTR_NOINLINE` / `ATTR_GNU_INLINE` → `Decl.flags`. -- [ ] Decode `ATTR_VISIBILITY` → `Decl.visibility`. -- [ ] Decode `ATTR_ALIAS` → `Decl.alias_target`. +- [x] Decode `ATTR_VISIBILITY` → `Decl.visibility`. +- [x] Decode `ATTR_ALIAS` → `Decl.alias_target`. **W3 — Decl honoring** (`src/decl/decl.c`) -- [ ] `decl_declare` honors `DF_WEAK` → `SB_WEAK`; uses +- [x] `decl_declare` honors `DF_WEAK` → `SB_WEAK`; uses `obj_symbol_ex` so `Decl.visibility` reaches `ObjSym.vis`. -- [ ] `decl_define_object` / `decl_define_function` honor +- [x] `decl_define_object` / `decl_define_function` honor `Decl.section_id` (bypass default picker). -- [ ] `decl_define_*` set `SF_RETAIN` on the defining section when +- [x] `decl_define_*` set `SF_RETAIN` on the defining section when `DF_USED`. - [ ] `define_static_object` takes `max(specs.align, attr_align)`. + (Parser-side; lands with PR-Z.) **W4 — Aliases** (`src/decl/decl.c`) -- [ ] Implement `decl_define_alias` (presently a stub). +- [x] Implement `decl_define_alias` (presently a stub). **PR-Z — Parser wire-up** (`src/parse/parse.c`) diff --git a/src/abi/abi.c b/src/abi/abi.c @@ -191,6 +191,8 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { for (u16 i = 0; i < t->rec.nfields; ++i) { const Field* f = &t->rec.fields[i]; ABITypeInfo fi = abi_type_info(a, f->type); + if (t->rec.packed) fi.align = 1; + if (f->align_override > fi.align) fi.align = f->align_override; if (fi.align > max_align) max_align = fi.align; u32 mask = fi.align ? fi.align - 1 : 0; off = (off + mask) & ~mask; @@ -207,6 +209,8 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { for (u16 i = 0; i < t->rec.nfields; ++i) { const Field* f = &t->rec.fields[i]; ABITypeInfo fi = abi_type_info(a, f->type); + if (t->rec.packed) fi.align = 1; + if (f->align_override > fi.align) fi.align = f->align_override; if (fi.align > max_align) max_align = fi.align; if (fi.size > mx) mx = fi.size; fl[i].offset = 0; @@ -216,6 +220,11 @@ static ABIRecordLayout* compute_record_layout(TargetABI* a, const Type* t) { L->size = (mx + mask) & ~mask; } L->align = max_align; + if (t->rec.align_override > L->align) { + L->align = t->rec.align_override; + u32 mask = L->align - 1; + L->size = (L->size + mask) & ~mask; + } L->nfields = t->rec.nfields; L->fields = fl; return L; diff --git a/src/decl/decl.c b/src/decl/decl.c @@ -89,6 +89,12 @@ DeclId decl_declare(DeclTable* t, const Decl* in) { slot->storage != DS_REGISTER) { SymBind bind = (slot->linkage == DL_EXTERNAL) ? SB_GLOBAL : SB_LOCAL; SymKind k = (slot->type && slot->type->kind == TY_FUNC) ? SK_FUNC : SK_OBJ; + if (slot->flags & DF_WEAK) { + if (slot->linkage != DL_EXTERNAL) + compiler_panic(t->c, slot->loc, + "weak attribute requires external linkage"); + bind = SB_WEAK; + } Sym onwire = slot->name; /* Mach-O C-symbol convention: every C identifier carries a leading * `_` on disk (so `int main()` is exposed as `_main`). The cgtarget @@ -141,8 +147,16 @@ void decl_define_function(DeclTable* t, DeclId id, ObjSecId text_section_id, * callers that want explicit decl-side definition (e.g. asm-defined * functions where no CGTarget func_end runs). */ const Decl* d = decl_get(t, id); + ObjSecId sec_id; if (!d || d->obj_sym == OBJ_SYM_NONE) return; - obj_symbol_define(t->ob, d->obj_sym, text_section_id, value, size); + /* Caller's section wins when supplied; otherwise fall back to the Decl's + * attribute-driven section. */ + sec_id = (text_section_id != OBJ_SEC_NONE) ? text_section_id : d->section_id; + obj_symbol_define(t->ob, d->obj_sym, sec_id, value, size); + if ((d->flags & DF_USED) && sec_id != OBJ_SEC_NONE) { + const Section* s = obj_section_get(t->ob, sec_id); + if (s) obj_section_set_flags(t->ob, sec_id, (u16)(s->flags | SF_RETAIN)); + } } void decl_define_object(DeclTable* t, DeclId id, u64 size, u32 align, @@ -152,6 +166,7 @@ void decl_define_object(DeclTable* t, DeclId id, u64 size, u32 align, Sym sec_name; int has_nonzero; u32 i; + u32 base; if (!d || d->obj_sym == OBJ_SYM_NONE) return; has_nonzero = 0; for (i = 0; i < ninit; ++i) { @@ -160,19 +175,65 @@ void decl_define_object(DeclTable* t, DeclId id, u64 size, u32 align, break; } } + if (d->section_id != OBJ_SEC_NONE) { + /* Attribute-pinned section: use it for both BSS-style (all-zero) and + * initialized layouts. The section's kind was set at creation time. */ + sec_id = d->section_id; + if (!has_nonzero) { + obj_reserve_bss(t->ob, sec_id, (u32)size, align ? align : 1u); + obj_symbol_define(t->ob, d->obj_sym, sec_id, 0, size); + } else { + base = obj_pos(t->ob, sec_id); + obj_reserve(t->ob, sec_id, size); + obj_symbol_define(t->ob, d->obj_sym, sec_id, base, size); + for (i = 0; i < ninit; ++i) { + const InitItem* it = &init[i]; + switch (it->kind) { + case INIT_BYTES: + obj_patch(t->ob, sec_id, base + it->offset, it->v.bytes.bytes, + it->v.bytes.size); + break; + case INIT_FILL: { + u32 j; + u8 b = it->v.fill.byte; + for (j = 0; j < it->size; ++j) { + obj_patch(t->ob, sec_id, base + it->offset + j, &b, 1); + } + break; + } + case INIT_RELOC: + obj_reloc(t->ob, sec_id, base + it->offset, it->v.reloc.kind, + it->v.reloc.target, it->v.reloc.addend); + break; + case INIT_ZERO: + default: + break; + } + } + } + if (d->flags & DF_USED) { + const Section* s = obj_section_get(t->ob, sec_id); + if (s) obj_section_set_flags(t->ob, sec_id, (u16)(s->flags | SF_RETAIN)); + } + return; + } if (!has_nonzero) { sec_name = pool_intern_cstr(t->c->global, ".bss"); sec_id = obj_section(t->ob, sec_name, SEC_BSS, SF_ALLOC | SF_WRITE, align ? align : 1u); obj_reserve_bss(t->ob, sec_id, (u32)size, align ? align : 1u); obj_symbol_define(t->ob, d->obj_sym, sec_id, 0, size); + if (d->flags & DF_USED) { + const Section* s = obj_section_get(t->ob, sec_id); + if (s) obj_section_set_flags(t->ob, sec_id, (u16)(s->flags | SF_RETAIN)); + } return; } sec_name = pool_intern_cstr(t->c->global, ".data"); sec_id = obj_section(t->ob, sec_name, SEC_DATA, SF_ALLOC | SF_WRITE, align ? align : 1u); { - u32 base = obj_pos(t->ob, sec_id); + base = obj_pos(t->ob, sec_id); obj_reserve(t->ob, sec_id, size); obj_symbol_define(t->ob, d->obj_sym, sec_id, base, size); for (i = 0; i < ninit; ++i) { @@ -200,6 +261,10 @@ void decl_define_object(DeclTable* t, DeclId id, u64 size, u32 align, } } } + if (d->flags & DF_USED) { + const Section* s = obj_section_get(t->ob, sec_id); + if (s) obj_section_set_flags(t->ob, sec_id, (u16)(s->flags | SF_RETAIN)); + } } void decl_define_tentative(DeclTable* t, DeclId id, u64 size, u32 align) { @@ -211,7 +276,27 @@ void decl_define_tentative(DeclTable* t, DeclId id, u64 size, u32 align) { } void decl_define_alias(DeclTable* t, DeclId self, DeclId target) { - (void)t; - (void)self; - (void)target; + const Decl* sd = decl_get(t, self); + const Decl* td = decl_get(t, target); + ObjSymId tsym; + const ObjSym* ts; + if (!sd || sd->obj_sym == OBJ_SYM_NONE) { + compiler_panic(t->c, sd ? sd->loc : (SrcLoc){0}, + "alias self invalid"); + } + if (!td || target == DECL_NONE) { + compiler_panic(t->c, sd->loc, "alias target invalid"); + } + tsym = td->obj_sym; + if (tsym == OBJ_SYM_NONE) { + compiler_panic(t->c, sd->loc, "alias target has no symbol"); + } + ts = obj_symbol_get(t->ob, tsym); + if (!ts || ts->kind == SK_UNDEF) { + size_t nl = 0; + const char* nm = td->name ? pool_str(t->c->global, td->name, &nl) : NULL; + compiler_panic(t->c, sd->loc, "alias target '%s' is undefined", + nm ? nm : "?"); + } + obj_symbol_define(t->ob, sd->obj_sym, ts->section_id, ts->value, ts->size); } diff --git a/src/decl/decl_attrs.c b/src/decl/decl_attrs.c @@ -1,12 +1,101 @@ #include "decl/decl_attrs.h" -/* PR-0 interface seed: no-op body. The Phase 2 worker (W2 in - * doc/ATTRIBUTE.md's parallelization plan) fills this in. Once - * implemented, parse.c gains a single call per decl-declaring site - * and every honored attribute lights up without parser changes. */ - -void attr_list_to_decl(Compiler* c, const Attr* attrs, Decl* out) { - (void)c; - (void)attrs; - (void)out; +#include <string.h> + +#include "core/pool.h" +#include "obj/obj.h" + +/* Bare `__attribute__((aligned))` (no argument) means "biggest scalar + * alignment". cfree's targets all have `_Alignof(long double) == 16` + * (x86_64 SysV, AArch64 AAPCS, RISC-V LP64D), so 16 is a valid v1 + * stand-in across the board. */ +#define ATTR_ALIGNED_DEFAULT 16u + +static void apply_section(Compiler* c, DeclTable* t, const Attr* a, Decl* out) { + if (!t || a->v.sym == 0) return; + ObjBuilder* ob = decl_obj(t); + size_t nlen = 0; + const char* name = pool_str(c->global, a->v.sym, &nlen); + if (!name) return; + + SecKind kind; + u16 flags; + if (strstr(name, "text") != NULL) { + kind = SEC_TEXT; + flags = SF_ALLOC | SF_EXEC; + } else if (strstr(name, "rodata") != NULL) { + kind = SEC_RODATA; + flags = SF_ALLOC; + } else if (strstr(name, "bss") != NULL) { + kind = SEC_BSS; + flags = SF_ALLOC | SF_WRITE; + } else { + kind = SEC_DATA; + flags = SF_ALLOC | SF_WRITE; + } + out->section_id = obj_section(ob, a->v.sym, kind, flags, 1u); +} + +static void apply_visibility(Compiler* c, const Attr* a, Decl* out) { + if (a->v.sym == 0) { + compiler_panic(c, a->loc, "visibility attribute missing argument"); + } + size_t n = 0; + const char* s = pool_str(c->global, a->v.sym, &n); + if (s && strcmp(s, "default") == 0) { + out->visibility = SV_DEFAULT; + } else if (s && strcmp(s, "hidden") == 0) { + out->visibility = SV_HIDDEN; + } else if (s && strcmp(s, "protected") == 0) { + out->visibility = SV_PROTECTED; + } else if (s && strcmp(s, "internal") == 0) { + out->visibility = SV_INTERNAL; + } else { + compiler_panic(c, a->loc, + "unknown visibility '%s' (expected default|hidden|" + "protected|internal)", + s ? s : ""); + } +} + +void attr_list_to_decl(Compiler* c, DeclTable* t, const Attr* attrs, + Decl* out) { + for (const Attr* a = attrs; a; a = a->next) { + switch ((AttrKind)a->kind) { + case ATTR_ALIGNED: { + u32 v = (a->nargs == 0) ? ATTR_ALIGNED_DEFAULT : (u32)a->v.i; + if (v > out->align) out->align = v; + break; + } + case ATTR_SECTION: + apply_section(c, t, a, out); + break; + case ATTR_USED: + out->flags |= DF_USED; + break; + case ATTR_WEAK: + out->flags |= DF_WEAK; + break; + case ATTR_NORETURN: + out->flags |= DF_NORETURN; + break; + case ATTR_ALWAYS_INLINE: + out->flags |= DF_ALWAYS_INLINE; + break; + case ATTR_NOINLINE: + out->flags |= DF_NOINLINE; + break; + case ATTR_GNU_INLINE: + out->flags |= DF_GNU_INLINE; + break; + case ATTR_VISIBILITY: + apply_visibility(c, a, out); + break; + case ATTR_ALIAS: + out->alias_target = a->v.sym; + break; + default: + break; + } + } } diff --git a/src/decl/decl_attrs.h b/src/decl/decl_attrs.h @@ -24,7 +24,9 @@ * * `attrs` may be NULL; `out` must be non-NULL. Idempotent: applying a * list twice produces the same Decl state. Phase 2 callers invoke this - * once, between filling out the bulk Decl fields and decl_declare(). */ -void attr_list_to_decl(Compiler*, const Attr* attrs, Decl* out); + * once, between filling out the bulk Decl fields and decl_declare(). + * The DeclTable* is used to reach the underlying ObjBuilder when + * `__attribute__((section("...")))` requires creating an ObjSecId. */ +void attr_list_to_decl(Compiler*, DeclTable*, const Attr* attrs, Decl* out); #endif