w6c+selfhost: localloadop helper for sign-aware ident loads (closes #19)

Read-side fix dual to fldloadop: signed-narrow local/global ident loads
now MOVSXD/MOVSWQ/MOVSBQ from the slot instead of raw MOVQ. Deref-stores
(MOVL/MOVW/MOVB) no longer corrupt downstream i64 widens. Compound RMW
restructured to gate direct-mem ADDQ/SUBQ on load_op == MOVQ. Top-level
lets use LEAQ+indirect (w6a doesn't expose MOVSXD/MOVSWQ/MOVSBQ for
D_EXTERN).

dotchainresolve out-params restored to natural *i32 (workaround retired).
selfhost/CLAUDE.md graduated.
This commit is contained in:
2026-05-14 01:58:52 +09:00
parent 5f87c60e6c
commit d2f4659305
7 changed files with 728 additions and 281 deletions

View File

@@ -185,8 +185,7 @@ static const struct row rows[] = {
* widens all numeric out-params to *i64 as a workaround until
* #19 lands. This row pins the WORKAROUND: out-param typed
* *i64, caller reads i64 → MOVQ store/load pair agree, -16
* round-trips intact. When #19 lands, add a sibling row that
* exercises the natural `*p: *i32` shape and expects -16. */
* round-trips intact. */
{ "i64_out_param_neg_widen_deref_workaround",
"fn setneg(p: *i64) void = { *p = -16i64; };\n"
"fn main() i32 = {\n"
@@ -196,6 +195,95 @@ static const struct row rows[] = {
" return 0;\n"
"};\n",
42 },
/* Task #19 fix. Natural `*p: *i32` shape — `*p = -16i32` lowers
* to MOVL (correct 4B store to a 4B pointee), and the caller's
* later i64-widening read must MOVSXD the slot so the sign bit
* propagates. Without the fix, the MOVQ read of the slot returns
* 0x00000000_FFFFFFF0 (4294967280) and the equality fails. */
{ "i32_out_param_neg_widen_deref",
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
"fn main() i32 = {\n"
" let x: i32 = 0i32;\n"
" setneg(&x);\n"
" let z: i64 = x: i64;\n"
" if (z == -16i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Task #19 fix, i16 sibling — `*p: *i16 = -16i16` writes 2B,
* caller widens to i64. Without MOVSWQ the read sees 0xFFF0
* (65520). */
{ "i16_out_param_neg_widen_deref",
"fn setneg(p: *i16) void = { *p = -16i64: i16; };\n"
"fn main() i32 = {\n"
" let x: i16 = 0i16;\n"
" setneg(&x);\n"
" let z: i64 = x: i64;\n"
" if (z == -16i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Task #19 fix, i8 sibling — `*p: *i8 = -16i8` writes 1B, caller
* widens to i64. Without MOVSBQ the read sees 0xF0 (240). */
{ "i8_out_param_neg_widen_deref",
"fn setneg(p: *i8) void = { *p = -16i64: i8; };\n"
"fn main() i32 = {\n"
" let x: i8 = 0i8;\n"
" setneg(&x);\n"
" let z: i64 = x: i64;\n"
" if (z == -16i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Task #19 fix, top-level let target. Same shape but the local
* `x` is replaced by a top-level let so the read goes through
* the RIP-relative path. Without the fix the read is MOVQ from
* &x(SB) and sees zero-extended garbage. */
{ "i32_global_neg_widen_deref",
"let g: i32 = 0i32;\n"
"fn setneg(p: *i32) void = { *p = -16i32; };\n"
"fn main() i32 = {\n"
" setneg(&g);\n"
" let z: i64 = g: i64;\n"
" if (z == -16i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Task #19 fix, compound RMW on a narrow local that was last
* written via a deref-store. Pins the cgassign restructure that
* routes the load half of `x OP= v` through localloadop (so the
* upper bytes feeding the combine come from a sign-extending
* load, not raw 8B off the slot) and gates the direct-mem
* ADDQ/SUBQ shortcut on `load_op == MOVQ`. Reads x as i32 to
* exercise the in-band path without an explicit i64 widen. */
{ "i32_compound_rmw_after_deref_store",
"fn setv(p: *i32, v: i32) void = { *p = v; };\n"
"fn main() i32 = {\n"
" let x: i32 = 0i32;\n"
" setv(&x, 0x7FFFFFFEi32);\n"
" x += 1i32;\n"
" if (x == 0x7FFFFFFFi32) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Task #19 fix, aliased narrow read. `type myinv = !i32` wraps
* i32 with the err flag — its tnode is N_TBANG(N_TNAME("i32")).
* Pins the TBANG/TENUM/TNAME → aliaslookup loop inside the
* wwstage `localloadop`: without it `fieldsize` falls back to 8
* on the bare alias name and the read picks MOVQ, so the deref-
* stored -16 round-trips as 4294967280 instead of -16. cstage
* resolves the alias through Type.size and type_isunsigned. */
{ "alias_bang_i32_widen_deref",
"type myinv = !i32;\n"
"fn setneg(p: *myinv) void = { *p = -16i64: myinv; };\n"
"fn main() i32 = {\n"
" let x: myinv = 0i64: myinv;\n"
" setneg(&x);\n"
" let z: i64 = x: i64;\n"
" if (z == -16i64) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
};
static int