From: Georg-Johann Lay Date: Tue, 28 Jul 2026 13:50:14 +0000 (+0200) Subject: AVR: Add 64-bit fixed-point divisions to libgcc. X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=eb738824d502d5f6da77451d5105091b8a7db92d;p=thirdparty%2Fgcc.git AVR: Add 64-bit fixed-point divisions to libgcc. 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. --- 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 index 00000000000..760171e7ebc --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/fx64-div-1.c @@ -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 index 00000000000..24cccf742eb --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/fx64-div-2.c @@ -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 index 00000000000..66434b3bb7e --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/fx64-div-3.c @@ -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; +} diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul-1.c b/gcc/testsuite/gcc.target/avr/fx64-mul-1.c index fb342b3cce7..68fd0dda0bb 100644 --- a/gcc/testsuite/gcc.target/avr/fx64-mul-1.c +++ b/gcc/testsuite/gcc.target/avr/fx64-mul-1.c @@ -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) \ diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul-2.c b/gcc/testsuite/gcc.target/avr/fx64-mul-2.c index a679c010928..266c7579da8 100644 --- a/gcc/testsuite/gcc.target/avr/fx64-mul-2.c +++ b/gcc/testsuite/gcc.target/avr/fx64-mul-2.c @@ -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) { diff --git a/gcc/testsuite/gcc.target/avr/fx64-mul.h b/gcc/testsuite/gcc.target/avr/fx64.h similarity index 88% rename from gcc/testsuite/gcc.target/avr/fx64-mul.h rename to gcc/testsuite/gcc.target/avr/fx64.h index 5c935d58c1a..c7b968a901b 100644 --- a/gcc/testsuite/gcc.target/avr/fx64-mul.h +++ b/gcc/testsuite/gcc.target/avr/fx64.h @@ -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 diff --git a/libgcc/config/avr/lib1funcs-fixed.S b/libgcc/config/avr/lib1funcs-fixed.S index f1b61e12e97..2996a5960eb 100644 --- a/libgcc/config/avr/lib1funcs-fixed.S +++ b/libgcc/config/avr/lib1funcs-fixed.S @@ -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 */ /******************************************************* diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr index 657c61a5666..edd0e8f8698 100644 --- a/libgcc/config/avr/t-avr +++ b/libgcc/config/avr/t-avr @@ -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 \