wcc: peel N_TNAME alias chain on cgdot receiver before lkind decision (#191)
`type vs = *vt; fn(s: vs) s.field` linked-failed in wwstage with `undefined reference to field' — cgdot read lc.tnode.kind without first walking N_TNAME aliases, so lkind stayed N_TNAME (not the underlying N_TPTR), structlookupchain missed (`vs` isn't a struct alias), and the lookup fell through to the SB-global fallback that emits `MOVQ <field>(SB), AX`. Mirror cstage type_chase_named (cmd/w6c/cgen.c:144-155) via an aliaslookup loop, stopping at struct aliases so the existing direct-struct N_TNAME arm stays byte-id with pre-fix #22 callers. LOOP (not single-peel) — Phase-N builds N_TNAME chains (project_tinfo_lossy_nominal), depth-2+ aliases require iteration. Inner peel on the pointee is unnecessary: the existing structlookupchain already walks N_TNAME chains via aliaslookup (cgenutil.ww:1266-1276); row 4 of probe 769 proves the inner-chain depth-3 path stays green without an explicit inner peel. Probe test/wcc/769_dot_aliased_ptr.c covers 4 rows (fn-param read, let-binding read, double-alias receiver, pointee-alias chain), per-row runtime + byte-id gates. Files inline two sibling bugs surfaced during impl (cgassign write-side silent-drop, chained-N_DOT spine link-fail) plus a cstage checker assignability gap on chain-depth-2 aliases — all out-of-scope per rule 11 split.
This commit is contained in:
7
Makefile
7
Makefile
@@ -319,6 +319,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
||||
$(BIN)/test_star_fn_deref_call \
|
||||
$(BIN)/test_match_variant_dispatch \
|
||||
$(BIN)/test_io_types_run \
|
||||
$(BIN)/test_dot_aliased_ptr \
|
||||
$(BIN)/test_use_promote_alias \
|
||||
$(BIN)/test_field_signed $(BIN)/test_frame_argcount \
|
||||
$(BIN)/test_selfhost $(BIN)/test_w6a_ww $(BIN)/test_w6l_ww \
|
||||
@@ -639,6 +640,12 @@ $(BIN)/test_io_types_run: test/wcc/768_io_types_run.c \
|
||||
$(LIB)/libwwrt.a | $(BIN)
|
||||
$(CC) $(CFLAGS) -o $@ $<
|
||||
|
||||
$(BIN)/test_dot_aliased_ptr: test/wcc/769_dot_aliased_ptr.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_arrlit_str_full: test/wcc/711_arrlit_str_full.c \
|
||||
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||
|
||||
@@ -19284,8 +19284,24 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let lkind: nkind = nkind.N_NONE;
|
||||
if (tn != nil) { lkind = tn.kind; };
|
||||
// Receiver is a NAMED alias chain — peel via aliaslookup
|
||||
// until tn exposes a non-N_TNAME kind (or a struct alias).
|
||||
// Without this, `type vs = *vt` leaves lkind == N_TNAME and
|
||||
// structlookupchain misses (vs is not a struct), so we fall
|
||||
// through to the SB-global fallback and emit a wrong
|
||||
// `MOVQ <fld>(SB), AX`. Mirror cstage type_chase_named
|
||||
// (cmd/w6c/cgen.c:144-155); LOOP, not single-peel — Phase-N
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
let lkind: nkind = tn.kind;
|
||||
// Pointer-to-struct: deref then field load.
|
||||
if (lkind == nkind.N_TPTR) {
|
||||
let inner: *node = tn.lhs;
|
||||
|
||||
@@ -1672,8 +1672,24 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let lkind: nkind = nkind.N_NONE;
|
||||
if (tn != nil) { lkind = tn.kind; };
|
||||
// Receiver is a NAMED alias chain — peel via aliaslookup
|
||||
// until tn exposes a non-N_TNAME kind (or a struct alias).
|
||||
// Without this, `type vs = *vt` leaves lkind == N_TNAME and
|
||||
// structlookupchain misses (vs is not a struct), so we fall
|
||||
// through to the SB-global fallback and emit a wrong
|
||||
// `MOVQ <fld>(SB), AX`. Mirror cstage type_chase_named
|
||||
// (cmd/w6c/cgen.c:144-155); LOOP, not single-peel — Phase-N
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
let lkind: nkind = tn.kind;
|
||||
// Pointer-to-struct: deref then field load.
|
||||
if (lkind == nkind.N_TPTR) {
|
||||
let inner: *node = tn.lhs;
|
||||
|
||||
@@ -19284,8 +19284,24 @@ fn cgdot(c: *cgen, n: *node) void = {
|
||||
let lc: *local = localfindnode(c, nm);
|
||||
if (lc != nil) {
|
||||
let tn: *node = lc.tnode;
|
||||
let lkind: nkind = nkind.N_NONE;
|
||||
if (tn != nil) { lkind = tn.kind; };
|
||||
// Receiver is a NAMED alias chain — peel via aliaslookup
|
||||
// until tn exposes a non-N_TNAME kind (or a struct alias).
|
||||
// Without this, `type vs = *vt` leaves lkind == N_TNAME and
|
||||
// structlookupchain misses (vs is not a struct), so we fall
|
||||
// through to the SB-global fallback and emit a wrong
|
||||
// `MOVQ <fld>(SB), AX`. Mirror cstage type_chase_named
|
||||
// (cmd/w6c/cgen.c:144-155); LOOP, not single-peel — Phase-N
|
||||
// builds NAMED chains (project_tinfo_lossy_nominal). Stops
|
||||
// at struct aliases so the existing direct-struct arm below
|
||||
// stays byte-id with pre-fix #22 callers. #191.
|
||||
for (tn != nil && tn.kind == nkind.N_TNAME) {
|
||||
if (structlookup(c, tn.str) != nil) { break; };
|
||||
let nx: *node = aliaslookup(c, tn.str);
|
||||
if (nx == nil) { break; };
|
||||
tn = nx;
|
||||
};
|
||||
if (tn == nil) { return; };
|
||||
let lkind: nkind = tn.kind;
|
||||
// Pointer-to-struct: deref then field load.
|
||||
if (lkind == nkind.N_TPTR) {
|
||||
let inner: *node = tn.lhs;
|
||||
|
||||
346
test/wcc/769_dot_aliased_ptr.c
Normal file
346
test/wcc/769_dot_aliased_ptr.c
Normal file
@@ -0,0 +1,346 @@
|
||||
/*
|
||||
* 769_dot_aliased_ptr — project #191: wwstage cgdot N_DOT on an
|
||||
* aliased-pointer receiver. `type vt = struct{...}; type vs = *vt;
|
||||
* fn f(s: vs) { s.field }` — wwstage's cgdot read the receiver's
|
||||
* tnode kind WITHOUT first peeling N_TNAME alias chains, so lkind
|
||||
* stayed N_TNAME (not the underlying N_TPTR), structlookupchain
|
||||
* missed (`vs` isn't a struct alias), and the lookup fell through
|
||||
* to the SB-global fallback. The emitted asm was
|
||||
* MOVQ field(SB), AX
|
||||
* which links as an undefined symbol — runtime never reached.
|
||||
*
|
||||
* Cstage (cmd/w6c/cgen.c:7001-7004) uses type_chase_named to walk
|
||||
* TY_NAMED.under to the underlying TY_PTR / TY_STRUCT before the
|
||||
* kind-gated arms fire. Mirror: a loop via `aliaslookup(c, name)`
|
||||
* at the top of cgdot's lc != nil branch, stopping at struct
|
||||
* aliases so the existing direct-struct N_TNAME arm below stays
|
||||
* byte-id with pre-fix #22 callers.
|
||||
*
|
||||
* Fix: selfhost/cmd/wcc/cgenexpr.ww cgdot, after `let tn = lc.tnode;`
|
||||
* insert a peel loop before the lkind decision. LOOP not single-
|
||||
* peel — Phase-N builds N_TNAME chains (memory
|
||||
* project_tinfo_lossy_nominal), so depth-2+ aliases require
|
||||
* iteration. Inner peel on the pointee is unnecessary because the
|
||||
* existing `structlookupchain(c, inner)` already walks N_TNAME
|
||||
* chains via aliaslookup (cgenutil.ww:1266-1276); row 4 below
|
||||
* proves the inner-chain depth-3 path stays green without an
|
||||
* explicit inner peel.
|
||||
*
|
||||
* Coverage (4 rows):
|
||||
* 1. fn_param_read — `fn f(s: vs) { s.field }`. Single-
|
||||
* alias receiver. Pre-fix: link
|
||||
* failure on `field(SB)`. Post-fix:
|
||||
* deref + offset load. The impl-e1
|
||||
* fold-blocker exact shape.
|
||||
* 2. let_binding_read — `let s: vs = (&v): vs; s.field`.
|
||||
* Single-alias via let-init; the
|
||||
* explicit cast routes around the
|
||||
* cstage checker assignability gap
|
||||
* for chain-depth-1 (see row 3).
|
||||
* 3. double_alias_read — `type pvt = *vt; type vs = pvt;`
|
||||
* with `let s: vs = (&v): vs`.
|
||||
* KEN'S LOOP-PEEL VERIFY GATE:
|
||||
* chain depth 2 on the receiver, so
|
||||
* a single-peel implementation would
|
||||
* stop at N_TNAME("pvt") and miss
|
||||
* via structlookupchain (which only
|
||||
* bottoms out when aliaslookup
|
||||
* returns an N_TNAME that
|
||||
* structlookup hits — here the next
|
||||
* hop is N_TPTR, breaking its
|
||||
* inner-loop guard). The cast in
|
||||
* the let-init dodges the cstage
|
||||
* checker's "*vt not assignable to
|
||||
* vs" through double aliases (see
|
||||
* SIBLING below).
|
||||
* 4. pointee_alias_chain — `type vti = vt; type vti2 = vti;
|
||||
* type vs = *vti2;`. Receiver itself
|
||||
* is single-aliased; the chain
|
||||
* depth lives inside the pointee.
|
||||
* DREW'S INNER-PEEL VERIFY GATE:
|
||||
* proves structlookupchain's
|
||||
* aliaslookup loop is sufficient
|
||||
* without an explicit inner peel.
|
||||
* If this row reds, the structural
|
||||
* no-inner-peel claim above is
|
||||
* wrong and the fix needs an
|
||||
* inner-side peel too.
|
||||
*
|
||||
* Per-row gates: cstage runtime exit, wwstage runtime exit,
|
||||
* cs.s == ww.s byte-identical (rule-10 stage symmetry).
|
||||
*
|
||||
* SIBLING BUGS surfaced during impl — NOT fixed here, filed for
|
||||
* separate single-class commits (rule 11 split, rule 7 no
|
||||
* workarounds, task spec "If you find sibling bugs in cgdot: file
|
||||
* inline"):
|
||||
*
|
||||
* (A) cgassign N_DOT TK_ASSIGN on aliased-ptr receiver SILENTLY
|
||||
* DROPS THE STORE. `s.field = 7` where s: vs = *vt emits
|
||||
* nothing — wwstage MOVs the rhs into AX and discards.
|
||||
* Cstage emits the correct `MOVQ off(BP), BX; MOVL AX, (BX)`.
|
||||
* Same pre-peel pattern as cgdot, at cgenexpr.ww:5160-5168
|
||||
* (the `if (lkind == nkind.N_TPTR)` arm of the cgassign
|
||||
* base-IDENT branch). Out of scope for #191 (read-side
|
||||
* cgdot only); siblings to file as a follow-up class along
|
||||
* with cgassign compound (+=, -=, etc.) — same shape, same
|
||||
* drop. Repro:
|
||||
* type vt = struct { field: i32 };
|
||||
* type vs = *vt;
|
||||
* fn setit(s: vs) void = { s.field = 7; };
|
||||
*
|
||||
* (B) Chained N_DOT spine (`p.i.x` where p: vs = *outer, outer
|
||||
* has field i: inner, inner has field x) on aliased-ptr
|
||||
* receiver also link-fails. The chained spine has dotlhs ==
|
||||
* N_DOT (not N_IDENT), so #191's branch doesn't fire; the
|
||||
* chained-spine codepath has its own peel gap. Repro:
|
||||
* type inner = struct { x: i32 };
|
||||
* type outer = struct { i: inner };
|
||||
* type vs = *outer;
|
||||
* fn callit(p: vs) i32 = { return p.i.x; };
|
||||
*
|
||||
* (C) Cstage checker rejects single-deep-alias assignability:
|
||||
* `let s: vs = &v;` where vs = pvt = *vt errors with
|
||||
* `init *vt not assignable to declared vs`. Workaround in
|
||||
* row 3: `(&v): vs` explicit cast. The checker walks single
|
||||
* aliases but not chains of depth >= 2. Not a wwstage cgen
|
||||
* bug; cited as the reason row 3's let-init carries an
|
||||
* explicit cast.
|
||||
*
|
||||
* GATE POLARITY: must stay GREEN. A red here means the cgdot
|
||||
* outer-peel regressed (row 1/2), the loop-peel depth handling
|
||||
* regressed (row 3), or the inner-chain structlookupchain path
|
||||
* drifted (row 4).
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdlib.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;
|
||||
}
|
||||
|
||||
#define STAGE_CS 1
|
||||
#define STAGE_WW 2
|
||||
|
||||
struct row {
|
||||
const char *label;
|
||||
const char *src;
|
||||
int expected_exit;
|
||||
int stage_mask;
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
{ "fn_param_read",
|
||||
"package main;\n"
|
||||
"type vt = struct { field: i32 };\n"
|
||||
"type vs = *vt;\n"
|
||||
"fn callit(s: vs) i32 = { return s.field; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: vt; v.field = 42;\n"
|
||||
" return callit(&v);\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "let_binding_read",
|
||||
"package main;\n"
|
||||
"type vt = struct { field: i32 };\n"
|
||||
"type vs = *vt;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: vt; v.field = 42;\n"
|
||||
" let s: vs = (&v): vs;\n"
|
||||
" return s.field;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "double_alias_read",
|
||||
"package main;\n"
|
||||
"type vt = struct { field: i32 };\n"
|
||||
"type pvt = *vt;\n"
|
||||
"type vs = pvt;\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: vt; v.field = 42;\n"
|
||||
" let s: vs = (&v): vs;\n"
|
||||
" return s.field;\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
{ "pointee_alias_chain",
|
||||
"package main;\n"
|
||||
"type vt = struct { field: i32 };\n"
|
||||
"type vti = vt;\n"
|
||||
"type vti2 = vti;\n"
|
||||
"type vs = *vti2;\n"
|
||||
"fn callit(s: vs) i32 = { return s.field; };\n"
|
||||
"export fn main() i32 = {\n"
|
||||
" let v: vti2; v.field = 42;\n"
|
||||
" return callit(&v);\n"
|
||||
"};\n",
|
||||
42,
|
||||
STAGE_CS | STAGE_WW },
|
||||
};
|
||||
|
||||
static int
|
||||
write_source(const char *src_path, const char *src)
|
||||
{
|
||||
FILE *f = fopen(src_path, "wb");
|
||||
if (!f) return -1;
|
||||
fputs(src, f);
|
||||
fclose(f);
|
||||
return 0;
|
||||
}
|
||||
|
||||
static void
|
||||
cleanup_tmp(const char *tmpdir, const char *base)
|
||||
{
|
||||
char p[512];
|
||||
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.s", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s.combined.ww", tmpdir, base); unlink(p);
|
||||
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
||||
rmdir(tmpdir);
|
||||
}
|
||||
|
||||
static int
|
||||
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
||||
{
|
||||
char cmd[1024];
|
||||
snprintf(cmd, sizeof cmd, "cd %s && timeout 180 %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
return runwait(cmd);
|
||||
}
|
||||
|
||||
static int
|
||||
run_row(const char *driver, const struct row *r, int seq)
|
||||
{
|
||||
char tmpdir[256], src[256], base[64], outbin[512];
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/dap_%d_d_%d", getpid(), seq);
|
||||
snprintf(src, sizeof src, "%s/main769.ww", tmpdir);
|
||||
snprintf(base, sizeof base, "main769");
|
||||
mkdir(tmpdir, 0755);
|
||||
if (write_source(src, r->src) != 0) {
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return -1;
|
||||
}
|
||||
int rc = -1;
|
||||
if (build_via_driver(driver, tmpdir, src) == 0) {
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
rc = runwait(outbin);
|
||||
}
|
||||
cleanup_tmp(tmpdir, base);
|
||||
return rc;
|
||||
}
|
||||
|
||||
/* asm_byte_identical — diff cstage vs wwstage .s. Parallel trees so
|
||||
* ww_ww writing intermediates next to the source doesn't clobber the
|
||||
* cstage .s (CLAUDE.md rule 14 phase split). */
|
||||
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];
|
||||
snprintf(tdc, sizeof tdc, "/tmp/dap_%d_c_%d", getpid(), seq);
|
||||
snprintf(tdw, sizeof tdw, "/tmp/dap_%d_w_%d", getpid(), seq);
|
||||
snprintf(base, sizeof base, "main769");
|
||||
mkdir(tdc, 0755);
|
||||
mkdir(tdw, 0755);
|
||||
snprintf(src, sizeof src, "%s/main769.ww", tdc);
|
||||
if (write_source(src, r->src) != 0) { cleanup_tmp(tdc, base); cleanup_tmp(tdw, base); return -1; }
|
||||
int rc = -1;
|
||||
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
|
||||
snprintf(cs, sizeof cs, "%s/%s.s", tdc, base);
|
||||
|
||||
snprintf(src, sizeof src, "%s/main769.ww", tdw);
|
||||
if (write_source(src, r->src) != 0) goto out;
|
||||
if (build_via_driver(wdrv, tdw, src) != 0) goto out;
|
||||
snprintf(ws, sizeof ws, "%s/%s.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:
|
||||
cleanup_tmp(tdc, base);
|
||||
cleanup_tmp(tdw, base);
|
||||
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++) {
|
||||
if (rows[i].stage_mask & STAGE_CS) {
|
||||
total++;
|
||||
int got = run_row(cdrv, &rows[i], seq++);
|
||||
if (got != rows[i].expected_exit) {
|
||||
fprintf(stderr,
|
||||
"dot_aliased_ptr[cstage run][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].expected_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
if (wwpresent && (rows[i].stage_mask & STAGE_WW)) {
|
||||
total++;
|
||||
int got = run_row(wdrv, &rows[i], seq++);
|
||||
if (got != rows[i].expected_exit) {
|
||||
fprintf(stderr,
|
||||
"dot_aliased_ptr[wwstage run][%s]: exit=%d want=%d\n",
|
||||
rows[i].label, got, rows[i].expected_exit);
|
||||
fail++;
|
||||
}
|
||||
total++;
|
||||
if (asm_byte_identical(cdrv, wdrv, &rows[i], seq++) != 0) {
|
||||
fprintf(stderr,
|
||||
"dot_aliased_ptr[byte-id][%s]: cstage vs wwstage asm differs\n",
|
||||
rows[i].label);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!wwpresent)
|
||||
fprintf(stderr, "dot_aliased_ptr: skip wwstage (no %s)\n", wdrv);
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "dot_aliased_ptr: %d/%d fixtures failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("dot_aliased_ptr: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user