diff --git a/Makefile b/Makefile index a2784ba8..c8d83569 100644 --- a/Makefile +++ b/Makefile @@ -258,6 +258,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_trystr_run \ $(BIN)/test_allocalias_run \ $(BIN)/test_defdim_field_run \ + $(BIN)/test_defdim_argslice_run \ $(BIN)/test_gunsigned_run \ $(BIN)/test_taggedidx_run \ $(BIN)/test_fnptrcollide_run \ @@ -769,6 +770,17 @@ $(BIN)/test_defdim_field_run: test/wcc/989_defdim_field_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +# 989_defdim_argslice_run (#56 c4): passing a slice of a `def`-dimensioned +# array as a call arg resolves the default-hi length from the type table in +# the N_SLICE arg-push arms (cgenutil pushargsrev local+global). Builds+runs +# on BOTH driver twins (rule-10). +$(BIN)/test_defdim_argslice_run: test/wcc/989_defdim_argslice_run.c \ + $(BIN)/ww $(BIN)/ww_ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + # 989_gunsigned_run (F7-c5, #25): a module-global unsigned ident on the # divide/shift/relational path must pick the unsigned opcode. Builds+runs on # BOTH driver twins (rule-10). CLASS-M — see the test header. diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 3b91f04e..11b942a4 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -18355,10 +18355,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { let lenn: *node = tn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim — resolve from the stamped + // array tinfo (rule-13). The #60 bu60 arm below + // covers only NAMED-alias bases (tn.kind == + // N_TNAME); a plain `[MAX]u8` base reaches here + // with a non-N_INTLIT dim and dropped the + // default-hi entirely. cstage reads bu->alen. + let abt: *tinfo = tichase(tn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; @@ -18391,10 +18402,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim on a global array base — + // resolve from the stamped array tinfo (rule-13), + // twin of the local arg-push arm above. + let abt: *tinfo = tichase(globaltn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 542e12f0..3b52b782 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -641,10 +641,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { let lenn: *node = tn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim — resolve from the stamped + // array tinfo (rule-13). The #60 bu60 arm below + // covers only NAMED-alias bases (tn.kind == + // N_TNAME); a plain `[MAX]u8` base reaches here + // with a non-N_INTLIT dim and dropped the + // default-hi entirely. cstage reads bu->alen. + let abt: *tinfo = tichase(tn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; @@ -677,10 +688,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim on a global array base — + // resolve from the stamped array tinfo (rule-13), + // twin of the local arg-push arm above. + let abt: *tinfo = tichase(globaltn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 6ff15632..2f93dd34 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -18355,10 +18355,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { if (tn != nil) { if (tn.kind == nkind.N_TARRAY) { let lenn: *node = tn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim — resolve from the stamped + // array tinfo (rule-13). The #60 bu60 arm below + // covers only NAMED-alias bases (tn.kind == + // N_TNAME); a plain `[MAX]u8` base reaches here + // with a non-N_INTLIT dim and dropped the + // default-hi entirely. cstage reads bu->alen. + let abt: *tinfo = tichase(tn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; @@ -18391,10 +18402,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node, memphase: bool) i32 = { } else { if (globaltn != nil) { if (globaltn.kind == nkind.N_TARRAY) { let lenn: *node = globaltn.rhs; - if (lenn != nil) { - if (lenn.kind == nkind.N_INTLIT) { + if (lenn != nil && lenn.kind == nkind.N_INTLIT) { + emitline("\tMOVQ\t$"); + emituint(lenn.uval); + emitline(", AX\n"); + } else { + // #56: def/const dim on a global array base — + // resolve from the stamped array tinfo (rule-13), + // twin of the local arg-push arm above. + let abt: *tinfo = tichase(globaltn.type_: *tinfo); + if (abt != nil && abt.kind == tykind.TY_ARRAY) { emitline("\tMOVQ\t$"); - emituint(lenn.uval); + emitint(abt.alen: i64); emitline(", AX\n"); }; }; diff --git a/test/wcc/989_defdim_argslice_run.c b/test/wcc/989_defdim_argslice_run.c new file mode 100644 index 00000000..a6e4379b --- /dev/null +++ b/test/wcc/989_defdim_argslice_run.c @@ -0,0 +1,165 @@ +/* + * 989_defdim_argslice_run (#56, c4) — passing a slice of a `def`-dimensioned + * array (`take(buf[1:])` on `[MAX]T`) as a call argument must resolve the + * default-hi length from the type table, the N_SLICE arg-push member of the + * def-dim family. + * + * THE BUG (wwstage only): pushargsrev's N_SLICE default-hi arms (cgenutil.ww + * local ~643 / global ~690) read the array dimension straight off the AST + * tnode and only handled an N_INTLIT dim. The #60 bu60 fallback fires ONLY for + * NAMED-alias bases (bt60.kind == TY_NAMED), so a PLAIN `[MAX]u8` base reached + * the N_INTLIT arm, emitted nothing, and the slice header's len word was left + * stale — the callee read garbage. cstage reads the resolved bu->alen. THE + * FIX: when the dim is not N_INTLIT, read alen from the stamped array tinfo + * (rule-13), in both the local and global arg-push arms — the same shape as + * the #21 cgslice and #56 cgdot fixes. + * + * row | shape | exit (cs==ww) + * --------+--------------------------------------------+-------------- + * local | def MAX=5; let buf:[MAX]u8; take(buf[1:]) | 4 (was ww 135) + * global | def MAX=5; let g:[MAX]u8; take(g[1:]) | 4 (was ww 255) + * litctrl | let buf:[5]u8; take(buf[1:]) | 4 (no regress) + * The litctrl row pins the literal-dim path so the new else-arm can't double- + * emit or perturb it. + */ +#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_exit; }; + +static const struct row rows[] = { + { "local", + "package main;\n" + "def MAX: i32 = 5;\n" + "fn take(s: []u8) i32 = { return s.len: i32; };\n" + "export fn main() i32 = {\n" + " let buf: [MAX]u8 = [1, 2, 3, 4, 5];\n" + " return take(buf[1:]);\n" + "};\n", + 4 }, + + { "global", + "package main;\n" + "def MAX: i32 = 5;\n" + "let g: [MAX]u8 = [1, 2, 3, 4, 5];\n" + "fn take(s: []u8) i32 = { return s.len: i32; };\n" + "export fn main() i32 = {\n" + " return take(g[1:]);\n" + "};\n", + 4 }, + + { "litctrl", + "package main;\n" + "fn take(s: []u8) i32 = { return s.len: i32; };\n" + "export fn main() i32 = {\n" + " let buf: [5]u8 = [1, 2, 3, 4, 5];\n" + " return take(buf[1:]);\n" + "};\n", + 4 }, +}; + +static int +run_build(const char *driver, const struct row *r, int i) +{ + char src[64], tmpdir[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/dda_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/dda_%d_d_%d", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -2; + fputs(r->src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, src); + int brc = runwait(cmd); + + 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 = -1; + if (brc == 0) got = runwait(outbin); + + unlink(src); unlink(outbin); rmdir(tmpdir); + return brc == 0 ? got : -1; +} + +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], wdrv[1024]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin); + int have_ww = (access(wdrv, X_OK) == 0); + + int n = (int)(sizeof rows / sizeof rows[0]); + int total = 0, fail = 0; + + for (int i = 0; i < n; i++) { + total++; + int gc = run_build(cdrv, &rows[i], i); + if (gc < 0) { + fprintf(stderr, "defdim_argslice[cstage][%s]: build/run failed " + "(got %d)\n", rows[i].label, gc); + fail++; + continue; + } + if (gc != rows[i].want_exit) { + fprintf(stderr, "defdim_argslice[cstage][%s]: exit=%d want=%d\n", + rows[i].label, gc, rows[i].want_exit); + fail++; + } + if (!have_ww) { + fprintf(stderr, "defdim_argslice: skip wwstage (no %s)\n", wdrv); + continue; + } + int gw = run_build(wdrv, &rows[i], i); + if (gw != gc) { + fprintf(stderr, "defdim_argslice[%s]: cs=%d != ww=%d " + "(def-dim arg-push default-hi divergence — #56 c4)\n", + rows[i].label, gc, gw); + fail++; + } + if (gw != rows[i].want_exit) { + fprintf(stderr, "defdim_argslice[wwstage][%s]: exit=%d want=%d\n", + rows[i].label, gw, rows[i].want_exit); + fail++; + } + } + + if (fail) { + fprintf(stderr, "defdim_argslice_run: %d/%d checks failed\n", + fail, total); + return 1; + } + printf("defdim_argslice_run: %d/%d ok\n", total, total); + return 0; +}