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:
2026-06-14 00:14:42 +09:00
parent 1074239859
commit 323607d1d0
3 changed files with 292 additions and 7 deletions

View 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;
}