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:
7
Makefile
7
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_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 $@ $<
|
||||||
|
|||||||
@@ -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)) {
|
||||||
|
|||||||
@@ -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")) {
|
||||||
|
|||||||
@@ -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")) {
|
||||||
|
|||||||
@@ -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");
|
||||||
|
|||||||
@@ -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")) {
|
||||||
|
|||||||
223
test/wcc/943_subslice_ptresz_run.c
Normal file
223
test/wcc/943_subslice_ptresz_run.c
Normal 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;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user