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:
7
Makefile
7
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 \
|
||||
|
||||
@@ -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). */
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
382
test/wcc/685_arr_tagged_elem.c
Normal file
382
test/wcc/685_arr_tagged_elem.c
Normal 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;
|
||||
}
|
||||
Reference in New Issue
Block a user