From 577b4667db3687ad611d1b589777111bd48bcda7 Mon Sep 17 00:00:00 2001 From: Quentin Carbonneaux Date: Wed, 29 Apr 2026 17:45:20 +0200 Subject: [PATCH] negated conditions rework Floating point comparisons are subtle due to unordered operands (i.e., when one or two of the operands are nans). In amd64 we arrange post isel to make sure we only have comparisons that will return false on unordered operands and for which we have a negated version (returning true on unordered operands). I made this situation a bit more explicit by marking unexpected comparisons with "?" in amd64/emit.c. In arm64, the instruction set is rich enough to have a negated version for all operators that returns true on unordered inputs. So we build a backend specific table. I made sure to remove cmpneg() from utils.c; it is a footgun as it does not work well for floating point ops because of the 'unordered' edge case. I found the ARM docs about condition codes pretty bad; the following C program is a good substitute: #include int flags(float a, float b) { int z, c, n, v, le; __asm__( "fcmpe %s5, %s6\n" "\tcset %0, eq\n" "\tcset %1, mi\n" "\tcset %2, cs\n" "\tcset %3, vs\n" "\tcset %4, le\n" : "=r"(z),"=r"(n),"=r"(c),"=r"(v),"=r"(le) : "x"(a), "x"(b) : "cc"); printf( "cmp(%g,%g): z=%d n=%d c=%d v=%d le=%d\n", a, b, z, n, c, v, le); } int main() { flags(1.0, 1.0); flags(0.0, 1.0); flags(1.0, 0.0); flags(0.0, 0.0/0.0); flags(0.0/0.0, 0.0); } Compile & run with: aarch64-linux-gnu-gcc -static -no-pie flags.c qemu-aarch64 ./a.out --- all.h | 1 - amd64/emit.c | 26 ++++++++++++++------------ arm64/emit.c | 49 +++++++++++++++++++++++++------------------------ util.c | 11 ++--------- 4 files changed, 41 insertions(+), 46 deletions(-) diff --git a/all.h b/all.h index 9e1e633..eb79fc0 100644 --- a/all.h +++ b/all.h @@ -500,7 +500,6 @@ void emiti(Ins); void idup(Blk *, Ins *, ulong); Ins *icpy(Ins *, Ins *, ulong); int cmpop(int); -int cmpneg(int); int cmpwlneg(int); int clsmerge(short *, short); int phicls(int, Tmp *); diff --git a/amd64/emit.c b/amd64/emit.c index d1efc49..99e4d1f 100644 --- a/amd64/emit.c +++ b/amd64/emit.c @@ -22,8 +22,8 @@ struct E { X(Ciuge, "ae", "b") \ X(Cieq, "z", "nz") \ X(Cine, "nz", "z") \ - X(NCmpI+Cfle, "be", "a") \ - X(NCmpI+Cflt, "b", "ae") \ + X(NCmpI+Cfle, "?" , "?") \ + X(NCmpI+Cflt, "?", "?") \ X(NCmpI+Cfgt, "a", "be") \ X(NCmpI+Cfge, "ae", "b") \ X(NCmpI+Cfo, "np", "p") \ @@ -627,8 +627,8 @@ sysv_framesz(E *e) void amd64_sysv_emitfn(Fn *fn, FILE *f) { - static char *ctoa[] = { - #define X(c, s, _) [c] = s, + static char *ctoa[][2] = { + #define X(c, s, n) [c] = {s, n}, CMP(X) #undef X }; @@ -710,13 +710,14 @@ amd64_sysv_emitfn(Fn *fn, FILE *f) default: c = b->jmp.type - Jjf; if (0 <= c && c <= NCmp) { - if (b->link == b->s2 || c >= NCmpI) { + if (b->link == b->s2) { s = b->s1; b->s1 = b->s2; b->s2 = s; + n = 0; } else - c = cmpneg(c); - fprintf(f, "\tj%s %sbb%d\n", ctoa[c], + n = 1; + fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n], T.asloc, id0+b->s2->id); goto Jmp; } @@ -752,15 +753,15 @@ winabi_framesz(E *e) void amd64_winabi_emitfn(Fn *fn, FILE *f) { - static char *ctoa[] = { - #define X(c, s, _) [c] = s, + static char *ctoa[][2] = { + #define X(c, s, n) [c] = {s, n}, CMP(X) #undef X }; static int id0; Blk *b, *s; Ins *i, itmp; - int *r, c, lbl; + int *r, c, n, lbl; E *e; e = &(E){.f = f, .fn = fn}; @@ -831,9 +832,10 @@ amd64_winabi_emitfn(Fn *fn, FILE *f) s = b->s1; b->s1 = b->s2; b->s2 = s; + n = 0; } else - c = cmpneg(c); - fprintf(f, "\tj%s %sbb%d\n", ctoa[c], + n = 1; + fprintf(f, "\tj%s %sbb%d\n", ctoa[c][n], T.asloc, id0+b->s2->id); goto Jmp; } diff --git a/arm64/emit.c b/arm64/emit.c index b9984b6..108a928 100644 --- a/arm64/emit.c +++ b/arm64/emit.c @@ -10,24 +10,24 @@ struct E { }; #define CMP(X) \ - X(Cieq, "eq") \ - X(Cine, "ne") \ - X(Cisge, "ge") \ - X(Cisgt, "gt") \ - X(Cisle, "le") \ - X(Cislt, "lt") \ - X(Ciuge, "cs") \ - X(Ciugt, "hi") \ - X(Ciule, "ls") \ - X(Ciult, "cc") \ - X(NCmpI+Cfeq, "eq") \ - X(NCmpI+Cfge, "ge") \ - X(NCmpI+Cfgt, "gt") \ - X(NCmpI+Cfle, "ls") \ - X(NCmpI+Cflt, "mi") \ - X(NCmpI+Cfne, "ne") \ - X(NCmpI+Cfo, "vc") \ - X(NCmpI+Cfuo, "vs") + X(Cieq, "eq", "ne") \ + X(Cine, "ne", "eq") \ + X(Cisge, "ge", "lt") \ + X(Cisgt, "gt", "le") \ + X(Cisle, "le", "gt") \ + X(Cislt, "lt", "ge") \ + X(Ciuge, "cs", "cc") \ + X(Ciugt, "hi", "ls") \ + X(Ciule, "ls", "hi") \ + X(Ciult, "cc", "cs") \ + X(NCmpI+Cfeq, "eq", "ne") \ + X(NCmpI+Cfge, "ge", "lt") \ + X(NCmpI+Cfgt, "gt", "le") \ + X(NCmpI+Cfle, "ls", "hi") \ + X(NCmpI+Cflt, "mi", "pl") \ + X(NCmpI+Cfne, "ne", "eq") \ + X(NCmpI+Cfo, "vc", "vs") \ + X(NCmpI+Cfuo, "vs", "vc") enum { Ki = -1, /* matches Kw and Kl */ @@ -102,7 +102,7 @@ static struct { { Oacmn, Ki, "cmn %0, %1" }, { Oafcmp, Ka, "fcmpe %0, %1" }, -#define X(c, str) \ +#define X(c, str, _) \ { Oflag+c, Ki, "cset %=, " str }, CMP(X) #undef X @@ -532,8 +532,8 @@ framelayout(E *e) void arm64_emitfn(Fn *fn, FILE *out) { - static char *ctoa[] = { - #define X(c, s) [c] = s, + static char *ctoa[][2] = { + #define X(c, s, n) [c] = {s, n}, CMP(X) #undef X }; @@ -660,15 +660,16 @@ arm64_emitfn(Fn *fn, FILE *out) c = b->jmp.type - Jjf; if (c < 0 || c > NCmp) die("unhandled jump %d", b->jmp.type); - if (b->link == b->s2 || c >= NCmpI) { + if (b->link == b->s2) { t = b->s1; b->s1 = b->s2; b->s2 = t; + n = 0; } else - c = cmpneg(c); + n = 1; fprintf(e->f, "\tb%s\t%s%d\n", - ctoa[c], T.asloc, id0+b->s2->id + ctoa[c][n], T.asloc, id0+b->s2->id ); goto Jmp; } diff --git a/util.c b/util.c index a5fa790..5b7abf4 100644 --- a/util.c +++ b/util.c @@ -364,13 +364,6 @@ static int cmptab[][2] ={ [NCmpI+Cfuo] = {-1, NCmpI+Cfuo}, }; -int -cmpneg(int c) -{ - assert(0 <= c && c < NCmpI); - return cmptab[c][0]; -} - int cmpop(int c) { @@ -382,9 +375,9 @@ int cmpwlneg(int op) { if (INRANGE(op, Ocmpw, Ocmpw1)) - return cmpneg(op - Ocmpw) + Ocmpw; + return cmptab[op - Ocmpw][0] + Ocmpw; if (INRANGE(op, Ocmpl, Ocmpl1)) - return cmpneg(op - Ocmpl) + Ocmpl; + return cmptab[op - Ocmpl][0] + Ocmpl; die("not a wl comparison"); }