wcc: &s.len / &s.cap typed as *i64 (closes #13)

TK_AMP early-exit on slice/str .len/.cap pseudo-fields now returns
*i64 instead of legacy *i32. Slice ABI is 24B fixed; LEAQ at the slot
was already correct, only the pointer typing was wrong — store-width
flips from MOVL to MOVQ via the existing primsize-from-tnode path.
&s.ptr untouched (already **T).
This commit is contained in:
2026-05-14 00:21:53 +09:00
parent 6354f5267c
commit f2b47087a2
2 changed files with 109 additions and 4 deletions

View File

@@ -102,8 +102,10 @@ static const struct row rows[] = {
55 },
/* The motivating idiom: write through `&s.len` on a slice header
* to truncate without re-allocating. Mirrors the Hare slice-
* header poke pattern that bufio will eventually want. Test:
* fill a slice's len to 5, then *&s.len = 0; assert s.len == 0. */
* header poke pattern that bufio will eventually want. The slice
* header's .len slot is 8B even though the surface type is i32,
* so `&s.len` is `*i64` (matches storage); deref-store hits all
* 8B, and the i64 load reads back the value the user wrote. */
{ "amp_dot_slice_len_writethrough",
"fn main() i32 = {\n"
" let arr: [4]u8;\n"
@@ -111,11 +113,97 @@ static const struct row rows[] = {
" s.ptr = &arr[0];\n"
" s.len = 4;\n"
" s.cap = 4;\n"
" let q: *i32 = &s.len;\n"
" *q = 0;\n"
" let q: *i64 = &s.len;\n"
" *q = 0i64;\n"
" return s.len: i32;\n"
"};\n",
0 },
/* Width-stress for `&s.len`: write a value whose lower-32B
* differs from upper-32B and confirm the upper bytes don't
* leak from the prior 8B store of `s.len = 4`. Before the fix,
* `&s.len` was `*i32` and `*q = v` lowered to MOVL, leaving
* the upper 4B at whatever the MOVQ store of 4 left there
* (zero — pass by accident). With a non-zero stale upper or a
* fresh write that fills both halves, the i64 read of s.len
* exposes the mismatch. We write 0xFFFF_FFFF_FFFF_FFFF and
* return 1 iff s.len reads back as -1 (i64). */
{ "amp_dot_slice_len_width_stress",
"fn main() i32 = {\n"
" let arr: [4]u8;\n"
" let s: []u8;\n"
" s.ptr = &arr[0];\n"
" s.len = 4;\n"
" s.cap = 4;\n"
" let q: *i64 = &s.len;\n"
" *q = -1i64;\n"
" let v: i64 = s.len: i64;\n"
" if (v == -1i64) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
/* Same shape for `&s.cap`. cap lives at +16 in the slice header
* and was the second pseudo-field broken by the same width bug. */
{ "amp_dot_slice_cap_width_stress",
"fn main() i32 = {\n"
" let arr: [4]u8;\n"
" let s: []u8;\n"
" s.ptr = &arr[0];\n"
" s.len = 4;\n"
" s.cap = 4;\n"
" let q: *i64 = &s.cap;\n"
" *q = -1i64;\n"
" let v: i64 = s.cap: i64;\n"
" if (v == -1i64) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
/* Same shape for str.len. str header is (ptr, len) with len at
* +8, also 8B storage. */
{ "amp_dot_str_len_width_stress",
"fn main() i32 = {\n"
" let s: str = \"abcd\";\n"
" let q: *i64 = &s.len;\n"
" *q = -1i64;\n"
" let v: i64 = s.len: i64;\n"
" if (v == -1i64) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
/* `&s.ptr` write-through. ptr lives at +0 and is `*T` (not a
* pseudo-i32), so `&s.ptr` types as `**T` — already the right
* width pre-fix. Pin it so a future regression doesn't slip the
* other way. */
{ "amp_dot_slice_ptr_writethrough",
"fn main() i32 = {\n"
" let arr: [2]u8;\n"
" arr[0] = 13; arr[1] = 0;\n"
" let s: []u8;\n"
" s.len = 2; s.cap = 2;\n"
" let pp: **u8 = &s.ptr;\n"
" *pp = &arr[0];\n"
" return s.ptr[0]: i32;\n"
"};\n",
13 },
/* Chained `&w.s.len` — slice WRAPPED in a struct. The pseudo-
* field width override gates on the leaf .len/.cap with base
* type TY_SLICE/TY_STR; for `&w.s.len` the base of the leaf
* dot is `w.s` (type []u8), so the override must still fire
* and the deref-store must hit all 8B at the wrapped slice
* header's len slot (offset of .s + 8). */
{ "amp_dot_slice_field_len_width",
"type wrap = struct { s: []u8, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]u8;\n"
" let w: wrap;\n"
" w.s.ptr = &arr[0];\n"
" w.s.len = 4; w.s.cap = 4;\n"
" let q: *i64 = &w.s.len;\n"
" *q = -1i64;\n"
" let v: i64 = w.s.len: i64;\n"
" if (v == -1i64) { return 1; };\n"
" return 0;\n"
"};\n",
1 },
/* &o.s.ptr — slice-FIELD of a struct: combines shape 1 (walk
* into struct field at .s) and shape 3 (slice pseudo-tail
* .ptr at offset 0 of the header). Proves the spine walker's