cgen: slice-elem store/read -> 3-word via kind-OR (both stages)
The G-cluster gave str its 3-word {ptr,len,cap} store/read at indexed/field/chained sites, but each arm was gated on str only; the slice arm fell through to the 1-word fldstoreop default, dropping len+cap. A []T value stored through arr[i]=, arr[i].f=, *struct.f=, or value-spine o.i.f= (and read back via arr[i] / arr[i].f) silently lost length and capacity.
Widen all six arms (4 stores + 2 read mirrors) with a kind-OR (TY_STR||TY_SLICE / typeisstr||typeisslice), never a size test: str and slice are both 24B, so a width gate would fire on both and mask the missing slice arm. The str kind stays distinct and nominal -- the arm is widened, the kinds are not collapsed. cstage and wwstage mirrored.
Gate-blind class: store and read were both short, so byte-identity and cstage==wwstage stayed green on self-consistent garbage; only a runtime len/cap round-trip exposes it (test 941, table-driven, 4 shapes x 2 stages, fail-before/pass-after on both ww and ww_ww).
Deref store (*p=) and tuple-elem store (N_MLET/N_MASSIGN, distinct DX,CX,R8 return-ABI) are the same bug class but separate folds.
This commit is contained in:
7
Makefile
7
Makefile
@@ -262,6 +262,7 @@ TESTS = $(BIN)/test_smoke $(BIN)/test_lex $(BIN)/test_parse $(BIN)/test_check \
|
|||||||
$(BIN)/test_str_arrfield_store_cap_run \
|
$(BIN)/test_str_arrfield_store_cap_run \
|
||||||
$(BIN)/test_str_chainfield_store_cap_run \
|
$(BIN)/test_str_chainfield_store_cap_run \
|
||||||
$(BIN)/test_str_massign_store_cap_run \
|
$(BIN)/test_str_massign_store_cap_run \
|
||||||
|
$(BIN)/test_slice_store_cap_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 \
|
||||||
@@ -683,6 +684,12 @@ $(BIN)/test_str_forrange_loopvar_run: test/wcc/940_str_forrange_loopvar_run.c \
|
|||||||
$(LIB)/libwwrt.a | $(BIN)
|
$(LIB)/libwwrt.a | $(BIN)
|
||||||
$(CC) $(CFLAGS) -o $@ $<
|
$(CC) $(CFLAGS) -o $@ $<
|
||||||
|
|
||||||
|
$(BIN)/test_slice_store_cap_run: test/wcc/941_slice_store_cap_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 $@ $<
|
||||||
|
|||||||
@@ -2887,8 +2887,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (n->op == TK_ASSIGN
|
if (n->op == TK_ASSIGN
|
||||||
&& fu && fu->kind == TY_STR) {
|
&& fu && (fu->kind == TY_STR
|
||||||
/* str IS []u8: rhs leaves
|
|| fu->kind == TY_SLICE)) {
|
||||||
|
/* str/slice: rhs leaves
|
||||||
* AX=ptr, BX=len, CX=cap
|
* AX=ptr, BX=len, CX=cap
|
||||||
* (#1/Phase 3). Spill all
|
* (#1/Phase 3). Spill all
|
||||||
* three across the index/
|
* three across the index/
|
||||||
@@ -3111,8 +3112,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
amem(D_BX, foff));
|
amem(D_BX, foff));
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (fu && fu->kind == TY_STR) {
|
if (fu && (fu->kind == TY_STR
|
||||||
/* str IS []u8: rhs leaves AX=ptr,
|
|| fu->kind == TY_SLICE)) {
|
||||||
|
/* str/slice: rhs leaves AX=ptr,
|
||||||
* BX=len, CX=cap (#1/Phase 3). Spill
|
* BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
* all three across the base-expr eval
|
* all three across the base-expr eval
|
||||||
* (it may clobber any reg), stage the
|
* (it may clobber any reg), stage the
|
||||||
@@ -3310,8 +3312,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
int fsz = (int)(leaf_type
|
int fsz = (int)(leaf_type
|
||||||
? leaf_type->size : 8);
|
? leaf_type->size : 8);
|
||||||
int store_op = fldstoreop(leaf_type, fsz);
|
int store_op = fldstoreop(leaf_type, fsz);
|
||||||
if (fu && fu->kind == TY_STR) {
|
if (fu && (fu->kind == TY_STR
|
||||||
/* str IS []u8: store ptr/len/cap. cgexpr
|
|| fu->kind == TY_SLICE)) {
|
||||||
|
/* str/slice: store ptr/len/cap. cgexpr
|
||||||
* leaves CX=cap, so the via_cx base goes in
|
* leaves CX=cap, so the via_cx base goes in
|
||||||
* DX (not CX) to avoid clobbering it — same
|
* DX (not CX) to avoid clobbering it — same
|
||||||
* as the single-dot str field store
|
* as the single-dot str field store
|
||||||
@@ -3578,6 +3581,7 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
Type *eff = idx_eff(bt);
|
Type *eff = idx_eff(bt);
|
||||||
int esz = (eff && eff->sub) ? (int)eff->sub->size : 1;
|
int esz = (eff && eff->sub) ? (int)eff->sub->size : 1;
|
||||||
int elem_is_str = eff && eff->sub && type_isstr(eff->sub);
|
int elem_is_str = eff && eff->sub && type_isstr(eff->sub);
|
||||||
|
int elem_is_slice = eff && eff->sub && type_isslice(eff->sub);
|
||||||
Type *esub = eff ? eff->sub : NULL;
|
Type *esub = eff ? eff->sub : NULL;
|
||||||
Type *esubu = (esub && esub->kind == TY_NAMED)
|
Type *esubu = (esub && esub->kind == TY_NAMED)
|
||||||
? esub->under : esub;
|
? esub->under : esub;
|
||||||
@@ -3645,9 +3649,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
}
|
}
|
||||||
if (is_arr || is_sl || is_ptr) {
|
if (is_arr || is_sl || is_ptr) {
|
||||||
cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len,CX=cap if str) */
|
cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len,CX=cap if str) */
|
||||||
/* str IS []u8: stash cap+len so all three store
|
/* str/slice: stash cap+len so all three store
|
||||||
* (#1/Phase 3). */
|
* (#1/Phase 3). */
|
||||||
if (elem_is_str) {
|
if (elem_is_str || elem_is_slice) {
|
||||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||||
}
|
}
|
||||||
@@ -3689,8 +3693,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins1(c, A_POPQ, areg(D_AX)); /* scaled idx */
|
ins1(c, A_POPQ, areg(D_AX)); /* scaled idx */
|
||||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||||
ins1(c, A_POPQ, areg(D_AX)); /* value (ptr if str) */
|
ins1(c, A_POPQ, areg(D_AX)); /* value (ptr if str) */
|
||||||
if (elem_is_str) {
|
if (elem_is_str || elem_is_slice) {
|
||||||
/* str IS []u8: store ptr/len/cap (#1/Phase 3). */
|
/* str/slice: store ptr/len/cap (#1/Phase 3, #7). */
|
||||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
||||||
ins1(c, A_POPQ, areg(D_CX));
|
ins1(c, A_POPQ, areg(D_CX));
|
||||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
|
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
|
||||||
@@ -6141,8 +6145,9 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
Type *ft = f->type;
|
Type *ft = f->type;
|
||||||
Type *fu = (ft && ft->kind == TY_NAMED)
|
Type *fu = (ft && ft->kind == TY_NAMED)
|
||||||
? ft->under : ft;
|
? ft->under : ft;
|
||||||
if (fu && fu->kind == TY_STR) {
|
if (fu && (fu->kind == TY_STR
|
||||||
/* str IS the 3-word {ptr,len,cap}
|
|| fu->kind == TY_SLICE)) {
|
||||||
|
/* str/slice: the 3-word {ptr,len,cap}
|
||||||
* slice header (#1). AX holds the
|
* slice header (#1). AX holds the
|
||||||
* element base, so load .ptr (which
|
* element base, so load .ptr (which
|
||||||
* targets AX) LAST. Matches the
|
* targets AX) LAST. Matches the
|
||||||
@@ -6232,12 +6237,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_BX));
|
||||||
}
|
}
|
||||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||||
/* str element: load the full (ptr, len, cap) header
|
/* str/slice element: load the full (ptr, len, cap) header
|
||||||
* into (AX, BX, CX) — str is 24B since #1, so the cap
|
* into (AX, BX, CX) — both are 24B since #1, so the cap
|
||||||
* word must survive. Kind-gate on type_isstr, never
|
* word must survive. Kind-gate on type_isstr||type_isslice,
|
||||||
* size==24 (slices are 24B too; a size gate bleeds into
|
* never size==24: a >16B struct is 24B+ too but takes the
|
||||||
* the general >16B struct path, #10). Base is BX. */
|
* struct-copy path, not this 3-word header load (#10).
|
||||||
if (u->sub && type_isstr(u->sub)) {
|
* Base is BX. */
|
||||||
|
if (u->sub && (type_isstr(u->sub) || type_isslice(u->sub))) {
|
||||||
cgslicehdr(c, D_BX);
|
cgslicehdr(c, D_BX);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -6281,10 +6287,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
|||||||
cgexpr(c, n->lhs, locals);
|
cgexpr(c, n->lhs, locals);
|
||||||
ins1(c, A_POPQ, areg(D_BX));
|
ins1(c, A_POPQ, areg(D_BX));
|
||||||
ins2(c, A_ADDQ, areg(D_BX), areg(D_AX));
|
ins2(c, A_ADDQ, areg(D_BX), areg(D_AX));
|
||||||
/* str element via fallback base: load the full (ptr, len,
|
/* str/slice element via fallback base: load the full (ptr, len,
|
||||||
* cap) header into (AX, BX, CX). Kind-gate on type_isstr,
|
* cap) header into (AX, BX, CX). Kind-gate on type_isstr||
|
||||||
* never size==24 (see Site A). Base is AX. */
|
* type_isslice, never size==24 (see Site A). Base is AX. */
|
||||||
if (u && u->sub && type_isstr(u->sub)) {
|
if (u && u->sub && (type_isstr(u->sub) || type_isslice(u->sub))) {
|
||||||
cgslicehdr(c, D_AX);
|
cgslicehdr(c, D_AX);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14556,12 +14556,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
let idx: *node = n.rhs;
|
let idx: *node = n.rhs;
|
||||||
let esz: i32 = 8;
|
let esz: i32 = 8;
|
||||||
let signed_elem: bool = false;
|
let signed_elem: bool = false;
|
||||||
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
|
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
|
||||||
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
|
// 24B+ too), so the header branches below MUST gate on KIND
|
||||||
// not a bare `esz == primtypesize("str")` size check — otherwise a
|
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
|
||||||
// []u8 element (also 24B) misfires into the str 2-word load and
|
// elem_is_slice), not a bare `esz == primtypesize("str")` size
|
||||||
// diverges from cstage (#60 collision class; sentinel 754).
|
// check — a size gate would route a plain >16B struct into the
|
||||||
|
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
|
||||||
|
// class; sentinel 754).
|
||||||
let elemisstr: bool = false;
|
let elemisstr: bool = false;
|
||||||
|
let elemisslice: bool = false;
|
||||||
let baselocal: *local = nil;
|
let baselocal: *local = nil;
|
||||||
// Global `[N]T` array or `*T` pointer used as an index base.
|
// Global `[N]T` array or `*T` pointer used as an index base.
|
||||||
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
||||||
@@ -14603,7 +14606,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
||||||
// stays unset, as before.
|
// stays unset, as before.
|
||||||
let dt: *tinfo = n.type_: *tinfo;
|
let dt: *tinfo = n.type_: *tinfo;
|
||||||
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
|
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
|
||||||
} else { if (base.kind == nkind.N_INDEX) {
|
} else { if (base.kind == nkind.N_INDEX) {
|
||||||
// #60: chained `names[i][k]` — n.type_ is the checker-
|
// #60: chained `names[i][k]` — n.type_ is the checker-
|
||||||
// stamped outer element tinfo (indexresult over the inner
|
// stamped outer element tinfo (indexresult over the inner
|
||||||
@@ -14653,6 +14656,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
elemisstr = isstrtype(c, etn);
|
elemisstr = isstrtype(c, etn);
|
||||||
|
elemisslice = isslicetype(c, etn);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, idx);
|
cgexpr(c, idx);
|
||||||
@@ -14686,10 +14690,11 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: load the full (ptr, len, cap) header into
|
// str/slice element: load the full (ptr, len, cap) header into
|
||||||
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
|
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
|
||||||
// Kind-gate on isstrtype, never size==24. Base is BX.
|
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
|
||||||
if (elemisstr) {
|
// struct is 24B+ too but takes the struct-copy path). Base is BX.
|
||||||
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -14726,9 +14731,9 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: full (ptr, len, cap) header into (AX, BX, CX);
|
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
|
||||||
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -14756,10 +14761,10 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element via fallback base: full (ptr, len, cap) header
|
// str/slice element via fallback base: full (ptr, len, cap) header
|
||||||
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
||||||
// size==24. Base AX.
|
// size==24. Base AX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "AX");
|
cgslicehdr(c, "AX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -15610,8 +15615,8 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tBX, AX\n");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
};
|
};
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
// str IS the 3-word {ptr,len,cap}
|
// str/slice: the 3-word {ptr,len,cap}
|
||||||
// slice header (#1). AX holds the
|
// slice header (#1). AX holds the
|
||||||
// element base, so load .ptr (which
|
// element base, so load .ptr (which
|
||||||
// targets AX) LAST. Matches the
|
// targets AX) LAST. Matches the
|
||||||
@@ -17718,16 +17723,16 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, n.rhs); // value → AX
|
cgexpr(c, n.rhs); // value → AX
|
||||||
// str IS []u8: spill cap (CX) + len (BX) before
|
// str/slice: spill cap (CX) + len (BX) before
|
||||||
// computing the index so the post-index store can
|
// computing the index so the post-index store can
|
||||||
// pop all three. #1/Phase 3: str=24B collides with
|
// pop all three. str=24B (#1/Phase 3) collides with
|
||||||
// slice=24B, so this MUST gate on kind (cstage's
|
// slice=24B, so this MUST gate on kind (cstage's
|
||||||
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
|
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
|
||||||
// `esz == primtypesize("str")` — or a []u8 element
|
// never a bare esz==24: a >16B struct is also >=24B
|
||||||
// (also 24B) misfires the str 3-word store and
|
// but takes the struct-copy path, not this 3-word
|
||||||
// diverges from cstage. Write-side mirror of the
|
// {ptr,len,cap} store. Write-side mirror of the
|
||||||
// cgindex read-path gate (#7/754).
|
// cgindex read-path gate (#7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
};
|
};
|
||||||
@@ -17768,10 +17773,10 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||||
emitline("\tADDQ\tAX, BX\n");
|
emitline("\tADDQ\tAX, BX\n");
|
||||||
emitline("\tPOPQ\tAX\n"); // value
|
emitline("\tPOPQ\tAX\n"); // value
|
||||||
// str IS []u8: pop the saved len + cap and store
|
// str/slice: pop the saved len + cap and store
|
||||||
// all three words. Kind-gate, not size — see the
|
// all three words. Kind-gate, not size — see the
|
||||||
// spill site above (#1/Phase 3, #7/754).
|
// spill site above (#1/Phase 3, #7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tMOVQ\tAX, (BX)\n");
|
emitline("\tMOVQ\tAX, (BX)\n");
|
||||||
emitline("\tPOPQ\tCX\n");
|
emitline("\tPOPQ\tCX\n");
|
||||||
emitline("\tMOVQ\tCX, 8(BX)\n");
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
||||||
@@ -17875,7 +17880,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\n");
|
emitline("\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the index/address
|
// all three across the index/address
|
||||||
// computation (IMULQ's CX scratch
|
// computation (IMULQ's CX scratch
|
||||||
@@ -17883,7 +17888,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
// off the str AX/BX/CX convention
|
// off the str AX/BX/CX convention
|
||||||
// (mirrors s.f=v), then store the full
|
// (mirrors s.f=v), then store the full
|
||||||
// triple at foff+0/+8/+16.
|
// triple at foff+0/+8/+16.
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
@@ -18784,8 +18789,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (streq(tf.name, fld)) {
|
if (streq(tf.name, fld)) {
|
||||||
let ft: *tinfo = tf.type_;
|
let ft: *tinfo = tf.type_;
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
if (typeisstr(ft)) {
|
if (typeisstr(ft) || typeisslice(ft)) {
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the base-expr eval
|
// all three across the base-expr eval
|
||||||
// (it may clobber any reg), stage the
|
// (it may clobber any reg), stage the
|
||||||
@@ -18906,8 +18911,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (typeisstr(leaftype)) {
|
if (typeisstr(leaftype) || typeisslice(leaftype)) {
|
||||||
// str IS []u8: store ptr/len/cap. cgexpr leaves
|
// str/slice: store ptr/len/cap. cgexpr leaves
|
||||||
// CX=cap, so the viacx base goes in DX (not CX) to
|
// CX=cap, so the viacx base goes in DX (not CX) to
|
||||||
// avoid clobbering it — same as the single-dot str
|
// avoid clobbering it — same as the single-dot str
|
||||||
// field store (#1/Phase 3).
|
// field store (#1/Phase 3).
|
||||||
|
|||||||
@@ -723,12 +723,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
let idx: *node = n.rhs;
|
let idx: *node = n.rhs;
|
||||||
let esz: i32 = 8;
|
let esz: i32 = 8;
|
||||||
let signed_elem: bool = false;
|
let signed_elem: bool = false;
|
||||||
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
|
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
|
||||||
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
|
// 24B+ too), so the header branches below MUST gate on KIND
|
||||||
// not a bare `esz == primtypesize("str")` size check — otherwise a
|
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
|
||||||
// []u8 element (also 24B) misfires into the str 2-word load and
|
// elem_is_slice), not a bare `esz == primtypesize("str")` size
|
||||||
// diverges from cstage (#60 collision class; sentinel 754).
|
// check — a size gate would route a plain >16B struct into the
|
||||||
|
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
|
||||||
|
// class; sentinel 754).
|
||||||
let elemisstr: bool = false;
|
let elemisstr: bool = false;
|
||||||
|
let elemisslice: bool = false;
|
||||||
let baselocal: *local = nil;
|
let baselocal: *local = nil;
|
||||||
// Global `[N]T` array or `*T` pointer used as an index base.
|
// Global `[N]T` array or `*T` pointer used as an index base.
|
||||||
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
||||||
@@ -770,7 +773,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
||||||
// stays unset, as before.
|
// stays unset, as before.
|
||||||
let dt: *tinfo = n.type_: *tinfo;
|
let dt: *tinfo = n.type_: *tinfo;
|
||||||
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
|
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
|
||||||
} else { if (base.kind == nkind.N_INDEX) {
|
} else { if (base.kind == nkind.N_INDEX) {
|
||||||
// #60: chained `names[i][k]` — n.type_ is the checker-
|
// #60: chained `names[i][k]` — n.type_ is the checker-
|
||||||
// stamped outer element tinfo (indexresult over the inner
|
// stamped outer element tinfo (indexresult over the inner
|
||||||
@@ -820,6 +823,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
elemisstr = isstrtype(c, etn);
|
elemisstr = isstrtype(c, etn);
|
||||||
|
elemisslice = isslicetype(c, etn);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, idx);
|
cgexpr(c, idx);
|
||||||
@@ -853,10 +857,11 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: load the full (ptr, len, cap) header into
|
// str/slice element: load the full (ptr, len, cap) header into
|
||||||
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
|
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
|
||||||
// Kind-gate on isstrtype, never size==24. Base is BX.
|
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
|
||||||
if (elemisstr) {
|
// struct is 24B+ too but takes the struct-copy path). Base is BX.
|
||||||
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -893,9 +898,9 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: full (ptr, len, cap) header into (AX, BX, CX);
|
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
|
||||||
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -923,10 +928,10 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element via fallback base: full (ptr, len, cap) header
|
// str/slice element via fallback base: full (ptr, len, cap) header
|
||||||
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
||||||
// size==24. Base AX.
|
// size==24. Base AX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "AX");
|
cgslicehdr(c, "AX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -1777,8 +1782,8 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tBX, AX\n");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
};
|
};
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
// str IS the 3-word {ptr,len,cap}
|
// str/slice: the 3-word {ptr,len,cap}
|
||||||
// slice header (#1). AX holds the
|
// slice header (#1). AX holds the
|
||||||
// element base, so load .ptr (which
|
// element base, so load .ptr (which
|
||||||
// targets AX) LAST. Matches the
|
// targets AX) LAST. Matches the
|
||||||
@@ -3885,16 +3890,16 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, n.rhs); // value → AX
|
cgexpr(c, n.rhs); // value → AX
|
||||||
// str IS []u8: spill cap (CX) + len (BX) before
|
// str/slice: spill cap (CX) + len (BX) before
|
||||||
// computing the index so the post-index store can
|
// computing the index so the post-index store can
|
||||||
// pop all three. #1/Phase 3: str=24B collides with
|
// pop all three. str=24B (#1/Phase 3) collides with
|
||||||
// slice=24B, so this MUST gate on kind (cstage's
|
// slice=24B, so this MUST gate on kind (cstage's
|
||||||
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
|
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
|
||||||
// `esz == primtypesize("str")` — or a []u8 element
|
// never a bare esz==24: a >16B struct is also >=24B
|
||||||
// (also 24B) misfires the str 3-word store and
|
// but takes the struct-copy path, not this 3-word
|
||||||
// diverges from cstage. Write-side mirror of the
|
// {ptr,len,cap} store. Write-side mirror of the
|
||||||
// cgindex read-path gate (#7/754).
|
// cgindex read-path gate (#7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
};
|
};
|
||||||
@@ -3935,10 +3940,10 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||||
emitline("\tADDQ\tAX, BX\n");
|
emitline("\tADDQ\tAX, BX\n");
|
||||||
emitline("\tPOPQ\tAX\n"); // value
|
emitline("\tPOPQ\tAX\n"); // value
|
||||||
// str IS []u8: pop the saved len + cap and store
|
// str/slice: pop the saved len + cap and store
|
||||||
// all three words. Kind-gate, not size — see the
|
// all three words. Kind-gate, not size — see the
|
||||||
// spill site above (#1/Phase 3, #7/754).
|
// spill site above (#1/Phase 3, #7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tMOVQ\tAX, (BX)\n");
|
emitline("\tMOVQ\tAX, (BX)\n");
|
||||||
emitline("\tPOPQ\tCX\n");
|
emitline("\tPOPQ\tCX\n");
|
||||||
emitline("\tMOVQ\tCX, 8(BX)\n");
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
||||||
@@ -4042,7 +4047,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\n");
|
emitline("\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the index/address
|
// all three across the index/address
|
||||||
// computation (IMULQ's CX scratch
|
// computation (IMULQ's CX scratch
|
||||||
@@ -4050,7 +4055,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
// off the str AX/BX/CX convention
|
// off the str AX/BX/CX convention
|
||||||
// (mirrors s.f=v), then store the full
|
// (mirrors s.f=v), then store the full
|
||||||
// triple at foff+0/+8/+16.
|
// triple at foff+0/+8/+16.
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
@@ -4951,8 +4956,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (streq(tf.name, fld)) {
|
if (streq(tf.name, fld)) {
|
||||||
let ft: *tinfo = tf.type_;
|
let ft: *tinfo = tf.type_;
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
if (typeisstr(ft)) {
|
if (typeisstr(ft) || typeisslice(ft)) {
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the base-expr eval
|
// all three across the base-expr eval
|
||||||
// (it may clobber any reg), stage the
|
// (it may clobber any reg), stage the
|
||||||
@@ -5073,8 +5078,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (typeisstr(leaftype)) {
|
if (typeisstr(leaftype) || typeisslice(leaftype)) {
|
||||||
// str IS []u8: store ptr/len/cap. cgexpr leaves
|
// str/slice: store ptr/len/cap. cgexpr leaves
|
||||||
// CX=cap, so the viacx base goes in DX (not CX) to
|
// CX=cap, so the viacx base goes in DX (not CX) to
|
||||||
// avoid clobbering it — same as the single-dot str
|
// avoid clobbering it — same as the single-dot str
|
||||||
// field store (#1/Phase 3).
|
// field store (#1/Phase 3).
|
||||||
|
|||||||
@@ -14556,12 +14556,15 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
let idx: *node = n.rhs;
|
let idx: *node = n.rhs;
|
||||||
let esz: i32 = 8;
|
let esz: i32 = 8;
|
||||||
let signed_elem: bool = false;
|
let signed_elem: bool = false;
|
||||||
// #1/Phase 3: str=24B collides with slice=24B, so the str-element
|
// #1/Phase 3: str and slice are both 24B (and a >16B struct is
|
||||||
// branches below MUST gate on kind (mirroring cstage's elem_is_str),
|
// 24B+ too), so the header branches below MUST gate on KIND
|
||||||
// not a bare `esz == primtypesize("str")` size check — otherwise a
|
// (elemisstr/elemisslice, mirroring cstage's elem_is_str||
|
||||||
// []u8 element (also 24B) misfires into the str 2-word load and
|
// elem_is_slice), not a bare `esz == primtypesize("str")` size
|
||||||
// diverges from cstage (#60 collision class; sentinel 754).
|
// check — a size gate would route a plain >16B struct into the
|
||||||
|
// 3-word {ptr,len,cap} load and diverge from cstage (#60 collision
|
||||||
|
// class; sentinel 754).
|
||||||
let elemisstr: bool = false;
|
let elemisstr: bool = false;
|
||||||
|
let elemisslice: bool = false;
|
||||||
let baselocal: *local = nil;
|
let baselocal: *local = nil;
|
||||||
// Global `[N]T` array or `*T` pointer used as an index base.
|
// Global `[N]T` array or `*T` pointer used as an index base.
|
||||||
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
// The local-ident lookup above misses it; we need LEAQ name(SB)
|
||||||
@@ -14603,7 +14606,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
// cgen.c:3517-18). esz-only — N_DOT-base signedness
|
||||||
// stays unset, as before.
|
// stays unset, as before.
|
||||||
let dt: *tinfo = n.type_: *tinfo;
|
let dt: *tinfo = n.type_: *tinfo;
|
||||||
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); };
|
if (dt != nil) { esz = dt.size: i32; elemisstr = typeisstr(dt); elemisslice = typeisslice(dt); };
|
||||||
} else { if (base.kind == nkind.N_INDEX) {
|
} else { if (base.kind == nkind.N_INDEX) {
|
||||||
// #60: chained `names[i][k]` — n.type_ is the checker-
|
// #60: chained `names[i][k]` — n.type_ is the checker-
|
||||||
// stamped outer element tinfo (indexresult over the inner
|
// stamped outer element tinfo (indexresult over the inner
|
||||||
@@ -14653,6 +14656,7 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
elemisstr = isstrtype(c, etn);
|
elemisstr = isstrtype(c, etn);
|
||||||
|
elemisslice = isslicetype(c, etn);
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, idx);
|
cgexpr(c, idx);
|
||||||
@@ -14686,10 +14690,11 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: load the full (ptr, len, cap) header into
|
// str/slice element: load the full (ptr, len, cap) header into
|
||||||
// (AX, BX, CX) — str is 24B since #1, so cap must survive.
|
// (AX, BX, CX) — both are 24B since #1, so cap must survive.
|
||||||
// Kind-gate on isstrtype, never size==24. Base is BX.
|
// Kind-gate on isstrtype||isslicetype, never size==24 (a >16B
|
||||||
if (elemisstr) {
|
// struct is 24B+ too but takes the struct-copy path). Base is BX.
|
||||||
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -14726,9 +14731,9 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element: full (ptr, len, cap) header into (AX, BX, CX);
|
// str/slice element: full (ptr, len, cap) header into (AX, BX, CX);
|
||||||
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
// cap must survive (#1). Kind-gate, never size==24. Base BX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "BX");
|
cgslicehdr(c, "BX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -14756,10 +14761,10 @@ fn cgindex(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tMOVQ\t(BX), AX\n");
|
emitline("\tMOVQ\t(BX), AX\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str element via fallback base: full (ptr, len, cap) header
|
// str/slice element via fallback base: full (ptr, len, cap) header
|
||||||
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
// into (AX, BX, CX); cap must survive (#1). Kind-gate, never
|
||||||
// size==24. Base AX.
|
// size==24. Base AX.
|
||||||
if (elemisstr) {
|
if (elemisstr || elemisslice) {
|
||||||
cgslicehdr(c, "AX");
|
cgslicehdr(c, "AX");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -15610,8 +15615,8 @@ fn cgdot(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
emitline("\tMOVQ\tBX, AX\n");
|
emitline("\tMOVQ\tBX, AX\n");
|
||||||
};
|
};
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
// str IS the 3-word {ptr,len,cap}
|
// str/slice: the 3-word {ptr,len,cap}
|
||||||
// slice header (#1). AX holds the
|
// slice header (#1). AX holds the
|
||||||
// element base, so load .ptr (which
|
// element base, so load .ptr (which
|
||||||
// targets AX) LAST. Matches the
|
// targets AX) LAST. Matches the
|
||||||
@@ -17718,16 +17723,16 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
cgexpr(c, n.rhs); // value → AX
|
cgexpr(c, n.rhs); // value → AX
|
||||||
// str IS []u8: spill cap (CX) + len (BX) before
|
// str/slice: spill cap (CX) + len (BX) before
|
||||||
// computing the index so the post-index store can
|
// computing the index so the post-index store can
|
||||||
// pop all three. #1/Phase 3: str=24B collides with
|
// pop all three. str=24B (#1/Phase 3) collides with
|
||||||
// slice=24B, so this MUST gate on kind (cstage's
|
// slice=24B, so this MUST gate on kind (cstage's
|
||||||
// elem_is_str, cmd/w6c/cgen.c:3576) — not a bare
|
// elem_is_str||elem_is_slice, cmd/w6c/cgen.c:3581),
|
||||||
// `esz == primtypesize("str")` — or a []u8 element
|
// never a bare esz==24: a >16B struct is also >=24B
|
||||||
// (also 24B) misfires the str 3-word store and
|
// but takes the struct-copy path, not this 3-word
|
||||||
// diverges from cstage. Write-side mirror of the
|
// {ptr,len,cap} store. Write-side mirror of the
|
||||||
// cgindex read-path gate (#7/754).
|
// cgindex read-path gate (#7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
};
|
};
|
||||||
@@ -17768,10 +17773,10 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\tPOPQ\tAX\n"); // scaled idx
|
emitline("\tPOPQ\tAX\n"); // scaled idx
|
||||||
emitline("\tADDQ\tAX, BX\n");
|
emitline("\tADDQ\tAX, BX\n");
|
||||||
emitline("\tPOPQ\tAX\n"); // value
|
emitline("\tPOPQ\tAX\n"); // value
|
||||||
// str IS []u8: pop the saved len + cap and store
|
// str/slice: pop the saved len + cap and store
|
||||||
// all three words. Kind-gate, not size — see the
|
// all three words. Kind-gate, not size — see the
|
||||||
// spill site above (#1/Phase 3, #7/754).
|
// spill site above (#1/Phase 3, #7/754).
|
||||||
if (isstrtype(c, elemtn)) {
|
if (isstrtype(c, elemtn) || isslicetype(c, elemtn)) {
|
||||||
emitline("\tMOVQ\tAX, (BX)\n");
|
emitline("\tMOVQ\tAX, (BX)\n");
|
||||||
emitline("\tPOPQ\tCX\n");
|
emitline("\tPOPQ\tCX\n");
|
||||||
emitline("\tMOVQ\tCX, 8(BX)\n");
|
emitline("\tMOVQ\tCX, 8(BX)\n");
|
||||||
@@ -17875,7 +17880,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
emitline("\n");
|
emitline("\n");
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the index/address
|
// all three across the index/address
|
||||||
// computation (IMULQ's CX scratch
|
// computation (IMULQ's CX scratch
|
||||||
@@ -17883,7 +17888,7 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
// off the str AX/BX/CX convention
|
// off the str AX/BX/CX convention
|
||||||
// (mirrors s.f=v), then store the full
|
// (mirrors s.f=v), then store the full
|
||||||
// triple at foff+0/+8/+16.
|
// triple at foff+0/+8/+16.
|
||||||
if (isstrtype(c, fi.tnode)) {
|
if (isstrtype(c, fi.tnode) || isslicetype(c, fi.tnode)) {
|
||||||
cgexpr(c, n.rhs);
|
cgexpr(c, n.rhs);
|
||||||
emitline("\tPUSHQ\tCX\n");
|
emitline("\tPUSHQ\tCX\n");
|
||||||
emitline("\tPUSHQ\tBX\n");
|
emitline("\tPUSHQ\tBX\n");
|
||||||
@@ -18784,8 +18789,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
if (streq(tf.name, fld)) {
|
if (streq(tf.name, fld)) {
|
||||||
let ft: *tinfo = tf.type_;
|
let ft: *tinfo = tf.type_;
|
||||||
if (n.op == tkind.TK_ASSIGN) {
|
if (n.op == tkind.TK_ASSIGN) {
|
||||||
if (typeisstr(ft)) {
|
if (typeisstr(ft) || typeisslice(ft)) {
|
||||||
// str IS []u8: rhs leaves AX=ptr,
|
// str/slice: rhs leaves AX=ptr,
|
||||||
// BX=len, CX=cap (#1/Phase 3). Spill
|
// BX=len, CX=cap (#1/Phase 3). Spill
|
||||||
// all three across the base-expr eval
|
// all three across the base-expr eval
|
||||||
// (it may clobber any reg), stage the
|
// (it may clobber any reg), stage the
|
||||||
@@ -18906,8 +18911,8 @@ fn cgassign(c: *cgen, n: *node) void = {
|
|||||||
};
|
};
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if (typeisstr(leaftype)) {
|
if (typeisstr(leaftype) || typeisslice(leaftype)) {
|
||||||
// str IS []u8: store ptr/len/cap. cgexpr leaves
|
// str/slice: store ptr/len/cap. cgexpr leaves
|
||||||
// CX=cap, so the viacx base goes in DX (not CX) to
|
// CX=cap, so the viacx base goes in DX (not CX) to
|
||||||
// avoid clobbering it — same as the single-dot str
|
// avoid clobbering it — same as the single-dot str
|
||||||
// field store (#1/Phase 3).
|
// field store (#1/Phase 3).
|
||||||
|
|||||||
244
test/wcc/941_slice_store_cap_run.c
Normal file
244
test/wcc/941_slice_store_cap_run.c
Normal file
@@ -0,0 +1,244 @@
|
|||||||
|
/*
|
||||||
|
* 941_slice_store_cap_run — runtime coverage for task #7: a `[]T` (slice-typed)
|
||||||
|
* VALUE stored through an indexed / field / chained lhs, and read back, must
|
||||||
|
* move the full 24B {ptr,len,cap} header, not just {ptr}. Slices are 24B always;
|
||||||
|
* the G-cluster's store+read arms were kind-gated on str ONLY (the slice arm was
|
||||||
|
* never extended), so a slice value fell to the 1-word fldstoreop/fldloadop
|
||||||
|
* default and silently DROPPED len+cap. str IS []u8 since Phase 2 (#1), so the
|
||||||
|
* str 3-word machinery applies to slices verbatim — the fix widens each gate
|
||||||
|
* from `str` to `str || slice` (kind-OR, never a sz==24 test, which would also
|
||||||
|
* catch >16B structs).
|
||||||
|
*
|
||||||
|
* Sites exercised (cstage cmd/w6c/cgen.c + cgenexpr.ww twin), each store paired
|
||||||
|
* with its read mirror:
|
||||||
|
* A whole-element `xs[i] = sl` + read `xs[i]` (cgen.c store ~3650/3692;
|
||||||
|
* read N_INDEX cgslicehdr; ww cgassign elemtn + cgindex elemisslice).
|
||||||
|
* B arr[i].field `arr[i].f = sl` + read `arr[i].f` (G1-twin store; cgdot
|
||||||
|
* arrfield read).
|
||||||
|
* C chained *struct `r.sym.f = sl` + read `r.sym.f` (G2-twin store; the
|
||||||
|
* chained-*struct read was already 3-word).
|
||||||
|
* D value-spine `o.i.f = sl` + read `o.i.f` (value-struct-spine
|
||||||
|
* store; the value-spine read was already 3-word).
|
||||||
|
*
|
||||||
|
* UNLIKE the str store probes (937/938), here BOTH the store AND the read were
|
||||||
|
* 1-word before the fix, so each row is a store->read-back ROUNDTRIP: a 1-word
|
||||||
|
* store leaves the dst's len/cap at their prior value, and a 1-word read never
|
||||||
|
* loads them, so a broken stage yields a value whose len/cap are not p's. The
|
||||||
|
* full {ptr,len,cap} triple is asserted (ptr via s[0]=='h'=104, len=2, cap=8;
|
||||||
|
* cap!=len so a dropped len OR cap is caught).
|
||||||
|
*
|
||||||
|
* POISON: rows B/C/D seed the dst slot with a DIFFERENT slice q (len=4,cap=5)
|
||||||
|
* via a PROVEN 3-word slice store that is NOT the site under test — the single-
|
||||||
|
* dot field store, which was merged onto the slice arm pre-#7 (cgen.c C4.4):
|
||||||
|
* B via `pr.f = q` (pr=&arr[1], *struct field);
|
||||||
|
* C via `st.f = q` (direct field);
|
||||||
|
* D via `pi.f = q` (pi=&o.i, via-ptr field).
|
||||||
|
* So before the fix the dst keeps q's cap=5 while the read returns stale words;
|
||||||
|
* either way cap != 8. Row A is a bare roundtrip with no explicit poison: no
|
||||||
|
* proven 3-word store targets a bare slice array-element header (the same reason
|
||||||
|
* str has no standalone whole-element-store probe), so the store+read pair is
|
||||||
|
* itself the discriminator.
|
||||||
|
*
|
||||||
|
* Slices are built with explicit pseudo-field stores (`s.ptr=&buf[0]; s.len=N;
|
||||||
|
* s.cap=M`), the proven construction used by 691 — NOT sub-slicing, whose cap is
|
||||||
|
* the separate #20 fix that depends on this fold.
|
||||||
|
*
|
||||||
|
* Verified fail-before (all 4 rows exit 1, cap reads the poison/stale, not 8) /
|
||||||
|
* pass-after (exit 0) on BOTH the cstage `ww` and wwstage `ww_ww` drivers.
|
||||||
|
* NNN<950, self-contained (/tmp, no imports), so rule-14's selfhost-sibling
|
||||||
|
* race does not apply (mirrors the 928/932 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 — whole-element `xs[0] = p` into a [2][]u8 local array, read back
|
||||||
|
* via `xs[0]`. Exercises the N_INDEX store arm (elem_is_slice) and the
|
||||||
|
* N_INDEX read (cgslicehdr) together. No proven poison for a bare
|
||||||
|
* array-element header; the roundtrip is the probe. */
|
||||||
|
{ "slice_store_wholeelem",
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let hb: [8]u8; hb[0] = 104u8;\n"
|
||||||
|
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
|
||||||
|
" let xs: [2][]u8;\n"
|
||||||
|
" xs[0] = p;\n"
|
||||||
|
" let e: []u8 = xs[0];\n"
|
||||||
|
" if (e.cap: i32 != 8) { return 1; };\n"
|
||||||
|
" if (e.len: i32 != 2) { return 2; };\n"
|
||||||
|
" if (e[0] != 104u8) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* B — `arr[i].f = p` into a [N]rec field (G1-twin). Poison arr[1].f
|
||||||
|
* (cap=5,len=4,'q') via &arr[1] + the proven single-dot *struct field
|
||||||
|
* store; then the field-of-indexed store lands p (cap=8,len=2,'h'). */
|
||||||
|
{ "slice_store_arrfield",
|
||||||
|
"type rec = struct { f: []u8 };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let qb: [8]u8; qb[0] = 113u8;\n"
|
||||||
|
" let hb: [8]u8; hb[0] = 104u8;\n"
|
||||||
|
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
|
||||||
|
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
|
||||||
|
" let arr: [3]rec;\n"
|
||||||
|
" let pr: *rec = &arr[1];\n"
|
||||||
|
" pr.f = q;\n"
|
||||||
|
" arr[1].f = p;\n"
|
||||||
|
" let s: []u8 = arr[1].f;\n"
|
||||||
|
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||||
|
" if (s.len: i32 != 2) { return 2; };\n"
|
||||||
|
" if (s[0] != 104u8) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* C — chained `r.sym.f = p` where r.sym is a *inner (G2-twin). Poison
|
||||||
|
* the pointee field via the proven direct field store (`st.f = q`); the
|
||||||
|
* chained store derefs r.sym and overwrites st.f. */
|
||||||
|
{ "slice_store_chained_ptr",
|
||||||
|
"type inner = struct { f: []u8 };\n"
|
||||||
|
"type outer = struct { sym: *inner };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let qb: [8]u8; qb[0] = 113u8;\n"
|
||||||
|
" let hb: [8]u8; hb[0] = 104u8;\n"
|
||||||
|
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
|
||||||
|
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
|
||||||
|
" let st: inner;\n"
|
||||||
|
" st.f = q;\n"
|
||||||
|
" let r: outer;\n"
|
||||||
|
" r.sym = &st;\n"
|
||||||
|
" r.sym.f = p;\n"
|
||||||
|
" let s: []u8 = r.sym.f;\n"
|
||||||
|
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||||
|
" if (s.len: i32 != 2) { return 2; };\n"
|
||||||
|
" if (s[0] != 104u8) { return 3; };\n"
|
||||||
|
" return 0;\n"
|
||||||
|
"};\n",
|
||||||
|
0 },
|
||||||
|
/* D — value-spine `o.i.f = p` where o.i is a value-struct field (not a
|
||||||
|
* pointer). Poison o.i.f via &o.i + the proven via-ptr field store
|
||||||
|
* (`pi.f = q`); the value-spine store walks o.i and overwrites .f. */
|
||||||
|
{ "slice_store_valuespine",
|
||||||
|
"type inner = struct { f: []u8 };\n"
|
||||||
|
"type outer = struct { i: inner };\n"
|
||||||
|
"export fn main() i32 = {\n"
|
||||||
|
" let qb: [8]u8; qb[0] = 113u8;\n"
|
||||||
|
" let hb: [8]u8; hb[0] = 104u8;\n"
|
||||||
|
" let q: []u8; q.ptr = &qb[0]; q.len = 4; q.cap = 5;\n"
|
||||||
|
" let p: []u8; p.ptr = &hb[0]; p.len = 2; p.cap = 8;\n"
|
||||||
|
" let o: outer;\n"
|
||||||
|
" let pi: *inner = &o.i;\n"
|
||||||
|
" pi.f = q;\n"
|
||||||
|
" o.i.f = p;\n"
|
||||||
|
" let s: []u8 = o.i.f;\n"
|
||||||
|
" if (s.cap: i32 != 8) { return 1; };\n"
|
||||||
|
" if (s.len: i32 != 2) { return 2; };\n"
|
||||||
|
" if (s[0] != 104u8) { 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/slicestore_%d_%d.ww", getpid(), i);
|
||||||
|
snprintf(tmpdir, sizeof tmpdir, "/tmp/slicestore_%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,
|
||||||
|
"slice_store_cap_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,
|
||||||
|
"slice_store_cap_run[%s][%s]: exit=%d want=%d\n",
|
||||||
|
drivers[d].name, rows[i].label,
|
||||||
|
got, rows[i].want);
|
||||||
|
fail++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (fail) {
|
||||||
|
fprintf(stderr, "slice_store_cap_run: %d/%d fixtures failed\n",
|
||||||
|
fail, total);
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("slice_store_cap_run: %d/%d ok\n", total, total);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user