/* * 756_alias_chain_unwrap — sentinel for #22 alias-chain SRET/N_DOT * wedge. Pre-fix both stages single-peeled `if (t->kind == TY_NAMED) * t = t->under` in cg_sret_retsize / N_LET sizing / cgreturn / N_DOT * field-access (cstage) and structlookup-on-N_TNAME (wwstage); a * `type b = a;` over an alias-of-struct stacked two TY_NAMED layers * and the single peel bottomed out at the inner alias — still * TY_NAMED, not TY_STRUCT. Outcome: SRET-shaped returns silently * routed through the scalar-AX ABI (caller's receive slot * corrupted), let-init slots under-sized, and `s.field` reads * collapsed to base+0 instead of base+field_offset. * * Polarity (rule 10, mutual-symmetric): cstage's hot-path peels * graduated to a transitive `while (t->kind == TY_NAMED) t = t->under` * walk; wwstage's structlookup-misses fall through to aliaslookup + * recurse, mirroring slotsize's existing N_TNAME arm (cgenutil.ww * line 1955). The strings.tokenize wrapper shape (`type tokenizer = * bytes::tokenizer;`) is the original surfacing site (#22). * * row | shape | gate * ---------------------+----------------------------------------+-------- * r1_i32_struct | direct TY_STRUCT, no alias | works pre-fix * r2_slice_single | `type a = struct{[]u8,[]u8,i64}` | wedge pre-fix (RC=12) * | aliased once → `type b = a;` | * r3_slice_direct | direct struct, slice fields | works pre-fix * r4_i32_double | `type b = a; type a = struct{i32...}` | wedge pre-fix (RC=14) * | aliased twice → `type c = b;` | * * Each row asserts: * - cstage RC == 0 * - wwstage RC == 0 (when w6c_ww present) * - cstage and wwstage emit byte-identical asm * * Pre-fix: rows 2 and 4 fail the RC check on both stages and the * byte-id is also broken on rows 2/4 (one stage emits sret discipline, * the other emits the scalar-AX fall-through). Post-fix all 12 * fixtures pass. */ #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; }; static const struct row rows[] = { /* r1: direct i32-fields struct, no alias. Baseline — single * TY_NAMED layer (the type itself) suffices for the existing * single-peel pattern. */ { "r1_i32_struct", "package main;\n" "type r1 = struct {\n" "\ta: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32,\n" "\th: i32, i: i32, j: i32, k: i32, l: i32, m: i32, p: i32,\n" "};\n" "fn make_r1() r1 = {\n" "\tlet b: r1;\n" "\tb.p = 99i32;\n" "\treturn b;\n" "};\n" "export fn main() i32 = {\n" "\tlet s: r1 = make_r1();\n" "\tif (s.p != 99i32) { return 11; };\n" "\treturn 0;\n" "};\n", }, /* r2: slice-fields struct aliased once. Pre-fix wedge: * `type r2_alias = r2_struct;` stacks two TY_NAMED layers, the * single peel lands on the inner alias and the SRET gate falls * through to scalar-AX. RC=12 pre-fix on both stages. */ { "r2_slice_single", "package main;\n" "type r2_struct = struct { in: []u8, delim: []u8, p: i64 };\n" "type r2_alias = r2_struct;\n" "fn make_r2() r2_alias = {\n" "\tlet b: r2_struct;\n" "\tb.p = 99i64;\n" "\treturn b;\n" "};\n" "export fn main() i32 = {\n" "\tlet s: r2_alias = make_r2();\n" "\tif (s.p != 99i64) { return 12; };\n" "\treturn 0;\n" "};\n", }, /* r3: slice-fields struct, no alias — baseline that proves the * slice-payload sret discipline itself is unaffected. */ { "r3_slice_direct", "package main;\n" "type r3 = struct { in: []u8, delim: []u8, p: i64 };\n" "fn make_r3() r3 = {\n" "\tlet b: r3;\n" "\tb.p = 99i64;\n" "\treturn b;\n" "};\n" "export fn main() i32 = {\n" "\tlet s: r3 = make_r3();\n" "\tif (s.p != 99i64) { return 13; };\n" "\treturn 0;\n" "};\n", }, /* r4: i32-fields struct double-aliased. Same wedge as r2 but * chain length 3 — extra TY_NAMED layer between the alias and * the struct. Confirms the discriminator is the chain length, * not the field shape. RC=14 pre-fix on both stages. */ { "r4_i32_double", "package main;\n" "type r4_struct = struct {\n" "\ta: i32, b: i32, c: i32, d: i32, e: i32, f: i32, g: i32,\n" "\th: i32, i: i32, j: i32, k: i32, l: i32, m: i32, p: i32,\n" "};\n" "type r4_base = r4_struct;\n" "type r4_alias = r4_base;\n" "fn make_r4() r4_alias = {\n" "\tlet b: r4_alias;\n" "\tb.p = 99i32;\n" "\treturn b;\n" "};\n" "export fn main() i32 = {\n" "\tlet s: r4_alias = make_r4();\n" "\tif (s.p != 99i32) { return 14; };\n" "\treturn 0;\n" "};\n", }, }; static int build_and_run(const char *driver, const struct row *r, int i) { char src[64], tmpdir[64], cmd[1024]; snprintf(src, sizeof src, "/tmp/acu_%d_%d.ww", getpid(), i); snprintf(tmpdir, sizeof tmpdir, "/tmp/acu_%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; } static int emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap) { char src[96], cmd[1024]; snprintf(src, sizeof src, "/tmp/acu_s_%d_%d.ww", getpid(), i); snprintf(out_s, cap, "/tmp/acu_s_%d_%d_%s.s", getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c"); FILE *f = fopen(src, "wb"); if (!f) return -1; fputs(r->src, f); fclose(f); snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src); int rc = runwait(cmd); unlink(src); return rc; } 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], wdrv[640]; snprintf(cdrv, sizeof cdrv, "%s/ww", bin); snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); char cw6c[640], ww6c[640]; snprintf(cw6c, sizeof cw6c, "%s/w6c", bin); snprintf(ww6c, sizeof ww6c, "%s/w6c_ww", bin); int have_ww = (access(wdrv, X_OK) == 0) && (access(ww6c, X_OK) == 0); int n = (int)(sizeof rows / sizeof rows[0]); int total = 0, fail = 0; for (int i = 0; i < n; i++) { /* cstage RC */ total++; int got_c = build_and_run(cdrv, &rows[i], i); if (got_c != 0) { fprintf(stderr, "alias_chain[cstage][%s]: exit=%d want=0\n", rows[i].label, got_c); fail++; } if (!have_ww) continue; /* wwstage RC */ total++; int got_w = build_and_run(wdrv, &rows[i], i); if (got_w != 0) { fprintf(stderr, "alias_chain[wwstage][%s]: exit=%d want=0\n", rows[i].label, got_w); fail++; } /* byte-id cstage vs wwstage */ total++; char cs_path[160], ws_path[160]; if (emit_s(cw6c, &rows[i], i, cs_path, sizeof cs_path) != 0 || emit_s(ww6c, &rows[i], i, ws_path, sizeof ws_path) != 0) { fprintf(stderr, "alias_chain[byte-id][%s]: emit failed\n", rows[i].label); fail++; unlink(cs_path); unlink(ws_path); continue; } char cmpcmd[512]; snprintf(cmpcmd, sizeof cmpcmd, "cmp -s %s %s", cs_path, ws_path); if (runwait(cmpcmd) != 0) { fprintf(stderr, "alias_chain[byte-id][%s]: cstage vs wwstage asm differs\n", rows[i].label); fail++; } unlink(cs_path); unlink(ws_path); } if (fail) { fprintf(stderr, "alias_chain: %d/%d fixtures failed\n", fail, total); return 1; } printf("alias_chain: %d/%d ok\n", total, total); return 0; }