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

@@ -813,7 +813,8 @@ export fn encode(a: *asm_) i32 = {
else { if (op == A_JBE) { cc = 134u8; }
else { if (op == A_JA) { cc = 135u8; }
else { if (op == A_JAE) { cc = 131u8; }
else { isjcc = false; };};};};};};};};};};};};
else { if (op == A_JP) { cc = 138u8; } // 0x8A, UCOMISD unordered (#97)
else { isjcc = false; };};};};};};};};};};};};};
if (isjcc) {
emitbyte(a, 15u8);
emitbyte(a, cc);

View File

@@ -2805,6 +2805,10 @@ def A_JA: i32 = 55;
def A_JAE: i32 = 56;
def A_JZ: i32 = 57;
def A_JNZ: i32 = 58;
// 67 (next free above A_CQO=66): appended so the existing A_MOV*/
// A_SYSCALL/A_DATAW/A_DATAR/A_CQO numbers stay put. Jump on
// parity (PF=1): UCOMISD unordered (#97).
def A_JP: i32 = 67;
def A_SYSCALL: i32 = 59;
@@ -3060,6 +3064,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "JAE")) { return A_JAE; };
if (streqlit(p, n, "JZ")) { return A_JZ; };
if (streqlit(p, n, "JNZ")) { return A_JNZ; };
if (streqlit(p, n, "JP")) { return A_JP; };
if (streqlit(p, n, "SYSCALL")) { return A_SYSCALL; };
if (streqlit(p, n, "TEXT")) { return A_TEXT; };
if (streqlit(p, n, "DATA")) { return A_DATA; };
@@ -4370,7 +4375,8 @@ export fn encode(a: *asm_) i32 = {
else { if (op == A_JBE) { cc = 134u8; }
else { if (op == A_JA) { cc = 135u8; }
else { if (op == A_JAE) { cc = 131u8; }
else { isjcc = false; };};};};};};};};};};};};
else { if (op == A_JP) { cc = 138u8; } // 0x8A, UCOMISD unordered (#97)
else { isjcc = false; };};};};};};};};};};};};};
if (isjcc) {
emitbyte(a, 15u8);
emitbyte(a, cc);

View File

@@ -122,6 +122,10 @@ def A_JA: i32 = 55;
def A_JAE: i32 = 56;
def A_JZ: i32 = 57;
def A_JNZ: i32 = 58;
// 67 (next free above A_CQO=66): appended so the existing A_MOV*/
// A_SYSCALL/A_DATAW/A_DATAR/A_CQO numbers stay put. Jump on
// parity (PF=1): UCOMISD unordered (#97).
def A_JP: i32 = 67;
def A_SYSCALL: i32 = 59;

View File

@@ -90,6 +90,7 @@ fn opcodelookup(p: *u8, n: u64) i32 = {
if (streqlit(p, n, "JAE")) { return A_JAE; };
if (streqlit(p, n, "JZ")) { return A_JZ; };
if (streqlit(p, n, "JNZ")) { return A_JNZ; };
if (streqlit(p, n, "JP")) { return A_JP; };
if (streqlit(p, n, "SYSCALL")) { return A_SYSCALL; };
if (streqlit(p, n, "TEXT")) { return A_TEXT; };
if (streqlit(p, n, "DATA")) { return A_DATA; };

View File

@@ -16848,16 +16848,12 @@ fn cgbin(c: *cgen, n: *node) void = {
return;
};
let isfcmp: bool = false;
let jcc: str = "";
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
// matches the ordered comparisons we need.
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
if (n.op == tkind.TK_EQ) { isfcmp = true; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; };
if (n.op == tkind.TK_LT) { isfcmp = true; };
if (n.op == tkind.TK_LE) { isfcmp = true; };
if (n.op == tkind.TK_GT) { isfcmp = true; };
if (n.op == tkind.TK_GE) { isfcmp = true; };
if (isfcmp) {
cgexpr(c, n.rhs);
emitline("\tSUBQ\t$8, SP\n");
@@ -16868,6 +16864,51 @@ fn cgbin(c: *cgen, n: *node) void = {
let ucomi: str = "UCOMISD";
if (fk == 1) { ucomi = "UCOMISS"; };
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
// IEEE-754: UCOMISD/SS sets PF=ZF=CF=1 on unordered (a
// NaN operand). 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 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.op == tkind.TK_NEQ) {
// not-equal OR unordered -> true
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJNE\t"); emitline(t); emitline("\n");
emitline("\tJP\t"); emitline(t); emitline("\n");
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
if (n.op == tkind.TK_EQ || n.op == tkind.TK_LT ||
n.op == tkind.TK_LE) {
// unordered -> false; otherwise the ordered Jcc decides.
let jcc: str = "JE";
if (n.op == tkind.TK_LT) { jcc = "JB"; };
if (n.op == tkind.TK_LE) { jcc = "JBE"; };
let fl: str = mklabel(c, "cf");
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJP\t"); emitline(fl); emitline("\n");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
emitlabel(fl);
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
// `>`/`>=`: JA/JAE already reject unordered (CF=1), so
// keep the pre-#97 single-template shape verbatim.
let jcc: str = "JA";
if (n.op == tkind.TK_GE) { jcc = "JAE"; };
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");

View File

@@ -2825,16 +2825,12 @@ fn cgbin(c: *cgen, n: *node) void = {
return;
};
let isfcmp: bool = false;
let jcc: str = "";
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
// matches the ordered comparisons we need.
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
if (n.op == tkind.TK_EQ) { isfcmp = true; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; };
if (n.op == tkind.TK_LT) { isfcmp = true; };
if (n.op == tkind.TK_LE) { isfcmp = true; };
if (n.op == tkind.TK_GT) { isfcmp = true; };
if (n.op == tkind.TK_GE) { isfcmp = true; };
if (isfcmp) {
cgexpr(c, n.rhs);
emitline("\tSUBQ\t$8, SP\n");
@@ -2845,6 +2841,51 @@ fn cgbin(c: *cgen, n: *node) void = {
let ucomi: str = "UCOMISD";
if (fk == 1) { ucomi = "UCOMISS"; };
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
// IEEE-754: UCOMISD/SS sets PF=ZF=CF=1 on unordered (a
// NaN operand). 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 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.op == tkind.TK_NEQ) {
// not-equal OR unordered -> true
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJNE\t"); emitline(t); emitline("\n");
emitline("\tJP\t"); emitline(t); emitline("\n");
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
if (n.op == tkind.TK_EQ || n.op == tkind.TK_LT ||
n.op == tkind.TK_LE) {
// unordered -> false; otherwise the ordered Jcc decides.
let jcc: str = "JE";
if (n.op == tkind.TK_LT) { jcc = "JB"; };
if (n.op == tkind.TK_LE) { jcc = "JBE"; };
let fl: str = mklabel(c, "cf");
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJP\t"); emitline(fl); emitline("\n");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
emitlabel(fl);
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
// `>`/`>=`: JA/JAE already reject unordered (CF=1), so
// keep the pre-#97 single-template shape verbatim.
let jcc: str = "JA";
if (n.op == tkind.TK_GE) { jcc = "JAE"; };
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");

View File

@@ -16848,16 +16848,12 @@ fn cgbin(c: *cgen, n: *node) void = {
return;
};
let isfcmp: bool = false;
let jcc: str = "";
// UCOMISD/SS sets ZF/PF/CF; unordered (NaN) propagates as
// "not equal / not less". JA/JAE/JB/JBE keys off CF which
// matches the ordered comparisons we need.
if (n.op == tkind.TK_EQ) { isfcmp = true; jcc = "JE"; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; jcc = "JNE"; };
if (n.op == tkind.TK_LT) { isfcmp = true; jcc = "JB"; };
if (n.op == tkind.TK_LE) { isfcmp = true; jcc = "JBE"; };
if (n.op == tkind.TK_GT) { isfcmp = true; jcc = "JA"; };
if (n.op == tkind.TK_GE) { isfcmp = true; jcc = "JAE"; };
if (n.op == tkind.TK_EQ) { isfcmp = true; };
if (n.op == tkind.TK_NEQ) { isfcmp = true; };
if (n.op == tkind.TK_LT) { isfcmp = true; };
if (n.op == tkind.TK_LE) { isfcmp = true; };
if (n.op == tkind.TK_GT) { isfcmp = true; };
if (n.op == tkind.TK_GE) { isfcmp = true; };
if (isfcmp) {
cgexpr(c, n.rhs);
emitline("\tSUBQ\t$8, SP\n");
@@ -16868,6 +16864,51 @@ fn cgbin(c: *cgen, n: *node) void = {
let ucomi: str = "UCOMISD";
if (fk == 1) { ucomi = "UCOMISS"; };
emitline("\t"); emitline(ucomi); emitline("\tX1, X0\n");
// IEEE-754: UCOMISD/SS sets PF=ZF=CF=1 on unordered (a
// NaN operand). 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 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.op == tkind.TK_NEQ) {
// not-equal OR unordered -> true
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJNE\t"); emitline(t); emitline("\n");
emitline("\tJP\t"); emitline(t); emitline("\n");
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
if (n.op == tkind.TK_EQ || n.op == tkind.TK_LT ||
n.op == tkind.TK_LE) {
// unordered -> false; otherwise the ordered Jcc decides.
let jcc: str = "JE";
if (n.op == tkind.TK_LT) { jcc = "JB"; };
if (n.op == tkind.TK_LE) { jcc = "JBE"; };
let fl: str = mklabel(c, "cf");
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\tJP\t"); emitline(fl); emitline("\n");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");
emitlabel(fl);
emitline("\tMOVQ\t$0, AX\n");
emitline("\tJMP\t"); emitline(e); emitline("\n");
emitlabel(t);
emitline("\tMOVQ\t$1, AX\n");
emitlabel(e);
return;
};
// `>`/`>=`: JA/JAE already reject unordered (CF=1), so
// keep the pre-#97 single-template shape verbatim.
let jcc: str = "JA";
if (n.op == tkind.TK_GE) { jcc = "JAE"; };
let t: str = mklabel(c, "ct");
let e: str = mklabel(c, "ce");
emitline("\t"); emitline(jcc); emitline("\t"); emitline(t); emitline("\n");