cgen: load full 24B header on whole str/slice deref-by-value

`let s: str = *h` (a str/slice loaded by value through *str / *[]T)
fell through the N_UN deref arm to the scalar load, emitting a single
MOVQ that read only the 8B .ptr and left .len/.cap from stale registers,
so len(*p) returned garbage. Both stages emitted byte-identical wrong
code, so the self-compile byte-id gate was blind to it. Add a str/slice
arm that loads the full {ptr,len,cap} via cgslicehdr when the chased
pointee is TY_STR/TY_SLICE.

Surfaced by the codegen miscompile hunt (finding C1b). Pinned by
test/lang/deref_hdr_test.ww, which interposes a different-sized decoy
header so the test reddens when the arm is reverted.
This commit is contained in:
2026-06-27 11:47:54 +09:00
parent b855b3bdbc
commit c67f362bbd
3 changed files with 105 additions and 0 deletions

View File

@@ -4748,6 +4748,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
if (ru && ru->kind == TY_TAGGED
&& !ru->nullable && ru->size > 8)
break;
/* C1b: a whole str/slice loaded BY VALUE
* through *str / *[]T — AX (the operand value) IS
* the 24B {ptr,len,cap} header address. The scalar
* load below pulled ONLY word0 (.ptr); .len/.cap
* were then stored from stale BX/CX, so len(*p) read
* garbage — byte-id-blind on both stages. Reuse the
* same 3-word header load as the slice-FIELD /
* N_INDEX str-element arms (cgslicehdr). */
if (ru && (ru->kind == TY_STR
|| ru->kind == TY_SLICE)) {
cgslicehdr(c, D_AX);
break;
}
}
/* f64/f32 result rides X0 (SSE), not AX — an integer
* MOVQ strands the value off the float ABI and the