rtl: builtins: (not just) rs6000: Add builtins for fegetround, feclearexcept and feraiseexcept [PR94193]

This optimizations were originally in glibc, but was removed
and suggested that they were a good fit as gcc builtins[1].

feclearexcept and feraiseexcept were extended (in comparison to the
glibc version) to accept any combination of the accepted flags, not
limited to just one flag bit at a time anymore.

The builtin expanders needs knowledge of the target libc's FE_*
values, so they are limited to expand only to suitable libcs.

[1] https://sourceware.org/legacy-ml/libc-alpha/2020-03/msg00047.html
    https://sourceware.org/legacy-ml/libc-alpha/2020-03/msg00080.html

2020-08-13  Raoni Fassina Firmino  <raoni@linux.ibm.com>

gcc/
	PR target/94193
	* builtins.cc (expand_builtin_fegetround): New function.
	(expand_builtin_feclear_feraise_except): New function.
	(expand_builtin): Add cases for BUILT_IN_FEGETROUND,
	BUILT_IN_FECLEAREXCEPT and BUILT_IN_FERAISEEXCEPT.
	* config/rs6000/rs6000.md (fegetroundsi): New pattern.
	(feclearexceptsi): New Pattern.
	(feraiseexceptsi): New Pattern.
	* doc/extend.texi: Add a new introductory paragraph about the
	new builtins.
	* doc/md.texi: (fegetround@var{m}): Document new optab.
	(feclearexcept@var{m}): Document new optab.
	(feraiseexcept@var{m}): Document new optab.
	* optabs.def (fegetround_optab): New optab.
	(feclearexcept_optab): New optab.
	(feraiseexcept_optab): New optab.

gcc/testsuite/
	PR target/94193
	* gcc.target/powerpc/builtin-feclearexcept-feraiseexcept-1.c: New test.
	* gcc.target/powerpc/builtin-feclearexcept-feraiseexcept-2.c: New test.
	* gcc.target/powerpc/builtin-fegetround.c: New test.

Signed-off-by: Raoni Fassina Firmino <raoni@linux.ibm.com>
This commit is contained in:
Raoni Fassina Firmino 2022-01-13 14:08:53 -03:00 committed by Segher Boessenkool
parent b1aa2a3cf1
commit 4343f5e256
8 changed files with 419 additions and 0 deletions

View File

@ -119,6 +119,9 @@ static rtx expand_builtin_mathfn_3 (tree, rtx, rtx);
static rtx expand_builtin_mathfn_ternary (tree, rtx, rtx);
static rtx expand_builtin_interclass_mathfn (tree, rtx);
static rtx expand_builtin_sincos (tree);
static rtx expand_builtin_fegetround (tree, rtx, machine_mode);
static rtx expand_builtin_feclear_feraise_except (tree, rtx, machine_mode,
optab);
static rtx expand_builtin_cexpi (tree, rtx);
static rtx expand_builtin_int_roundingfn (tree, rtx);
static rtx expand_builtin_int_roundingfn_2 (tree, rtx);
@ -2555,6 +2558,59 @@ expand_builtin_sincos (tree exp)
return const0_rtx;
}
/* Expand call EXP to the fegetround builtin (from C99 fenv.h), returning the
result and setting it in TARGET. Otherwise return NULL_RTX on failure. */
static rtx
expand_builtin_fegetround (tree exp, rtx target, machine_mode target_mode)
{
if (!validate_arglist (exp, VOID_TYPE))
return NULL_RTX;
insn_code icode = direct_optab_handler (fegetround_optab, SImode);
if (icode == CODE_FOR_nothing)
return NULL_RTX;
if (target == 0
|| GET_MODE (target) != target_mode
|| !(*insn_data[icode].operand[0].predicate) (target, target_mode))
target = gen_reg_rtx (target_mode);
rtx pat = GEN_FCN (icode) (target);
if (!pat)
return NULL_RTX;
emit_insn (pat);
return target;
}
/* Expand call EXP to either feclearexcept or feraiseexcept builtins (from C99
fenv.h), returning the result and setting it in TARGET. Otherwise return
NULL_RTX on failure. */
static rtx
expand_builtin_feclear_feraise_except (tree exp, rtx target,
machine_mode target_mode, optab op_optab)
{
if (!validate_arglist (exp, INTEGER_TYPE, VOID_TYPE))
return NULL_RTX;
rtx op0 = expand_normal (CALL_EXPR_ARG (exp, 0));
insn_code icode = direct_optab_handler (op_optab, SImode);
if (icode == CODE_FOR_nothing)
return NULL_RTX;
if (target == 0
|| GET_MODE (target) != target_mode
|| !(*insn_data[icode].operand[0].predicate) (target, target_mode))
target = gen_reg_rtx (target_mode);
rtx pat = GEN_FCN (icode) (target, op0);
if (!pat)
return NULL_RTX;
emit_insn (pat);
return target;
}
/* Expand a call to the internal cexpi builtin to the sincos math function.
EXP is the expression that is a call to the builtin function; if convenient,
the result should be placed in TARGET. */
@ -7056,6 +7112,26 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode,
return target;
break;
case BUILT_IN_FEGETROUND:
target = expand_builtin_fegetround (exp, target, target_mode);
if (target)
return target;
break;
case BUILT_IN_FECLEAREXCEPT:
target = expand_builtin_feclear_feraise_except (exp, target, target_mode,
feclearexcept_optab);
if (target)
return target;
break;
case BUILT_IN_FERAISEEXCEPT:
target = expand_builtin_feclear_feraise_except (exp, target, target_mode,
feraiseexcept_optab);
if (target)
return target;
break;
case BUILT_IN_APPLY_ARGS:
return expand_builtin_apply_args ();

View File

@ -6912,6 +6912,117 @@
[(set_attr "type" "fpload")
(set_attr "length" "8")
(set_attr "isa" "*,p8v,p8v")])
;; int fegetround(void)
;;
;; This expansion for the C99 function only expands for compatible
;; target libcs, because it needs to return one of FE_DOWNWARD,
;; FE_TONEAREST, FE_TOWARDZERO or FE_UPWARD with the values as defined
;; by the target libc, and since the libc is free to choose the values
;; (and they may differ from the hardware) and the expander needs to
;; know then beforehand, this expanded only expands for target libcs
;; that it can handle the values is knows.
;; Because of these restriction, this only expands on the desired
;; case and fallback to a call to libc otherwise.
(define_expand "fegetroundsi"
[(set (match_operand:SI 0 "gpc_reg_operand")
(unspec_volatile:SI [(const_int 0)] UNSPECV_MFFSL))]
"TARGET_HARD_FLOAT"
{
if (!OPTION_GLIBC)
FAIL;
rtx tmp_df = gen_reg_rtx (DFmode);
emit_insn (gen_rs6000_mffsl (tmp_df));
rtx tmp_di = simplify_gen_subreg (DImode, tmp_df, DFmode, 0);
rtx tmp_di_2 = gen_reg_rtx (DImode);
emit_insn (gen_anddi3 (tmp_di_2, tmp_di, GEN_INT (3)));
rtx tmp_si = gen_reg_rtx (SImode);
tmp_si = gen_lowpart (SImode, tmp_di_2);
emit_move_insn (operands[0], tmp_si);
DONE;
})
;; int feclearexcept(int excepts)
;;
;; This expansion for the C99 function only works when EXCEPTS is a
;; constant known at compile time and specifies any one of
;; FE_INEXACT, FE_DIVBYZERO, FE_UNDERFLOW and FE_OVERFLOW flags.
;; It doesn't handle values out of range, and always returns 0.
;; Note that FE_INVALID is unsupported because it maps to more than
;; one bit of the FPSCR register.
;; The FE_* are defined in the target libc, and since they are free to
;; choose the values and the expand needs to know them beforehand,
;; this expander only expands for target libcs that it can handle the
;; values it knows.
;; Because of these restrictions, this only expands on the desired
;; cases and fallback to a call to libc on any other case.
(define_expand "feclearexceptsi"
[(use (match_operand:SI 1 "const_int_operand" "n"))
(set (match_operand:SI 0 "gpc_reg_operand")
(const_int 0))]
"TARGET_HARD_FLOAT"
{
if (!OPTION_GLIBC)
FAIL;
unsigned int fe = INTVAL (operands[1]);
if (fe != (fe & 0x1e000000))
FAIL;
if (fe & 0x02000000) /* FE_INEXACT */
emit_insn (gen_rs6000_mtfsb0 (gen_rtx_CONST_INT (SImode, 6)));
if (fe & 0x04000000) /* FE_DIVBYZERO */
emit_insn (gen_rs6000_mtfsb0 (gen_rtx_CONST_INT (SImode, 5)));
if (fe & 0x08000000) /* FE_UNDERFLOW */
emit_insn (gen_rs6000_mtfsb0 (gen_rtx_CONST_INT (SImode, 4)));
if (fe & 0x10000000) /* FE_OVERFLOW */
emit_insn (gen_rs6000_mtfsb0 (gen_rtx_CONST_INT (SImode, 3)));
emit_move_insn (operands[0], const0_rtx);
DONE;
})
;; int feraiseexcept(int excepts)
;;
;; This expansion for the C99 function only works when excepts is a
;; constant known at compile time and specifies any one of
;; FE_INEXACT, FE_DIVBYZERO, FE_UNDERFLOW and FE_OVERFLOW flags.
;; It doesn't handle values out of range, and always returns 0.
;; Note that FE_INVALID is unsupported because it maps to more than
;; one bit of the FPSCR register.
;; The FE_* are defined in the target libc, and since they are free to
;; choose the values and the expand needs to know them beforehand,
;; this expander only expands for target libcs that it can handle the
;; values it knows.
;; Because of these restrictions, this only expands on the desired
;; cases and fallback to a call to libc on any other case.
(define_expand "feraiseexceptsi"
[(use (match_operand:SI 1 "const_int_operand" "n"))
(set (match_operand:SI 0 "gpc_reg_operand")
(const_int 0))]
"TARGET_HARD_FLOAT"
{
if (!OPTION_GLIBC)
FAIL;
unsigned int fe = INTVAL (operands[1]);
if (fe != (fe & 0x1e000000))
FAIL;
if (fe & 0x02000000) /* FE_INEXACT */
emit_insn (gen_rs6000_mtfsb1 (gen_rtx_CONST_INT (SImode, 6)));
if (fe & 0x04000000) /* FE_DIVBYZERO */
emit_insn (gen_rs6000_mtfsb1 (gen_rtx_CONST_INT (SImode, 5)));
if (fe & 0x08000000) /* FE_UNDERFLOW */
emit_insn (gen_rs6000_mtfsb1 (gen_rtx_CONST_INT (SImode, 4)));
if (fe & 0x10000000) /* FE_OVERFLOW */
emit_insn (gen_rs6000_mtfsb1 (gen_rtx_CONST_INT (SImode, 3)));
emit_move_insn (operands[0], const0_rtx);
DONE;
})
;; Define the TImode operations that can be done in a small number
;; of instructions. The & constraints are to prevent the register

View File

@ -13506,6 +13506,14 @@ In the same fashion, GCC provides @code{fpclassify}, @code{isfinite},
@code{__builtin_} prefixed. The @code{isinf} and @code{isnan}
built-in functions appear both with and without the @code{__builtin_} prefix.
GCC provides built-in versions of the ISO C99 floating-point rounding and
exceptions handling functions @code{fegetround}, @code{feclearexcept} and
@code{feraiseexcept}. They may not be available for all targets, and because
they need close interaction with libc internal values, they may not be available
for all target libcs, but in all cases they will gracefully fallback to libc
calls. This built-in functions appear both with and without the
@code{__builtin_} prefix.
@deftypefn {Built-in Function} void *__builtin_alloca (size_t size)
The @code{__builtin_alloca} function must be called at block scope.
The function allocates an object @var{size} bytes large on the stack

View File

@ -6087,6 +6087,23 @@ mode @var{m}, which is a scalar or vector floating-point mode.
This pattern is not allowed to @code{FAIL}.
@cindex @code{fegetround@var{m}} instruction pattern
@item @samp{fegetround@var{m}}
Store the current machine floating-point rounding mode into operand 0.
Operand 0 has mode @var{m}, which is scalar. This pattern is used to
implement the @code{fegetround} function from the ISO C99 standard.
@cindex @code{feclearexcept@var{m}} instruction pattern
@cindex @code{feraiseexcept@var{m}} instruction pattern
@item @samp{feclearexcept@var{m}}
@item @samp{feraiseexcept@var{m}}
Clears or raises the supported machine floating-point exceptions
represented by the bits in operand 1. Error status is stored as
nonzero value in operand 0. Both operands have mode @var{m}, which is
a scalar. These patterns are used to implement the
@code{feclearexcept} and @code{feraiseexcept} functions from the ISO
C99 standard.
@cindex @code{exp@var{m}2} instruction pattern
@item @samp{exp@var{m}2}
Raise e (the base of natural logarithms) to the power of operand 1

View File

@ -331,6 +331,10 @@ OPTAB_D (sinh_optab, "sinh$a2")
OPTAB_D (tan_optab, "tan$a2")
OPTAB_D (tanh_optab, "tanh$a2")
OPTAB_D (fegetround_optab, "fegetround$a")
OPTAB_D (feclearexcept_optab, "feclearexcept$a")
OPTAB_D (feraiseexcept_optab, "feraiseexcept$a")
/* C99 implementations of fmax/fmin. */
OPTAB_D (fmax_optab, "fmax$a3")
OPTAB_D (fmin_optab, "fmin$a3")

View File

@ -0,0 +1,76 @@
/* { dg-do run } */
/* { dg-require-effective-target fenv_exceptions } */
/* { dg-options "-lm -fno-builtin" } */
/* This testcase ensures that the builtins expand with the matching arguments
or otherwise fallback gracefully to a function call, and don't ICE during
compilation.
"-fno-builtin" option is used to enable calls to libc implementation of the
gcc builtins tested when not using __builtin_ prefix. */
#include <fenv.h>
int
main ()
{
int rsi = 0;
long rsl = 0;
short rss = 0;
char rsc = 0;
unsigned int rui = 0;
unsigned long rul = 0;
unsigned short rus = 0;
unsigned char ruc = 0;
int e = FE_DIVBYZERO;
__builtin_feclearexcept(e); // CALL
__builtin_feclearexcept(FE_ALL_EXCEPT); // CALL
__builtin_feclearexcept(FE_INVALID); // CALL
__builtin_feclearexcept(FE_INVALID | FE_INEXACT); // CALL
__builtin_feclearexcept(FE_INEXACT | FE_DIVBYZERO |
FE_UNDERFLOW | FE_OVERFLOW); // EXPAND
__builtin_feclearexcept(FE_INEXACT | FE_OVERFLOW); // EXPAND
__builtin_feclearexcept(FE_INEXACT); // EXPAND
__builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
__builtin_feclearexcept(FE_UNDERFLOW); // EXPAND
__builtin_feclearexcept(FE_OVERFLOW); // EXPAND
__builtin_feclearexcept(0); // EXPAND
rsi = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rsl = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rss = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rsc = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rui = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rul = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
rus = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
ruc = __builtin_feclearexcept(FE_DIVBYZERO); // EXPAND
__builtin_feraiseexcept(e); // CALL
__builtin_feraiseexcept(FE_ALL_EXCEPT); // CALL
__builtin_feraiseexcept(FE_INVALID); // CALL
__builtin_feraiseexcept(FE_INVALID | FE_INEXACT); // CALL
__builtin_feraiseexcept(FE_INEXACT | FE_DIVBYZERO |
FE_UNDERFLOW | FE_OVERFLOW); // EXPAND
__builtin_feraiseexcept(FE_INEXACT | FE_OVERFLOW); // EXPAND
__builtin_feraiseexcept(FE_INEXACT); // EXPAND
__builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
__builtin_feraiseexcept(FE_UNDERFLOW); // EXPAND
__builtin_feraiseexcept(FE_OVERFLOW); // EXPAND
__builtin_feraiseexcept(0); // EXPAND
rsi = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rsl = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rss = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rsc = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rui = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rul = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
rus = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
ruc = __builtin_feraiseexcept(FE_DIVBYZERO); // EXPAND
return 0;
}

View File

@ -0,0 +1,91 @@
/* { dg-do run } */
/* { dg-require-effective-target fenv_exceptions } */
/* { dg-options "-lm -fno-builtin" } */
/* This testcase ensures that the builtins are correctly expanded and match the
expected result.
"-fno-builtin" option is used to enable calls to libc implementation of the
gcc builtins tested when not using __builtin_ prefix.
The excepts parameter needs to be passed as constant to
__builtin_feclearexcept and __builtin_feraiseexcept because some bultins only
expand on constant input. */
#include <fenv.h>
#ifdef DEBUG
#include <stdio.h>
#define INFO(...) printf(__VA_ARGS__)
#define FAIL(ret, raised, expected, excepts, excepts_str, func) \
printf("ERROR [l %d] testing %s (%x): %s returned %d." \
" Raised except bits %x, expecected %x\n", \
__LINE__, excepts_str, excepts, func, ret, raised, expected)
#else
void abort (void);
#define INFO(...)
#define FAIL(ret, raised, expected, excepts, excepts_str, func) abort()
#endif
#define TEST(excepts) \
do { \
int ret = 0; \
int raised = 0; \
\
INFO("test: %s (%x)\n", #excepts, excepts); \
\
feclearexcept(FE_ALL_EXCEPT); \
ret = __builtin_feraiseexcept(excepts); \
raised = fetestexcept(FE_ALL_EXCEPT); \
if (ret != 0 || raised != (excepts)) \
FAIL(ret, raised, excepts, excepts, #excepts, \
"__builtin_feraiseexcept"); \
\
feraiseexcept(FE_ALL_EXCEPT); \
ret = __builtin_feclearexcept(excepts); \
raised = fetestexcept(FE_ALL_EXCEPT); \
if (ret != 0 || raised != (FE_ALL_EXCEPT & ~(excepts))) \
FAIL(ret, raised, FE_ALL_EXCEPT & ~(excepts), excepts, #excepts, \
"__builtin_feclearexcept"); \
} while (0)
int
main ()
{
TEST(0);
TEST(FE_ALL_EXCEPT);
TEST(FE_INVALID);
TEST(FE_DIVBYZERO);
TEST(FE_INEXACT);
TEST(FE_OVERFLOW);
TEST(FE_UNDERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO);
TEST(FE_INVALID | FE_INEXACT);
TEST(FE_INVALID | FE_OVERFLOW);
TEST(FE_INVALID | FE_UNDERFLOW);
TEST(FE_DIVBYZERO | FE_INEXACT);
TEST(FE_DIVBYZERO | FE_OVERFLOW);
TEST(FE_DIVBYZERO | FE_UNDERFLOW);
TEST(FE_INEXACT | FE_OVERFLOW);
TEST(FE_INEXACT | FE_UNDERFLOW);
TEST(FE_OVERFLOW | FE_UNDERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO | FE_INEXACT);
TEST(FE_INVALID | FE_DIVBYZERO | FE_OVERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO | FE_UNDERFLOW);
TEST(FE_INVALID | FE_INEXACT | FE_OVERFLOW);
TEST(FE_INVALID | FE_INEXACT | FE_UNDERFLOW);
TEST(FE_INVALID | FE_OVERFLOW | FE_UNDERFLOW);
TEST(FE_DIVBYZERO | FE_INEXACT | FE_OVERFLOW);
TEST(FE_DIVBYZERO | FE_INEXACT | FE_UNDERFLOW);
TEST(FE_DIVBYZERO | FE_OVERFLOW | FE_UNDERFLOW);
TEST(FE_INEXACT | FE_OVERFLOW | FE_UNDERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO | FE_INEXACT | FE_UNDERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO | FE_INEXACT | FE_OVERFLOW);
TEST(FE_INVALID | FE_DIVBYZERO | FE_UNDERFLOW | FE_OVERFLOW);
TEST(FE_INVALID | FE_INEXACT | FE_UNDERFLOW | FE_OVERFLOW);
TEST(FE_DIVBYZERO | FE_INEXACT | FE_UNDERFLOW | FE_OVERFLOW);
return 0;
}

View File

@ -0,0 +1,36 @@
/* { dg-do run } */
/* { dg-require-effective-target fenv_exceptions } */
/* { dg-options "-lm -fno-builtin" } */
/* This testcase ensures that the builtins is correctly expanded and match the
expected result from the standard function.
"-fno-builtin" option is used to enable calls to libc implementation of the
gcc builtins tested when not using __builtin_ prefix. */
#include <fenv.h>
#ifdef DEBUG
#include <stdio.h>
#define FAIL(v, e) printf("ERROR, __builtin_fegetround() returned %d," \
" not the expecected value %d\n", v, e);
#else
void abort (void);
#define FAIL(v, e) abort()
#endif
int
main ()
{
int i, rounding, expected;
const int rm[] = {FE_TONEAREST, FE_TOWARDZERO, FE_UPWARD, FE_DOWNWARD};
for (i = 0; i < sizeof rm / sizeof rm[0]; i++)
{
fesetround(rm[i]);
rounding = __builtin_fegetround();
expected = fegetround();
if (rounding != expected)
FAIL(rounding, expected);
}
return 0;
}