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_elem_field_write \
|
||||||
$(BIN)/test_arr_enum_elem \
|
$(BIN)/test_arr_enum_elem \
|
||||||
$(BIN)/test_arr_strslice_elem \
|
$(BIN)/test_arr_strslice_elem \
|
||||||
|
$(BIN)/test_arr_tagged_elem \
|
||||||
$(BIN)/test_arr_infer_len \
|
$(BIN)/test_arr_infer_len \
|
||||||
$(BIN)/test_dot_str_chained_arg \
|
$(BIN)/test_dot_str_chained_arg \
|
||||||
$(BIN)/test_dot_slice_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)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(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)/test_dot_str_chained_arg: test/wcc/692_dot_str_chained_arg.c $(BIN)/ww \
|
||||||
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
$(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
|
int is_agg = esubu && (esubu->kind == TY_STRUCT
|
||||||
|| esubu->kind == TY_ARRAY
|
|| esubu->kind == TY_ARRAY
|
||||||
|| esubu->kind == TY_TUPLE);
|
|| 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);
|
int is_str_el = type_isstr(esub);
|
||||||
/* #20/#270 str-slice arm: a slice element is a 24B
|
/* #20/#270 str-slice arm: a slice element is a 24B
|
||||||
* {ptr,len,cap} header just like str; cgexpr lowers it
|
* {ptr,len,cap} header just like str; cgexpr lowers it
|
||||||
@@ -9144,6 +9151,13 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
idx++;
|
idx++;
|
||||||
continue;
|
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);
|
cgexpr(c, e, *locals);
|
||||||
if (is_str_el || is_slice_el) {
|
if (is_str_el || is_slice_el) {
|
||||||
ins2(c, A_MOVQ, areg(D_AX),
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
@@ -9165,6 +9179,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
if (repeat && is_agg)
|
if (repeat && is_agg)
|
||||||
fatal("#270-1c: `...` repeat of an aggregate "
|
fatal("#270-1c: `...` repeat of an aggregate "
|
||||||
"array-literal element not wired (rule-7)");
|
"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) {
|
if (repeat && last) {
|
||||||
/* fill remaining slots with the value still in
|
/* fill remaining slots with the value still in
|
||||||
* AX (and BX for str). */
|
* AX (and BX for str). */
|
||||||
|
|||||||
@@ -29634,6 +29634,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let isslicel: bool = esubti != nil
|
let isslicel: bool = esubti != nil
|
||||||
&& esubti.kind == tykind.TY_SLICE;
|
&& esubti.kind == tykind.TY_SLICE;
|
||||||
if (isslicel) { esz = esubti.size: i32; };
|
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
|
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
// 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.write(2, m1c.ptr, m1c.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
}; };
|
}; };
|
||||||
|
} else { if (istaggedel) {
|
||||||
|
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
if (isstrel || isslicel) {
|
if (isstrel || isslicel) {
|
||||||
@@ -29746,7 +29759,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitoff((off + idx * esz): i64);
|
emitoff((off + idx * esz): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
}; };
|
}; };
|
||||||
};
|
}; };
|
||||||
idx += 1;
|
idx += 1;
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
@@ -29756,6 +29769,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
os.write(2, m1cr.ptr, m1cr.len: u64);
|
os.write(2, m1cr.ptr, m1cr.len: u64);
|
||||||
os.exit(1);
|
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;
|
// AX (and BX for str) still holds the last stored value;
|
||||||
// fill remaining slots up to the declared length with it.
|
// fill remaining slots up to the declared length with it.
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
|
|||||||
@@ -1783,6 +1783,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let isslicel: bool = esubti != nil
|
let isslicel: bool = esubti != nil
|
||||||
&& esubti.kind == tykind.TY_SLICE;
|
&& esubti.kind == tykind.TY_SLICE;
|
||||||
if (isslicel) { esz = esubti.size: i32; };
|
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
|
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
// 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.write(2, m1c.ptr, m1c.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
}; };
|
}; };
|
||||||
|
} else { if (istaggedel) {
|
||||||
|
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
if (isstrel || isslicel) {
|
if (isstrel || isslicel) {
|
||||||
@@ -1895,7 +1908,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitoff((off + idx * esz): i64);
|
emitoff((off + idx * esz): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
}; };
|
}; };
|
||||||
};
|
}; };
|
||||||
idx += 1;
|
idx += 1;
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
@@ -1905,6 +1918,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
os.write(2, m1cr.ptr, m1cr.len: u64);
|
os.write(2, m1cr.ptr, m1cr.len: u64);
|
||||||
os.exit(1);
|
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;
|
// AX (and BX for str) still holds the last stored value;
|
||||||
// fill remaining slots up to the declared length with it.
|
// fill remaining slots up to the declared length with it.
|
||||||
if (repeat) {
|
if (repeat) {
|
||||||
|
|||||||
@@ -29634,6 +29634,17 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
let isslicel: bool = esubti != nil
|
let isslicel: bool = esubti != nil
|
||||||
&& esubti.kind == tykind.TY_SLICE;
|
&& esubti.kind == tykind.TY_SLICE;
|
||||||
if (isslicel) { esz = esubti.size: i32; };
|
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
|
// #8: a named-narrow element (`[N]tk`, tk = enum i32) is
|
||||||
// neither a builtin prim (primsize=0 above, so esz stayed
|
// neither a builtin prim (primsize=0 above, so esz stayed
|
||||||
// the 8 sentinel) nor an aggregate, so the scalar store kept
|
// 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.write(2, m1c.ptr, m1c.len: u64);
|
||||||
os.exit(1);
|
os.exit(1);
|
||||||
}; };
|
}; };
|
||||||
|
} else { if (istaggedel) {
|
||||||
|
cgwidentaggedstore(c, esubti, e, "BP", off + idx * esz, esz);
|
||||||
} else {
|
} else {
|
||||||
cgexpr(c, e);
|
cgexpr(c, e);
|
||||||
if (isstrel || isslicel) {
|
if (isstrel || isslicel) {
|
||||||
@@ -29746,7 +29759,7 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
emitoff((off + idx * esz): i64);
|
emitoff((off + idx * esz): i64);
|
||||||
emitline("(BP)\n");
|
emitline("(BP)\n");
|
||||||
}; };
|
}; };
|
||||||
};
|
}; };
|
||||||
idx += 1;
|
idx += 1;
|
||||||
e = e.next;
|
e = e.next;
|
||||||
};
|
};
|
||||||
@@ -29756,6 +29769,14 @@ fn cglet(c: *cgen, n: *node) void = {
|
|||||||
os.write(2, m1cr.ptr, m1cr.len: u64);
|
os.write(2, m1cr.ptr, m1cr.len: u64);
|
||||||
os.exit(1);
|
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;
|
// AX (and BX for str) still holds the last stored value;
|
||||||
// fill remaining slots up to the declared length with it.
|
// fill remaining slots up to the declared length with it.
|
||||||
if (repeat) {
|
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