Files
ww/test/wcc/755_amp_dot_idx.c
Hojun-Cho 7b9488706b parse: enforce strict-package — reject package-less files (#24a)
Flip the soft-default to a hard "missing package clause" error symmetrically in
both stages (cmd/wcc/parse.c + lib/ww/syntax/parse.ww): the first real decl of a
primary section with empty pathmod/resetmod and no seen clause is now rejected.
Closes the documented soft-default divergence (the 63-wrapper carve-out).

The gate flip can't be split from the migration it breaks, so this is one atomic
commit: ~80 test/wcc wrappers gain `package main;` via a shared wwtestpkg.h
helper, 6 data fixtures plus 17 asm-grep assertions update for the bare->main.<leaf>
root-helper mangle shift, and rt/ declares `package rt;` with @symbol pinning the
bare rt_ensure/rt_malloc linker names.

Root mangling narrows: the executable entry `main` stays bare (existing
carve-out), but root helper symbols become main.X. The #84 cluster is rewritten
to assert main.run distinct from aa.run/test.run; its cgen fix and bare machinery
are retained — still load-bearing for package-less module-reset deps. New
table-driven test 782_strict_package.c (6 rows, both stages).

Retiring //ww:module-reset is deferred to #24b: it is load-bearing (clears the
.wwi pathmod so the body's package clause asserts), not a vestige; fusing its
removal here would be a silent mismatch.

All byte-id gates green; full make test reports "all 335 tests passed".
2026-06-29 03:55:26 +09:00

268 lines
8.6 KiB
C
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 755_amp_dot_idx — sentinel for latent #21 (wwstage vs cstage divergence
* in the `&N_DOT[N_INDEX]` cgen path). Two shapes ride the same wedge:
*
* Shape A — register polarity in the "complex base" arm of cgun's
* TK_AMP N_INDEX branch. cstage emits the lean
* PUSHQ AX ; cgexpr(base) ; POPQ BX ; ADDQ BX, AX
* using BX as the popped scratch. wwstage interposed an extra
* MOVQ AX, BX ; POPQ AX ; ADDQ BX, AX
* scratch shuffle with no semantic need (cstage's three-line shape
* also lands ptr+offset in AX). Polarity DOWN to cstage's leaner
* form (rule 10): no semantic asymmetry, just verbose-defensive
* redundancy on the wwstage side.
*
* Shape B — `indexbaseesz` over-applies the `.ptr` pseudo-field arm.
* cgenutil.ww:1148-1160 treated any field literally named "ptr" as
* a str/slice pseudo-field, falling to a hard-coded `return 8` when
* the base wasn't actually str/slice. Bites `&p.ptr[i]` (also the
* value-load path `p.ptr[i]`) for any `*struct{ ptr: *T, ... }`
* where T is a narrow primitive: stride scales to 8 instead of T's
* primsize, and for `*u8` callers the load width drops from MOVZBQ
* to MOVQ (silent miscompile, reading 8B at offset i*8 instead of
* 1B at offset i). cstage doesn't carry the sister bug: it reads
* `base->type->sub->size` directly off the typed AST. Fix: the
* `.ptr` arm now only matches when the inner is str or slice; a
* struct N_TNAME base falls through to the generic struct-field
* arm at 1162+, which already routes `*T` fields through
* `primsize` correctly.
*
* Both shapes share the `cgun` TK_AMP N_INDEX path. The polarity fix
* is the same edit for slice / struct / i64 / u8 element strides; the
* stride fix is the same edit for the u8 case at the `.ptr` arm; they
* compose at the cgun call site (esz from indexbaseesz, then either
* IMULQ-or-elide gate, then complex-base lean form).
*
* Rows 1-4 × (cstage, wwstage) — every row asserts:
* - the expected stride immediate appears (or no stride scale at
* all for esz=1), inside `TEXT probe`;
* - cstage and wwstage emit byte-identical asm.
*
* Pre-fix: all 4 rows fail the byte-id check (Shape A); row 3 also
* fails the stride check (Shape B: wwstage emits `MOVQ $8, CX` for
* a u8 element). Post-fix every row passes both.
*
* Probes mirror the .ai/probe_amp_*.ww shapes that surfaced the wedge:
* - row 0 / `slice_elem_24`: `&s.ptr[i]` where s: *[][]u8 — the
* `.ai/probe_amp_dot_idx.ww` shape 1:1. Stride 24 (slice header
* element span via indexbaseesz's `.ptr` arm with N_TSLICE inner).
* - row 1 / `struct_field_16`: `&p.items[i]` where items: *box16
* (i64+i64). Stride 16 (struct slot size via structlookup +
* totsize in indexbaseesz's *S elem arm).
* - row 2 / `byte_field_1`: `&p.ptr[i]` where ptr: *u8. Pre-fix
* Shape B: stride 8 emitted erroneously. Post-fix stride is
* elided (esz=1, no IMULQ).
* - row 3 / `i64_field_8`: `&p.qs[i]` where qs: *i64. Stride 8
* (already correct since the field name is "qs", not "ptr",
* so the bare path hits the generic struct-field arm).
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/wait.h>
#include "wwtestpkg.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;
const char *stride_imm; /* expected `MOVQ\t$<stride>, CX` or NULL */
};
static const struct row rows[] = {
/* slice_elem_24: `&s.ptr[i]` where s: *[][]u8. Stride 24 — the
* slice header element span via indexbaseesz's `.ptr` arm on
* an N_TSLICE inner (post-N_TPTR peel). Polarity-A only.
* Mirrors .ai/probe_amp_dot_idx.ww 1:1. */
{ "slice_elem_24",
"fn probe(s: *[][]u8, i: i32) *[]u8 = {\n"
"\treturn &s.ptr[i];\n"
"};\n"
"export fn main() i32 = { return 0; };\n",
"MOVQ\t$24, CX" },
/* struct_field_16: `&p.items[i]` where items: *box16, box16
* is two i64 fields → struct slot 16B. Stride 16 via
* indexbaseesz's `*S` arm (structlookup totsize). */
{ "struct_field_16",
"type box16 = struct { a: i64, b: i64 };\n"
"type holder = struct { items: *box16 };\n"
"fn probe(p: *holder, i: i32) *box16 = {\n"
"\treturn &p.items[i];\n"
"};\n"
"export fn main() i32 = { return 0; };\n",
"MOVQ\t$16, CX" },
/* byte_field_1: `&p.ptr[i]` where ptr: *u8. Pre-fix wwstage
* emits stride 8 (`.ptr` arm overreach). Post-fix: no IMULQ
* because stride is 1 and the gate elides. */
{ "byte_field_1",
"type holder = struct { ptr: *u8 };\n"
"fn probe(p: *holder, i: i32) *u8 = {\n"
"\treturn &p.ptr[i];\n"
"};\n"
"export fn main() i32 = { return 0; };\n",
NULL },
/* i64_field_8: `&p.qs[i]` where qs: *i64. Stride 8 via
* the generic struct-field arm (`primsize("i64")` = 8).
* Polarity-A only — the `.ptr` arm doesn't gate on "qs",
* so even pre-fix the stride is correct. */
{ "i64_field_8",
"type holder = struct { qs: *i64 };\n"
"fn probe(p: *holder, i: i32) *i64 = {\n"
"\treturn &p.qs[i];\n"
"};\n"
"export fn main() i32 = { return 0; };\n",
"MOVQ\t$8, CX" },
};
static int
slurp(const char *path, char *buf, size_t cap)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
size_t n = fread(buf, 1, cap - 1, f);
fclose(f);
buf[n] = '\0';
return (int)n;
}
static int
emit_s(const char *w6c, const struct row *r, int i, char *out_s, size_t cap)
{
char src[96], cmd[1024];
snprintf(src, sizeof src, "/tmp/adi_%d_%d.ww", getpid(), i);
snprintf(out_s, cap, "/tmp/adi_%d_%d_%s.s",
getpid(), i, w6c[strlen(w6c) - 1] == 'w' ? "ww" : "c");
FILE *f = fopen(src, "wb");
if (!f) return -1;
wwtest_fputs(r->src, f);
fclose(f);
snprintf(cmd, sizeof cmd, "%s -o %s %s 2>/dev/null", w6c, out_s, src);
int rc = runwait(cmd);
unlink(src);
return rc;
}
static int
check_stride(const char *spath, const struct row *r, const char *stage)
{
char buf[1 << 14];
if (slurp(spath, buf, sizeof buf) < 0) {
fprintf(stderr, "row[%s][%s]: cannot read %s\n",
r->label, stage, spath);
return -1;
}
const char *fn = strstr(buf, "TEXT main.probe");
if (!fn) {
fprintf(stderr,
"row[%s][%s]: no TEXT probe in %s\n",
r->label, stage, spath);
return -1;
}
const char *ret = strstr(fn, "\tRET\n");
if (!ret) ret = fn + strlen(fn);
if (r->stride_imm) {
const char *m = strstr(fn, r->stride_imm);
if (!m || m >= ret) {
fprintf(stderr,
"row[%s][%s]: expected `%s` inside TEXT probe\n",
r->label, stage, r->stride_imm);
return -1;
}
} else {
const char *m = strstr(fn, "\tMOVQ\t$");
while (m && m < ret) {
const char *eol = strchr(m, '\n');
const char *cxpos = strstr(m, ", CX\n");
if (eol && cxpos && cxpos < eol) {
fprintf(stderr,
"row[%s][%s]: unexpected MOVQ $imm, CX "
"(scale) inside TEXT probe — stride-1 "
"path should elide the multiply\n",
r->label, stage);
return -1;
}
m = eol ? strstr(eol, "\tMOVQ\t$") : NULL;
}
}
return 0;
}
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 w6c[640], w6c_ww[640];
snprintf(w6c, sizeof w6c, "%s/w6c", bin);
snprintf(w6c_ww, sizeof w6c_ww, "%s/w6c_ww", bin);
int have_ww = (access(w6c_ww, X_OK) == 0);
int n = (int)(sizeof rows / sizeof rows[0]);
int total = 0, fail = 0;
for (int i = 0; i < n; i++) {
char cs_path[128], ws_path[128];
if (emit_s(w6c, &rows[i], i, cs_path, sizeof cs_path) != 0) {
fprintf(stderr,
"amp_dot_idx[cstage][%s]: w6c failed\n",
rows[i].label);
fail++; total++; continue;
}
total++;
if (check_stride(cs_path, &rows[i], "cstage") != 0) fail++;
if (!have_ww) { unlink(cs_path); continue; }
if (emit_s(w6c_ww, &rows[i], i, ws_path, sizeof ws_path) != 0) {
fprintf(stderr,
"amp_dot_idx[wwstage][%s]: w6c_ww failed\n",
rows[i].label);
fail++; total++;
unlink(cs_path); continue;
}
total++;
if (check_stride(ws_path, &rows[i], "wwstage") != 0) fail++;
total++;
char cmd[512];
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path);
if (runwait(cmd) != 0) {
fprintf(stderr,
"amp_dot_idx[%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
unlink(cs_path); unlink(ws_path);
}
if (fail) {
fprintf(stderr,
"amp_dot_idx: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("amp_dot_idx: %d/%d ok\n", total, total);
return 0;
}