Skip to content

immediate: a float constant is identified by EQL, not ZEROP - #621

Merged
xrme merged 1 commit into
Clozure:arm64from
cyclistmass:immediate-eql-not-zerop
Sep 8, 2026
Merged

xrme merged 1 commit into
Clozure:arm64from
cyclistmass:immediate-eql-not-zerop

Conversation

@cyclistmass

Copy link
Copy Markdown
Contributor

<arch>2-immediate asks an identity question with a numeric predicate. It must
know whether the constant IS 0.0d0 or 0.0s0, so it can zero a register
instead of loading one. The line directly below already asks that with EQL.
ZEROP expands to (= form 0) against a fixnum, and two defects follow.

A NaN constant aborts the compile (#608). The compare reaches
fixnum-dfloat-compare, and the shipped FPU mode leaves invalid-operation
unmasked, so COMPILE-FILE dies at compile time. 1D+-0 reads back as a quiet
NaN, so no library is needed to hit this.

A negative zero double loses its sign, silently. (zerop -0.0d0) is T and
(eql -0.0d0 0.0d0) is NIL, so -0.0d0 takes the zero branch and reaches
zero-single-float-register on a double-mode vreg. The constant is zeroed
either way — the double arm would emit +0.0d0 just as the single arm does — so
the harm is that -0.0d0 compiles to a zeroed register, and the wrong arm is
only how it gets there.

EQL answers both, and the inner EQL is unchanged. The construct is unchanged
since 2008 (60c5e0d5), and I reproduced it on stock 1.12.2 LinuxX8664, so it is
not specific to the reporter's 1.13/FreeBSD. compiler/PPC/ppc2.lisp holds the
same construct and is deliberately untouched: no PPC Lisp can be built or tested.

(zerop 1d+-0) at runtime is separate and is NOT fixed here. Please do not fix
it with a quiet compare alone. Float compares are read with UNSIGNED condition
codes, so = becomes e, < becomes b and <= becomes be, while > and
>= become a and ae. COMISD reports unordered as ZF=PF=CF=1 and PF is
never consulted, so with :invalid masked, =, < and <= all return T
against a NaN while > and >= return NIL. A PF-aware flag test is needed too.

VERIFIED: built and tested on linuxarm64. ANSI :TOTAL 21679 :FAILED 0 :EXCLUDED NIL, ccl.lsp :TOTAL 243 :FAILED 0. Three files, and x862.lisp is
the shared x86 backend serving both widths. Built on arm64 only — not on
x86-64, x86-32 or arm32.

<arch>2-immediate asks an IDENTITY question with a NUMERIC predicate.  The
peephole wants to know "is this constant the object +0.0d0 (or +0.0s0), so I
can emit a register-zeroing instruction instead of a constant load?"  That is
an EQL question, and the very next line already asks it that way:

      (if (zerop form)                       ; = (= form 0), touches the FPU
        (if (eql form 0.0d0)                 ; the real question
          (! zero-double-float-register vreg)
          (! zero-single-float-register vreg))

ZEROP is (= n 0), a numeric comparison.  Two defects follow, and they are
independent of each other.

=== D1  A NaN CONSTANT ABORTS THE COMPILE

Reported as Clozure/ccl issue Clozure#608 ("Quiet NaN chokes compiler's emitter"), on
1.13/FreebsdX8664, from a source file holding double-float NaN literals:

    [Condition of type FLOATING-POINT-INVALID-OPERATION]
    0: (CCL::FIXNUM-DFLOAT-COMPARE 0 1D+-0 #| not-a-number |#)

1D+-0 is how CCL prints a quiet NaN, and the reader reads it back as one, so
the literal route needs no library.  The invalid-operation trap is enabled in
the shipped FPU mode, so comparing the constant to 0 at COMPILE TIME signals
and COMPILE-FILE dies.

=== D2  A NEGATIVE ZERO DOUBLE TAKES THE SINGLE-FLOAT ARM

(zerop -0.0d0) is T, because -0.0 = 0.0.  (eql -0.0d0 0.0d0) is NIL, because
they are different objects.  So -0.0d0 enters the zero branch and then falls to
zero-single-float-register -- on a vreg the enclosing test has just established
is in DOUBLE mode.

The constant loses its sign either way -- the double arm would emit +0.0d0 just
as the single arm does -- so the harm is that -0.0d0 compiles to a zeroed
register, and the wrong arm is how it gets there.  This has nothing to do with
NaN, it is silent, and it is what makes the line a defect rather than merely the
place where Clozure#608 surfaces.

=== THE FIX

Ask the identity question with EQL.  EQL distinguishes -0.0 from 0.0 and never
compares a NaN numerically, so both defects go with one predicate.  The inner
EQL is already correct and is left alone.

=== WHAT THIS DOES NOT FIX, AND THE TRAP IN FIXING IT

(zerop 1d+-0) signalling at RUNTIME is separate and is NOT addressed here.
IEEE 754 makes equality a quiet predicate, so it should return NIL rather than
signal.  Do not "fix" that by switching to a quiet compare.

MEASURED on x86-64 with :invalid masked, against (NaN, 0.0d0):

    =    T      <-- wrong
    <    T      <-- wrong
    <=   T      <-- wrong
    >    NIL
    >=   NIL

x862-compare-double-float-registers reads the result with UNSIGNED condition
codes, via x862-cr-bit-for-unsigned-comparison: = becomes e, < becomes b, <=
becomes be, > becomes a and >= becomes ae.  COMISD reports UNORDERED as
ZF=PF=CF=1, and PF is never consulted, so e, b and be cannot separate unordered
from equal or below while a and ae happen to reject it.  That accounts for every
row above, including the two that look right.  The trap is therefore
LOAD-BEARING: it is the only thing standing between the user and three silently
wrong answers.  A correct fix needs the quiet compare AND a PF-aware flag test,
and a decision about whether the ordered predicates should keep signalling,
which IEEE permits.

=== SCOPE

Three files.  compiler/X86/x862.lisp is the shared x86 backend and serves both
widths, so the change reaches x86-64, x86-32, arm64 and arm32.
compiler/PPC/ppc2.lisp carries the identical construct and is deliberately NOT
touched: no PPC Lisp can be built or tested,
so the change could not be confirmed by anyone.

=== CELL

Reproduced on stock CCL, no cross-compiler involved:
binary /local/ccl/ccl-full/lx86cl64, md5 f86ddf1405477915c45b267e13d3d919,
Version 1.12.2 (v1.12.2) LinuxX8664, backends (LINUXX8664).  20-cell probe with
positive controls: (zerop nan) and (zerop single-nan) both signal; COMPILE of
an aset with a NaN literal signals for double and single; the same COMPILE with
1.5d0 and with 0d0 returns :COMPILED.  The reporter is on 1.13/FreeBSD, so this
cell shows the defect is at least as old as 1.12.2 and says nothing about
FreeBSD-specific behaviour.
@xrme
xrme merged commit a74f464 into Clozure:arm64 Sep 8, 2026
1 check passed
@xrme

xrme commented Sep 8, 2026 •

Copy link
Copy Markdown
Member

argh, wrong branch. cherry-picked to master as 4ca4df4

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants