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:
@@ -111,12 +111,16 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
rundefers(c);
|
||||
let rhs: *node = n.lhs;
|
||||
if (rhs != nil) {
|
||||
// Tuple return `return a, b;`:
|
||||
// Tuple return `return a, b;`, word-indexed AX→DX→CX→R8 (the
|
||||
// SAME register sequence as the tagged-union return below; the
|
||||
// tuple just fills it positionally):
|
||||
// (scalar, scalar) — AX = v0, DX = v1.
|
||||
// (scalar, str) / (str, scalar) — AX = scalar elem,
|
||||
// DX = str.ptr, CX = str.len.
|
||||
// 24B convention mirrors the tagged-union return below; receive
|
||||
// sites destructure off the same regs regardless of position.
|
||||
// DX = str.ptr, CX = str.len, R8 = str.cap.
|
||||
// str IS []u8 (24B) → 32B tuple; cap rides R8, matching the
|
||||
// tagged-union return that already uses R8 for slot+24
|
||||
// (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
// same regs regardless of position.
|
||||
if (rhs.kind == nkind.N_TUPLE) {
|
||||
let v: *node = rhs.list;
|
||||
if (v != nil) {
|
||||
@@ -131,6 +135,7 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
cgexpr(c, scaln);
|
||||
emitline("\tPUSHQ\tAX\n");
|
||||
cgexpr(c, strn);
|
||||
emitline("\tMOVQ\tCX, R8\n");
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
emitline("\tPOPQ\tAX\n");
|
||||
@@ -283,12 +288,12 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
} else { if (nodeisstr(c, rhs)) {
|
||||
// str IS []u8: cgexpr leaves (AX=ptr, BX=len,
|
||||
// CX=cap). Same shuffle as the slice arm above —
|
||||
// DX=ptr, CX=len, R8=cap (#1/Phase 3).
|
||||
emitline("\tMOVQ\tCX, R8\n");
|
||||
emitline("\tMOVQ\tBX, CX\n");
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// str fills DX,CX. Zero R8 if dst covers slot+24.
|
||||
if (rsz > 24) {
|
||||
emitline("\tMOVQ\t$0, R8\n");
|
||||
};
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, DX\n");
|
||||
// scalar fills DX only. Zero CX / R8 if dst
|
||||
@@ -544,11 +549,8 @@ fn cgreturn(c: *cgen, n: *node) void = {
|
||||
};
|
||||
emitline("\tMOVQ\t$0, AX\n");
|
||||
};
|
||||
// SysV: 16-byte aggregates (str, 2-tuple) return in (AX, DX).
|
||||
// cgexpr leaves str in (AX, BX); shuffle BX→DX.
|
||||
if (isstrtype(c, c.fnret)) {
|
||||
emitline("\tMOVQ\tBX, DX\n");
|
||||
};
|
||||
// str IS []u8: cgexpr leaves AX=ptr, BX=len, CX=cap — str now
|
||||
// returns exactly like a slice, no AX:DX shuffle (#1/Phase 3).
|
||||
emitline("\tMOVQ\tBP, SP\n");
|
||||
emitline("\tPOPQ\tBP\n");
|
||||
emitline("\tRET\n");
|
||||
@@ -719,11 +721,13 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
};
|
||||
// 24B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX return
|
||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len.
|
||||
// Layout is positional, so we route each register to the
|
||||
// slot dictated by element type, not by AX/DX position.
|
||||
// 32B tuple init for `let t: (scalar, str) = call()` /
|
||||
// `let t: (str, scalar) = call()`. Per the AX:DX:CX:R8 return
|
||||
// convention: AX = scalar elem, DX = str.ptr, CX = str.len,
|
||||
// R8 = str.cap. Layout is positional (str takes 24B at its
|
||||
// position), so we route each register to the slot dictated by
|
||||
// element type, not by AX/DX position. str IS []u8 (24B) → 32B
|
||||
// tuple (#1/Phase 3, task #5).
|
||||
if (n.lhs != nil) {
|
||||
if (n.lhs.kind == nkind.N_TTUPLE) {
|
||||
let p0: *node = n.lhs.list;
|
||||
@@ -746,9 +750,12 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff((off + 24): i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
@@ -759,6 +766,9 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off + 24): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
@@ -1049,15 +1059,19 @@ fn cglet(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off: i64);
|
||||
emitline("(BP)\n");
|
||||
// str init: cgexpr also leaves len in BX; store both.
|
||||
// str IS []u8: cgexpr leaves (ptr,len,cap) in AX/BX/CX; store
|
||||
// all three, same as the slice arm below (#1/Phase 3).
|
||||
// #60: gate by kind too — under #1's str=24 bump, sizeof(str)
|
||||
// and sizeof(slice) collide, so a bare `sz ==` check fires
|
||||
// both branches for one let. Mirrors cstage cgen.c:6439's
|
||||
// both branches for one let. Mirrors cstage cgen.c's
|
||||
// `type_isstr(lt) && sz == ty_str->size` shape.
|
||||
if (isstrtype(c, tn) && sz == primtypesize("str"): i32) {
|
||||
emitline("\tMOVQ\tBX, ");
|
||||
emitoff((off + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
// slice init: ptr/len/cap in AX/BX/CX. Same kind+size gate as
|
||||
// the str arm — without the kind check this fires on a str let
|
||||
@@ -1218,11 +1232,12 @@ fn cgmassign(c: *cgen, n: *node) void = {
|
||||
// type is taken from its explicit annotation (l.lhs) when present
|
||||
// or inferred from the called fn's return-type tuple element.
|
||||
//
|
||||
// Per the AX:DX:CX return convention (mirrors C cgen nkind.N_MLET):
|
||||
// Per the AX:DX:CX:R8 return convention (mirrors C cgen nkind.N_MLET):
|
||||
// (scalar, scalar) — AX → l0, DX → l1.
|
||||
// (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
// as (.ptr, .len). Position-agnostic — the
|
||||
// (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot
|
||||
// as (.ptr, .len, .cap). Position-agnostic — the
|
||||
// regs are routed by element type, not by AX/DX.
|
||||
// str IS []u8 (24B): cap rides R8 (#1/Phase 3, task #5).
|
||||
fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let rhs: *node = n.rhs;
|
||||
if (rhs == nil) { return; };
|
||||
@@ -1291,16 +1306,21 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
let off0: i32 = localadd(c, l0.str, sz0, t0);
|
||||
let off1: i32 = localadd(c, l1.str, sz1, t1);
|
||||
if (s0_is_str) {
|
||||
// l0 str: ptr=DX, len=CX, cap=R8. l1 scalar = AX.
|
||||
emitline("\tMOVQ\tDX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off0 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off0 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off1: i64);
|
||||
emitline("(BP)\n");
|
||||
} else {
|
||||
// l0 scalar; l1 str: ptr=DX, len=CX, cap=R8.
|
||||
emitline("\tMOVQ\tAX, ");
|
||||
emitoff(off0: i64);
|
||||
emitline("(BP)\n");
|
||||
@@ -1310,6 +1330,9 @@ fn cgmlet(c: *cgen, n: *node) void = {
|
||||
emitline("\tMOVQ\tCX, ");
|
||||
emitoff((off1 + 8): i64);
|
||||
emitline("(BP)\n");
|
||||
emitline("\tMOVQ\tR8, ");
|
||||
emitoff((off1 + 16): i64);
|
||||
emitline("(BP)\n");
|
||||
};
|
||||
c.lastwasreturn = 0;
|
||||
return;
|
||||
|
||||
Reference in New Issue
Block a user