w6c+w6c_ww: box [N]tagged array-literal elements via the tagged-store path (fix #12)

A [N]tagged-union array-literal element fell through the is_agg
multi-word-copy path (STRUCT/ARRAY/TUPLE/str/slice only) to the scalar
1-word store: the raw value landed in word 0 (the tag slot) with no tag
written and no payload boxed, so a later match found no variant. Both
stages under-copied identically, so the copy-depth bug was byte-id-blind
— a stride-only fix would still store 1 word and pass the gate green on
both-wrong.

Route each tagged element through cg_widen_tagged_store / the N_LET "BP"
tagged-store wrapper — the same choke-point let-init, vararg gather and
struct-field stores already use — so boxing, tag-remap and zero-pad-to-
slot come for free. esz now comes from the stamped slot size (rule-13);
the wwstage narrow override only covered widths 1/2/4, leaving a 16/24B
tagged element on the wrong 8-byte sentinel stride. rule-7 loud-stops
the unwired `[N]tagged=[x...]` repeat-fill (the widen call consumes the
node and trashes AX).

test/wcc/685: table-driven runtime readback (106/42/13) + a build-fail
row for the repeat-fill loud-stop, both stages.
This commit is contained in:
2026-06-03 20:32:22 +09:00
parent 3f04ea15ef
commit 63142770de
6 changed files with 475 additions and 3 deletions

View File

@@ -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 \

View File

@@ -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). */

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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) {

View File

@@ -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 <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/wait.h>
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;
}