The migrate-and-retire audit left the surviving carriers as the only files still describing the retired combined.ww amalgamator as live: m2wwi/m3sep headers claimed .wwi was dead code, four byteid carriers carried dead cleanup of never-written .combined.ww intermediates, and sepbuild's enumeration mirror plus lib_byteid's completeness scan still skipped the retired artifact form (the same silent accommodation just removed from both drivers — dropping it here surfaced and flushed 39 stale untracked amalgamator outputs across lib/, which the gates now reject loudly). Remaining mentions are past-tense history or quoted diagnostics.
236 lines
7.4 KiB
C
236 lines
7.4 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 <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;
|
|
}
|
|
|
|
#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 },
|
|
};
|
|
|
|
/* nonzero on any cleanup failure; ENOENT tolerated because legs that
|
|
* fail early never create the later artifacts (a -S build also never
|
|
* produces the base binary / .o / .combined.ww). */
|
|
static int
|
|
cleanup_tmp(const char *tmpdir, const char *base)
|
|
{
|
|
char p[1024];
|
|
int bad = 0;
|
|
snprintf(p, sizeof p, "%s/%s", tmpdir, base);
|
|
if (unlink(p) != 0 && errno != ENOENT) { perror(p); bad = 1; }
|
|
snprintf(p, sizeof p, "%s/%s.ww", tmpdir, base);
|
|
if (unlink(p) != 0 && errno != ENOENT) { perror(p); bad = 1; }
|
|
snprintf(p, sizeof p, "rm -rf %s/%s.sepwork", tmpdir, base);
|
|
if (runwait(p) != 0) {
|
|
fprintf(stderr, "cleanup failed: %s\n", p);
|
|
bad = 1;
|
|
}
|
|
snprintf(p, sizeof p, "%s/%s.o", tmpdir, base);
|
|
if (unlink(p) != 0 && errno != ENOENT) { perror(p); bad = 1; }
|
|
if (rmdir(tmpdir) != 0) { perror(tmpdir); bad = 1; }
|
|
return bad;
|
|
}
|
|
|
|
static int
|
|
write_source(const char *path, const char *src)
|
|
{
|
|
FILE *f = fopen(path, "wb");
|
|
if (!f) return -1;
|
|
if (fputs(src, f) == EOF) {
|
|
fclose(f);
|
|
return -1;
|
|
}
|
|
if (fclose(f) != 0) return -1;
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
build_via_driver(const char *driver, const char *tmpdir, const char *src)
|
|
{
|
|
char cmd[2048];
|
|
/* #93 sep layout: emit asm to <src-stem>.sepwork/__root.s; pin
|
|
* all outputs stay under tmpdir. */
|
|
snprintf(cmd, sizeof cmd,
|
|
"cd %s && b='%s'; timeout 180 %s build -S "
|
|
"-o \"${b%%.ww}\" \"$b\" 2>/dev/null",
|
|
tmpdir, src, driver);
|
|
return runwait(cmd);
|
|
}
|
|
|
|
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");
|
|
int rc = -1, tdw_owned = 0, cleanfail = 0;
|
|
if (mkdir(tdc, 0755) != 0) {
|
|
perror(tdc);
|
|
return -1;
|
|
}
|
|
if (mkdir(tdw, 0755) != 0) {
|
|
perror(tdw);
|
|
goto out;
|
|
}
|
|
tdw_owned = 1;
|
|
snprintf(src, sizeof src, "%s/%s.ww", tdc, base);
|
|
if (write_source(src, r->src) != 0) goto out;
|
|
if (build_via_driver(cdrv, tdc, src) != 0) goto out;
|
|
snprintf(cs, sizeof cs, "%s/%s.sepwork/__root.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.sepwork/__root.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:
|
|
cleanfail = cleanup_tmp(tdc, base);
|
|
if (tdw_owned && cleanup_tmp(tdw, base) != 0)
|
|
cleanfail = 1;
|
|
/* a leaked tree fails an otherwise-green row; a real diff result
|
|
* (rc == -1) is never overwritten (110's cleanfail shape). */
|
|
if (cleanfail && rc == 0)
|
|
rc = -1;
|
|
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 (wwpresent && 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;
|
|
}
|