cgen: f64 compare consults parity flag for NaN, 4 relops (both stages, #97)

UCOMISD/UCOMISS set PF=ZF=CF=1 on unordered (a NaN operand). The old
arms keyed on ZF/CF only, so 4 of the 6 relops mishandled NaN:
`nan != nan` was false (JNE keys on ZF=0), `nan == nan` was true, and
`<`/`<=` (JB/JBE) fired on the unordered CF=1. IEEE-754: any relop
with a NaN operand is unordered — `!=` true, the rest false. `!=` now
jumps to true on JNE OR JP; `==`/`<`/`<=` jump to false on JP before
the ordered Jcc.

`>`/`>=` (JA/JAE) are LEFT UNCHANGED: they require CF=0, which an
unordered UCOMISD never produces, so they already reject NaN
correctly. Adding a PF guard there would only churn their .s (an extra
JP on every >/>= float compare) for no correctness gain, so their arm
stays byte-identical to the pre-#97 single template.

Bundles the cgen fix with JP-mnemonic support in both assemblers
(w6c enum/printer + w6a/w6a_ww parse+encode, 0F 8A). They can't split:
the cgen emits JP, which has no encoding without the assembler change,
so a cgen-only commit would not build. JP is the only PF-sensitive
jump on amd64 — there is no alternative instruction.
This commit is contained in:
2026-05-25 12:40:23 +09:00
parent 2f2a73bd41
commit fa136d0b88
12 changed files with 214 additions and 44 deletions

View File

@@ -811,7 +811,8 @@ a_encode(Asm *a)
break;
case A_JE: case A_JNE: case A_JL: case A_JLE:
case A_JG: case A_JGE: case A_JB: case A_JBE:
case A_JA: case A_JAE: case A_JZ: case A_JNZ: {
case A_JA: case A_JAE: case A_JZ: case A_JNZ:
case A_JP: {
u8 cc = 0;
switch (p->as) {
case A_JE: case A_JZ: cc = 0x84; break;
@@ -824,6 +825,7 @@ a_encode(Asm *a)
case A_JBE: cc = 0x86; break;
case A_JA: cc = 0x87; break;
case A_JAE: cc = 0x83; break;
case A_JP: cc = 0x8A; break;
default: break;
}
a_emit_byte(a, 0x0F);

View File

@@ -119,6 +119,7 @@ opcode_lookup(const char *m)
{ "JB", A_JB },{ "JBE", A_JBE },
{ "JA", A_JA },{ "JAE", A_JAE },
{ "JZ", A_JZ },{ "JNZ", A_JNZ },
{ "JP", A_JP },
{ "SYSCALL", A_SYSCALL },
{ "TEXT", A_TEXT },
{ "DATA", A_DATA },

View File

@@ -106,6 +106,7 @@ enum {
A_JL, A_JLE, A_JG, A_JGE,
A_JB, A_JBE, A_JA, A_JAE,
A_JZ, A_JNZ,
A_JP, /* jump on parity (PF=1): UCOMISD unordered, #97 */
A_SYSCALL,

View File

@@ -2358,8 +2358,14 @@ cgexpr(Cg *c, Node *n, Local *locals)
* unsigned compare, so the JA family is the right Jcc set
* regardless of how the operand types are signed. Plan 9's
* own w6c picks the same pattern (txt.c around AUCOMISD).
* NaN handling: UCOMI sets PF on unordered; we ignore it,
* which means NaN compares behave like Hare's default. */
* NaN handling: UCOMI sets PF=ZF=CF=1 on unordered (a NaN
* operand). IEEE-754: any relop with a NaN operand is
* unordered — `!=` true, the other five false. PF must steer
* `!=`/`==`/`<`/`<=` (#97): JNE keys on ZF=0 so `nan != nan`
* came out false; JE/JB/JBE all fire on the unordered ZF/CF.
* `>`/`>=` (JA/JAE) need CF=0, which unordered never gives,
* so they are ALREADY NaN-correct and stay byte-identical to
* the pre-#97 single-template arm — no redundant PF guard. */
if (n->lhs && node_isfloat(n->lhs) &&
(n->op == TK_EQ || n->op == TK_NEQ
|| n->op == TK_LT || n->op == TK_LE
@@ -2374,16 +2380,39 @@ cgexpr(Cg *c, Node *n, Local *locals)
ins2(c, mov, amem(D_SP, 0), areg(D_X1));
ins2(c, A_ADDQ, aimm(8), areg(D_SP));
ins2(c, ucomi, areg(D_X1), areg(D_X0));
int op = A_JE;
switch (n->op) {
case TK_EQ: op = A_JE; break;
case TK_NEQ: op = A_JNE; break;
case TK_LT: op = A_JB; break;
case TK_LE: op = A_JBE; break;
case TK_GT: op = A_JA; break;
case TK_GE: op = A_JAE; break;
default: break;
if (n->op == TK_NEQ) {
/* not-equal OR unordered -> true */
char *t = mklabel(c, "ct");
char *e = mklabel(c, "ce");
ins1(c, A_JNE, abranch(t));
ins1(c, A_JP, abranch(t));
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
ins1(c, A_JMP, abranch(e));
label(c, t);
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
label(c, e);
break;
}
if (n->op == TK_EQ || n->op == TK_LT || n->op == TK_LE) {
/* unordered -> false; otherwise the ordered Jcc decides */
int op = (n->op == TK_EQ) ? A_JE
: (n->op == TK_LT) ? A_JB : A_JBE;
char *fl = mklabel(c, "cf");
char *t = mklabel(c, "ct");
char *e = mklabel(c, "ce");
ins1(c, A_JP, abranch(fl));
ins1(c, op, abranch(t));
label(c, fl);
ins2(c, A_MOVQ, aimm(0), areg(D_AX));
ins1(c, A_JMP, abranch(e));
label(c, t);
ins2(c, A_MOVQ, aimm(1), areg(D_AX));
label(c, e);
break;
}
/* `>`/`>=`: JA/JAE already reject unordered (CF=1), so
* keep the pre-#97 single-template shape verbatim. */
int op = (n->op == TK_GT) ? A_JA : A_JAE;
char *t = mklabel(c, "ct");
char *e = mklabel(c, "ce");
ins1(c, op, abranch(t));

View File

@@ -88,6 +88,7 @@ anames(int op)
case A_JAE: return "JAE";
case A_JZ: return "JZ";
case A_JNZ: return "JNZ";
case A_JP: return "JP";
case A_SYSCALL: return "SYSCALL";
}
return "??";
@@ -184,6 +185,7 @@ txt_emit(FILE *f, Prog *head)
case A_JL: case A_JLE: case A_JG: case A_JGE:
case A_JB: case A_JBE: case A_JA: case A_JAE:
case A_JZ: case A_JNZ:
case A_JP:
fprintf(f, "\t%s\t", anames(p->as));
prAdr(f, p->to);
fputc('\n', f);