wcc: store to a global tagged-array element resolves the (SB) base (#16)
The indexed tagged-element assign arm computed its base without the isglobal -> LEAQ name(SB) branch the scalar element arm already has, so `gs[i] = v` on a global tagged array stored to a junk frame base and was lost -- cstage rc=0 where wwstage (which has the branch) rc=42. Mirror the scalar arm's base resolution; cstage aligns up to wwstage. Local tagged arrays and scalar globals are unchanged. The global tagged-array static initializer still mis-packs its DATA in both stages -- a separate emitter path, filed as #19.
This commit is contained in:
254
test/wcc/989_taggedglobalindex_run.c
Normal file
254
test/wcc/989_taggedglobalindex_run.c
Normal file
@@ -0,0 +1,254 @@
|
||||
/*
|
||||
* 989_taggedglobalindex_run — #16 teeth (recorded by impl-16, 2026-06-14).
|
||||
*
|
||||
* Indexed store into a GLOBAL array of a tagged-union type: `gs[i] = v` where
|
||||
* gs : [N](A | B). The indexed-tagged-element assign arm (cmd/w6c/cgen.c
|
||||
* ~6502) computed its base address with only LOCAL/frame variants — it LACKED
|
||||
* the `isglobal -> LEAQ name(SB)` branch the SCALAR `arr[i] = v` element arm
|
||||
* (~6767) already had. For a global tagged array the base resolved to a junk
|
||||
* BP-relative offset (off==0), so the widened store landed nowhere observable;
|
||||
* a later `match (gs[i])` then read the unchanged static-init slot. cstage
|
||||
* silently returned 0 (WRONG); wwstage (cgenexpr.ww index arm, already carries
|
||||
* the globalname branch) returned the correct value — a cs!=ww divergence the
|
||||
* byte-id gate could SEE. #16 aligns cstage UP: it adds the mirrored
|
||||
* `isglobal && is_arr -> LEAQ name(SB)` / `isglobal -> MOVQ name(SB)` branch to
|
||||
* the indexed-tagged-elem arm. cstage-only; no .ww source touched.
|
||||
*
|
||||
* Rows (each program self-checks and returns 0 on all-correct, a 1-based code
|
||||
* on the first mismatch; both stages build+run, rule-10, pin 0):
|
||||
* glo_taglo: global [N](int|bool), gs[1]=42 — tag-low (int) variant, read
|
||||
* back 42. The literal #16 teeth (cstage pre-fix gave 0).
|
||||
* glo_taghi: global [N](int|bool), gs[1]=true — tag-high (bool) variant;
|
||||
* the tag must remap through the widener.
|
||||
* glo_varidx: global [N](int|bool), runtime index gs[i]=99 — the scaled-
|
||||
* index path (IMULQ) layered on the global base.
|
||||
* glo_str3w: global [N](int|str), gs[1]="hello" — a str payload is a
|
||||
* 3-word {ptr,len,cap} header; proves the multi-word slot copy
|
||||
* over the global base is COMPLETE, not just word0/word1.
|
||||
* glo_struct2w:global [N](int|pair), gs[1]=pair{...} — a 16B struct payload
|
||||
* (slot = tag + 2 payload words); proves the mid-count copy loop
|
||||
* moves BOTH payload words over the global base.
|
||||
* loc_ctl: LOCAL [N](int|bool), a[1]=42 — control for the frame-base
|
||||
* path that ALREADY worked; must stay correct + byte-id.
|
||||
* scalar_ctl: scalar global [N]int, arr[1]=42 — control for the template
|
||||
* arm (the scalar element store) the fix was mirrored from.
|
||||
*/
|
||||
#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_exit; /* >= 0: pin the absolute value; -1: cs==ww only */
|
||||
};
|
||||
|
||||
static const struct row rows[] = {
|
||||
/* glo_taglo — the #16 teeth: global tagged array, int variant store. */
|
||||
{ "glo_taglo",
|
||||
"package main;\n"
|
||||
"let gs: [2](int | bool) = [0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" gs[1] = 42;\n"
|
||||
" match (gs[1]) {\n"
|
||||
" case let n: int => { if (n != 42) { return 1; }; return 0; };\n"
|
||||
" case bool => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* glo_taghi — bool variant via gs[1]=true; the tag must remap. */
|
||||
{ "glo_taghi",
|
||||
"package main;\n"
|
||||
"let gs: [2](int | bool) = [0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" gs[1] = true;\n"
|
||||
" match (gs[1]) {\n"
|
||||
" case int => { return 1; };\n"
|
||||
" case let b: bool => { if (!b) { return 2; }; return 0; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* glo_varidx — runtime index over the global base (scaled IMULQ). */
|
||||
{ "glo_varidx",
|
||||
"package main;\n"
|
||||
"let gs: [3](int | bool) = [0, 0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" let i: int = 2;\n"
|
||||
" gs[i] = 99;\n"
|
||||
" match (gs[2]) {\n"
|
||||
" case let n: int => { if (n != 99) { return 1; }; return 0; };\n"
|
||||
" case bool => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* glo_str3w — str payload (3-word header); the multi-word slot copy
|
||||
* over the global base must be complete. */
|
||||
{ "glo_str3w",
|
||||
"package main;\n"
|
||||
"let gs: [2](int | str) = [0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" gs[1] = \"hello\";\n"
|
||||
" match (gs[1]) {\n"
|
||||
" case int => { return 1; };\n"
|
||||
" case let s: str => { if (s.len: int != 5) { return 2; }; return 0; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* glo_struct2w — a 16B struct payload (slot = tag + 2 payload words);
|
||||
* proves the mid-count copy loop moves BOTH payload words. */
|
||||
{ "glo_struct2w",
|
||||
"package main;\n"
|
||||
"type pair = struct { a: i64, b: i64 };\n"
|
||||
"let gs: [2](int | pair) = [0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" gs[1] = pair { a = 0x1111, b = 0x2222 };\n"
|
||||
" match (gs[1]) {\n"
|
||||
" case int => { return 1; };\n"
|
||||
" case let q: pair => {\n"
|
||||
" if (q.a != 0x1111) { return 2; };\n"
|
||||
" if (q.b != 0x2222) { return 3; };\n"
|
||||
" return 0;\n"
|
||||
" };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* loc_ctl — LOCAL tagged array: the frame-base path that already worked
|
||||
* must stay correct (the fix must not perturb the non-global branch). */
|
||||
{ "loc_ctl",
|
||||
"package main;\n"
|
||||
"export fn main() int = {\n"
|
||||
" let a: [2](int | bool) = [0, 0];\n"
|
||||
" a[1] = 42;\n"
|
||||
" match (a[1]) {\n"
|
||||
" case let n: int => { if (n != 42) { return 1; }; return 0; };\n"
|
||||
" case bool => { return 2; };\n"
|
||||
" };\n"
|
||||
"};\n",
|
||||
0 },
|
||||
|
||||
/* scalar_ctl — scalar global array: the template arm the fix mirrors. */
|
||||
{ "scalar_ctl",
|
||||
"package main;\n"
|
||||
"let arr: [3]int = [0, 0, 0];\n"
|
||||
"export fn main() int = {\n"
|
||||
" arr[1] = 42;\n"
|
||||
" if (arr[1] != 42) { return 1; };\n"
|
||||
" return 0;\n"
|
||||
"};\n",
|
||||
0 },
|
||||
};
|
||||
|
||||
/* run_build — build+run `src` via `driver`; returns the binary's exit code,
|
||||
* or -1 on a build failure. */
|
||||
static int
|
||||
run_build(const char *driver, const struct row *r, int i)
|
||||
{
|
||||
char src[64], tmpdir[64], cmd[1024];
|
||||
snprintf(src, sizeof src, "/tmp/tagglobidx_%d_%d.ww", getpid(), i);
|
||||
snprintf(tmpdir, sizeof tmpdir, "/tmp/tagglobidx_%d_d_%d", getpid(), i);
|
||||
|
||||
FILE *f = fopen(src, "wb");
|
||||
if (!f) return -2;
|
||||
fputs(r->src, f);
|
||||
fclose(f);
|
||||
|
||||
mkdir(tmpdir, 0755);
|
||||
snprintf(cmd, sizeof cmd, "cd %s && %s build %s 2>/dev/null",
|
||||
tmpdir, driver, src);
|
||||
int brc = runwait(cmd);
|
||||
|
||||
const char *base = strrchr(src, '/');
|
||||
base = base ? base + 1 : src;
|
||||
char outbin[128];
|
||||
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||
char *dot = strrchr(outbin, '.');
|
||||
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||
|
||||
int got = -1;
|
||||
if (brc == 0) got = runwait(outbin);
|
||||
|
||||
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||
return brc == 0 ? got : -1;
|
||||
}
|
||||
|
||||
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 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++;
|
||||
int gc = run_build(cdrv, &rows[i], i);
|
||||
if (gc < 0) {
|
||||
fprintf(stderr, "taggedglobalindex_run[cstage][%s]: build/run "
|
||||
"failed (got %d)\n", rows[i].label, gc);
|
||||
fail++;
|
||||
continue;
|
||||
}
|
||||
if (rows[i].want_exit >= 0 && gc != rows[i].want_exit) {
|
||||
fprintf(stderr, "taggedglobalindex_run[cstage][%s]: exit=%d "
|
||||
"want=%d (global base dropped / store lost)\n",
|
||||
rows[i].label, gc, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
if (!have_ww) {
|
||||
fprintf(stderr, "taggedglobalindex_run: skip wwstage (no %s)\n",
|
||||
wdrv);
|
||||
continue;
|
||||
}
|
||||
int gw = run_build(wdrv, &rows[i], i);
|
||||
if (gw != gc) {
|
||||
fprintf(stderr, "taggedglobalindex_run[%s]: cs=%d != ww=%d "
|
||||
"(#16 indexed-global-tagged base divergence)\n",
|
||||
rows[i].label, gc, gw);
|
||||
fail++;
|
||||
}
|
||||
if (rows[i].want_exit >= 0 && gw != rows[i].want_exit) {
|
||||
fprintf(stderr, "taggedglobalindex_run[wwstage][%s]: exit=%d "
|
||||
"want=%d (global base dropped / store lost)\n",
|
||||
rows[i].label, gw, rows[i].want_exit);
|
||||
fail++;
|
||||
}
|
||||
}
|
||||
|
||||
if (fail) {
|
||||
fprintf(stderr, "taggedglobalindex_run: %d/%d checks failed\n",
|
||||
fail, total);
|
||||
return 1;
|
||||
}
|
||||
printf("taggedglobalindex_run: %d/%d ok\n", total, total);
|
||||
return 0;
|
||||
}
|
||||
Reference in New Issue
Block a user