/* * 713_struct_field_index — wwstage N_INDEX through a struct-field base * carries the element type. Three sister gaps in wwstage cgen's untyped * AST defaulted to esz=8 / scalar-push when the base was N_DOT with the * field a *T or []T. Surfaced by getopttest wwstage build (#37 * investigation, originally misattributed as a symbol-mangling bug). * * Pre-fix sites (selfhost/cmd/wcc/): * * 1. cgenutil.ww indexbaseesz `.ptr` pseudo-field on a slice field: * `&p.ptr[i]` where p: *[]S fell through `elemsizeof(innert)` * which returned 8 (named-struct element), not sizeof(S). So * `IMULQ $8, AX` instead of `IMULQ $24, AX` — every cross-element * addressing landed inside element 0. Fix: route slice case * through elemsizeofc so structlookup resolves the stride. * * 2. cgenexpr.ww cgun TK_AMP N_INDEX with N_DOT base: only * base.kind == N_IDENT was handled; N_DOT base left esz=8 from * the function-top default. `&p.ptr[i]` for any p.ptr (slice ptr * or *T field) hit this. Fix: add the N_DOT arm calling * indexbaseesz. * * 3. cgenutil.ww nodeisstr N_INDEX with N_DOT base: only N_IDENT * base was handled; `cmd.argsptr[i]` (argsptr: *str) returned * false, so pushargsrev pushed only AX and the call's str arg * lost its .len half to stack residue. Sister to #34's * [N]str-through-N_IDENT fix. Fix: walk the struct field's * element type and return isstrtype. * * All three are the same root pattern as #34 / #36: the untyped AST * loses the type through a struct field, so the per-shape recognizer * has to walk N_DOT → struct → field → element manually. A typed AST * (#11 wwstage check pass) would replace all three with one query. * * Rows are runtime-only (no asm byte-id): wwstage's local layout * legitimately differs from cstage's (slot offsets shift by 8), and * 995_self_rebuild already covers cross-stage drift on driver corpora. * The semantic invariant — values round-trip correctly — is what we * pin here. * * row | what it pins * -----------------------------+---------------------------------- * amp_ptr_index_struct_stride | &p.ptr[i] esz = sizeof(S)=24, * | not 8 (fix 1+2) * dot_ptr_index_str_arg | cmd.argsptr[i] as str arg pushes * | both halves (fix 3) * amp_ptr_index_str_pair | &p.ptr[i] esz=16 for *[]str — * | sister regression-pin to fix 1 */ #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[] = { /* 1. `&p.ptr[i]` where p: *[]S, S size 24 (i32 + str). Pre-fix * wwstage: IMULQ $8 → store landed inside element 0. Post-fix: * IMULQ $24 → store lands in element i. Reads back element 1's * `a` = 22 (mod 256). */ { "amp_ptr_index_struct_stride", "type S = struct { a: i32, b: str };\n" "fn main() i32 = {\n" " let buf: [3]S;\n" " buf[0].a = 10i32; buf[0].b = \"x\";\n" " buf[1].a = 22i32; buf[1].b = \"yy\";\n" " buf[2].a = 30i32; buf[2].b = \"zzz\";\n" " let xs: []S;\n" " xs.ptr = &buf[0];\n" " xs.len = 3;\n" " xs.cap = 3;\n" " let p: *[]S = &xs;\n" " let q: *S = &p.ptr[1];\n" " return q.a;\n" "};\n", 22 }, /* 2. `cmd.argsptr[i]` where argsptr: *str passed as str arg. * Pre-fix wwstage: nodeisstr returned false → call-arg push * dropped .len → streq received .len from stack residue → no * match. Post-fix: full str round-trip. */ { "dot_ptr_index_str_arg", "fn streq(a: str, b: str) i32 = {\n" " if (a.len != b.len) { return 0i32; };\n" " let i: i32 = 0;\n" " for (i < a.len) {\n" " if (a[i] != b[i]) { return 0i32; };\n" " i += 1;\n" " };\n" " return 1i32;\n" "};\n" "type cmd = struct { argsptr: *str, argslen: i32 };\n" "fn main() i32 = {\n" " let xs: [2]str;\n" " xs[0] = \"first\";\n" " xs[1] = \"second\";\n" " let c: cmd;\n" " c.argsptr = &xs[0];\n" " c.argslen = 2;\n" " if (streq(c.argsptr[0], \"first\") == 0i32) { return 1; };\n" " if (streq(c.argsptr[1], \"second\") == 0i32) { return 2; };\n" " return 0;\n" "};\n", 0 }, /* 3. `&p.ptr[i]` for *[]str — sister to row 1 with str (16B) * elements. Reads the .len half of element 1 to assert the * stride lands on the right slot. Pre-fix cgun N_DOT-base * (fix 2) left esz=8 even though elemsizeof would have returned * 16 for str directly, so `&p.ptr[1]` pointed at element 0's * `.len` half instead of element 1's `.ptr`. element 1's .len * = 2 ("yy"). */ { "amp_ptr_index_str_pair", "fn main() i32 = {\n" " let buf: [3]str;\n" " buf[0] = \"a\";\n" " buf[1] = \"yy\";\n" " buf[2] = \"zzz\";\n" " let xs: []str;\n" " xs.ptr = &buf[0];\n" " xs.len = 3;\n" " xs.cap = 3;\n" " let p: *[]str = &xs;\n" " let q: *str = &p.ptr[1];\n" " return q.len: i32;\n" "};\n", 2 }, }; 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/wcrsfi_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/wcrsfi_%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[512]; if (bin[0] != '/') { char cwd[256]; if (getcwd(cwd, sizeof cwd) == NULL) return 1; snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin); bin = absbin; } char cdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); char wdrv[640]; 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, "struct_field_index: 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, "struct_field_index[%s] row[%s]: exit=%d want=%d\n", drivers[d].name, rows[i].label, got, rows[i].want); fail++; } } } if (fail) { fprintf(stderr, "struct_field_index: %d/%d row(s) failed\n", fail, total); return 1; } printf("struct_field_index: %d/%d ok\n", total, total); return 0; }