wcc/ww: cgtypeassert gains the nullable arm
'as' on a nullable value compared the pointer itself to a tag (the missing arm). Mirror cgtypetest's nullable fold and the cstage twin (cgen.c:10694). Review item #17.
This commit is contained in:
7
Makefile
7
Makefile
@@ -321,6 +321,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_tagged_call_arg_run \
|
$(BIN)/test_tagged_call_arg_run \
|
||||||
$(BIN)/test_tryprop_tag_remap_run \
|
$(BIN)/test_tryprop_tag_remap_run \
|
||||||
$(BIN)/test_nullable_try_run \
|
$(BIN)/test_nullable_try_run \
|
||||||
|
$(BIN)/test_nullable_assert_run \
|
||||||
$(BIN)/test_nested_union_widen_run \
|
$(BIN)/test_nested_union_widen_run \
|
||||||
$(BIN)/test_sret_struct_return \
|
$(BIN)/test_sret_struct_return \
|
||||||
$(BIN)/test_sret_struct_return_run \
|
$(BIN)/test_sret_struct_return_run \
|
||||||
@@ -1771,6 +1772,12 @@ $(BIN)/test_nullable_try_run: test/wcc/949_nullable_try_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_nullable_assert_run: test/wcc/949_nullable_assert_run.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_sret_struct_return: test/wcc/721_sret_struct_return.c \
|
$(BIN)/test_sret_struct_return: test/wcc/721_sret_struct_return.c \
|
||||||
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
@@ -24019,8 +24019,32 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||||
if (want < 0) { want = 0; };
|
|
||||||
let okl: str = mklabel(c, "asrt_ok");
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
// Nullable `(*T | void)`: the slot word IS the pointer, not a tag.
|
||||||
|
// The *T variant asserts non-null, the void variant asserts null;
|
||||||
|
// AX keeps the pointer on the ok path (no slot+8 unwrap — the 8B
|
||||||
|
// nullable slot has no second word). The pre-fix path compared the
|
||||||
|
// POINTER against `want` (so a real pointer aborted, null passed)
|
||||||
|
// and unwrapped a frame word past the slot. `want` stays RAW (no
|
||||||
|
// clamp), mirroring cstage cgen.c N_TYPEASSERT nullable arm and
|
||||||
|
// ww's own cgtypetest nullable fold (cgenexpr.ww). (task #17/F4)
|
||||||
|
if (isnullabletype(scrutt)) {
|
||||||
|
let ptrtag: i32 = nullableptrtag(scrutt);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
if (want == ptrtag) {
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tJE\t");
|
||||||
|
};
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
|
|||||||
@@ -868,8 +868,32 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||||
if (want < 0) { want = 0; };
|
|
||||||
let okl: str = mklabel(c, "asrt_ok");
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
// Nullable `(*T | void)`: the slot word IS the pointer, not a tag.
|
||||||
|
// The *T variant asserts non-null, the void variant asserts null;
|
||||||
|
// AX keeps the pointer on the ok path (no slot+8 unwrap — the 8B
|
||||||
|
// nullable slot has no second word). The pre-fix path compared the
|
||||||
|
// POINTER against `want` (so a real pointer aborted, null passed)
|
||||||
|
// and unwrapped a frame word past the slot. `want` stays RAW (no
|
||||||
|
// clamp), mirroring cstage cgen.c N_TYPEASSERT nullable arm and
|
||||||
|
// ww's own cgtypetest nullable fold (cgenexpr.ww). (task #17/F4)
|
||||||
|
if (isnullabletype(scrutt)) {
|
||||||
|
let ptrtag: i32 = nullableptrtag(scrutt);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
if (want == ptrtag) {
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tJE\t");
|
||||||
|
};
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
|
|||||||
@@ -24019,8 +24019,32 @@ fn cgtypeassert(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
let want: i32 = cgtagvariantidx(c, scrutt, n.rhs);
|
||||||
if (want < 0) { want = 0; };
|
|
||||||
let okl: str = mklabel(c, "asrt_ok");
|
let okl: str = mklabel(c, "asrt_ok");
|
||||||
|
// Nullable `(*T | void)`: the slot word IS the pointer, not a tag.
|
||||||
|
// The *T variant asserts non-null, the void variant asserts null;
|
||||||
|
// AX keeps the pointer on the ok path (no slot+8 unwrap — the 8B
|
||||||
|
// nullable slot has no second word). The pre-fix path compared the
|
||||||
|
// POINTER against `want` (so a real pointer aborted, null passed)
|
||||||
|
// and unwrapped a frame word past the slot. `want` stays RAW (no
|
||||||
|
// clamp), mirroring cstage cgen.c N_TYPEASSERT nullable arm and
|
||||||
|
// ww's own cgtypetest nullable fold (cgenexpr.ww). (task #17/F4)
|
||||||
|
if (isnullabletype(scrutt)) {
|
||||||
|
let ptrtag: i32 = nullableptrtag(scrutt);
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff(scrutoff: i64);
|
||||||
|
emitline("(BP), AX\n");
|
||||||
|
emitline("\tCMPQ\t$0, AX\n");
|
||||||
|
if (want == ptrtag) {
|
||||||
|
emitline("\tJNE\t");
|
||||||
|
} else {
|
||||||
|
emitline("\tJE\t");
|
||||||
|
};
|
||||||
|
emitline(okl);
|
||||||
|
emitline("\n\tMOVQ\t$1, DI\n\tMOVQ\t$60, AX\n\tSYSCALL\n");
|
||||||
|
emitlabel(okl);
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
if (want < 0) { want = 0; };
|
||||||
emitline("\tMOVQ\t");
|
emitline("\tMOVQ\t");
|
||||||
emitoff(scrutoff: i64);
|
emitoff(scrutoff: i64);
|
||||||
emitline("(BP), AX\n");
|
emitline("(BP), AX\n");
|
||||||
|
|||||||
242
test/wcc/949_nullable_assert_run.c
Normal file
242
test/wcc/949_nullable_assert_run.c
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
/*
|
||||||
|
* 949_nullable_assert_run — the `e as T` type assertion on a nullable
|
||||||
|
* `(*T | void)` operand must discriminate POINTER-vs-NULL, not compare
|
||||||
|
* the pointer value against a variant tag index (report item #17, F4).
|
||||||
|
*
|
||||||
|
* Gate-blind hazard: for a nullable operand the slot word IS the
|
||||||
|
* pointer (no tag word), so pre-fix wwstage cgtypeassert emitted
|
||||||
|
* `CMPQ $want,AX; JE ok` against the POINTER and then unwrapped a frame
|
||||||
|
* word past the 8B slot — a valid pointer aborted, a void value
|
||||||
|
* silently PASSED, and the unwrap read garbage. cstage already carried
|
||||||
|
* the nullable arm (cmd/w6c/cgen.c N_TYPEASSERT) and ww's own
|
||||||
|
* cgtypetest carries the matching nullable fold; the fix mirrors that
|
||||||
|
* arm into cgtypeassert. wwstage-only (cstage correct) → after the fix
|
||||||
|
* cs.s == ww.s, so the byte-id row witnesses rule-10 convergence.
|
||||||
|
*
|
||||||
|
* Two assertions per row:
|
||||||
|
* - RUNTIME: build with `ww` (cstage) and `ww_ww` (wwstage), run,
|
||||||
|
* compare exit code. This is what was wrong pre-fix.
|
||||||
|
* - ASM BYTE-ID: compile the same source through `w6c` and `w6c_ww`
|
||||||
|
* and require byte-identical .s (rule 10).
|
||||||
|
*
|
||||||
|
* Rows:
|
||||||
|
* 1. asrt_ptr_ok — valid ptr `as *int` must unwrap and deref.
|
||||||
|
* Pre-fix wwstage aborted exit(1) (CMPQ against the pointer).
|
||||||
|
* 2. asrt_void_fail — void value `as *int` must abort exit(1).
|
||||||
|
* Pre-fix wwstage silently PASSED (null == ptr-tag 0).
|
||||||
|
* 3. asrt_void_target_fail — valid ptr `as void` must abort exit(1)
|
||||||
|
* (non-null != the void variant). Exercises the void polarity.
|
||||||
|
* 4. asrt_void_target_ok — void value `as void` must succeed.
|
||||||
|
* Exercises the void polarity on the matching side.
|
||||||
|
*/
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <sys/stat.h>
|
||||||
|
#include <sys/wait.h>
|
||||||
|
|
||||||
|
static int
|
||||||
|
runwait(const char *cmd)
|
||||||
|
{
|
||||||
|
int rc = system(cmd);
|
||||||
|
if (rc == -1) return -1;
|
||||||
|
if (WIFEXITED(rc)) return WEXITSTATUS(rc);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct row { const char *label; const char *src; int want; };
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
{ "asrt_ptr_ok",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: int = 42;\n"
|
||||||
|
" let p: (*int|void) = &x;\n"
|
||||||
|
" let q: *int = p as *int;\n"
|
||||||
|
" return (*q): i32;\n"
|
||||||
|
"};\n",
|
||||||
|
42 },
|
||||||
|
{ "asrt_void_fail",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let p: (*int|void) = void;\n"
|
||||||
|
" let q = p as *int;\n"
|
||||||
|
" return 50;\n"
|
||||||
|
"};\n",
|
||||||
|
1 },
|
||||||
|
{ "asrt_void_target_fail",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: int = 5;\n"
|
||||||
|
" let p: (*int|void) = &x;\n"
|
||||||
|
" let z = p as void;\n"
|
||||||
|
" return 7;\n"
|
||||||
|
"};\n",
|
||||||
|
1 },
|
||||||
|
{ "asrt_void_target_ok",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let p: (*int|void) = void;\n"
|
||||||
|
" let z = p as void;\n"
|
||||||
|
" return 7;\n"
|
||||||
|
"};\n",
|
||||||
|
7 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
write_src(const char *dir, const char *base, const struct row *r, char *out,
|
||||||
|
size_t outsz)
|
||||||
|
{
|
||||||
|
snprintf(out, outsz, "%s/%s.ww", dir, base);
|
||||||
|
FILE *f = fopen(out, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
build_run(const char *driver, const char *src, const char *workdir)
|
||||||
|
{
|
||||||
|
char cmd[8192];
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s > /dev/null 2>&1",
|
||||||
|
workdir, driver, src);
|
||||||
|
if (runwait(cmd) != 0) return -1;
|
||||||
|
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
char outbin[1024];
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", workdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
return runwait(outbin);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int
|
||||||
|
files_equal(const char *a, const char *b)
|
||||||
|
{
|
||||||
|
FILE *fa = fopen(a, "rb");
|
||||||
|
FILE *fb = fopen(b, "rb");
|
||||||
|
if (!fa || !fb) { if (fa) fclose(fa); if (fb) fclose(fb); return -1; }
|
||||||
|
int ca, cb, eq = 1;
|
||||||
|
do {
|
||||||
|
ca = fgetc(fa);
|
||||||
|
cb = fgetc(fb);
|
||||||
|
if (ca != cb) { eq = 0; break; }
|
||||||
|
} while (ca != EOF);
|
||||||
|
fclose(fa);
|
||||||
|
fclose(fb);
|
||||||
|
return eq;
|
||||||
|
}
|
||||||
|
|
||||||
|
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], cw6[640], ww6[640];
|
||||||
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||||
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||||
|
snprintf(cw6, sizeof cw6, "%s/w6c", bin);
|
||||||
|
snprintf(ww6, sizeof ww6, "%s/w6c_ww", bin);
|
||||||
|
|
||||||
|
int have_ww = (access(wdrv, X_OK) == 0);
|
||||||
|
int have_w6cww = (access(ww6, X_OK) == 0);
|
||||||
|
|
||||||
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
const struct row *r = &rows[i];
|
||||||
|
|
||||||
|
char dir[] = "/tmp/nasr.XXXXXX";
|
||||||
|
if (mkdtemp(dir) == NULL) {
|
||||||
|
fprintf(stderr, "row[%s]: mkdtemp failed\n", r->label);
|
||||||
|
fail++; total++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
char src[1024];
|
||||||
|
if (write_src(dir, "p", r, src, sizeof src) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: write src failed\n", r->label);
|
||||||
|
fail++; total++;
|
||||||
|
goto cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_w6cww) {
|
||||||
|
char css[1024], wss[1024], cmd[8192];
|
||||||
|
snprintf(css, sizeof css, "%s/cs.s", dir);
|
||||||
|
snprintf(wss, sizeof wss, "%s/ww.s", dir);
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
|
||||||
|
cw6, css, src);
|
||||||
|
int rc1 = runwait(cmd);
|
||||||
|
snprintf(cmd, sizeof cmd, "%s -o %s %s > /dev/null 2>&1",
|
||||||
|
ww6, wss, src);
|
||||||
|
int rc2 = runwait(cmd);
|
||||||
|
total++;
|
||||||
|
if (rc1 != 0 || rc2 != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s]: w6c/w6c_ww emit failed (%d/%d)\n",
|
||||||
|
r->label, rc1, rc2);
|
||||||
|
fail++;
|
||||||
|
} else if (files_equal(css, wss) != 1) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s]: cs.s != ww.s (rule-10 break)\n",
|
||||||
|
r->label);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
char wk[1024];
|
||||||
|
snprintf(wk, sizeof wk, "%s/cs", dir);
|
||||||
|
mkdir(wk, 0755);
|
||||||
|
int got = build_run(cdrv, src, wk);
|
||||||
|
total++;
|
||||||
|
if (got != r->want) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s][cstage]: exit=%d want=%d\n",
|
||||||
|
r->label, got, r->want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (have_ww) {
|
||||||
|
char wk[1024];
|
||||||
|
snprintf(wk, sizeof wk, "%s/ww", dir);
|
||||||
|
mkdir(wk, 0755);
|
||||||
|
int got = build_run(wdrv, src, wk);
|
||||||
|
total++;
|
||||||
|
if (got != r->want) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"row[%s][wwstage]: exit=%d want=%d\n",
|
||||||
|
r->label, got, r->want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanup:
|
||||||
|
{
|
||||||
|
char rm[1100];
|
||||||
|
snprintf(rm, sizeof rm, "rm -rf %s", dir);
|
||||||
|
runwait(rm);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"nullable_assert_run: %d/%d checks failed\n", fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("nullable_assert_run: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user