https://gcc.gnu.org/g:8080c7750f4c3383036cef8e5f3344929dcb7e97
commit r17-2582-g8080c7750f4c3383036cef8e5f3344929dcb7e97 Author: Georg-Johann Lay <[email protected]> Date: Tue Jul 21 12:02:40 2026 +0200 AVR: Add 64-bit fixed-point multiplications to libgcc. This patch adds (un)saturated 64-bit fixed-point multiplications to libgcc. The saturating functions are just aliases of the vanilla versions, which are also saturating. libgcc/ * config/avr/t-avr (FUNCS_notiny): Add: _umulditi3_raw, _muluda3, _muluta3, _muludq3, _mulda3 _multa3 _muldq3, _muluQ64_tail, _mulQ64_work, _divsa3 _udivusa3. * 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: --- gcc/testsuite/gcc.target/avr/fx24-mul.c | 140 ++++++++++++ libgcc/config/avr/asm-defs.h | 19 ++ libgcc/config/avr/lib1funcs-fixed.S | 384 ++++++++++++++++++++++++++++++++ libgcc/config/avr/t-avr | 4 + 4 files changed, 547 insertions(+) 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 000000000000..6cc0d75b8cb0 --- /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 cf943f046e9a..aa9bf474e802 100644 --- a/libgcc/config/avr/asm-defs.h +++ b/libgcc/config/avr/asm-defs.h @@ -194,6 +194,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 @@ -245,6 +258,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 0414b3a2eed9..02fc0b9fe699 100644 --- a/libgcc/config/avr/lib1funcs-fixed.S +++ b/libgcc/config/avr/lib1funcs-fixed.S @@ -906,6 +906,390 @@ ENDF __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| + .call_if_neg A7, __ssneg_8 + + ;; Stash away |A| + wmov r26, A6 +#ifdef __AVR_HAVE_MUL__ + wmov r0, A4 +#else + push A5 + push A4 +#endif + push A3 + push A2 + push A1 + push A0 + + ;; A = |B| + mov8 A0, B0 + .call_if_neg A7, __ssneg_8 + + ;; Restore B = |A| + wmov B6, r26 + pop B0 + pop B1 + pop B2 + pop B3 +#ifdef __AVR_HAVE_MUL__ + wmov B4, r0 + ;; No need to clear zero_reg since it won't be used until the + ;; next MUL in __umulditi3_raw -> __umulsidi3 -> __umulhisi3. +#else + pop B4 + pop B5 +#endif + + XICALL ; __muluxx3.2 + + ;; 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 2f9a2efc3cf4..df85485e0a79 100644 --- a/libgcc/config/avr/t-avr +++ b/libgcc/config/avr/t-avr @@ -95,6 +95,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 \
