selfhost+cstage+test: module-scope mklabel labels (#13)
Latent silent miscompile: cstage + wwstage mklabel emitted <fn>_<prefix>_<seq> with no module qualification, so two top-level fns sharing a leaf across modules (e.g. bytes.index + strings.index) emitted colliding labels into the same combined .s. Last assembler symbol-definition won; JNE/JMP rel32 resolved to the wrong fn's body. Repro (HEAD pre-fix): two_modules_same_leaf row in 750 — mod1.locate + mod2.locate sharing match-over-(u8|[]u8)+for shape. mod1.locate's JMP misresolved into mod2's body, exit 10. Post-fix: exit 0. Latent already at HEAD: bytes.contains_match_next_1 + strings.contains_match_next_1 collide today but the corpus had no forwarding path that surfaced it. cmd/w6c/cgen.c + selfhost/cmd/wcc/cgen.ww mklabel: prepend <module>. when c->cur_mod / c.curmod non-NULL/non-empty. Plan-9 convention extension: TEXT directive already uses <module>.<fnname> (lex.c:18 a_isidcont accepts '.'); mklabel now mirrors that for local labels. Both stages symmetric per rule 10. Fragment input (no `package`) collapses to pre-fix shape — no cross-unit risk. 750_mklabel_modscoped: table-driven 3 rows x 2 stages = 6 sub-cases (two_modules_same_leaf, bytes_strings_contains, same_module_same_leaf non-regression). All required substrings asserted via grep + runtime rc check. make test 124/124; ww2==ww3==ww4 byte-id holds via 995_self_rebuild. @-prefix slot keys (cg_tagbase, cg_tagscr, @retscr) are orthogonal (local_alloc keys, not mklabel emissions).
This commit is contained in:
7
Makefile
7
Makefile
@@ -284,6 +284,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(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_sumtype_forward \
|
||||||
|
$(BIN)/test_mklabel_modscoped \
|
||||||
$(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 \
|
||||||
@@ -713,6 +714,12 @@ $(BIN)/test_sumtype_forward: test/wcc/749_sumtype_forward.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_mklabel_modscoped: test/wcc/750_mklabel_modscoped.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 \
|
||||||
|
|||||||
@@ -1028,8 +1028,12 @@ abranch(const char *s)
|
|||||||
static char *
|
static char *
|
||||||
mklabel(Cg *c, const char *prefix)
|
mklabel(Cg *c, const char *prefix)
|
||||||
{
|
{
|
||||||
return aprintf(c->a, "%s_%s_%d", c->fnname ? c->fnname : "_", prefix,
|
/* Module-qualified to avoid cross-module same-leaf collisions
|
||||||
c->labelseq++);
|
* (task #13). w6a accepts '.' in label-cont (lex.c:18). */
|
||||||
|
return aprintf(c->a, "%s%s%s_%s_%d",
|
||||||
|
c->cur_mod ? c->cur_mod : "",
|
||||||
|
c->cur_mod ? "." : "",
|
||||||
|
c->fnname ? c->fnname : "_", prefix, c->labelseq++);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
|||||||
@@ -19382,13 +19382,23 @@ fn emitoff(v: i64) void = {
|
|||||||
if (v != 0i64) { emitint(v); };
|
if (v != 0i64) { emitint(v); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// mklabel — fresh label "<fnname>_<prefix>_<seq>". Returns an
|
// mklabel — fresh label "<module>.<fnname>_<prefix>_<seq>" (bare
|
||||||
// arena-owned str. Mirrors C cgen's mklabel so diffs match.
|
// "<fnname>_..." when curmod is empty). Returns an arena-owned str.
|
||||||
|
// Mirrors C cgen's mklabel so diffs match. Module-qualified to
|
||||||
|
// avoid cross-module same-leaf collisions (task #13); w6a accepts
|
||||||
|
// '.' in label-cont (lex.c:18).
|
||||||
fn mklabel(c: *cgen, prefix: str) str = {
|
fn mklabel(c: *cgen, prefix: str) str = {
|
||||||
let buf: [128]u8;
|
let buf: [128]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
let fname: str = c.fnname;
|
let mname: str = c.curmod;
|
||||||
let j: i32 = 0;
|
let j: i32 = 0;
|
||||||
|
for (j < mname.len) {
|
||||||
|
buf[i] = mname[j];
|
||||||
|
i += 1; j += 1;
|
||||||
|
};
|
||||||
|
if (mname.len > 0) { buf[i] = 46u8; i += 1; }; // '.'
|
||||||
|
let fname: str = c.fnname;
|
||||||
|
j = 0;
|
||||||
for (j < fname.len) {
|
for (j < fname.len) {
|
||||||
buf[i] = fname[j];
|
buf[i] = fname[j];
|
||||||
i += 1; j += 1;
|
i += 1; j += 1;
|
||||||
|
|||||||
@@ -768,13 +768,23 @@ fn emitoff(v: i64) void = {
|
|||||||
if (v != 0i64) { emitint(v); };
|
if (v != 0i64) { emitint(v); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// mklabel — fresh label "<fnname>_<prefix>_<seq>". Returns an
|
// mklabel — fresh label "<module>.<fnname>_<prefix>_<seq>" (bare
|
||||||
// arena-owned str. Mirrors C cgen's mklabel so diffs match.
|
// "<fnname>_..." when curmod is empty). Returns an arena-owned str.
|
||||||
|
// Mirrors C cgen's mklabel so diffs match. Module-qualified to
|
||||||
|
// avoid cross-module same-leaf collisions (task #13); w6a accepts
|
||||||
|
// '.' in label-cont (lex.c:18).
|
||||||
fn mklabel(c: *cgen, prefix: str) str = {
|
fn mklabel(c: *cgen, prefix: str) str = {
|
||||||
let buf: [128]u8;
|
let buf: [128]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
let fname: str = c.fnname;
|
let mname: str = c.curmod;
|
||||||
let j: i32 = 0;
|
let j: i32 = 0;
|
||||||
|
for (j < mname.len) {
|
||||||
|
buf[i] = mname[j];
|
||||||
|
i += 1; j += 1;
|
||||||
|
};
|
||||||
|
if (mname.len > 0) { buf[i] = 46u8; i += 1; }; // '.'
|
||||||
|
let fname: str = c.fnname;
|
||||||
|
j = 0;
|
||||||
for (j < fname.len) {
|
for (j < fname.len) {
|
||||||
buf[i] = fname[j];
|
buf[i] = fname[j];
|
||||||
i += 1; j += 1;
|
i += 1; j += 1;
|
||||||
|
|||||||
@@ -19382,13 +19382,23 @@ fn emitoff(v: i64) void = {
|
|||||||
if (v != 0i64) { emitint(v); };
|
if (v != 0i64) { emitint(v); };
|
||||||
};
|
};
|
||||||
|
|
||||||
// mklabel — fresh label "<fnname>_<prefix>_<seq>". Returns an
|
// mklabel — fresh label "<module>.<fnname>_<prefix>_<seq>" (bare
|
||||||
// arena-owned str. Mirrors C cgen's mklabel so diffs match.
|
// "<fnname>_..." when curmod is empty). Returns an arena-owned str.
|
||||||
|
// Mirrors C cgen's mklabel so diffs match. Module-qualified to
|
||||||
|
// avoid cross-module same-leaf collisions (task #13); w6a accepts
|
||||||
|
// '.' in label-cont (lex.c:18).
|
||||||
fn mklabel(c: *cgen, prefix: str) str = {
|
fn mklabel(c: *cgen, prefix: str) str = {
|
||||||
let buf: [128]u8;
|
let buf: [128]u8;
|
||||||
let i: i32 = 0;
|
let i: i32 = 0;
|
||||||
let fname: str = c.fnname;
|
let mname: str = c.curmod;
|
||||||
let j: i32 = 0;
|
let j: i32 = 0;
|
||||||
|
for (j < mname.len) {
|
||||||
|
buf[i] = mname[j];
|
||||||
|
i += 1; j += 1;
|
||||||
|
};
|
||||||
|
if (mname.len > 0) { buf[i] = 46u8; i += 1; }; // '.'
|
||||||
|
let fname: str = c.fnname;
|
||||||
|
j = 0;
|
||||||
for (j < fname.len) {
|
for (j < fname.len) {
|
||||||
buf[i] = fname[j];
|
buf[i] = fname[j];
|
||||||
i += 1; j += 1;
|
i += 1; j += 1;
|
||||||
|
|||||||
328
test/wcc/750_mklabel_modscoped.c
Normal file
328
test/wcc/750_mklabel_modscoped.c
Normal file
@@ -0,0 +1,328 @@
|
|||||||
|
/*
|
||||||
|
* 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 <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)
|
||||||
|
{
|
||||||
|
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';
|
||||||
|
mkdir(path, 0755);
|
||||||
|
*slash = '/';
|
||||||
|
}
|
||||||
|
FILE *f = fopen(path, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(src, f);
|
||||||
|
fclose(f);
|
||||||
|
return 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[64];
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/mklabel_%s_%d_%d",
|
||||||
|
tag, getpid(), idx);
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
int fail = 0;
|
||||||
|
|
||||||
|
for (int k = 0; k < r->n_files; k++) {
|
||||||
|
if (write_one(tmpdir, r->files[2 * k],
|
||||||
|
r->files[2 * k + 1]) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"mklabel_modscoped[%s][%s]: write %s failed\n",
|
||||||
|
tag, r->label, r->files[2 * k]);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
char cmd[2048];
|
||||||
|
snprintf(cmd, sizeof cmd,
|
||||||
|
"cd %s && %s build %s >/dev/null 2>&1", tmpdir, driver, r->entry);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"mklabel_modscoped[%s][%s]: build failed\n",
|
||||||
|
tag, r->label);
|
||||||
|
fail++;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Runtime check. */
|
||||||
|
char entry_bin[512];
|
||||||
|
snprintf(entry_bin, sizeof entry_bin, "%s/%s", tmpdir, r->entry);
|
||||||
|
char *dot = strrchr(entry_bin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
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. */
|
||||||
|
char entry_s[512];
|
||||||
|
snprintf(entry_s, sizeof entry_s, "%s/%s", tmpdir, r->entry);
|
||||||
|
char *d2 = strrchr(entry_s, '.');
|
||||||
|
if (d2 && strcmp(d2, ".ww") == 0) { strcpy(d2, ".s"); }
|
||||||
|
size_t sz = 0;
|
||||||
|
char *asm_buf = slurp(entry_s, &sz);
|
||||||
|
if (!asm_buf) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"mklabel_modscoped[%s][%s]: cannot read %s\n",
|
||||||
|
tag, r->label, entry_s);
|
||||||
|
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], entry_s);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
free(asm_buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
/* Best-effort cleanup. Leaving artifacts is fine on failure. */
|
||||||
|
snprintf(cmd, sizeof cmd, "rm -rf %s", tmpdir);
|
||||||
|
(void)runwait(cmd);
|
||||||
|
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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user