parse,check,type,wwi: @packed struct attribute, both stages (#51)

Hare/harec @packed struct layout: no inter-field or trailing padding, align =
max field align (NOT forced to 1) — matches harec type_store.c + types.c:621
(packed{u8,u64}=size 9/align 8). Parser consumes inline @packed (loud-rejects
unknown struct attrs, both stages); layout gates padding on !packed; cstage
type_eq enforces packed type-distinctness; the .wwi producer round-trips
"struct @packed {". wwstage sets slotsize=size for packed so its composite-ABI
copy matches cstage byte-for-byte. cstage identity is faithful; wwstage identity
rides the deferred #224 nominal-resolvealias arc (#108). Both stages byte-id;
447 tests pass.
This commit is contained in:
2026-06-19 00:17:49 +09:00
parent 0197dfb9e6
commit 027f90c572
12 changed files with 563 additions and 16 deletions

View File

@@ -133,6 +133,7 @@ export type node = struct {
next: *node,
attr: *node,
exported: i32, // bool — `export` keyword present
packed: i32, // N_TSTRUCT: `struct @packed` — no padding (harec ast.h:95)
type_: *void, // filled in by checker; type.ww treats it as *tinfo
tsuffix: str, // typed numeric literal suffix ("i32", "u64", ...)
nmod: str, // originating module from `// MODULE: foo`; "" if none
@@ -146,7 +147,7 @@ export fn newnode(k: nkind, file: str, line: i32, col: i32) *node = {
// fval cast-init: 990's wwdump TK_FLOAT diff requires this file
// to tokenise identically through C and ww (lex.ww:382 has the
// same workaround for the cstage %g-formats vs ww-skips divergence).
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
let n: *node = alloc(node{kind=k, file=file, line=line, col=col, op=tkind.TK_NONE, str="", uval=0u64, fval=0: f64, lhs=nil, rhs=nil, cond=nil, body=nil, els=nil, list=nil, next=nil, attr=nil, exported=0, packed=0, type_=nil, tsuffix="", nmod="", usepath="", imported=0})!;
return n;
};

View File

@@ -191,8 +191,21 @@ fn parsetype(p: *parser) *node = {
if (p.curkind == tkind.TK_STRUCT) {
advance(p);
expecttok(p, tkind.TK_LBRACE, "expected '{' after struct");
let n = newnode(nkind.N_TSTRUCT, pf, pl, pc);
// `@packed` is an inline struct TYPE attribute (harec ast.h:95),
// after `struct` and before `{` — NOT a fn-decl attr, so it does
// not route through parseattrs.
if (p.curkind == tkind.TK_AT) {
advance(p);
let an: str;
expectident(p, &an);
if (!streq(an, "packed")) {
errmsg(p, "unknown struct attribute");
} else {
n.packed = 1;
};
};
expecttok(p, tkind.TK_LBRACE, "expected '{' after struct");
let fhead: *node = nil;
let ftail: *node = nil;
for (p.curkind != tkind.TK_RBRACE) {

View File

@@ -144,6 +144,9 @@ export type tinfo = struct {
// still lives at slotsize()'s read site;
// graduating it here would break `[N]i32`
// stride (4*N stays natural).
packed: i32, // TY_STRUCT laid out with no padding; part of type
// identity (harec types.c:517/621). Mirrors cstage
// Type.packed (cmd/wcc/ww.h).
};
// #61 audit §1.8 / Rob+Drew convergence 2026-05-20: memoizes
@@ -202,7 +205,7 @@ export type tctx = struct {
// ---- constructors -----------------------------------------------------
export fn newtype(k: tykind) *tinfo = {
let t: *tinfo = alloc(tinfo{kind=k, size=0u64, align=0u64, sub=nil, alen=0u64, fields=nil, params=nil, tupleelems=nil, ret=nil, variadic=0, nullable=0, name="", under=nil, slotsize=0u64})!;
let t: *tinfo = alloc(tinfo{kind=k, size=0u64, align=0u64, sub=nil, alen=0u64, fields=nil, params=nil, tupleelems=nil, ret=nil, variadic=0, nullable=0, name="", under=nil, slotsize=0u64, packed=0})!;
return t;
};
@@ -559,6 +562,9 @@ export fn typeeq(a: *tinfo, b: *tinfo) bool = {
return true;
};
if (k == tykind.TY_STRUCT) {
// packed is part of struct identity: a packed struct is not
// equal to its unpacked twin (harec types.c:621).
if (a.packed != b.packed) { return false; };
let fa: *tfield = a.fields;
let fb: *tfield = b.fields;
for (true) {