You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Implement float comparisons with primitive operations
Summary:
Lower float comparisons to unboxed `PrimitiveCompare` on `CDouble` instead of
falling back to `PyFloat_Type.tp_richcompare`. This avoids boxing, a
type-generic dispatch, and a call per comparison on the hot path.
The subtlety is NaN. Python requires every ordering comparison involving a NaN
to be false, `nan == nan` to be false, and `nan != nan` to be true, but the
hardware float-compare instructions expose different flags for unordered (NaN)
operands on x86-64 (`comisd`) and ARM64 (`fcmp`). The HIR stays
architecture-independent, it emits the natural comparison
(`PrimitiveCompare<LessThan> x, y` and friends, and
`PrimitiveCompare<Equal>`/`<NotEqual>` for `==`/`!=`), and the NaN-correct
condition is chosen when the compare is lowered to LIR.
The choice must be made at the LIR-opcode level (not by, say, swapping operands
inside the compare's asm emission) because a compare that feeds a branch is
fused into a conditional jump whose condition is derived from the LIR opcode via
`compareToBranchCC`, reusing the compare's flags. If the compare's asm silently
swapped operands, the fused jump, which is unaware of that swap, would test the
wrong condition. So the LIR generator picks the opcode and operand order so
that the opcode's standard condition is NaN-correct for both the standalone
`setcc`/`cset` and the fused `jcc`/`b.cc`:
- x86-64: `comisd` sets the flags in an unsigned sense and marks unordered
operands with CF=ZF=PF=1, so only the above / above-equal conditions are false
for NaN. Every ordering is expressed as `>` / `>=`
(`kGreaterThanUnsigned`/`kGreaterThanEqualUnsigned`), swapping the operands
for `<` / `<=`. `comisd` folds unordered into ZF (the equality flag), so
`==`/`!=` have no NaN-correct single-instruction form; they are decomposed
into the ordering comparisons: `a == b` becomes `(a <= b) && (a >= b)` (both
false when a NaN is involved), and `a != b` negates that. `(a < b) || (a > b)`
would be wrong, it yields false for NaN.
- ARM64: `fcmp` sets NZCV and marks unordered operands with C=1, V=1 while
leaving Z=0. `>` / `>=` use the signed `GT`/`GE` conditions and `<` / `<=` the
unsigned `LO`/`LS` conditions (all false for unordered); `==`/`!=` use
`EQ`/`NE` (which key off Z alone and are already NaN-correct).
`TranslateCompare` is unchanged in spirit: it emits `comisd`/`fcmp` for double
operands and falls through to the shared per-opcode condition switch, so the
standalone and fused forms stay consistent. Floats are neither signed nor
unsigned, so both the signed and unsigned `PrimitiveCompareOp` variants are
handled together in the generator (Static Python `double` emits the unsigned
variants; the Python-float path emits the natural ones). A side benefit is that
Static Python `double` `<`/`<=` are now NaN-correct too (they previously used
`setb`/`setbe`, which are true for unordered operands).
Unboxing the nms float comparisons speeds it up ~7%; the other workloads are
within noise.
Reviewed By: mpage
Differential Revision: D110238069
fbshipit-source-id: 24952346336c44ea6d7225add39596c255a53f8b
0 commit comments