From 65db360b913ac1cb8cb7316b4fe0d33db8c84d32 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Tue, 19 May 2026 22:09:47 +0900 Subject: [PATCH] selfhost/cmd/wcc/cgen: size amalloc slots to struct, not sizeof(str)@16 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Nine sites used hardcoded byte counts sized for str=16. With str's in-memory size invariant about to grow under #1, the next-pointer or field write would land past the slot and corrupt the next bump allocation — selfhost/CLAUDE.md flags this exact pattern. Over-alloc by 8B is harmless under the bump allocator, so bumping the constants is correct at str=16 too. Sites: fnret, enumtype, modent (×4), ffi, strlit, enummember slot sizes; loopendbuf, loopcontbuf, yieldbuf LOOP_MAX strides. Latent bug found by str-size-hang-debug worker via PC trace on a str=24 probe: fnretlookup spun forever because the frnext write fell into the string heap, forming a cycle. Fix verified at str=16 (130/130 + 994/995) and probed at str=24 (994 still green; further graduation work tracked by #1). --- selfhost/cmd/w6c/main.combined.ww | 36 ++++++++++++++++++---------- selfhost/cmd/wcc/cgen.ww | 36 ++++++++++++++++++---------- selfhost/cmd/wwdump/main.combined.ww | 36 ++++++++++++++++++---------- 3 files changed, 72 insertions(+), 36 deletions(-) diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 9decbca4..00629d9e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -19854,7 +19854,8 @@ fn collectenums(c: *cgen, file: *node) void = { let body: *node = d.lhs; if (body != nil) { if (body.kind == nkind.N_TENUM) { - let et: *enumtype = amalloc(c.a, 64u64): *enumtype; + // #35: sizeof(enumtype) = 72 under str=24 ({str,str,*node,*enummember,*enumtype}); amalloc < struct corrupts the next slot. + let et: *enumtype = amalloc(c.a, 80u64): *enumtype; et.ename = d.str; et.emod = d.nmod; et.storage = body.lhs; @@ -19873,7 +19874,8 @@ fn collectenums(c: *cgen, file: *node) void = { }; }; prev = val; - let em: *enummember = amalloc(c.a, 32u64): *enummember; + // #35: sizeof(enummember) = 40 under str=24 ({str,u64,*enummember}). + let em: *enummember = amalloc(c.a, 48u64): *enummember; em.mname = m.str; em.mval = val; em.emnext = nil; @@ -20162,10 +20164,14 @@ fn cgeninit(c: *cgen, a: *arena) void = { // persist across cgfn calls within one file. cgfile resets them // at the start of each compilation unit. c.looptop = 0; - c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; - c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + // #35: per-slot stride is sizeof(str); the bare 16 is the str=16 + // width and undershoots under str=24, so indices past (LOOP_MAX*16)/24 + // = 10 would spill into the next amalloc. Bumped to 24/slot; +8/slot + // over-alloc under str=16 is harmless under the bump arena. + c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; + c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.yieldtop = 0; - c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.defertop = 0; c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; @@ -20521,7 +20527,8 @@ fn internstrlit(c: *cgen, bytes: str) str = { let lab: str; lab.ptr = p; lab.len = total; - let nw: *strlit = amalloc(c.a, 48u64): *strlit; + // #35: sizeof(strlit) = 56 under str=24 ({str,str,*strlit}). + let nw: *strlit = amalloc(c.a, 64u64): *strlit; nw.label = lab; nw.bytes = bytes; nw.slnext = c.strlits; @@ -21250,7 +21257,8 @@ fn collectfnrets(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_FNDECL) { - let f: *fnret = amalloc(c.a, 64u64): *fnret; + // #35: sizeof(fnret) = 72 under str=24 ({str,str,*node,*node,*fnret}); was the trigger that hung w6c_ww on the hangtest — frnext overflowed into the next amalloc, breaking fnretlookup's chain walk. + let f: *fnret = amalloc(c.a, 80u64): *fnret; f.fname = d.str; f.fmod = d.nmod; f.rtype = d.lhs; @@ -21490,6 +21498,9 @@ type modent = struct { mnext: *modent, }; +// #35: sizeof(modent) = 56 under str=24 ({str,str,*modent}); each +// of the four amallocs below grew from 48 to 64 to keep mnext inside +// the slot. fn collectmods(c: *cgen, file: *node) void = { c.mods = nil; if (file == nil) { return; }; @@ -21513,7 +21524,7 @@ fn collectmods(c: *cgen, file: *node) void = { }; if (!isffi) { if (!streq(d.str, "main")) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21525,7 +21536,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_DEF) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21536,7 +21547,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_TYPEDECL) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21547,7 +21558,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_LET) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21653,7 +21664,8 @@ fn fficollect(c: *cgen, file: *node) void = { let symnode: *node = a.list; if (symnode != nil) { if (symnode.kind == nkind.N_STRLIT) { - let f: *ffi = amalloc(c.a, 48u64): *ffi; + // #35: sizeof(ffi) = 56 under str=24 ({str,str,*ffi}). + let f: *ffi = amalloc(c.a, 64u64): *ffi; f.ident = d.str; f.symbol = symnode.str; f.fnext = c.ffis; diff --git a/selfhost/cmd/wcc/cgen.ww b/selfhost/cmd/wcc/cgen.ww index 72d71e9e..72b95367 100644 --- a/selfhost/cmd/wcc/cgen.ww +++ b/selfhost/cmd/wcc/cgen.ww @@ -241,7 +241,8 @@ fn collectenums(c: *cgen, file: *node) void = { let body: *node = d.lhs; if (body != nil) { if (body.kind == nkind.N_TENUM) { - let et: *enumtype = amalloc(c.a, 64u64): *enumtype; + // #35: sizeof(enumtype) = 72 under str=24 ({str,str,*node,*enummember,*enumtype}); amalloc < struct corrupts the next slot. + let et: *enumtype = amalloc(c.a, 80u64): *enumtype; et.ename = d.str; et.emod = d.nmod; et.storage = body.lhs; @@ -260,7 +261,8 @@ fn collectenums(c: *cgen, file: *node) void = { }; }; prev = val; - let em: *enummember = amalloc(c.a, 32u64): *enummember; + // #35: sizeof(enummember) = 40 under str=24 ({str,u64,*enummember}). + let em: *enummember = amalloc(c.a, 48u64): *enummember; em.mname = m.str; em.mval = val; em.emnext = nil; @@ -549,10 +551,14 @@ fn cgeninit(c: *cgen, a: *arena) void = { // persist across cgfn calls within one file. cgfile resets them // at the start of each compilation unit. c.looptop = 0; - c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; - c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + // #35: per-slot stride is sizeof(str); the bare 16 is the str=16 + // width and undershoots under str=24, so indices past (LOOP_MAX*16)/24 + // = 10 would spill into the next amalloc. Bumped to 24/slot; +8/slot + // over-alloc under str=16 is harmless under the bump arena. + c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; + c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.yieldtop = 0; - c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.defertop = 0; c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; @@ -908,7 +914,8 @@ fn internstrlit(c: *cgen, bytes: str) str = { let lab: str; lab.ptr = p; lab.len = total; - let nw: *strlit = amalloc(c.a, 48u64): *strlit; + // #35: sizeof(strlit) = 56 under str=24 ({str,str,*strlit}). + let nw: *strlit = amalloc(c.a, 64u64): *strlit; nw.label = lab; nw.bytes = bytes; nw.slnext = c.strlits; @@ -1637,7 +1644,8 @@ fn collectfnrets(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_FNDECL) { - let f: *fnret = amalloc(c.a, 64u64): *fnret; + // #35: sizeof(fnret) = 72 under str=24 ({str,str,*node,*node,*fnret}); was the trigger that hung w6c_ww on the hangtest — frnext overflowed into the next amalloc, breaking fnretlookup's chain walk. + let f: *fnret = amalloc(c.a, 80u64): *fnret; f.fname = d.str; f.fmod = d.nmod; f.rtype = d.lhs; @@ -1877,6 +1885,9 @@ type modent = struct { mnext: *modent, }; +// #35: sizeof(modent) = 56 under str=24 ({str,str,*modent}); each +// of the four amallocs below grew from 48 to 64 to keep mnext inside +// the slot. fn collectmods(c: *cgen, file: *node) void = { c.mods = nil; if (file == nil) { return; }; @@ -1900,7 +1911,7 @@ fn collectmods(c: *cgen, file: *node) void = { }; if (!isffi) { if (!streq(d.str, "main")) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -1912,7 +1923,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_DEF) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -1923,7 +1934,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_TYPEDECL) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -1934,7 +1945,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_LET) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -2040,7 +2051,8 @@ fn fficollect(c: *cgen, file: *node) void = { let symnode: *node = a.list; if (symnode != nil) { if (symnode.kind == nkind.N_STRLIT) { - let f: *ffi = amalloc(c.a, 48u64): *ffi; + // #35: sizeof(ffi) = 56 under str=24 ({str,str,*ffi}). + let f: *ffi = amalloc(c.a, 64u64): *ffi; f.ident = d.str; f.symbol = symnode.str; f.fnext = c.ffis; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 2e53c64c..ec1d233d 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -19854,7 +19854,8 @@ fn collectenums(c: *cgen, file: *node) void = { let body: *node = d.lhs; if (body != nil) { if (body.kind == nkind.N_TENUM) { - let et: *enumtype = amalloc(c.a, 64u64): *enumtype; + // #35: sizeof(enumtype) = 72 under str=24 ({str,str,*node,*enummember,*enumtype}); amalloc < struct corrupts the next slot. + let et: *enumtype = amalloc(c.a, 80u64): *enumtype; et.ename = d.str; et.emod = d.nmod; et.storage = body.lhs; @@ -19873,7 +19874,8 @@ fn collectenums(c: *cgen, file: *node) void = { }; }; prev = val; - let em: *enummember = amalloc(c.a, 32u64): *enummember; + // #35: sizeof(enummember) = 40 under str=24 ({str,u64,*enummember}). + let em: *enummember = amalloc(c.a, 48u64): *enummember; em.mname = m.str; em.mval = val; em.emnext = nil; @@ -20162,10 +20164,14 @@ fn cgeninit(c: *cgen, a: *arena) void = { // persist across cgfn calls within one file. cgfile resets them // at the start of each compilation unit. c.looptop = 0; - c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; - c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + // #35: per-slot stride is sizeof(str); the bare 16 is the str=16 + // width and undershoots under str=24, so indices past (LOOP_MAX*16)/24 + // = 10 would spill into the next amalloc. Bumped to 24/slot; +8/slot + // over-alloc under str=16 is harmless under the bump arena. + c.loopendbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; + c.loopcontbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.yieldtop = 0; - c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 16u64): *str; + c.yieldbuf = amalloc(a, (LOOP_MAX: u64) * 24u64): *str; c.defertop = 0; c.deferbuf = amalloc(a, (DEFER_MAX: u64) * 8u64): **node; }; @@ -20521,7 +20527,8 @@ fn internstrlit(c: *cgen, bytes: str) str = { let lab: str; lab.ptr = p; lab.len = total; - let nw: *strlit = amalloc(c.a, 48u64): *strlit; + // #35: sizeof(strlit) = 56 under str=24 ({str,str,*strlit}). + let nw: *strlit = amalloc(c.a, 64u64): *strlit; nw.label = lab; nw.bytes = bytes; nw.slnext = c.strlits; @@ -21250,7 +21257,8 @@ fn collectfnrets(c: *cgen, file: *node) void = { let d: *node = file.list; for (d != nil) { if (d.kind == nkind.N_FNDECL) { - let f: *fnret = amalloc(c.a, 64u64): *fnret; + // #35: sizeof(fnret) = 72 under str=24 ({str,str,*node,*node,*fnret}); was the trigger that hung w6c_ww on the hangtest — frnext overflowed into the next amalloc, breaking fnretlookup's chain walk. + let f: *fnret = amalloc(c.a, 80u64): *fnret; f.fname = d.str; f.fmod = d.nmod; f.rtype = d.lhs; @@ -21490,6 +21498,9 @@ type modent = struct { mnext: *modent, }; +// #35: sizeof(modent) = 56 under str=24 ({str,str,*modent}); each +// of the four amallocs below grew from 48 to 64 to keep mnext inside +// the slot. fn collectmods(c: *cgen, file: *node) void = { c.mods = nil; if (file == nil) { return; }; @@ -21513,7 +21524,7 @@ fn collectmods(c: *cgen, file: *node) void = { }; if (!isffi) { if (!streq(d.str, "main")) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21525,7 +21536,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_DEF) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21536,7 +21547,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_TYPEDECL) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21547,7 +21558,7 @@ fn collectmods(c: *cgen, file: *node) void = { if (d.kind == nkind.N_LET) { if (d.exported == 0) { if (d.nmod.len > 0) { - let m: *modent = amalloc(c.a, 48u64): *modent; + let m: *modent = amalloc(c.a, 64u64): *modent; m.mname = d.str; m.nmod = d.nmod; m.mnext = c.mods; @@ -21653,7 +21664,8 @@ fn fficollect(c: *cgen, file: *node) void = { let symnode: *node = a.list; if (symnode != nil) { if (symnode.kind == nkind.N_STRLIT) { - let f: *ffi = amalloc(c.a, 48u64): *ffi; + // #35: sizeof(ffi) = 56 under str=24 ({str,str,*ffi}). + let f: *ffi = amalloc(c.a, 64u64): *ffi; f.ident = d.str; f.symbol = symnode.str; f.fnext = c.ffis;