wcc: for-range destructure copies the full str/slice binding, both stages
The per-binding copy loop moved ONE word of a 24B str/slice binding — .len and .cap read zero/garbage in BOTH stages (byte-identical, the deepest both-wrong-identical of the drain: the F7-era stride fix asserted convergence without re-measuring the absolute). Copy the full extent for an sz>8 str/slice binding; the rewritten 989_tupfieldsize pins all three header words with sliced caps so cap!=len has teeth. The tagged-binding arm remains open as task #53 (wwstage paramfieldsize). Review-era task #40, recategorized #263 fused. Both stages move in one commit: one emission contract; splitting would leave the byte-id gates red between the halves.
This commit is contained in:
@@ -14344,6 +14344,50 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
for (int b = 0; b < nbinds; b++) {
|
for (int b = 0; b < nbinds; b++) {
|
||||||
|
/* #40 (#263): a str/slice/struct destructure
|
||||||
|
* binding (24B header / aggregate, sz>8) copies
|
||||||
|
* its FULL extent — the single fldloadop word
|
||||||
|
* truncated a slice binding to its .ptr, dropping
|
||||||
|
* .len/.cap (both stages identically, byte-id-
|
||||||
|
* WRONG; F7-c4 fixed only the STRIDE). Same
|
||||||
|
* word-run + sized-tail idiom as the non-
|
||||||
|
* destructure aggregate copy above. */
|
||||||
|
int bsz = binds[b].sz;
|
||||||
|
if (bsz > 8) {
|
||||||
|
int k = 0;
|
||||||
|
for (; k + 8 <= bsz; k += 8) {
|
||||||
|
ins2(c, A_MOVQ,
|
||||||
|
amem(D_BX, binds[b].foff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVQ, areg(D_AX),
|
||||||
|
amem(D_BP, binds[b].off + k));
|
||||||
|
}
|
||||||
|
if (k + 4 <= bsz) {
|
||||||
|
ins2(c, A_MOVL,
|
||||||
|
amem(D_BX, binds[b].foff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVL, areg(D_AX),
|
||||||
|
amem(D_BP, binds[b].off + k));
|
||||||
|
k += 4;
|
||||||
|
}
|
||||||
|
if (k + 2 <= bsz) {
|
||||||
|
ins2(c, A_MOVW,
|
||||||
|
amem(D_BX, binds[b].foff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVW, areg(D_AX),
|
||||||
|
amem(D_BP, binds[b].off + k));
|
||||||
|
k += 2;
|
||||||
|
}
|
||||||
|
if (k + 1 <= bsz) {
|
||||||
|
ins2(c, A_MOVB,
|
||||||
|
amem(D_BX, binds[b].foff + k),
|
||||||
|
areg(D_AX));
|
||||||
|
ins2(c, A_MOVB, areg(D_AX),
|
||||||
|
amem(D_BP, binds[b].off + k));
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
int op = fldloadop(binds[b].ftype, binds[b].sz);
|
||||||
ins2(c, op, amem(D_BX, binds[b].foff),
|
ins2(c, op, amem(D_BX, binds[b].foff),
|
||||||
areg(D_AX));
|
areg(D_AX));
|
||||||
|
|||||||
@@ -39696,6 +39696,54 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
let b: i32 = 0;
|
let b: i32 = 0;
|
||||||
for (b < nbinds) {
|
for (b < nbinds) {
|
||||||
|
// #40 (#263): a str/slice/struct destructure binding
|
||||||
|
// (24B header / aggregate, sz>8) copies its FULL extent
|
||||||
|
// — the single load word truncated a slice binding to
|
||||||
|
// its .ptr, dropping .len/.cap (both stages identically,
|
||||||
|
// byte-id-WRONG; F7-c4 fixed only the STRIDE). Same
|
||||||
|
// word-run + sized-tail idiom as the non-destructure
|
||||||
|
// aggregate copy above.
|
||||||
|
if (bind_sz[b] > 8) {
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k + 8 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 8;
|
||||||
|
};
|
||||||
|
if (k + 4 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 4;
|
||||||
|
};
|
||||||
|
if (k + 2 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 2;
|
||||||
|
};
|
||||||
|
if (k + 1 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 1;
|
||||||
|
};
|
||||||
|
b += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(op);
|
emitline(op);
|
||||||
|
|||||||
@@ -4144,6 +4144,54 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
let b: i32 = 0;
|
let b: i32 = 0;
|
||||||
for (b < nbinds) {
|
for (b < nbinds) {
|
||||||
|
// #40 (#263): a str/slice/struct destructure binding
|
||||||
|
// (24B header / aggregate, sz>8) copies its FULL extent
|
||||||
|
// — the single load word truncated a slice binding to
|
||||||
|
// its .ptr, dropping .len/.cap (both stages identically,
|
||||||
|
// byte-id-WRONG; F7-c4 fixed only the STRIDE). Same
|
||||||
|
// word-run + sized-tail idiom as the non-destructure
|
||||||
|
// aggregate copy above.
|
||||||
|
if (bind_sz[b] > 8) {
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k + 8 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 8;
|
||||||
|
};
|
||||||
|
if (k + 4 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 4;
|
||||||
|
};
|
||||||
|
if (k + 2 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 2;
|
||||||
|
};
|
||||||
|
if (k + 1 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 1;
|
||||||
|
};
|
||||||
|
b += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(op);
|
emitline(op);
|
||||||
|
|||||||
@@ -39696,6 +39696,54 @@ fn cgforrange(c: *cgen, n: *node) void = {
|
|||||||
} else {
|
} else {
|
||||||
let b: i32 = 0;
|
let b: i32 = 0;
|
||||||
for (b < nbinds) {
|
for (b < nbinds) {
|
||||||
|
// #40 (#263): a str/slice/struct destructure binding
|
||||||
|
// (24B header / aggregate, sz>8) copies its FULL extent
|
||||||
|
// — the single load word truncated a slice binding to
|
||||||
|
// its .ptr, dropping .len/.cap (both stages identically,
|
||||||
|
// byte-id-WRONG; F7-c4 fixed only the STRIDE). Same
|
||||||
|
// word-run + sized-tail idiom as the non-destructure
|
||||||
|
// aggregate copy above.
|
||||||
|
if (bind_sz[b] > 8) {
|
||||||
|
let k: i32 = 0;
|
||||||
|
for (k + 8 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVQ\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVQ\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 8;
|
||||||
|
};
|
||||||
|
if (k + 4 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVL\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVL\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 4;
|
||||||
|
};
|
||||||
|
if (k + 2 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVW\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVW\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 2;
|
||||||
|
};
|
||||||
|
if (k + 1 <= bind_sz[b]) {
|
||||||
|
emitline("\tMOVB\t");
|
||||||
|
emitoff((bind_foff[b] + k): i64);
|
||||||
|
emitline("(BX), AX\n");
|
||||||
|
emitline("\tMOVB\tAX, ");
|
||||||
|
emitoff((bind_off[b] + k): i64);
|
||||||
|
emitline("(BP)\n");
|
||||||
|
k += 1;
|
||||||
|
};
|
||||||
|
b += 1;
|
||||||
|
continue;
|
||||||
|
};
|
||||||
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
let op: str = loadopsz(bind_signed[b], bind_sz[b]);
|
||||||
emitline("\t");
|
emitline("\t");
|
||||||
emitline(op);
|
emitline(op);
|
||||||
|
|||||||
@@ -17,26 +17,32 @@
|
|||||||
* slice-header SSoT, rule-13) and the N_TTUPLE arm (sum of 8B-floored
|
* slice-header SSoT, rule-13) and the N_TTUPLE arm (sum of 8B-floored
|
||||||
* element slots, recursive), aligning wwstage UP.
|
* element slots, recursive), aligning wwstage UP.
|
||||||
*
|
*
|
||||||
* NB on the assertion: the slice-field row is pinned cs==ww (MATCH-ONLY),
|
* #40 (#263, now CLOSED both stages — fused): F7-c4 fixed only the STRIDE,
|
||||||
* NOT to an absolute value. cstage independently mis-loads a single-word
|
* so the slice-field row was pinned cs==ww (MATCH-ONLY) at 40, NOT the
|
||||||
* tuple-destructure element (a SEPARATE bug ken flagged out of F7 scope,
|
* arithmetic-expected 45 — because the destructure LOAD-into-binding copied
|
||||||
* filed as task #40 — cstage's absolute value is wrong: 40 != 45), so
|
* only the PTR word of the 24B slice binding, dropping .len/.cap (both stages
|
||||||
* both stages currently land 40, not the arithmetic-expected 45. The
|
* identically; nobody had re-measured the ww absolute after F7-c4). The fused
|
||||||
* #43 fix's job is to remove the cs≠ww STRIDE/OFFSET divergence (40 vs 16
|
* #40 fix (cgen.c N_FORRANGE destructure + cgenstmt.ww cgforrange twin: copy
|
||||||
* → 40 vs 40); pinning cs==ww tracks exactly that and won't false-fail
|
* the binding's FULL extent for an aggregate sz>8 binding, same word-run +
|
||||||
* when the separate destructure-load bug is later fixed (both move
|
* sized-tail idiom as the non-destructure copy) lands all three header words.
|
||||||
* together). The scalar-tuple control IS correct on both stages, so it
|
* The rows now pin the ABSOLUTE value cs==ww across all three words — the
|
||||||
* also pins the absolute value (no-regression teeth).
|
* recurring dropped word in this 24B-aggregate-destructure family is the CAP
|
||||||
|
* (cf #29 chained-dot, #43 sizer), so the cap row reads b.cap with cap!=len
|
||||||
|
* (sliced) to teeth it; a ptr+len-only set would pass green while cap stayed
|
||||||
|
* stale (exactly how #40 hid).
|
||||||
*
|
*
|
||||||
* Tuples are built by whole-tuple element store (`xs[i] = (..)`); the
|
* Tuples are built by whole-tuple element store (`xs[i] = (..)`).
|
||||||
* `xs[i].0 = ..` field-store target and the `[[..]]` nested literal are
|
|
||||||
* both independently unsupported / #270-1c-blocked (orthogonal).
|
|
||||||
*
|
*
|
||||||
* Rows (cstage `ww` always; wwstage `ww_ww` when present; rule-10):
|
* Rows (cstage `ww` + wwstage `ww_ww`; rule-10; all pin the absolute value):
|
||||||
* row | shape | assert
|
* row | reads | exit (cs==ww)
|
||||||
* -------------------+--------------------------------+----------------
|
* ------------------+------------------------------------+--------------
|
||||||
* slice_field_tuple | for(.. [2]([]u8,i64)) | cs==ww [#43 bug]
|
* len_read | len(b)+n over [2]([]u8,i64) | 45 (2+10+3+30)
|
||||||
* scalar_tuple_ctl | for(.. [2](i64,i64)) = 48 | cs==ww AND == 48
|
* cap_read | b.cap+n, sliced caps 4/5 (cap!=len) | 49 (4+10+5+30)
|
||||||
|
* ptr_read | b[0]+ (byte through ptr) | 217 (97+120)
|
||||||
|
* scalar_tuple_ctl | for(.. [2](i64,i64)) | 48 (control)
|
||||||
|
* Pre-fix (single-word copy): len_read=40, cap_read=40 (len/cap words both 0),
|
||||||
|
* ptr_read=217 (word0 always copied — coverage, no teeth); cs==ww==wrong (the
|
||||||
|
* #263 both-wrong-identical residual the fused fix retires).
|
||||||
*/
|
*/
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
@@ -61,9 +67,9 @@ struct row {
|
|||||||
};
|
};
|
||||||
|
|
||||||
static const struct row rows[] = {
|
static const struct row rows[] = {
|
||||||
/* (1) #43 — a []u8 tuple-field sized 8 not 24 → wrong stride/offset.
|
/* (1) len_read (#40 teeth, word 1): the slice binding's .len must be
|
||||||
* cs==ww only (cstage's 40≠45 is the separate destructure-load bug). */
|
* copied. Pre-fix the single-word copy dropped it → len(b)=0 → 40. */
|
||||||
{ "slice_field_tuple",
|
{ "len_read",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"export fn main() int = {\n"
|
"export fn main() int = {\n"
|
||||||
" let b0: []u8 = ['a', 'b'];\n"
|
" let b0: []u8 = ['a', 'b'];\n"
|
||||||
@@ -77,11 +83,44 @@ static const struct row rows[] = {
|
|||||||
" };\n"
|
" };\n"
|
||||||
" return sum: int;\n"
|
" return sum: int;\n"
|
||||||
"};\n",
|
"};\n",
|
||||||
-1 },
|
45 },
|
||||||
|
|
||||||
/* (2) control — scalar-only tuple (no slice/str field): paramfieldsize
|
/* (2) cap_read (#40 teeth, word 2 — THE recurring dropped word): caps
|
||||||
* already handled it, so c4 must not regress it. Correct on both stages:
|
* 4/5 are SLICED so cap != len (2/3); pre-fix b.cap=0 → 40. */
|
||||||
* 3+10+5+30 == 48. Pins cs==ww AND the absolute value. */
|
{ "cap_read",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() int = {\n"
|
||||||
|
" let base0: []u8 = ['a', 'b', 'c', 'd'];\n"
|
||||||
|
" let base1: []u8 = ['p', 'q', 'r', 's', 't'];\n"
|
||||||
|
" let xs: [2]([]u8, i64);\n"
|
||||||
|
" xs[0] = (base0[0:2], 10);\n"
|
||||||
|
" xs[1] = (base1[0:3], 30);\n"
|
||||||
|
" let sum: i64 = 0;\n"
|
||||||
|
" for (let (b, n) .. xs) {\n"
|
||||||
|
" sum = sum + b.cap: i64 + n;\n"
|
||||||
|
" };\n"
|
||||||
|
" return sum: int;\n"
|
||||||
|
"};\n",
|
||||||
|
49 },
|
||||||
|
|
||||||
|
/* (3) ptr_read (word 0 — coverage; word0 was always copied, no teeth):
|
||||||
|
* a byte read through the binding's .ptr. b0[0]='a'=97, b1[0]='x'=120. */
|
||||||
|
{ "ptr_read",
|
||||||
|
"package main;\n"
|
||||||
|
"export fn main() int = {\n"
|
||||||
|
" let b0: []u8 = ['a', 'b'];\n"
|
||||||
|
" let b1: []u8 = ['x', 'y', 'z'];\n"
|
||||||
|
" let xs: [2]([]u8, i64);\n"
|
||||||
|
" xs[0] = (b0, 10);\n"
|
||||||
|
" xs[1] = (b1, 30);\n"
|
||||||
|
" let sum: i64 = 0;\n"
|
||||||
|
" for (let (b, n) .. xs) { sum = sum + b[0]: i64; };\n"
|
||||||
|
" return sum: int;\n"
|
||||||
|
"};\n",
|
||||||
|
217 },
|
||||||
|
|
||||||
|
/* (4) control — scalar-only tuple (no aggregate field): paramfieldsize
|
||||||
|
* already handled it; the sz>8 arm must not touch it. 3+10+5+30 == 48. */
|
||||||
{ "scalar_tuple_ctl",
|
{ "scalar_tuple_ctl",
|
||||||
"package main;\n"
|
"package main;\n"
|
||||||
"export fn main() int = {\n"
|
"export fn main() int = {\n"
|
||||||
|
|||||||
Reference in New Issue
Block a user