[gcc r17-2987] AVR: Add [un]signed sat accum divisions.
Georg-Johann Lay via Gcc-cvs <[email protected]> Wed, 5 Aug 2026 15:31:01 +0000 (GMT)
| Newsgroups | gmane.comp.gcc.cvs |
|---|---|
| Message-ID | <[email protected]> |
https://gcc.gnu.org/g:898cd93819867f14fe26d708288434a0c033721a commit r17-2987-g898cd93819867f14fe26d708288434a0c033721a Author: Georg-Johann Lay <[email protected]> Date: Wed Aug 5 17:28:35 2026 +0200 AVR: Add [un]signed sat accum divisions. This patch adds support for the signed and unsigned saturated accum [U]SAmode divisions. The functions can be used without adjustment on the Reduced Core, so enable them there. 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. 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/ * config/avr/t-avr (FUNCS_notiny) <_divsa3, _udivusa3>: Move to LIB1ASMFUNCS. * 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: --- gcc/config/avr/avr-fixed.md | 37 +++--- gcc/config/avr/avr.md | 1 + gcc/testsuite/gcc.target/avr/fx32-div-1.c | 72 ++++++++++ gcc/testsuite/gcc.target/avr/fx32-div-2.c | 72 ++++++++++ gcc/testsuite/gcc.target/avr/fx32-div-3.c | 54 ++++++++ gcc/testsuite/gcc.target/avr/fx32.h | 36 +++++ libgcc/config/avr/lib1funcs-fixed.S | 210 +++++++++++++++++------------- libgcc/config/avr/t-avr | 2 +- 8 files changed, 379 insertions(+), 105 deletions(-) diff --git a/gcc/config/avr/avr-fixed.md b/gcc/config/avr/avr-fixed.md index d638d0b68d7b..4d8c9b1af01b 100644 --- a/gcc/config/avr/avr-fixed.md +++ b/gcc/config/avr/avr-fixed.md @@ -460,7 +460,8 @@ ; / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / / ; 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,45 +550,51 @@ ;; 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" "") (reg:ALL4A 22))] - "" + "SIGNED_FIXED_POINT_MODE_P (<MODE>mode) + == (<CODE> == DIV || <CODE> == SS_DIV)" { avr_fix_inputs (operands, 1 << 2, regmask (<MODE>mode, 24)); }) -;; "*divsa3.call" "*udivusa3.call" -(define_insn_and_split "*<code><mode>3.call_split" +;; "*divsa3.split" "*udivusa3.split" "*ssdivsa3.split" "*usdivusa3.split" +(define_insn_and_split "*<code_stdname><mode>3.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))] - "" + "SIGNED_FIXED_POINT_MODE_P (<MODE>mode) + == (<CODE> == DIV || <CODE> == SS_DIV)" "#" "&& reload_completed" [(scratch)] { DONE_ADD_CCC }) -(define_insn "*<code><mode>3.call" +;; "*divsa3.call" "*udivusa3.call" "*ssdivsa3.call" "*usdivusa3.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" + "reload_completed + && SIGNED_FIXED_POINT_MODE_P (<MODE>mode) + == (<CODE> == DIV || <CODE> == SS_DIV)" + "%~call __<code_stdname><mode>3" [(set_attr "type" "xcall")]) diff --git a/gcc/config/avr/avr.md b/gcc/config/avr/avr.md index f57df70db930..f67278b87ad2 100644 --- a/gcc/config/avr/avr.md +++ b/gcc/config/avr/avr.md @@ -390,6 +390,7 @@ (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 000000000000..cdaebb644824 --- /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 000000000000..41279424dbda --- /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 000000000000..616f3ea8f0eb --- /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 000000000000..0fdc00128f31 --- /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 2996a5960eb1..a380db1e708b 100644 --- a/libgcc/config/avr/lib1funcs-fixed.S +++ b/libgcc/config/avr/lib1funcs-fixed.S @@ -1681,112 +1681,144 @@ ENDF __udivuha3 #undef r_divH #undef r_cnt -/******************************************************* - Fixed Division 16.16 / 16.16 -*******************************************************/ +/********************************************************* + Fixed 16.16 and s16.15 saturated [U]SAmode Divisions +*********************************************************/ -#ifndef __AVR_TINY__ +;;; Argument 1: Dividend gets passed already in place. +#define A0 24 +#define A1 A0+1 +#define A2 26 +#define A3 A2+1 + +;;; Argument 2: Divisor +#define B0 18 +#define B1 B0+1 +#define B2 20 +#define B3 B2+1 -#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!) */ +;;; Return value: Quotient +#define Q0 22 +#define Q1 Q0+1 +#define Q2 A0 +#define Q3 A0+1 + +;;; Remainder +#define M0 A2 +#define M1 A2+1 +#define M2 __tmp_reg__ +#define M3 __zero_reg__ + +#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. + ;; C = 0 (unsigned). + ;; C = LSB (from signed caller). + 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 - -#endif /* ! AVR_TINY */ +#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 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; diff --git a/libgcc/config/avr/t-avr b/libgcc/config/avr/t-avr index edd0e8f86983..da4be9fb55dc 100644 --- a/libgcc/config/avr/t-avr +++ b/libgcc/config/avr/t-avr @@ -44,6 +44,7 @@ LIB1ASMFUNCS = \ _divqq3 _udivuqq3 _divqq_helper \ _divhq3 _udivuhq3 \ _divha3 _udivuha3 \ + _divsa3 _udivusa3 \ _ssneg_2 \ _ssabs_1 _ssabs_2 \ _mask1 _ret \ @@ -99,7 +100,6 @@ FUNCS_notiny += \ _muluda3 _muluta3 _muludq3 \ _mulda3 _multa3 _muldq3 \ _muluQ64_tail _mulQ64_work \ - _divsa3 _udivusa3 \ _udivuda3 _udivuta3 _udivudq3 _udivuQ64_common \ _divda3 _divta3 _divdq3 _divQ64_tail \ _clr_8 \