AVR/LibF7: target/122177 - fix fmin / fmax return value for one NaN arg.

fmin and fmax should return the non-NaN argument in the case where
exactly one argument is a NaN.

Moreover, IEEE double fmin and fmax can be performed without
first converting the args to the internal representation and
then converting back again.

	PR target/122177
libgcc/config/avr/libf7/
	* libf7-common.mk (m_ddd): Remove: fmin, fmax.
	(F7_ASM_PARTS): Add: D_fminfmax.
	* libf7-asm.sx (D_fmanfmax): New module.
	* f7-wraps.h: Rebuild.

gcc/testsuite/
	* gcc.target/avr/fminfmax-1.c: New test.
This commit is contained in:
Georg-Johann Lay 2025-10-05 20:56:56 +02:00
parent e5731a4bc5
commit efb3cd64fd
4 changed files with 206 additions and 30 deletions

View File

@ -0,0 +1,116 @@
/* { dg-do run { target { ! avr_tiny } } } */
/* { dg-additional-options { -std=gnu99 -Os -mcall-prologues } } */
typedef __INT8_TYPE__ int8_t;
typedef __UINT8_TYPE__ uint8_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT64_TYPE__ uint64_t;
typedef __INT64_TYPE__ int64_t;
#define ARRAY_SIZE(X) (sizeof(X) / sizeof(*X))
const __flash uint64_t vals[] =
{
// NaNs
0xffffffffffffffff,
0x7fffffffffffffff,
0xfff0000000000001,
0x7ff0000000000001,
// Some non-NaN doubles, increasing in magnitude.
0xfff0000000000000, // -Inf
0xffefffffffffffff,
0xffe0000000000000,
0x8010000000000000,
0x800fffffffffffff,
0x800ffffffffffffe,
0x8007fffffffffffe,
0x8000000000000001,
0x0000000000000000,
0x0000000000000001,
0x0007fffffffffffe,
0x000ffffffffffffe,
0x000fffffffffffff,
0x0010000000000000,
0x7fe0000000000000,
0x7fefffffffffffff,
0x7ff0000000000000 // +Inf
};
#define SMASK ((uint64_t) 1 << 63)
char d64_nan_p (uint64_t a)
{
return (a & ~SMASK) > (uint64_t) 0x7ff << 52;
}
extern uint64_t xmin (uint64_t, uint64_t) __asm("__fmin");
extern uint64_t xmax (uint64_t, uint64_t) __asm("__fmax");
void test_fmin (uint8_t i, uint8_t j)
{
uint64_t a = vals[i];
uint64_t b = vals[j];
uint64_t m = xmin (a, b);
char a_nan_p = d64_nan_p (a);
char b_nan_p = d64_nan_p (b);
if (a_nan_p + b_nan_p == 2)
{
if (!d64_nan_p (m))
__builtin_exit (__LINE__);
}
else
{
uint64_t r = 0?0
: a_nan_p ? b
: b_nan_p ? a
: i < j ? a : b;
if (r != m)
__builtin_exit (__LINE__);
}
}
void test_fmax (uint8_t i, uint8_t j)
{
uint64_t a = vals[i];
uint64_t b = vals[j];
uint64_t m = xmax (a, b);
char a_nan_p = d64_nan_p (a);
char b_nan_p = d64_nan_p (b);
if (a_nan_p + b_nan_p == 2)
{
if (!d64_nan_p (m))
__builtin_exit (__LINE__);
}
else
{
uint64_t r = 0?0
: a_nan_p ? b
: b_nan_p ? a
: i > j ? a : b;
if (r != m)
__builtin_exit (__LINE__);
}
}
void tests (void)
{
for (uint8_t i = 0; i < ARRAY_SIZE (vals); ++i)
for (uint8_t j = 0; j < ARRAY_SIZE (vals); ++j)
{
test_fmin (i, j);
}
}
int main (void)
{
tests ();
return 0;
}

View File

@ -169,7 +169,7 @@ _ENDF __extendsfdf2
;; Functions that usually live in libm: Depending on [long] double layout,
;; define <name> and <name>l as weak alias(es) of __<name> for <name> in:
;; pow fmin fmax fmod hypot atan2 fdim
;; pow fmod hypot atan2 fdim
;; double __pow (double, double)
#ifdef F7MOD_D_pow_
@ -183,30 +183,6 @@ _DEFUN __pow
_ENDF __pow
#endif /* F7MOD_D_pow_ */
;; double __fmin (double, double)
#ifdef F7MOD_D_fmin_
_DEFUN __fmin
DALIAS fmin
LALIAS fminl
.global F7_NAME(fmin)
ldi ZH, hi8(gs(F7_NAME(fmin)))
ldi ZL, lo8(gs(F7_NAME(fmin)))
F7jmp call_ddd
_ENDF __fmin
#endif /* F7MOD_D_fmin_ */
;; double __fmax (double, double)
#ifdef F7MOD_D_fmax_
_DEFUN __fmax
DALIAS fmax
LALIAS fmaxl
.global F7_NAME(fmax)
ldi ZH, hi8(gs(F7_NAME(fmax)))
ldi ZL, lo8(gs(F7_NAME(fmax)))
F7jmp call_ddd
_ENDF __fmax
#endif /* F7MOD_D_fmax_ */
;; double __fmod (double, double)
#ifdef F7MOD_D_fmod_
_DEFUN __fmod

View File

@ -1765,10 +1765,14 @@ ENDF class_D
;;; T = 0: Comparison is ordered, and Z, N, C, S flags are set according
;;; to compare (double A, double B) as if set by a signed int comparison.
;;; Note that f(+0) = f(-0) = 0.
;;; In any case, return R24 = 1.
;;; In any case:
;;; - return R24 = 1.
;;; - return R25.0 = isNaN (A)
;;; - return R25.1 = isNaN (B)
DEFUN D_cmp
rcall D_cmp.map_i64
brts 9f
bld __tmp_reg__, 0
push __tmp_reg__
;; Save A somewhere else...
wmov AA6, A6
mov AA5, A5
@ -1794,7 +1798,14 @@ DEFUN D_cmp
cpc AA5, BB5
cpc AA6, BB6
cpc AA7, BB7
9: ldi r24, 1
pop r25
;; R25.0 <=> A is NaN
;; R25.1 <=> B is NaN
;; T <=> comparison is unordered
bld r25, 1
sbrc r25, 0
set
ldi r24, 1
ret
;;; A is NaN: Set T=1.
@ -1904,6 +1915,79 @@ _DEFUN __unorddf2
_ENDF __unorddf2
#endif /* F7MOD_D_unord_ */
#ifdef F7MOD_D_fminfmax_
_DEFUN __fmin
DALIAS fmin
LALIAS fminl
inc __zero_reg__
_LABEL __fmax
DALIAS fmax
LALIAS fmaxl
;; Push A[].
push r25
push r24
push r23
push r22
push r21
push r20
push r19
push r18
;; fmin or fmax
push __zero_reg__
clr __zero_reg__
XCALL __gedf2
pop __tmp_reg__
andi r25, 0x3 ; NaNs?
brne .Lnan
;; No NaNs involved.
eor __tmp_reg__, r24 ; (f == fmin) ^ (A >= B)
brne 1f
2:
;; Return B since the cases are:
;; fmax && A < B
;; fmin && A >= B
#ifdef __AVR_XMEGA__
in XL, __SP_L__
in XH, __SP_H__
adiw XL, 8
out __SP_L__, XL
out __SP_H__, XH
#else
pop r0 $ pop r0 $ pop r0 $ pop r0
pop r0 $ pop r0 $ pop r0 $ pop r0
#endif
wmov r24, r16
wmov r22, r14
wmov r20, r12
wmov r18, r10
ret
1:
;; Return A since the cases are:
;; fmax && A >= B
;; fmin && A < B
pop r18
pop r19
pop r20
pop r21
pop r22
pop r23
pop r24
pop r25
ret
.Lnan:
;; There are NaNs.
;; When only the 1st argument is a NaN, then return the 2nd argument
cpi r25, 0x1
breq 2b
;; When the 2nd argument is a NaN, then return the 1st argument.
;; When both arguments are NaNs, then return NaN (e.g. the 1st argument).
rjmp 1b
_ENDF __fmax
#endif /* F7MOD_D_fminfmax_ */
#ifdef F7MOD_call_dd_

View File

@ -24,7 +24,7 @@ F7_ASM_PARTS += store_expo sqrt16 sqrt_approx div
F7_ASM_PARTS += D_class D_fma D_powi
F7_ASM_PARTS += D_isnan D_isinf D_isfinite D_signbit D_copysign D_neg D_fabs
F7_ASM_PARTS += D_cmp D_eq D_ne D_ge D_gt D_le D_lt D_unord
F7_ASM_PARTS += D_cmp D_eq D_ne D_ge D_gt D_le D_lt D_unord D_fminfmax
F7_ASM_PARTS += call_dd call_ddd
@ -35,7 +35,7 @@ g_xdd_cmp +=
g_dx += floatunsidf floatsidf extendsfdf2
g_xd += fixdfsi fixdfdi fixunsdfdi fixunsdfsi truncdfsf2
m_ddd += pow fmin fmax fmod hypot atan2 fdim
m_ddd += pow fmod hypot atan2 fdim
m_ddx += ldexp frexp
m_dd += sqrt cbrt exp exp10 pow10 log log10 log2 sin cos tan cotan asin acos atan
m_dd += ceil floor trunc round sinh cosh tanh