diff --git a/Makefile b/Makefile index 965227e1..03dafc89 100644 --- a/Makefile +++ b/Makefile @@ -264,6 +264,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \ $(BIN)/test_str_massign_store_cap_run \ $(BIN)/test_slice_store_cap_run \ $(BIN)/test_subslice_cap_run \ + $(BIN)/test_subslice_ptresz_run \ $(BIN)/test_str_forrange_loopvar_run \ $(BIN)/test_composite_call_arg \ $(BIN)/test_composite_call_arg_run \ @@ -697,6 +698,12 @@ $(BIN)/test_subslice_cap_run: test/wcc/942_subslice_cap_run.c \ $(LIB)/libwwrt.a | $(BIN) $(CC) $(CFLAGS) -o $@ $< +$(BIN)/test_subslice_ptresz_run: test/wcc/943_subslice_ptresz_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_composite_call_arg: test/wcc/723_composite_call_arg.c \ $(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(CC) $(CFLAGS) -o $@ $< diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index 2346c0b7..8c885324 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -4590,6 +4590,13 @@ cgexpr(Cg *c, Node *n, Local *locals) Type *bt = base ? base->type : NULL; Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + /* N_IDENT-gated: non-ident bases stay esz=1 + * (unscaled), byte-id with wwstage which has no + * tnode there (rule 10) -- #76 residual, non- + * ident cluster #74. */ + int esz = (base && base->kind == N_IDENT + && bu && bu->sub) + ? (int)bu->sub->size : 1; /* base addr → push */ if (base->kind == N_IDENT) { int boff = localfind(locals, base->str); @@ -4642,8 +4649,16 @@ cgexpr(Cg *c, Node *n, Local *locals) /* len = hi - lo (DX) */ ins2(c, A_MOVQ, areg(D_BX), areg(D_DX)); ins2(c, A_SUBQ, areg(D_AX), areg(D_DX)); - /* ptr = base + lo */ - ins2(c, A_ADDQ, areg(D_AX), areg(D_CX)); + /* ptr = base + lo*esz (#76; ensure.ha:30 + * membsz-unit). BX=lo*esz; AX=lo PRESERVED + * for cap. BX (dead hi) reloaded by cap below. */ + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_BX)); + ins2(c, A_IMULQ, areg(D_AX), areg(D_BX)); + ins2(c, A_ADDQ, areg(D_BX), areg(D_CX)); + } else { + ins2(c, A_ADDQ, areg(D_AX), areg(D_CX)); + } /* push cap, len, ptr (top). cap = base_cap - lo * (#20); AX=lo, BX free. */ if (cg_base_cap(c, base, bu, locals, D_BX)) { @@ -6361,17 +6376,22 @@ cgexpr(Cg *c, Node *n, Local *locals) } case N_SLICE: { /* base[lo:hi] as a slice value. Leaves the triple in - * (AX=base+lo, BX=hi-lo, CX=base_cap-lo) so callers can + * (AX=base+lo*esz, BX=hi-lo, CX=base_cap-lo) so callers can * route to a slice slot, return, or arg with the same ABI. * cap is the storage remaining to the base's end (#20, - * Go/Hare-identical), via cg_base_cap. Element scaling on - * the ptr isn't wired (lo*esz is #76), so non-u8 slices - * need that follow-up before their ptr is correct. */ + * Go/Hare-identical), via cg_base_cap. ptr advances by BYTES + * (lo*esz, #76; ref/hare/rt/ensure.ha:30 membsz-unit); esz + * from the type table, mirroring the N_INDEX idiom. */ Node *base = n->lhs; Node *lo = n->rhs; Node *hi = n->cond; Type *bt = base ? base->type : NULL; Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; + /* N_IDENT-gated: non-ident bases stay esz=1 (unscaled), + * byte-id with wwstage which has no tnode there to resolve + * esz (rule 10) -- #76 residual, non-ident cluster #74. */ + int esz = (base && base->kind == N_IDENT && bu && bu->sub) + ? (int)bu->sub->size : 1; if (base && base->kind == N_IDENT) { int boff = localfind(locals, base->str); int isglobal = (boff == 0) && let_islet(base->str); @@ -6415,7 +6435,15 @@ cgexpr(Cg *c, Node *n, Local *locals) ins2(c, A_MOVQ, areg(D_AX), areg(D_BX)); ins1(c, A_POPQ, areg(D_CX)); ins1(c, A_POPQ, areg(D_AX)); - ins2(c, A_ADDQ, areg(D_CX), areg(D_AX)); + /* ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + * DX=lo*esz; CX=lo PRESERVED for len + cap (#20). */ + if (esz > 1) { + ins2(c, A_MOVQ, aimm(esz), areg(D_DX)); + ins2(c, A_IMULQ, areg(D_CX), areg(D_DX)); + ins2(c, A_ADDQ, areg(D_DX), areg(D_AX)); + } else { + ins2(c, A_ADDQ, areg(D_CX), areg(D_AX)); + } ins2(c, A_SUBQ, areg(D_CX), areg(D_BX)); /* cap = base_cap - lo (#20); CX=lo, BX=len here. */ if (cg_base_cap(c, base, bu, locals, D_DX)) { diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 9d8aaca4..35c5a7a0 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -10863,6 +10863,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors + // the cgindex idiom). Non-ident base stays esz=1 -> ptr + // unscaled, matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -10949,7 +10958,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tPOPQ\tCX\n"); // base emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len - emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // BX=lo*esz; AX=lo PRESERVED for cap. BX (dead hi) reloaded + // by cgbasecap below. + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", BX\n"); + emitline("\tIMULQ\tAX, BX\n"); + emitline("\tADDQ\tBX, CX\n"); + } else { + emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + }; // cap = base_cap - lo (#20); AX=lo, BX free. if (cgbasecap(c, base, "BX")) { emitline("\tSUBQ\tAX, BX\n"); @@ -14861,11 +14881,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return false; }; -// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo*esz, // BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, // return, or arg with the same triple ABI. cap is the storage // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. -// Element scaling on the ptr isn't wired (lo*esz is #76). +// ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 +// membsz-unit); esz from the type table, mirroring the cgindex idiom. fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -14886,6 +14907,15 @@ fn cgslice(c: *cgen, n: *node) void = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors the + // cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled, + // matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -14981,7 +15011,17 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); - emitline("\tADDQ\tCX, AX\n"); + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // DX=lo*esz; CX=lo PRESERVED for len + cap (#20). + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tCX, DX\n"); + emitline("\tADDQ\tDX, AX\n"); + } else { + emitline("\tADDQ\tCX, AX\n"); + }; emitline("\tSUBQ\tCX, BX\n"); // cap = base_cap - lo (#20); CX=lo, BX=len here. if (cgbasecap(c, base, "DX")) { diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index cb6419dc..660977dc 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -1022,11 +1022,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return false; }; -// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo*esz, // BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, // return, or arg with the same triple ABI. cap is the storage // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. -// Element scaling on the ptr isn't wired (lo*esz is #76). +// ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 +// membsz-unit); esz from the type table, mirroring the cgindex idiom. fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -1047,6 +1048,15 @@ fn cgslice(c: *cgen, n: *node) void = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors the + // cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled, + // matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -1142,7 +1152,17 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); - emitline("\tADDQ\tCX, AX\n"); + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // DX=lo*esz; CX=lo PRESERVED for len + cap (#20). + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tCX, DX\n"); + emitline("\tADDQ\tDX, AX\n"); + } else { + emitline("\tADDQ\tCX, AX\n"); + }; emitline("\tSUBQ\tCX, BX\n"); // cap = base_cap - lo (#20); CX=lo, BX=len here. if (cgbasecap(c, base, "DX")) { diff --git a/selfhost/cmd/wcc/cgenutil.ww b/selfhost/cmd/wcc/cgenutil.ww index 2e67940a..38f4762b 100644 --- a/selfhost/cmd/wcc/cgenutil.ww +++ b/selfhost/cmd/wcc/cgenutil.ww @@ -302,6 +302,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors + // the cgindex idiom). Non-ident base stays esz=1 -> ptr + // unscaled, matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -388,7 +397,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tPOPQ\tCX\n"); // base emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len - emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // BX=lo*esz; AX=lo PRESERVED for cap. BX (dead hi) reloaded + // by cgbasecap below. + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", BX\n"); + emitline("\tIMULQ\tAX, BX\n"); + emitline("\tADDQ\tBX, CX\n"); + } else { + emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + }; // cap = base_cap - lo (#20); AX=lo, BX free. if (cgbasecap(c, base, "BX")) { emitline("\tSUBQ\tAX, BX\n"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9da0a7aa..0b53811e 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -10863,6 +10863,15 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors + // the cgindex idiom). Non-ident base stays esz=1 -> ptr + // unscaled, matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address → push if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -10949,7 +10958,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = { emitline("\tPOPQ\tCX\n"); // base emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len - emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // BX=lo*esz; AX=lo PRESERVED for cap. BX (dead hi) reloaded + // by cgbasecap below. + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", BX\n"); + emitline("\tIMULQ\tAX, BX\n"); + emitline("\tADDQ\tBX, CX\n"); + } else { + emitline("\tADDQ\tAX, CX\n"); // CX = base + lo = ptr + }; // cap = base_cap - lo (#20); AX=lo, BX free. if (cgbasecap(c, base, "BX")) { emitline("\tSUBQ\tAX, BX\n"); @@ -14861,11 +14881,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = { return false; }; -// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo, +// cgslice — `base[lo:hi]` as a slice value. Leaves (AX=base+lo*esz, // BX=hi-lo, CX=base_cap-lo) so callers can route to a slice slot, // return, or arg with the same triple ABI. cap is the storage // remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. -// Element scaling on the ptr isn't wired (lo*esz is #76). +// ptr advances by BYTES (lo*esz, #76; ref/hare/rt/ensure.ha:30 +// membsz-unit); esz from the type table, mirroring the cgindex idiom. fn cgslice(c: *cgen, n: *node) void = { let base: *node = n.lhs; let lo: *node = n.rhs; @@ -14886,6 +14907,15 @@ fn cgslice(c: *cgen, n: *node) void = { }; }; }; + // esz from the type table for an N_IDENT base (#76; mirrors the + // cgindex idiom). Non-ident base stays esz=1 -> ptr unscaled, + // matching cstage's base->kind==N_IDENT gate. + let esz: i32 = 1; + if (baselocal != nil) { + esz = elemsizeofc(c, baselocal.tnode); + } else { if (globaltn != nil) { + esz = elemsizeofc(c, globaltn); + };}; // base address if (baselocal != nil) { let tn: *node = baselocal.tnode; @@ -14981,7 +15011,17 @@ fn cgslice(c: *cgen, n: *node) void = { emitline("\tMOVQ\tAX, BX\n"); emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tAX\n"); - emitline("\tADDQ\tCX, AX\n"); + // ptr = base + lo*esz (#76; ensure.ha:30 membsz-unit). + // DX=lo*esz; CX=lo PRESERVED for len + cap (#20). + if (esz > 1) { + emitline("\tMOVQ\t$"); + emitint(esz: i64); + emitline(", DX\n"); + emitline("\tIMULQ\tCX, DX\n"); + emitline("\tADDQ\tDX, AX\n"); + } else { + emitline("\tADDQ\tCX, AX\n"); + }; emitline("\tSUBQ\tCX, BX\n"); // cap = base_cap - lo (#20); CX=lo, BX=len here. if (cgbasecap(c, base, "DX")) { diff --git a/test/wcc/943_subslice_ptresz_run.c b/test/wcc/943_subslice_ptresz_run.c new file mode 100644 index 00000000..cf69f013 --- /dev/null +++ b/test/wcc/943_subslice_ptresz_run.c @@ -0,0 +1,223 @@ +/* + * 943_subslice_ptresz_run — runtime coverage for project #76: a sub-slice + * `base[lo:hi]` must advance its DATA pointer by lo*esz (BYTES), not by lo + * (element COUNT). The rt invariant is membsz-unit pointer arithmetic + * (ref/hare/rt/ensure.ha:30). For esz==1 (u8/str) lo*1 == lo, so those + * paths are unaffected; the bug only bites esz>1 elements. + * + * Before the fix BOTH stages emitted ptr = base + lo (unscaled), so the + * first element of the sub-slice was read at byte offset `lo` into the + * base storage — garbage straddling base[0]/base[1] for any esz>1. Each + * row picks values where the unscaled read cannot alias the scaled one, + * so a stale `+lo` stage is observably wrong (returns garbage, not the + * expected element). + * + * Sites exercised (all four; cstage cmd/w6c/cgen.c N_SLICE value path + + * N_SLICE call-arg fast-path; wwstage cgenexpr.ww cgslice + cgenutil.ww + * pushargsrev twin) — esz scaled via the type table, mirroring the + * cgindex idiom (rule 13): + * A esz=4 array base, let-form (value path): `a[2:5]` over [8]i32, + * a[i]=1000+i -> s[0]==1002, s[1]==1003 (pre-fix reads byte off 2). + * B esz=2 array base, let-form: `a[3:6]` over [8]i16 -> s[0]==103. + * C esz=8 array base, let-form: `a[1:4]` over [8]i64 -> s[0]==5001. + * D esz=4 slice base, let-form: p{ptr=&a,len=6,cap=8}; `p[2:5]` -> + * s[0]==1002 (base ptr carried from the header, then +lo*esz). + * E esz=4 call-arg (the pushargsrev/cgen.c:4646 twin): pass `a[3:6]` + * to a fn reading s[0]/s[1] -> 1003 / 1004. + * F esz=8 call-arg: pass `a[2:5]` over [8]i64 -> s[0]==5002, s[1]==5003. + * + * NNN < 950, self-contained (/tmp, no imports), so rule-14's selfhost- + * sibling race does not apply (mirrors the 928/932/941/942 precedent). + */ +#include +#include +#include +#include +#include +#include + +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[] = { + /* A — esz=4 array base, let-form (value path). s[0]=a[2]=1002. */ + { "subslice_esz4_array_let", + "export fn main() i32 = {\n" + " let a: [8]i32;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = 1000 + i; i += 1; };\n" + " let s: []i32 = a[2:5];\n" + " if (s.len: i32 != 3) { return 1; };\n" + " if (s[0] != 1002) { return 2; };\n" + " if (s[1] != 1003) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* B — esz=2 array base, let-form. s[0]=a[3]=103. */ + { "subslice_esz2_array_let", + "export fn main() i32 = {\n" + " let a: [8]i16;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (100 + i): i16; i += 1; };\n" + " let s: []i16 = a[3:6];\n" + " if (s.len: i32 != 3) { return 1; };\n" + " if (s[0]: i32 != 103) { return 2; };\n" + " if (s[1]: i32 != 104) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* C — esz=8 array base, let-form. s[0]=a[1]=5001. */ + { "subslice_esz8_array_let", + "export fn main() i32 = {\n" + " let a: [8]i64;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n" + " let s: []i64 = a[1:4];\n" + " if (s.len: i32 != 3) { return 1; };\n" + " if (s[0]: i32 != 5001) { return 2; };\n" + " if (s[1]: i32 != 5002) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* D — esz=4 slice base, let-form. base ptr from header + lo*esz. + * s[0]=a[2]=1002. */ + { "subslice_esz4_slice_let", + "export fn main() i32 = {\n" + " let a: [8]i32;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = 1000 + i; i += 1; };\n" + " let p: []i32; p.ptr = &a[0]; p.len = 6; p.cap = 8;\n" + " let s: []i32 = p[2:5];\n" + " if (s.len: i32 != 3) { return 1; };\n" + " if (s[0] != 1002) { return 2; };\n" + " if (s[1] != 1003) { return 3; };\n" + " return 0;\n" + "};\n", + 0 }, + /* E — esz=4 call-arg (pushargsrev / cgen.c:4646 twin). The fn reads + * s[0]/s[1] of `a[3:6]` -> 1003 / 1004. */ + { "subslice_esz4_call_arg", + "fn e0(s: []i32) i32 = { return s[0]; };\n" + "fn e1(s: []i32) i32 = { return s[1]; };\n" + "export fn main() i32 = {\n" + " let a: [8]i32;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = 1000 + i; i += 1; };\n" + " if (e0(a[3:6]) != 1003) { return 1; };\n" + " if (e1(a[3:6]) != 1004) { return 2; };\n" + " return 0;\n" + "};\n", + 0 }, + /* F — esz=8 call-arg. The fns read s[0]/s[1] of `a[2:5]` -> 5002 / 5003. */ + { "subslice_esz8_call_arg", + "fn f0(s: []i64) i64 = { return s[0]; };\n" + "fn f1(s: []i64) i64 = { return s[1]; };\n" + "export fn main() i32 = {\n" + " let a: [8]i64;\n" + " let i: i32 = 0;\n" + " for (i < 8) { a[i] = (5000 + i): i64; i += 1; };\n" + " if (f0(a[2:5]): i32 != 5002) { return 1; };\n" + " if (f1(a[2:5]): i32 != 5003) { return 2; };\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/subsliceptr_%d_%d.ww", getpid(), i); + snprintf(tmpdir, sizeof tmpdir, "/tmp/subsliceptr_%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, + "subslice_ptresz_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, + "subslice_ptresz_run[%s][%s]: exit=%d want=%d\n", + drivers[d].name, rows[i].label, + got, rows[i].want); + fail++; + } + } + } + + if (fail) { + fprintf(stderr, "subslice_ptresz_run: %d/%d fixtures failed\n", + fail, total); + return 1; + } + printf("subslice_ptresz_run: %d/%d ok\n", total, total); + return 0; +}