[gcc r17-2582] AVR: Add 64-bit fixed-point multiplications to libgcc.

Georg-Johann Lay via Gcc-cvs <[email protected]>
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
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 \
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.