cgen: sub-slice ptr = base + lo*esz (both stages, #76)

A sub-slice base[lo:hi] advanced its data pointer by lo (element
COUNT) instead of lo*esz (BYTES), so the base pointer was wrong for
any esz>1 element. Pointer arithmetic is membsz-unit per the rt
invariant (ref/hare/rt/ensure.ha:30); esz==1 (u8/str) is unchanged.

Four emission sites, fixed byte-identically across stages (rule 10):
  - value path:  cmd/w6c/cgen.c N_SLICE  <-> cgenexpr.ww cgslice
  - call-arg:    cmd/w6c/cgen.c:4646     <-> cgenutil.ww pushargsrev

Scaling mirrors the cgindex idiom: esz from the type table (rule 13;
cstage bu->sub->size, wwstage elemsizeofc) gated to an N_IDENT base,
uniform IMULQ (no SHL special-case, no immediate form -- w6a is
reg-reg only). The live lo reg is the multiplicand so the one free
GP (DX value / BX arg) holds esz*lo; lo is preserved for len (hi-lo)
and cap (base_cap-lo, #20). The esz==1 path keeps the single ADDQ,
byte-identical to before (#75/#20/str unaffected). Non-ident bases
stay unscaled in both stages (wwstage has no tnode there), tracked
as a #76 residual alongside #74.

New 943_subslice_ptresz_run: table-driven, dual-driver (ww/ww_ww),
esz in {2,4,8} array+slice base, lo>0, let-form + call-arg form;
asserts s[0]==base[lo] & s[1]==base[lo+1]. Fails on every fixture
pre-fix on both stages, passes post-fix. Registered in Makefile
(TESTS + target) so test/run builds and runs it.
This commit is contained in:
2026-05-25 02:26:15 +09:00
parent 8b23ff3517
commit ab9de65b8e
7 changed files with 397 additions and 19 deletions

View File

@@ -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_str_massign_store_cap_run \
$(BIN)/test_slice_store_cap_run \ $(BIN)/test_slice_store_cap_run \
$(BIN)/test_subslice_cap_run \ $(BIN)/test_subslice_cap_run \
$(BIN)/test_subslice_ptresz_run \
$(BIN)/test_str_forrange_loopvar_run \ $(BIN)/test_str_forrange_loopvar_run \
$(BIN)/test_composite_call_arg \ $(BIN)/test_composite_call_arg \
$(BIN)/test_composite_call_arg_run \ $(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) $(LIB)/libwwrt.a | $(BIN)
$(CC) $(CFLAGS) -o $@ $< $(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)/test_composite_call_arg: test/wcc/723_composite_call_arg.c \
$(BIN)/w6c $(BIN)/w6c_ww | $(BIN) $(BIN)/w6c $(BIN)/w6c_ww | $(BIN)
$(CC) $(CFLAGS) -o $@ $< $(CC) $(CFLAGS) -o $@ $<

View File

@@ -4590,6 +4590,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
Type *bt = base ? base->type : NULL; Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ? Type *bu = (bt && bt->kind == TY_NAMED) ?
bt->under : bt; 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 */ /* base addr → push */
if (base->kind == N_IDENT) { if (base->kind == N_IDENT) {
int boff = localfind(locals, base->str); int boff = localfind(locals, base->str);
@@ -4642,8 +4649,16 @@ cgexpr(Cg *c, Node *n, Local *locals)
/* len = hi - lo (DX) */ /* len = hi - lo (DX) */
ins2(c, A_MOVQ, areg(D_BX), areg(D_DX)); ins2(c, A_MOVQ, areg(D_BX), areg(D_DX));
ins2(c, A_SUBQ, areg(D_AX), areg(D_DX)); ins2(c, A_SUBQ, areg(D_AX), areg(D_DX));
/* ptr = base + lo */ /* ptr = base + lo*esz (#76; ensure.ha:30
ins2(c, A_ADDQ, areg(D_AX), areg(D_CX)); * 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 /* push cap, len, ptr (top). cap = base_cap - lo
* (#20); AX=lo, BX free. */ * (#20); AX=lo, BX free. */
if (cg_base_cap(c, base, bu, locals, D_BX)) { if (cg_base_cap(c, base, bu, locals, D_BX)) {
@@ -6361,17 +6376,22 @@ cgexpr(Cg *c, Node *n, Local *locals)
} }
case N_SLICE: { case N_SLICE: {
/* base[lo:hi] as a slice value. Leaves the triple in /* 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. * route to a slice slot, return, or arg with the same ABI.
* cap is the storage remaining to the base's end (#20, * cap is the storage remaining to the base's end (#20,
* Go/Hare-identical), via cg_base_cap. Element scaling on * Go/Hare-identical), via cg_base_cap. ptr advances by BYTES
* the ptr isn't wired (lo*esz is #76), so non-u8 slices * (lo*esz, #76; ref/hare/rt/ensure.ha:30 membsz-unit); esz
* need that follow-up before their ptr is correct. */ * from the type table, mirroring the N_INDEX idiom. */
Node *base = n->lhs; Node *base = n->lhs;
Node *lo = n->rhs; Node *lo = n->rhs;
Node *hi = n->cond; Node *hi = n->cond;
Type *bt = base ? base->type : NULL; Type *bt = base ? base->type : NULL;
Type *bu = (bt && bt->kind == TY_NAMED) ? bt->under : bt; 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) { if (base && base->kind == N_IDENT) {
int boff = localfind(locals, base->str); int boff = localfind(locals, base->str);
int isglobal = (boff == 0) && let_islet(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)); ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
ins1(c, A_POPQ, areg(D_CX)); ins1(c, A_POPQ, areg(D_CX));
ins1(c, A_POPQ, areg(D_AX)); 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)); ins2(c, A_SUBQ, areg(D_CX), areg(D_BX));
/* cap = base_cap - lo (#20); CX=lo, BX=len here. */ /* cap = base_cap - lo (#20); CX=lo, BX=len here. */
if (cg_base_cap(c, base, bu, locals, D_DX)) { if (cg_base_cap(c, base, bu, locals, D_DX)) {

View File

@@ -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 // base address → push
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -10949,7 +10958,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPOPQ\tCX\n"); // base emitline("\tPOPQ\tCX\n"); // base
emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tMOVQ\tBX, DX\n"); // DX = hi
emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len 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. // cap = base_cap - lo (#20); AX=lo, BX free.
if (cgbasecap(c, base, "BX")) { if (cgbasecap(c, base, "BX")) {
emitline("\tSUBQ\tAX, BX\n"); emitline("\tSUBQ\tAX, BX\n");
@@ -14861,11 +14881,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = {
return false; 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, // 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 // return, or arg with the same triple ABI. cap is the storage
// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // 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 = { fn cgslice(c: *cgen, n: *node) void = {
let base: *node = n.lhs; let base: *node = n.lhs;
let lo: *node = n.rhs; 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 // base address
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -14981,7 +15011,17 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\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"); emitline("\tSUBQ\tCX, BX\n");
// cap = base_cap - lo (#20); CX=lo, BX=len here. // cap = base_cap - lo (#20); CX=lo, BX=len here.
if (cgbasecap(c, base, "DX")) { if (cgbasecap(c, base, "DX")) {

View File

@@ -1022,11 +1022,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = {
return false; 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, // 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 // return, or arg with the same triple ABI. cap is the storage
// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // 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 = { fn cgslice(c: *cgen, n: *node) void = {
let base: *node = n.lhs; let base: *node = n.lhs;
let lo: *node = n.rhs; 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 // base address
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -1142,7 +1152,17 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\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"); emitline("\tSUBQ\tCX, BX\n");
// cap = base_cap - lo (#20); CX=lo, BX=len here. // cap = base_cap - lo (#20); CX=lo, BX=len here.
if (cgbasecap(c, base, "DX")) { if (cgbasecap(c, base, "DX")) {

View File

@@ -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 // base address → push
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -388,7 +397,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPOPQ\tCX\n"); // base emitline("\tPOPQ\tCX\n"); // base
emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tMOVQ\tBX, DX\n"); // DX = hi
emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len 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. // cap = base_cap - lo (#20); AX=lo, BX free.
if (cgbasecap(c, base, "BX")) { if (cgbasecap(c, base, "BX")) {
emitline("\tSUBQ\tAX, BX\n"); emitline("\tSUBQ\tAX, BX\n");

View File

@@ -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 // base address → push
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -10949,7 +10958,18 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
emitline("\tPOPQ\tCX\n"); // base emitline("\tPOPQ\tCX\n"); // base
emitline("\tMOVQ\tBX, DX\n"); // DX = hi emitline("\tMOVQ\tBX, DX\n"); // DX = hi
emitline("\tSUBQ\tAX, DX\n"); // DX = hi - lo = len 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. // cap = base_cap - lo (#20); AX=lo, BX free.
if (cgbasecap(c, base, "BX")) { if (cgbasecap(c, base, "BX")) {
emitline("\tSUBQ\tAX, BX\n"); emitline("\tSUBQ\tAX, BX\n");
@@ -14861,11 +14881,12 @@ fn cgbasecap(c: *cgen, base: *node, dst: str) bool = {
return false; 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, // 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 // return, or arg with the same triple ABI. cap is the storage
// remaining to the base's end (#20, Go/Hare-identical) via cgbasecap. // 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 = { fn cgslice(c: *cgen, n: *node) void = {
let base: *node = n.lhs; let base: *node = n.lhs;
let lo: *node = n.rhs; 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 // base address
if (baselocal != nil) { if (baselocal != nil) {
let tn: *node = baselocal.tnode; let tn: *node = baselocal.tnode;
@@ -14981,7 +15011,17 @@ fn cgslice(c: *cgen, n: *node) void = {
emitline("\tMOVQ\tAX, BX\n"); emitline("\tMOVQ\tAX, BX\n");
emitline("\tPOPQ\tCX\n"); emitline("\tPOPQ\tCX\n");
emitline("\tPOPQ\tAX\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"); emitline("\tSUBQ\tCX, BX\n");
// cap = base_cap - lo (#20); CX=lo, BX=len here. // cap = base_cap - lo (#20); CX=lo, BX=len here.
if (cgbasecap(c, base, "DX")) { if (cgbasecap(c, base, "DX")) {

View File

@@ -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 <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[] = {
/* 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;
}