wcc: str -> 24B {ptr,len,cap}, 3-reg ABI -- parity with []u8 (both stages)
A ww `str` becomes a 24-byte {ptr,len,cap} value, identical in layout to
[]u8 -- the enabling prerequisite for the Phase 2 `str == []u8` collapse.
Both stages, atomically:
- ty_str 16->24B; str value flows 3-reg AX/BX/CX (was 2-reg); str literals
emit cap (=len).
- str in a tagged union grows to a 32B slot, using the AX/DX/CX/R8 4th-word
path already used by 32B slice-variant unions -- str-variant is now
structurally identical.
- tuple (scalar,str) return: 4-reg AX/DX/CX/R8 + 32B receive, extending the
existing type-keyed return (no sret).
- str == []u8 for index and .ptr/.len/.cap, kind-gated where size-based
dispatch collided at 24B; cstage and wwstage mirror exactly.
- table-driven runtime coverage: test/wcc/928_str_abi_run.c.
Cannot be split (rule 10/11): a 24B str and a 16B str cannot coexist across
the two compiler stages without breaking byte-identity, so the size change
and every dependent ABI/codegen site land in one atomic commit, both stages.
Known follow-ups (zero corpus impact, tracked): str-literal global .cap
static-init; >16B struct by-value (pre-existing); tagged-union
match-scrutinee stage divergence (pre-existing).
This commit is contained in:
@@ -249,8 +249,11 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
emitline(", AX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
} else { if (nodeisstr(c, arg)) {
|
||||
// slot 24: [+0]=tag,[+8]=ptr,[+16]=len. Push high→low
|
||||
// so pop drains tag first into arg-reg[0].
|
||||
// str IS []u8: slot 32 [+0]=tag,[+8]=ptr,[+16]=len,
|
||||
// [+24]=cap — same shape as the slice arm above. Push
|
||||
// cap, len, ptr, tag high→low so pop drains tag first
|
||||
// into arg-reg[0] (#1/Phase 3).
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -468,9 +471,12 @@ fn pushargsrev(c: *cgen, arg: *node, param: *node) i32 = {
|
||||
return rest + 3;
|
||||
};
|
||||
if (nodeisstr(c, arg)) {
|
||||
// str IS []u8: cgexpr left (AX=ptr, BX=len, CX=cap). Push
|
||||
// the triple, same as the slice arm above (#1/Phase 3).
|
||||
emitline("\tPUSHQ\tCX\n");
|
||||
emitline("\tPUSHQ\tBX\n");
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
return rest + 2;
|
||||
return rest + 3;
|
||||
};
|
||||
// #21: CALL returning a tagged-union — the aistagged guard
|
||||
// above kept us out of the widening path. Push the tagged-
|
||||
@@ -2320,7 +2326,7 @@ fn cgloadtaggedfield(c: *cgen, basereg: str, foff: i32, slot_sz: i32) void = {
|
||||
// would need a reversed direction we don't currently emit).
|
||||
// - struct src (literal or ident): zero slot, write fields at +8+foff,
|
||||
// tag last.
|
||||
// - str src: tag@+0, ptr@+8, len@+16.
|
||||
// - str src: tag@+0, ptr@+8, len@+16, cap@+24 (str IS []u8, #1/Phase 3).
|
||||
// - scalar src: tag@+0, value@+8.
|
||||
fn cgwidentaggedstore(c: *cgen, dst: *tinfo, src: *node,
|
||||
basereg: str, slot_off: i32, slot_sz: i32) void = {
|
||||
@@ -2538,12 +2544,18 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
} else { if (isstrtype(c, fi.tnode)) {
|
||||
// str IS []u8: 3-word field
|
||||
// (ptr,len,cap) from cgexpr's
|
||||
// AX/BX/CX (#1/Phase 3).
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((slot_off + 8 + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 8 + fi.foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
let sop: str = fieldstoreop(c, fi);
|
||||
emitline("\t");
|
||||
@@ -2601,7 +2613,9 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
return;
|
||||
};
|
||||
};
|
||||
// Str payload.
|
||||
// str IS []u8: AX=ptr, BX=len, CX=cap from cgexpr. Slot layout
|
||||
// tag@+0, ptr@+8, len@+16, cap@+24 — same 32B shape as the slice
|
||||
// payload below (#1/Phase 3).
|
||||
if (nodeisstr(c, src)) {
|
||||
cgexpr(c, src);
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
@@ -2610,6 +2624,9 @@ fn cgwidentaggedstorebp(c: *cgen, dst: *tinfo, src: *node, slot_off: i32, slot_s
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((slot_off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((slot_off + 24): i64);
|
||||
emitline("(BP)\n");
|
||||
let tag: i32 = taggedvariantindext(c, dt, src);
|
||||
if (tag < 0) { tag = 0; };
|
||||
emitline("\tMOVQ\t$");
|
||||
@@ -2837,11 +2854,15 @@ export fn dotchainresolve(c: *cgen, n: *node,
|
||||
for (ft != nil && ft.kind == tykind.TY_NAMED) { ft = ft.under; };
|
||||
if (ft == nil) { return false; };
|
||||
if (ft.kind == tykind.TY_STR) {
|
||||
// str IS []u8: .cap is the third header word, same as
|
||||
// the TY_SLICE leaf below — cstage treats str≡slice for
|
||||
// .ptr/.len/.cap (cmd/w6c/cgen.c:2478) (#1/Phase 3, #11).
|
||||
if (i != 1) { return false; };
|
||||
let pseudo: str = stk[0].str;
|
||||
let delta: i32 = -1;
|
||||
if (streq(pseudo, "ptr")) { delta = 0; }
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }; };
|
||||
else { if (streq(pseudo, "len")) { delta = 8; }
|
||||
else { if (streq(pseudo, "cap")) { delta = 16; }; }; };
|
||||
if (delta < 0) { return false; };
|
||||
*outtotaloff = *outtotaloff + foff;
|
||||
*outslicedelta = delta;
|
||||
@@ -3154,6 +3175,44 @@ fn cgstructlitfill(c: *cgen, si: *structinfo, lit: *node,
|
||||
fi = nil;
|
||||
} else if (callwhole) {
|
||||
fi = nil;
|
||||
} else if (isstrtype(c, fi.tnode)) {
|
||||
// str IS []u8: 3-word field (ptr,len,cap).
|
||||
// cgexpr leaves AX/BX/CX; for non-BP modes
|
||||
// the dst base goes in DX to dodge BX=len /
|
||||
// CX=cap (the generic store reloads BX, which
|
||||
// would clobber len) (#1/Phase 3).
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
if (mode == 0) {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((disp + fi.foff): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((disp + fi.foff + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((disp + fi.foff + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
if (mode == 1) {
|
||||
emitline("\tMOVQ\t");
|
||||
emitoff(srcoff: i64);
|
||||
emitline("(BP), DX\n");
|
||||
} else {
|
||||
emitline("\tLEAQ\t");
|
||||
emitsymname(c, srcname);
|
||||
emitline("(SB), DX\n");
|
||||
};
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitdispreg((disp + fi.foff): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitdispreg((disp + fi.foff + 8): i64, "DX");
|
||||
emitline("\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitdispreg((disp + fi.foff + 16): i64, "DX");
|
||||
emitline("\n");
|
||||
};
|
||||
fi = nil;
|
||||
} else {
|
||||
cgexpr(c, fieldnode.lhs);
|
||||
// For non-BP modes, cgexpr just clobbered
|
||||
|
||||
Reference in New Issue
Block a user