Files
ww/test/wcc/681_arr_elem_field_write.c
Hojun-Cho 8b23ff3517 cgen: sub-slice cap = base_cap - lo
A sub-slice `base[lo:hi]` now sets cap to base_cap - lo (the storage
remaining to the underlying end; Go/Hare-identical) instead of hi - lo
(== len). base_cap is the array length N for [N]T, or the .capacity
word carried in a slice/str header at +16. Authored once per stage in
the cg_base_cap / cgbasecap helper, applied at both cap sites: the
N_SLICE value path (which serves let-init since the prior commit) and
the call-arg push. Both stages stay byte-identical (find-4 closed).

cap arithmetic per ref/harec/src/eval.c:1017 (slice: slice.cap -=
start) and eval.c:1024 (array: cap = array.length - start); capacity
is a distinct field per ref/hare/rt/ensure.ha:4-8 and cap >= len per
ref/harec/src/check.c:596. Only the cap arithmetic transfers: the ptr
stays unscaled (lo*esz is #76) and eval.c's stricter start>=end bound
is not ported (ww's runtime bound is start>end).

str[lo:hi] yields str with a real .capacity (D1), so the str base uses
the same +16 load -- no downgrade to []u8. base_cap falls back to len
(prior behavior) where it isn't cleanly available: a non-ident base
(its header cap was discarded by cgexpr; len is likewise wrong for a
defaulted hi there, pre-existing) and a global str base (wwstage
cgslice has no global-str load, #73 -- the carve-out keeps both
stages byte-identical).

Test: 942_subslice_cap_run, table-driven over both drivers, array /
slice / str base + an append-no-realloc row, each shape chosen so
base_cap-lo != hi-lo.

Fold in three pre-existing fixtures that asserted the old cap == len
and so failed under the corrected semantics (project #20):
681_arr_elem_field_write (slice_field_value_write,
slice_field_ptr_write, slice_field_distinct_bytes),
693_dot_tagged_source (local_struct_slice_variant,
via_ptr_slice_variant, letinit_slice_roundtrip, top_level_global_slice),
and 695_match_bind_struct (slice_neg_control). Each cap word updated to
base_cap - lo: a [8]u8 base sliced at lo=0 yields cap 8 (5->8, 3->8);
distinct_bytes slices a [16]u8 at lo=0, yielding cap 16 (6->16). len /
mark / ptr assertions are unchanged -- only the cap word moved.
2026-05-25 01:59:14 +09:00

584 lines
21 KiB
C

/*
* 681_arr_elem_field_write — `arr[i].field = v` (write-side counterpart
* of 680_arr_elem_field). Both stages had a silent store-drop:
*
* cstage's chained-pointer-field-write branch (cgen.c near 1986) caught
* `[N]*Struct` writes via its `!= N_IDENT` guard but skipped `[N]Struct`
* value-arrays (TY_STRUCT element fails the TY_PTR guard).
*
* wwstage had no N_DOT(N_INDEX,...) lhs branch at all in cgassign
* (cgenexpr.ww). Both shapes silently emitted no store.
*
* Closed in task #16 by mirroring task #8's read-side N_INDEX-lhs branch
* onto the write side (fldstoreop, str/float leaf coverage, deref-or-not
* for `*Struct` vs `Struct` element).
*
* Coverage — both array shapes, scalar/sub-word/str/float leaves, dyn
* idx, and read-modify-write compound (`arr[i].x += 5`). Cstage and
* wwstage on every fixture; wwstage gated on access(X_OK).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
static int
runwait(const char *cmd)
{
int rc = system(cmd);
if (rc == -1) return -1;
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
return -1;
}
struct row { const char *label; const char *src; int want; };
static const struct row rows[] = {
/* [N]*Struct, i32 field write. Sole write into stk[0].x; read it
* back. Returns 42. */
{ "ptr_arr_i32_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.name = \"hi\"; v.x = 0;\n"
" let stk: [16]*nd; stk[0] = &v;\n"
" stk[0].x = 42;\n"
" return stk[0].x;\n"
"};\n",
42 },
/* [N]*Struct, u8 field write. Sub-word store via MOVB through
* fieldstoreop. Returns 9. */
{ "ptr_arr_u8_write",
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.tag = 0u8; v.pad = 0u8; v.x = 0;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].tag = 9u8;\n"
" return stk[0].tag: i32;\n"
"};\n",
9 },
/* [N]*Struct, str field write — 16B store of both ptr+len.
* Returns len("hello") = 5. */
{ "ptr_arr_str_write",
"type nd = struct { a: i32, b: i32, name: str };\n"
"fn main() i32 = {\n"
" let v: nd; v.a = 0; v.b = 0; v.name = \"old\";\n"
" let stk: [16]*nd; stk[0] = &v;\n"
" stk[0].name = \"hello\";\n"
" return stk[0].name.len: i32;\n"
"};\n",
5 },
/* [N]Struct, i32 field write (value-array). The cstage chained-
* pointer-field branch's TY_PTR guard skips this; without the new
* value-array path the store silently drops. Returns 50. */
{ "val_arr_i32_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[2].x = 50;\n"
" return arr[2].x;\n"
"};\n",
50 },
/* [N]Struct, u8 field write — value-array sub-word store. Returns 7. */
{ "val_arr_u8_write",
"type nd = struct { tag: u8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].tag = 7u8;\n"
" return arr[1].tag: i32;\n"
"};\n",
7 },
/* [N]Struct, str field write — value-array 16B store. Returns
* len("world") = 5. */
{ "val_arr_str_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].name = \"world\";\n"
" return arr[1].name.len: i32;\n"
"};\n",
5 },
/* Sub-word signed: write -3i8 then read back as i32. If MOVB
* stored the truncated low byte and the field's fldloadop sign-
* extends correctly, the i32 read returns -3. Returns 42 when
* the equality check passes, 0 otherwise. */
{ "ptr_arr_i8_signed_write",
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.tag = 0i8; v.pad = 0u8; v.x = 0;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].tag = -3i8;\n"
" let t: i32 = stk[0].tag: i32;\n"
" if (t == -3) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* Value-array sub-word signed: distinct from ptr_arr_i8_signed —
* this path takes LEAQ &arr[i] (no MOVQ-to-deref) and stores the
* truncated byte via fldstoreop MOVB. Read back via fldloadop
* MOVSBQ sign-extends to i32; -3 round-trips. Returns 42. */
{ "val_arr_i8_signed_write",
"type nd = struct { tag: i8, pad: u8, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].tag = -3i8;\n"
" let t: i32 = arr[1].tag: i32;\n"
" if (t == -3) { return 42; };\n"
" return 0;\n"
"};\n",
42 },
/* f64 field write through value-array — pins MOVSD from X0 into
* field offset of &arr[i]. Returns 23. */
{ "val_arr_f64_write",
"type nd = struct { x: i32, d: f64 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].d = 23.0f64;\n"
" let v: f64 = arr[1].d;\n"
" return v: i32;\n"
"};\n",
23 },
/* Dyn idx through [N]*Struct write — index isn't a literal, so
* the IMULQ path fires. Returns 99. */
{ "ptr_arr_dyn_idx_write",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let a: nd; a.name = \"a\"; a.x = 0;\n"
" let b: nd; b.name = \"b\"; b.x = 0;\n"
" let c: nd; c.name = \"c\"; c.x = 0;\n"
" let stk: [4]*nd;\n"
" stk[0] = &a; stk[1] = &b; stk[2] = &c;\n"
" let i: i32 = 2;\n"
" stk[i].x = 99;\n"
" return stk[2].x;\n"
"};\n",
99 },
/* Read-modify-write compound on [N]Struct — exercises BOTH the
* read (load old field) and the write (store combined). For
* `arr[1].x += 5` with arr[1].x = 37 → 42. */
{ "val_arr_compound_plus",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[1].x = 37;\n"
" arr[1].x += 5;\n"
" return arr[1].x;\n"
"};\n",
42 },
/* Non-PLUSEQ compound (CARETEQ) on [N]Struct — exercises the XORQ
* arm of the compound switch. Worker wired all six integer
* compound ops; this pins one of the non-PLUSEQ arms so a future
* regression in the switch table is caught. 0x2A ^ 0x14 = 0x3E
* (62), then return 62 - 20 = 42. */
{ "val_arr_compound_xor",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let arr: [4]nd;\n"
" arr[2].x = 42;\n"
" arr[2].x ^= 20;\n"
" return arr[2].x - 20;\n"
"};\n",
42 },
/* Compound through [N]*Struct — same shape but viaptr is true,
* so the address compute deref-loads (BX), and the load_op picks
* MOVSXD for i32. 10 += 22 → 32. */
{ "ptr_arr_compound_plus",
"type nd = struct { name: str, x: i32 };\n"
"fn main() i32 = {\n"
" let v: nd; v.name = \"a\"; v.x = 10;\n"
" let stk: [4]*nd; stk[0] = &v;\n"
" stk[0].x += 22;\n"
" return stk[0].x;\n"
"};\n",
32 },
/* Whole-slice rhs to struct field, direct struct local (task #24).
* cgen N_ASSIGN had a TY_STR branch that stored ptr+len at +0/+8
* but no TY_SLICE branch; the fallthrough generic store wrote only
* AX (ptr), silently dropping .len and .cap. Fixed by a parallel
* TY_SLICE branch that stores AX/BX/CX at +0/+8/+16. The bug bit
* bufio.init's `b.rbuf = rbuf` (first stdlib struct with a []u8
* field). Returns 42 when len, cap, and a trailing scalar field
* all survived the store. */
{ "slice_field_value_write",
"type bs = struct { rbuf: []u8, mark: i32 };\n"
"fn main() i32 = {\n"
" let raw: [8]u8; raw[0] = 0u8;\n"
" let rbuf: []u8 = raw[0:5];\n"
" let b: bs;\n"
" b.mark = 7;\n"
" b.rbuf = rbuf;\n"
" if (b.rbuf.len != 5) { return 1; };\n"
" if (b.rbuf.cap != 8) { return 2; };\n"
" if (b.mark != 7) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Same bug via pointer to struct — the bufio scenario exactly
* (`init(b: *bstream, rbuf: []u8) { b.rbuf = rbuf; }`). The via_ptr
* arm of the new TY_SLICE branch stages the struct addr in DX to
* avoid clobbering CX (cap). */
{ "slice_field_ptr_write",
"type bs = struct { rbuf: []u8, mark: i32 };\n"
"fn init(b: *bs, rbuf: []u8) void = {\n"
" b.rbuf = rbuf;\n"
" b.mark = 9;\n"
"};\n"
"fn main() i32 = {\n"
" let raw: [8]u8; raw[0] = 0u8;\n"
" let b: bs;\n"
" init(&b, raw[0:5]);\n"
" if (b.rbuf.len != 5) { return 1; };\n"
" if (b.rbuf.cap != 8) { return 2; };\n"
" if (b.mark != 9) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* Distinct byte-pattern check — independent verification that
* the AX/BX/CX MOVQs landed at +0/+8/+16 rather than passing by
* coincidence with neighbouring zero memory. Distinct byte
* patterns at raw[0] (0xAA) and raw[5] (0xFF) are read back
* through b.rbuf[i], which only succeeds if .ptr (AX) survived
* at +0. Trailing mark uses a distinctive value (0x33) so a
* stray CX store one slot too far is caught separately. Here
* .len=6 (hi-lo) and .cap=16 (base_cap-lo; raw is [16]u8) per
* project #20, so this row pins .ptr survival and additionally
* confirms .len and .cap land as distinct words (they no longer
* coincide), while the other rows pin .len/.cap distinctness
* from the zero default. */
{ "slice_field_distinct_bytes",
"type bs = struct { rbuf: []u8, mark: i32 };\n"
"fn main() i32 = {\n"
" let raw: [16]u8;\n"
" raw[0] = 0xAAu8; raw[1] = 0xBBu8; raw[2] = 0xCCu8;\n"
" raw[3] = 0xDDu8; raw[4] = 0xEEu8; raw[5] = 0xFFu8;\n"
" let b: bs;\n"
" b.mark = 0x33;\n"
" b.rbuf = raw[0:6];\n"
" if (b.rbuf.len != 6) { return 1; };\n"
" if (b.rbuf.cap != 16) { return 2; };\n"
" if (b.mark != 0x33) { return 3; };\n"
" if (b.rbuf[0] != 0xAAu8) { return 4; };\n"
" if (b.rbuf[5] != 0xFFu8) { return 5; };\n"
" return 42;\n"
"};\n",
42 },
/* Str-field regression for the parallel TY_STR branch right above
* the new TY_SLICE branch — pin that store path so a future refactor
* doesn't drop it. Direct struct local; `b.s = "hello"` stores
* AX/BX at +0/+8, and the trailing i32 mark survives. */
{ "str_field_value_write_regression",
"type bs = struct { s: str, mark: i32 };\n"
"fn main() i32 = {\n"
" let b: bs;\n"
" b.mark = 11;\n"
" b.s = \"hello\";\n"
" if (b.s.len != 5) { return 1; };\n"
" if (b.mark != 11) { return 2; };\n"
" return 42;\n"
"};\n",
42 },
/* Whole-struct rhs to struct field (task #25). cgen N_ASSIGN had
* special-cases for TY_STR (2 words) and TY_SLICE (3 words) but no
* TY_STRUCT branch — the fallthrough fldstoreop wrote only AX,
* dropping every trailing word. Fixed by a word-copy branch that
* runs MOVQ from rhs slot to dest field, plus a 4/1-byte ragged
* tail. The 16B row is the BORDER case: same total size as TY_STR
* but must NOT take the str branch (str field has 16B header; this
* is a 2-word struct value, structurally identical but type-
* dispatched). Distinct byte values per word so a stray store at
* the wrong offset surfaces as a misvalue, not coincidental zero. */
{ "struct_field_value_write_16B",
"type inner = struct { a: i64, b: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x1122334455667788i64;\n"
" o.b = 0x99aabbccddeeff00i64;\n"
" let x: outer;\n"
" x.mark = 0x55;\n"
" x.i = o;\n"
" if (x.i.a != 0x1122334455667788i64) { return 1; };\n"
" if (x.i.b != 0x99aabbccddeeff00i64) { return 2; };\n"
" if (x.mark != 0x55) { return 3; };\n"
" return 42;\n"
"};\n",
42 },
/* 24B struct rhs (the original #25 probe case). Three words at +0/+8/+16
* exercise the word-copy loop with no ragged tail. Distinct byte
* patterns pin each word independently. */
{ "struct_field_value_write_24B",
"type inner = struct { a: i64, b: i64, c: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" let x: outer;\n"
" x.mark = 7;\n"
" x.i = o;\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.mark != 7) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
/* 32B struct rhs — four-word copy, no ragged tail. Trailing mark in
* the outer struct catches a stray fifth-word store. */
{ "struct_field_value_write_32B",
"type inner = struct { a: i64, b: i64, c: i64, d: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" o.d = 0x3d3d3d3d3d3d3d3di64;\n"
" let x: outer;\n"
" x.mark = 0x33;\n"
" x.i = o;\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.i.d != 0x3d3d3d3d3d3d3d3di64) { return 4; };\n"
" if (x.mark != 0x33) { return 5; };\n"
" return 42;\n"
"};\n",
42 },
/* Nested struct field write through chained N_DOT spine
* (`b.inner.deep = other_deep`). Confirms #22's spine walker
* handles a TY_STRUCT terminal, not just scalar/str/slice. The
* 24B inner struct payload must survive both the spine walk and
* the word-copy. */
{ "struct_field_value_write_nested_chain",
"type deep = struct { a: i64, b: i64, c: i64 };\n"
"type mid = struct { d: deep, mark: i32 };\n"
"type outer = struct { m: mid, tag: i32 };\n"
"fn main() i32 = {\n"
" let other: deep;\n"
" other.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" other.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" other.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" let v: outer;\n"
" v.m.mark = 5;\n"
" v.tag = 9;\n"
" v.m.d = other;\n"
" if (v.m.d.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (v.m.d.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (v.m.d.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (v.m.mark != 5) { return 4; };\n"
" if (v.tag != 9) { return 5; };\n"
" return 42;\n"
"};\n",
42 },
/* Whole-struct rhs to a struct field via a *struct base — exercises
* the via_ptr arm of the new TY_STRUCT branch. Stages the dest addr
* in BX (MOVQ off(BP),BX) before iterating, then writes at fi.foff+k(BX). */
{ "struct_field_value_write_via_ptr",
"type inner = struct { a: i64, b: i64, c: i64 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn fill(x: *outer) void = {\n"
" let o: inner;\n"
" o.a = 0x0a0a0a0a0a0a0a0ai64;\n"
" o.b = 0x1b1b1b1b1b1b1b1bi64;\n"
" o.c = 0x2c2c2c2c2c2c2c2ci64;\n"
" x.mark = 7;\n"
" x.i = o;\n"
"};\n"
"fn main() i32 = {\n"
" let x: outer;\n"
" fill(&x);\n"
" if (x.i.a != 0x0a0a0a0a0a0a0a0ai64) { return 1; };\n"
" if (x.i.b != 0x1b1b1b1b1b1b1b1bi64) { return 2; };\n"
" if (x.i.c != 0x2c2c2c2c2c2c2c2ci64) { return 3; };\n"
" if (x.mark != 7) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
/* Ragged tail — 12B inner struct (3 i32 fields, maxalign=4, no 8B
* round-up) lands the word-copy loop at k=8 with 4 bytes left and
* pins the MOVL tail branch. Tail-only structs are reachable for
* any TY_STRUCT whose maxalign < 8: this is the smallest such
* shape. The trailing i32 mark in the outer struct catches a stray
* MOVQ tail (which would overwrite +4 of the mark slot). The 5/3/2
* byte tails (struct of just i8 fields) fall back to MOVQ in cgen
* and would overwrite past the field boundary; the language permits
* align==1 structs but they're not exercised here — filed as a
* sub-followup if a real callsite surfaces. */
{ "struct_field_value_write_ragged_tail_12B",
"type inner = struct { a: i32, b: i32, c: i32 };\n"
"type outer = struct { i: inner, mark: i32 };\n"
"fn main() i32 = {\n"
" let o: inner;\n"
" o.a = 0x11223344;\n"
" o.b = 0x55667788;\n"
" o.c = 0x29aabbcc;\n"
" let x: outer;\n"
" x.mark = 0x33;\n"
" x.i = o;\n"
" if (x.i.a != 0x11223344) { return 1; };\n"
" if (x.i.b != 0x55667788) { return 2; };\n"
" if (x.i.c != 0x29aabbcc) { return 3; };\n"
" if (x.mark != 0x33) { return 4; };\n"
" return 42;\n"
"};\n",
42 },
/* Whole-tagged rhs to struct tagged-union field (task #26). cgen
* N_ASSIGN had a TY_TAGGED branch that called cg_tag_for_variant
* with the rhs type — when the rhs was the tagged union itself
* (not a concrete variant), the function returned -1 and the
* branch wrote literal 0 as the tag plus only AX as the payload,
* dropping the original tag and trailing payload words. Fixed
* by routing through cg_widen_tagged_store, which copies all slot
* words from the source's tagged slot and runs cg_widen_tag_remap.
*
* Verification: the write must not bleed into the trailing `mark`
* field. Direct struct local. Reading x.e back via match is a
* separate code path (gated on a wwstage read-side bug for
* tagged N_DOT — filed below as a follow-up); these rows pin the
* write-side bug and let mark act as a canary for stray stores
* past the field boundary. */
{ "tagged_field_value_write_local_mark_canary",
"type ev = (i64 | i32);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn main() i32 = {\n"
" let v: ev = 33: i32;\n"
" let x: holder;\n"
" x.mark = 0x3a3a3a3a;\n"
" x.e = v;\n"
" if (x.mark != 0x3a3a3a3a) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Same shape, concrete-variant str widening. The str payload
* occupies +8/+16 (ptr/len); a missing store to +16 (the pre-#26
* shape that wrote only AX) would leave .len at whatever the
* neighbouring memory held. mark canary at +24 catches a stray
* AX-only path that wrote past 16B into the mark slot. */
{ "tagged_field_value_write_str_variant_mark_canary",
"type ev = (i32 | str);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn main() i32 = {\n"
" let x: holder;\n"
" x.mark = 0x5e5e5e5e;\n"
" x.e = (\"hello\": ev);\n"
" if (x.mark != 0x5e5e5e5e) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
/* Pointer-rooted dst — `(*p).e = v` exercises the base-reg
* parameterisation added to cg_widen_tagged_store in #26. The
* wrapper spills BX (the *struct pointer), routes the body
* through a BP-rooted scratch, reloads BX, and word-copies the
* scratch to (BX, foff). Without that path the via_ptr arm of
* the new TY_TAGGED branch would either trample BX during cgexpr
* or write to a stale slot address. mark canary at +24 catches
* a stray spill that wrote past the e field. */
{ "tagged_field_value_write_via_ptr_mark_canary",
"type ev = (i64 | i32);\n"
"type holder = struct { e: ev, mark: i32 };\n"
"fn fill(h: *holder) void = {\n"
" let v: ev = 33: i32;\n"
" h.mark = 0x77777777;\n"
" h.e = v;\n"
"};\n"
"fn main() i32 = {\n"
" let x: holder;\n"
" fill(&x);\n"
" if (x.mark != 0x77777777) { return 1; };\n"
" return 42;\n"
"};\n",
42 },
};
static int
run_driver(const char *driver, const struct row *r, int i)
{
char src[64], tmpdir[64], cmd[1024];
snprintf(src, sizeof src, "/tmp/waew_%d_%d.ww", getpid(), i);
snprintf(tmpdir, sizeof tmpdir, "/tmp/waew_%d_d_%d", getpid(), i);
FILE *f = fopen(src, "wb");
if (!f) return -1;
fputs(r->src, f);
fclose(f);
mkdir(tmpdir, 0755);
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
tmpdir, driver, src);
if (runwait(cmd) != 0) {
fprintf(stderr, "row[%s]: build via %s failed\n",
r->label, driver);
unlink(src); rmdir(tmpdir);
return -1;
}
const char *base = strrchr(src, '/');
base = base ? base + 1 : src;
char outbin[128];
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
char *dot = strrchr(outbin, '.');
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
int got = runwait(outbin);
unlink(src); unlink(outbin); rmdir(tmpdir);
return got;
}
int
main(void)
{
const char *bin = getenv("BIN");
if (!bin) bin = "out/bin";
char absbin[1024];
if (bin[0] != '/') {
char cwd[1024];
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
bin = absbin;
}
char cdrv[1024];
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
char wdrv[1024];
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
struct { const char *name; const char *path; int gated_on_existence; }
drivers[] = {
{ "cstage", cdrv, 0 },
{ "wwstage", wdrv, 1 },
{ NULL, NULL, 0 },
};
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int d = 0; drivers[d].name; d++) {
if (drivers[d].gated_on_existence
&& access(drivers[d].path, X_OK) != 0) {
fprintf(stderr, "arr_elem_field_write: skip %s (no %s)\n",
drivers[d].name, drivers[d].path);
continue;
}
for (int i = 0; i < n; i++) {
int got = run_driver(drivers[d].path, &rows[i], i);
total++;
if (got != rows[i].want) {
fprintf(stderr,
"arr_elem_field_write[%s][%s]: exit=%d want=%d\n",
drivers[d].name, rows[i].label,
got, rows[i].want);
fail++;
}
}
}
if (fail) {
fprintf(stderr,
"arr_elem_field_write: %d/%d fixtures failed\n", fail, total);
return 1;
}
printf("arr_elem_field_write: %d/%d ok\n", total, total);
return 0;
}