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.
--- /dev/null
+/* { 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;
+}
--- /dev/null
+/* { 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;
+}
--- /dev/null
+/* { 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;
+}
// !!! 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) \
// !!! 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)
{
#define UMAX 0xffffffffffffffff
#define SMAX 0x7fffffffffffffff
#define SMIN 0x8000000000000000
+#define X80 0x8000000000000000
// Values are in min <= x <= max.
#define max_lk lkbits (SMAX)
#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
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
.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 */
/*******************************************************
_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 \