From: Georg-Johann Lay Date: Wed, 5 Aug 2026 15:34:19 +0000 (+0200) Subject: AVR: Add 16-bit saturated fixed-point divisions. X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;ds=inline;p=thirdparty%2Fgcc.git 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 --git a/gcc/config/avr/avr-fixed.md b/gcc/config/avr/avr-fixed.md index 4d8c9b1af01..e4c08cbd84f 100644 --- a/gcc/config/avr/avr-fixed.md +++ b/gcc/config/avr/avr-fixed.md @@ -503,48 +503,54 @@ "%~call __3" [(set_attr "type" "xcall")]) -;; "divhq3" "udivuhq3" -;; "divha3" "udivuha3" -(define_expand "3" +;; "divhq3" "udivuhq3" "ssdivhq3" "usdivuhq3" +;; "divha3" "udivuha3" "ssdivha3" "usdivuha3" +(define_expand "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) + == ( == DIV || == SS_DIV)" { avr_fix_inputs (operands, 1 << 2, regmask (mode, 26)); }) -;; "*divhq3.call" "*udivuhq3.call" -;; "*divha3.call" "*udivuha3.call" -(define_insn_and_split "*3.call_split" +;; "*divhq3.split" "*udivuhq3.split" "*ssdivhq3.split" "*usdivuhq3.split" +;; "*divha3.split" "*udivuha3.split" "*ssdivha3.split" "*usdivuha3.split" +(define_insn_and_split "*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) + == ( == DIV || == SS_DIV)" "#" "&& reload_completed" [(scratch)] { DONE_ADD_CCC }) -(define_insn "*3.call" +;; "*divhq3.call" "*udivuhq3.call" "*ssdivhq3.call" "*usdivuhq3.call" +;; "*divha3.call" "*udivuha3.call" "*ssdivha3.call" "*usdivuha3.call" +(define_insn "*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 __3" + "reload_completed + && SIGNED_FIXED_POINT_MODE_P (mode) + == ( == DIV || == SS_DIV)" + "%~call __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 00000000000..e51b84e3aad --- /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 00000000000..fadc7ea76be --- /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 00000000000..9c9ffc92172 --- /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 00000000000..a0c1a24d64f --- /dev/null +++ b/gcc/testsuite/gcc.target/avr/fx16.h @@ -0,0 +1,50 @@ +#include +#include +#include +#include + +#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 a380db1e708..d7a297064b4 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 da4be9fb55d..56e26870c0d 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 \