This patch adds support for the signed and unsigned
saturated accum [U]SAmode divisions.
The code for the vanilla divisions has been reworked, and
the non-saturating functions are now just aliases of the
saturating ones. This entails the following size changes:
__udivusa3: Becomes one instruction shorter.
__divsa3: Grows by 7 instructions that handle saturation.
Ok for trunk?
Johann
--
gcc/
* config/avr/avr.md (code_stdname): Add code attributes
for: ss_div, us_div, div, udiv.
* config/avr/avr-fixed.md (alldiv): New code iterator.
Use it in all [U]SAmode division expanders and insns.
Use <code_stdname> instead of <code> as needed.
libgcc/
* libgcc/config/avr/lib1funcs-fixed.S
(__divsa3, __udivusa3): Overhaul. Saturate result.
(__ssdivsa3, __usdivusa3, __udivusa3.2): New ENTRYs.
gcc/testsuite/
* gcc.target/avr/fx32-div-1.c: New test.
* gcc.target/avr/fx32-div-2.c: New test.
* gcc.target/avr/fx32-div-3.c: New test.
* gcc.target/avr/fx32.h: New file.diff --git a/gcc/config/avr/avr-fixed.md b/gcc/config/avr/avr-fixed.md
index d638d0b68d7..d2ed022ec91 100644
--- a/gcc/config/avr/avr-fixed.md
+++ b/gcc/config/avr/avr-fixed.md
@@ -460,7 +460,8 @@ (define_insn "*mul<mode>3.call"
; / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / /
; div
-(define_code_iterator usdiv [udiv div])
+(define_code_iterator usdiv [udiv div])
+(define_code_iterator alldiv [udiv div us_div ss_div])
;; "divqq3" "udivuqq3"
(define_expand "<code><mode>3"
@@ -549,14 +550,15 @@ (define_insn "*<code><mode>3.call"
;; Note the first parameter gets passed in already offset by 2 bytes
;; "divsa3" "udivusa3"
-(define_expand "<code><mode>3"
+;; "ssdivsa3" "usdivusa3"
+(define_expand "<code_stdname><mode>3"
[(set (reg:ALL4A 24)
(match_operand:ALL4A 1 "register_operand" ""))
(set (reg:ALL4A 18)
(match_operand:ALL4A 2 "register_operand" ""))
(parallel [(set (reg:ALL4A 22)
- (usdiv:ALL4A (reg:ALL4A 24)
- (reg:ALL4A 18)))
+ (alldiv:ALL4A (reg:ALL4A 24)
+ (reg:ALL4A 18)))
(clobber (reg:HI 26))
(clobber (reg:HI 30))])
(set (match_operand:ALL4A 0 "register_operand" "")
@@ -567,10 +569,11 @@ (define_expand "<code><mode>3"
})
;; "*divsa3.call" "*udivusa3.call"
-(define_insn_and_split "*<code><mode>3.call_split"
+;; "*ssdivsa3.call" "*usdivusa3.call"
+(define_insn_and_split "*<code_stdname><mode>3.call_split"
[(set (reg:ALL4A 22)
- (usdiv:ALL4A (reg:ALL4A 24)
- (reg:ALL4A 18)))
+ (alldiv:ALL4A (reg:ALL4A 24)
+ (reg:ALL4A 18)))
(clobber (reg:HI 26))
(clobber (reg:HI 30))]
""
@@ -579,15 +582,15 @@ (define_insn_and_split "*<code><mode>3.call_split"
[(scratch)]
{ DONE_ADD_CCC })
-(define_insn "*<code><mode>3.call"
+(define_insn "*<code_stdname><mode>3.call"
[(set (reg:ALL4A 22)
- (usdiv:ALL4A (reg:ALL4A 24)
- (reg:ALL4A 18)))
+ (alldiv:ALL4A (reg:ALL4A 24)
+ (reg:ALL4A 18)))
(clobber (reg:HI 26))
(clobber (reg:HI 30))
(clobber (reg:CC REG_CC))]
"reload_completed"
- "%~call __<code><mode>3"
+ "%~call __<code_stdname><mode>3"
[(set_attr "type" "xcall")])
diff --git a/gcc/config/avr/avr.md b/gcc/config/avr/avr.md
index f57df70db93..f67278b87ad 100644
--- a/gcc/config/avr/avr.md
+++ b/gcc/config/avr/avr.md
@@ -390,6 +390,7 @@ (define_code_attr code_stdname
(rotate "rotl")
(ss_plus "ssadd") (ss_minus "sssub") (ss_neg "ssneg") (ss_abs "ssabs")
(us_plus "usadd") (us_minus "ussub") (us_neg "usneg")
+ (ss_div "ssdiv") (us_div "usdiv") (div "div") (udiv "udiv")
])
;;========================================================================
diff --git a/gcc/testsuite/gcc.target/avr/fx32-div-1.c b/gcc/testsuite/gcc.target/avr/fx32-div-1.c
new file mode 100644
index 00000000000..cdaebb64482
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx32-div-1.c
@@ -0,0 +1,72 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx32.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 (k)
+MK_TEST (uk)
+
+NI void test_div (float a, float b)
+{
+ test_div_k (a, b);
+ test_div_uk (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, +0x1p5, +0x1p6, +0x1p-7,
+ -1.0, -2.0, -0.5, -0x1p5, -0x1p6, -0x1p-7,
+ };
+
+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/fx32-div-2.c b/gcc/testsuite/gcc.target/avr/fx32-div-2.c
new file mode 100644
index 00000000000..41279424dbd
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx32-div-2.c
@@ -0,0 +1,72 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx32.h"
+
+NI void test_k (uint32_t a, float fb, uint32_t res)
+{
+ k_t b = (k_t) fb;
+ if (kbits (a) / b != kbits (res))
+ exit (id_k + 4);
+}
+
+NI void test_uk (uint32_t a, float fb, uint32_t res)
+{
+ uk_t b = (uk_t) fb;
+ if (ukbits (a) / b != ukbits (res))
+ exit (id_uk + 4);
+}
+
+NI void test2_k (uint32_t a, uint32_t b, uint32_t res)
+{
+ if (kbits (a) / kbits (b) != kbits (res))
+ exit (id_k + 5);
+}
+
+NI void test2_uk (uint32_t a, uint32_t b, uint32_t res)
+{
+ if (ukbits (a) / ukbits (b) != ukbits (res))
+ exit (id_uk + 5);
+}
+
+NI void test2 (void)
+{
+ test_uk (+0xaabbcc99, +0x11.0p0f, +0x0a0b0c09);
+ test_k (-0x77bbcc99, +0x11.0p0f, -0x070b0c09);
+ test_k (+0x77bbcc99, -0x11.0p0f, -0x070b0c09);
+ test_k (-0x77bbcc99, -0x11.0p0f, +0x070b0c09);
+ test_k (-0x7ffe0268, 2.0f, -0x3fff0134);
+ test_uk (+0x7ffe0268, 2.0f, +0x3fff0134);
+ test_uk (+0xc42ecb55, 2.0f, +0x621765aa);
+ test_k (-0x642ecb55, 2.0f, -0x321765aa);
+ test_uk (+0xffffffff, 2.0f, +0x7fffffff);
+ test_k (-0x7fffffff, 2.0f, -0x3fffffff);
+ test_k (+0, 0.0f, SMAX);
+ test_k (+1, 0.0f, SMAX);
+ test_k (-1, 0.0f, SMIN);
+ test_uk (+0, 0.0f, UMAX);
+ test_uk (+1, 0.0f, UMAX);
+ test_k (SMAX - 1000, +0x0.fffp0f, SMAX);
+ test_k (SMIN + 1000, +0x0.fffp0f, SMIN);
+ test_k (SMAX - 1000, -0x0.fffp0f, SMIN);
+ test_k (SMIN + 1000, -0x0.fffp0f, SMAX);
+ test_uk (UMAX - 1000, +0x0.fffp0f, UMAX);
+ test_uk (UMAX - 1000, +0x0.fffp0f, UMAX);
+
+ test2_uk (X80 + 1, X80, uk_1);
+ test2_uk (X80, X80 + 1, uk_1 - 1);
+ test2_uk (UMAX - 1, UMAX, uk_1 - 1);
+ test2_k (SMAX, SMIN, -(k_1 - 1));
+ test2_k (SMIN, SMAX, -k_1);
+ test2_k (SMAX, SMAX, k_1);
+ test2_k (SMAX, SMAX, k_1);
+ test2_uk (SMIN, SMIN, uk_1);
+ test2_uk (1, 1, uk_1);
+ test2_k (1, 1, k_1);
+}
+
+int main (void)
+{
+ test2 ();
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx32-div-3.c b/gcc/testsuite/gcc.target/avr/fx32-div-3.c
new file mode 100644
index 00000000000..616f3ea8f0e
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx32-div-3.c
@@ -0,0 +1,54 @@
+/* { dg-do run { target { ! avr_tiny } } } */
+/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
+
+#include "fx32.h"
+
+#define MK_TEST(fx) \
+ NI void test_div_##fx (uint32_t a, float fb, uint32_t r) \
+ { \
+ fx##_t ab = fx##bits (a) / (fx##_t) fb; \
+ if (ab != fx##bits (r)) \
+ exit (id_##fx + 6); \
+ return; \
+ }
+
+MK_TEST (k)
+MK_TEST (uk)
+
+NI void test1_div (uint32_t a, float fb, uint32_t r)
+{
+ test_div_uk (a, fb, r);
+ if ((a & X80) == 0)
+ {
+ test_div_k (a, fb, r);
+ }
+}
+
+NI void test2_div (uint32_t a, float fb, uint32_t r)
+{
+ test_div_uk (a, fb, r);
+ if ((r & X80) == 0)
+ {
+ test_div_k (a, fb, r);
+ }
+ else
+ {
+ test_div_k (a, fb, SMAX);
+ }
+}
+
+NI void test3 (void)
+{
+ for (uint32_t a = (uint32_t) 0xff01 << (32 - 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)
+{
+ test3 ();
+ return 0;
+}
diff --git a/gcc/testsuite/gcc.target/avr/fx32.h b/gcc/testsuite/gcc.target/avr/fx32.h
new file mode 100644
index 00000000000..0fdc00128f3
--- /dev/null
+++ b/gcc/testsuite/gcc.target/avr/fx32.h
@@ -0,0 +1,36 @@
+#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 accum k_t;
+typedef unsigned accum uk_t;
+
+typedef sat accum sat_k_t;
+typedef sat unsigned accum sat_uk_t;
+
+// Values are in fmin <= x < fmax.
+#define fmax_k 0x1.0p16f
+#define fmin_k (-fmax_k)
+#define fmax_uk fmax_k
+#define fmin_uk 0.0f
+
+#define UMAX 0xffffffff
+#define SMAX 0x7fffffff
+#define SMIN 0x80000000
+#define X80 0x80000000
+
+// Values are in min <= x <= max.
+#define max_k kbits (SMAX)
+#define min_k kbits (SMIN)
+#define max_uk ukbits (UMAX)
+#define min_uk ukbits (0)
+
+#define k_1 (1ul << __SA_FBIT__)
+#define uk_1 (1ul << __USA_FBIT__)
+
+#define id_k 10
+#define id_uk 20
diff --git a/libgcc/config/avr/lib1funcs-fixed.S b/libgcc/config/avr/lib1funcs-fixed.S
index 2996a5960eb..dfb51bb75af 100644
--- a/libgcc/config/avr/lib1funcs-fixed.S
+++ b/libgcc/config/avr/lib1funcs-fixed.S
@@ -1687,104 +1687,137 @@ DEFUN __udivuha3
#ifndef __AVR_TINY__
-#define r_arg1L 24 /* arg1 gets passed already in place */
-#define r_arg1H 25
-#define r_arg1HL 26
-#define r_arg1HH 27
-#define r_divdL 26 /* dividend Low */
-#define r_divdH 27
-#define r_divdHL 30
-#define r_divdHH 31 /* dividend High */
-#define r_quoL 22 /* quotient Low */
-#define r_quoH 23
-#define r_quoHL 24
-#define r_quoHH 25 /* quotient High */
-#define r_divL 18 /* divisor Low */
-#define r_divH 19
-#define r_divHL 20
-#define r_divHH 21 /* divisor High */
-#define r_cnt __zero_reg__ /* loop count (0 after the loop!) */
+;;; Dividend arg1 gets passed already in place.
+#define A0 24
+#define A1 A0+1
+#define A2 26
+#define A3 A2+1
+
+;;; Remainder
+#define M0 A2
+#define M1 A2+1
+#define M2 0
+#define M3 M2+1
+
+;;; Quotient return value
+#define Q0 22
+#define Q1 Q0+1
+#define Q2 A0
+#define Q3 A0+1
+
+;;; Divisor
+#define B0 18
+#define B1 B0+1
+#define B2 20
+#define B3 B2+1
+#define Cnt r31
+#define Sign r30
#if defined (L_divsa3)
+;;; (set (reg:SA 22)
+;;; (ss_div:SA (reg:SA 24)
+;;; (reg:SA 18)))
DEFUN __divsa3
- mov r0, r_arg1HH
- eor r0, r_divHH
- .call_if_neg r_divHH, __negsi2_r18
- sbrs r_arg1HH, 7
+ENTRY __ssdivsa3
+ mov Sign, B3
+ .call_if_neg B3, __negsi2_r18
+ sbrs A3, 7
rjmp 2f
- NEG4 r_arg1L
-2:
- XCALL __udivusa3
- lsr r_quoHH ; adjust to 15 fractional bits
- ror r_quoHL
- ror r_quoH
- ror r_quoL
- sbrs r0, 7 ; negate result if needed
+ com Sign
+ NEG4 A0
+2: lsr A3 ; Adjust to 15 fractional bits.
+ ror A2
+ ror A1
+ ror A0 ; Pass LSB in C.
+ XCALL __udivusa3.2 ; N = sign (Q)
+ ;; In the negative result case, Q = 0x80.. is no overflow,
+ ;; but treating it as such keeps the value unchanged.
+ brmi .Lsaturate ; Overflow?
+ ;; Handle result sign.
+ sbrs Sign, 7
ret
- ;; negate r_quoL
XJMP __negsi2
+.Lsaturate:
+ ;; Sign.7 = 0 -> C = 1 -> 0x7f..
+ ;; Sign.7 = 1 -> C = 0 -> 0x80..
+ cpi Sign, 0x80
+ sbc Q0, Q0
+ sbc Q1, Q1
+ wmov Q2, Q0
+ subi Q3, 0x80
+ ret
ENDF __divsa3
#endif /* defined (L_divsa3) */
#if defined (L_udivusa3)
+;;; (set (reg:USA 22)
+;;; (us_div:USA (reg:USA 24)
+;;; (reg:USA 18)))
+;;; Register layout:
+;;; A3 A2 A1 A0 [---const----]
+;;; M3 M2 M1 M0 Q3 Q2 Q1 Q0 B3 B2 B1 B0
+;;; r1 r0 27 26 25 24 23 22 21 20 19 18
DEFUN __udivusa3
- ldi r_divdHL, 32 ; init loop counter
- mov r_cnt, r_divdHL
- clr r_divdHL
- clr r_divdHH
- wmov r_quoL, r_divdHL
- lsl r_quoHL ; shift quotient into carry
- rol r_quoHH
-__udivusa3_loop:
- rol r_divdL ; shift dividend (with CARRY)
- rol r_divdH
- rol r_divdHL
- rol r_divdHH
- brcs __udivusa3_ep ; dividend overflow
- cp r_divdL,r_divL ; compare dividend & divisor
- cpc r_divdH,r_divH
- cpc r_divdHL,r_divHL
- cpc r_divdHH,r_divHH
- brcc __udivusa3_ep ; dividend >= divisor
- rol r_quoL ; shift quotient (with CARRY)
- rjmp __udivusa3_cont
-__udivusa3_ep:
- sub r_divdL,r_divL ; restore dividend
- sbc r_divdH,r_divH
- sbc r_divdHL,r_divHL
- sbc r_divdHH,r_divHH
- lsl r_quoL ; shift quotient (without CARRY)
-__udivusa3_cont:
- rol r_quoH ; shift quotient
- rol r_quoHL
- rol r_quoHH
- dec r_cnt ; decrement loop counter
- brne __udivusa3_loop
- com r_quoL ; complement result
- com r_quoH ; because C flag was complemented in loop
- com r_quoHL
- com r_quoHH
+ENTRY __usdivusa3
+ clc
+ENTRY __udivusa3.2
+ ldi Cnt, 33 ; Init loop counter.
+ clr M2
+ wmov Q0, M2 ; M3 === R1 = 0 already.
+ rjmp .Lstart
+.Loop:
+ rol M0 ; Shift dividend with Carry from Q.
+ rol M1
+ rol M2
+ rol M3
+ brcs .Lsub ; Remainder overflow: B will always fit.
+ cp M0, B0 ; Compare remainder and divisor.
+ cpc M1, B1
+ cpc M2, B2
+ cpc M3, B3
+ brcc .Lsub ; Remainder >= divisor?
+ rol Q0 ; No: Shift quotient (C = 1, 0 in result)
+ rjmp .Lcont
+.Lsub:
+ sub M0, B0 ; Yes: Restore remainder.
+ sbc M1, B1
+ sbc M2, B2
+ sbc M3, B3
+ lsl Q0 ; Shift quotient (C = 0, 1 in result).
+.Lcont:
+ rol Q1 ; Shift quotient into C into M.
+.Lstart:
+ rol Q2
+ rol Q3
+ dec Cnt ; Decrement loop counter until 0.
+ brne .Loop
+ clr __zero_reg__
+ com Q0 ; Complement result because Carry
+ com Q1 ; was complemented in the loop.
+ com Q2
+ com Q3 ; N is the sign of Q
ret
ENDF __udivusa3
#endif /* defined (L_udivusa3) */
-#undef r_arg1L
-#undef r_arg1H
-#undef r_arg1HL
-#undef r_arg1HH
-#undef r_divdL
-#undef r_divdH
-#undef r_divdHL
-#undef r_divdHH
-#undef r_quoL
-#undef r_quoH
-#undef r_quoHL
-#undef r_quoHH
-#undef r_divL
-#undef r_divH
-#undef r_divHL
-#undef r_divHH
-#undef r_cnt
+#undef A0
+#undef A1
+#undef A2
+#undef A3
+#undef M0
+#undef M1
+#undef M2
+#undef M3
+#undef Q0
+#undef Q1
+#undef Q2
+#undef Q3
+#undef B0
+#undef B1
+#undef B2
+#undef B3
+#undef Cnt
+#undef Sign
#endif /* ! AVR_TINY */