wwstage's cglet no-rhs path zero-inited only 8B primitives (MOVQ) and >8B composites (XORQ run), so an 8B *composite* local (single-field struct/tagged, e.g. struct{src:*vtable}) declared bare (let b: box;) was left uninitialized -- reading an unassigned field returned stack garbage (a silent read-before-init), and it diverged from cstage which zero-inits any 8B local (cs!=ww byte-id, surfaced by #5's bufio box{src:io.stream}). Add the missing arm: a non-array composite of size 8 emits MOVQ $0, matching cstage's no-rhs sz==8 zeroing. cstage unchanged (already correct -- align wwstage UP). Scope is 8B-only: cstage does not zero-init sub-8 composites either (sub-8 falls through to nothing on both stages, already cs==ww), so zeroing sub-8 on wwstage would create a new divergence; the sub-8 read-before-init garbage is a separate shared-both-stages latent (#20). Adds test/wcc/790 (8B byte-id row + read-before-init correctness lock reading 0 on both stages). rule-10 align-up; closes the #213 8B-composite slice; unblocks post-eFinal #5.
244 lines
7.5 KiB
C
244 lines
7.5 KiB
C
/*
|
|
* 790_single_field_struct_zeroinit — project #213 (≤8B-composite slice).
|
|
* Pins that a bare `let b: T;` of an 8B single-field STRUCT zero-inits
|
|
* the slot on BOTH stages byte-identically (rule-10), and that the
|
|
* zero is observable (read-before-init sees 0, not stack garbage).
|
|
*
|
|
* THE BUG (wwstage cgen): wwstage's cglet no-rhs zero-init
|
|
* (selfhost/cmd/wcc/cgenstmt.ww) only zeroed an 8B *primitive*
|
|
* (scalar/ptr/fn/chan → MOVQ $0) or a `zsz > 8` composite (XORQ run).
|
|
* An 8B *composite* (single-field struct/tagged) was NEITHER → it fell
|
|
* through with no zero-init, while cstage emits `MOVQ $0` (cgen.c N_LET
|
|
* `else if (sz == 8)`, per ww's bare-`let`-composite contract). So a
|
|
* read-before-init saw stack garbage on wwstage but 0 on cstage —
|
|
* cs!=ww byte-id (surfaced by #5's bufio `box { src: io.stream }`) AND
|
|
* a latent silent miscompile. cstage is correct; align wwstage UP.
|
|
*
|
|
* SCOPE: 8B composites only. Sub-8 (4B/1B) composites are intentionally
|
|
* left un-zeroed — cstage doesn't zero them either, so zeroing them on
|
|
* wwstage would RE-diverge; that sub-8 read-before-init garbage is a
|
|
* SHARED (cs==ww) latent, out of this slice.
|
|
*
|
|
* row | shape | exit | byte-id
|
|
* -------------------+-----------------------------------------+------+--------
|
|
* field8_assign | box{src:*vtable}; let b; b.src=&vt; | 42 | cs==ww
|
|
* | read b.src.x — the 778/#5 shape | |
|
|
* | (#5-independent: no io/union) | |
|
|
* read_before_init | box{x:i64}; dirty the stack, then bare | 0 | cs==ww
|
|
* | `let b; return b.x` UNassigned — must | |
|
|
* | read 0 (the load-bearing zero-init, | |
|
|
* | was stack garbage pre-fix on wwstage) | |
|
|
*
|
|
* GATE POLARITY: must stay GREEN. Red on field8_assign = the 8B-struct
|
|
* zero-init regressed (cs!=ww); red on read_before_init = the zero-init
|
|
* stopped being emitted (garbage read).
|
|
*/
|
|
#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 want_exit;
|
|
int stage_mask;
|
|
int byte_id;
|
|
};
|
|
|
|
static const struct row rows[] = {
|
|
/* The 778/#5 shape, #5-independent: single-field struct bare-let
|
|
* then field-assign. Pre-fix cstage zero-inits b (dead here since
|
|
* b.src is assigned), wwstage omits it → cs!=ww. */
|
|
{ "field8_assign",
|
|
"package main;\n"
|
|
"type vtable = struct { x: i32 };\n"
|
|
"type box = struct { src: *vtable };\n"
|
|
"fn use(b: *box) i32 = { return b.src.x; };\n"
|
|
"export fn main() i32 = {\n"
|
|
" let vt: vtable; vt.x = 42;\n"
|
|
" let b: box; b.src = &vt;\n"
|
|
" return use(&b);\n"
|
|
"};\n",
|
|
42, STAGE_CS | STAGE_WW, 1 },
|
|
|
|
/* The correctness lock: dirty the stack, then read an UNassigned
|
|
* 8B-struct field — must be 0 (zero-init), not garbage. */
|
|
{ "read_before_init",
|
|
"package main;\n"
|
|
"type box = struct { x: i64 };\n"
|
|
"fn dirty() i64 = { let a: i64 = 0x7777777777777777i64; let b: i64 = a; return b; };\n"
|
|
"fn readit() i64 = { let b: box; return b.x; };\n"
|
|
"export fn main() i32 = {\n"
|
|
" let j: i64 = dirty();\n"
|
|
" let v: i64 = readit();\n"
|
|
" if (v == 0i64) { return 0; };\n"
|
|
" return 1;\n"
|
|
"};\n",
|
|
0, STAGE_CS | STAGE_WW, 1 },
|
|
};
|
|
|
|
static void
|
|
cleanup_tmp(const char *tmpdir, const char *base)
|
|
{
|
|
char p[1024];
|
|
snprintf(p, sizeof p, "%s/%s", tmpdir, base); unlink(p);
|
|
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);
|
|
rmdir(tmpdir);
|
|
}
|
|
|
|
static int
|
|
write_source(const char *path, const char *src)
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
fputs(src, f);
|
|
fclose(f);
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
|
{
|
|
char cmd[2048];
|
|
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[512], base[64], outbin[768];
|
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/sfsz_%d_d_%d", getpid(), seq);
|
|
snprintf(base, sizeof base, "main790");
|
|
snprintf(src, sizeof src, "%s/%s.ww", tmpdir, base);
|
|
mkdir(tmpdir, 0755);
|
|
if (write_source(src, r->src) != 0) { cleanup_tmp(tmpdir, base); return -1; }
|
|
int rc;
|
|
if (build_via_driver(driver, tmpdir, src) == 0) {
|
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
|
rc = runwait(outbin);
|
|
} else {
|
|
rc = -1;
|
|
}
|
|
cleanup_tmp(tmpdir, base);
|
|
return rc;
|
|
}
|
|
|
|
static int
|
|
asm_byte_identical(const char *cdrv, const char *wdrv, const struct row *r,
|
|
int seq)
|
|
{
|
|
char src[512], tdc[256], tdw[256], base[64], cs[512], ws[512];
|
|
snprintf(tdc, sizeof tdc, "/tmp/sfsz_%d_c_%d", getpid(), seq);
|
|
snprintf(tdw, sizeof tdw, "/tmp/sfsz_%d_w_%d", getpid(), seq);
|
|
snprintf(base, sizeof base, "main790");
|
|
mkdir(tdc, 0755);
|
|
mkdir(tdw, 0755);
|
|
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
|
|
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/%s.ww", tdw, base);
|
|
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 cwd[256];
|
|
if (getcwd(cwd, sizeof cwd) == NULL) return 1;
|
|
char absbin[512];
|
|
if (bin[0] != '/') {
|
|
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 n = (int)(sizeof rows / sizeof rows[0]);
|
|
int total = 0, fail = 0, seq = 0;
|
|
int wwpresent = (access(wdrv, X_OK) == 0);
|
|
|
|
for (int i = 0; i < n; i++) {
|
|
const struct row *r = &rows[i];
|
|
|
|
if (r->stage_mask & STAGE_CS) {
|
|
total++;
|
|
int got = run_row(cdrv, r, seq++);
|
|
if (got != r->want_exit) {
|
|
fprintf(stderr, "sfsz[cs][%s]: exit=%d want=%d\n",
|
|
r->label, got, r->want_exit);
|
|
fail++;
|
|
}
|
|
}
|
|
|
|
if (wwpresent && (r->stage_mask & STAGE_WW)) {
|
|
total++;
|
|
int got = run_row(wdrv, r, seq++);
|
|
if (got != r->want_exit) {
|
|
fprintf(stderr, "sfsz[ww][%s]: exit=%d want=%d\n",
|
|
r->label, got, r->want_exit);
|
|
fail++;
|
|
}
|
|
if (r->byte_id) {
|
|
total++;
|
|
if (asm_byte_identical(cdrv, wdrv, r, seq++) != 0) {
|
|
fprintf(stderr, "sfsz[byte-id][%s]: cstage vs wwstage asm differs\n",
|
|
r->label);
|
|
fail++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fail) {
|
|
fprintf(stderr, "single_field_struct_zeroinit: %d/%d checks failed\n",
|
|
fail, total);
|
|
return 1;
|
|
}
|
|
printf("single_field_struct_zeroinit: %d/%d ok\n", total, total);
|
|
return 0;
|
|
}
|