cgen: #35/#46 Family C — tagged transport from deref/cast/unwrap sources goes mem-based, both stages
A tagged value reaching a transport consumer from a DEREF, CAST, or UNWRAP source materialized as ONE scalar word — the N_UN(STAR) arm's localloadop pulled word0 (the tag) and every cursor consumer then transported garbage payload (cs pushed stale DX, ww stored 0/garbage; divergent asm AND silent-wrong on both stages — ken f35 asm proof + ken37v D3a/D3b). Consumers × sources × sizes all wrong: arg push, let-init, assign, return, match scrutinee, as, widen — from *p at 16/24/32B, from identity/widening tagged casts, and from `?`/`!` whose success variant is itself tagged (nested box: payload words dropped). `is` and ww-match-16B passed only by stale-register luck. Fix extends the #37 mem-based machinery (26d3754) below the 32B cap instead of re-deriving: - cg_tagged_memread/taggedmemread: an N_UN(STAR) deref of a non-nullable tagged box is mem-based at ANY size — the pointer value IS the box address. The N_UN emitter skips the scalar load (joins the TY_FN/TY_ARRAY value-is-address skip); the existing size-generic memread arms in widen-store/match/as pick the source up unchanged, `is` loads the tag through the address. - arg push: the memread fatal becomes the mem-based push (words high→low from (AX)) — closes #35's word0-only push and wires the 33-48B INDEX/DOT loud as a side effect. Pop side drains via pushargsrev's returned word count, unchanged. - cg_tagged_castpeel/taggedcastpeel: tagged→tagged casts are transport-transparent; peel at the call-arg widen decision and the widen-store entry so the ident/deref arms see the carrier and the remap keys on the operand type. The identity-only subset (cg_tagged_idcastpeel) applies at is/as/match, which key variant indices on the scrutinee's own type; surviving non-identity casts die LOUD there and at the widen cursor arms (rule 7) instead of word0 garbage. The peel also wires 929's >48B memarg same-type cast row (place resolves post-peel; loud pin flips to a run row). - tryprop/tryunw: IDENT sources load the cursor from their slot, memread sources from the box address (≤32B); >32B non-call and global tagged idents die loud (rule 7). A TAGGED success variant shifts the nested box past the outer tag (twin of the #241 tuple shift) — closes the unwrap-source half (ken unw16). - wwstage alignment UP to the cstage type-keyed routes: rhstaggedabicall admits N_UN deref + N_TRYPROP/N_TRYUNW (stamped type), matchscrutt carries the N_UN stamped type (spill size + variant indices; was nil → tag-0 clamp + mis-sized spill), cgreturn routes memread sources through the widener (the fall-through wrapped the un-deref'd POINTER as payload), and pushargsrev's aistagged gate admits the deref kind. Emitters and consumers ship as ONE commit: they share the memread contract, and splitting opens a transient window where a wired emitter hands an address to an unwired consumer — the #61/#37 route-sharing fuse. The test flips ride along because they pin the flip itself: 941's two #37 deref loud-symmetry pins become run rows (the loud is now wired), 929's fail_rvalue_cast becomes memarg_idcast_peeled. No-drift bar held: ≤32B IDENT/INDEX/DOT sources emit byte-identical asm vs master4c46d3a(probe corpus nd1: ident let/match/arg, struct-field, indexed element, call, nullable, ident-widen — both stages IDENTICAL); is-on-deref is incidentally byte-identical too (the tag load moved from the emitter to the consumer). Tests: 941 grows 252→272 checks — ken's exact f35/D3a/D3b shapes, each consumer × source × size cell (16/24/32/56B, str + struct payloads), neighbor-guard row, identity-cast arg, widening-cast let (payload checked — the old cs pass was is-only luck), success-first unwrap-to-tagged + ident/deref unwrap, the 56B slice-deref let+match flips (payload-pinned), and 2 rule-7 loud pins (global tagged `?`, cast-to-third-union). Reviewer-C rows commit ken's remaining adversarial shapes (gC1 deref-wrapped cast arg, gC2 void-variant deref, gC3 slice-element-pointer deref, gC6 56B memarg-leg deref arg — that one a regression pin, already place-resolved at base), a 40B deref ARG (the 33-48B mem-push leg, silent word0 at base), and a multi-arg pop-balance row (tagged-deref arg mid-list, called twice — the original #35 1-push-2-pops symptom). At base4c46d3athe impl rows fail 53/254 (silent-wrong exits, cs≠ww asm, missing louds); the reviewer rows kill at base too (flip rows LOUD, others wrong-exit) except the gC6 pin. At HEAD 272/272 + 929 22 rows + test-unit 284. Residuals (filed separately, pre-existing): #216 success-tag divergence — error-FIRST unions emit CMPQ $1 (cs s_tag) vs CMPQ $0 (ww first-param) and ww's first-param success-type read misses the tagged shift; match-expr tagged yield is cstage-checker-rejected while ww runs it (the #34/#43 acceptance family); `as` binding a STRUCT payload at let-init stays loud ("aggregate init from unhandled rhs shape"); wwstage tagged-source arg-widen-into-WIDER-slot (the pushargsrev #21-comment out-of-scope boundary, task #55) — the deref leg of that family is now cstage-correct via the widen-store memread arm but stays wwstage-silent-wrong (joins the pre-existing INDEX leg; ident leg runs by prefix-luck under-push); truncating scalar cast as a box payload drops the conversion (task #56, both stages, untouched by the peel — scalar→scalar casts are never peeled).
This commit is contained in:
301
cmd/w6c/cgen.c
301
cmd/w6c/cgen.c
@@ -653,17 +653,69 @@ tagged_memarg_size(Type *t)
|
||||
* for an N_INDEX/N_DOT read whose box exceeds the 4-reg cursor — the
|
||||
* same mem-based class as an sret-classified call (which the #38b
|
||||
* gates key separately on cg_sret_retsize). Every cursor-spill
|
||||
* consumer must branch on this before reading AX as the tag. */
|
||||
* consumer must branch on this before reading AX as the tag.
|
||||
* Family C (#35/#46): a DEREF source is mem-based at ANY size — the
|
||||
* pointer value IS the box address, so the N_UN(STAR) emitter skips
|
||||
* the scalar load (which carried only the tag word) and the
|
||||
* consumers copy from memory. ≤32B INDEX/DOT keep the cursor
|
||||
* byte-for-byte (the #37 no-drift bar); the nullable one-word fold
|
||||
* stays a scalar deref. */
|
||||
static int
|
||||
cg_tagged_memread(Node *e)
|
||||
{
|
||||
Type *u;
|
||||
if (e == NULL || (e->kind != N_INDEX && e->kind != N_DOT)) return 0;
|
||||
if (e == NULL) return 0;
|
||||
if (e->kind == N_UN && e->op == TK_STAR) {
|
||||
u = type_chase_named(e->type);
|
||||
return u && u->kind == TY_TAGGED && !u->nullable
|
||||
&& u->size > 8;
|
||||
}
|
||||
if (e->kind != N_INDEX && e->kind != N_DOT) return 0;
|
||||
u = type_chase_named(e->type);
|
||||
if (u == NULL || u->kind != TY_TAGGED) return 0;
|
||||
return (int)u->size > TUPLE_GPCAP * 8;
|
||||
}
|
||||
|
||||
/* cg_tagged_castpeel — Family C (#35): a tagged→tagged cast is
|
||||
* transport-transparent — the operand's box IS the value; transport
|
||||
* consumers (widen-store, arg push) derive the remap from the
|
||||
* operand's type. Peeling exposes the ident/deref carrier their
|
||||
* source arms key on; cgexpr on the cast node itself collapses to
|
||||
* one word. Concrete-variant casts (`7: size`) keep their node so
|
||||
* variant-tag lookup sees the cast's type. The nullable one-word
|
||||
* fold never spills a cursor — excluded. */
|
||||
static Node *
|
||||
cg_tagged_castpeel(Node *e)
|
||||
{
|
||||
while (e && e->kind == N_CAST && e->lhs) {
|
||||
Type *cu = type_chase_named(e->type);
|
||||
Type *iu = type_chase_named(e->lhs->type);
|
||||
if (cu == NULL || cu->kind != TY_TAGGED || cu->nullable)
|
||||
break;
|
||||
if (iu == NULL || iu->kind != TY_TAGGED || iu->nullable)
|
||||
break;
|
||||
e = e->lhs;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/* cg_tagged_idcastpeel — the IDENTITY-only subset of the peel for
|
||||
* consumers that key variant indices on the scrutinee's own type
|
||||
* (is/as/match): same-type casts are no-ops there, but a WIDENING
|
||||
* cast changes the tag numbering and must NOT be peeled — those die
|
||||
* loud at the consumer's cast catch-all instead. */
|
||||
static Node *
|
||||
cg_tagged_idcastpeel(Node *e)
|
||||
{
|
||||
while (e && e->kind == N_CAST && e->lhs
|
||||
&& type_eq(e->type, e->lhs->type)) {
|
||||
Type *cu = type_chase_named(e->type);
|
||||
if (cu == NULL || cu->kind != TY_TAGGED) break;
|
||||
e = e->lhs;
|
||||
}
|
||||
return e;
|
||||
}
|
||||
|
||||
/* type_isnullable — TY_TAGGED with the (*T | void) one-word fold. */
|
||||
static int
|
||||
type_isnullable(Type *t)
|
||||
@@ -2380,6 +2432,12 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
if (via_outer) goto copy_out;
|
||||
return;
|
||||
}
|
||||
/* Family C (#35): a tagged→tagged cast is transport-transparent
|
||||
* — peel it so the ident/deref/memread source arms below see the
|
||||
* carrier and the remap keys on the operand's type. Pre-#35 the
|
||||
* cast node fell to the cursor arm, whose cgexpr collapsed to
|
||||
* word0 (`let w: un3 = (v: un3)` stored garbage payload). */
|
||||
src = cg_tagged_castpeel(src);
|
||||
/* `expr: TaggedAlias` where the cast's destination IS the union
|
||||
* itself is a widening, not a re-interpret. cgexpr on the cast
|
||||
* leaves the inner expression's register shape (str: AX=ptr,
|
||||
@@ -2475,6 +2533,14 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
fatal("#37: >32B tagged payload from "
|
||||
"a non-mem-based source (kind %d) "
|
||||
"unwired (rule 7)", src->kind);
|
||||
/* Family C catch-all (rule 7): a tagged
|
||||
* cast surviving cg_tagged_castpeel (cast
|
||||
* to a THIRD union) has no cursor — loud,
|
||||
* not word0 garbage. */
|
||||
if (src->kind == N_CAST)
|
||||
fatal("#35: tagged cast source shape "
|
||||
"unwired at the widen nested arm "
|
||||
"(rule 7)");
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 8));
|
||||
@@ -2532,6 +2598,12 @@ cg_widen_tagged_store(Cg *c, Local **locals_p, Type *dst, Node *src,
|
||||
fatal("#37: >32B tagged source of a non-mem-"
|
||||
"based kind (%d) unwired (rule 7)",
|
||||
src->kind);
|
||||
/* Family C catch-all (rule 7): a tagged cast
|
||||
* surviving cg_tagged_castpeel (cast to a THIRD
|
||||
* union) has no cursor — loud, not word0 garbage. */
|
||||
if (src->kind == N_CAST)
|
||||
fatal("#35: tagged cast source shape unwired "
|
||||
"at the widen subset arm (rule 7)");
|
||||
cgexpr(c, src, *locals_p);
|
||||
ins2(c, A_MOVQ, areg(D_AX),
|
||||
amem(D_BP, write_off + 0));
|
||||
@@ -4153,6 +4225,18 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
if (ru && (ru->kind == TY_FN
|
||||
|| ru->kind == TY_ARRAY))
|
||||
break;
|
||||
/* Family C (#35/#46): a tagged box behind
|
||||
* *p joins the mem-based class at ANY size
|
||||
* (cg_tagged_memread) — AX = p's value IS
|
||||
* the box address. The scalar load below
|
||||
* pulled word0 (the tag) and every cursor
|
||||
* consumer then transported garbage payload
|
||||
* words — silent-wrong on both stages (the
|
||||
* ken f35/D3a/D3b family). The nullable
|
||||
* one-word fold stays a scalar deref. */
|
||||
if (ru && ru->kind == TY_TAGGED
|
||||
&& !ru->nullable && ru->size > 8)
|
||||
break;
|
||||
}
|
||||
/* f64/f32 result rides X0 (SSE), not AX — an integer
|
||||
* MOVQ strands the value off the float ABI and the
|
||||
@@ -8258,6 +8342,15 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
}
|
||||
}
|
||||
}
|
||||
/* Family C (#35): peel tagged→tagged casts FIRST so the
|
||||
* widen decision below keys on the operand's type — an
|
||||
* identity cast (`take((v: un))`) reduces to the ident
|
||||
* fast path, a widening cast (`take((v: wider))`) trips
|
||||
* widen[i] and re-boxes with the remap. cgexpr on the
|
||||
* cast node itself collapses to one word (silent word0
|
||||
* push pre-#35). */
|
||||
for (int i = 0; i < argcount; i++)
|
||||
args[i] = cg_tagged_castpeel(args[i]);
|
||||
/* widen[i]: param is tagged and arg needs re-layout.
|
||||
* - arg is a concrete variant (str/struct/scalar) — wrap
|
||||
* in the param's slot shape.
|
||||
@@ -8696,23 +8789,31 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#38b: >32B tagged call result "
|
||||
"as a call argument unwired "
|
||||
"(#40-family follow-up)");
|
||||
/* #37 (rule 7): a 33-48B box from an
|
||||
* INDEX/DOT read is mem-based (AX = addr,
|
||||
* no cursor to push) — was silent cursor
|
||||
* truncation pre-#37; the mem-based push
|
||||
* is the #35 family. */
|
||||
if (cg_tagged_memread(args[i]))
|
||||
fatal("#37: >32B tagged arg from a "
|
||||
"mem-based read unwired (#35/"
|
||||
"#40-family follow-up)");
|
||||
int sz = tagged_arg_size(args[i]->type);
|
||||
if (sz > 24)
|
||||
ins1(c, A_PUSHQ, areg(D_R8));
|
||||
if (sz > 16)
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
if (sz > 8)
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
/* #35 (Family C): a mem-based read left the
|
||||
* box ADDRESS in AX — push the words from
|
||||
* memory high→low, the mem twin of the
|
||||
* cursor push below. Covers the any-size
|
||||
* deref source and the 33-48B INDEX/DOT
|
||||
* reads that loud-stopped here pre-#35. */
|
||||
if (cg_tagged_memread(args[i])) {
|
||||
int msz = tagged_arg_size(
|
||||
args[i]->type);
|
||||
for (int k = msz - 8; k >= 0; k -= 8) {
|
||||
ins2(c, A_MOVQ, amem(D_AX, k),
|
||||
areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
}
|
||||
} else {
|
||||
int sz = tagged_arg_size(
|
||||
args[i]->type);
|
||||
if (sz > 24)
|
||||
ins1(c, A_PUSHQ, areg(D_R8));
|
||||
if (sz > 16)
|
||||
ins1(c, A_PUSHQ, areg(D_CX));
|
||||
if (sz > 8)
|
||||
ins1(c, A_PUSHQ, areg(D_DX));
|
||||
ins1(c, A_PUSHQ, areg(D_AX));
|
||||
}
|
||||
} else if (tuparg_push) {
|
||||
/* #163: tuple ARG (param twin of #164's return).
|
||||
* cgexpr above left the tuple in the return-ABI
|
||||
@@ -9127,7 +9228,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* Nullable folded `(*T | void)`: slot is one 8B word holding
|
||||
* the pointer; null IS the void variant. Discriminator =
|
||||
* value, not a separate tag. */
|
||||
Node *s = n->lhs;
|
||||
/* Family C (#35): identity-cast peel — see the `is` twin. */
|
||||
Node *s = cg_tagged_idcastpeel(n->lhs);
|
||||
Type *st = s ? s->type : NULL;
|
||||
Type *su = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
int is_nullable = type_isnullable(st);
|
||||
@@ -9228,6 +9330,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#37: >32B tagged match scrutinee from "
|
||||
"a non-mem-based source (kind %d) unwired "
|
||||
"(rule 7)", s->kind);
|
||||
/* Family C catch-all (rule 7): a widening tagged
|
||||
* cast scrutinee has no cursor — loud. */
|
||||
if (s->kind == N_CAST && !is_nullable
|
||||
&& su && su->kind == TY_TAGGED)
|
||||
fatal("#35: tagged cast source shape unwired "
|
||||
"at match (rule 7)");
|
||||
cgexpr(c, s, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||
if (!is_nullable) {
|
||||
@@ -9370,18 +9478,54 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#38b: `?` on an sret-class call result "
|
||||
"unwired (mem-based unwrap is a #40-family "
|
||||
"follow-up)");
|
||||
/* #37 (rule 7): a >32B box read leaves AX = address, not
|
||||
* the tag the unwrap below compares. */
|
||||
if (cg_tagged_memread(n->lhs))
|
||||
fatal("#37: `?` on a >32B mem-based tagged read "
|
||||
"unwired (#40-family follow-up)");
|
||||
if (cg_sret_retsize(cg_ret_type) > 0)
|
||||
fatal("#38b: `?` propagation into a >32B tagged "
|
||||
"return unwired (sret error-propagate is a "
|
||||
"#40-family follow-up)");
|
||||
cgexpr(c, n->lhs, locals);
|
||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
/* Family C (#35/#46): non-call sources don't fill the
|
||||
* AX/DX/CX/R8 cursor the unwrap below reads — an IDENT
|
||||
* loads it from its slot, a mem-based read (deref at any
|
||||
* size, >32B INDEX/DOT) from the box address cgexpr left
|
||||
* in AX. Both were silent word0 unwraps pre-#35. >32B
|
||||
* stays loud (the cursor can't carry it; #40 family). */
|
||||
if (u && u->kind == TY_TAGGED && !u->nullable
|
||||
&& (int)u->size > TUPLE_GPCAP * 8
|
||||
&& n->lhs->kind != N_CALL)
|
||||
fatal("#37: `?` on a >32B mem-based tagged read "
|
||||
"unwired (#40-family follow-up)");
|
||||
if (n->lhs && n->lhs->kind == N_IDENT
|
||||
&& u && u->kind == TY_TAGGED && !u->nullable) {
|
||||
int boff = localfind(locals, n->lhs->str);
|
||||
int bsz = (int)u->size;
|
||||
/* rule 7: a module-level tagged `g?` has no frame
|
||||
* slot; the global cursor load is unwired. */
|
||||
if (boff == 0)
|
||||
fatal("#35: `?` on a global tagged ident "
|
||||
"unwired (rule 7)");
|
||||
if (bsz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 24),
|
||||
areg(D_R8));
|
||||
if (bsz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 16),
|
||||
areg(D_CX));
|
||||
if (bsz > 8)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 8),
|
||||
areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
||||
} else if (cg_tagged_memread(n->lhs)) {
|
||||
int bsz = (int)type_chase_named(n->lhs->type)->size;
|
||||
cgexpr(c, n->lhs, locals);
|
||||
if (bsz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 24), areg(D_R8));
|
||||
if (bsz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 16), areg(D_CX));
|
||||
if (bsz > 8)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 8), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
||||
} else
|
||||
cgexpr(c, n->lhs, locals);
|
||||
Type *r = cg_ret_type;
|
||||
if (r && r->kind == TY_NAMED) r = r->under;
|
||||
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||
@@ -9444,6 +9588,24 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cg_tagged_tuple_payload_shift(c, stu);
|
||||
break;
|
||||
}
|
||||
/* Family C (#35, unwrap source): a TAGGED success
|
||||
* variant is a NESTED box (ww keeps nested unions
|
||||
* un-flattened) riding the payload words intact —
|
||||
* shift past the outer tag so consumers see the
|
||||
* standard AX=tag cursor. The scalar MOVQ DX,AX
|
||||
* below carried only the inner tag and dropped the
|
||||
* payload (ken unw16). Nullable folds to one word
|
||||
* and stays on the scalar move. */
|
||||
if (stu && stu->kind == TY_TAGGED && !stu->nullable) {
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
if (stu->size > 8)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
areg(D_DX));
|
||||
if (stu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
areg(D_CX));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success value arrives in the tagged
|
||||
@@ -9465,13 +9627,45 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#38b: `!` on an sret-class call result "
|
||||
"unwired (mem-based unwrap is a #40-family "
|
||||
"follow-up)");
|
||||
/* #37 (rule 7): see the N_TRYPROP twin. */
|
||||
if (cg_tagged_memread(n->lhs))
|
||||
fatal("#37: `!` on a >32B mem-based tagged read "
|
||||
"unwired (#40-family follow-up)");
|
||||
cgexpr(c, n->lhs, locals);
|
||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
/* Family C (#35/#46): see the N_TRYPROP twin — ident loads
|
||||
* the cursor from its slot, a mem-based read from the box
|
||||
* address; >32B non-call stays loud (#40 family). */
|
||||
if (u && u->kind == TY_TAGGED && !u->nullable
|
||||
&& (int)u->size > TUPLE_GPCAP * 8
|
||||
&& n->lhs->kind != N_CALL)
|
||||
fatal("#37: `!` on a >32B mem-based tagged read "
|
||||
"unwired (#40-family follow-up)");
|
||||
if (n->lhs && n->lhs->kind == N_IDENT
|
||||
&& u && u->kind == TY_TAGGED && !u->nullable) {
|
||||
int boff = localfind(locals, n->lhs->str);
|
||||
int bsz = (int)u->size;
|
||||
if (boff == 0)
|
||||
fatal("#35: `!` on a global tagged ident "
|
||||
"unwired (rule 7)");
|
||||
if (bsz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 24),
|
||||
areg(D_R8));
|
||||
if (bsz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 16),
|
||||
areg(D_CX));
|
||||
if (bsz > 8)
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff + 8),
|
||||
areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_BP, boff), areg(D_AX));
|
||||
} else if (cg_tagged_memread(n->lhs)) {
|
||||
int bsz = (int)type_chase_named(n->lhs->type)->size;
|
||||
cgexpr(c, n->lhs, locals);
|
||||
if (bsz > 24)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 24), areg(D_R8));
|
||||
if (bsz > 16)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 16), areg(D_CX));
|
||||
if (bsz > 8)
|
||||
ins2(c, A_MOVQ, amem(D_AX, 8), areg(D_DX));
|
||||
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
||||
} else
|
||||
cgexpr(c, n->lhs, locals);
|
||||
if (u && u->kind == TY_TAGGED && u->nullable) {
|
||||
char *cont = mklabel(c, "tryunw_ok");
|
||||
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
|
||||
@@ -9505,6 +9699,19 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
cg_tagged_tuple_payload_shift(c, stu);
|
||||
break;
|
||||
}
|
||||
/* Family C (#35): TAGGED success = nested box on the
|
||||
* payload words — shift past the outer tag (see the
|
||||
* N_TRYPROP twin). */
|
||||
if (stu && stu->kind == TY_TAGGED && !stu->nullable) {
|
||||
ins2(c, A_MOVQ, areg(D_DX), areg(D_AX));
|
||||
if (stu->size > 8)
|
||||
ins2(c, A_MOVQ, areg(D_CX),
|
||||
areg(D_DX));
|
||||
if (stu->size > 16)
|
||||
ins2(c, A_MOVQ, areg(D_R8),
|
||||
areg(D_CX));
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (success_is_str) {
|
||||
/* str IS []u8: success arrives DX=ptr, CX=len, R8=cap
|
||||
@@ -9527,12 +9734,25 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
&& cg_sret_retsize(n->lhs->type) > 0)
|
||||
fatal("#38b: `is` on an sret-class call result "
|
||||
"unwired (#40-family follow-up)");
|
||||
cgexpr(c, n->lhs, locals);
|
||||
/* #37: a >32B box read leaves its ADDRESS in AX — load
|
||||
* the tag word from memory before the compare. */
|
||||
if (cg_tagged_memread(n->lhs))
|
||||
/* Family C (#35): identity casts are transport no-ops —
|
||||
* peel so the ident emission carries; a WIDENING tagged
|
||||
* cast renumbers the tag the compare below keys on and
|
||||
* has no wired source arm — loud, not a mis-keyed test. */
|
||||
Node *tl = cg_tagged_idcastpeel(n->lhs);
|
||||
{
|
||||
Type *tcu = tl ? type_chase_named(tl->type) : NULL;
|
||||
if (tl && tl->kind == N_CAST && tcu
|
||||
&& tcu->kind == TY_TAGGED && !tcu->nullable)
|
||||
fatal("#35: tagged cast source shape unwired "
|
||||
"at `is` (rule 7)");
|
||||
}
|
||||
cgexpr(c, tl, locals);
|
||||
/* #37: a mem-based box read (>32B INDEX/DOT, any-size
|
||||
* deref) leaves its ADDRESS in AX — load the tag word
|
||||
* from memory before the compare. */
|
||||
if (cg_tagged_memread(tl))
|
||||
ins2(c, A_MOVQ, amem(D_AX, 0), areg(D_AX));
|
||||
Type *u = n->lhs ? n->lhs->type : NULL;
|
||||
Type *u = tl ? tl->type : NULL;
|
||||
if (u && u->kind == TY_NAMED) u = u->under;
|
||||
Type *vt = n->rhs ? n->rhs->type : NULL;
|
||||
char *ne = mklabel(c, "is_ne");
|
||||
@@ -9572,7 +9792,8 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
* Nullable: the slot's word IS the pointer. *T variant
|
||||
* asserts non-null; void variant asserts null. The value
|
||||
* left in AX after the check is the pointer itself. */
|
||||
Node *s = n->lhs;
|
||||
/* Family C (#35): identity-cast peel — see the `is` twin. */
|
||||
Node *s = cg_tagged_idcastpeel(n->lhs);
|
||||
Type *st = s ? s->type : NULL;
|
||||
Type *u = (st && st->kind == TY_NAMED) ? st->under : st;
|
||||
Type *vt = n->type;
|
||||
@@ -9617,6 +9838,12 @@ cgexpr(Cg *c, Node *n, Local *locals)
|
||||
fatal("#37: `as` on a >32B tagged value from "
|
||||
"a non-mem-based source (kind %d) unwired "
|
||||
"(rule 7)", s->kind);
|
||||
/* Family C catch-all (rule 7): a widening tagged
|
||||
* cast source has no cursor — loud. */
|
||||
if (s->kind == N_CAST && u && u->kind == TY_TAGGED
|
||||
&& !u->nullable)
|
||||
fatal("#35: tagged cast source shape unwired "
|
||||
"at `as` (rule 7)");
|
||||
cgexpr(c, s, locals);
|
||||
ins2(c, A_MOVQ, areg(D_AX), amem(D_BP, sl_off + 0));
|
||||
if (!(u && u->kind == TY_TAGGED && u->nullable)) {
|
||||
|
||||
Reference in New Issue
Block a user