test: port the amp-fn LEAQ observer to ww; retire 764_amp_fn_ident
The five symmetric rows compile their r764_amp_fn_ident_* fixture sources in place (runtime owned by those fixtures; pre-fix junk-store still exits 0, so the LEAQ window is the discriminator) and keep the cs==ws byte-id leg. The cross-module row stays cstage-only pending #184; its wwstage/byte-id legs graduate with that task.
This commit is contained in:
3
Makefile
3
Makefile
@@ -386,7 +386,8 @@ XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
|
||||
# test-compiler beside the surviving residual carriers.
|
||||
ASM_WW_TESTS = test/asm/modshadow_test.ww test/asm/sret_test.ww \
|
||||
test/asm/callarg_test.ww test/asm/chain_test.ww \
|
||||
test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww
|
||||
test/asm/dataemit_test.ww test/asm/matchdispatch_test.ww \
|
||||
test/asm/ampfn_test.ww
|
||||
ASM_WW_TARGETS = $(ASM_WW_TESTS:%=wwtest/%)
|
||||
|
||||
# Ww-native assembler/linker OBJECT-level observers: single-file ww
|
||||
|
||||
127
test/asm/ampfn_test.ww
Normal file
127
test/asm/ampfn_test.ww
Normal file
@@ -0,0 +1,127 @@
|
||||
package ampfn_test;
|
||||
|
||||
// Direct-w6c asm-window gate over `&fn` address-of lowering (#180).
|
||||
// Port of the retired native carrier test/wcc/764_amp_fn_ident.c;
|
||||
// every unowned assertion preserved. Runtime for the five symmetric
|
||||
// rows is owned by the r764_amp_fn_ident_* fixtures (their sources
|
||||
// are compiled here IN PLACE so fixture and window can never drift);
|
||||
// pre-fix those fixtures still exit 0 on the junk store — the LEAQ
|
||||
// presence below is the actual discriminator the corpus cannot own.
|
||||
//
|
||||
// Rows 1-5: the cstage .s must contain the row's `LEAQ <sym>(SB)`
|
||||
// line (absent pre-#180: the cgen N_UN TK_AMP IDENT arm had no TY_FN
|
||||
// branch, so the assign site stored stale AX junk), and cstage vs
|
||||
// wwstage .s must be byte-identical (rule 10; the carrier compared
|
||||
// driver __root.s — same emitter output, direct w6c form per the
|
||||
// residual audit).
|
||||
//
|
||||
// cross_module: `&pkg.fn` over a real import tree. CSTAGE-ONLY —
|
||||
// wwstage bails asserttyped on the N_UN TK_AMP N_DOT-mod-ident shape
|
||||
// (open #184); its build/run and byte-id legs graduate when #184
|
||||
// lifts. The M1 path-mangled `LEAQ wcamffn764mod.somefn(SB)` and the
|
||||
// build+run exit 0 are pinned; separate fresh trees keep the -S
|
||||
// compile and the full build from sharing a stale sepwork.
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("ampfn FAIL: ", label, " -- ", why, "\n");
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
assert(false);
|
||||
};
|
||||
|
||||
fn tmo() time.duration = {
|
||||
return (180i64 * (time.second: i64)): time.duration;
|
||||
};
|
||||
|
||||
fn emit(td: str, label: str, stage: str, drv: str, src: str,
|
||||
outname: str) void = {
|
||||
let av: []str = [drv, "-o", strings.concat(td, "/", outname), src];
|
||||
let co: testenv.commandout;
|
||||
testenv.runcommand(td, td, stage, av, tmo(), &co);
|
||||
if (co.termination != exec.termination.EXIT || co.code != 0) {
|
||||
fail(label, strings.concat(stage, " compile failed"));
|
||||
};
|
||||
};
|
||||
|
||||
fn leaqrow(label: str, fixture: str, needle: str) void = {
|
||||
let src: str = strings.concat(testenv.repo(),
|
||||
"/test/wcc/data/r764_amp_fn_ident_", fixture, "/case.ww");
|
||||
let td: str = testenv.fresh();
|
||||
emit(td, label, "cstage", testenv.driver("w6c"), src, "cs.s");
|
||||
emit(td, label, "wwstage", testenv.driver("w6c_ww"), src, "ws.s");
|
||||
let cs: str = testenv.readfile(strings.concat(td, "/cs.s"));
|
||||
let ws: str = testenv.readfile(strings.concat(td, "/ws.s"));
|
||||
if (!testenv.has(cs, needle)) {
|
||||
fail(label, strings.concat("missing `", needle,
|
||||
"` in cstage .s (silent TK_AMP IDENT drop)"));
|
||||
};
|
||||
if (!testenv.same(cs, ws)) {
|
||||
fail(label, "cstage vs wwstage asm differs");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
@test fn ampfnleaq() void = {
|
||||
leaqrow("minimal", "minimal", "LEAQ\tmain.add1(SB)");
|
||||
leaqrow("branched_callee", "branched", "LEAQ\tmain.bb(SB)");
|
||||
leaqrow("alias_chain", "alias_chain", "LEAQ\tmain.add1(SB)");
|
||||
leaqrow("fn_with_args", "args", "LEAQ\tmain.many(SB)");
|
||||
leaqrow("fn_tuple_return", "tuple_return", "LEAQ\tmain.pair(SB)");
|
||||
};
|
||||
|
||||
fn xmodtree() str = {
|
||||
let td: str = testenv.fresh();
|
||||
assert(os.mkdir(strings.concat(td, "/wcamffn764mod"), 493) == 0);
|
||||
testenv.writefile(
|
||||
strings.concat(td, "/wcamffn764mod/wcamffn764mod.ww"),
|
||||
strings.concat(
|
||||
"package wcamffn764mod;\n",
|
||||
"export fn somefn(x: i32) i32 = { return x + 1; };\n"));
|
||||
testenv.writefile(strings.concat(td, "/main764.ww"), strings.concat(
|
||||
"package main;\n",
|
||||
"import wcamffn764mod;\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let f = &wcamffn764mod.somefn;\n",
|
||||
" return 0;\n",
|
||||
"};\n"));
|
||||
return td;
|
||||
};
|
||||
|
||||
fn runcode(dir: str, name: str, argv: []str) i32 = {
|
||||
let co: testenv.commandout;
|
||||
testenv.runcommand(dir, dir, name, argv, tmo(), &co);
|
||||
if (co.termination != exec.termination.EXIT) { return -1; };
|
||||
return co.code;
|
||||
};
|
||||
|
||||
@test fn crossmodule() void = {
|
||||
let tda: str = xmodtree();
|
||||
let av: []str = [testenv.driver("ww"), "build", "-S", "-o",
|
||||
strings.concat(tda, "/main764"), "main764.ww"];
|
||||
if (runcode(tda, "compile", av) != 0) {
|
||||
fail("cross_module", "cstage ww build -S failed");
|
||||
};
|
||||
let s: str = testenv.readfile(
|
||||
strings.concat(tda, "/main764.sepwork/__root.s"));
|
||||
if (!testenv.has(s, "LEAQ\twcamffn764mod.somefn(SB)")) {
|
||||
fail("cross_module",
|
||||
"missing path-mangled LEAQ wcamffn764mod.somefn(SB)");
|
||||
};
|
||||
testenv.clean(tda);
|
||||
let tdb: str = xmodtree();
|
||||
let bav: []str = [testenv.driver("ww"), "build", "-o",
|
||||
strings.concat(tdb, "/main764"), "main764.ww"];
|
||||
if (runcode(tdb, "build", bav) != 0) {
|
||||
fail("cross_module", "cstage ww build failed");
|
||||
};
|
||||
let rav: []str = [strings.concat(tdb, "/main764")];
|
||||
if (runcode(tdb, "run", rav) != 0) {
|
||||
fail("cross_module", "run-exit != 0");
|
||||
};
|
||||
testenv.clean(tdb);
|
||||
};
|
||||
@@ -1,445 +0,0 @@
|
||||
/*
|
||||
* 764_amp_fn_ident — root-cause lock for project #180. Pre-fix master
|
||||
* (caca68e baseline), the cgen N_UN TK_AMP arm fell through silently
|
||||
* when the operand was an N_IDENT naming a top-level function: the
|
||||
* arm had branches for off!=0 (local), let_islet, def_isstructdef/
|
||||
* arraydef/scalardef and a def_isanydef fatal, but NO TY_FN arm. The
|
||||
* store at the assign site picked up whatever AX held from prior
|
||||
* code (often a stale argument register), so `let f = &add1` wrote
|
||||
* junk into f. A subsequent `(*f)(...)` then jumped through that
|
||||
* junk and segfaulted. Sister-bug at cgen.c:2330 already had the
|
||||
* TY_FN arm for the value-read of a bare ident (`let f = add1;` —
|
||||
* though that ww-side spelling is rejected by the checker today);
|
||||
* #180 adds the address-of twin.
|
||||
*
|
||||
* Fix: cmd/w6c/cgen.c N_UN TK_AMP IDENT inserts a TY_FN branch
|
||||
* before the let/def cascade — `LEAQ mafn(opnd->str, c->cur_mod), AX`
|
||||
* — mirror of the read-arm at line 2330. Selfhost twin in selfhost/
|
||||
* cmd/wcc/cgenexpr.ww cgun TK_AMP IDENT uses `fnretlookup(c, nm) !=
|
||||
* nil` as the analogous predicate (cstage tracks fn-ness via Type;
|
||||
* wwstage tracks via the fnret registry — both stages resolve to
|
||||
* the same LEAQ on byte-id).
|
||||
*
|
||||
* Phase 1 cross-mod probe (ken's mandate): `&pkg.fn` on CSTAGE
|
||||
* already flows through the N_DOT TK_AMP branch (cgen.c:2477-2493)
|
||||
* and emits the correct LEAQ via mafn(opnd->str, opnd->lhs->str) —
|
||||
* verdict = FINE for cstage. WWSTAGE however bails asserttyped on
|
||||
* the same shape (`un main.combined.ww:7`), a sibling checker gap
|
||||
* filed as project #184 (wwstage N_UN TK_AMP N_DOT-mod-ident type_
|
||||
* stamp missing). Row 6 below therefore runs CSTAGE-ONLY to lock
|
||||
* the working cstage behaviour; wwstage cross-mod is gated on the
|
||||
* stage_mask field and skipped until #184 lifts the bail.
|
||||
*
|
||||
* Coverage (drew option (b) — exercise the address-of without the
|
||||
* deref-call; runtime coverage for `(*f)(...)` deferred to project
|
||||
* #181's probe, which lifts the wwstage asserttyped bail):
|
||||
* 1. minimal — `let f = &add1`
|
||||
* 2. branched callee — conditionally pick one of two fns
|
||||
* 3. alias chain — `let f = &fn; let g = f`
|
||||
* 4. fn-with-args — multiple scalar + ptr params
|
||||
* 5. fn-with-tuple-return — (i64, str) return shape
|
||||
* 6. cross-module — `let f = &pkg.fn` (Phase 1 = FINE)
|
||||
*
|
||||
* Runtime for the five symmetric rows now lives in r764_* fixtures.
|
||||
* This artifact wrapper retains:
|
||||
* a. cstage .s contains the expected `LEAQ <fnname>(SB)` line —
|
||||
* pre-fix that line is absent (silent drop).
|
||||
* b. cstage .s == wwstage .s byte-identical — symmetry gate per
|
||||
* CLAUDE.md rule 10.
|
||||
* c. the cross-module C-stage-only runtime gate pending #184.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means either the cgen
|
||||
* TK_AMP IDENT TY_FN arm regressed, or stage symmetry drifted.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.h>
|
||||
#include <unistd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/wait.h>
|
||||
#include "wwtestpkg.h"
|
||||
|
||||
static int
|
||||
runwait(const char *cmd)
|
||||
{
|
||||
int rc = system(cmd);
|
||||
if (rc == -1) return -1;
|
||||
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||
return -1;
|
||||
}
|
||||
|
||||
/* stage_mask bits — controls per-row which stages this fixture
|
||||
* gates. Row 6 (cross_module) is cstage-only until project #184
|
||||
* lifts the wwstage asserttyped bail on `&mod.fn`. */
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
/* Symmetric rows use their corpus source as the sole fixture body. */
|
||||
const char *fixture;
|
||||
/* Source written into <tmpdir>/<basename>.ww; for cross-mod
|
||||
* (row 6) the secondary module lives at <tmpdir>/<modname>/
|
||||
* <modname>.ww. modname/modsrc NULL for single-file rows. */
|
||||
const char *src;
|
||||
const char *modname;
|
||||
const char *modsrc;
|
||||
/* expected_leaq: the LEAQ <sym>(SB) symbol expected in the
|
||||
* cstage emitted .s. Pre-fix this line is absent on rows 1-5.
|
||||
* Row 6 already worked via the N_DOT TK_AMP path. */
|
||||
const char *expected_leaq;
|
||||
int stage_mask;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "minimal",
|
||||
"test/wcc/data/r764_amp_fn_ident_minimal/case.ww",
|
||||
NULL,
|
||||
NULL, NULL,
|
||||
"LEAQ\tmain.add1(SB)",
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "branched_callee",
|
||||
"test/wcc/data/r764_amp_fn_ident_branched/case.ww",
|
||||
NULL,
|
||||
NULL, NULL,
|
||||
"LEAQ\tmain.bb(SB)",
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "alias_chain",
|
||||
"test/wcc/data/r764_amp_fn_ident_alias_chain/case.ww",
|
||||
NULL,
|
||||
NULL, NULL,
|
||||
"LEAQ\tmain.add1(SB)",
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "fn_with_args",
|
||||
"test/wcc/data/r764_amp_fn_ident_args/case.ww",
|
||||
NULL,
|
||||
NULL, NULL,
|
||||
"LEAQ\tmain.many(SB)",
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "fn_tuple_return",
|
||||
"test/wcc/data/r764_amp_fn_ident_tuple_return/case.ww",
|
||||
NULL,
|
||||
NULL, NULL,
|
||||
"LEAQ\tmain.pair(SB)",
|
||||
STAGE_CS | STAGE_WW },
|
||||
/* Cross-mod: cstage emits the LEAQ via N_DOT TK_AMP (already
|
||||
* working pre-#180). Wwstage bails asserttyped on the same
|
||||
* shape — filed as #184; this row stays cstage-only until that
|
||||
* lifts. M1 #22: `wcamffn764mod` is a directory package, so its
|
||||
* exported `somefn` path-mangles to `wcamffn764mod.somefn` (the
|
||||
* pre-M1 bare `somefn` is gone, mirroring 989_m1mangle_sym); the
|
||||
* &-of hint routes through use_hint and the LEAQ matches the
|
||||
* mangled definition. */
|
||||
{ "cross_module",
|
||||
NULL,
|
||||
"import wcamffn764mod;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let f = &wcamffn764mod.somefn;\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
"wcamffn764mod",
|
||||
"package wcamffn764mod;\n"
|
||||
"export fn somefn(x: i32) i32 = { return x + 1; };\n",
|
||||
"LEAQ\twcamffn764mod.somefn(SB)",
|
||||
STAGE_CS },
|
||||
};
|
||||
|
||||
static int cleanup_sources(const struct row *, const char *, const char *, int);
|
||||
|
||||
/* write_sources — copies a migrated fixture into an isolated package-main
|
||||
* root, or writes the one asymmetric cross-module row into that tree. */
|
||||
static int
|
||||
write_sources(const struct row *r, char *src, size_t srcsz,
|
||||
char *tmpdir, size_t tdsz, int seq, char *base_out, size_t basz)
|
||||
{
|
||||
(void)seq;
|
||||
int moddir_owned = 0;
|
||||
snprintf(tmpdir, tdsz, "/tmp/wcamffn_XXXXXX");
|
||||
if (mkdtemp(tmpdir) == NULL) return -1;
|
||||
snprintf(src, srcsz, "%s/main764.ww", tmpdir);
|
||||
snprintf(base_out, basz, "main764");
|
||||
if (r->fixture != NULL) {
|
||||
FILE *in = fopen(r->fixture, "rb");
|
||||
if (in == NULL) goto fail;
|
||||
if (fseek(in, 0, SEEK_END) != 0) { fclose(in); goto fail; }
|
||||
long n = ftell(in);
|
||||
if (n < 0 || fseek(in, 0, SEEK_SET) != 0) {
|
||||
fclose(in);
|
||||
goto fail;
|
||||
}
|
||||
char *body = malloc((size_t)n + 1);
|
||||
if (body == NULL) { fclose(in); goto fail; }
|
||||
size_t got = fread(body, 1, (size_t)n, in);
|
||||
int readerr = ferror(in);
|
||||
fclose(in);
|
||||
if (got != (size_t)n || readerr) { free(body); goto fail; }
|
||||
body[got] = '\0';
|
||||
|
||||
FILE *out = fopen(src, "wb");
|
||||
if (out == NULL) { free(body); goto fail; }
|
||||
wwtest_fputs(body, out);
|
||||
int wr = ferror(out) ? -1 : 0;
|
||||
free(body);
|
||||
if (fclose(out) != 0) wr = -1;
|
||||
if (wr != 0) goto fail;
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (r->modname != NULL) {
|
||||
char moddir[256], modfile[256];
|
||||
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
|
||||
snprintf(modfile, sizeof modfile, "%s/%s.ww",
|
||||
moddir, r->modname);
|
||||
if (mkdir(moddir, 0755) != 0) goto fail;
|
||||
moddir_owned = 1;
|
||||
FILE *mf = fopen(modfile, "wb");
|
||||
if (!mf) goto fail;
|
||||
wwtest_fputs(r->modsrc, mf);
|
||||
int wr = ferror(mf) ? -1 : 0;
|
||||
if (fclose(mf) != 0) wr = -1;
|
||||
if (wr != 0) goto fail;
|
||||
}
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) goto fail;
|
||||
wwtest_fputs(r->src, f);
|
||||
int wr = ferror(f) ? -1 : 0;
|
||||
if (fclose(f) != 0) wr = -1;
|
||||
if (wr != 0) goto fail;
|
||||
return 0;
|
||||
|
||||
fail:
|
||||
if (cleanup_sources(r, tmpdir, "main764", moddir_owned) != 0)
|
||||
fprintf(stderr, "amp_fn_ident: setup cleanup failed for %s\n", tmpdir);
|
||||
return -1;
|
||||
}
|
||||
|
||||
static int
|
||||
cleanup_sources(const struct row *r, const char *tmpdir, const char *base,
|
||||
int moddir_owned)
|
||||
{
|
||||
char p[512];
|
||||
int rc = 0;
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
/* #93: the sep scratch dir + the tmpdir-owned outputs. */
|
||||
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base);
|
||||
if (runwait(p) != 0) rc = -1;
|
||||
if (moddir_owned) {
|
||||
char moddir[256];
|
||||
snprintf(moddir, sizeof moddir, "%s/%s", tmpdir, r->modname);
|
||||
snprintf(p, sizeof p, "%s/%s.ww", moddir, r->modname);
|
||||
if (unlink(p) != 0 && errno != ENOENT) rc = -1;
|
||||
if (rmdir(moddir) != 0 && errno != ENOENT) rc = -1;
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) rc = -1;
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* compile_via_driver — runs compiler-only driver output in <tmpdir>; returns
|
||||
* the compiler exit code without assembling or linking. */
|
||||
static int
|
||||
compile_via_driver(const char *driver, const char *tmpdir, const char *src)
|
||||
{
|
||||
/* #93 sep layout: `-o <src-stem>` emits the asm to
|
||||
* <stem>.sepwork/__root.s (the post-flip layout); all outputs stay
|
||||
* under tmpdir. */
|
||||
char cmd[2048];
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -S -o %s/main764 %s "
|
||||
"2>/dev/null", tmpdir, driver, tmpdir, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
||||
{
|
||||
char stem[512], cmd[1280];
|
||||
snprintf(stem, sizeof stem, "%s", src);
|
||||
char *dot = strrchr(stem, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && timeout 180 %s build -o %s %s "
|
||||
"2>/dev/null", tmpdir, driver, stem, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
/* run_row — full build+run gate via a ww driver. Returns 0 if the
|
||||
* binary builds and exits 0, else -1. */
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int seq)
|
||||
{
|
||||
char src[256], tmpdir[256], base[64], outbin[512];
|
||||
if (write_sources(r, src, sizeof src, tmpdir, sizeof tmpdir,
|
||||
seq, base, sizeof base) != 0)
|
||||
return -1;
|
||||
int rc = -1;
|
||||
if (build_via_driver(driver, tmpdir, src) == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
if (runwait(outbin) == 0) rc = 0;
|
||||
}
|
||||
if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) {
|
||||
fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* file_contains — true if file <path> has at least one line containing
|
||||
* <needle>. */
|
||||
static int
|
||||
file_contains(const char *path, const char *needle)
|
||||
{
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f) return 0;
|
||||
char buf[1024];
|
||||
int found = 0;
|
||||
while (fgets(buf, sizeof buf, f) != NULL) {
|
||||
if (strstr(buf, needle) != NULL) { found = 1; break; }
|
||||
}
|
||||
fclose(f);
|
||||
return found;
|
||||
}
|
||||
|
||||
/* check_leaq — build the row via cstage, scan the emitted .s for the
|
||||
* expected LEAQ line. Returns 0 if present, -1 otherwise. */
|
||||
static int
|
||||
check_leaq(const char *driver, const struct row *r, int seq)
|
||||
{
|
||||
char src[256], tmpdir[256], base[64], asmf[512];
|
||||
if (write_sources(r, src, sizeof src, tmpdir, sizeof tmpdir,
|
||||
seq, base, sizeof base) != 0)
|
||||
return -1;
|
||||
int rc = -1;
|
||||
if (compile_via_driver(driver, tmpdir, src) == 0) {
|
||||
snprintf(asmf, sizeof asmf, "%s/%s.sepwork/__root.s", tmpdir, base);
|
||||
if (file_contains(asmf, r->expected_leaq)) rc = 0;
|
||||
}
|
||||
if (cleanup_sources(r, tmpdir, base, r->modname != NULL) != 0) {
|
||||
fprintf(stderr, "amp_fn_ident[%s]: cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — diff cstage vs wwstage .s for the row. */
|
||||
static int
|
||||
asm_byte_identical(const char *cdrv, const char *wdrv,
|
||||
const struct row *r, int seq)
|
||||
{
|
||||
char src[256], tdc[256], tdw[256], base[64], cs[512], ws[512];
|
||||
if (write_sources(r, src, sizeof src, tdc, sizeof tdc,
|
||||
seq, base, sizeof base) != 0)
|
||||
return -1;
|
||||
int rc = -1;
|
||||
int have_tdw = 0;
|
||||
if (compile_via_driver(cdrv, tdc, src) != 0) goto out;
|
||||
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.s", tdc, base);
|
||||
|
||||
/* Build a parallel tree so each driver's explicitly retained
|
||||
* <stem>.sepwork/__root.s remains available for comparison. */
|
||||
if (write_sources(r, src, sizeof src, tdw, sizeof tdw,
|
||||
seq + 100000, base, sizeof base) != 0)
|
||||
goto out;
|
||||
have_tdw = 1;
|
||||
if (compile_via_driver(wdrv, tdw, src) != 0) {
|
||||
goto out;
|
||||
}
|
||||
snprintf(ws, sizeof ws, "%s/%s.sepwork/__root.s", tdw, base);
|
||||
|
||||
FILE *fc = fopen(cs, "rb");
|
||||
FILE *fw = fopen(ws, "rb");
|
||||
if (fc && fw) {
|
||||
rc = 0;
|
||||
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);
|
||||
out:
|
||||
if (have_tdw
|
||||
&& cleanup_sources(r, tdw, base, r->modname != NULL) != 0) {
|
||||
fprintf(stderr, "amp_fn_ident[%s]: ww cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
if (cleanup_sources(r, tdc, base, r->modname != NULL) != 0) {
|
||||
fprintf(stderr, "amp_fn_ident[%s]: c cleanup failed\n", r->label);
|
||||
rc = -1;
|
||||
}
|
||||
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], wdrv[1024];
|
||||
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||
|
||||
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||
int total = 0, fail = 0;
|
||||
int wwpresent = (access(wdrv, X_OK) == 0);
|
||||
|
||||
int seq = 0;
|
||||
|
||||
for (int i = 0; i < n; i++) {
|
||||
/* Symmetric runtime rows are owned by r764_* fixtures. Keep
|
||||
* only the cross-module C-stage-only runtime evidence here. */
|
||||
if ((rows[i].stage_mask & STAGE_CS)
|
||||
&& !(rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
if (run_row(cdrv, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"amp_fn_ident[cstage run][%s]: build/run failed\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
if (check_leaq(cdrv, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"amp_fn_ident[cstage leaq][%s]: missing `%s` in .s\n",
|
||||
rows[i].label, rows[i].expected_leaq);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"amp_fn_ident[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "amp_fn_ident: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "amp_fn_ident: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("amp_fn_ident: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user