This patch adds (un)saturated 64-bit fixed-point multiplications
to libgcc. The saturating functions are just aliased of the
vanilla versions, which are also saturating.
Ok for trunk?
Johann
--
libgcc/
* config/avr/t-avr (FUNCS_notiny): Add: _umulditi3_raw,
_muluda3, _muluta3, _muludq3, _mulda3 _multa3 _muldq3,
_muluQ64_tail, _mulQ64_work.
* config/avr/asm-defs.h (mov8, ENTRY): New .macro's.
* config/avr/lib1funcs-fixed.S (__umulditi3_raw, __muluda3)
(__muluta3, __muludq3, __muluQ64_tail, __muldq3, __mulda3)
(__multa3, __mulQ64_work): New DEFUN's.
gcc/testsuite/
* gcc.target/avr/fx24-mul.c: New test.diff --git a/gcc/testsuite/gcc.target/avr/fx24-mul.c b/gcc/testsuite/gcc.target/avr/fx24-mul.c
new file mode 100644
index 00000000000..6cc0d75b8cb
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx24-mul.c
@@ -0,0 +1,140 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+// !!! Requires the fx64 <-> float conversions from AVR-LibC.
+
+#include <stdfix.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <avr/pgmspace.h>
+
+#define NI __attribute((noipa))
+
+typedef long accum lk_t;
+typedef long long accum llk_t;
+typedef long long fract llr_t;
+
+typedef unsigned long accum ulk_t;
+typedef unsigned long long accum ullk_t;
+typedef unsigned long long fract ullr_t;
+
+// Values are in fmin <= x < fmax.
+#define fmax_llr 1.0f
+#define fmin_llr (-fmax_llr)
+#define fmax_ullr fmax_llr
+#define fmin_ullr 0.0f
+
+#define fmax_lk 0x1.0p32f
+#define fmin_lk (-fmax_lk)
+#define fmax_ulk fmax_lk
+#define fmin_ulk 0.0f
+
+#define fmax_llk 0x1.0p16f
+#define fmin_llk (-fmax_llk)
+#define fmax_ullk fmax_llk
+#define fmin_ullk 0.0f
+
+#define UMAX 0xffffffffffffffff
+#define SMAX 0x7fffffffffffffff
+#define SMIN 0x8000000000000000
+
+// Values are in min <= x <= max.
+#define max_lk lkbits (SMAX)
+#define min_lk lkbits (SMIN)
+#define max_ulk ulkbits (UMAX)
+#define min_ulk ulkbits (0)
+
+#define max_llk llkbits (SMAX)
+#define min_llk llkbits (SMIN)
+#define max_ullk ullkbits (UMAX)
+#define min_ullk ullkbits (0)
+
+#define max_llr llrbits (SMAX)
+#define min_llr llrbits (SMIN)
+#define max_ullr ullrbits (UMAX)
+#define min_ullr ullrbits (0)
+
+#define id_lk 10
+#define id_ulk 20
+#define id_llk 30
+#define id_ullk 40
+#define id_llr 50
+#define id_ullr 60
+
+#define MK_TEST(fx) \
+ NI bool in_range_##fx (float x) \
+ { \
+ return x < fmax_##fx && x >= fmin_##fx; \
+ } \
+ \
+ NI void test_mul_##fx (float a, float b) \
+ { \
+ if (!in_range_##fx (a)) \
+ return; \
+ if (!in_range_##fx (b)) \
+ return; \
+ float f = a * b; \
+ __asm ("" : "+r" (f)); \
+ fx##_t ab = ((fx##_t) a) * (fx##_t) b; \
+ if (f < fmin_##fx) \
+ { \
+ if (ab != min_##fx) \
+ exit (id_##fx + 1); \
+ return; \
+ } \
+ if (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_mul (float a, float b)
+{
+ test_mul_lk (a, b);
+ test_mul_ulk (a, b);
+
+ test_mul_llk (a, b);
+ test_mul_ullk (a, b);
+
+ test_mul_llr (a, b);
+ test_mul_ullr (a, b);
+}
+
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
+
+// Results / arguments must be representable as float, so no rounding occurs.
+// No-overflow results must be representable as fixed, so no rounding occurs.
+const PROGMEM float fvals[] =
+ {
+ 0.0,
+ +1.0, +2.0, +0.5, +0xff.0p0, +0xf.fp0, +0x0.ffp0, +0xcd.0p12, +0x0.0a1p0,
+ -1.0, -2.0, -0.5, -0xff.0p0, -0xf.fp0, -0x0.ffp0, -0xcd.0p12, -0x0.0a1p0,
+ };
+
+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_mul (fa, fb);
+ }
+}
+
+int main (void)
+{
+ test ();
+ return 0;
+}
diff --git a/libgcc/config/avr/asm-defs.h b/libgcc/config/avr/asm-defs.h
index 62879464f9d..970428f01ce 100644
--- a/libgcc/config/avr/asm-defs.h
+++ b/libgcc/config/avr/asm-defs.h
@@ -179,6 +179,19 @@
.endm
+.macro mov8 dst, src
+ REGNO ..mov8.dst, \dst
+ REGNO ..mov8.src, \src
+ .if ..mov8.dst < ..mov8.src
+ mov4 ..mov8.dst+0, ..mov8.src+0
+ mov4 ..mov8.dst+4, ..mov8.src+4
+ .else
+ mov4 ..mov8.dst+4, ..mov8.src+4
+ mov4 ..mov8.dst+0, ..mov8.src+0
+ .endif
+.endm
+
+
;; Negate a 2-byte value held in consecutive registers.
.macro NEG2 reg
com \reg+1
@@ -230,6 +243,12 @@
.endfunc
.endm
+.macro ENTRY name
+ .global \name
+ .type \name, @function
+ .size \name, 0
+ \name:
+.endm
#ifndef __AVR_TINY__
diff --git a/libgcc/config/avr/lib1funcs-fixed.S b/libgcc/config/avr/lib1funcs-fixed.S
index 4b77ee2dd56..8deb21096fd 100644
--- a/libgcc/config/avr/lib1funcs-fixed.S
+++ b/libgcc/config/avr/lib1funcs-fixed.S
@@ -906,6 +906,382 @@ DEFUN __ssmulsa3
#undef C7
#undef SS
+/*******************************************************
+ Fixed 64 x 64 saturated Multiplication
+*******************************************************/
+
+.pushsection .text.libgcc.mul64, "ax", @progbits
+
+#define A0 18
+#define A1 A0+1
+#define A2 A0+2
+#define A3 A0+3
+#define A4 A0+4
+#define A5 A0+5
+#define A6 A0+6
+#define A7 A0+7
+
+#define B0 10
+#define B1 B0+1
+#define B2 B0+2
+#define B3 B0+3
+#define B4 B0+4
+#define B5 B0+5
+#define B6 B0+6
+#define B7 B0+7
+
+#define C0 10
+#define C1 C0+1
+#define C2 C0+2
+#define C3 C0+3
+#define C4 C0+4
+#define C5 C0+5
+#define C6 C0+6
+#define C7 C0+7
+#define C8 2
+#define C9 C8+1
+#define Ca C8+2
+#define Cb C8+3
+#define Cc C8+4
+#define Cd C8+5
+#define Ce C8+6
+#define Cf C8+7
+
+#define M0 18
+#define M1 M0+1
+#define M2 M0+2
+#define M3 M0+3
+#define M4 M0+4
+#define M5 M0+5
+#define M6 M0+6
+#define M7 M0+7
+
+#if defined (L_umulditi3_raw)
+
+.macro .Mul a, b
+ wmov r24, Y
+ adiw r24, \a
+ wmov r30, Y
+ adiw r30, \b
+ rcall __umulsidi3_pBE
+.endm
+
+;;; uint128_t R10[16] = (uint128_t) R10[8] * R18[8]
+;;; No regs are restored except Y.
+DEFUN __umulditi3_raw
+ push A0
+ push A1
+ push A2
+ push A3
+ push A4
+ push A5
+ push A6
+ push A7
+ do_prologue_saves 10
+ ;; Now we have the following stack layout:
+ ;; Y+1..Y+2 saved Y
+ ;; Y+3..Y+10 B[8] as big endian bytes
+ ;; Y+11..Y+18 A[8] as big endian bytes
+ A.hi = 11
+ A.lo = A.hi + 4
+ B.hi = 3
+ B.lo = B.hi + 4
+
+ ;; In terms of uint32_t we have to perform 4 multiplications:
+
+ .Mul A.lo, B.lo
+ mov8 C0, M0
+
+ .Mul A.hi, B.hi
+ mov8 C8, M0
+
+ .Mul A.hi, B.lo
+ rcall __umulditi3_12
+
+ .Mul A.lo, B.hi
+ rcall __umulditi3_12
+
+ ;; Move result into place.
+ mov8 C0 + 0x8, C8
+
+ ;; Epilogue: We pushed 18 bytes. Let epilogue_restores pop
+ ;; them by adjusting SP accordingly.
+ ldi r30, 18
+ ;; Pretend we pushed only 2 bytes (Y) so that the low part of
+ ;; the return value in R10..R17 won't be overridden by LDDs.
+ XJMP __epilogue_restores__ + ((18 - 2) * 2)
+
+__umulditi3_12:
+ ADD C4, M0
+ adc C5, M1
+ adc C6, M2
+ adc C7, M3
+ adc C8, M4
+ adc C9, M5
+ adc Ca, M6
+ adc Cb, M7
+ adc Cc, __zero_reg__
+ adc Cd, __zero_reg__
+ adc Ce, __zero_reg__
+ adc Cf, __zero_reg__
+ ret
+
+;;; uint64_t R18 = uint32_t BE *Z * uint32_t BE *W
+__umulsidi3_pBE:
+ ldd A0, Z + 3
+ ldd A1, Z + 2
+ ldd A2, Z + 1
+ ldd A3, Z + 0
+ wmov r30, r24
+ ldd A4, Z + 3
+ ldd A5, Z + 2
+ ldd A6, Z + 1
+ ldd A7, Z + 0
+ XJMP __umulsidi3
+ENDF __umulditi3_raw
+#endif /* L_umulditi3_raw */
+
+#if defined (L_muluda3)
+;;; uint128_t R10 = umulsidi3 (uint32_t R22, uint32_t R18)
+FALIAS __usmuluda3
+DEFUN __muluda3
+ clt
+ENTRY __muluda3.2
+ do_prologue_saves 18
+ bld __tmp_reg__, 7
+ push __tmp_reg__
+ XCALL __umulditi3_raw
+ or A7, A6
+ or A7, A5
+ or A7, A4
+ mov8 A0, r14
+ mov r27, r13
+ XJMP __muluQ64_tail
+ENDF __muluda3
+#endif /* L_muluda3 */
+
+#if defined (L_muluta3)
+FALIAS __usmuluta3
+DEFUN __muluta3
+ clt
+ENTRY __muluta3.2
+ do_prologue_saves 18
+ bld __tmp_reg__, 7
+ push __tmp_reg__
+ XCALL __umulditi3_raw
+ or A7, A6
+ mov8 A0, r16
+ mov r27, r15
+ XJMP __muluQ64_tail
+ENDF __muluta3
+#endif /* L_muluta3 */
+
+#if defined (L_muludq3)
+FALIAS __usmuludq3
+DEFUN __muludq3
+ clt
+ENTRY __muludq3.2
+ do_prologue_saves 18
+ bld __tmp_reg__, 7
+ push __tmp_reg__
+ XCALL __umulditi3_raw
+ sez
+ mov r27, r17
+ XJMP __muluQ64_tail
+ENDF __muludq3
+#endif /* L_muludq3 */
+
+#if defined (L_muluQ64_tail)
+;;; Inputs:
+;;; R27.7 = LSB (bit -1)
+;;; Z = 0 -> Overflow
+;;; Stack.7: On behalf of signed routines?
+DEFUN __muluQ64_tail
+ pop __tmp_reg__
+ brne 1f
+ ;; tmp.7 = for signed?
+ ;; For signed: return LSB in T, no rounding
+ bst r27, 7
+ tst __tmp_reg__
+ brmi 9f
+
+ ;; For unsigned: round
+ lsl r27
+ brcc 9f
+ ldi r26, 1
+ XCALL __adddi3_s8
+ brcs 1f
+9: do_epilogue_restores 18
+
+1: ;; Unsigned overflow: Saturate to 0xff...
+ sec
+ XCALL __sbc_8
+ set
+ rjmp 9b
+ENDF __muluQ64_tail
+#endif /* L_muluQ64_tail */
+
+#if defined (L_muldq3)
+FALIAS __ssmuldq3
+DEFUN __muldq3
+ ldi r30, lo8(gs(__muludq3.2))
+ ldi r31, hi8(gs(__muludq3.2))
+ XJMP __mulQ64_work
+ENDF __muldq3
+#endif /* L_muldq3 */
+
+#if defined (L_mulda3)
+FALIAS __ssmulda3
+DEFUN __mulda3
+ ldi r30, lo8(gs(__muluda3.2))
+ ldi r31, hi8(gs(__muluda3.2))
+ XJMP __mulQ64_work
+ENDF __mulda3
+#endif /* L_mulda3 */
+
+#if defined (L_multa3)
+FALIAS __ssmulta3
+DEFUN __multa3
+ ldi r30, lo8(gs(__muluta3.2))
+ ldi r31, hi8(gs(__muluta3.2))
+ XJMP __mulQ64_work
+ENDF __multa3
+#endif /* L_multa3 */
+
+#if defined (L_mulQ64_work)
+DEFUN __mulQ64_work
+ mov __tmp_reg__, A7
+ eor __tmp_reg__, B7
+ push __tmp_reg__
+
+ ;; A = |A|
+ tst A7
+ brpl 1f
+ XCALL __ssneg_8
+1: ;; Stash away |A|
+ wmov r26, A6
+ push A5
+ push A4
+ push A3
+ push A2
+ push A1
+ push A0
+
+ ;; A = |B|
+ mov8 A0, B0
+ tst A7
+ brpl 2f
+ XCALL __ssneg_8
+2: ;; Restore B = |A|
+ wmov B6, r26
+ pop B0
+ pop B1
+ pop B2
+ pop B3
+ pop B4
+ pop B5
+
+ XICALL
+
+ ;; C = T = LSB (bit -1) from the unsigned mult.
+ bld __tmp_reg__, 7
+ rol __tmp_reg__
+ ;; T = result sign
+ pop __tmp_reg__
+ bst __tmp_reg__, 7
+ ;; Adjust for signed Q formats that have one FBIT less.
+ rol A0
+ rol A1
+ rol A2
+ rol A3
+ rol A4
+ rol A5
+ rol A6
+ rol A7
+ brts .Lneg
+
+ ;; Result must be is >= 0
+ ;; C = 1: Positive overflow
+ brcs .Lsaturate
+ ;; A < 0: Positive overflow
+ sec
+ brmi .Lsaturate
+ ret
+
+.L0x80:
+ ;; Return 0x80..
+ clc
+.Lsaturate:
+ ;; C = 1 -> 0x7f...
+ ;; C = 0 -> 0x80...
+ XCALL __sbc_8
+ subi A7, 0x80
+ ret
+
+.Lneg:
+ ;; Result must be <= 0
+ brcs .L0x80
+ XCALL __negdi2
+ brmi 9f
+ ;; Values that are > 0 after the negation are overflow.
+ sbiw A6, 0
+ sbci A5, 0
+ sbci A4, 0
+ sbci A3, 0
+ sbci A2, 0
+ sbci A1, 0
+ sbci A0, 0
+ brne .L0x80
+9: ret
+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
+#undef C3
+#undef C4
+#undef C5
+#undef C6
+#undef C7
+#undef C8
+#undef C9
+#undef Ca
+#undef Cb
+#undef Cc
+#undef Cd
+#undef Ce
+#undef Cf
+
+#undef M0
+#undef M1
+#undef M2
+#undef M3
+#undef M4
+#undef M5
+#undef M6
+#undef M7
+
+.popsection ; .text.libgcc.mul64
+
#endif /* ! AVR_TINY */
/*******************************************************
diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr
index 9b05c8575b6..a1651b9692f 100644
--- a/libgcc/config/avr/t-avr
+++ b/libgcc/config/avr/t-avr
@@ -94,6 +94,10 @@ FUNCS_notiny += \
_fractsfsq _fractsfusq _fractsfsa _fractsfusa \
_mulsa3 _mulusa3 \
_usmulusa3 _ssmulsa3 \
+ _umulditi3_raw \
+ _muluda3 _muluta3 _muludq3 \
+ _mulda3 _multa3 _muldq3 \
+ _muluQ64_tail _mulQ64_work \
_divsa3 _udivusa3 \
_clr_8 \
_ssneg_4 _ssneg_8 \