From fa136d0b88ea05f4119eeb64241dfd52f8371540 Mon Sep 17 00:00:00 2001 From: Hojun-Cho Date: Mon, 25 May 2026 12:40:23 +0900 Subject: [PATCH] cgen: f64 compare consults parity flag for NaN, 4 relops (both stages, #97) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- cmd/w6a/asm.c | 4 +- cmd/w6a/parse.c | 1 + cmd/w6c/6.out.h | 1 + cmd/w6c/cgen.c | 51 ++++++++++++++++++----- cmd/w6c/txt.c | 2 + selfhost/cmd/w6a/asm.ww | 3 +- selfhost/cmd/w6a/main.combined.ww | 8 +++- selfhost/cmd/w6a/opcodes.ww | 4 ++ selfhost/cmd/w6a/parse.ww | 1 + selfhost/cmd/w6c/main.combined.ww | 61 +++++++++++++++++++++++----- selfhost/cmd/wcc/cgenexpr.ww | 61 +++++++++++++++++++++++----- selfhost/cmd/wwdump/main.combined.ww | 61 +++++++++++++++++++++++----- 12 files changed, 214 insertions(+), 44 deletions(-) diff --git a/cmd/w6a/asm.c b/cmd/w6a/asm.c index e380a9ac..2c0a5b16 100644 --- a/cmd/w6a/asm.c +++ b/cmd/w6a/asm.c @@ -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); diff --git a/cmd/w6a/parse.c b/cmd/w6a/parse.c index ae3e61a5..4d516a80 100644 --- a/cmd/w6a/parse.c +++ b/cmd/w6a/parse.c @@ -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 }, diff --git a/cmd/w6c/6.out.h b/cmd/w6c/6.out.h index 83a44d16..7ca02c61 100644 --- a/cmd/w6c/6.out.h +++ b/cmd/w6c/6.out.h @@ -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, diff --git a/cmd/w6c/cgen.c b/cmd/w6c/cgen.c index a2a333fb..f3953b91 100644 --- a/cmd/w6c/cgen.c +++ b/cmd/w6c/cgen.c @@ -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)); diff --git a/cmd/w6c/txt.c b/cmd/w6c/txt.c index 3200b866..2cfef33c 100644 --- a/cmd/w6c/txt.c +++ b/cmd/w6c/txt.c @@ -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); diff --git a/selfhost/cmd/w6a/asm.ww b/selfhost/cmd/w6a/asm.ww index 657d8013..b2bbe433 100644 --- a/selfhost/cmd/w6a/asm.ww +++ b/selfhost/cmd/w6a/asm.ww @@ -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); diff --git a/selfhost/cmd/w6a/main.combined.ww b/selfhost/cmd/w6a/main.combined.ww index ea2bfed7..05b22889 100644 --- a/selfhost/cmd/w6a/main.combined.ww +++ b/selfhost/cmd/w6a/main.combined.ww @@ -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); diff --git a/selfhost/cmd/w6a/opcodes.ww b/selfhost/cmd/w6a/opcodes.ww index 7729a39d..a7ce82a8 100644 --- a/selfhost/cmd/w6a/opcodes.ww +++ b/selfhost/cmd/w6a/opcodes.ww @@ -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; diff --git a/selfhost/cmd/w6a/parse.ww b/selfhost/cmd/w6a/parse.ww index 8e305524..145b9ea4 100644 --- a/selfhost/cmd/w6a/parse.ww +++ b/selfhost/cmd/w6a/parse.ww @@ -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; }; diff --git a/selfhost/cmd/w6c/main.combined.ww b/selfhost/cmd/w6c/main.combined.ww index 683af0fb..4c242da2 100644 --- a/selfhost/cmd/w6c/main.combined.ww +++ b/selfhost/cmd/w6c/main.combined.ww @@ -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"); diff --git a/selfhost/cmd/wcc/cgenexpr.ww b/selfhost/cmd/wcc/cgenexpr.ww index 9493c5a6..1ed7cfbc 100644 --- a/selfhost/cmd/wcc/cgenexpr.ww +++ b/selfhost/cmd/wcc/cgenexpr.ww @@ -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"); diff --git a/selfhost/cmd/wwdump/main.combined.ww b/selfhost/cmd/wwdump/main.combined.ww index 9dd53a74..9ab1071c 100644 --- a/selfhost/cmd/wwdump/main.combined.ww +++ b/selfhost/cmd/wwdump/main.combined.ww @@ -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");