/* * 690_amp_dot — address-of through a DOT chain. * * cstage's TK_AMP early-exit historically only handled `&ident` and * `&base[i]`; everything else fell through to a silent-drop fallback, * so `&o.i.a` left AX undefined (not even the value — undefined). * Filed as task #9 from worker-chained-dot judgement #3. Pinned here: * * - single-DOT `&o.f` on a value-struct local AND a global root, * - chained `&o.i.a` on a value-struct (depth 2), * - 3-deep `&o.a.b.c` (confirms the walker is loop-shaped, not * hardcoded to depth 2), * - pointer-field `&p.f` where p:*T, * - slice-header `&s.len`: write through it (`*&s.len = 0;`) and * read back via `s.len`. This is the motivating Hare-slice-header * poke pattern — the whole reason this gap got filed. * - address agrees with read: `*&o.i.a == o.i.a` round-trips so the * spine walk's offset arithmetic matches the read path's. * * Exercises both stages via `ww` (cstage) and `ww_ww` (wwstage) when * present. */ #include #include #include #include #include #include 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[] = { /* &o.f single-DOT on a value-struct local. Round-trips a write * through the address, returns the field directly. */ { "amp_dot_single_local", "type pt = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let o: pt;\n" " let p: *i32 = &o.x;\n" " *p = 21;\n" " return o.x;\n" "};\n", 21 }, /* &g.f single-DOT on a global (top-level let) struct root. * Pins the LEAQ name(SB), CX → LEAQ disp(CX), AX form. */ { "amp_dot_single_global", "type pt = struct { x: i32, y: i32 };\n" "let g: pt;\n" "fn main() i32 = {\n" " let p: *i32 = &g.x;\n" " *p = 33;\n" " return g.x;\n" "};\n", 33 }, /* &o.i.a — Drew's chained value-struct shape. Address-of side * of task #6's read fix. */ { "amp_dot_chain_2deep", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn main() i32 = {\n" " let o: outer;\n" " let p: *i32 = &o.i.a;\n" " *p = 17;\n" " return o.i.a;\n" "};\n", 17 }, /* 3-deep chain: confirms the spine walker is loop-shaped, not * hardcoded to depth 2. Same shape as 650's value_struct_3deep * but going through &. */ { "amp_dot_chain_3deep", "type a3 = struct { a: i32 };\n" "type a2 = struct { a: a3 };\n" "type a1 = struct { a: a2 };\n" "fn main() i32 = {\n" " let v: a1;\n" " let p: *i32 = &v.a.a.a;\n" " *p = 9;\n" " return v.a.a.a;\n" "};\n", 9 }, /* &p.f where p:*T (pointer-field). Spine walker aborts on the * *T base; the pointer-field fallback should fire. */ { "amp_dot_ptr_field", "type pt = struct { x: i32, y: i32 };\n" "fn main() i32 = {\n" " let o: pt;\n" " o.x = 0; o.y = 0;\n" " let p: *pt = &o;\n" " let q: *i32 = &p.y;\n" " *q = 55;\n" " return o.y;\n" "};\n", 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. 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" " let s: []u8;\n" " s.ptr = &arr[0];\n" " s.len = 4;\n" " s.cap = 4;\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 * slice_delta fold composes with the struct-field offset; the * write through `*&o.s.ptr` must land at the slice header's * ptr slot inside the enclosing struct. */ { "amp_dot_slice_field_ptr", "type wrap = struct { s: []u8, x: i32 };\n" "fn main() i32 = {\n" " let arr: [4]u8;\n" " let w: wrap;\n" " w.s.len = 4; w.s.cap = 4;\n" " let pp: **u8 = &w.s.ptr;\n" " *pp = &arr[0];\n" " arr[0] = 71;\n" " return w.s.ptr[0]: i32;\n" "};\n", 71 }, /* Address agrees with read: `*&o.i.a == o.i.a`. The deref load * of the address must reproduce the same value the read path * lowers — proves the spine walker's offset sum is identical * across both directions. */ { "amp_dot_addr_eq_read", "type inner = struct { a: i32, b: i32 };\n" "type outer = struct { i: inner, x: i32 };\n" "fn main() i32 = {\n" " let o: outer;\n" " o.i.a = 41;\n" " let p: *i32 = &o.i.a;\n" " if (*p == o.i.a) { return 42; };\n" " return 0;\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/wad_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/wad_%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[1100]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[1100]; 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, "amp_dot: 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, "amp_dot[%s][%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "amp_dot: %d/%d fixtures failed\n", fail, total); return 1; } printf("amp_dot: %d/%d ok\n", total, total); return 0; }