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

@@ -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")) {

View File

@@ -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")) {

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
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");

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
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")) {