diff --git a/Makefile b/Makefile index da6168b7..e62be690 100644 --- a/Makefile +++ b/Makefile @@ -241,6 +241,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_arr_elem_field_write \ $(BIN)/test_arr_enum_elem \ $(BIN)/test_arr_strslice_elem \ + $(BIN)/test_arr_tagged_elem \ $(BIN)/test_arr_infer_len \ $(BIN)/test_dot_str_chained_arg \ $(BIN)/test_dot_slice_arg \ @@ -568,6 +569,12 @@ $(BIN)/test_arr_infer_len: test/wcc/684_arr_infer_len.c $(BIN)/ww \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_arr_tagged_elem: test/wcc/685_arr_tagged_elem.c $(BIN)/ww \ + $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ + $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ + $(LIB)/libwwrt.a | $(BIN) + $(CC) $(CFLAGS) -o $@ $< + $(BIN)/test_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.c $(BIN)/ww \ $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \ $(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \ diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 3cd691c5..57749c60 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -9059,6 +9059,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) int is_agg = esubu && (esubu->kind == TY_STRUCT || esubu->kind == TY_ARRAY || esubu->kind == TY_TUPLE); + /* #12: a tagged-union element. NOT folded into is_agg — + * is_agg's body does N_STRUCTLIT/N_IDENT word-copy and + * FATALs on the literal/scalar case, never boxing the + * tag+payload. Route each element through the same + * cg_widen_tagged_store choke-point every other tagged + * store uses (let-init, vararg gather, struct-field). */ + int is_tagged_el = esubu && esubu->kind == TY_TAGGED; int is_str_el = type_isstr(esub); /* #20/#270 str-slice arm: a slice element is a 24B * {ptr,len,cap} header just like str; cgexpr lowers it @@ -9144,6 +9151,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) idx++; continue; } + if (is_tagged_el) { + cg_widen_tagged_store(c, locals, esub, + e, D_BP, base, esz); + last = e; + idx++; + continue; + } cgexpr(c, e, *locals); if (is_str_el || is_slice_el) { ins2(c, A_MOVQ, areg(D_AX), @@ -9165,6 +9179,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame) if (repeat && is_agg) fatal("#270-1c: `...` repeat of an aggregate " "array-literal element not wired (rule-7)"); + /* #12: `...` re-stores from AX, but cg_widen_tagged_store + * consumed the node and trashed AX — a repeat-fill would + * write garbage. No consumer needs `[N]tagged=[x,...]`. */ + if (repeat && is_tagged_el) + fatal("#12: `...` repeat of a tagged-union " + "array-literal element not wired (rule-7)"); if (repeat && last) { /* fill remaining slots with the value still in * AX (and BX for str). */ diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index afc8f46c..7091597e 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -29634,6 +29634,17 @@ fn cglet(c: *cgen, n: *node) void = { let isslicel: bool = esubti != nil && esubti.kind == tykind.TY_SLICE; if (isslicel) { esz = esubti.size: i32; }; + // #12: a tagged-union element. NOT folded into isagg — + // isagg's body word-copies/fatals and never boxes the + // tag+payload; route through the cgwidentaggedstore + // choke-point the N_LET tagged path (cgenstmt.ww:1627) + // uses. esz must come from the stamped slot size (#8-class + // trap, rule-13): the narrow override below only rescues + // 1/2/4, so a tagged 16/24B element keeps the wrong 8 + // sentinel stride without this. + let istaggedel: bool = esubti != nil + && esubti.kind == tykind.TY_TAGGED; + if (istaggedel) { esz = esubti.size: i32; }; // #8: a named-narrow element (`[N]tk`, tk = enum i32) is // neither a builtin prim (primsize=0 above, so esz stayed // the 8 sentinel) nor an aggregate, so the scalar store kept @@ -29721,6 +29732,8 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1c.ptr, m1c.len: u64); os.exit(1); }; }; + } else { if (istaggedel) { + cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz); } else { cgexpr(c, e); if (isstrel || isslicel) { @@ -29746,7 +29759,7 @@ fn cglet(c: *cgen, n: *node) void = { emitoff((off + idx * esz): i64); emitline("(BP)\n"); }; }; - }; + }; }; idx += 1; e = e.next; }; @@ -29756,6 +29769,14 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1cr.ptr, m1cr.len: u64); os.exit(1); }; + // #12: `...` re-stores from AX, but cgwidentaggedstore consumed + // the node and trashed AX — the repeat-fill would write garbage. + // No consumer needs `[N]tagged=[x,...]`. + if (repeat && istaggedel) { + let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n"; + os.write(2, m12r.ptr, m12r.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/selfhost/cmd/wcc/cgenstmt.ww b/selfhost/cmd/wcc/cgenstmt.ww index 012d35c3..732018c4 100644 --- a/selfhost/cmd/wcc/cgenstmt.ww +++ b/selfhost/cmd/wcc/cgenstmt.ww @@ -1783,6 +1783,17 @@ fn cglet(c: *cgen, n: *node) void = { let isslicel: bool = esubti != nil && esubti.kind == tykind.TY_SLICE; if (isslicel) { esz = esubti.size: i32; }; + // #12: a tagged-union element. NOT folded into isagg — + // isagg's body word-copies/fatals and never boxes the + // tag+payload; route through the cgwidentaggedstore + // choke-point the N_LET tagged path (cgenstmt.ww:1627) + // uses. esz must come from the stamped slot size (#8-class + // trap, rule-13): the narrow override below only rescues + // 1/2/4, so a tagged 16/24B element keeps the wrong 8 + // sentinel stride without this. + let istaggedel: bool = esubti != nil + && esubti.kind == tykind.TY_TAGGED; + if (istaggedel) { esz = esubti.size: i32; }; // #8: a named-narrow element (`[N]tk`, tk = enum i32) is // neither a builtin prim (primsize=0 above, so esz stayed // the 8 sentinel) nor an aggregate, so the scalar store kept @@ -1870,6 +1881,8 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1c.ptr, m1c.len: u64); os.exit(1); }; }; + } else { if (istaggedel) { + cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz); } else { cgexpr(c, e); if (isstrel || isslicel) { @@ -1895,7 +1908,7 @@ fn cglet(c: *cgen, n: *node) void = { emitoff((off + idx * esz): i64); emitline("(BP)\n"); }; }; - }; + }; }; idx += 1; e = e.next; }; @@ -1905,6 +1918,14 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1cr.ptr, m1cr.len: u64); os.exit(1); }; + // #12: `...` re-stores from AX, but cgwidentaggedstore consumed + // the node and trashed AX — the repeat-fill would write garbage. + // No consumer needs `[N]tagged=[x,...]`. + if (repeat && istaggedel) { + let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n"; + os.write(2, m12r.ptr, m12r.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 7905e6c0..f5daa0ed 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -29634,6 +29634,17 @@ fn cglet(c: *cgen, n: *node) void = { let isslicel: bool = esubti != nil && esubti.kind == tykind.TY_SLICE; if (isslicel) { esz = esubti.size: i32; }; + // #12: a tagged-union element. NOT folded into isagg — + // isagg's body word-copies/fatals and never boxes the + // tag+payload; route through the cgwidentaggedstore + // choke-point the N_LET tagged path (cgenstmt.ww:1627) + // uses. esz must come from the stamped slot size (#8-class + // trap, rule-13): the narrow override below only rescues + // 1/2/4, so a tagged 16/24B element keeps the wrong 8 + // sentinel stride without this. + let istaggedel: bool = esubti != nil + && esubti.kind == tykind.TY_TAGGED; + if (istaggedel) { esz = esubti.size: i32; }; // #8: a named-narrow element (`[N]tk`, tk = enum i32) is // neither a builtin prim (primsize=0 above, so esz stayed // the 8 sentinel) nor an aggregate, so the scalar store kept @@ -29721,6 +29732,8 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1c.ptr, m1c.len: u64); os.exit(1); }; }; + } else { if (istaggedel) { + cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz); } else { cgexpr(c, e); if (isstrel || isslicel) { @@ -29746,7 +29759,7 @@ fn cglet(c: *cgen, n: *node) void = { emitoff((off + idx * esz): i64); emitline("(BP)\n"); }; }; - }; + }; }; idx += 1; e = e.next; }; @@ -29756,6 +29769,14 @@ fn cglet(c: *cgen, n: *node) void = { os.write(2, m1cr.ptr, m1cr.len: u64); os.exit(1); }; + // #12: `...` re-stores from AX, but cgwidentaggedstore consumed + // the node and trashed AX — the repeat-fill would write garbage. + // No consumer needs `[N]tagged=[x,...]`. + if (repeat && istaggedel) { + let m12r: str = "#12: `...` repeat of a tagged-union array-literal element not wired (rule-7)\n"; + os.write(2, m12r.ptr, m12r.len: u64); + os.exit(1); + }; // AX (and BX for str) still holds the last stored value; // fill remaining slots up to the declared length with it. if (repeat) { diff --git a/test/wcc/685_arr_tagged_elem.c b/test/wcc/685_arr_tagged_elem.c new file mode 100644 index 00000000..914ec587 --- /dev/null +++ b/test/wcc/685_arr_tagged_elem.c @@ -0,0 +1,382 @@ +/* + * 685_arr_tagged_elem — cstage and wwstage agree, byte-for-byte and at + * runtime, that a `[N]tagged` array-LITERAL init boxes the FULL tag+ + * payload slot of every element (task #12, the #270 aggregate-element- + * store family's tagged-union arm; sibling of #20's str/slice arm in + * test 683). + * + * The bug: cgen's N_ARRLIT per-element store excluded TY_TAGGED from the + * is_agg predicate, so a tagged element fell through to the scalar + * 1-word MOVQ — it stored the raw scalar (99) into WORD0 (the TAG slot), + * with NO tag word and NO payload placement. A subsequent `match` found + * no variant (tag was the payload value, not a variant index) and the + * arms silently produced 0. wwstage was worse: esz stayed the 8 sentinel + * for a 16B tagged element (the #8-class stride trap), so element i+1 + * overran element i's tail. The under-copy DEPTH (1 word vs full slot) + * is byte-id-blind — both stages stored 1 word, so the asm-byte-id gate + * passed green on both-wrong; only the runtime readback below catches it. + * + * The fix (BOTH stages, converged byte-identical): route each tagged + * element through the SAME cg_widen_tagged_store / cgwidentaggedstore + * choke-point every other tagged store uses (let-init, vararg gather, + * struct-field) — NOT a new is_agg branch (is_agg word-copies/fatals and + * never boxes the tag). cstage adds is_tagged_el = esubu->kind == + * TY_TAGGED; wwstage adds istaggedel and sizes esz from the stamped + * tinfo (esubti.size, rule-13) so the 16B/24B stride is correct. + * + * row | shape | want + * ----------------+-------------------------------------+----------- + * lit_sum106 | [2]cell=[99i64,7i64] (cell= | 106 (ken + * | (i64|bool)); match+sum both. | oracle) + * lit_first | same; match t[0] only. | 99 + * lit_second | same; match t[1] only. | 7 + * bool_variant | [2]cell=[5i64, true]; match t[1]. | 1 pins the + * | The bool arm only fires if the tag | tag word + * | word was written (not payload-in- | was placed + * | word0). Pre-fix: 0 (no tag). | + * var_sum | [2]cell=[a,b] (a=40i64,b=2i64 | 42 variable- + * | idents); match+sum. Repro B. | source + * stride3 | [3]cell=[10i64,20i64,30i64]; match | 30 16B per- + * | t[2]. Exercises the per-element | element + * | slot stride (wwstage 8-sentinel | stride + * | overran into the wrong slot). | + * boxed_void | [3](i32|void)=[1i32,2i32,void]; | 13 bare-value + * | match: i32 arm sums payload, void | box: tag + + * | arm adds 10. 1+2+10. | payload + + * | | zero-pad + * + * The asm-byte-id rows pin the symmetric widen (cstage == wwstage). The + * under-copy itself is byte-id-blind (see above) — these rows guard the + * stride/op symmetry, the runtime rows guard the correctness. + */ +#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[] = { + { "lit_sum106", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [2]cell = [99i64, 7i64];\n" + "\tlet sum: i64 = 0i64;\n" + "\tlet i: i32 = 0;\n" + "\tfor (i < 2) {\n" + "\t\tmatch (t[i]) {\n" + "\t\tcase let v: i64 => { sum = sum + v; };\n" + "\t\tcase let z: bool => { sum = sum + 1i64; };\n" + "\t\t};\n" + "\t\ti = i + 1;\n" + "\t};\n" + "\treturn sum: i32;\n" + "};\n", + 106 }, + + { "lit_first", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [2]cell = [99i64, 7i64];\n" + "\tmatch (t[0]) {\n" + "\tcase let v: i64 => { return v: i32; };\n" + "\tcase let z: bool => { return -1; };\n" + "\t};\n" + "};\n", + 99 }, + + { "lit_second", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [2]cell = [99i64, 7i64];\n" + "\tmatch (t[1]) {\n" + "\tcase let v: i64 => { return v: i32; };\n" + "\tcase let z: bool => { return -1; };\n" + "\t};\n" + "};\n", + 7 }, + + { "bool_variant", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [2]cell = [5i64, true];\n" + "\tmatch (t[1]) {\n" + "\tcase let v: i64 => { return -1; };\n" + "\tcase let z: bool => { if (z) { return 1; }; return 0; };\n" + "\t};\n" + "};\n", + 1 }, + + { "var_sum", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet a: cell = 40i64;\n" + "\tlet b: cell = 2i64;\n" + "\tlet t: [2]cell = [a, b];\n" + "\tlet sum: i64 = 0i64;\n" + "\tlet i: i32 = 0;\n" + "\tfor (i < 2) {\n" + "\t\tmatch (t[i]) {\n" + "\t\tcase let v: i64 => { sum = sum + v; };\n" + "\t\tcase let z: bool => { sum = sum + 100i64; };\n" + "\t\t};\n" + "\t\ti = i + 1;\n" + "\t};\n" + "\treturn sum: i32;\n" + "};\n", + 42 }, + + { "stride3", + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [3]cell = [10i64, 20i64, 30i64];\n" + "\tmatch (t[2]) {\n" + "\tcase let v: i64 => { return v: i32; };\n" + "\tcase let z: bool => { return -1; };\n" + "\t};\n" + "};\n", + 30 }, + + { "boxed_void", + "package main;\n" + "type ov = (i32 | void);\n" + "export fn main() i32 = {\n" + "\tlet t: [3]ov = [1i32, 2i32, void];\n" + "\tlet sum: i32 = 0;\n" + "\tlet i: i32 = 0;\n" + "\tfor (i < 3) {\n" + "\t\tmatch (t[i]) {\n" + "\t\tcase let v: i32 => { sum = sum + v; };\n" + "\t\tcase void => { sum = sum + 10; };\n" + "\t\t};\n" + "\t\ti = i + 1;\n" + "\t};\n" + "\treturn sum;\n" + "};\n", + 13 }, +}; + +/* rule-7 loud-stop: `[N]tagged = [x, ...]` repeat-fill is not wired + * (cg_widen_tagged_store consumes the node and trashes AX, so the + * repeat re-store would write garbage). Both stages must FAIL the + * build loudly, not silently emit a broken array. */ +static const char *neg[] = { + "package main;\n" + "type cell = (i64 | bool);\n" + "export fn main() i32 = {\n" + "\tlet t: [3]cell = [99i64...];\n" + "\treturn 0;\n" + "};\n", +}; + +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/atag_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/atag_%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 2>/dev/null", + 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; +} + +/* build_should_fail — a `[N]tagged=[x,...]` repeat-fill must error on + * `driver` (rule-7 loud-stop); returns 0 when the build correctly + * FAILS, non-zero when it wrongly succeeded. */ +static int +build_should_fail(const char *driver, const char *src, int i) +{ + char s[64], tmpdir[64], cmd[1024]; + snprintf(s, sizeof s, "/tmp/atagn_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/atagn_%d_d_%d", getpid(), i); + + FILE *f = fopen(s, "wb"); + if (!f) return -1; + fputs(src, f); + fclose(f); + + mkdir(tmpdir, 0755); + snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null", + tmpdir, driver, s); + int rc = runwait(cmd); + unlink(s); + const char *base = strrchr(s, '/'); + base = base ? base + 1 : s; + char outbin[128]; + snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base); + char *dot = strrchr(outbin, '.'); + if (dot && strcmp(dot, ".ww") == 0) *dot = '\0'; + unlink(outbin); + rmdir(tmpdir); + return rc == 0 ? -1 : 0; /* build must NOT succeed */ +} + +/* asm_byte_identical — generate .s via cstage's w6c and wwstage's + * w6c_ww and diff. The under-copy depth is byte-id-blind (both stages + * stored 1 word pre-fix), so this row guards only the symmetric widen + * stride/op; the runtime rows above are the correctness net. */ +static int +asm_byte_identical(const char *bin, const struct row *r, int i) +{ + char src[64], cs[64], ws[64], cmd[1024]; + snprintf(src, sizeof src, "/tmp/atag_asm_%d_%d.ww", getpid(), i); + snprintf(cs, sizeof cs, "/tmp/atag_asm_%d_%d_c.s", getpid(), i); + snprintf(ws, sizeof ws, "/tmp/atag_asm_%d_%d_w.s", getpid(), i); + + FILE *f = fopen(src, "wb"); + if (!f) return -1; + fputs(r->src, f); + fclose(f); + + snprintf(cmd, sizeof cmd, "%s/w6c -o %s %s 2>/dev/null", bin, cs, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c errored\n", r->label); + unlink(src); + return -1; + } + snprintf(cmd, sizeof cmd, "%s/w6c_ww -o %s %s 2>/dev/null", + bin, ws, src); + if (runwait(cmd) != 0) { + fprintf(stderr, "row[%s]: w6c_ww errored\n", r->label); + unlink(src); unlink(cs); + return -1; + } + + FILE *fc = fopen(cs, "rb"); + FILE *fw = fopen(ws, "rb"); + int rc = 0; + if (!fc || !fw) { + rc = -1; + } else { + for (;;) { + int a = fgetc(fc); + int b = fgetc(fw); + if (a != b) { rc = -1; break; } + if (a == EOF) break; + } + } + if (fc) fclose(fc); + if (fw) fclose(fw); + if (rc != 0) + fprintf(stderr, "row[%s]: cstage vs wwstage asm differs\n", + r->label); + unlink(src); unlink(cs); unlink(ws); + return rc; +} + +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]; + snprintf(cdrv, sizeof cdrv, "%s/ww", bin); + char wdrv[1024]; + 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 nn = (int)(sizeof neg / sizeof neg[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, "arr_tagged_elem: 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, + "arr_tagged_elem[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + for (int i = 0; i < nn; i++) { + total++; + if (build_should_fail(drivers[d].path, neg[i], + 100 + i) != 0) { + fprintf(stderr, + "arr_tagged_elem[%s][neg%d]: built ok, " + "expected a loud error (rule-7)\n", + drivers[d].name, i); + fail++; + } + } + } + + if (access(wdrv, X_OK) == 0) { + for (int i = 0; i < n; i++) { + total++; + if (asm_byte_identical(bin, &rows[i], i) != 0) + fail++; + } + } + + if (fail) { + fprintf(stderr, + "arr_tagged_elem: %d/%d fixtures failed\n", fail, total); + return 1; + } + printf("arr_tagged_elem: %d/%d ok\n", total, total); + return 0; +}