[gcc r17-2988] AVR: Add 16-bit saturated fixed-point divisions.

Georg-Johann Lay via Gcc-cvs <[email protected]> Wed, 5 Aug 2026 15:35:28 +0000 (GMT)
Newsgroups gmane.comp.gcc.cvs
Message-ID <[email protected]>
https://gcc.gnu.org/g:c109d79f5bc4d4e2b65c6ee284a3d853c0173d7d

commit r17-2988-gc109d79f5bc4d4e2b65c6ee284a3d853c0173d7d
Author: Georg-Johann Lay <[email protected]>
Date:   Wed Aug 5 17:34:19 2026 +0200

    AVR: Add 16-bit saturated fixed-point divisions.
    
    Overhaul the 16-bit fixed-point divisions:  Add support for
    saturating divisions of [U]HAmode and [U]HQmode to libgcc.
    The non-saturating functions are just aliases of the saturating
    ones.  Add test cases.
    
    gcc/
            * config/avr/avr-fixed.md: Use alldiv as code iterator for the
            16-bit fixed-point insns and expanders of [U]HAmode, [U]HQmode.
            Support saturated divisions in [U]HAmode, [U]HQmode.
    libgcc/
            * config/avr/t-avr (LIB1ASMFUNCS): Add _divQ16_tail.
            * config/avr/lib1funcs-fixed.S (__divQ16_tail): New DEFUN.
            (__divhq3, __udivuhq3, __divha3, __udivuha3): Overhaul.
            (__ssdivhq3, __usdivuhq3, __ssdivha3, __usdivuha3): New ENTRYs.
    gcc/testsuite/
            * gcc.target/avr/fx16.h: New file.
            * gcc.target/avr/fx16-div-1.c: New test.
            * gcc.target/avr/fx16-div-2.c: New test.
            * gcc.target/avr/fx16-div-3.c: New test.

Diff:
---
 gcc/config/avr/avr-fixed.md               |  40 +++---
 gcc/testsuite/gcc.target/avr/fx16-div-1.c |  74 ++++++++++
 gcc/testsuite/gcc.target/avr/fx16-div-2.c |  66 +++++++++
 gcc/testsuite/gcc.target/avr/fx16-div-3.c |  64 +++++++++
 gcc/testsuite/gcc.target/avr/fx16.h       |  50 +++++++
 libgcc/config/avr/lib1funcs-fixed.S       | 229 ++++++++++++++++++------------
 libgcc/config/avr/t-avr                   |   2 +-
 7 files changed, 419 insertions(+), 106 deletions(-)

diff --git a/gcc/config/avr/avr-fixed.md b/gcc/config/avr/avr-fixed.md
index 4d8c9b1af01b..e4c08cbd84fc 100644
--- a/gcc/config/avr/avr-fixed.md
+++ b/gcc/config/avr/avr-fixed.md
@@ -503,48 +503,54 @@
   "%~call __<code><mode>3"
   [(set_attr "type" "xcall")])
 
-;; "divhq3" "udivuhq3"
-;; "divha3" "udivuha3"
-(define_expand "<code><mode>3"
+;; "divhq3" "udivuhq3" "ssdivhq3" "usdivuhq3"
+;; "divha3" "udivuha3" "ssdivha3" "usdivuha3"
+(define_expand "<code_stdname><mode>3"
   [(set (reg:ALL2QA 26)
         (match_operand:ALL2QA 1 "register_operand" ""))
    (set (reg:ALL2QA 22)
         (match_operand:ALL2QA 2 "register_operand" ""))
    (parallel [(set (reg:ALL2QA 24)
-                   (usdiv:ALL2QA (reg:ALL2QA 26)
-                                 (reg:ALL2QA 22)))
+                   (alldiv:ALL2QA (reg:ALL2QA 26)
+                                  (reg:ALL2QA 22)))
               (clobber (reg:HI 26))
               (clobber (reg:QI 21))])
    (set (match_operand:ALL2QA 0 "register_operand" "")
         (reg:ALL2QA 24))]
-  ""
+  "SIGNED_FIXED_POINT_MODE_P (<MODE>mode)
+   == (<CODE> == DIV || <CODE> == SS_DIV)"
   {
     avr_fix_inputs (operands, 1 << 2, regmask (<MODE>mode, 26));
   })
 
-;; "*divhq3.call" "*udivuhq3.call"
-;; "*divha3.call" "*udivuha3.call"
-(define_insn_and_split "*<code><mode>3.call_split"
+;; "*divhq3.split" "*udivuhq3.split" "*ssdivhq3.split" "*usdivuhq3.split"
+;; "*divha3.split" "*udivuha3.split" "*ssdivha3.split" "*usdivuha3.split"
+(define_insn_and_split "*<code_stdname><mode>3.split"
   [(set (reg:ALL2QA 24)
-        (usdiv:ALL2QA (reg:ALL2QA 26)
-                      (reg:ALL2QA 22)))
+        (alldiv:ALL2QA (reg:ALL2QA 26)
+                       (reg:ALL2QA 22)))
    (clobber (reg:HI 26))
    (clobber (reg:QI 21))]
-  ""
+  "SIGNED_FIXED_POINT_MODE_P (<MODE>mode)
+   == (<CODE> == DIV || <CODE> == SS_DIV)"
   "#"
   "&& reload_completed"
   [(scratch)]
   { DONE_ADD_CCC })
 
-(define_insn "*<code><mode>3.call"
+;; "*divhq3.call" "*udivuhq3.call" "*ssdivhq3.call" "*usdivuhq3.call"
+;; "*divha3.call" "*udivuha3.call" "*ssdivha3.call" "*usdivuha3.call"
+(define_insn "*<code_stdname><mode>3.call"
   [(set (reg:ALL2QA 24)
-        (usdiv:ALL2QA (reg:ALL2QA 26)
-                      (reg:ALL2QA 22)))
+        (alldiv:ALL2QA (reg:ALL2QA 26)
+                       (reg:ALL2QA 22)))
    (clobber (reg:HI 26))
    (clobber (reg:QI 21))
    (clobber (reg:CC REG_CC))]
-  "reload_completed"
-  "%~call __<code><mode>3"
+  "reload_completed
+   && SIGNED_FIXED_POINT_MODE_P (<MODE>mode)
+      == (<CODE> == DIV || <CODE> == SS_DIV)"
+  "%~call __<code_stdname><mode>3"
   [(set_attr "type" "xcall")])
 
 ;; Note the first parameter gets passed in already offset by 2 bytes
diff --git a/gcc/testsuite/gcc.target/avr/fx16-div-1.c b/gcc/testsuite/gcc.target/avr/fx16-div-1.c
new file mode 100644
index 000000000000..e51b84e3aad4
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx16-div-1.c
@@ -0,0 +1,74 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx16.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 (hk)
+MK_TEST (uhk)
+MK_TEST (r)
+MK_TEST (ur)
+
+NI void test_div (float a, float b)
+{
+  test_div_hk (a, b);
+  test_div_uhk (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, +0x1p2, +0x1p3, +0x1p-3,
+    -1.0, -2.0, -0.5, -0x1p2, -0x1p3, -0x1p-3,
+  };
+
+NI void test1 (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)
+{
+  test1 ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx16-div-2.c b/gcc/testsuite/gcc.target/avr/fx16-div-2.c
new file mode 100644
index 000000000000..fadc7ea76be9
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx16-div-2.c
@@ -0,0 +1,66 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx16.h"
+
+NI void test_hk (uint16_t a, float fb, uint16_t res)
+{
+  hk_t b = (hk_t) fb;
+  if (hkbits (a) / b != hkbits (res))
+    exit (id_hk + 4);
+}
+
+NI void test_uhk (uint16_t a, float fb, uint16_t res)
+{
+  uhk_t b = (hk_t) fb;
+  if (uhkbits (a) / b != uhkbits (res))
+    exit (id_uhk + 4);
+}
+
+NI void test2_uhk (uint16_t a, uint16_t b, uint16_t res)
+{
+  if (uhkbits (a) / uhkbits (b) != uhkbits (res))
+    exit (id_uhk + 5);
+}
+
+NI void test2_hk (uint16_t a, uint16_t b, uint16_t res)
+{
+  if (hkbits (a) / hkbits (b) != hkbits (res))
+    exit (id_hk + 5);
+}
+
+void test2 (void)
+{
+  test_uhk (+0xaabb, +0x11.0p0f, +0x0a0b);
+  test_hk  (-0x77bb, +0x11.0p0f, -0x070b);
+  test_hk  (+0x77bb, -0x11.0p0f, -0x070b);
+  test_hk  (-0x77bb, -0x11.0p0f, +0x070b);
+  test_hk  (-0x7ffe, 2.0f, -0x3fff);
+  test_uhk (+0x7ffe, 2.0f, +0x3fff);
+  test_uhk (+0xa955, 2.0f, +0x54aa);
+  test_hk  (+0x5955, 2.0f, +0x2caa);
+  test_hk  (-0x5955, 2.0f, -0x2caa);
+  test_uhk (+0xffff, 2.0f, +0x7fff);
+  test_hk  (-0x7fff, 2.0f, -0x3fff);
+  test_hk  (+0, 0.0f, SMAX);
+  test_hk  (+1, 0.0f, SMAX);
+  test_hk  (-1, 0.0f, SMIN);
+  test_uhk (+0, 0.0f, UMAX);
+  test_uhk (+1, 0.0f, UMAX);
+  test_hk  (SMAX - 100, +0x0.f8p0f, SMAX);
+  test_hk  (SMIN + 100, +0x0.f8p0f, SMIN);
+  test_hk  (SMAX - 100, -0x0.f8p0f, SMIN);
+  test_hk  (SMIN + 100, -0x0.f8p0f, SMAX);
+  test_uhk (UMAX - 100, +0x0.f8p0f, UMAX);
+  test_uhk (UMAX - 100, +0x0.f8p0f, UMAX);
+
+  test2_uhk (X80 + 1, X80,  uhk_1);
+  test2_uhk (X80, X80 + 1,  uhk_1 - 1);
+  test2_uhk (UMAX - 1, UMAX, uhk_1 - 1);
+}
+
+int main (void)
+{
+  test2 ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx16-div-3.c b/gcc/testsuite/gcc.target/avr/fx16-div-3.c
new file mode 100644
index 000000000000..9c9ffc921727
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx16-div-3.c
@@ -0,0 +1,64 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx16.h"
+
+#define MK_TEST(fx)						\
+  NI void test_div_##fx (uint16_t a, float fb, uint16_t r)	\
+  {								\
+    if (fb < fmax_##fx)						\
+      {								\
+	fx##_t b = (fx##_t) fb;					\
+	fx##_t ab = fx##bits (a) / b;				\
+	if (ab != fx##bits (r))					\
+	  exit (id_##fx + 6);					\
+      }								\
+  }
+
+MK_TEST (hk)
+MK_TEST (uhk)
+MK_TEST (r)
+MK_TEST (ur)
+
+NI void test1_div (uint16_t a, float fb, uint16_t r)
+{
+  test_div_uhk (a, fb, r);
+  test_div_ur (a, fb, r);
+  if ((a & X80) == 0)
+    {
+      test_div_hk (a, fb, r);
+      test_div_r (a, fb, r);
+    }
+}
+
+NI void test2_div (uint16_t a, float fb, uint16_t r)
+{
+  test_div_uhk (a, fb, r);
+  test_div_ur (a, fb, r);
+  if ((r & X80) == 0)
+    {
+      test_div_hk (a, fb, r);
+      test_div_r (a, fb, r);
+    }
+  else
+    {
+      test_div_hk (a, fb, SMAX);
+      test_div_r (a, fb, SMAX);
+    }
+}
+
+NI void test3 (void)
+{
+  for (uint16_t a = (uint16_t) 0xff01; a; a >>= 1)
+    {
+      test1_div (a, 2.0f, a >> 1);
+      if ((a & X80) == 0)
+	test2_div (a, 0.5f, a << 1);
+    }
+}
+
+int main (void)
+{
+  test3 ();
+  return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx16.h b/gcc/testsuite/gcc.target/avr/fx16.h
new file mode 100644
index 000000000000..a0c1a24d64ff
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx16.h
@@ -0,0 +1,50 @@
+#include <stdfix.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <avr/pgmspace.h>
+
+#define NI __attribute((noipa))
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*x))
+
+typedef short accum hk_t;
+typedef unsigned short accum uhk_t;
+typedef fract r_t;
+typedef unsigned fract ur_t;
+
+typedef short sat accum sat_hk_t;
+typedef short sat unsigned accum sat_uhk_t;
+typedef sat fract sat_r_t;
+typedef sat unsigned fract sat_ur_t;
+
+// Values are in fmin <= x < fmax.
+#define fmax_hk 0x1.0p8f
+#define fmin_hk (-fmax_hk)
+#define fmax_uhk fmax_hk
+#define fmin_uhk 0.0f
+#define fmax_r  0x1.0p0f
+#define fmin_r  (-fmax_r)
+#define fmax_ur fmax_r
+#define fmin_ur 0.0f
+
+#define UMAX 0xffff
+#define SMAX 0x7fff
+#define SMIN 0x8000
+#define X80  0x8000
+
+// Values are in min <= x <= max.
+#define max_hk hkbits (SMAX)
+#define min_hk hkbits (SMIN)
+#define max_uhk uhkbits (UMAX)
+#define min_uhk uhkbits (0)
+#define max_r   rbits (SMAX)
+#define min_r   rbits (SMIN)
+#define max_ur  urbits (UMAX)
+#define min_ur  urbits (0)
+
+#define hk_1   (1u << __HA_FBIT__)
+#define uhk_1  (1u << __UHA_FBIT__)
+
+#define id_hk   10
+#define id_uhk  20
+#define id_r    30
+#define id_ur   40
diff --git a/libgcc/config/avr/lib1funcs-fixed.S b/libgcc/config/avr/lib1funcs-fixed.S
index a380db1e708b..d7a297064b4a 100644
--- a/libgcc/config/avr/lib1funcs-fixed.S
+++ b/libgcc/config/avr/lib1funcs-fixed.S
@@ -1568,118 +1568,171 @@ ENDF __divqq_helper
 /*******************************************************
     Fractional Division 16 / 16
 *******************************************************/
-#define r_divdL 26     /* dividend Low */
-#define r_divdH 27     /* dividend Hig */
-#define r_quoL  24     /* quotient Low */
-#define r_quoH  25     /* quotient High */
-#define r_divL  22     /* divisor */
-#define r_divH  23     /* divisor */
-#define r_cnt   21
+;;; Argument 1: Dividend and remainder
+#define A0 26
+#define A1 A0+1
+
+;;; Argument 2: Divisor
+#define B0 22
+#define B1 B0+1
+
+;;; Return value: Quotient
+#define Q0 24
+#define Q1 Q0+1
+
+#define Cnt 21
+#define Sign __tmp_reg__
 
 #if defined (L_divhq3)
-DEFUN   __divhq3
-    mov     __tmp_reg__, r_divdH
-    eor     __tmp_reg__, r_divH
-    sbrs    r_divH, 7
-    rjmp    1f
-    NEG2    r_divL
+;;;  (set (reg:HQ 24)
+;;;       (ss_div:HQ (reg:HQ 26)
+;;;                  (reg:HQ 22)))
+;;;  (clobber (reg:HI 26))
+;;;  (clobber (reg:QI 21))
+;;; Rounds towards zero.  Overflow saturates.
+DEFUN __divhq3
+ENTRY __ssdivhq3
+    mov     Sign, A1
+    tst     B1
+    brpl 1f
+    com     Sign
+    NEG2    B0
 1:
-    sbrs    r_divdH, 7
-    rjmp    2f
-    NEG2    r_divdL
+    tst     A1
+    brpl 2f
+    NEG2    A0
 2:
-    cp      r_divdL, r_divL
-    cpc     r_divdH, r_divH
-    breq    __divhq3_minus1  ; if equal return -1
-    XCALL   __udivuhq3
-    lsr     r_quoH
-    ror     r_quoL
-    brpl    9f
-    ;; negate result if needed
-    NEG2    r_quoL
-9:
-    ret
-__divhq3_minus1:
-    ldi     r_quoH, 0x80
-    clr     r_quoL
-    ret
+    lsr     A1                  ; Adjust for FBIT = 15.
+    ror     A0                  ; Pass LSB in C.
+    clr     Q1
+    XCALL   __udivuhq3.2
+    XJMP    __divQ16_tail
 ENDF __divhq3
 #endif  /* defined (L_divhq3) */
 
+#if defined (L_divQ16_tail)
+DEFUN __divQ16_tail
+    ;; In the negative result case, Q = 0x80.. is no overflow,
+    ;; but treating it as such keeps the value unchanged.
+    brmi    .Lsaturate          ; N = sign (Q)
+    ;; Negate result as needed.
+    sbrs    Sign, 7
+    ret
+    NEG2    Q0
+    ret
+.Lsaturate:
+    ;; Sign.7 = 0 -> 0x7f..
+    ;; Sign.7 = 1 -> 0x80..
+#ifndef __AVR_TINY__
+    ldi     Q0, lo8(0x7fff)
+    ldi     Q1, hi8(0x7fff)
+    sbrc    Sign, 7
+    adiw    Q0, 1
+#else
+    cpi     Sign, 0x80
+    sbc     Q0, Q0
+    sbc     Q1, Q1
+    subi    Q1, 0x80
+#endif
+    ret
+ENDF __divQ16_tail
+#endif /* L_divQ16_tail */
+
 #if defined (L_udivuhq3)
-DEFUN   __udivuhq3
-    sub     r_quoH,r_quoH   ; clear quotient and carry
-    ;; FALLTHRU
+;;;  (set (reg:UHQ 24)
+;;;       (us_div:UHQ (reg:UHQ 26)
+;;;                   (reg:UHQ 22)))
+;;;  (clobber (reg:HI 26))
+;;;  (clobber (reg:QI 21))
+;;; Rounds towards zero.  Overflow saturates.
+DEFUN __udivuhq3
+ENTRY __usdivuhq3
+    sub     Q1, Q1      ; CLC
+ENTRY __udivuhq3.2
+    clr     Q0          ; Clear quotient
+    ldi     Cnt, 16     ; Init loop counter.
+    ;; C = 0 (unsigned).
+    ;; C = LSB from the caller (signed).
+.Loop:
+    rol     A0          ; Shift one more dividend bit into the remainder.
+    rol     A1
+    brcs .Lsub          ; Divisor always fits in remainder?
+    cp      A0, B0      ; Remainder >= B?
+    cpc     A1, B1
+    brcc .Lsub
+    rol     Q0          ; No: Shift quotient with C = 1 (0 in result).
+    rjmp .Lcont
+.Lsub:
+    sub     A0, B0      ; Yes: Restore remainder by subtracting B...
+    sbc     A1, B1
+    lsl     Q0          ; ...and shift quotient with C = 0 (1 in result).
+.Lcont:
+    rol     Q1          ; Shift quotient.
+    dec     Cnt         ; Decrement loop counter.
+    brne .Loop
+    com     Q0          ; Complement result because C was
+    com     Q1          ; complemented in the loop.
+    ret                 ; N = sign (Q).
 ENDF __udivuhq3
-
-DEFUN   __udivuha3_common
-    clr     r_quoL          ; clear quotient
-    ldi     r_cnt,16        ; init loop counter
-__udivuhq3_loop:
-    rol     r_divdL         ; shift dividend (with CARRY)
-    rol     r_divdH
-    brcs    __udivuhq3_ep   ; dividend overflow
-    cp      r_divdL,r_divL  ; compare dividend & divisor
-    cpc     r_divdH,r_divH
-    brcc    __udivuhq3_ep   ; dividend >= divisor
-    rol     r_quoL          ; shift quotient (with CARRY)
-    rjmp    __udivuhq3_cont
-__udivuhq3_ep:
-    sub     r_divdL,r_divL  ; restore dividend
-    sbc     r_divdH,r_divH
-    lsl     r_quoL          ; shift quotient (without CARRY)
-__udivuhq3_cont:
-    rol     r_quoH          ; shift quotient
-    dec     r_cnt           ; decrement loop counter
-    brne    __udivuhq3_loop
-    com     r_quoL          ; complement result
-    com     r_quoH          ; because C flag was complemented in loop
-    ret
-ENDF __udivuha3_common
 #endif  /* defined (L_udivuhq3) */
 
 /*******************************************************
     Fixed Division 8.8 / 8.8
 *******************************************************/
 #if defined (L_divha3)
-DEFUN   __divha3
-    mov     __tmp_reg__, r_divdH
-    eor     __tmp_reg__, r_divH
-    sbrs    r_divH, 7
-    rjmp    1f
-    NEG2    r_divL
+;;;  (set (reg:HA 24)
+;;;       (ss_div:HA (reg:HA 26)
+;;;                  (reg:HA 22)))
+;;;  (clobber (reg:HI 26))
+;;;  (clobber (reg:QI 21))
+;;; Rounds towards zero.  Overflow saturates.
+DEFUN __divha3
+ENTRY __ssdivha3
+    mov     Sign, A1
+    tst     B1
+    brpl 1f
+    com     Sign
+    NEG2    B0
 1:
-    sbrs    r_divdH, 7
-    rjmp    2f
-    NEG2    r_divdL
+    tst     A1
+    brpl 2f
+    NEG2    A0
 2:
-    XCALL   __udivuha3
-    lsr     r_quoH  ; adjust to 7 fractional bits
-    ror     r_quoL
-    sbrs    __tmp_reg__, 7  ; negate result if needed
-    ret
-    NEG2    r_quoL
-    ret
+    lsr     A1              ; Adjust to FBIT = 7.
+    ror     A0              ; Pass LSB in C.
+    XCALL   __udivuha3.2    ; N = sign (Q)
+    XJMP    __divQ16_tail
 ENDF __divha3
 #endif  /* defined (L_divha3) */
 
 #if defined (L_udivuha3)
-DEFUN   __udivuha3
-    mov     r_quoH, r_divdL
-    mov     r_divdL, r_divdH
-    clr     r_divdH
-    lsl     r_quoH     ; shift quotient into carry
-    XJMP    __udivuha3_common ; same as fractional after rearrange
+;;;  (set (reg:UHA 24)
+;;;       (us_div:UHA (reg:UHA 26)
+;;;                   (reg:UHA 22)))
+;;;  (clobber (reg:HI 26))
+;;;  (clobber (reg:QI 21))
+;;; Rounds towards zero.  Overflow saturates.
+DEFUN __udivuha3
+ENTRY __usdivuha3
+    clc
+ENTRY __udivuha3.2
+    mov     Q1, A0          ; Put dividend in A0:Q1.
+    mov     A0, A1
+    clr     A1
+    rol     Q1              ; Shift quotient into carry.
+    XJMP    __udivuhq3.2    ; Same as fractional after rearrange.
 ENDF __udivuha3
 #endif  /* defined (L_udivuha3) */
 
-#undef  r_divdL
-#undef  r_divdH
-#undef  r_quoL
-#undef  r_quoH
-#undef  r_divL
-#undef  r_divH
-#undef  r_cnt
+#undef A0
+#undef A1
+#undef Q0
+#undef Q1
+#undef B0
+#undef B1
+#undef Cnt
+#undef Sign
+
 
 /*********************************************************
     Fixed 16.16 and s16.15 saturated [U]SAmode Divisions
diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr
index da4be9fb55dc..56e26870c0df 100644
--- a/libgcc/config/avr/t-avr
+++ b/libgcc/config/avr/t-avr
@@ -42,7 +42,7 @@ LIB1ASMFUNCS = \
 	_mulha3 _muluha3 _muluha3_round \
 	_usmuluha3 _ssmulha3 \
 	_divqq3 _udivuqq3 _divqq_helper \
-	_divhq3 _udivuhq3 \
+	_divhq3 _udivuhq3 _divQ16_tail \
 	_divha3 _udivuha3 \
 	_divsa3 _udivusa3 \
 	_ssneg_2 \