test: port the fn/mklabel mangle observers to ww
test/xmod/label_test.ww replaces 706_fnlabel_mangle.c and 750_mklabel_modscoped.c with every assertion preserved (fnlabelmangle fixture run-112 both stages; three mklabel rows with run exits and module-qualified label needles). Strengthened: fnlabel adds the cs==ww sepwork .s byte-id the carrier never asserted.
This commit is contained in:
3
Makefile
3
Makefile
@@ -372,7 +372,8 @@ SEP_WW_TARGETS = $(SEP_WW_TESTS:%=wwtest/%)
|
||||
# label-mangle runs, cross-module typecheck rejects, enumerator
|
||||
# capacity, directive adjacency). Compiler/driver gates like test/sep:
|
||||
# they run under test-compiler.
|
||||
XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww
|
||||
XMOD_WW_TESTS = test/xmod/collide_test.ww test/xmod/m1_test.ww \
|
||||
test/xmod/label_test.ww
|
||||
XMOD_WW_TARGETS = $(XMOD_WW_TESTS:%=wwtest/%)
|
||||
BOOTSTRAP_WRAPPER_SOURCES = test/wcc/950_selfcheck.c \
|
||||
test/wcc/991_w6a_ww.c \
|
||||
|
||||
@@ -1,158 +0,0 @@
|
||||
/*
|
||||
* 706_fnlabel_mangle — cgen mangles fn labels by module so two
|
||||
* modules can each `export fn ping` (and each define a private
|
||||
* `fn helper`) without colliding at link time.
|
||||
*
|
||||
* The fixture in test/wcc/data/fnlabelmangle/ pins all four
|
||||
* label-emit sites #9 / #12 touch:
|
||||
* mod{1,2}/mod{1,2}.ww
|
||||
* export fn ping = bareval + helper() + fpi();
|
||||
* fn helper — private, same-leaf across modules
|
||||
* fn fpi — `let h = helper; h();` pins LEAQ N_IDENT
|
||||
* pos.ww
|
||||
* let p1 = mod1.ping; let p2 = mod2.ping; // LEAQ N_DOT × 2
|
||||
* return mod1.ping() + mod2.ping() // CALL N_DOT × 2
|
||||
* + p1() + p2(); // through fn ptr
|
||||
*
|
||||
* mod1.ping = 3 + 11 + 11 = 25
|
||||
* mod2.ping = 5 + 13 + 13 = 31
|
||||
* total = 25 + 31 + 25 + 31 = 112
|
||||
*
|
||||
* Pre-#9 (cgen emitted bare `TEXT ping` for both modules' exports),
|
||||
* the linker collapsed both `ping` symbols and one dispatch landed
|
||||
* on the wrong body. The bare-IDENT helper / `let h = helper` call-
|
||||
* sites would also pick whichever module the lookup found first,
|
||||
* silently miscompiling fpi's intra-module call.
|
||||
*
|
||||
* Pre-#12 wwstage cgdot's `let p = mod.ping` fell through to the
|
||||
* MOVQ leaf(SB) module-qualified-value fallback, loading 8 bytes
|
||||
* of fn-prologue into AX; `p()` then jumped into mid-prologue
|
||||
* garbage. Cstage cgdot already had a TY_FN LEAQ branch from #9.
|
||||
*
|
||||
* Coverage notes:
|
||||
* - The skip rule's three remaining cases (@symbol / main / empty-
|
||||
* module) are not pinned by a dedicated row because every passing
|
||||
* run of `make test` already exercises them: the bootstrap and
|
||||
* stdlib pull through `@symbol("rt_syscall")` bindings (lib/fmt,
|
||||
* lib/log), every test program links a bare `main`, and the inline-
|
||||
* source tests (700, 701, ...) compile fixtures with no module
|
||||
* directive. A regression in any of those rules would cascade
|
||||
* across the suite, not show up here.
|
||||
*
|
||||
* Both stages run the positive case — cstage and wwstage cgen must
|
||||
* agree on the mangling rule for ww2/ww3/ww4 byte-identity to hold.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.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;
|
||||
}
|
||||
|
||||
static int
|
||||
run_pos(const char *driver, const char *fixdir, const char *tag)
|
||||
{
|
||||
/* Keep the output and caller-owned pos.sepwork outside the tracked
|
||||
* fixture tree; cd <fixdir> stays so pos.ww imports siblings mod1/mod2. */
|
||||
char td[1024];
|
||||
snprintf(td, sizeof td, "/tmp/fnlbl_%d_%s", getpid(), tag);
|
||||
if (mkdir(td, 0755) != 0) {
|
||||
fprintf(stderr, "fnlabel_mangle[%s]: mkdir %s: %s\n",
|
||||
tag, td, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
char bin[2048], sepdir[2048], rmcmd[2112];
|
||||
snprintf(bin, sizeof bin, "%s/pos", td);
|
||||
snprintf(sepdir, sizeof sepdir, "%s.sepwork", bin);
|
||||
snprintf(rmcmd, sizeof rmcmd, "rm -rf %s", sepdir);
|
||||
|
||||
char cmd[2048];
|
||||
int failed = 0, got;
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && %s build -o %s/pos pos.ww >/dev/null 2>&1",
|
||||
fixdir, driver, td);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"fnlabel_mangle[%s]: pos.ww build failed\n", tag);
|
||||
failed = 1;
|
||||
goto cleanup;
|
||||
}
|
||||
got = runwait(bin);
|
||||
if (got != 112) {
|
||||
fprintf(stderr,
|
||||
"fnlabel_mangle[%s]: pos.ww exit=%d want=112 — "
|
||||
"fn labels likely collapsed at link, or LEAQ-of-fn "
|
||||
"N_DOT branch missing in cgdot\n", tag, got);
|
||||
failed = 1;
|
||||
}
|
||||
|
||||
cleanup:
|
||||
{
|
||||
int cleanup_failed = 0;
|
||||
if (runwait(rmcmd) != 0) {
|
||||
fprintf(stderr, "fnlabel_mangle[%s]: remove %s failed\n",
|
||||
tag, sepdir);
|
||||
cleanup_failed = 1;
|
||||
}
|
||||
if (unlink(bin) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr, "fnlabel_mangle[%s]: unlink %s: %s\n",
|
||||
tag, bin, strerror(errno));
|
||||
cleanup_failed = 1;
|
||||
}
|
||||
if (rmdir(td) != 0) {
|
||||
fprintf(stderr, "fnlabel_mangle[%s]: rmdir %s: %s\n",
|
||||
tag, td, strerror(errno));
|
||||
cleanup_failed = 1;
|
||||
}
|
||||
if (!failed && cleanup_failed) failed = 1;
|
||||
}
|
||||
return failed;
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
char fixdir[1024];
|
||||
if (getcwd(fixdir, sizeof fixdir) == NULL) return 1;
|
||||
size_t cwd_n = strlen(fixdir);
|
||||
const char *rel = "/test/wcc/data/fnlabelmangle";
|
||||
if (cwd_n + strlen(rel) + 1 >= sizeof fixdir) return 1;
|
||||
memcpy(fixdir + cwd_n, rel, strlen(rel) + 1);
|
||||
|
||||
int fail = 0;
|
||||
fail += run_pos(cdrv, fixdir, "cstage");
|
||||
fail += run_pos(wdrv, fixdir, "wwstage");
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"fnlabel_mangle: %d case(s) failed\n", fail);
|
||||
return 1;
|
||||
}
|
||||
printf("fnlabel_mangle: 2/2 ok\n");
|
||||
return 0;
|
||||
}
|
||||
@@ -1,399 +0,0 @@
|
||||
/*
|
||||
* 750_mklabel_modscoped — sentinel for task #13. mklabel pre-fix
|
||||
* formatted labels as `<fnname>_<prefix>_<seq>` (just the leaf). Two
|
||||
* top-level fns in different modules sharing a leaf (e.g. `bytes.index`
|
||||
* + `strings.index`) emitted colliding labels into the same combined
|
||||
* .s; w6a's a_intern collapsed the names and the LAST definition's
|
||||
* `s->addr` won — earlier fns' JNE/JMP rel32 resolved into the later
|
||||
* fn's body. Silent miscompile in cstage; wwstage's mklabel had the
|
||||
* same shape so the symmetric path was equally exposed (the wedge
|
||||
* just happened to fire under cstage's combined link first).
|
||||
*
|
||||
* Fix (rule 10): `<module>.<fnname>_<prefix>_<seq>` mirrors the
|
||||
* existing TEXT-directive convention (`TEXT bytes.index,$N`). w6a
|
||||
* already accepts `.` in label-cont (lex.c:18) so the format is a
|
||||
* valid Plan-9 symbol.
|
||||
*
|
||||
* row | what it pins
|
||||
* ----------------------------|--------------------------------
|
||||
* two_modules_same_leaf | original wedge. mod1.locate /
|
||||
* | mod2.locate both contain `match`
|
||||
* | so both stamp `_match_next_1`.
|
||||
* | Pre-fix run aborts on mis-jump
|
||||
* | (exit=10); post-fix exit=0 and
|
||||
* | mod1.locate_match_next_1 +
|
||||
* | mod2.locate_match_next_1 BOTH
|
||||
* | appear in the .s.
|
||||
* bytes_strings_contains | the latent that flips live once
|
||||
* | task #11 lands strings.index.
|
||||
* | Force both bytes.contains and
|
||||
* | strings.contains into the link;
|
||||
* | confirm post-fix bytes.contains_*
|
||||
* | and strings.contains_* labels
|
||||
* | are distinct.
|
||||
* same_module_same_leaf | non-regression: two fns in the
|
||||
* | same module with the same `match`
|
||||
* | shape still get unique labels via
|
||||
* | the per-fn `labelseq` counter.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <errno.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;
|
||||
int want_exit;
|
||||
int n_files;
|
||||
const char *files[8]; /* {path, src, path, src, ...} */
|
||||
const char *entry; /* file to `ww build` (relative) */
|
||||
int n_required;
|
||||
const char *required[6]; /* substrings every emitted .s must contain */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* Original wedge shape. mod1.locate and mod2.locate both run a
|
||||
* `match (needle) { case u8 / case []u8 }` over a `for` loop —
|
||||
* both stamp `_match_next_1` and `_loop_2` etc. Pre-fix the
|
||||
* second TEXT's labels overwrite the first's s->addr; mod1.locate
|
||||
* misjumps and returns the wrong byte offset. Post-fix the
|
||||
* module-qualified labels coexist. */
|
||||
{ "two_modules_same_leaf", 0, 3,
|
||||
{ "mod1/mod1.ww",
|
||||
"package mod1;\n"
|
||||
"export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let c: u8 => {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < s.len) {\n"
|
||||
" if (s[i] == c) { return i; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return;\n"
|
||||
" };\n"
|
||||
" case let sub: []u8 => { return; };\n"
|
||||
" };\n"
|
||||
" return;\n"
|
||||
"};\n",
|
||||
"mod2/mod2.ww",
|
||||
"package mod2;\n"
|
||||
"export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let c: u8 => {\n"
|
||||
" let i: i32 = 0;\n"
|
||||
" for (i < s.len) {\n"
|
||||
" if (s[i] == c) { return i + 100; };\n"
|
||||
" i += 1;\n"
|
||||
" };\n"
|
||||
" return;\n"
|
||||
" };\n"
|
||||
" case let sub: []u8 => { return; };\n"
|
||||
" };\n"
|
||||
" return;\n"
|
||||
"};\n",
|
||||
"main.ww",
|
||||
"package main;\n"
|
||||
"import mod1;\n"
|
||||
"import mod2;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let arr: [3]u8 = [65u8, 66u8, 67u8];\n"
|
||||
" let s: []u8 = arr[0:3];\n"
|
||||
" let n: u8 = 66u8;\n"
|
||||
" let needle: (u8 | []u8) = n;\n"
|
||||
" let a: (i32 | void) = mod1.locate(s, needle);\n"
|
||||
" let b: (i32 | void) = mod2.locate(s, needle);\n"
|
||||
" let av: i32 = match (a) { case let v: i32 => yield v; "
|
||||
"case void => yield -1; };\n"
|
||||
" let bv: i32 = match (b) { case let v: i32 => yield v; "
|
||||
"case void => yield -1; };\n"
|
||||
" if (av != 1) { return 10; };\n"
|
||||
" if (bv != 101) { return 20; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
"main.ww", 2,
|
||||
{ "mod1.locate_match_next_1", "mod2.locate_match_next_1" } },
|
||||
|
||||
/* The pre-located bytes.contains / strings.contains latent. Both
|
||||
* libs already export `contains`; force both into the link so the
|
||||
* pair coexists in main.s. */
|
||||
{ "bytes_strings_contains", 0, 1,
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"import bytes;\n"
|
||||
"import strings;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let s: str = \"abc\";\n"
|
||||
" let b: []u8 = strings.toutf8(s);\n"
|
||||
" let n: (u8 | []u8) = 98u8;\n"
|
||||
" if (!bytes.contains(b, n)) { return 11; };\n"
|
||||
" if (!strings.contains(s, 'b')) { return 12; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
"main.ww", 2,
|
||||
{ "bytes.contains_", "strings.contains_" } },
|
||||
|
||||
/* Non-regression: same module, two fns with the same `match`
|
||||
* shape. Per-fn labelseq still gives unique labels — both fns'
|
||||
* labels carry the same `pkg.fn_` prefix but differ in the seq
|
||||
* suffix. */
|
||||
{ "same_module_same_leaf", 0, 1,
|
||||
{ "main.ww",
|
||||
"package main;\n"
|
||||
"fn a(needle: (u8 | []u8)) i32 = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let c: u8 => return 1;\n"
|
||||
" case let s: []u8 => return 2;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"fn b(needle: (u8 | []u8)) i32 = {\n"
|
||||
" match (needle) {\n"
|
||||
" case let c: u8 => return 3;\n"
|
||||
" case let s: []u8 => return 4;\n"
|
||||
" };\n"
|
||||
" return 0;\n"
|
||||
"};\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let x: (u8 | []u8) = 7u8;\n"
|
||||
" if (a(x) != 1) { return 30; };\n"
|
||||
" if (b(x) != 3) { return 31; };\n"
|
||||
" return 0;\n"
|
||||
"};\n" },
|
||||
"main.ww", 2,
|
||||
{ "main.a_match_next_1", "main.b_match_next_1" } },
|
||||
};
|
||||
|
||||
/* Write `src` to `<dir>/<rel>`, mkdir'ing intermediate dirs. */
|
||||
static int
|
||||
write_one(const char *dir, const char *rel, const char *src,
|
||||
int *parent_owned)
|
||||
{
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/%s", dir, rel);
|
||||
/* mkdir parent if rel has a slash. */
|
||||
char *slash = strchr(path + strlen(dir) + 1, '/');
|
||||
if (slash) {
|
||||
*slash = '\0';
|
||||
if (mkdir(path, 0755) != 0) return -1;
|
||||
*parent_owned = 1;
|
||||
*slash = '/';
|
||||
}
|
||||
FILE *f = fopen(path, "wb");
|
||||
if (!f) return -1;
|
||||
int failed = fputs(src, f) == EOF;
|
||||
if (fclose(f) != 0) failed = 1;
|
||||
return failed ? -1 : 0;
|
||||
}
|
||||
|
||||
static char *
|
||||
slurp(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;
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int idx, const char *tag)
|
||||
{
|
||||
char tmpdir[96];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/mklabel_%s_%d_%d",
|
||||
tag, getpid(), idx);
|
||||
if (mkdir(tmpdir, 0755) != 0) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: cannot acquire temp directory: %s\n",
|
||||
tag, r->label, strerror(errno));
|
||||
return 1;
|
||||
}
|
||||
int fail = 0;
|
||||
int parent_owned[4] = { 0 };
|
||||
char entry_bin[512], stem[512], alls[600], cmd[2048];
|
||||
snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry);
|
||||
char *dot = strrchr(entry_bin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
snprintf(stem, sizeof stem, "%s/%s", tmpdir, r->entry);
|
||||
char *d2 = strrchr(stem, '.');
|
||||
if (d2 && strcmp(d2, ".ww") == 0) *d2 = '\0';
|
||||
snprintf(alls, sizeof alls, "%s/all.s", tmpdir);
|
||||
|
||||
for (int k = 0; k < r->n_files; k++) {
|
||||
if (write_one(tmpdir, r->files[2 * k],
|
||||
r->files[2 * k + 1], &parent_owned[k]) != 0) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: write %s failed\n",
|
||||
tag, r->label, r->files[2 * k]);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
}
|
||||
|
||||
/* #93 sep layout: emit asm to <entry-stem>.sepwork/<pkg>.s; pin
|
||||
* all outputs stay under tmpdir. */
|
||||
snprintf(cmd, sizeof cmd,
|
||||
"cd %s && b='%s'; %s build "
|
||||
"-o \"${b%%.ww}\" \"$b\" >/dev/null 2>&1",
|
||||
tmpdir, r->entry, driver);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: build failed\n",
|
||||
tag, r->label);
|
||||
fail++;
|
||||
goto cleanup;
|
||||
}
|
||||
|
||||
/* Runtime check. */
|
||||
int got = runwait(entry_bin);
|
||||
if (got != r->want_exit) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: rc=%d want=%d\n",
|
||||
tag, r->label, got, r->want_exit);
|
||||
fail++;
|
||||
}
|
||||
|
||||
/* Required-substring check on the emitted .s. #93: root + every
|
||||
* imported pkg now compile to separate <stem>.sepwork/<pkg>.s; the
|
||||
* cross-module label substrings (mod1.x / strings.x) live in the
|
||||
* per-package files, so concat ALL of them (sorted glob) — a single
|
||||
* __root.s read would FALSE-FAIL the multi-package rows. */
|
||||
char catcmd[1200];
|
||||
snprintf(catcmd, sizeof catcmd, "cat %s.sepwork/*.s > %s 2>/dev/null",
|
||||
stem, alls);
|
||||
(void)runwait(catcmd);
|
||||
size_t sz = 0;
|
||||
char *asm_buf = slurp(alls, &sz);
|
||||
if (!asm_buf) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: cannot read %s\n",
|
||||
tag, r->label, alls);
|
||||
fail++;
|
||||
} else {
|
||||
for (int k = 0; k < r->n_required; k++) {
|
||||
if (strstr(asm_buf, r->required[k]) == NULL) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: missing label "
|
||||
"substring \"%s\" in %s\n",
|
||||
tag, r->label, r->required[k], alls);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
free(asm_buf);
|
||||
}
|
||||
|
||||
cleanup: ;
|
||||
/* The retained build scratch is the only recursively removed path. */
|
||||
int cleanup_fail = 0;
|
||||
char sepwork[600];
|
||||
snprintf(sepwork, sizeof sepwork, "%s.sepwork", stem);
|
||||
snprintf(cmd, sizeof cmd, "rm -rf -- '%s'", sepwork);
|
||||
if (runwait(cmd) != 0) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: remove %s failed\n",
|
||||
tag, r->label, sepwork);
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
const char *outputs[] = { alls, entry_bin };
|
||||
for (size_t k = 0; k < sizeof outputs / sizeof outputs[0]; k++) {
|
||||
if (unlink(outputs[k]) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: unlink %s: %s\n",
|
||||
tag, r->label, outputs[k], strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
for (int k = 0; k < r->n_files; k++) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]);
|
||||
char *slash = strchr(path + strlen(tmpdir) + 1, '/');
|
||||
if (slash && !parent_owned[k]) continue;
|
||||
if (unlink(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: unlink %s: %s\n",
|
||||
tag, r->label, path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
for (int k = r->n_files - 1; k >= 0; k--) {
|
||||
char path[512];
|
||||
snprintf(path, sizeof path, "%s/%s", tmpdir, r->files[2 * k]);
|
||||
char *slash = strrchr(path, '/');
|
||||
if (parent_owned[k] && slash && slash > path + strlen(tmpdir)) {
|
||||
*slash = '\0';
|
||||
if (rmdir(path) != 0 && errno != ENOENT) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: rmdir %s: %s\n",
|
||||
tag, r->label, path, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (rmdir(tmpdir) != 0) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped[%s][%s]: rmdir %s: %s\n",
|
||||
tag, r->label, tmpdir, strerror(errno));
|
||||
cleanup_fail = 1;
|
||||
}
|
||||
if (cleanup_fail) fail++;
|
||||
return fail;
|
||||
}
|
||||
|
||||
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++) {
|
||||
total++;
|
||||
fail += run_row(cdrv, &rows[i], i, "cs");
|
||||
if (have_ww) {
|
||||
total++;
|
||||
fail += run_row(wdrv, &rows[i], i, "ws");
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr,
|
||||
"mklabel_modscoped: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("mklabel_modscoped: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
280
test/xmod/label_test.ww
Normal file
280
test/xmod/label_test.ww
Normal file
@@ -0,0 +1,280 @@
|
||||
package label_test;
|
||||
|
||||
// Module-scoped label-emission observers, both driver stages. Ports of
|
||||
// the retired native carriers test/wcc/706_fnlabel_mangle.c and
|
||||
// test/wcc/750_mklabel_modscoped.c; every assertion preserved.
|
||||
//
|
||||
// fnlabel (#9 + #12) — the on-disk fixture tree
|
||||
// test/wcc/data/fnlabelmangle/ pins all four label-emit sites: two
|
||||
// modules each `export fn ping` + private `fn helper` + `fn fpi`
|
||||
// (LEAQ N_IDENT), root takes `let p = mod.ping` (LEAQ N_DOT) and
|
||||
// calls both directly (CALL N_DOT) and through the ptrs. Build+run
|
||||
// exit 112 (25+31+25+31) on BOTH stages; a link-time label collapse
|
||||
// or a missing TY_FN LEAQ branch lands a wrong body. Strengthened
|
||||
// beyond the carrier: cs==ww byte-id over the sepwork .s concat (the
|
||||
// carrier never asserted it).
|
||||
//
|
||||
// mklabel (#13) — mklabel emits `<module>.<fnname>_<prefix>_<seq>`,
|
||||
// never leaf-only labels that w6a's a_intern collapses across units:
|
||||
// two_modules_same_leaf | mod1.locate/mod2.locate both stamp
|
||||
// | _match_next_1; run exit 0 (mis-jump
|
||||
// | exits 10/20) AND BOTH module-qualified
|
||||
// | labels appear in the .s concat
|
||||
// bytes_strings_contains| the bytes.contains/strings.contains
|
||||
// | latent pair coexists (run 0; 11/12)
|
||||
// same_module_same_leaf | per-fn labelseq keeps same-shape fns in
|
||||
// | ONE module distinct (run 0; 30/31)
|
||||
//
|
||||
// Dropped C machinery, not assertions: the ww_ww-absent skip gate
|
||||
// (the Make target declares both drivers) and the per-path cleanup
|
||||
// ledger (testenv.clean asserts the removal). The .s concat order is
|
||||
// strengthened from the carrier's shell glob to byte-sorted listdir.
|
||||
|
||||
import os;
|
||||
import os.exec;
|
||||
import strings;
|
||||
import testenv;
|
||||
import time;
|
||||
|
||||
fn fail(label: str, why: str) void = {
|
||||
let m: str = strings.concat("label FAIL: ", label, " -- ", why, "\n");
|
||||
os.write(2, m.ptr, m.len: u64);
|
||||
assert(false);
|
||||
};
|
||||
|
||||
fn tmo() time.duration = {
|
||||
return (240i64 * (time.second: i64)): time.duration;
|
||||
};
|
||||
|
||||
// -1 encodes an abnormal (non-EXIT) termination, never a valid code.
|
||||
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;
|
||||
};
|
||||
|
||||
// Every per-package .s under `sepdir`, concatenated in byte-sorted
|
||||
// order; "" when the directory is missing or holds no .s.
|
||||
fn catasm(sepdir: str) str = {
|
||||
if (!testenv.isdir(sepdir)) { return ""; };
|
||||
let names: []str = testenv.listdir(sepdir);
|
||||
let out: str = "";
|
||||
let i: i32 = 0;
|
||||
for (i < names.len) {
|
||||
if (strings.hassuffix(names[i], ".s")) {
|
||||
out = strings.concat(out,
|
||||
testenv.readfile(strings.concat(sepdir, "/", names[i])));
|
||||
};
|
||||
i += 1;
|
||||
};
|
||||
return out;
|
||||
};
|
||||
|
||||
// ---- fnlabel (#9 + #12) ------------------------------------------------
|
||||
|
||||
@test fn fnlabel() void = {
|
||||
let fixdir: str = strings.concat(testenv.repo(),
|
||||
"/test/wcc/data/fnlabelmangle");
|
||||
let td: str = testenv.fresh();
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["cstage", "wwstage"];
|
||||
let asms: []str = ["", ""];
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
let stem: str = strings.concat(td, "/pos.", tags[s]);
|
||||
let av: []str = [testenv.driver(drvs[s]), "build", "-o", stem,
|
||||
strings.concat(fixdir, "/pos.ww")];
|
||||
if (runcode(td, strings.concat("build_", tags[s]), av) != 0) {
|
||||
fail("fnlabel", strings.concat(tags[s],
|
||||
" pos.ww build failed"));
|
||||
};
|
||||
let rav: []str = [stem];
|
||||
if (runcode(td, strings.concat("run_", tags[s]), rav) != 112) {
|
||||
fail("fnlabel", strings.concat(tags[s], " exit != 112 -- fn ",
|
||||
"labels likely collapsed at link, or LEAQ-of-fn N_DOT ",
|
||||
"branch missing in cgdot"));
|
||||
};
|
||||
asms[s] = catasm(strings.concat(stem, ".sepwork"));
|
||||
s += 1;
|
||||
};
|
||||
if (asms[0].len == 0 || asms[1].len == 0) {
|
||||
fail("fnlabel", "empty .s concat");
|
||||
};
|
||||
if (!testenv.same(asms[0], asms[1])) {
|
||||
fail("fnlabel", "cstage.s != wwstage.s (byte-id break)");
|
||||
};
|
||||
testenv.clean(td);
|
||||
};
|
||||
|
||||
// ---- mklabel (#13) -----------------------------------------------------
|
||||
|
||||
fn mod1locate() str = {
|
||||
return strings.concat(
|
||||
"package mod1;\n",
|
||||
"export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n",
|
||||
" match (needle) {\n",
|
||||
" case let c: u8 => {\n",
|
||||
" let i: i32 = 0;\n",
|
||||
" for (i < s.len) {\n",
|
||||
" if (s[i] == c) { return i; };\n",
|
||||
" i += 1;\n",
|
||||
" };\n",
|
||||
" return;\n",
|
||||
" };\n",
|
||||
" case let sub: []u8 => { return; };\n",
|
||||
" };\n",
|
||||
" return;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
fn mod2locate() str = {
|
||||
return strings.concat(
|
||||
"package mod2;\n",
|
||||
"export fn locate(s: []u8, needle: (u8 | []u8)) (i32 | void) = {\n",
|
||||
" match (needle) {\n",
|
||||
" case let c: u8 => {\n",
|
||||
" let i: i32 = 0;\n",
|
||||
" for (i < s.len) {\n",
|
||||
" if (s[i] == c) { return i + 100; };\n",
|
||||
" i += 1;\n",
|
||||
" };\n",
|
||||
" return;\n",
|
||||
" };\n",
|
||||
" case let sub: []u8 => { return; };\n",
|
||||
" };\n",
|
||||
" return;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
fn locatemain() str = {
|
||||
return strings.concat(
|
||||
"package main;\n",
|
||||
"import mod1;\n",
|
||||
"import mod2;\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let arr: [3]u8 = [65u8, 66u8, 67u8];\n",
|
||||
" let s: []u8 = arr[0:3];\n",
|
||||
" let n: u8 = 66u8;\n",
|
||||
" let needle: (u8 | []u8) = n;\n",
|
||||
" let a: (i32 | void) = mod1.locate(s, needle);\n",
|
||||
" let b: (i32 | void) = mod2.locate(s, needle);\n",
|
||||
" let av: i32 = match (a) { case let v: i32 => yield v; ",
|
||||
"case void => yield -1; };\n",
|
||||
" let bv: i32 = match (b) { case let v: i32 => yield v; ",
|
||||
"case void => yield -1; };\n",
|
||||
" if (av != 1) { return 10; };\n",
|
||||
" if (bv != 101) { return 20; };\n",
|
||||
" return 0;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
fn containsmain() str = {
|
||||
return strings.concat(
|
||||
"package main;\n",
|
||||
"import bytes;\n",
|
||||
"import strings;\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let s: str = \"abc\";\n",
|
||||
" let b: []u8 = strings.toutf8(s);\n",
|
||||
" let n: (u8 | []u8) = 98u8;\n",
|
||||
" if (!bytes.contains(b, n)) { return 11; };\n",
|
||||
" if (!strings.contains(s, 'b')) { return 12; };\n",
|
||||
" return 0;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
fn sameleafmain() str = {
|
||||
return strings.concat(
|
||||
"package main;\n",
|
||||
"fn a(needle: (u8 | []u8)) i32 = {\n",
|
||||
" match (needle) {\n",
|
||||
" case let c: u8 => return 1;\n",
|
||||
" case let s: []u8 => return 2;\n",
|
||||
" };\n",
|
||||
" return 0;\n",
|
||||
"};\n",
|
||||
"fn b(needle: (u8 | []u8)) i32 = {\n",
|
||||
" match (needle) {\n",
|
||||
" case let c: u8 => return 3;\n",
|
||||
" case let s: []u8 => return 4;\n",
|
||||
" };\n",
|
||||
" return 0;\n",
|
||||
"};\n",
|
||||
"export fn main() i32 = {\n",
|
||||
" let x: (u8 | []u8) = 7u8;\n",
|
||||
" if (a(x) != 1) { return 30; };\n",
|
||||
" if (b(x) != 3) { return 31; };\n",
|
||||
" return 0;\n",
|
||||
"};\n");
|
||||
};
|
||||
|
||||
// dirs/rels/srcs describe the row's tree; every row builds main.ww,
|
||||
// runs to exit 0 (mis-jump polarities live in the sources), and needs
|
||||
// both required label substrings in the .s concat.
|
||||
fn mkrow(label: str, dirs: []str, rels: []str, srcs: []str,
|
||||
req: []str) void = {
|
||||
let drvs: []str = ["ww", "ww_ww"];
|
||||
let tags: []str = ["cs", "ws"];
|
||||
let s: i32 = 0;
|
||||
for (s < 2) {
|
||||
let td: str = testenv.fresh();
|
||||
let d: i32 = 0;
|
||||
for (d < dirs.len) {
|
||||
assert(os.mkdir(strings.concat(td, "/", dirs[d]), 493) == 0);
|
||||
d += 1;
|
||||
};
|
||||
let f: i32 = 0;
|
||||
for (f < rels.len) {
|
||||
testenv.writefile(strings.concat(td, "/", rels[f]), srcs[f]);
|
||||
f += 1;
|
||||
};
|
||||
let av: []str = [testenv.driver(drvs[s]), "build", "-o", "main",
|
||||
"main.ww"];
|
||||
if (runcode(td, strings.concat("build_", tags[s]), av) != 0) {
|
||||
fail(label, strings.concat(drvs[s], " build failed"));
|
||||
};
|
||||
let rav: []str = [strings.concat(td, "/main")];
|
||||
if (runcode(td, strings.concat("run_", tags[s]), rav) != 0) {
|
||||
fail(label, strings.concat(drvs[s], " run-exit != 0 ",
|
||||
"(collapsed label mis-jump -- #13)"));
|
||||
};
|
||||
let all: str = catasm(strings.concat(td, "/main.sepwork"));
|
||||
if (all.len == 0) { fail(label, "empty .s concat"); };
|
||||
let k: i32 = 0;
|
||||
for (k < req.len) {
|
||||
if (!testenv.has(all, req[k])) {
|
||||
fail(label, strings.concat("label substring `", req[k],
|
||||
"` missing from the emitted .s"));
|
||||
};
|
||||
k += 1;
|
||||
};
|
||||
testenv.clean(td);
|
||||
s += 1;
|
||||
};
|
||||
};
|
||||
|
||||
@test fn two_modules_same_leaf() void = {
|
||||
let dirs: []str = ["mod1", "mod2"];
|
||||
let rels: []str = ["mod1/mod1.ww", "mod2/mod2.ww", "main.ww"];
|
||||
let srcs: []str = [mod1locate(), mod2locate(), locatemain()];
|
||||
let req: []str = ["mod1.locate_match_next_1",
|
||||
"mod2.locate_match_next_1"];
|
||||
mkrow("two_modules_same_leaf", dirs, rels, srcs, req);
|
||||
};
|
||||
|
||||
@test fn bytes_strings_contains() void = {
|
||||
let dirs: []str = [];
|
||||
let rels: []str = ["main.ww"];
|
||||
let srcs: []str = [containsmain()];
|
||||
let req: []str = ["bytes.contains_", "strings.contains_"];
|
||||
mkrow("bytes_strings_contains", dirs, rels, srcs, req);
|
||||
};
|
||||
|
||||
@test fn same_module_same_leaf() void = {
|
||||
let dirs: []str = [];
|
||||
let rels: []str = ["main.ww"];
|
||||
let srcs: []str = [sameleafmain()];
|
||||
let req: []str = ["main.a_match_next_1", "main.b_match_next_1"];
|
||||
mkrow("same_module_same_leaf", dirs, rels, srcs, req);
|
||||
};
|
||||
Reference in New Issue
Block a user