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:
288
cmd/w6c/cgen.c
288
cmd/w6c/cgen.c
@@ -1399,10 +1399,14 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||
? ftype->under : ftype;
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
/* str IS []u8: 3-word field (ptr,len,cap)
|
||||
* from cgexpr's AX/BX/CX (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, write_off + 8 + (int)foff + 16));
|
||||
continue;
|
||||
}
|
||||
int op = A_MOVQ;
|
||||
@@ -1417,11 +1421,14 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* str payload: AX=ptr, BX=len from cgexpr. */
|
||||
/* 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 (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, write_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, write_off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, write_off + 24));
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
ins2(c, A_MOVQ, aimm(tag < 0 ? 0 : tag),
|
||||
amem(D_BP, write_off + 0));
|
||||
@@ -1511,8 +1518,11 @@ cg_widen_tagged_push(Cg *c, Local **locals_p, Type *dst, Node *src, int sz)
|
||||
int tag = cg_tag_for_variant(du, st);
|
||||
if (tag < 0) tag = 0;
|
||||
if (type_isstr(st) || (su && su->kind == TY_STR)) {
|
||||
/* slot 24: [+0]=tag, [+8]=ptr, [+16]=len. Push len,
|
||||
* ptr, tag (high→low so pop drains tag first). */
|
||||
/* str IS []u8: slot 32 [+0]=tag, [+8]=ptr, [+16]=len,
|
||||
* [+24]=cap — same shape as the slice arm below. Push
|
||||
* cap, len, ptr, tag (high→low so pop drains tag first)
|
||||
* (#1/Phase 3). */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr */
|
||||
ins2(c, A_MOVQ, aimm(tag), areg(D_AX));
|
||||
@@ -1731,6 +1741,35 @@ cg_structlit_fill(Cg *c, Local **locals_p, Type *lu, Node *lit,
|
||||
}
|
||||
continue;
|
||||
}
|
||||
/* 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 below reloads BX, which
|
||||
* would clobber len) (#1/Phase 3). */
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
if (mode == DST_BP) {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, disp + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, disp + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, disp + (int)foff + 16));
|
||||
} else {
|
||||
if (mode == DST_PTR_LOCAL)
|
||||
ins2(c, A_MOVQ, amem(D_BP, srcoff),
|
||||
areg(D_DX));
|
||||
else
|
||||
ins2(c, A_LEAQ, masym(c, name),
|
||||
areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_DX, disp + (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_DX, disp + (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, disp + (int)foff + 16));
|
||||
}
|
||||
continue;
|
||||
}
|
||||
cgexpr(c, f->lhs, *locals_p);
|
||||
/* For non-BP modes, cgexpr just clobbered BX; reload it
|
||||
* before the store. */
|
||||
@@ -1784,11 +1823,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
case N_STRLIT: {
|
||||
/* result lives as the (ptr, len) pair: ptr in AX, len in BX.
|
||||
* Call sites that pass a str arg pick these up directly. */
|
||||
/* str IS []u8: the (ptr, len, cap) triple — ptr in AX, len in
|
||||
* BX, cap in CX. A static literal has no spare storage, so
|
||||
* cap = len (#1/Phase 3, task (b)). */
|
||||
const char *lab = intern_strlit(c, n->str, n->strlen);
|
||||
ins2(c, A_LEAQ, asym(lab), areg(D_AX));
|
||||
ins2(c, A_MOVQ, aimm((long long)n->strlen), areg(D_BX));
|
||||
ins2(c, A_MOVQ, aimm((long long)n->strlen), areg(D_CX));
|
||||
break;
|
||||
}
|
||||
case N_TRUE: cgexpr_int(c, 1); break;
|
||||
@@ -1802,11 +1843,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int op = op_for(n, A_MOVSD, A_MOVSS);
|
||||
ins2(c, op, amem(D_BP, off), areg(D_X0));
|
||||
} else if (node_isstr(n)) {
|
||||
/* str values flow as (AX=ptr, BX=len) so they
|
||||
* can be returned in AX:DX or pushed to the
|
||||
* call-arg stack uniformly. */
|
||||
ins2(c, A_MOVQ, amem(D_BP, off), areg(D_AX));
|
||||
/* str IS []u8: flow as (AX=ptr, BX=len, CX=cap),
|
||||
* mirroring the slice local load below (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 8), areg(D_BX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, off + 16), areg(D_CX));
|
||||
} else if (node_isslice(n)) {
|
||||
/* slice values flow as (AX=ptr, BX=len, CX=cap)
|
||||
* — mirror the global-slice load so a slice
|
||||
@@ -1864,23 +1905,26 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ,
|
||||
aimm((long long)s->len),
|
||||
areg(D_BX));
|
||||
/* str IS []u8: cap = len for a static
|
||||
* def literal (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ,
|
||||
aimm((long long)s->len),
|
||||
areg(D_CX));
|
||||
goto ident_done;
|
||||
}
|
||||
}
|
||||
if (let_islet(n->str)
|
||||
&& (let_isstr(n->type) || let_isslice(n->type))) {
|
||||
/* Top-level str/slice global: load each half
|
||||
/* Top-level str/slice global: load each word
|
||||
* via its address (the asm has no `name+8(SB)`
|
||||
* operand form). Slice has a third 8B (cap)
|
||||
* — the address holder CX gets overwritten by
|
||||
* the cap as the last step, after we no longer
|
||||
* need it. */
|
||||
int is_slice = let_isslice(n->type);
|
||||
* operand form). str IS []u8 now — both carry a
|
||||
* third 8B (cap); the address holder CX gets
|
||||
* overwritten by the cap as the last step, after
|
||||
* we no longer need it (#1/Phase 3). */
|
||||
ins2(c, A_LEAQ, masym(c, n->str), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 0), areg(D_AX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 8), areg(D_BX));
|
||||
if (is_slice)
|
||||
ins2(c, A_MOVQ, amem(D_CX, 16), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_CX, 16), areg(D_CX));
|
||||
goto ident_done;
|
||||
}
|
||||
if (let_islet(n->str) && let_isfloat(n->type)) {
|
||||
@@ -2539,27 +2583,31 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
int is_global = (boff == 0 && !via_ptr
|
||||
&& let_islet(base->str));
|
||||
int foff = (int)f->offset;
|
||||
/* str-typed field: rhs cgexpr leaves (AX=ptr, BX=len);
|
||||
* store both halves at field+0 and field+8. The 8/16
|
||||
* trailing-padding bytes are left untouched, which
|
||||
* matches the let-init shape elsewhere in cgen. Only
|
||||
* plain `=` is wired; compound on a str field is not
|
||||
* meaningful. */
|
||||
/* str IS []u8: rhs cgexpr leaves (AX=ptr, BX=len,
|
||||
* CX=cap); store all three at field+0/+8/+16,
|
||||
* mirroring the slice-field arm below. Address
|
||||
* scratch must dodge CX (holds cap), so via_ptr/
|
||||
* is_global stage the struct base in DX (#1/Phase 3).
|
||||
* Only plain `=` is wired; compound on a str field is
|
||||
* not meaningful. */
|
||||
Type *str_fu = (f->type && f->type->kind == TY_NAMED)
|
||||
? f->type->under : f->type;
|
||||
if (n->op == TK_ASSIGN && str_fu && str_fu->kind == TY_STR) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (via_ptr) {
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16));
|
||||
} else if (is_global) {
|
||||
ins2(c, A_LEAQ, masym(c, base->str), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, foff + 8));
|
||||
ins2(c, A_LEAQ, masym(c, base->str), areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_DX, foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_DX, foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_DX, foff + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, boff + foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, boff + foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, boff + foff + 16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3259,25 +3307,34 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
? leaf_type->size : 8);
|
||||
int store_op = fldstoreop(leaf_type, fsz);
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
/* str IS []u8: store ptr/len/cap. cgexpr
|
||||
* leaves CX=cap, so the via_cx base goes in
|
||||
* DX (not CX) to avoid clobbering it — same
|
||||
* as the single-dot str field store
|
||||
* (#1/Phase 3). */
|
||||
cgexpr(c, n->rhs, locals);
|
||||
if (via_cx) {
|
||||
if (ptr_root)
|
||||
ins2(c, A_MOVQ,
|
||||
amem(D_BP, base_disp),
|
||||
areg(D_CX));
|
||||
areg(D_DX));
|
||||
else
|
||||
ins2(c, A_LEAQ,
|
||||
masym(c, cur->str),
|
||||
areg(D_CX));
|
||||
areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_CX, total_off + 0));
|
||||
amem(D_DX, total_off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_CX, total_off + 8));
|
||||
amem(D_DX, total_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, total_off + 16));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, base_disp + total_off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_BP, base_disp + total_off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_BP, base_disp + total_off + 16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -3583,10 +3640,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
if (is_arr || is_sl || is_ptr) {
|
||||
cgexpr(c, n->rhs, locals); /* AX (and BX if str) */
|
||||
/* str element: also stash len so we can store both */
|
||||
if (elem_is_str)
|
||||
ins1(c, A_PUSHQ, areg(D_BX));
|
||||
cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len,CX=cap if str) */
|
||||
/* str IS []u8: stash cap+len so all three store
|
||||
* (#1/Phase 3). */
|
||||
if (elem_is_str) {
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
}
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, n->lhs->rhs, locals); /* idx → AX */
|
||||
if (esz > 1) {
|
||||
@@ -3626,9 +3686,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_ADDQ, areg(D_AX), areg(D_BX));
|
||||
ins1(c, A_POPQ, areg(D_AX)); /* value (ptr if str) */
|
||||
if (elem_is_str) {
|
||||
/* str IS []u8: store ptr/len/cap (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 8));
|
||||
ins1(c, A_POPQ, areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 16));
|
||||
break;
|
||||
}
|
||||
int store_op = fldstoreop(esub, esz);
|
||||
@@ -3678,12 +3741,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, mov, areg(D_X0), amem(D_BX, 0));
|
||||
break;
|
||||
}
|
||||
cgexpr(c, n->rhs, locals); /* AX = value (BX too if str) */
|
||||
cgexpr(c, n->rhs, locals); /* AX=ptr (BX=len, CX=cap if str) */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
if (vt && vt->kind == TY_STR) ins1(c, A_PUSHQ, areg(D_BX));
|
||||
if (vt && vt->kind == TY_STR) {
|
||||
/* str IS []u8: also stash len + cap across the
|
||||
* pointer eval, which clobbers BX/CX (#1/Phase 3). */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||
}
|
||||
cgexpr(c, n->lhs->lhs, locals); /* AX = pointer */
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_BX));
|
||||
if (vt && vt->kind == TY_STR) {
|
||||
ins1(c, A_POPQ, areg(D_CX)); /* cap */
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BX, 16));
|
||||
ins1(c, A_POPQ, areg(D_CX)); /* len */
|
||||
ins1(c, A_POPQ, areg(D_AX)); /* ptr */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BX, 0));
|
||||
@@ -3752,11 +3822,11 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* Plain `name = strexpr;` for a str-typed local. cgexpr leaves
|
||||
* (AX=ptr, BX=len); store both halves at off+0 and off+8.
|
||||
* Mirrors the let-init shape so reassignment doesn't truncate.
|
||||
* Top-level str globals follow the same shape but go through
|
||||
* &name(SB) since the asm has no `name+8(SB)` operand form. */
|
||||
/* Plain `name = strexpr;` for a str-typed local. str IS []u8:
|
||||
* cgexpr leaves (AX=ptr, BX=len, CX=cap); store all three at
|
||||
* off+0/+8/+16, identical to the slice arm below. Top-level
|
||||
* str globals go through &name(SB) → DI scratch (CX holds cap)
|
||||
* since the asm has no `name+8(SB)` operand form (#1/Phase 3). */
|
||||
if (n->lhs && n->lhs->kind == N_IDENT && n->op == TK_ASSIGN
|
||||
&& n->lhs->type) {
|
||||
Type *lt = n->lhs->type;
|
||||
@@ -3767,14 +3837,17 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||
break;
|
||||
}
|
||||
if (let_islet(n->lhs->str)) {
|
||||
cgexpr(c, n->rhs, locals);
|
||||
ins2(c, A_MOVQ, areg(D_CX), areg(D_DI));
|
||||
ins2(c, A_LEAQ, masym(c, n->lhs->str),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_CX, 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_CX, 8));
|
||||
ins2(c, A_MOVQ, areg(D_DI), amem(D_CX, 16));
|
||||
break;
|
||||
}
|
||||
break;
|
||||
@@ -4176,17 +4249,20 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
amem(D_BX, (int)foff));
|
||||
continue;
|
||||
}
|
||||
/* str-typed field: cgexpr leaves (AX=ptr, BX=len).
|
||||
* Route the heap base through CX so both halves
|
||||
* survive — using BX would clobber len. */
|
||||
/* str IS []u8: cgexpr leaves (AX=ptr, BX=len,
|
||||
* CX=cap). Route the heap base through DX so all
|
||||
* three survive — CX now holds cap, BX holds len
|
||||
* (#1/Phase 3). */
|
||||
Type *fu = (ftype && ftype->kind == TY_NAMED)
|
||||
? ftype->under : ftype;
|
||||
if (fu && fu->kind == TY_STR) {
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_CX));
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_DX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_CX, (int)foff + 0));
|
||||
amem(D_DX, (int)foff + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
amem(D_CX, (int)foff + 8));
|
||||
amem(D_DX, (int)foff + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
amem(D_DX, (int)foff + 16));
|
||||
continue;
|
||||
}
|
||||
ins2(c, A_MOVQ, amem(D_SP, 0), areg(D_BX));
|
||||
@@ -4606,6 +4682,10 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_SUBQ, aimm(8), areg(D_SP));
|
||||
ins2(c, A_MOVSD, areg(D_X0), amem(D_SP, 0));
|
||||
} else if (node_isstr(args[i])) {
|
||||
/* str IS []u8: cgexpr left (AX=ptr, BX=len,
|
||||
* CX=cap). Push the triple, same as slice
|
||||
* (#1/Phase 3). */
|
||||
ins1(c, A_PUSHQ, areg(D_CX)); /* cap */
|
||||
ins1(c, A_PUSHQ, areg(D_BX)); /* len */
|
||||
ins1(c, A_PUSHQ, areg(D_AX)); /* ptr — top */
|
||||
} else if (node_isslice(args[i])) {
|
||||
@@ -4711,7 +4791,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
stackslots++; /* leave on stack */
|
||||
}
|
||||
} else if (node_isstr(args[i])) {
|
||||
for (int k = 0; k < 2; k++) {
|
||||
/* str IS []u8: 3-word arg, same as slice (#1/Phase 3). */
|
||||
for (int k = 0; k < 3; k++) {
|
||||
if (ii < 6)
|
||||
ins1(c, A_POPQ, areg(sysv_argregs[ii++]));
|
||||
else
|
||||
@@ -4824,9 +4905,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
/* SysV: caller cleans stack args. */
|
||||
if (stackslots > 0)
|
||||
ins2(c, A_ADDQ, aimm(stackslots * 8), areg(D_SP));
|
||||
/* If callee returns a str (16B → AX:DX per SysV), shuffle
|
||||
* len from DX into BX so str values stay in (AX, BX). */
|
||||
if (node_isstr(n)) ins2(c, A_MOVQ, areg(D_DX), areg(D_BX));
|
||||
/* str IS []u8: callee returns AX=ptr, BX=len, CX=cap —
|
||||
* same as a slice, no receive-side shuffle (#1/Phase 3). */
|
||||
break;
|
||||
}
|
||||
case N_MATCH: {
|
||||
@@ -5089,8 +5169,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
ins0(c, A_RET);
|
||||
label(c, cont);
|
||||
if (success_is_str)
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success value arrives in the tagged
|
||||
* ABI as DX=ptr, CX=len, R8=cap (slot 32B). Move len
|
||||
* out before cap overwrites CX (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_R8), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -5126,8 +5211,13 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
ins2(c, A_MOVQ, aimm(60), areg(D_AX));
|
||||
ins0(c, A_SYSCALL);
|
||||
label(c, cont);
|
||||
if (success_is_str)
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
||||
* (slot 32B). Move len out before cap clobbers CX
|
||||
* (#1/Phase 3). */
|
||||
ins2(c, A_MOVQ, areg(D_CX), areg(D_BX));
|
||||
ins2(c, A_MOVQ, areg(D_R8), areg(D_CX));
|
||||
}
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
break;
|
||||
}
|
||||
@@ -6437,13 +6527,15 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
break;
|
||||
}
|
||||
}
|
||||
/* str initialiser: cgexpr produces (AX=ptr, BX=len).
|
||||
* #43: gate width via ty_str->size so a future str-layout
|
||||
* bump (#1) propagates without touching this site. */
|
||||
/* str IS []u8: cgexpr produces (AX=ptr, BX=len, CX=cap);
|
||||
* store all three, same as the slice initialiser below.
|
||||
* #43 gate via ty_str->size already tracks the 24B bump
|
||||
* (#1/Phase 3). */
|
||||
if (n->rhs && type_isstr(lt) && sz == (int)ty_str->size) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_BX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||
break;
|
||||
}
|
||||
/* 2-tuple initialiser from a function call: SysV returns
|
||||
@@ -6455,13 +6547,14 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||
break;
|
||||
}
|
||||
/* 24B tuple initialiser for `(scalar, str)` / `(str, scalar)`.
|
||||
* Per the AX:DX:CX return convention: AX = scalar elem,
|
||||
* DX = str.ptr, CX = str.len. The slot is laid out positionally
|
||||
* (e0 at +0, e1 at +8 for scalars; str takes 16B starting at
|
||||
* its position), so we route each register to the slot dictated
|
||||
* by the element's type, not by AX/DX position. */
|
||||
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 24) {
|
||||
/* 32B tuple initialiser for `(scalar, str)` / `(str, scalar)`.
|
||||
* Per the AX:DX:CX:R8 return convention: AX = scalar elem,
|
||||
* DX = str.ptr, CX = str.len, R8 = str.cap. The slot is laid
|
||||
* out positionally (str takes 24B at its position), so we route
|
||||
* each register to the slot dictated by the element's type, not
|
||||
* by AX/DX position. str IS []u8 (24B) → 32B tuple (#1/Phase 3,
|
||||
* task #5). */
|
||||
if (n->rhs && lu && lu->kind == TY_TUPLE && sz == 32) {
|
||||
Tparam *p0 = lu->params;
|
||||
Tparam *p1 = p0 ? p0->next : NULL;
|
||||
Type *t0 = p0 ? p0->type : NULL;
|
||||
@@ -6473,15 +6566,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
if (e0_str ^ e1_str) {
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
if (e0_str) {
|
||||
/* layout: str@+0 (16B), scalar@+16. */
|
||||
/* layout: str@+0 (24B), scalar@+24. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 24));
|
||||
} else {
|
||||
/* layout: scalar@+0 (8B), str@+8 (16B). */
|
||||
/* layout: scalar@+0 (8B), str@+8 (24B). */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off + 0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off + 8));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off + 16));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off + 24));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -6846,15 +6941,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_DX));
|
||||
} else if (type_isstr(vt)) {
|
||||
/* 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). */
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
areg(D_R8));
|
||||
ins2(c, A_MOVQ, areg(D_BX),
|
||||
areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_DX));
|
||||
/* str fills DX,CX. Zero R8 if dst
|
||||
* slot covers slot+24. */
|
||||
if (rsz > 24)
|
||||
ins2(c, A_MOVQ, aimm(0),
|
||||
areg(D_R8));
|
||||
} else {
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
areg(D_DX));
|
||||
@@ -7140,22 +7237,28 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
}
|
||||
}
|
||||
if (n->lhs && node_isstr(n->lhs)) {
|
||||
cgexpr(c, n->lhs, *locals); /* AX=ptr, BX=len */
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_DX));
|
||||
/* str IS []u8: AX=ptr, BX=len, CX=cap from cgexpr —
|
||||
* no AX:DX shuffle, same as a slice (#1/Phase 3). */
|
||||
cgexpr(c, n->lhs, *locals);
|
||||
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
|
||||
ins1(c, A_POPQ, areg(D_BP));
|
||||
ins0(c, A_RET);
|
||||
break;
|
||||
}
|
||||
if (n->lhs && n->lhs->kind == N_TUPLE) {
|
||||
/* 2-tuple ABI:
|
||||
/* 2-tuple ABI, word-indexed AX→DX→CX→R8 (the SAME
|
||||
* register sequence as the tagged-union return; the
|
||||
* tuple just fills it positionally):
|
||||
* (scalar, scalar) — AX = e0, DX = e1. (16B, fits SysV.)
|
||||
* (scalar, str) — AX = scalar elem,
|
||||
* DX = str.ptr, CX = str.len. (24B custom.)
|
||||
* (scalar, str) — AX = scalar elem, DX = str.ptr,
|
||||
* CX = str.len, R8 = str.cap. (32B.)
|
||||
* (str, scalar) — same regs, type-keyed not position-keyed.
|
||||
*
|
||||
* The 24B convention mirrors the existing tagged-union return
|
||||
* (AX:DX:CX); receive sites destructure off the same regs. */
|
||||
* str IS []u8 (24B), so a (scalar, str) tuple is 32B and
|
||||
* rides AX:DX:CX:R8 — the cap is the 4th word, matching the
|
||||
* tagged-union return that already uses R8 for slot+24
|
||||
* (#1/Phase 3, task #5). Receive sites destructure off the
|
||||
* same regs. */
|
||||
Node *e0 = n->lhs->list;
|
||||
Node *e1 = e0 ? e0->next : NULL;
|
||||
if (e1 && e1->next == NULL) {
|
||||
@@ -7166,7 +7269,8 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
Node *scaln = e0_is_str ? e1 : e0;
|
||||
cgexpr(c, scaln, *locals); /* AX = scalar */
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
cgexpr(c, strn, *locals); /* AX=ptr, BX=len */
|
||||
cgexpr(c, strn, *locals); /* AX=ptr, BX=len, CX=cap */
|
||||
ins2(c, A_MOVQ, areg(D_CX), areg(D_R8));
|
||||
ins2(c, A_MOVQ, areg(D_BX), areg(D_CX));
|
||||
ins2(c, A_MOVQ, areg(D_AX), areg(D_DX));
|
||||
ins1(c, A_POPQ, areg(D_AX));
|
||||
@@ -7346,11 +7450,12 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
case N_MLET: {
|
||||
/* eval rhs; consume the per-type return-ABI registers.
|
||||
* (scalar, scalar) — AX → l0, DX → l1.
|
||||
* (scalar, str) — AX → scalar slot, (DX, CX) → str slot
|
||||
* as (.ptr, .len). Position-agnostic.
|
||||
* (scalar, str) — AX → scalar slot, (DX, CX, R8) → str slot
|
||||
* as (.ptr, .len, .cap). Position-agnostic.
|
||||
* Local sizing comes from each l->type so the str slot gets
|
||||
* the full 16B; without this, only DX would land and the
|
||||
* len half (CX) would have nowhere to go. */
|
||||
* the full 24B; without this, only DX would land and the
|
||||
* len/cap halves (CX/R8) would have nowhere to go.
|
||||
* str IS []u8 (24B): the cap rides R8 (#1/Phase 3, task #5). */
|
||||
cgexpr(c, n->rhs, *locals);
|
||||
Node *l0 = n->list;
|
||||
Node *l1 = l0 ? l0->next : NULL;
|
||||
@@ -7368,15 +7473,17 @@ cgstmt(Cg *c, Node *n, Local **locals, int *frame)
|
||||
int off0 = localoff(c, locals, l0->str, sz0, frame);
|
||||
int off1 = localoff(c, locals, l1->str, sz1, frame);
|
||||
if (s0_is_str) {
|
||||
/* l0 is str: ptr=DX, len=CX. l1 is scalar: l1 = AX. */
|
||||
/* l0 is str: ptr=DX, len=CX, cap=R8. l1 scalar = AX. */
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off0 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off0 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off0 + 16));
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off1));
|
||||
} else {
|
||||
/* l0 is scalar; l1 is str. */
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, off0));
|
||||
ins2(c, A_MOVQ, areg(D_DX), amem(D_BP, off1 + 0));
|
||||
ins2(c, A_MOVQ, areg(D_CX), amem(D_BP, off1 + 8));
|
||||
ins2(c, A_MOVQ, areg(D_R8), amem(D_BP, off1 + 16));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -7554,10 +7661,11 @@ cgfn(Cg *c, FILE *out, Node *fn)
|
||||
* offsets, no spill needed. */
|
||||
int struct_eb = is_struct ? ((pu->size > 8) ? 2 : 1) : 0;
|
||||
int tagged_eb = is_tagged ? (tagged_sz / 8) : 0;
|
||||
int eightbytes = slice ? 3 :
|
||||
(is_str ? 2 :
|
||||
/* str IS []u8: 3 eightbytes (ptr,len,cap), same as a slice
|
||||
* — the caller pushes the triple (#1/Phase 3). */
|
||||
int eightbytes = (slice || is_str) ? 3 :
|
||||
(is_struct ? struct_eb :
|
||||
(is_tagged ? tagged_eb : 1)));
|
||||
(is_tagged ? tagged_eb : 1));
|
||||
int regs_left = isf ? (8 - fargi) : (6 - argi);
|
||||
if (regs_left >= eightbytes) {
|
||||
/* #60: route slice/str slot widths through Type.size SSoT
|
||||
|
||||
@@ -60,8 +60,10 @@ typesinit(Arena *a)
|
||||
ty_uintptr= prim(a, TY_UINTPTR,"uintptr", 8, 8);
|
||||
ty_f32 = prim(a, TY_F32, "f32", 4, 4);
|
||||
ty_f64 = prim(a, TY_F64, "f64", 8, 8);
|
||||
/* str is { *u8, len } — 16 bytes on amd64. ABI: pointer + u64. */
|
||||
ty_str = prim(a, TY_STR, "str", 16, 8); /* sizelint-ok: SSoT for ty_str (#64) */
|
||||
/* str IS []u8: { *u8, len, cap } — 24 bytes, 3-reg ABI (#1/Phase 3).
|
||||
* Size sourced from the slice SSoT (type_slice) so str and []u8 can
|
||||
* never drift; no second hardcoded 24. */
|
||||
ty_str = prim(a, TY_STR, "str", type_slice(a, ty_u8)->size, 8);
|
||||
ty_err = prim(a, TY_ERR, "<err>", 0, 1);
|
||||
ty_never = prim(a, TY_NEVER, "never", 0, 1);
|
||||
/* #29: predeclared `type nomem = !void;`. NAMED so variant_match
|
||||
|
||||
Reference in New Issue
Block a user