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