diff --git a/cmd/wcc/check.c b/cmd/wcc/check.c index a578b035..5a7d53b5 100644 --- a/cmd/wcc/check.c +++ b/cmd/wcc/check.c @@ -3193,6 +3193,10 @@ static int init_tagged_raw_static(Type *u, Node *n) { Node *r = init_strip_cast(n); + /* Cstage keeps true/false as untyped-bool, distinct from the concrete + * bool variant selected by runtime boxing. Keep boolean payloads on that + * runtime path in both stages so package artifacts remain identical. */ + if (r != NULL && (r->kind == N_TRUE || r->kind == N_FALSE)) return 0; Type *ru = type_chase_named(r ? r->type : NULL); u64 ignored; return u != NULL && u->kind == TY_TAGGED && !u->nullable @@ -3336,8 +3340,13 @@ init_expr_static(Type *t, Node *n) if (u != NULL && u->kind == TY_TAGGED && !u->nullable) { Type *ru = type_chase_named(r->type); if (!variant_present(u->params, r->type)) return 0; - if (ru != NULL && (ru->kind == TY_STR || ru->kind == TY_SLICE)) - return r->kind == N_STRLIT; + /* String carriers need a relocation-bearing tagged row. Cstage's + * untyped string is not the concrete str variant here; keep every + * such mutable value on the common runtime path rather than let one + * stage classify the same spelling as static data. */ + if (ru != NULL && (ru->kind == TY_STR + || ru->kind == TY_UNTYPED_STR || ru->kind == TY_SLICE)) + return 0; return init_tagged_raw_static(u, r); } if (init_fnptr_static(r)) return 1; @@ -3345,6 +3354,67 @@ init_expr_static(Type *t, Node *n) return fold_int_literal(r, &ignored); } +/* A slice literal has no declared element count for WW's trailing `...` + * repeat to fill. Keep this a checker-owned semantic rejection when a + * mutable package let moves from static data to runtime initialization; + * otherwise the backing counter drops the marker and silently publishes the + * explicit prefix. Array repeats remain valid because their target length is + * known. Recurse through the aggregate shapes package-init lowering owns so + * a nested slice cannot bypass the same rule. */ +static int +init_validate_slice_repeats(Checker *c, Type *want, Node *expr) +{ + Node *r = init_strip_cast(expr); + Type *u = type_chase_named(want); + if (r == NULL || u == NULL) return 0; + if (u->kind == TY_SLICE && r->kind == N_ARRLIT) { + for (Node *e = r->list; e; e = e->next) { + if (e->kind == N_FIELD && e->str != NULL + && strcmp(e->str, "...") == 0) { + err(c, e->pos, "'...' repeat has no target length " + "in a slice literal"); + return -1; + } + if (init_validate_slice_repeats(c, u->sub, e) < 0) + return -1; + } + return 0; + } + if (u->kind == TY_ARRAY && r->kind == N_ARRLIT) { + for (Node *e = r->list; e; e = e->next) { + if (e->kind == N_FIELD && e->str != NULL + && strcmp(e->str, "...") == 0) + continue; + if (init_validate_slice_repeats(c, u->sub, e) < 0) + return -1; + } + return 0; + } + if (u->kind == TY_STRUCT && r->kind == N_STRUCTLIT) { + for (Node *e = r->list; e; e = e->next) { + Tfield *field = NULL; + for (Tfield *f = u->fields; f; f = f->next) + if (e->str != NULL && f->name != NULL + && strcmp(e->str, f->name) == 0) { + field = f; + break; + } + if (field != NULL + && init_validate_slice_repeats(c, field->type, + e->lhs) < 0) + return -1; + } + return 0; + } + if (u->kind == TY_TUPLE && r->kind == N_TUPLE) { + Tparam *p = u->params; + for (Node *e = r->list; e && p; e = e->next, p = p->next) + if (init_validate_slice_repeats(c, p->type, e) < 0) + return -1; + } + return 0; +} + struct initwalkitem { Node *node; struct initwalkitem *next; @@ -3665,6 +3735,8 @@ init_lower_package(Checker *c, Node *file) return -1; } nvar++; + if (init_validate_slice_repeats(c, d->type, d->rhs) < 0) + continue; if (!init_expr_static(d->type, d->rhs)) { d->runtimeinit = 1; nruntime++; @@ -4601,8 +4673,19 @@ check_file(Checker *c, Node *file) * returns 0 silently and is left untouched). * Stamp LAST — the assignability check above * consumes the pre-stamp cexpr type. */ + /* An explicit scalar-to-tagged cast is also the carrier + * selection. Folding it to an integer literal while keeping + * the tagged result type makes cgen consume scalar registers + * as an already-wide tagged ABI value. Mutable package lets + * preserve the cast so the concrete carrier is boxed correctly; + * const remains on its established static-only path. */ + Type *foldt = type_chase_named(d->type); + int preserve_tagged_cast = d->op != TK_CONST && d->rhs + && d->rhs->kind == N_CAST && foldt != NULL + && foldt->kind == TY_TAGGED && !foldt->nullable; u64 dv; - if (d->rhs && !fold_int_literal(d->rhs, &dv) + if (d->rhs && !preserve_tagged_cast + && !fold_int_literal(d->rhs, &dv) && eval_def_const(c, d->rhs, &dv, 0)) stamp_intlit(c, d->rhs, dv); } else if (d->type && d->type->kind == TY_ARRAY diff --git a/cmd/ww/main.c b/cmd/ww/main.c index 64adc23b..ea13487a 100644 --- a/cmd/ww/main.c +++ b/cmd/ww/main.c @@ -4648,11 +4648,6 @@ build_one_sep_impl(const char *src, int entry_is_dir, && !sep_root_is_command(&g->pkg[products[0].root]); if (sep_validate_artifact_paths(g, scratch) < 0) return 1; - if (sep_validate_request_staging(g, scratch, warm, products, nproducts, - root_package, publish_package, emit_asm, is_test) < 0) { - sep_free_product_staging(products, nproducts); - return 1; - } if (warm && workdir_exists && sep_validate_workdir_owners(g, scratch) < 0) return 1; int *order = calloc((size_t)g->n, sizeof *order); @@ -4737,6 +4732,12 @@ build_one_sep_impl(const char *src, int entry_is_dir, free(order); return 1; } + if (sep_validate_request_staging(g, scratch, warm, products, nproducts, + root_package, publish_package, emit_asm, is_test) < 0) { + sep_free_product_staging(products, nproducts); + free(order); + return 1; + } /* Product completion and persistent package state remain untouched until * all source-derived imports, contextual legality, cycles, command kind, * output paths, and action closures have passed their pre-tool checks. */ diff --git a/internal/wwfixture/types.ww b/internal/wwfixture/types.ww index d577cd30..1f650427 100644 --- a/internal/wwfixture/types.ww +++ b/internal/wwfixture/types.ww @@ -2,10 +2,10 @@ package wwfixture; def protocolversion: i32 = 1; def corpuscount: i32 = 1759; -def errorcount: i32 = 351; +def errorcount: i32 = 338; def compilecount: i32 = 22; -def runcount: i32 = 209; -def runexitcount: i32 = 1177; +def runcount: i32 = 214; +def runexitcount: i32 = 1185; def nativecount: i32 = 3518; def corpushash: str = "47d731a8fd089ecdef9a1b9b94f3dfc06a733b7e03236014f095b068a67ce176"; diff --git a/selfhost/cmd/wcc/check.ww b/selfhost/cmd/wcc/check.ww index 73e3b485..486cac78 100644 --- a/selfhost/cmd/wcc/check.ww +++ b/selfhost/cmd/wcc/check.ww @@ -7731,6 +7731,11 @@ fn initfloatstatic(n: *syntax.node) bool = { fn inittaggedrawstatic(u: *syntax.tinfo, n: *syntax.node) bool = { let r: *syntax.node = initstripcast(n); + // Cstage keeps true/false as untyped-bool, distinct from the concrete + // bool variant selected by runtime boxing. Keep boolean payloads on that + // runtime path in both stages so package artifacts remain identical. + if (r != nil && (r.kind == syntax.nkind.N_TRUE + || r.kind == syntax.nkind.N_FALSE)) { return false; }; if (u == nil || u.kind != syntax.tykind.TY_TAGGED || u.nullable != 0 || r == nil || !variantpresent(u.params, r.type_: *syntax.tinfo) || syntax.typeisstr(r.type_: *syntax.tinfo) @@ -7871,9 +7876,13 @@ fn initexprstatic(t: *syntax.tinfo, n: *syntax.node) bool = { && u.nullable == 0) { if (!variantpresent(u.params, r.type_: *syntax.tinfo)) { return false; }; let ru: *syntax.tinfo = tichase(r.type_: *syntax.tinfo); + // Relocation-bearing string carriers stay on the common runtime + // path: Cstage's untyped string is not the concrete str variant at + // this classifier, so a static wwstage row would change artifacts. if (ru != nil && (ru.kind == syntax.tykind.TY_STR + || ru.kind == syntax.tykind.TY_UNTYPED_STR || ru.kind == syntax.tykind.TY_SLICE)) { - return r.kind == syntax.nkind.N_STRLIT; + return false; }; return inittaggedrawstatic(u, r); }; @@ -7882,6 +7891,78 @@ fn initexprstatic(t: *syntax.tinfo, n: *syntax.node) bool = { return foldintliteral(r, &ignored); }; +// A slice literal has no declared element count for WW's trailing `...` +// repeat to fill. Keep this a checker-owned semantic rejection when a mutable +// package let moves from static data to runtime initialization; otherwise the +// backing counter drops the marker and publishes only the explicit prefix. +// Arrays retain the repeat because their target length is known. Recurse +// through the aggregate shapes package-init lowering owns. +fn initvalidateslicerepeats(c: *checker, want: *syntax.tinfo, + expr: *syntax.node) bool = { + let r: *syntax.node = initstripcast(expr); + let u: *syntax.tinfo = tichase(want); + if (r == nil || u == nil) { return true; }; + if (u.kind == syntax.tykind.TY_SLICE + && r.kind == syntax.nkind.N_ARRLIT) { + let e: *syntax.node = r.list; + for (e != nil) { + if (e.kind == syntax.nkind.N_FIELD + && syntax.streq(e.str, "...")) { + importdiagprefix(e); + cerr("'...' repeat has no target length in a slice literal\n"); + c.errs += 1; + return false; + }; + if (!initvalidateslicerepeats(c, u.sub, e)) { return false; }; + e = e.next; + }; + return true; + }; + if (u.kind == syntax.tykind.TY_ARRAY + && r.kind == syntax.nkind.N_ARRLIT) { + let e: *syntax.node = r.list; + for (e != nil) { + if (!(e.kind == syntax.nkind.N_FIELD + && syntax.streq(e.str, "..."))) { + if (!initvalidateslicerepeats(c, u.sub, e)) { + return false; + }; + }; + e = e.next; + }; + return true; + }; + if (u.kind == syntax.tykind.TY_STRUCT + && r.kind == syntax.nkind.N_STRUCTLIT) { + let e: *syntax.node = r.list; + for (e != nil) { + let field: *syntax.tfield = u.fields; + for (field != nil) { + if (syntax.streq(e.str, field.name)) { break; }; + field = field.tnext; + }; + if (field != nil) { + if (!initvalidateslicerepeats(c, field.type_, e.lhs)) { + return false; + }; + }; + e = e.next; + }; + return true; + }; + if (u.kind == syntax.tykind.TY_TUPLE + && r.kind == syntax.nkind.N_TUPLE) { + let te: *syntax.ttupleelem = u.tupleelems; + let e: *syntax.node = r.list; + for (e != nil && te != nil) { + if (!initvalidateslicerepeats(c, te.type_, e)) { return false; }; + e = e.next; + te = te.tnext; + }; + }; + return true; +}; + type initwalkitem = struct { node: *syntax.node, next: *initwalkitem, @@ -8266,6 +8347,10 @@ fn initlowerpackage(c: *checker, file: *syntax.node) void = { else { if (d.rhs != nil) { dt = d.rhs.type_: *syntax.tinfo; }; }; + if (!initvalidateslicerepeats(c, dt, d.rhs)) { + d = d.next; + continue; + }; if (!initexprstatic(dt, d.rhs)) { d.runtimeinit = 1; nruntime += 1u64; @@ -8906,8 +8991,22 @@ fn checkfile(c: *checker, file: *syntax.node) void = { // rhs returns false silently and is left untouched). // Stamp LAST — checkletassign consumes the pre-stamp type. if (d.rhs != nil) { + // An explicit scalar-to-tagged cast selects the carrier. + // Folding it while retaining the tagged result type makes + // cstage consume scalar registers as an already-wide ABI + // value. Mutable package lets preserve the cast so the concrete + // carrier is boxed correctly; const keeps static-only behavior. + let foldt: *syntax.tinfo = nil; + if (d.lhs != nil) { + foldt = tichase(d.lhs.type_: *syntax.tinfo); + }; + let preservetaggedcast: bool = d.op != syntax.tkind.TK_CONST + && d.rhs.kind == syntax.nkind.N_CAST + && foldt != nil + && foldt.kind == syntax.tykind.TY_TAGGED + && foldt.nullable == 0; let dv: u64 = 0u64; - if (!foldintliteral(d.rhs, &dv)) { + if (!preservetaggedcast && !foldintliteral(d.rhs, &dv)) { if (evaldefconst(c, d.rhs, &dv, 0)) { stampintlit(d.rhs, dv); }; diff --git a/selfhost/cmd/ww/main.ww b/selfhost/cmd/ww/main.ww index 9fd190c7..6d31da17 100644 --- a/selfhost/cmd/ww/main.ww +++ b/selfhost/cmd/ww/main.ww @@ -5959,11 +5959,6 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, && !g.pkg[products[0].root].failed && !seprootiscommand(&g.pkg[products[0].root]); if (sepvalidateartifactpaths(g, scratch) < 0) { return 1; }; - if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts, - rootpackage, publishpackage, emitasm, istest) < 0) { - sepfreeproductstaging(products, nproducts); - return 1; - }; if (warm && workdirexists && sepvalidateworkdirowners(g, scratch) < 0) { return 1; }; let ci: i32 = 0; @@ -6064,6 +6059,11 @@ fn buildonesepimpl(selfdir: *u8, src: *u8, entryisdir: i32, producti += 1; }; if (!viableproduct) { return 1; }; + if (sepvalidaterequeststaging(g, scratch, warm, products, nproducts, + rootpackage, publishpackage, emitasm, istest) < 0) { + sepfreeproductstaging(products, nproducts); + return 1; + }; // Source-derived resolution and contextual legality are complete before // coordinator completion markers or persistent vouchers are changed. let createdoutput: sepcreateddirs; diff --git a/test/lang/array_static_init_test.ww b/test/lang/array_static_init_test.ww index 2d9d6110..2b0c09cb 100644 --- a/test/lang/array_static_init_test.ww +++ b/test/lang/array_static_init_test.ww @@ -3,8 +3,9 @@ // #156). emit_array_lit_bytes recurses on a TY_ARRAY element (esz=etype->size, // rule 13); cgindex leaves the sub-array ADDRESS for an array element so the // outer index dereferences the right cell (#135 sister). cstage build+run was -// T1; cs==ww .s byte-id rides T2 (test-lang-byteid). The nested-array `...` -// repeat reject is the runww carrier test/wcc/data/arr_nested_ellipsis_reject. +// T1; cs==ww .s byte-id rides T2 (test-lang-byteid). A nested-array `...` +// repeat now runs through package initialization when static emission cannot +// represent it, as pinned by test/wcc/data/arr_nested_ellipsis_reject. // // Float-element rows assert bit-exact incl negative elements (drew: exercise the // sign-XOR byte-loop per element). The struct-with-array-field rows read the diff --git a/test/sep/importdir_test.ww b/test/sep/importdir_test.ww index ce71938a..6995ac48 100644 --- a/test/sep/importdir_test.ww +++ b/test/sep/importdir_test.ww @@ -93,17 +93,26 @@ fn samefile(a: str, b: str, why: str) void = { "example.foo.unit.ww")); let wantfoo: str = strings.concat("//ww:module-reset example.foo\n", afoosrc, "\n//ww:module-reset example.foo\n", zfoosrc, "\n"); - if (!testenv.same(foounit, wantfoo) + let foovoucher: str = "//ww:direct-export example.bar "; + if (!strings.hasprefix(foounit, wantfoo) + || testenv.occurrences(foounit, "//ww:direct-export ") != 1 + || testenv.occurrences(foounit, foovoucher) != 1 + || testenv.pos(foounit, foovoucher) != wantfoo.len || testenv.has(foounit, "DECOY_FILE") || testenv.has(foounit, "//ww:module ")) { - fail("foo unit is not exactly its sorted, owned source set"); + fail("foo owner prefix/direct-export voucher mismatch"); }; let wantroot: str = strings.concat("//ww:module-reset ", appidentity, "\n", appsrc, "\n"); - if (!testenv.same(wantroot, testenv.readfile(strings.concat(cwork, - appidentity, ".unit.ww")))) { - fail("root unit is not exactly its owned source"); + let rootunit: str = testenv.readfile(strings.concat(cwork, + appidentity, ".unit.ww")); + let rootvoucher: str = "//ww:direct-export example.foo "; + if (!strings.hasprefix(rootunit, wantroot) + || testenv.occurrences(rootunit, "//ww:direct-export ") != 1 + || testenv.occurrences(rootunit, rootvoucher) != 1 + || testenv.pos(rootunit, rootvoucher) != wantroot.len) { + fail("root owner prefix/direct-export voucher mismatch"); }; let keys: []str = ["example.base", "example.bar", "example.foo", appidentity]; diff --git a/test/sep/localbuild_test.ww b/test/sep/localbuild_test.ww index 1161c040..dec0861e 100644 --- a/test/sep/localbuild_test.ww +++ b/test/sep/localbuild_test.ww @@ -288,10 +288,14 @@ fn rejectstable(dir: str, label: str, target: str, needle: str) void = { }; let expectedunit: str = strings.concat("//ww:module-reset main\n", mainsrc, "\n"); - if (!testenv.same(expectedunit, testenv.readfile(strings.concat(cwork, - "main.unit.ww"))) || !testenv.same(expectedunit, - testenv.readfile(strings.concat(wwork, "main.unit.ww")))) { - fail("self-contained", "consumer unit was not exactly its owned source"); + let cunit: str = testenv.readfile(strings.concat(cwork, "main.unit.ww")); + let cunit2: str = testenv.readfile(strings.concat(cwork2, "main.unit.ww")); + let wunit: str = testenv.readfile(strings.concat(wwork, "main.unit.ww")); + if (!strings.hasprefix(cunit, expectedunit) + || testenv.occurrences(cunit, "//ww:direct-export api ") != 1 + || testenv.pos(cunit, "//ww:direct-export api ") != expectedunit.len + || !testenv.same(cunit, cunit2) || !testenv.same(cunit, wunit)) { + fail("self-contained", "consumer owner prefix/direct-export voucher mismatch"); }; if (!testenv.exists(strings.concat(cwork, "implementation.a")) || !testenv.exists(strings.concat(cwork, "dimensions.a"))) { @@ -408,9 +412,16 @@ fn writediamond(td: str, reverse: bool) str = { let rootbody: str = testenv.readfile(strings.concat(main, "/main.ww")); let wantroot: str = strings.concat("//ww:module-reset main\n", rootbody, "\n"); - if (!testenv.same(unit, wantroot) || testenv.has(unit, - "//ww:module ")) { - fail("diamond", "root unit contains non-owned export text"); + let leftvoucher: str = "//ww:direct-export left "; + let rightvoucher: str = "//ww:direct-export right "; + if (!strings.hasprefix(unit, wantroot) || testenv.has(unit, + "//ww:module ") + || testenv.occurrences(unit, "//ww:direct-export ") != 2 + || testenv.occurrences(unit, leftvoucher) != 1 + || testenv.occurrences(unit, rightvoucher) != 1 + || testenv.pos(unit, leftvoucher) != wantroot.len + || testenv.pos(unit, leftvoucher) >= testenv.pos(unit, rightvoucher)) { + fail("diamond", "root owner prefix/sorted direct-export vouchers changed"); }; let sharedunit: str = testenv.readfile(strings.concat(scratch, "shared.unit.ww")); @@ -534,6 +545,9 @@ fn writediamond(td: str, reverse: bool) str = { let referencearchive: str = ""; let referencerootwwi: str = ""; let referencerootarchive: str = ""; + let referenceinitunit: str = ""; + let referenceinitasm: str = ""; + let referenceinitobj: str = ""; let referencebin: str = ""; let referencecompiler: str = ""; let referenceassembler: str = ""; @@ -583,9 +597,21 @@ fn writediamond(td: str, reverse: bool) str = { || !testenv.exists(strings.concat(work, "dep.a")) || !testenv.exists(strings.concat(work, "main.o")) || !testenv.exists(strings.concat(work, "main.wwi")) - || !testenv.exists(strings.concat(work, "main.a"))) { + || !testenv.exists(strings.concat(work, "main.a")) + || !testenv.exists(strings.concat(work, "main.init.unit.ww")) + || !testenv.exists(strings.concat(work, "main.init.s")) + || !testenv.exists(strings.concat(work, "main.init.o"))) { fail("library-roots", "package artifacts do not match root ownership"); }; + let initunit: str = testenv.readfile(strings.concat(work, + "main.init.unit.ww")); + if (!testenv.same(initunit, strings.concat( + "//ww:init-root __ww..pkg.p.main.v0.r0.init\n", + "//ww:init-call __ww..pkg.p.leaf.v0.r0.init\n", + "//ww:init-call __ww..pkg.p.dep.v0.r0.init\n", + "//ww:init-call __ww..pkg.p.main.v0.r0.init\n"))) { + fail("library-roots", "root dispatcher unit order changed"); + }; let ctrace: str = testenv.readfile(compilertrace); let atrace: str = testenv.readfile(assemblertrace); let ltrace: str = testenv.readfile(linkertrace); @@ -604,20 +630,29 @@ fn writediamond(td: str, reverse: bool) str = { fail("library-roots", "compiler actions were not deterministic postorder"); }; if (!testenv.has(ctrace, strings.concat( - "BEGIN<-c><--import><", work, "leaf.wwi><-I><", work, + "BEGIN<--package-init-symbol><__ww..pkg.p.leaf.v0.r0.init>", + "<-c><-I><", work, "leaf.wwi><-o><", work, + "leaf.s><", work, "leaf.unit.ww>")) + || !testenv.has(ctrace, strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.dep.v0.r0.init>", + "<-c><--import><", work, "leaf.wwi><-I><", work, "dep.wwi><-o><", work, "dep.s><", work, "dep.unit.ww>")) || !testenv.has(ctrace, strings.concat( - "BEGIN<--entry><-c><--import><", work, + "BEGIN<--entry><--package-init-symbol>", + "<__ww..pkg.p.main.v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c><--import><", work, "dep.wwi><-I><", work, "main.wwi><-o><", work, "main.s><", work, "main.unit.ww>")) - || testenv.occurrences(atrace, "\n") != 3 + || testenv.occurrences(atrace, "\n") != 4 || !testenv.has(atrace, strings.concat("BEGIN<-o><", work, - "dep.o><", work, "dep.s>"))) { + "dep.o><", work, "dep.s>")) + || !testenv.has(atrace, strings.concat("BEGIN<-o><", work, + "main.init.o><", work, "main.init.s>"))) { fail("library-roots", "compiler or assembler argv changed"); }; if (testenv.occurrences(ltrace, "\n") != 1 - || !testenv.has(ltrace, strings.concat("BEGIN<-o><", bin, "><", + || !testenv.has(ltrace, strings.concat("BEGIN<-o><", bin, ".new><", work, "main.a>")) || testenv.pos(ltrace, strings.concat("<", work, "dep.a>")) < 0 || testenv.pos(ltrace, strings.concat("<", work, "leaf.a>")) @@ -655,6 +690,11 @@ fn writediamond(td: str, reverse: bool) str = { "main.wwi"))); referencerootarchive = strings.dup(testenv.readfile(strings.concat(work, "main.a"))); + referenceinitunit = strings.dup(initunit); + referenceinitasm = strings.dup(testenv.readfile(strings.concat(work, + "main.init.s"))); + referenceinitobj = strings.dup(testenv.readfile(strings.concat(work, + "main.init.o"))); referencebin = strings.dup(testenv.readfile(bin)); referencecompiler = strings.dup(ctrace); referenceassembler = strings.dup(atrace); @@ -669,6 +709,11 @@ fn writediamond(td: str, reverse: bool) str = { testenv.readfile(strings.concat(work, "main.wwi"))) || !testenv.same(referencerootarchive, testenv.readfile(strings.concat(work, "main.a"))) + || !testenv.same(referenceinitunit, initunit) + || !testenv.same(referenceinitasm, + testenv.readfile(strings.concat(work, "main.init.s"))) + || !testenv.same(referenceinitobj, + testenv.readfile(strings.concat(work, "main.init.o"))) || !testenv.same(referencebin, testenv.readfile(bin)) || !testenv.same(referencecompiler, ctrace) || !testenv.same(referenceassembler, atrace) @@ -747,7 +792,7 @@ fn writediamond(td: str, reverse: bool) str = { || !testenv.exists(strings.concat(emptywork, "types.a")) || testenv.occurrences(ctrace, strings.concat("<", emptywork, "types.unit.ww>")) != 1 - || testenv.occurrences(atrace, "\n") != 2) { + || testenv.occurrences(atrace, "\n") != 3) { fail("library-roots", "empty WW_SRCLIB did not use the default source root"); }; if (testenv.occurrences(ltrace, "\n") != 1 @@ -900,6 +945,9 @@ fn writediamond(td: str, reverse: bool) str = { || !testenv.exists(strings.concat(work, "/dep.a")) || !testenv.exists(strings.concat(work, "/main.wwi")) || !testenv.exists(strings.concat(work, "/main.a")) + || !testenv.exists(strings.concat(work, "/main.init.unit.ww")) + || !testenv.exists(strings.concat(work, "/main.init.s")) + || !testenv.exists(strings.concat(work, "/main.init.o")) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.ww")), testenv.readfile(copied[si])) || !testenv.same(testenv.readfile(strings.concat(work, @@ -908,7 +956,7 @@ fn writediamond(td: str, reverse: bool) str = { "/.wwtool.w6a")), testenv.readfile(assembler)) || !testenv.same(testenv.readfile(strings.concat(work, "/.wwtool.stamp")), - "ww workdir fmt 15 mode build asm 0\n")) { + "ww workdir fmt 17 mode build asm 0\n")) { fail("driver-identity", "persistent artifacts or identities are incomplete"); }; let coldwwi: str = testenv.readfile(strings.concat(work, "/dep.wwi")); @@ -919,28 +967,38 @@ fn writediamond(td: str, reverse: bool) str = { let coldlinker: str = testenv.readfile(linkertrace); if (testenv.occurrences(coldcompiler, "\n") != 3 || !testenv.has(coldcompiler, strings.concat( - "BEGIN<-c><--import><", work, "/leaf.wwi><-I><", work, + "BEGIN<--package-init-symbol><__ww..pkg.p.leaf.v0.r0.init>", + "<-c><-I><", work, "/leaf.wwi.new><-o><", work, + "/leaf.s.new><", work, "/leaf.unit.new>")) + || !testenv.has(coldcompiler, strings.concat( + "BEGIN<--package-init-symbol><__ww..pkg.p.dep.v0.r0.init>", + "<-c><--import><", work, "/leaf.wwi.new><-I><", work, "/dep.wwi.new><-o><", work, "/dep.s.new><", work, "/dep.unit.new>")) || !testenv.has(coldcompiler, strings.concat( - "BEGIN<--entry><-c><--import><", work, - "/dep.wwi><-I><", work, "/main.wwi.new><-o><", work, + "BEGIN<--entry><--package-init-symbol>", + "<__ww..pkg.p.main.v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c><--import><", work, + "/dep.wwi.new><-I><", work, "/main.wwi.new><-o><", work, "/main.s.new><", work, "/main.unit.new>")) - || testenv.occurrences(coldassembler, "\n") != 3 + || testenv.occurrences(coldassembler, "\n") != 4 || !testenv.has(coldassembler, strings.concat( "BEGIN<-o><", work, "/dep.o.new><", work, - "/dep.s.new>"))) { + "/dep.s.new>")) + || !testenv.has(coldassembler, strings.concat( + "BEGIN<-o><", work, "/main.init.o.new><", work, + "/main.init.s.new>"))) { fail("driver-identity", "cold compiler or assembler argv changed"); }; if (testenv.occurrences(coldlinker, "\n") != 1 || !testenv.has(coldlinker, strings.concat("BEGIN<-o><", bin, - "><", work, "/main.a>")) + ".new><", work, "/main.a.new>")) || testenv.pos(coldlinker, strings.concat("<", work, - "/dep.a>")) < 0 + "/dep.a.new>")) < 0 || testenv.pos(coldlinker, strings.concat("<", work, - "/leaf.a>")) < testenv.pos(coldlinker, strings.concat("<", - work, "/dep.a>")) + "/leaf.a.new>")) < testenv.pos(coldlinker, strings.concat("<", + work, "/dep.a.new>")) || !testenv.has(coldlinker, strings.concat("<", runtime, "/libwwrt.a>")) || testenv.has(coldlinker, ".wwi>")) { @@ -970,7 +1028,7 @@ fn writediamond(td: str, reverse: bool) str = { || testenv.occurrences(changedexportcompiler, strings.concat("<", work, "/main.unit.new>")) != 1 || testenv.occurrences(changedexportcompiler, "\n") != 5 - || testenv.occurrences(changedexportassembler, "\n") != 5) { + || testenv.occurrences(changedexportassembler, "\n") != 6) { fail("driver-identity", "changed export did not stop at an unchanged importer export"); }; if (code(cwd, strings.concat("driver-export-change-run-", tags[si]), @@ -1004,7 +1062,7 @@ fn writediamond(td: str, reverse: bool) str = { || testenv.occurrences(testenv.readfile(compilertrace), strings.concat( "<", work, "/main.unit.new>")) != 1 || testenv.occurrences(testenv.readfile(compilertrace), "\n") != 7 - || testenv.occurrences(testenv.readfile(assemblertrace), "\n") != 7 + || testenv.occurrences(testenv.readfile(assemblertrace), "\n") != 8 || !testenv.same(coldbin, testenv.readfile(bin))) { fail("driver-identity", "restored export did not rebuild only its direct importer"); }; @@ -1029,7 +1087,7 @@ fn writediamond(td: str, reverse: bool) str = { || testenv.occurrences(changedcompiler, strings.concat("<", work, "/main.unit.new>")) != 2 || testenv.occurrences(changedcompiler, "\n") != 10 - || testenv.occurrences(changedassembler, "\n") != 10 + || testenv.occurrences(changedassembler, "\n") != 12 || testenv.occurrences(changedlinker, "\n") != 5) { fail("driver-identity", "driver change reused stale package actions"); }; @@ -1339,9 +1397,11 @@ fn writediamond(td: str, reverse: bool) str = { let linkers: []str = ["w6l", "w6l_ww"]; let tags: []str = ["c", "ww"]; let suffixes: []str = [".unit.ww", ".wwi", ".s", ".o", ".a"]; + let initsuffixes: []str = [".init.unit.ww", ".init.s", ".init.o"]; let rootreference: []str = ["", "", "", "", ""]; let depreference: []str = ["", "", "", "", ""]; let commandreference: []str = ["", "", "", "", ""]; + let commandinitreference: []str = ["", "", ""]; let nooutrootreference: []str = ["", "", "", "", ""]; let nooutdepreference: []str = ["", "", "", "", ""]; let publishreference: str = ""; @@ -1382,11 +1442,14 @@ fn writediamond(td: str, reverse: bool) str = { let ctrace: str = testenv.readfile(compilertrace); let atrace: str = testenv.readfile(assemblertrace); let wantcompiler: str = strings.concat( - "BEGIN<-c><-I><", work, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work, "/foo.dep.wwi.new><-o><", work, "/foo.dep.s.new><", work, "/foo.dep.unit.new>\n", - "BEGIN<-c><--import><", work, - "/foo.dep.wwi><-I><", work, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.bar.v0.r0.init><-c>", + "<--import><", work, + "/foo.dep.wwi.new><-I><", work, "/foo.bar.wwi.new><-o><", work, "/foo.bar.s.new><", work, "/foo.bar.unit.new>\n"); let wantassembler: str = strings.concat( @@ -1461,15 +1524,20 @@ fn writediamond(td: str, reverse: bool) str = { atrace = testenv.readfile(assemblertrace); let commandlink: str = testenv.readfile(linkertrace); let wantcommandcompiler: str = strings.concat( - "BEGIN<-c><-I><", commandwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", commandwork, "/foo.dep.wwi.new><-o><", commandwork, "/foo.dep.s.new><", commandwork, "/foo.dep.unit.new>\n", - "BEGIN<-c><--import><", commandwork, - "/foo.dep.wwi><-I><", commandwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.bar.v0.r0.init><-c>", + "<--import><", commandwork, + "/foo.dep.wwi.new><-I><", commandwork, "/foo.bar.wwi.new><-o><", commandwork, "/foo.bar.s.new><", commandwork, "/foo.bar.unit.new>\n", - "BEGIN<--entry><-c><--import><", commandwork, - "/foo.bar.wwi><-I><", commandwork, + "BEGIN<--entry><--package-init-symbol>", + "<__ww..pkg.p.foo.cmd.v0.r0.init><--init-dispatch-symbol>", + "<__ww..dispatch><-c><--import><", commandwork, + "/foo.bar.wwi.new><-I><", commandwork, "/foo.cmd.wwi.new><-o><", commandwork, "/foo.cmd.s.new><", commandwork, "/foo.cmd.unit.new>\n"); let wantcommandassembler: str = strings.concat( @@ -1478,10 +1546,12 @@ fn writediamond(td: str, reverse: bool) str = { "BEGIN<-o><", commandwork, "/foo.bar.o.new><", commandwork, "/foo.bar.s.new>\n", "BEGIN<-o><", commandwork, "/foo.cmd.o.new><", commandwork, - "/foo.cmd.s.new>\n"); + "/foo.cmd.s.new>\n", + "BEGIN<-o><", commandwork, "/foo.cmd.init.o.new><", commandwork, + "/foo.cmd.init.s.new>\n"); let wantcommandlink: str = strings.concat("BEGIN<-o><", commandbin, - "><", commandwork, "/foo.cmd.a><", commandwork, - "/foo.bar.a><", commandwork, "/foo.dep.a><", runtime, + ".new><", commandwork, "/foo.cmd.a.new><", commandwork, + "/foo.bar.a.new><", commandwork, "/foo.dep.a.new><", runtime, "/libwwrt.a>\n"); if (out.termination != exec.termination.EXIT || out.code != 0 || out.stderr.len != 0 @@ -1510,6 +1580,18 @@ fn writediamond(td: str, reverse: bool) str = { }; }; ai += 1; }; + ai = 0; + for (ai < initsuffixes.len) { + let initbytes: str = testenv.readfile(strings.concat(commandwork, + "/foo.cmd", initsuffixes[ai])); + if (si == 0) { + commandinitreference[ai] = strings.dup(initbytes); + } else { if (!testenv.same(commandinitreference[ai], initbytes)) { + fail("package-autoaction", + "Cstage and WWstage dispatcher artifacts differ"); + }; }; + ai += 1; + }; let commandrun: []str = [commandbin]; if (code(td, strings.concat("auto-command-run-", tags[si]), commandrun) != 42) { @@ -1544,11 +1626,14 @@ fn writediamond(td: str, reverse: bool) str = { ctrace = testenv.readfile(compilertrace); atrace = testenv.readfile(assemblertrace); let wantnooutcompiler: str = strings.concat( - "BEGIN<-c><-I><", nooutwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", nooutwork, "/foo.dep.wwi.new><-o><", nooutwork, "/foo.dep.s.new><", nooutwork, "/foo.dep.unit.new>\n", - "BEGIN<-c><--import><", nooutwork, - "/foo.dep.wwi><-I><", nooutwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.bar.v0.r0.init><-c>", + "<--import><", nooutwork, + "/foo.dep.wwi.new><-I><", nooutwork, "/foo.bar.wwi.new><-o><", nooutwork, "/foo.bar.s.new><", nooutwork, "/foo.bar.unit.new>\n"); let wantnooutassembler: str = strings.concat( @@ -1660,11 +1745,14 @@ fn writediamond(td: str, reverse: bool) str = { testenv.runcommandenv(td, td, strings.concat("auto-asm-long-output-", tags[si]), asmav, env, tmo(), &out); let wantasmcompiler: str = strings.concat( - "BEGIN<-c><-I><", asmwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", asmwork, "/foo.dep.wwi.new><-o><", asmwork, "/foo.dep.s.new><", asmwork, "/foo.dep.unit.new>\n", - "BEGIN<-c><--import><", asmwork, - "/foo.dep.wwi><-I><", asmwork, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.bar.v0.r0.init><-c>", + "<--import><", asmwork, + "/foo.dep.wwi.new><-I><", asmwork, "/foo.bar.wwi.new><-o><", asmwork, "/foo.bar.s.new><", asmwork, "/foo.bar.unit.new>\n"); if (out.termination != exec.termination.EXIT || out.code != 0 @@ -1752,7 +1840,8 @@ fn writediamond(td: str, reverse: bool) str = { ctrace = testenv.readfile(compilertrace); atrace = testenv.readfile(assemblertrace); let wantprivatecompiler: str = strings.concat( - "BEGIN<-c><-I><", work, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work, "/foo.dep.wwi.new><-o><", work, "/foo.dep.s.new><", work, "/foo.dep.unit.new>\n"); let wantprivateassembler: str = strings.concat( @@ -1778,11 +1867,14 @@ fn writediamond(td: str, reverse: bool) str = { ctrace = testenv.readfile(compilertrace); atrace = testenv.readfile(assemblertrace); let wantexportcompiler: str = strings.concat( - "BEGIN<-c><-I><", work, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.dep.v0.r0.init><-c><-I><", work, "/foo.dep.wwi.new><-o><", work, "/foo.dep.s.new><", work, "/foo.dep.unit.new>\n", - "BEGIN<-c><--import><", work, - "/foo.dep.wwi><-I><", work, + "BEGIN<--package-init-symbol>", + "<__ww..pkg.p.foo.bar.v0.r0.init><-c>", + "<--import><", work, + "/foo.dep.wwi.new><-I><", work, "/foo.bar.wwi.new><-o><", work, "/foo.bar.s.new><", work, "/foo.bar.unit.new>\n"); let wantexportassembler: str = strings.concat( diff --git a/test/sep/sepbuild_test.ww b/test/sep/sepbuild_test.ww index 1b852b30..15de177f 100644 --- a/test/sep/sepbuild_test.ww +++ b/test/sep/sepbuild_test.ww @@ -101,7 +101,8 @@ fn samefile(label: str, what: str, a: str, b: str) void = { i += 1; }; - // cs==ww (rule 10): per-package artifacts + the final binary + // cs==ww (rule 10): per-source-action artifacts + the final binary; + // sepinit owns the command root's additional dispatcher artifacts. let sufs: []str = [".s", ".wwi", ".a", ".unit.ww"]; let p: i32 = 0; for (p < pkgs.len) { @@ -164,7 +165,8 @@ fn samefile(label: str, what: str, a: str, b: str) void = { s += 1; }; - // cs==ww for the complete package artifacts, including the root archive. + // cs==ww for every source-action artifact, including the root archive; + // sepinit owns the command root's additional dispatcher artifacts. let pkgs: []str = ["c", "__root"]; let sufs: []str = [".s", ".wwi", ".a", ".unit.ww"]; let p: i32 = 0; diff --git a/test/sep/sepimport_test.ww b/test/sep/sepimport_test.ww index a436d286..6f95b601 100644 --- a/test/sep/sepimport_test.ww +++ b/test/sep/sepimport_test.ww @@ -209,11 +209,12 @@ fn depmainrow(label: str, entry: str, want: i32, deps: str, l += 1; }; - // Both the dependency and raw explicit root emit complete package artifacts. + // Both the dependency and raw explicit root emit complete source-action + // artifacts; sepinit owns command-dispatcher artifact parity. let parts: []str = ["aa.s", "aa.wwi", "aa.a", "aa.unit.ww", "__root.s", "__root.wwi", "__root.a", "__root.unit.ww"]; - // cs==ww (rule 10) per layout over the complete package-artifact table + // cs==ww (rule 10) per layout over the source-action artifact table let l2: i32 = 0; for (l2 < 2) { let p: i32 = 0; diff --git a/test/sep/seplink_test.ww b/test/sep/seplink_test.ww index b4ab6129..b7c9717e 100644 --- a/test/sep/seplink_test.ww +++ b/test/sep/seplink_test.ww @@ -4,8 +4,9 @@ package seplink_test; // retired native carriers test/wcc/989_separchive_run.c and // 989_sepcycle_dup.c; every assertion preserved. // -// archive — `ww build` wraps every package action's .o in the existing -// deterministic single-member .a and links the root archive first: +// archive — `ww build` wraps every library package action's .o in the existing +// deterministic single-member .a; the command root also owns its fixed +// dispatcher member, and the linker receives that root archive first: // build+run exit 7 both stages; __root.a + helper.a present; both archives // are byte-identical across stages; 3 cold cstage rebuilds emit // byte-identical archives (zeroed mtime/uid/gid, fixed mode/member — diff --git a/test/wcc/996_dyn_ww.c b/test/wcc/996_dyn_ww.c index 2cd995b9..059b0b09 100644 --- a/test/wcc/996_dyn_ww.c +++ b/test/wcc/996_dyn_ww.c @@ -81,14 +81,14 @@ main(void) } /* #93 sep layout: build an explicit command source via `ww build`, - * then independently link .sepwork/__root.o with both linkers. + * then independently link .sepwork/__root.a with both linkers. * The direct getpid call guarantees one dynamic libc relocation. */ char cmd[4096], src[256], stem[256], scratch[320]; - char obj[384], co[256], wo[256]; + char archive[384], co[256], wo[256]; snprintf(src, sizeof src, "%s/dyn.ww", td); snprintf(stem, sizeof stem, "%s/built", td); snprintf(scratch, sizeof scratch, "%s.sepwork", stem); - snprintf(obj, sizeof obj, "%s/__root.o", scratch); + snprintf(archive, sizeof archive, "%s/__root.a", scratch); snprintf(co, sizeof co, "%s/c", td); snprintf(wo, sizeof wo, "%s/w", td); @@ -125,7 +125,7 @@ main(void) snprintf(cmd, sizeof cmd, "%s/w6l -o %s %s -L %s " "-l c %s/out/lib/libwwrt.a 2>/dev/null", - bin, co, obj, libdir, cwd); + bin, co, archive, libdir, cwd); if (runwait(cmd) != 0) { fprintf(stderr, "w6l_ww-dyn FAIL: C w6l errored\n"); rc = 1; @@ -135,7 +135,7 @@ main(void) snprintf(cmd, sizeof cmd, "%s/w6l_ww -o %s %s -L %s " "-l c %s/out/lib/libwwrt.a 2>/dev/null", - bin, wo, obj, libdir, cwd); + bin, wo, archive, libdir, cwd); if (runwait(cmd) != 0) { fprintf(stderr, "w6l_ww-dyn FAIL: ww w6l errored\n"); rc = 1; diff --git a/test/wcc/data/alias_g73_static_str/case.ww b/test/wcc/data/alias_g73_static_str/case.ww index 16aaaf98..b3998eaa 100644 --- a/test/wcc/data/alias_g73_static_str/case.ww +++ b/test/wcc/data/alias_g73_static_str/case.ww @@ -1,4 +1,4 @@ -//ww:error "#129 A.2 scope" +//ww:run package main; type s1t = str; type s2t = s1t; diff --git a/test/wcc/data/alias_infptr_global/case.ww b/test/wcc/data/alias_infptr_global/case.ww index d93d3716..7963a06b 100644 --- a/test/wcc/data/alias_infptr_global/case.ww +++ b/test/wcc/data/alias_infptr_global/case.ww @@ -1,10 +1,6 @@ -//ww:error "runtime initializer unsupported (alloc/call; rule 7)" -// Module-scope alloc initializer: no DATAW slot exists for a -// runtime-computed global, so pre-reject every reference died at -// LINK time ("undefined reference to 'main.gp'") — the checker now -// rejects at the declaration on both frontends (Hare model: -// ref/harec/src/check.c:4360 rejects non-compile-time-evaluable -// global initializers; ww has no @init path). +//ww:run +// A module-scope allocation is evaluated once by the package task before +// main; its pointer remains live in the zero-backed package variable. package main; type box = struct { s: str, n: int }; let gp = alloc(box { s = "hi", n = 1 })!; diff --git a/test/wcc/data/alias_slcstr_plain/case.ww b/test/wcc/data/alias_slcstr_plain/case.ww index 391c9e74..d82cfbb5 100644 --- a/test/wcc/data/alias_slcstr_plain/case.ww +++ b/test/wcc/data/alias_slcstr_plain/case.ww @@ -1,7 +1,9 @@ -//ww:error "slice-of-{str,slice,tagged} literal static-init unsupported" +//ww:run package main; let G: []str = ["aa", "bbb"]; export fn main() i32 = { - if (len(G[0]) != 2) { return 1; }; + if (G.len != 2 || G.cap != 2) { return 1; }; + if (len(G[0]) != 2) { return 2; }; + if (len(G[1]) != 3) { return 3; }; return 0; }; diff --git a/test/wcc/data/arr_nested_ellipsis_reject/case.ww b/test/wcc/data/arr_nested_ellipsis_reject/case.ww index d8447e1a..e0489fd8 100644 --- a/test/wcc/data/arr_nested_ellipsis_reject/case.ww +++ b/test/wcc/data/arr_nested_ellipsis_reject/case.ww @@ -1,7 +1,12 @@ -//ww:error "repeat with nested-array elements" -// #156/rule-7 carrier: a `...` repeat marker with a nested-array element must -// REJECT on both stages (no consumer needs it; powers_of_ten is fully -// enumerated). From test/wcc/919_array_static_init_run.c nested_ellipsis_reject. +//ww:run +// A fixed outer array gives the nested-row repeat an exact target length. +// Package initialization evaluates the row once and fills every remaining row. package main; let A: [4][2]u64 = [[1u64, 2u64]...]; -export fn main() i32 = { return 0; }; +export fn main() i32 = { + if (A[0][0] != 1 || A[0][1] != 2) { return 1; }; + if (A[1][0] != 1 || A[1][1] != 2) { return 2; }; + if (A[2][0] != 1 || A[2][1] != 2) { return 3; }; + if (A[3][0] != 1 || A[3][1] != 2) { return 4; }; + return 0; +}; diff --git a/test/wcc/data/callinit_global_reject/case.ww b/test/wcc/data/callinit_global_reject/case.ww index 99b27133..0faf82b7 100644 --- a/test/wcc/data/callinit_global_reject/case.ww +++ b/test/wcc/data/callinit_global_reject/case.ww @@ -1,6 +1,5 @@ -//ww:error "runtime initializer unsupported (alloc/call; rule 7)" -// The call sibling of alias_infptr_global's alloc reject: a -// module-scope call initializer has no link-time data either. +//ww:run-exit 7 +// A module-scope call initializer executes once in the package task. package main; fn mk() i64 = { return 7; }; let gc: i64 = mk(); diff --git a/test/wcc/data/r687_neg_str_slicelit/case.ww b/test/wcc/data/r687_neg_str_slicelit/case.ww index a0125bdc..a5c52b96 100644 --- a/test/wcc/data/r687_neg_str_slicelit/case.ww +++ b/test/wcc/data/r687_neg_str_slicelit/case.ww @@ -1,4 +1,4 @@ -//ww:error "slice-of-{str,slice,tagged} literal static-init unsupported" +//ww:run-exit 2 package main; let g: []str = ["a", "b"]; export fn main() i32 = { return g.len: i32; }; diff --git a/test/wcc/data/r83_gtag_boolcast_reject/case.ww b/test/wcc/data/r83_gtag_boolcast_reject/case.ww index 949abdc4..f8e32f82 100644 --- a/test/wcc/data/r83_gtag_boolcast_reject/case.ww +++ b/test/wcc/data/r83_gtag_boolcast_reject/case.ww @@ -1,5 +1,12 @@ -//ww:error "unsupported variant init" +//ww:run-exit 11 package main; type u = (int | bool | str); let g: u = true: u; -export fn main() i32 = { return 0; }; +export fn main() i32 = { + match (g) { + case let n: int => return n: i32; + case let b: bool => return 11; + case let s: str => return 22; + }; + return 99; +}; diff --git a/test/wcc/data/r83_gtag_intcast_reject/case.ww b/test/wcc/data/r83_gtag_intcast_reject/case.ww index b5e4fe26..8f9b7597 100644 --- a/test/wcc/data/r83_gtag_intcast_reject/case.ww +++ b/test/wcc/data/r83_gtag_intcast_reject/case.ww @@ -1,5 +1,12 @@ -//ww:error "unsupported variant init" +//ww:run-exit 7 package main; type u = (int | bool | str); let g: u = 7: u; -export fn main() i32 = { return 0; }; +export fn main() i32 = { + match (g) { + case let n: int => return n: i32; + case let b: bool => return 11; + case let s: str => return 22; + }; + return 99; +}; diff --git a/test/wcc/data/r84_tagstatic_wide_array/case.ww b/test/wcc/data/r84_tagstatic_wide_array/case.ww index dfed0472..90449207 100644 --- a/test/wcc/data/r84_tagstatic_wide_array/case.ww +++ b/test/wcc/data/r84_tagstatic_wide_array/case.ww @@ -1,4 +1,18 @@ -//ww:error "tagged-union array element static-init needs a zero/int payload" +//ww:run-exit 2 package main; let gs: [2](int | str) = ["hi", 0]; -export fn main() int = { return 0; }; +export fn main() int = { + match (gs[0]) { + case let n: int => return 10; + case let s: str => { + if (s.len != 2 || s[0] != 104u8 || s[1] != 105u8) { + return 11; + }; + }; + }; + match (gs[1]) { + case let n: int => { if (n != 0) { return 12; }; }; + case let s: str => return 13; + }; + return 2; +}; diff --git a/test/wcc/data/r84_tagstatic_wide_struct/case.ww b/test/wcc/data/r84_tagstatic_wide_struct/case.ww index e5eef765..6e56f126 100644 --- a/test/wcc/data/r84_tagstatic_wide_struct/case.ww +++ b/test/wcc/data/r84_tagstatic_wide_struct/case.ww @@ -1,5 +1,15 @@ -//ww:error "tagged-union struct-field" +//ww:run-exit 2 package main; type sbox = struct { s: (int | str) }; let g: sbox = sbox { s = "hi" }; -export fn main() int = { return 0; }; +export fn main() int = { + match (g.s) { + case let n: int => return 10; + case let s: str => { + if (s.len != 2 || s[0] != 104u8 || s[1] != 105u8) { + return 11; + }; + }; + }; + return 2; +}; diff --git a/test/wcc/data/r944_alias_emit_slcstr_2lvl/case.ww b/test/wcc/data/r944_alias_emit_slcstr_2lvl/case.ww index 9dde15ec..bc0862ce 100644 --- a/test/wcc/data/r944_alias_emit_slcstr_2lvl/case.ww +++ b/test/wcc/data/r944_alias_emit_slcstr_2lvl/case.ww @@ -1,9 +1,11 @@ -//ww:error "slice-of-{str,slice,tagged} literal static-init unsupported" +//ww:run-exit 0 package main; type ms0 = str; type ms = ms0; let G: []ms = ["aa", "bbb"]; export fn main() i32 = { - if (len(G[0]) != 2) { return 1; }; + if (G.len != 2 || G.cap != 2) { return 1; }; + if (len(G[0]) != 2) { return 2; }; + if (len(G[1]) != 3) { return 3; }; return 0; }; diff --git a/test/wcc/data/tslot_t22_reject_global_init/case.ww b/test/wcc/data/tslot_t22_reject_global_init/case.ww index d3affccf..dc40ace5 100644 --- a/test/wcc/data/tslot_t22_reject_global_init/case.ww +++ b/test/wcc/data/tslot_t22_reject_global_init/case.ww @@ -1,4 +1,4 @@ -//ww:error "unsupported element init (int/str literals only; rule 7)" +//ww:run-exit 4 package main; let g: ((void | size), i64) = (5, 4); export fn main() i32 = { return g.1: i32; }; diff --git a/test/wcc/data/tslot_t3_reject_float_global/case.ww b/test/wcc/data/tslot_t3_reject_float_global/case.ww index dff74b3e..46869e9a 100644 --- a/test/wcc/data/tslot_t3_reject_float_global/case.ww +++ b/test/wcc/data/tslot_t3_reject_float_global/case.ww @@ -1,4 +1,4 @@ -//ww:error "unsupported element init (int/str literals only; rule 7)" +//ww:run package main; let g: (f64, i64) = (2.5, 4); export fn main() i32 = { diff --git a/test/xmod/collide_test.ww b/test/xmod/collide_test.ww index 536205f9..86c8be2e 100644 --- a/test/xmod/collide_test.ww +++ b/test/xmod/collide_test.ww @@ -2,15 +2,15 @@ package collide_test; // Cross-module leaf-name collision observers on both driver stages. // Ports of the retired native carriers test/wcc/989_fnptrcollide_run.c -// and 989_barefn_collide_run.c; every assertion preserved. +// and 989_barefn_collide_run.c; their collision teeth remain explicit under +// the package-initialization contract. // -// fnptrcollide (#14 F7-c7) — a data global `slot: i64` whose LEAF -// collides with the imported fn bar.slot must FAIL to build on BOTH -// stages: type-keyed nodefnptr refuses to fold `&slot` into the fn's -// TEXT reloc. Pre-fix wwstage was name-keyed and silently BUILT it -// (a DATAR to the fn symbol — cat-A: cs loud-fails, ww builds). The -// collision REQUIRES the import path (imported fn leaf vs local data -// global), so a single-file fixture cannot express it. +// fnptrcollide (#14 F7-c7) — a data global `slot: i64` whose leaf +// collides with imported fn bar.slot. `let fp: *i64 = &slot` is valid +// runtime package initialization: both stages must address main.slot, +// initialize fp before main, and run 7. The old loud-reject expectation +// predated runtime package lets. The collision still requires an import, +// and name-keyed lowering to bar.slot is rejected by exact asm/runtime pins. // // barefn (#84/#24a) — a `package main;` root `fn run` coexists with // imported aa.run: build+run exit 9 on BOTH stages while an explicit @@ -52,32 +52,86 @@ fn runcode(dir: str, name: str, argv: []str) i32 = { }; @test fn fnptrcollide() void = { + let td: str = testenv.fresh(); + let bar: str = strings.concat(td, "/bar"); + let main: str = strings.concat(td, "/main"); + assert(os.mkdir(bar, 493) == 0); + assert(os.mkdir(main, 493) == 0); + testenv.writefile(strings.concat(bar, "/bar.ww"), strings.concat( + "package bar;\n", + "export fn slot() i64 = { return 99; };\n")); + testenv.writefile(strings.concat(main, "/main.ww"), strings.concat( + "package main;\n", + "import bar;\n", + "let slot: i64 = 7;\n", + "let fp: *i64 = &slot;\n", + "export fn main() int = { let _ = bar.slot(); ", + "return (*fp): int; };\n")); let drvs: []str = ["ww", "ww_ww"]; + let tags: []str = ["cs", "ww"]; + let works: []str = [strings.concat(td, "/work-c"), + strings.concat(td, "/work-ww")]; + let progs: []str = [strings.concat(td, "/prog.cs"), + strings.concat(td, "/prog.ww")]; let i: i32 = 0; for (i < 2) { - let td: str = testenv.fresh(); - assert(os.mkdir(strings.concat(td, "/bar"), 493) == 0); - testenv.writefile(strings.concat(td, "/bar/bar.ww"), - strings.concat( - "package bar;\n", - "export fn slot() i64 = { return 99; };\n")); - testenv.writefile(strings.concat(td, "/main.ww"), strings.concat( - "package main;\n", - "import bar;\n", - "let slot: i64 = 7;\n", - "let fp: *i64 = &slot;\n", - "export fn main() int = { let _ = bar.slot(); ", - "return (*fp): int; };\n")); - let av: []str = [testenv.driver(drvs[i]), "build", "-I", "bar", - "main.ww"]; - if (runcode(td, strings.concat("build_", drvs[i]), av) == 0) { - fail("fnptrcollide", strings.concat(drvs[i], " built ok, ", - "expected the leaf-name collision to be rejected (#14 -- ", - "&slot mis-folded to the fn TEXT reloc)")); + assert(os.mkdir(works[i], 493) == 0); + let av: []str = [testenv.driver(drvs[i]), "build", "-w", works[i], + "-I", td, "-o", progs[i], "main"]; + if (runcode(td, strings.concat("build_", tags[i]), av) != 0) { + fail("fnptrcollide", strings.concat(drvs[i], " build failed")); + }; + let rav: []str = [progs[i]]; + if (runcode(td, strings.concat("run_", tags[i]), rav) != 7) { + fail("fnptrcollide", strings.concat(drvs[i], " exit != 7")); + }; + let sourceasm: str = testenv.readfile(strings.concat(works[i], + "/main.s")); + let initasm: str = testenv.readfile(strings.concat(works[i], + "/main.init.s")); + let barasm: str = testenv.readfile(strings.concat(works[i], "/bar.s")); + if (testenv.occurrences(sourceasm, "DATAW main.slot(SB)") != 1 + || testenv.occurrences(sourceasm, "DATAW main.fp(SB)") != 1 + || testenv.occurrences(sourceasm, + "CALL\t__ww..dispatch(SB)") != 1 + || testenv.occurrences(sourceasm, + "LEAQ\tmain.slot(SB), AX") != 1 + || testenv.occurrences(sourceasm, + "MOVQ\tAX, main.fp(SB)") != 1 + || testenv.has(sourceasm, "LEAQ\tbar.slot(SB)") + || testenv.has(sourceasm, "DATAR main.fp") + || testenv.occurrences(initasm, + "CALL\t__ww..pkg.p.bar.v0.r0.init(SB)") != 1 + || testenv.occurrences(initasm, + "CALL\t__ww..pkg.p.main.v0.r0.init(SB)") != 1 + || testenv.occurrences(barasm, "TEXT bar.slot,") != 1) { + fail("fnptrcollide", "runtime initializer selected the colliding fn"); }; - testenv.clean(td); i += 1; }; + let wantdispatch: str = strings.concat( + "//ww:init-root __ww..pkg.p.main.v0.r0.init\n", + "//ww:init-call __ww..pkg.p.bar.v0.r0.init\n", + "//ww:init-call __ww..pkg.p.main.v0.r0.init\n"); + if (!testenv.same(wantdispatch, testenv.readfile(strings.concat(works[0], + "/main.init.unit.ww")))) { + fail("fnptrcollide", "dispatcher did not initialize dependency first"); + }; + let names: []str = ["bar.unit.ww", "bar.wwi", "bar.s", "bar.o", "bar.a", + "main.unit.ww", "main.wwi", "main.s", "main.o", "main.a", + "main.init.unit.ww", "main.init.s", "main.init.o"]; + i = 0; + for (i < names.len) { + if (!testenv.same(testenv.readfile(strings.concat(works[0], "/", names[i])), + testenv.readfile(strings.concat(works[1], "/", names[i])))) { + fail("fnptrcollide", strings.concat(names[i], " differs by stage")); + }; + i += 1; + }; + if (!testenv.same(testenv.readfile(progs[0]), testenv.readfile(progs[1]))) { + fail("fnptrcollide", "Cstage/WWstage binaries differ"); + }; + testenv.clean(td); }; // __root.s then aa.s: the carrier's fixed #93 sep concat order.