[compiler-rt] [compiler-rt][ARM] Optimized FP double <-> single conversion (PR #179926)

Saleem Abdulrasool via llvm-commits llvm-commits at lists.llvm.org
Sun May 24 12:33:07 PDT 2026


================
@@ -0,0 +1,195 @@
+//===-- extendsfdf2.S - single- to double-precision FP conversion ---------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+//
+// This file implements the __extendsfdf2 function (single to double precision
+// floating point conversion) for the Arm and Thumb2 ISAs.
+//
+//===----------------------------------------------------------------------===//
+
+#include "../assembly.h"
+#include "crt_endian.h"
+
+  .syntax unified
+  .text
+  .p2align 2
+
+#if __ARM_PCS_VFP
+DEFINE_COMPILERRT_FUNCTION(__extendsfdf2)
+  push {r4, lr}
+  vmov r0, s0
+  bl __aeabi_f2d
+  VMOV_TO_DOUBLE(d0, r0, r1)
+  pop {r4, pc}
+#else
+DEFINE_COMPILERRT_FUNCTION_ALIAS(__extendsfdf2, __aeabi_f2d)
+#endif
+
+DEFINE_COMPILERRT_FUNCTION(__aeabi_f2d)
+
+  // Start with the fast path, dealing with normalized single-precision inputs.
+  // We handle these as quickly as possible in straight-line code, and branch
+  // out of line to a single 'handle everything else' label which will have to
+  // figure out what kind of unusual thing has happened.
+
+  // Extend the exponent field by 3 bits, by shifting the sign bit off the top
+  // of r0 into the carry flag, shifting the rest of the input word right by 3,
+  // then using RRX to put the sign back. So we end up with a word shaped like
+  // the top half of a double, but the exponent field is still biased by the
+  // single-precision offset of 0x7f instead of the double-precision 0x3ff.
+  lsls    r3, r0, #1
+  lsr     r12, r3, #3
+  rrx     r12, r12
+
+  // For a normalized number, the remaining steps are to rebias the exponent,
+  // recover the remaining 3 mantissa bits from r0 which aren't included in the
+  // word we've just made, and move both into the right output registers.
+  //
+  // But we must also check for the difficult cases. These occur when the input
+  // exponent is either 0 or 0xFF. Those two values can be identified by the
+  // property that exp XOR (exp << 1) has the top 7 bits all zero.
+
+  // Do the test for uncommon values. Instead of using a shifter operand in the
+  // obvious way (EOR output, r0, r0, lsl #1), we use the fact that the setup
+  // code above already has a shifted-left copy of the input word in r3. In
+  // Thumb, this makes the EORS a 16-bit instruction instead of 32-bit.
+  eors    r3, r3, r0
+
+  // Now prepare the output, for normal inputs.
+  //
+  // We make this pair of instructions conditional on NE, i.e. we skip it if r3
+  // and r0 were actually equal (which could only happen if r0 was 0, i.e. the
+  // input was +0). This is fine, because in that situation the input wasn't
+  // normalized, so we aren't going to return this output anyway.
+  //
+  // The _point_ of conditionalizing these two instructions is that this way we
+  // have only one IT instruction on the fast path, and it's _here_, where this
+  // comment is, so that it comes immediately after the above 16-bit EORS and
+  // can be executed in the same cycle by Cortex-M3.
+  lslne   xl, r0, #29           // xl now has the bottom 3 input mantissa bits
+  addne   xh, r12, #(0x3ff - 0x7f) << 20 // rebias exponent in xh
+
+  // Finally, check whether the test word in r3 has its top 7 exponent bits
+  // zero. If not, we can return the fast-path answer.
+  tstne   r3, #0x7f000000
+  bxne    lr
+
+  // Now we've handled the fast-path cases as fast as we know how, what do we
+  // do next? We almost certainly don't have the input value in r0 any more,
+  // because we overwrote it by writing an unused output to xh:xl in the above
+  // code. Worse, we didn't _reliably_ overwrite it, because those writes to
+  // xh:xl might not have happened if the whole test word in r3 was zero. So
+  // where can we find the input bits?
+  //
+  // We have r3 = input XOR (input << 1). That's actually an invertible
+  // transformation, so in principle we could recover the full original input
+  // float from just r3. The quickest way to do that involves these five
+  // instructions (in any order, since they commute):
+  //
+  //   EOR     r3, r3, r3, lsl #16
+  //   EOR     r3, r3, r3, lsl #8
+  //   EOR     r3, r3, r3, lsl #4
+  //   EOR     r3, r3, r3, lsl #2
+  //   EOR     r3, r3, r3, lsl #1
+  //
+  // But that's rather slow, and we can do better. r12 contains most of the
+  // input bits in a more usable form: we inserted three zero bits between the
+  // sign and the top of the exponent, but everything from the input is there
+  // _somewhere_, except for the low 3 bits.
+  //
+  // However, on one code path below we'll use a subset of those EOR
+  // instructions to recover the low 3 bits of the input.
+
+  // First, find out whether the input exponent was 0 (zero or denormal), or
+  // 0xFF (infinity or NaN). We know it was one of the two, or we would have
+  // taken the early return from the fast path. So it's enough to test any
+  // single bit of the exponent in r12.
+  tst     r12, #1<<27           // bit 27 is topmost bit of the 8-bit exponent
+  bne     LOCAL_LABEL(inf_or_nan)
+
+  // If we didn't take that branch, we have a denormal or zero. Zeroes are
+  // likely to be common, so we'd prefer to handle those with highest priority.
+  //
+  // r3 = (input XOR (input << 1)) will take the values 0 or 0x80000000 for a
+  // zero input. So it contains precisely the right value to return in xh.
+  //
+  // The BICS here combines the zeroing of xl with the test of r3, because it
+  // sets Z if and only if the input was one of those two values, and if so,
+  // sets xl=0.
+  //
+  // Unfortunately this has the side effect of clobbering xl in the case where
+  // we _don't_ take the early return, so now we've lost our verbatim copy of
+  // the low 3 input bits! On the denormal-handling path we'll have to recover
+  // those from r3 more awkwardly. But denormal handling is rare, and slow
+  // anyway, so it's worth the awkwardness to save a cycle in the much more
+  // common case of a zero input.
+  bics    xl, r3, #0x80000000   // EQ if output is zero
+  moveq   xh, r3                // if so, copy input sign into xh
+  bxeq    lr                    // and return
+
+  // Now we know we're dealing with a denormal, so we need to recover the whole
+  // input mantissa. Most of it is in r12, but those last three bits now need
+  // to be reconstructed from r3 by using part of the shift+EOR trick shown
+  // above. We only need the left shifts by 1 and by 2, because the other three
+  // don't affect the bottom 3 bits at all.
+  eor     r3, r3, r3, lsl #2
+  eor     r3, r3, r3, lsl #1
+  and     r3, r3, #7
+
+  // Now r3 contains just the low bits of the mantissa. The rest of the
+  // mantissa is in r12, shifted right by 3 bits, so this instruction rebuilds
+  // the entire input mantissa in xh. (The exponent field is known to be zero,
+  // and the sign bit at the top of r12 is discarded by the left shift.)
+  orr     xh, r3, r12, lsl #3
+
+  // Renormalize that input mantissa so that its high bit is at the top of the
+  // word.
+  clz     r2, xh
+  lsl     xh, xh, r2
+
+  // Compute the right sign + exponent to go with that mantissa.
+  //
+  // If the input mantissa had had only its low bit set, then the input float
+  // would be 2^-149, which has a double-precision exponent of 0x36a. In that
+  // situation we'd have r2 = 31 (output from the CLZ). So we need the output
+  // exponent to be (0x389 - r2). But the leading bit of the mantissa will
+  // increment the exponent field when we add them together, so in fact we want
+  // to calculate (0x388 - r2). That's particularly convenient, because 0x388
+  // fits in an AArch32 immediate field!
+  and     r3, r12, #0x80000000  // get the sign bit from the top of r12
+  add     r3, r3, #0x388 << 20  // add the exponent bias as calculated above
----------------
compnerd wrote:

```suggestion
  add     r3, r3, #(0x388 << 20)  // add the exponent bias as calculated above
```

https://github.com/llvm/llvm-project/pull/179926


More information about the llvm-commits mailing list