Files
ww/test/lang/glob_ptr_field_test.ww
Hojun-Cho 214ced303f wwstage: displacement store for global-ptr scalar field, align to cstage
cgassign had dedicated N_DOT-store arms for a local-ptr base, a global
value-struct, and chained bases, but none for a global-pointer scalar
field. That case fell through to the generic cgplaceaddr/dotchainaddr
route, which folds the field offset (ADDQ $foff,BX) then stores to (BX).
cstage emits a single displacement store (MOVQ AX,foff(BX)) via its
via_ptr global scalar arm, so the two stages diverged on asm shape
(rule 10). Both forms are runtime-correct here -- BX is a fresh throwaway
in the generic route -- so this was a byte-id divergence, not a
miscompile.

Add the missing displacement-store arm, predicate-mirroring cstage's
via_ptr global scalar arm exactly: plain assignment only, scalar field
only; non-scalar field types stay on the generic path (their global-ptr
deref is a separate deferred item). glob_ptr_field_test.ww gains an
off-8 row as the regression pin -- offset-0 cannot catch it because
ADDQ $0 is suppressed.

Surfaced by the fold-2 Fam-5 migration.
2026-06-22 14:56:11 +09:00

41 lines
1.7 KiB
Plaintext

// glob_ptr_field_test — a scalar field store through a module-GLOBAL `*struct`
// pointer (`gp.f = v`) must load the pointer value from gp(SB), not deref the
// saved-BP word, migrated from test/wcc/989_globptrfield_run.c (inverse-set
// #47, report-item [58]). cstage's N_DOT-lhs via_ptr scalar store loaded the
// base with `MOVQ boff(BP),BX`; for a module-global base boff==0, so it emitted
// `MOVQ (BP),BX` — derefing saved BP as the pointer → store through garbage →
// SEGV. wwstage was already correct (LEAQ gp(SB),BX; MOVQ (BX),BX); the cstage
// half aligned UP and the .s is byte-identical, so a behavioral @test is the
// net. backing is a fresh (zeroed) global; a store that lands in garbage never
// writes the symbol, so a read-back of backing's field catches the miss. The
// static-init form `let gp: *S = &backing;` is #48-blocked, so the runtime
// `gp = &backing` form is used (as in the C twin).
package glob_ptr_field_test;
type S = struct { f: i64, g: i64 };
let backing: S = S { f = 0, g = 0 };
let gp: *S = nil;
@test fn store_off0() void = {
// the exact #58 repro: field at offset 0 through the global pointer.
gp = &backing;
gp.f = 7;
assert(backing.f: i32 == 7);
};
@test fn store_off8() void = {
// #8 (rule-10): a NON-zero-offset field (g at +8) through the global
// pointer. wwstage folded the offset into BX (`ADDQ $8,BX` + plain
// `MOVQ AX,(BX)`) while cstage emitted the displacement store
// `MOVQ AX,8(BX)` — a cs!=ww .s divergence T2 catches. Offset-0 rows
// CANNOT pin this (`ADDQ $0` is suppressed). backing.g is poisoned
// non-zero first, then overwritten via gp.g, so a store that lands at
// the wrong displacement is caught on read-back.
gp = &backing;
backing.g = 11;
gp.g = 7;
assert(backing.g: i32 == 7);
};