]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
AVR: Add 64-bit fixed-point divisions to libgcc.
authorGeorg-Johann Lay <avr@gjlay.de>
Tue, 28 Jul 2026 13:50:14 +0000 (15:50 +0200)
committerGeorg-Johann Lay <avr@gjlay.de>
Tue, 28 Jul 2026 13:53:38 +0000 (15:53 +0200)
This adds 64-bit saturated fixed-point divisions to lib1funcs-fixed.S.
The ordinary divisions are just aliases of the saturating ones.
Rounding is towards zero.

libgcc/
* config/avr/t-avr (FUNCS_notiny): Add _udivuQ64_common, _udivuda3,
_udivuta3, _udivudq3, _divda3, _divta3, _divdq3, _divQ64_tail.
* config/avr/lib1funcs-fixed.S (__udivuta3, __usdivuta3)
(__udivudq3, __usdivudq3, __udivuda3, __usdivuda3)
(__udivuQ64_common, __divda3, __ssdivda3, __divta3, __ssdivta3)
(__divdq3, __ssdivdq3, __divQ64_tail): New functions.
gcc/testsuite/
* gcc.target/avr/fx64-div-1.c: New test.
* gcc.target/avr/fx64-div-2.c: New test.
* gcc.target/avr/fx64-div-3.c: New test.
* gcc.target/avr/fx64-mul.h: Rename to...
* gcc.target/avr/fx64.h: ...this.
* gcc.target/avr/fx64-mul-1.c: Same.
* gcc.target/avr/fx64-mul-2.c: Same.

gcc/testsuite/gcc.target/avr/fx64-div-1.c [new file with mode: 0644]
gcc/testsuite/gcc.target/avr/fx64-div-2.c [new file with mode: 0644]
gcc/testsuite/gcc.target/avr/fx64-div-3.c [new file with mode: 0644]
gcc/testsuite/gcc.target/avr/fx64-mul-1.c
gcc/testsuite/gcc.target/avr/fx64-mul-2.c
gcc/testsuite/gcc.target/avr/fx64.h [moved from gcc/testsuite/gcc.target/avr/fx64-mul.h with 88% similarity]
libgcc/config/avr/lib1funcs-fixed.S
libgcc/config/avr/t-avr

diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-1.c b/gcc/testsuite/gcc.target/avr/fx64-div-1.c
new file mode 100644 (file)
index 0000000..760171e
--- /dev/null
@@ -0,0 +1,84 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+#define MK_TEST(fx)                                \
+  NI bool in_range_##fx (float x)                  \
+  {                                                \
+    return x < fmax_##fx && x >= fmin_##fx;        \
+  }                                                \
+                                                   \
+  NI void test_div_##fx (float a, float b)         \
+  {                                                \
+    if (!in_range_##fx (a))                        \
+      return;                                      \
+    if (!in_range_##fx (b))                        \
+      return;                                      \
+    float f = b ? a / b : 0.0f;                            \
+    __asm volatile ("" : "+r" (f));                \
+    fx##_t ax = (fx##_t) a;                        \
+    fx##_t bx = (fx##_t) b;                        \
+    fx##_t ab = ax / bx;                           \
+    if ((b == 0 && a < 0) || (b && f < fmin_##fx))  \
+      {                                                    \
+       if (ab != min_##fx)                         \
+         exit (id_##fx + 1);                       \
+       return;                                     \
+      }                                                    \
+    if ((b == 0 && a >= 0) || (b && f > fmax_##fx)) \
+      {                                                    \
+       if (ab != max_##fx)                         \
+         exit (id_##fx + 2);                       \
+       return;                                     \
+      }                                                    \
+    if (f != (float) ab)                           \
+      exit (id_##fx + 3);                          \
+  }
+
+MK_TEST (lk)
+MK_TEST (ulk)
+MK_TEST (llk)
+MK_TEST (ullk)
+MK_TEST (llr)
+MK_TEST (ullr)
+
+NI void test_div (float a, float b)
+{
+  test_div_lk (a, b);
+  test_div_ulk (a, b);
+
+  test_div_llk (a, b);
+  test_div_ullk (a, b);
+
+  test_div_llr (a, b);
+  test_div_ullr (a, b);
+}
+
+// Results / args must be representable as float, so no rounding occurs.
+// Non-overflow results must be representable as fixed, so no rounding occurs.
+const PROGMEM float fvals[] =
+  {
+    0.0,
+    +1.0, +2.0, +0.5, +0x1p10, +0x1p12, +0x1p-14,
+    -1.0, -2.0, -0.5, -0x1p10, -0x1p12, -0x1p-14,
+  };
+
+NI void test (void)
+{
+  for (uint8_t a = 0; a < ARRAY_SIZE (fvals); ++a)
+    for (uint8_t b = 0; b < ARRAY_SIZE (fvals); ++b)
+      {
+       float fa = pgm_read_float (&fvals[a]);
+       float fb = pgm_read_float (&fvals[b]);
+       test_div (fa, fb);
+      }
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-2.c b/gcc/testsuite/gcc.target/avr/fx64-div-2.c
new file mode 100644 (file)
index 0000000..24cccf7
--- /dev/null
@@ -0,0 +1,74 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+NI void test_lk (uint64_t a, float fb, uint64_t res)
+{
+  lk_t b = (lk_t) fb;
+  if (lkbits (a) / b != lkbits (res))
+    exit (id_lk + 4);
+}
+
+NI void test_ulk (uint64_t a, float fb, uint64_t res)
+{
+  ulk_t b = (ulk_t) fb;
+  if (ulkbits (a) / b != ulkbits (res))
+    exit (id_ulk + 4);
+}
+
+NI void test2_lk (uint64_t a, uint64_t b, uint64_t res)
+{
+  if (lkbits (a) / lkbits (b) != lkbits (res))
+    exit (id_lk + 5);
+}
+
+NI void test2_ulk (uint64_t a, uint64_t b, uint64_t res)
+{
+  if (ulkbits (a) / ulkbits (b) != ulkbits (res))
+    exit (id_ulk + 5);
+}
+
+NI void test (void)
+{
+  test_ulk (+0xaabbccddeeff7799, +0x11.0p0f, +0x0a0b0c0d0e0f0709);
+  test_lk  (-0x77bbccddeeff7799, +0x11.0p0f, -0x070b0c0d0e0f0709);
+  test_lk  (+0x77bbccddeeff7799, -0x11.0p0f, -0x070b0c0d0e0f0709);
+  test_lk  (-0x77bbccddeeff7799, -0x11.0p0f, +0x070b0c0d0e0f0709);
+  test_lk  (-0x7ffe02468ace2468, 2.0f, -0x3fff012345671234);
+  test_ulk (+0x7ffe02468ace2468, 2.0f, +0x3fff012345671234);
+  test_ulk (+0xc42eca8642eca955, 2.0f, +0x62176543217654aa);
+  test_lk  (-0x642eca8642eca955, 2.0f, -0x32176543217654aa);
+  test_ulk (+0xffffffffffffffff, 2.0f, +0x7fffffffffffffff);
+  test_lk  (-0x7fffffffffffffff, 2.0f, -0x3fffffffffffffff);
+  test_lk  (+0, 0.0f, SMAX);
+  test_lk  (+1, 0.0f, SMAX);
+  test_lk  (-1, 0.0f, SMIN);
+  test_ulk (+0, 0.0f, UMAX);
+  test_ulk (+1, 0.0f, UMAX);
+  test_lk  (SMAX - 1000, +0x0.fffp0f, SMAX);
+  test_lk  (SMIN + 1000, +0x0.fffp0f, SMIN);
+  test_lk  (SMAX - 1000, -0x0.fffp0f, SMIN);
+  test_lk  (SMIN + 1000, -0x0.fffp0f, SMAX);
+  test_ulk (UMAX - 1000, +0x0.fffp0f, UMAX);
+  test_ulk (UMAX - 1000, +0x0.fffp0f, UMAX);
+
+  test2_ulk (X80 + 1, X80, ulk_1);
+  test2_ulk (X80, X80 + 1, ulk_1 - 1);
+  test2_ulk (UMAX - 1, UMAX, ulk_1 - 1);
+  test2_lk  (SMAX, SMIN, -(lk_1 - 1));
+  test2_lk  (SMIN, SMAX, -lk_1);
+  test2_lk  (SMAX, SMAX, lk_1);
+  test2_lk  (SMAX, SMAX, lk_1);
+  test2_ulk (SMIN, SMIN, ulk_1);
+  test2_ulk (1, 1, ulk_1);
+  test2_lk  (1, 1, lk_1);
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx64-div-3.c b/gcc/testsuite/gcc.target/avr/fx64-div-3.c
new file mode 100644 (file)
index 0000000..66434b3
--- /dev/null
@@ -0,0 +1,63 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include "fx64.h"
+
+#define MK_TEST(fx)                                            \
+  NI void test_div_##fx (uint64_t a, float fb, uint64_t r)     \
+  {                                                            \
+    fx##_t ab = fx##bits (a) / (fx##_t) fb;                    \
+    if (ab != fx##bits (r))                                    \
+      exit (id_##fx + 6);                                      \
+    return;                                                    \
+  }
+
+MK_TEST (lk)
+MK_TEST (ulk)
+MK_TEST (llk)
+MK_TEST (ullk)
+
+NI void test1_div (uint64_t a, float fb, uint64_t r)
+{
+  test_div_ulk (a, fb, r);
+  test_div_ullk (a, fb, r);
+  if ((a & X80) == 0)
+    {
+      test_div_lk (a, fb, r);
+      test_div_llk (a, fb, r);
+    }
+}
+
+NI void test2_div (uint64_t a, float fb, uint64_t r)
+{
+  test_div_ulk (a, fb, r);
+  test_div_ullk (a, fb, r);
+  if ((r & X80) == 0)
+    {
+      test_div_lk (a, fb, r);
+      test_div_llk (a, fb, r);
+    }
+  else
+    {
+      test_div_lk (a, fb, SMAX);
+      test_div_llk (a, fb, SMAX);
+    }
+}
+
+NI void test (void)
+{
+  for (uint64_t a = (uint64_t) 0xff01 << (64 - 16); a; a >>= 1)
+    {
+      test1_div (a, 2.0f, a >> 1);
+      if ((a & X80) == 0)
+       test2_div (a, 0.5f, a << 1);
+    }
+}
+
+int main (void)
+{
+  test ();
+  return 0;
+}
index fb342b3cce78daacd0e36f860a8d28643a7f66ec..68fd0dda0bbf81b7f4b0283a27075858bfaafff0 100644 (file)
@@ -3,7 +3,7 @@
 
 // !!! Requires the fx64 <-> float conversions from AVR-LibC.
 
-#include "fx64-mul.h"
+#include "fx64.h"
 
 #define MK_TEST(fx)                                \
   NI bool in_range_##fx (float x)                  \
index a679c010928f640aa59fb49870bbc380c1bc9bde..266c7579da85c044bb0c579f2d7a4503d0de3fcf 100644 (file)
@@ -3,7 +3,7 @@
 
 // !!! Requires the fx64 <-> float conversions from AVR-LibC.
 
-#include "fx64-mul.h"
+#include "fx64.h"
 
 NI void test_mul (float fa, float fb, uint64_t res)
 {
similarity index 88%
rename from gcc/testsuite/gcc.target/avr/fx64-mul.h
rename to gcc/testsuite/gcc.target/avr/fx64.h
index 5c935d58c1a789f8c65af5b90221be53aaac586f..c7b968a901beecfc122c40714ab8267bcf1b51a8 100644 (file)
@@ -35,6 +35,7 @@ typedef unsigned long long fract ullr_t;
 #define UMAX 0xffffffffffffffff
 #define SMAX 0x7fffffffffffffff
 #define SMIN 0x8000000000000000
+#define X80  0x8000000000000000
 
 // Values are in min <= x <= max.
 #define max_lk lkbits (SMAX)
@@ -52,6 +53,11 @@ typedef unsigned long long fract ullr_t;
 #define max_ullr ullrbits (UMAX)
 #define min_ullr ullrbits (0)
 
+#define lk_1   (1ull << __DA_FBIT__)
+#define ulk_1  (1ull << __UDA_FBIT__)
+#define llk_1  (1ull << __TA_FBIT__)
+#define ullk_1 (1ull << __UTA_FBIT__)
+
 #define id_lk   10
 #define id_ulk  20
 #define id_llk  30
index f1b61e12e976d44153c05840634e5ef47a6cee0b..2996a5960eb113f0cd1f2136635f3cb87920bddf 100644 (file)
@@ -1198,24 +1198,6 @@ DEFUN __mulQ64_work
 ENDF __mulQ64_work
 #endif /* L_mulQ64_work */
 
-#undef A0
-#undef A1
-#undef A2
-#undef A3
-#undef A4
-#undef A5
-#undef A6
-#undef A7
-
-#undef B0
-#undef B1
-#undef B2
-#undef B3
-#undef B4
-#undef B5
-#undef B6
-#undef B7
-
 #undef C0
 #undef C1
 #undef C2
@@ -1244,6 +1226,277 @@ ENDF __mulQ64_work
 
 .popsection ; .text.libgcc.mul64
 
+/*******************************************************
+    Fixed 64 / 64 saturated Divisions
+*******************************************************/
+
+;;; Cnt counts from 65 - BBB down to CStop = -FBIT.
+#define Cnt     r26
+#define CStop   r27
+#define BBB     24
+
+#if defined (L_udivuta3)
+;;; unsigned long [sat] accum
+DEFUN __udivuta3
+ENTRY __usdivuta3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UTA_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivuta3
+#endif /* L_udivuta3 */
+
+#if defined (L_udivudq3)
+;;; unsigned long long [sat] fract
+DEFUN __udivudq3
+ENTRY __usdivudq3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UDQ_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivudq3
+#endif /* L_udivudq3 */
+
+#if defined (L_udivuda3)
+;;; unsigned long [sat] accum
+DEFUN __udivuda3
+ENTRY __usdivuda3
+    push    r8
+    push    r9
+    push    r28
+    push    r29
+    ldi     CStop, -__UDA_FBIT__
+    XCALL   __udivuQ64_common
+    pop     r29
+    pop     r28
+    pop     r9
+    pop     r8
+    ret
+ENDF __udivuda3
+#endif /* L_udivuda3 */
+
+#if defined (L_udivuQ64_common)
+;;; Remainder
+#define M0  A5
+#define M1  A6
+#define M2  A7
+#define M3  28
+#define M4  M3+1
+#define M5  0
+#define M6  M5+1
+#define M7  9
+
+;;; Quotient
+#define Q0  30
+#define Q1  Q0+1
+#define Q2  8
+#define Q3  A0
+#define Q4  A1
+#define Q5  A2
+#define Q6  A3
+#define Q7  A4
+
+;;; Perform unsigned saturated 64-bit fixed-point division.
+;;; Rounds towards zero.  Division by 0 is saturated to 0xff..
+;;; R18  = R18  / R10
+;;; A[8] = A[8] / B[8]
+;;; CStop R27 is -FBIT.
+;;; No callee-saved regs are saved.
+;;; T is not touched.
+;;;
+;;; Registers overlap as follows:
+;;;                     A7  A6  A5  A4  A3  A2  A1  A0
+;;; M7  M6  M5  M4  M3  M2  M1  M0  Q7  Q6  Q5  Q4  Q3  Q2  Q1  Q0
+;;; r9  r1  r0  r29 r28 r25 r24 r23 r22 r21 r20 r19 r18 r8  r31 r30
+;;; This means the algorithm has a head start of BBB = 24 bits.
+;;; (32 bits is too much to pass the tests.)
+DEFUN __udivuQ64_common
+    ldi     Cnt, 65 - BBB
+    ;; M[7..3] = 0.
+    ;; Q[2..0] = 0.
+    sub     M5, M5              ; CLC
+    ;; M6 = 0 already.
+    wmov    r8, M5              ; M7, Q2
+    wmov    M3, M5              ; M4, M3
+    wmov    Q0, M5              ; Q1, Q0
+    rjmp .Lenter
+
+.Loop:
+    ;; Shift one more bit of A into M from the low end.
+    ;; When A's bits are exhausted, shift in 0s.
+    ROL  M0         $ rol  M1       $ rol  M2       $ rol  M3
+    rol  M4         $ rol  M5       $ rol  M6       $ rol  M7
+
+    ;; When M overflows then B will always fit.
+    brcs    .LsubB
+    CP   M0, B0     $ cpc  M1, B1   $ cpc  M2, B2   $ cpc  M3, B3
+    cpc  M4, B4     $ cpc  M5, B5   $ cpc  M6, B6   $ cpc  M7, B7
+    brlo    .LshiftQ
+
+.LsubB:
+    SUB  M0, B0     $ sbc  M1, B1   $ sbc  M2, B2   $ sbc  M3, B3
+    sbc  M4, B4     $ sbc  M5, B5   $ sbc  M6, B6   $ sbc  M7, B7
+
+    SEC
+    ROL  Q0
+    skip
+.LshiftQ:
+    LSL  Q0         $ rol  Q1       $ rol  Q2
+.Lenter:                                            $ rol  Q3
+    rol  Q4         $ rol  Q5       $ rol  Q6       $ rol  Q7
+
+    dec     Cnt
+    ;; Cnt >= 0: Feed one more bit of A (plus a final 0 for latency) into M.
+    brpl .Loop
+
+    cpse Cnt, CStop
+    ;; Cnt < 0: Feed 0s into M.
+    brcc .Loop
+
+    ;; Done
+    clr     __zero_reg__
+    ;; When quotient 1s drop out of Q, then it's overflow.
+    brcs .Loverflow
+
+    ;; Move result into place.
+    mov  A7, Q7     $ mov  A6, Q6   $ mov  A5, Q5   $ mov  A4, Q4
+    mov  A3, Q3     $ mov  A2, Q2   $                 wmov A0, Q0
+    ret
+
+.Loverflow:
+    ;; A = 0xff..
+    XJMP    __sbc_8
+ENDF __udivuQ64_common
+
+#undef Q0
+#undef Q1
+#undef Q2
+#undef Q3
+#undef Q4
+#undef Q5
+#undef Q6
+#undef Q7
+
+#undef M0
+#undef M1
+#undef M2
+#undef M3
+#undef M4
+#undef M5
+#undef M6
+#undef M7
+#endif /* L_udivuQ64_common */
+
+;;; Perform signed saturated 64-bit fixed-point division.
+;;; Division by 0 is saturated according to the sign of the dividend.
+;;; Rounds towards zero.
+
+#if defined (L_divda3)
+;;; signed long [sat] accum
+DEFUN __divda3
+ENTRY __ssdivda3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__DA_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divda3
+#endif /* L_divda3 */
+
+#if defined (L_divta3)
+;;; signed long long [sat] accum
+DEFUN __divta3
+ENTRY __ssdivta3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__TA_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divta3
+#endif /* L_divta3 */
+
+#if defined (L_divdq3)
+;;; signed long long [sat] fract
+DEFUN __divdq3
+ENTRY __ssdivdq3
+    ;; Save R8, R9, B and Y.
+    do_prologue_saves 12
+    ldi     CStop, -__DQ_FBIT__
+    XJMP    __divQ64_tail
+ENDF __divdq3
+#endif /* L_divdq3 */
+
+#if defined (L_divQ64_tail)
+DEFUN __divQ64_tail
+    mov     __tmp_reg__, A7
+    eor     __tmp_reg__, B7
+    bst     __tmp_reg__, 7
+
+    .call_if_neg  A7, __negdi2          ; A = |A|
+    .call_if_neg  B7, __negdi2_r10      ; B = |B|
+    XCALL   __udivuQ64_common           ; A /= B as unsigned
+
+    ;; r30.7 = result sign
+    bld     r30, 7
+
+    ;; Handle overflow.
+    tst     A7
+    ;; In the negative result case, A = 0x80.. is no overflow,
+    ;; but treating it as such keeps the value unchanged.
+    brmi .Lsaturate
+
+    ;; Handle result sign.
+    brtc 9f
+    XCALL   __negdi2
+
+9:  do_epilogue_restores 12
+
+.Lsaturate:
+    ;; r30.7 = 0 -> 0x7f..
+    ;; r30.7 = 1 -> 0x80..
+    cpi     r30, 0x80
+    ;; C = 1 -> 0x7f..
+    ;; C = 0 -> 0x80..
+    XCALL   __sbc_8
+    subi    A7, 0x80
+    rjmp    9b
+ENDF __divQ64_tail
+#endif /* L_divQ64_tail */
+
+#undef Cnt
+#undef CStop
+#undef BBB
+
+#undef A0
+#undef A1
+#undef A2
+#undef A3
+#undef A4
+#undef A5
+#undef A6
+#undef A7
+
+#undef B0
+#undef B1
+#undef B2
+#undef B3
+#undef B4
+#undef B5
+#undef B6
+#undef B7
+
 #endif /* ! AVR_TINY */
 
 /*******************************************************
index 657c61a566649e2b848b3bd637ac3c08f9763d7b..edd0e8f8698354e1395f2bd159bac90865229b47 100644 (file)
@@ -100,6 +100,8 @@ FUNCS_notiny += \
        _mulda3 _multa3 _muldq3 \
        _muluQ64_tail _mulQ64_work \
        _divsa3 _udivusa3 \
+       _udivuda3 _udivuta3 _udivudq3 _udivuQ64_common \
+       _divda3 _divta3 _divdq3 _divQ64_tail \
        _clr_8 \
        _ssneg_4 _ssneg_8 \
        _ssabs_4 _ssabs_8 \