selfhost+test: graduate wwstage sum-typed N_INDEX call-arg to tagged ABI (#12)
pushargsrev's widening detection was N_IDENT-only — N_INDEX of a sum-typed slice element fell through to the scalar widening branch, which hardcoded the param's first-variant tag (MOVQ $1, AX) and pushed AX as a single scalar word. Callees that match-dispatched on the runtime tag always ran the static-guess arm on garbage. cstage knew the arg's type via check.c so its widen[] flag stayed off and the natural-push tagged-arg arm pushed CX/DX/AX (high → low) high → low. wwstage now mirrors via two narrow arms in pushargsrev: the aistagged guard treats N_INDEX-of-sum-typed-element matching the param slot as already-tagged, and the natural-push fallthrough emits PUSHQ CX / DX / AX for the same shape. Both arms gate on istaggedtype(indexvaluetnode(arg)) so literal- and ident-source sum args stay on their existing paths. Sentinel 749_sumtype_forward table-drives the three forward shapes (N_INDEX, N_IDENT, literal) and asserts per-stage runtime plus a byte-id window over the callsite asm. Combined.ww regen for wwdump_ww and w6c_ww follows the cgen source change; smoke.combined.ww unaffected. Tests: 123/123 pass; bootstrap fixed point holds (ww2==ww3==ww4).
This commit is contained in:
7
Makefile
7
Makefile
@@ -283,6 +283,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_strdef_inline \
|
$(BIN)/test_strdef_inline \
|
||||||
$(BIN)/test_def_modqual_modshadow \
|
$(BIN)/test_def_modqual_modshadow \
|
||||||
$(BIN)/test_size_strategy_convergence \
|
$(BIN)/test_size_strategy_convergence \
|
||||||
|
$(BIN)/test_sumtype_forward \
|
||||||
$(BIN)/test_param_shadow_mod \
|
$(BIN)/test_param_shadow_mod \
|
||||||
$(BIN)/test_localoff_scope \
|
$(BIN)/test_localoff_scope \
|
||||||
$(BIN)/test_cast_enum_movl \
|
$(BIN)/test_cast_enum_movl \
|
||||||
@@ -706,6 +707,12 @@ $(BIN)/test_size_strategy_convergence: test/wcc/748_size_strategy_convergence.c
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_sumtype_forward: test/wcc/749_sumtype_forward.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_match_4arm_cross_module_run: test/wcc/929_match_4arm_cross_module_run.c \
|
$(BIN)/test_match_4arm_cross_module_run: test/wcc/929_match_4arm_cross_module_run.c \
|
||||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
$(BIN)/ww $(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 \
|
||||||
|
|||||||
@@ -7292,6 +7292,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
||||||
aistagged = true;
|
aistagged = true;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element —
|
||||||
|
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
|
||||||
|
// Without this gate the widening scalar branch
|
||||||
|
// hardcodes the param's first-variant tag and
|
||||||
|
// the callee reads a fixed arm on garbage.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
if (slotsize(c, etn) == slotsize(c, ptype)) {
|
||||||
|
aistagged = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!aistagged) {
|
if (!aistagged) {
|
||||||
widensz = slotsize(c, ptype);
|
widensz = slotsize(c, ptype);
|
||||||
let tagged: *node = resolvetagged(c, ptype);
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
@@ -7591,6 +7606,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + tcs / 8;
|
return rest + tcs / 8;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element. cgindex above left
|
||||||
|
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
|
||||||
|
// would only carry the tag word and drop the payload.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
let isz: i32 = slotsize(c, etn);
|
||||||
|
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
|
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
|
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + isz / 8;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -152,6 +152,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
||||||
aistagged = true;
|
aistagged = true;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element —
|
||||||
|
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
|
||||||
|
// Without this gate the widening scalar branch
|
||||||
|
// hardcodes the param's first-variant tag and
|
||||||
|
// the callee reads a fixed arm on garbage.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
if (slotsize(c, etn) == slotsize(c, ptype)) {
|
||||||
|
aistagged = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!aistagged) {
|
if (!aistagged) {
|
||||||
widensz = slotsize(c, ptype);
|
widensz = slotsize(c, ptype);
|
||||||
let tagged: *node = resolvetagged(c, ptype);
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
@@ -451,6 +466,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + tcs / 8;
|
return rest + tcs / 8;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element. cgindex above left
|
||||||
|
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
|
||||||
|
// would only carry the tag word and drop the payload.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
let isz: i32 = slotsize(c, etn);
|
||||||
|
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
|
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
|
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + isz / 8;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -7292,6 +7292,21 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
if (taggedcallslot(c, arg) == slotsize(c, ptype)) {
|
||||||
aistagged = true;
|
aistagged = true;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element —
|
||||||
|
// cgindex emits the same AX/DX/CX/R8 tagged ABI.
|
||||||
|
// Without this gate the widening scalar branch
|
||||||
|
// hardcodes the param's first-variant tag and
|
||||||
|
// the callee reads a fixed arm on garbage.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
if (slotsize(c, etn) == slotsize(c, ptype)) {
|
||||||
|
aistagged = true;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
if (!aistagged) {
|
if (!aistagged) {
|
||||||
widensz = slotsize(c, ptype);
|
widensz = slotsize(c, ptype);
|
||||||
let tagged: *node = resolvetagged(c, ptype);
|
let tagged: *node = resolvetagged(c, ptype);
|
||||||
@@ -7591,6 +7606,22 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
|||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + tcs / 8;
|
return rest + tcs / 8;
|
||||||
};
|
};
|
||||||
|
// #12: N_INDEX of a sum-typed slice element. cgindex above left
|
||||||
|
// the tagged-CALL ABI in AX/DX/CX/R8; the bare PUSHQ AX below
|
||||||
|
// would only carry the tag word and drop the payload.
|
||||||
|
if (arg.kind == nkind.N_INDEX) {
|
||||||
|
let etn: *node = indexvaluetnode(c, arg);
|
||||||
|
if (etn != nil) {
|
||||||
|
if (istaggedtype(c, etn)) {
|
||||||
|
let isz: i32 = slotsize(c, etn);
|
||||||
|
if (isz > 24) { emitline("\tPUSHQ\tR8\n"); };
|
||||||
|
if (isz > 16) { emitline("\tPUSHQ\tCX\n"); };
|
||||||
|
if (isz > 8) { emitline("\tPUSHQ\tDX\n"); };
|
||||||
|
emitline("\tPUSHQ\tAX\n");
|
||||||
|
return rest + isz / 8;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
emitline("\tPUSHQ\tAX\n");
|
emitline("\tPUSHQ\tAX\n");
|
||||||
return rest + 1;
|
return rest + 1;
|
||||||
};
|
};
|
||||||
|
|||||||
327
test/wcc/749_sumtype_forward.c
Normal file
327
test/wcc/749_sumtype_forward.c
Normal file
@@ -0,0 +1,327 @@
|
|||||||
|
/*
|
||||||
|
* 749_sumtype_forward — sentinel for #12.
|
||||||
|
*
|
||||||
|
* Pre-fix wwstage hardcoded the param's first-variant tag when
|
||||||
|
* forwarding a sum-typed variadic element to a scalar (T|U) param.
|
||||||
|
* cgindex emitted the tagged-CALL ABI (AX=tag, DX=word0, CX=word1[,
|
||||||
|
* R8=word2]) for `needles[i]` correctly, then pushargsrev's widening
|
||||||
|
* detection — keyed only on N_IDENT-source — fell into the scalar
|
||||||
|
* widening branch: XORQ DX,DX / PUSHQ DX / PUSHQ AX / MOVQ $1,AX /
|
||||||
|
* PUSHQ AX. The hardcoded `$1` made the callee always run the rune
|
||||||
|
* arm; payload was the slice's first ptr word reinterpreted as a rune.
|
||||||
|
*
|
||||||
|
* cstage knew the arg's static type via check.c (`a->type == p->type`)
|
||||||
|
* so the widen flag stayed off and the natural-push tagged-arg arm
|
||||||
|
* pushed CX/DX/AX.
|
||||||
|
*
|
||||||
|
* Fix: wwstage pushargsrev now treats `arg.kind == N_INDEX &&
|
||||||
|
* istaggedtype(indexvaluetnode(arg))` as already-tagged (matching
|
||||||
|
* slot size to the param) and pushes CX/DX/AX in the natural-push
|
||||||
|
* fallthrough.
|
||||||
|
*
|
||||||
|
* Rows assert per-stage runtime AND byte-id at the affected callsite.
|
||||||
|
*
|
||||||
|
* row | what it pins
|
||||||
|
* -------------------------------+--------------------------------
|
||||||
|
* forward_index_of_sum_variadic | #12 wedge. Pre-fix wwstage
|
||||||
|
* | returned 11 (helper saw garbage
|
||||||
|
* | rune); cstage returned 0.
|
||||||
|
* | Post-fix both return 0.
|
||||||
|
* forward_ident_sum | Sibling: forward an ident-source
|
||||||
|
* | sum local to a sum-typed scalar
|
||||||
|
* | param. Stays on the N_IDENT
|
||||||
|
* | aistagged path — non-regression.
|
||||||
|
* forward_literal_sum | Sibling: forward a literal-source
|
||||||
|
* | value (str) to a sum-typed scalar
|
||||||
|
* | param. Stays on the widening
|
||||||
|
* | path — non-regression.
|
||||||
|
*/
|
||||||
|
#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_cs;
|
||||||
|
int want_ws;
|
||||||
|
};
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
/* 1. Original #12 wedge. helper(needles[i]) where needles is
|
||||||
|
* (str|rune)... — N_INDEX of sum-typed slice element forwarded
|
||||||
|
* to a scalar (str|rune) param. pick("foo","world",'x') hits
|
||||||
|
* the 'x' rune at i=2, so helper returns true and pick returns
|
||||||
|
* true. main returns 0 on success. */
|
||||||
|
{ "forward_index_of_sum_variadic",
|
||||||
|
"package main;\n"
|
||||||
|
"fn helper(needle: (str | rune)) bool = {\n"
|
||||||
|
" match (needle) {\n"
|
||||||
|
" case let s: str => return s.len > 0;\n"
|
||||||
|
" case let r: rune => return r == 'x';\n"
|
||||||
|
" };\n"
|
||||||
|
" return false;\n"
|
||||||
|
"};\n"
|
||||||
|
"fn pick(needles: (str | rune)...) bool = {\n"
|
||||||
|
" let i: i32 = 0;\n"
|
||||||
|
" for (i < needles.len) {\n"
|
||||||
|
" if (helper(needles[i])) { return true; };\n"
|
||||||
|
" i += 1;\n"
|
||||||
|
" };\n"
|
||||||
|
" return false;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (!pick(\"foo\", \"world\", 'x')) { return 11; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0 },
|
||||||
|
|
||||||
|
/* 2. Ident-source sum-arg. needle is a (str|rune) local; helper
|
||||||
|
* call routes through the existing N_IDENT-aistagged path. The
|
||||||
|
* (str|rune) ident is built from a str literal so the rune arm
|
||||||
|
* shouldn't fire. */
|
||||||
|
{ "forward_ident_sum",
|
||||||
|
"package main;\n"
|
||||||
|
"fn helper(needle: (str | rune)) bool = {\n"
|
||||||
|
" match (needle) {\n"
|
||||||
|
" case let s: str => return s.len == 3;\n"
|
||||||
|
" case let r: rune => return false;\n"
|
||||||
|
" };\n"
|
||||||
|
" return false;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let needle: (str | rune) = \"foo\";\n"
|
||||||
|
" if (!helper(needle)) { return 11; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0 },
|
||||||
|
|
||||||
|
/* 3. Literal-source sum-arg. helper(\"foo\") widens the str at
|
||||||
|
* the call site. Stays on the widening path (param tagged, arg
|
||||||
|
* not tagged) and pushes a synthesised (tag=0, ptr, len) slot. */
|
||||||
|
{ "forward_literal_sum",
|
||||||
|
"package main;\n"
|
||||||
|
"fn helper(needle: (str | rune)) bool = {\n"
|
||||||
|
" match (needle) {\n"
|
||||||
|
" case let s: str => return s.len == 3;\n"
|
||||||
|
" case let r: rune => return false;\n"
|
||||||
|
" };\n"
|
||||||
|
" return false;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" if (!helper(\"foo\")) { return 11; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
build_with(const char *driver, const char *src_path, const char *tmpdir)
|
||||||
|
{
|
||||||
|
char cmd[1024];
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||||
|
tmpdir, driver, src_path);
|
||||||
|
return runwait(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
exec_bin(const char *bin)
|
||||||
|
{
|
||||||
|
return runwait(bin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* read_file slurps the .s file produced alongside the driver build,
|
||||||
|
* returning a malloc'd buffer (NUL-terminated) or NULL on error. */
|
||||||
|
static char *
|
||||||
|
read_file(const char *path, size_t *outsz)
|
||||||
|
{
|
||||||
|
FILE *f = fopen(path, "rb");
|
||||||
|
if (!f) return NULL;
|
||||||
|
fseek(f, 0, SEEK_END);
|
||||||
|
long n = ftell(f);
|
||||||
|
fseek(f, 0, SEEK_SET);
|
||||||
|
if (n < 0) { fclose(f); return NULL; }
|
||||||
|
char *buf = malloc((size_t)n + 1);
|
||||||
|
if (!buf) { fclose(f); return NULL; }
|
||||||
|
size_t got = fread(buf, 1, (size_t)n, f);
|
||||||
|
fclose(f);
|
||||||
|
buf[got] = '\0';
|
||||||
|
if (outsz) *outsz = got;
|
||||||
|
return buf;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* slice_at finds the line beginning with `CALL\tmain.helper(SB)` and
|
||||||
|
* returns a pointer to the start of the block four lines above (the
|
||||||
|
* three PUSHQ + the tag MOV in the pre-fix case, the three PUSHQ in
|
||||||
|
* the post-fix). For our byte-id check we compare a 64-byte window
|
||||||
|
* starting at the first newline above that anchor. */
|
||||||
|
static const char *
|
||||||
|
helper_callsite(const char *asm_buf)
|
||||||
|
{
|
||||||
|
const char *anchor = strstr(asm_buf, "CALL\tmain.helper(SB)");
|
||||||
|
if (!anchor) return NULL;
|
||||||
|
/* walk back ~12 lines so the slice straddles the args set-up. */
|
||||||
|
const char *p = anchor;
|
||||||
|
int back = 12;
|
||||||
|
while (back > 0 && p > asm_buf) {
|
||||||
|
p--;
|
||||||
|
if (*p == '\n') back--;
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
}
|
||||||
|
|
||||||
|
int
|
||||||
|
main(void)
|
||||||
|
{
|
||||||
|
const char *bin = getenv("BIN");
|
||||||
|
if (!bin) bin = "out/bin";
|
||||||
|
char absbin[512];
|
||||||
|
if (bin[0] != '/') {
|
||||||
|
char cwd[256];
|
||||||
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
||||||
|
snprintf(absbin, sizeof absbin, "%s/%s", cwd, bin);
|
||||||
|
bin = absbin;
|
||||||
|
}
|
||||||
|
|
||||||
|
char cdrv[640], wdrv[640];
|
||||||
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||||
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||||
|
int have_ww = (access(wdrv, X_OK) == 0);
|
||||||
|
|
||||||
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const struct row *r = &rows[i];
|
||||||
|
|
||||||
|
char src[64], tmpdir[64];
|
||||||
|
snprintf(src, sizeof src, "/tmp/sumtype_fwd_%d_%d.ww",
|
||||||
|
getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/sumtype_fwd_%d_d_%d",
|
||||||
|
getpid(), i);
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) { fail++; total++; continue; }
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
char outbin[128], outasm[160];
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
/* .s lands alongside the .ww source per `ww build`. */
|
||||||
|
snprintf(outasm, sizeof outasm, "%s", src);
|
||||||
|
char *dot2 = strrchr(outasm, '.');
|
||||||
|
if (dot2 && strcmp(dot2, ".ww") == 0) {
|
||||||
|
strcpy(dot2, ".s");
|
||||||
|
}
|
||||||
|
|
||||||
|
char *cs_asm = NULL;
|
||||||
|
size_t cs_n = 0;
|
||||||
|
|
||||||
|
/* Runtime parity (cstage). */
|
||||||
|
total++;
|
||||||
|
if (build_with(cdrv, src, tmpdir) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[cs][%s]: build failed\n",
|
||||||
|
r->label);
|
||||||
|
fail++;
|
||||||
|
} else {
|
||||||
|
int got = exec_bin(outbin);
|
||||||
|
if (got != r->want_cs) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[cs][%s]: rc=%d want=%d\n",
|
||||||
|
r->label, got, r->want_cs);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
cs_asm = read_file(outasm, &cs_n);
|
||||||
|
}
|
||||||
|
unlink(outbin);
|
||||||
|
|
||||||
|
/* Runtime parity (wwstage). */
|
||||||
|
char *ws_asm = NULL;
|
||||||
|
size_t ws_n = 0;
|
||||||
|
if (have_ww) {
|
||||||
|
total++;
|
||||||
|
if (build_with(wdrv, src, tmpdir) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[ws][%s]: build failed\n",
|
||||||
|
r->label);
|
||||||
|
fail++;
|
||||||
|
} else {
|
||||||
|
int got = exec_bin(outbin);
|
||||||
|
if (got != r->want_ws) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[ws][%s]: rc=%d want=%d\n",
|
||||||
|
r->label, got, r->want_ws);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
ws_asm = read_file(outasm, &ws_n);
|
||||||
|
}
|
||||||
|
unlink(outbin);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Byte-id of the helper(needles[i]) callsite window. Only
|
||||||
|
* the first row has a main.helper call; rows 2/3 also have
|
||||||
|
* one but a simpler shape — match them all for symmetry. */
|
||||||
|
if (cs_asm && ws_asm) {
|
||||||
|
total++;
|
||||||
|
const char *cs_site = helper_callsite(cs_asm);
|
||||||
|
const char *ws_site = helper_callsite(ws_asm);
|
||||||
|
if (!cs_site || !ws_site) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[bid][%s]: no callsite anchor\n",
|
||||||
|
r->label);
|
||||||
|
fail++;
|
||||||
|
} else if (strncmp(cs_site, ws_site, 96) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward[bid][%s]: callsite asm diverged\n",
|
||||||
|
r->label);
|
||||||
|
fprintf(stderr, " cs: %.96s\n", cs_site);
|
||||||
|
fprintf(stderr, " ws: %.96s\n", ws_site);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(cs_asm);
|
||||||
|
free(ws_asm);
|
||||||
|
|
||||||
|
char extra[160];
|
||||||
|
unlink(outasm);
|
||||||
|
snprintf(extra, sizeof extra, "%s", src);
|
||||||
|
char *d3 = strrchr(extra, '.');
|
||||||
|
if (d3 && strcmp(d3, ".ww") == 0) {
|
||||||
|
strcpy(d3, ".o");
|
||||||
|
unlink(extra);
|
||||||
|
}
|
||||||
|
snprintf(extra, sizeof extra, "%s", src);
|
||||||
|
d3 = strrchr(extra, '.');
|
||||||
|
if (d3 && strcmp(d3, ".ww") == 0) {
|
||||||
|
strcpy(d3, ".combined.ww");
|
||||||
|
unlink(extra);
|
||||||
|
}
|
||||||
|
unlink(src);
|
||||||
|
rmdir(tmpdir);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sumtype_forward: %d/%d rows failed\n", fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("sumtype_forward: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user