cstage+test: walk fields for sret callee struct-copy width (#33)
Callee N_IDENT word-copy loop was driven off slot-padded rt->size;
trailing narrow field (e.g. bool@32 in 33B/40B struct) widened to
MOVQ at the loop tail, diverging from wwstage's natural-size MOVB.
Class A cgen divergence. 9th unmask of session 5, corpus-coverage-
blind on the cstage side — no in-tree lib struct had a narrow
(bool/u8/i8/i16) trailing field until lib/strings.iterator landed
`reverse: bool` per Hare's ref/hare/strings/iter.ha:8.
Pre-fix: cstage cgreturn's sret arm (cgen.c) used `int sz =
(int)rt->size` for its chained `while (k+8<=sz)` MOVQ-MOVL-MOVW-MOVB
copy loop. rt->size is slot-padded (8-rounded for downstream
frame alloc), e.g. 40B for {i32, []u8, bool}. Loop emitted MOVQ
at offset 32 covering the 1-byte bool tail plus 7 bytes of
padding into the caller's sret slot — clobbering the next 7 bytes
of caller frame on read-side. Wwstage's mirror loop drives off
sretretsize → structnaturalsize → max(foff+fsz) = 33B, so it
correctly stops at offset 32 and emits MOVB.
Polarity catalog: cstage OVER-WIDE — slot-padded size driving the
field-copy width. Convergence cstage → wwstage's structnaturalsize
discipline (rule 10 inverse: leaner-correct side wins).
Fix: compute natural size locally in the N_IDENT/N_STRUCTLIT sret
arm via walk over `rt->fields` (max foff+fsz), mirroring wwstage's
`structnaturalsize` (cgenutil.ww:1377). Frame allocation and
`cg_sret_retsize` (used for the caller-side @sretscr slot)
intentionally keep using rt->size — caller scratch sizing is
separate from callee per-field copy width.
Surfaced by lib/strings commit-2 (#30) iterator pre-flight when
the Hare-faithful `reverse: bool` field tripped the cstage-only
mis-width on 993/995 byte-id (strconv → strings → wwdump_ww +
w6c_ww). lib/strings c2 was stashed to land cleanly after this fix.
Note (out-of-scope): the chained MOV loop has no MOVW arm in
either stage, so a trailing i16 emits 2× MOVB at consecutive
offsets. Worth a future cleanup; both stages agree today.
Tests:
- 730_sret_narrow_field pins narrow-MOV store + load width and
no-MOVQ@trailing-offset assertions for bool / u8 / i16 / i32
trailing fields in a 33B-natural struct. 4 rows × 5 sentinels
= 20 fixtures. cmp -s cstage vs wwstage byte-id per row.
- 930_sret_narrow_field_run runtime-pins bool true/false, u8
high-bit, i16 negative, i32 negative, mixed (bool+i32+i64 after
slice) — 6 scenarios × 2 stages = 12 rows.
104/104 ok. 995_self_rebuild stays green (ww2==ww3==ww4 byte-id).
This commit is contained in:
12
Makefile
12
Makefile
@@ -250,6 +250,8 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_tagged_call_arg_run \
|
$(BIN)/test_tagged_call_arg_run \
|
||||||
$(BIN)/test_sret_struct_return \
|
$(BIN)/test_sret_struct_return \
|
||||||
$(BIN)/test_sret_struct_return_run \
|
$(BIN)/test_sret_struct_return_run \
|
||||||
|
$(BIN)/test_sret_narrow_field \
|
||||||
|
$(BIN)/test_sret_narrow_field_run \
|
||||||
$(BIN)/test_match_slice_variant \
|
$(BIN)/test_match_slice_variant \
|
||||||
$(BIN)/test_match_slice_variant_run \
|
$(BIN)/test_match_slice_variant_run \
|
||||||
$(BIN)/test_composite_call_arg \
|
$(BIN)/test_composite_call_arg \
|
||||||
@@ -556,6 +558,16 @@ $(BIN)/test_sret_struct_return_run: test/wcc/925_sret_struct_return_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_sret_narrow_field: test/wcc/730_sret_narrow_field.c \
|
||||||
|
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_sret_narrow_field_run: test/wcc/930_sret_narrow_field_run.c \
|
||||||
|
$(BIN)/ww $(BIN)/w6c $(BIN)/w6a $(BIN)/w6l \
|
||||||
|
$(BIN)/ww_ww $(BIN)/w6c_ww $(BIN)/w6a_ww $(BIN)/w6l_ww \
|
||||||
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
$(BIN)/test_match_slice_variant: test/wcc/722_match_slice_variant.c \
|
$(BIN)/test_match_slice_variant: test/wcc/722_match_slice_variant.c \
|
||||||
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|||||||
@@ -6611,7 +6611,20 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
&& (int)rt->size > 24
|
&& (int)rt->size > 24
|
||||||
&& (n->lhs->kind == N_IDENT
|
&& (n->lhs->kind == N_IDENT
|
||||||
|| n->lhs->kind == N_STRUCTLIT)) {
|
|| n->lhs->kind == N_STRUCTLIT)) {
|
||||||
int sz = (int)rt->size;
|
/* Natural size = max(foff + fsz) over declared
|
||||||
|
* fields; mirrors selfhost cgenutil.ww
|
||||||
|
* structnaturalsize / sretretsize. Pre-fix this
|
||||||
|
* used the slot-padded rt->size, so a trailing
|
||||||
|
* narrow field (e.g. bool@32 in a 33B struct
|
||||||
|
* padded to 40B) widened to an 8B MOVQ at the
|
||||||
|
* loop tail — diverged from wwstage's MOVB
|
||||||
|
* tail. Task #33, Class A. */
|
||||||
|
int sz = 0;
|
||||||
|
for (Tfield *fl = rt->fields; fl; fl = fl->next) {
|
||||||
|
int end = (int)fl->offset + (int)(fl->type
|
||||||
|
? fl->type->size : 8);
|
||||||
|
if (end > sz) sz = end;
|
||||||
|
}
|
||||||
if (n->lhs->kind == N_STRUCTLIT) {
|
if (n->lhs->kind == N_STRUCTLIT) {
|
||||||
/* Delegate to the shared *-relative
|
/* Delegate to the shared *-relative
|
||||||
* fill helper. Same store sequence the
|
* fill helper. Same store sequence the
|
||||||
|
|||||||
276
test/wcc/730_sret_narrow_field.c
Normal file
276
test/wcc/730_sret_narrow_field.c
Normal file
@@ -0,0 +1,276 @@
|
|||||||
|
/*
|
||||||
|
* 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>
|
||||||
|
|
||||||
|
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;
|
||||||
|
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 mk,")
|
||||||
|
|| strstr(line, "TEXT\tmk,"))
|
||||||
|
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 mk,")
|
||||||
|
|| strstr(line, "TEXT\tmk,"))
|
||||||
|
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;
|
||||||
|
}
|
||||||
230
test/wcc/930_sret_narrow_field_run.c
Normal file
230
test/wcc/930_sret_narrow_field_run.c
Normal file
@@ -0,0 +1,230 @@
|
|||||||
|
/*
|
||||||
|
* 930_sret_narrow_field_run — Class B semantic round-trip for the
|
||||||
|
* sret callee's narrow trailing-field copy (task #33). End-to-end
|
||||||
|
* pins that values cross the >24B struct-return boundary intact
|
||||||
|
* when the struct ends in a narrow primitive (bool / u8 / i16 /
|
||||||
|
* i32), including a mixed-width struct that interleaves several
|
||||||
|
* narrow fields after the 24B slice payload.
|
||||||
|
*
|
||||||
|
* Pre-fix cstage's N_IDENT word-copy tail used slot-padded
|
||||||
|
* `rt->size` and emitted MOVQ for the narrow trailing field —
|
||||||
|
* the narrow source slot was BP-relative low and bordered the
|
||||||
|
* @sretarg save (8B), so the 8-byte read at offset 32 swept in
|
||||||
|
* the saved RDI byte and either wrote garbage past the bool /
|
||||||
|
* misaligned the next stack slot at the callee's frame edge.
|
||||||
|
*
|
||||||
|
* Class A asm-presence is pinned at 730; this file catches
|
||||||
|
* shared miscompiles that asm byte-id can't see.
|
||||||
|
*/
|
||||||
|
#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; };
|
||||||
|
|
||||||
|
static const struct row rows[] = {
|
||||||
|
/* Trailing bool — the original surfacing case (lib/strings
|
||||||
|
* iterator shape). Pre-fix the bool round-tripped to a
|
||||||
|
* neighbour byte (the @sretarg low byte at -8(BP)), which on
|
||||||
|
* the local stack frame was 0x01 (the saved RDI pointer low
|
||||||
|
* byte). Test sets r=true and confirms the value survives. */
|
||||||
|
{ "bool_true",
|
||||||
|
"type t = struct { a: i32, s: []u8, r: bool };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.a = 7; v.r = true;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (x.a != 7) { return 1; };\n"
|
||||||
|
" if (!x.r) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
{ "bool_false",
|
||||||
|
"type t = struct { a: i32, s: []u8, r: bool };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.a = 9; v.r = false;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (x.a != 9) { return 1; };\n"
|
||||||
|
" if (x.r) { return 2; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* Trailing u8 with a high bit set. Pre-fix MOVQ-load at the
|
||||||
|
* source slot's offset would clobber the high 7 bytes of the
|
||||||
|
* read into AX with whatever lived above the byte slot. */
|
||||||
|
{ "u8_high_bit",
|
||||||
|
"type t = struct { a: i32, s: []u8, r: u8 };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.a = 0; v.r = 0xFFu8;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (x.r != 0xFFu8) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* Trailing i16 with a negative bit pattern: the MOVW path
|
||||||
|
* is exercised, and the receive site's sign extension must
|
||||||
|
* read only 2 bytes. */
|
||||||
|
{ "i16_negative",
|
||||||
|
"type t = struct { a: i32, s: []u8, r: i16 };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.a = 0; v.r = -123i16;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (x.r != -123i16) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* Trailing i32 negative: exercises the MOVL-tail path of the
|
||||||
|
* fixed loop (k+4<=sz arm; pre-fix the k+8<=40 arm absorbed
|
||||||
|
* this offset too). */
|
||||||
|
{ "i32_negative",
|
||||||
|
"type t = struct { a: i32, s: []u8, r: i32 };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.a = 0; v.r = -424242;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (x.r != -424242) { return 1; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* Mixed-width narrows interleaved after the 24B slice: bool +
|
||||||
|
* i32 + i64 mix into one struct. All three trailing fields
|
||||||
|
* survive intact only if each emits its declared width per
|
||||||
|
* field's natural offset/size. */
|
||||||
|
{ "mixed_narrow_after_slice",
|
||||||
|
"type t = struct { s: []u8, r: bool, n: i32, k: i64 };\n"
|
||||||
|
"fn mk() t = {\n"
|
||||||
|
" let v: t; let z: []u8;\n"
|
||||||
|
" v.s = z; v.r = true; v.n = 1234567; v.k = 9876543210i64;\n"
|
||||||
|
" return v;\n"
|
||||||
|
"};\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let x: t = mk();\n"
|
||||||
|
" if (!x.r) { return 1; };\n"
|
||||||
|
" if (x.n != 1234567) { return 2; };\n"
|
||||||
|
" if (x.k != 9876543210i64) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
static int
|
||||||
|
run_driver(const char *driver, const struct row *r, int i)
|
||||||
|
{
|
||||||
|
char src[96], tmpdir[96], cmd[1024];
|
||||||
|
snprintf(src, sizeof src, "/tmp/sret_nr_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/sret_nr_%d_d_%d", getpid(), i);
|
||||||
|
|
||||||
|
FILE *f = fopen(src, "wb");
|
||||||
|
if (!f) return -1;
|
||||||
|
fputs(r->src, f);
|
||||||
|
fclose(f);
|
||||||
|
|
||||||
|
mkdir(tmpdir, 0755);
|
||||||
|
snprintf(cmd, sizeof cmd, "cd %s && %s build %s",
|
||||||
|
tmpdir, driver, src);
|
||||||
|
if (runwait(cmd) != 0) {
|
||||||
|
fprintf(stderr, "row[%s]: build via %s failed\n",
|
||||||
|
r->label, driver);
|
||||||
|
unlink(src); rmdir(tmpdir);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
const char *base = strrchr(src, '/');
|
||||||
|
base = base ? base + 1 : src;
|
||||||
|
char outbin[160];
|
||||||
|
snprintf(outbin, sizeof outbin, "%s/%s", tmpdir, base);
|
||||||
|
char *dot = strrchr(outbin, '.');
|
||||||
|
if (dot && strcmp(dot, ".ww") == 0) *dot = '\0';
|
||||||
|
int got = runwait(outbin);
|
||||||
|
|
||||||
|
unlink(src); unlink(outbin); rmdir(tmpdir);
|
||||||
|
return got;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 cdrv[640];
|
||||||
|
snprintf(cdrv, sizeof cdrv, "%s/ww", bin);
|
||||||
|
char wdrv[640];
|
||||||
|
snprintf(wdrv, sizeof wdrv, "%s/ww_ww", bin);
|
||||||
|
|
||||||
|
struct { const char *name; const char *path; int gated_on_existence; }
|
||||||
|
drivers[] = {
|
||||||
|
{ "cstage", cdrv, 0 },
|
||||||
|
{ "wwstage", wdrv, 1 },
|
||||||
|
{ NULL, NULL, 0 },
|
||||||
|
};
|
||||||
|
|
||||||
|
int n = (int)(sizeof rows / sizeof rows[0]);
|
||||||
|
int total = 0, fail = 0;
|
||||||
|
for (int d = 0; drivers[d].name; d++) {
|
||||||
|
if (drivers[d].gated_on_existence
|
||||||
|
&& access(drivers[d].path, X_OK) != 0) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sret_narrow_field_run: skip %s (no %s)\n",
|
||||||
|
drivers[d].name, drivers[d].path);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < n; i++) {
|
||||||
|
int got = run_driver(drivers[d].path, &rows[i], i);
|
||||||
|
total++;
|
||||||
|
if (got != rows[i].want) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sret_narrow_field_run[%s][%s]: exit=%d want=%d\n",
|
||||||
|
drivers[d].name, rows[i].label,
|
||||||
|
got, rows[i].want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr,
|
||||||
|
"sret_narrow_field_run: %d/%d fixtures failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("sret_narrow_field_run: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user