Files
ww/test/wcc/730_sret_narrow_field.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

278 lines
8.2 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.
/*
* 730_sret_narrow_field — Class A asm-presence + byte-id for the
* sret callee's N_IDENT word-copy tail when the returned struct
* ends in a narrow primitive (bool / u8 / i8 / i16 / i32).
*
* Task #33. Cstage pre-fix used the slot-padded `rt->size` to drive
* the field-copy loop's MOVQ/MOVL/MOVB tail; for a struct whose
* natural size is unaligned (e.g. `{ i32, []u8, bool }` natural=33,
* padded=40), the trailing MOVQ at offset 32 widened a 1-byte bool
* into an 8-byte load+store — diverged from wwstage's correct MOVB
* (which used natural size via `sretretsize` / `structnaturalsize`).
* Corpus-coverage-blind: no in-tree stdlib struct had a bool field
* at any offset until lib/strings.iterator landed (Hare's
* `iterator { dec, reverse }` shape).
*
* Sentinel per row: in the `mk` body, between the final pre-RET
* "RAX = @sretarg load" pair (the SysV return-the-pointer
* discipline) and the first MOVQ word-store into (BX), assert
* the last copy instruction targets the narrow MOV width derived
* from the final field's type — NOT MOVQ. The trailing-field offset
* is hard-coded per row (structurally fixed) and we pin both the
* read-from-(BP) and store-to-(BX) sides.
*
* Plus byte-id between stages per row.
*/
#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;
}
/* `off` = byte offset of the trailing narrow field inside the
* struct (= natural offset of that field). `mov` = expected mnemonic
* for the trailing field-copy at that offset (MOVB / MOVW / MOVL).
* Rows all share the shape `{ i32 a, []u8 s, NARROW r }` so a sits
* at 0..4, s at 8..32, r at 32..32+sz(r). */
struct row { const char *label; const char *src; int off; const char *mov; };
static const struct row rows[] = {
{ "trailing_bool",
"type t = struct { a: i32, s: []u8, r: bool };\n"
"export fn mk() t = {\n"
" let v: t; let z: []u8;\n"
" v.s = z; v.a = 0; v.r = false;\n"
" return v;\n"
"};\n"
"export fn main() i32 = {\n"
" let x: t = mk();\n"
" if (x.r) { return 1; };\n"
" return 0;\n"
"};\n", 32, "MOVB" },
{ "trailing_u8",
"type t = struct { a: i32, s: []u8, r: u8 };\n"
"export fn mk() t = {\n"
" let v: t; let z: []u8;\n"
" v.s = z; v.a = 0; v.r = 0u8;\n"
" return v;\n"
"};\n"
"export fn main() i32 = {\n"
" let x: t = mk();\n"
" if (x.r != 0u8) { return 1; };\n"
" return 0;\n"
"};\n", 32, "MOVB" },
/* Trailing i16: natural size 34. The chained loop has only
* MOVQ/MOVL/MOVB arms (matching wwstage's identical chain),
* so a 2-byte tail falls through to 2× MOVB at offsets 32, 33.
* That's still narrow + byte-id; the bug-check is the negative
* "no MOVQ at offset 32". Promoting both stages to a MOVW arm
* is a separate refactor (preserves byte-id but out of #33). */
{ "trailing_i16",
"type t = struct { a: i32, s: []u8, r: i16 };\n"
"export fn mk() t = {\n"
" let v: t; let z: []u8;\n"
" v.s = z; v.a = 0; v.r = 0i16;\n"
" return v;\n"
"};\n"
"export fn main() i32 = {\n"
" let x: t = mk();\n"
" if (x.r != 0i16) { return 1; };\n"
" return 0;\n"
"};\n", 32, "MOVB" },
{ "trailing_i32",
"type t = struct { a: i32, s: []u8, r: i32 };\n"
"export fn mk() t = {\n"
" let v: t; let z: []u8;\n"
" v.s = z; v.a = 0; v.r = 0;\n"
" return v;\n"
"};\n"
"export fn main() i32 = {\n"
" let x: t = mk();\n"
" if (x.r != 0) { return 1; };\n"
" return 0;\n"
"};\n", 32, "MOVL" },
};
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/sret_narrow_%d_%d.ww", getpid(), i);
snprintf(out_s, cap, "/tmp/sret_narrow_%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;
}
/* Asm-presence sentinel: inside mk's body (between `TEXT mk,` and
* the first `RET`), the trailing field-copy at offset `r->off` must
* use the narrow MOV mnemonic — both the read-from-(BP) and the
* store-to-(BX) lines. Pre-fix the cstage emitted MOVQ at that
* offset (8-byte over-wide) because the loop drove off slot-padded
* size; wwstage emitted the narrow MOV via natural size. */
static int
check_narrow_mov(const char *path, const struct row *r)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
char line[1024];
int in_mk = 0;
int saw_store = 0;
int saw_load = 0;
char store_needle[64], load_needle[64];
snprintf(store_needle, sizeof store_needle, "%s\tAX, %d(BX)\n",
r->mov, r->off);
snprintf(load_needle, sizeof load_needle, "%d(BP), AX",
-16); /* not used as exact-match — see contains below */
(void)load_needle;
while (fgets(line, sizeof line, f)) {
if (!in_mk) {
if (strstr(line, "TEXT main.mk,")
|| strstr(line, "TEXT\tmain.mk,"))
in_mk = 1;
continue;
}
if (strstr(line, "\tRET\n")) break;
if (strstr(line, store_needle)) saw_store = 1;
/* load: `\tMOV?\t-K(BP), AX\n` with the matching width. */
char loadkey[16];
snprintf(loadkey, sizeof loadkey, "%s\t", r->mov);
if (strstr(line, loadkey) && strstr(line, "(BP), AX"))
saw_load = 1;
}
fclose(f);
if (!saw_store) {
fprintf(stderr,
"row[%s]: expected `%s AX, %d(BX)` in mk body\n",
r->label, r->mov, r->off);
return -1;
}
if (!saw_load) {
fprintf(stderr,
"row[%s]: expected narrow `%s -K(BP), AX` load in mk\n",
r->label, r->mov);
return -1;
}
return 0;
}
/* Negative sentinel: pre-fix bug pattern. cstage emitted
* `MOVQ AX, <r->off>(BX)` for the narrow trailing field — assert
* absence in mk body. */
static int
check_no_movq_at_off(const char *path, const struct row *r)
{
FILE *f = fopen(path, "rb");
if (!f) return -1;
char line[1024];
int in_mk = 0;
char needle[64];
snprintf(needle, sizeof needle, "MOVQ\tAX, %d(BX)\n", r->off);
int bad = 0;
while (fgets(line, sizeof line, f)) {
if (!in_mk) {
if (strstr(line, "TEXT main.mk,")
|| strstr(line, "TEXT\tmain.mk,"))
in_mk = 1;
continue;
}
if (strstr(line, "\tRET\n")) break;
if (strstr(line, needle)) { bad = 1; break; }
}
fclose(f);
if (bad) {
fprintf(stderr,
"row[%s]: unexpected `MOVQ AX, %d(BX)` (pre-#33"
" over-wide pattern) in mk\n", r->label, r->off);
return -1;
}
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, "row[%s]: w6c failed\n", rows[i].label);
fail++; total++; continue;
}
total += 2;
if (check_narrow_mov(cs_path, &rows[i]) != 0) fail++;
if (check_no_movq_at_off(cs_path, &rows[i]) != 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,
"row[%s]: w6c_ww failed\n", rows[i].label);
fail++; total++;
unlink(cs_path);
continue;
}
total += 2;
if (check_narrow_mov(ws_path, &rows[i]) != 0) fail++;
if (check_no_movq_at_off(ws_path, &rows[i]) != 0) fail++;
total++;
char cmd[512];
snprintf(cmd, sizeof cmd, "cmp -s %s %s", cs_path, ws_path);
if (runwait(cmd) != 0) {
fprintf(stderr,
"row[%s]: cstage vs wwstage asm differs\n",
rows[i].label);
fail++;
}
unlink(cs_path); unlink(ws_path);
}
if (fail) {
fprintf(stderr,
"sret_narrow_field: %d/%d fixtures failed\n",
fail, total);
return 1;
}
printf("sret_narrow_field: %d/%d ok\n", total, total);
return 0;
}