wcc: ? error-subset propagation typecheck + tag remap

`expr?` previously did a brain-dead RET through whatever AX/DX/CX
held — only safe when operand and enclosing fn had identical variant
ordering. Tests relied on that alignment by construction.

Now:
- Typecheck: each non-first variant of operand must appear as a
  variant of the enclosing fn's return tagged union. Enclosing must
  itself be tagged (a non-tagged return has no slot for errors to
  land in).
- Cgen: on tag != 0, walk operand's error variants and emit a
  conditional tag remap (cmp/jne/mov/jmp) for any whose index in
  enclosing differs from operand's. Identity cases emit nothing,
  so same-shape operands cost zero extra instructions.

Selfhost cgen doesn't implement N_TRYPROP at all (no selfhost source
uses `?`); byte-identity tests still pass.

One existing e2e row used `?` with main returning i32 — relied on
the old loose semantics. Switched to `!` (abort-on-error); it was
exercising success-unwrap, not propagation.
This commit is contained in:
2026-05-12 01:40:02 +09:00
parent fa070b6d07
commit 41a82021a3
4 changed files with 113 additions and 4 deletions

View File

@@ -1897,16 +1897,42 @@ cgexpr(Cg *c, Node *n, Local *locals)
/* Evaluate tagged value: AX=tag, DX=value0[, CX=value1].
* If tag != 0, propagate as the current function's return.
* On success, unwrap to the success-variant ABI: ≤8B values
* land in AX; str values land in (AX=ptr, BX=len). */
* land in AX; str values land in (AX=ptr, BX=len).
*
* Tag remap: when operand and enclosing fn have different
* variant orderings, the operand's error tag must be
* translated to the enclosing fn's tag for the same variant
* type. For each non-first variant V_i in operand at index
* i, if i != enclosing's index for V (call it j), emit a
* conditional MOV $j → AX. Identity cases emit nothing. */
cgexpr(c, n->lhs, locals);
Type *u = n->lhs ? n->lhs->type : NULL;
if (u && u->kind == TY_NAMED) u = u->under;
Type *r = cg_ret_type;
if (r && r->kind == TY_NAMED) r = r->under;
Type *first = (u && u->kind == TY_TAGGED && u->params)
? u->params->type : NULL;
int success_is_str = type_isstr(first);
char *cont = mklabel(c, "tryprop_ok");
ins2(c, A_CMPQ, aimm(0), areg(D_AX));
ins1(c, A_JE, abranch(cont));
if (u && r && r->kind == TY_TAGGED && u->params) {
char *propret = mklabel(c, "tryprop_ret");
int i = 1;
for (Tparam *p = u->params->next; p;
p = p->next, i++) {
int j = cg_tag_for_variant(r, p->type);
if (j < 0) j = 0;
if (j == i) continue;
char *skip = mklabel(c, "tryprop_skip");
ins2(c, A_CMPQ, aimm(i), areg(D_AX));
ins1(c, A_JNE, abranch(skip));
ins2(c, A_MOVQ, aimm(j), areg(D_AX));
ins1(c, A_JMP, abranch(propret));
label(c, skip);
}
label(c, propret);
}
ins2(c, A_MOVQ, areg(D_BP), areg(D_SP));
ins1(c, A_POPQ, areg(D_BP));
ins0(c, A_RET);