From a2ca83c04a73c4e1e8465a616d8bbe5118b1b05f Mon Sep 17 00:00:00 2001 From: David Schultz Date: Fri, 11 Jun 2004 03:22:34 +0000 Subject: [PATCH] Add some fenv.h regression tests. --- tools/regression/lib/msun/Makefile | 16 + tools/regression/lib/msun/test-fenv.c | 519 ++++++++++++++++++++++++++ 2 files changed, 535 insertions(+) create mode 100644 tools/regression/lib/msun/Makefile create mode 100644 tools/regression/lib/msun/test-fenv.c diff --git a/tools/regression/lib/msun/Makefile b/tools/regression/lib/msun/Makefile new file mode 100644 index 000000000000..7806d6d187f1 --- /dev/null +++ b/tools/regression/lib/msun/Makefile @@ -0,0 +1,16 @@ +# $FreeBSD$ + +TESTS= test-fenv +CFLAGS+= -O0 -lm +ARCH!= uname -m +.if ${ARCH} == "alpha" +CFLAGS+= -mieee-with-inexact -mfp-rounding-mode=d +.endif + +.PHONY: tests +tests: ${TESTS} + for p in ${TESTS}; do ${.OBJDIR}/$$p; done + +.PHONY: clean +clean: + -rm -f ${TESTS} diff --git a/tools/regression/lib/msun/test-fenv.c b/tools/regression/lib/msun/test-fenv.c new file mode 100644 index 000000000000..c789b07c466f --- /dev/null +++ b/tools/regression/lib/msun/test-fenv.c @@ -0,0 +1,519 @@ +/*- + * Copyright (c) 2004 David Schultz + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* + * Test the correctness and C99-compliance of various fenv.h features. + */ + +#include +__FBSDID("$FreeBSD$"); + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/* + * Implementations are permitted to define additional exception flags + * not specified in the standard, so it is not necessarily true that + * FE_ALL_EXCEPT == ALL_STD_EXCEPT. + */ +#define ALL_STD_EXCEPT (FE_DIVBYZERO | FE_INEXACT | FE_INVALID | \ + FE_OVERFLOW | FE_UNDERFLOW) + +#define NEXCEPTS (sizeof(std_excepts) / sizeof(std_excepts[0])) + +static const int std_excepts[] = { + FE_INVALID, + FE_DIVBYZERO, + FE_OVERFLOW, + FE_UNDERFLOW, + FE_INEXACT, +}; + +/* init_exceptsets() initializes this to the power set of std_excepts[] */ +static int std_except_sets[1 << NEXCEPTS]; + +static void init_exceptsets(void); + +static void test_dfl_env(void); +static void test_fegsetenv(void); +static void test_fegsetexceptflag(void); +static void test_fegsetmask(void); +static void test_fegsetround(void); +static void test_feholdupdate(void); +static void test_feraiseexcept(void); +static void test_fetestclearexcept(void); + +static int getround(void); +static void raiseexcept(int excepts); +static void trap_handler(int sig); + +#pragma STDC FENV_ACCESS ON + +int +main(int argc, char *argv[]) +{ + + init_exceptsets(); + test_dfl_env(); + test_fetestclearexcept(); + test_fegsetexceptflag(); + test_feraiseexcept(); + test_fegsetround(); + test_fegsetenv(); + test_fegsetmask(); + test_feholdupdate(); + + printf("PASS fenv\n"); + return (0); +} + +/* + * Initialize std_except_sets[] to the power set of std_excepts[] + */ +void +init_exceptsets(void) +{ + int i, j, sr; + + for (i = 0; i < 1 << NEXCEPTS; i++) { + for (sr = i, j = 0; sr != 0; sr >>= 1, j++) + std_except_sets[i] |= std_excepts[j] & ((~sr & 1) - 1); + } +} + +/* + * This tests checks the default FP environment, so it must be first. + * The memcmp() test below may be too much to ask for, since there + * could be multiple machine-specific default environments. + */ +static void +test_dfl_env(void) +{ +#ifndef NO_STRICT_DFL_ENV + fenv_t env; + + fegetenv(&env); + assert(memcmp(&env, FE_DFL_ENV, sizeof(env)) == 0); +#endif + assert(fetestexcept(FE_ALL_EXCEPT) == 0); +} + +/* + * Test fetestexcept() and feclearexcept(). + */ +static void +test_fetestclearexcept(void) +{ + int excepts, i; + + for (i = 0; i < 1 << NEXCEPTS; i++) + assert(fetestexcept(std_except_sets[i]) == 0); + for (i = 0; i < 1 << NEXCEPTS; i++) { + excepts = std_except_sets[i]; + + /* FE_ALL_EXCEPT might be special-cased, as on i386. */ + raiseexcept(excepts); + assert(fetestexcept(excepts) == excepts); + assert(feclearexcept(FE_ALL_EXCEPT) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + + raiseexcept(excepts); + assert(fetestexcept(excepts) == excepts); + if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0) { + excepts |= FE_INEXACT; + assert((fetestexcept(ALL_STD_EXCEPT) | FE_INEXACT) == + excepts); + } else { + assert(fetestexcept(ALL_STD_EXCEPT) == excepts); + } + assert(feclearexcept(excepts) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == 0); + } +} + +/* + * Test fegetexceptflag() and fesetexceptflag(). + * + * Prerequisites: fetestexcept(), feclearexcept() + */ +static void +test_fegsetexceptflag(void) +{ + fexcept_t flag; + int excepts, i; + + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + for (i = 0; i < 1 << NEXCEPTS; i++) { + excepts = std_except_sets[i]; + + assert(fegetexceptflag(&flag, excepts) == 0); + raiseexcept(ALL_STD_EXCEPT); + assert(fesetexceptflag(&flag, excepts) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == + (ALL_STD_EXCEPT ^ excepts)); + + assert(fegetexceptflag(&flag, FE_ALL_EXCEPT) == 0); + assert(feclearexcept(FE_ALL_EXCEPT) == 0); + assert(fesetexceptflag(&flag, excepts) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == 0); + assert(fesetexceptflag(&flag, ALL_STD_EXCEPT ^ excepts) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == + (ALL_STD_EXCEPT ^ excepts)); + + assert(feclearexcept(FE_ALL_EXCEPT) == 0); + } +} + +/* + * Test feraiseexcept(). + * + * Prerequisites: fetestexcept(), feclearexcept() + */ +static void +test_feraiseexcept(void) +{ + int excepts, i; + + for (i = 0; i < 1 << NEXCEPTS; i++) { + excepts = std_except_sets[i]; + + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(feraiseexcept(excepts) == 0); + if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0) { + excepts |= FE_INEXACT; + assert((fetestexcept(ALL_STD_EXCEPT) | FE_INEXACT) == + excepts); + } else { + assert(fetestexcept(ALL_STD_EXCEPT) == excepts); + } + assert(feclearexcept(FE_ALL_EXCEPT) == 0); + } + assert(feraiseexcept(FE_INVALID | FE_DIVBYZERO) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == (FE_INVALID | FE_DIVBYZERO)); + assert(feraiseexcept(FE_OVERFLOW | FE_UNDERFLOW | FE_INEXACT) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == ALL_STD_EXCEPT); + assert(feclearexcept(FE_ALL_EXCEPT) == 0); +} + +/* + * Test fegetround() and fesetround(). + */ +static void +test_fegsetround(void) +{ + + assert(fegetround() == FE_TONEAREST); + assert(getround() == FE_TONEAREST); + assert(fesetround(FE_DOWNWARD) == 0); + assert(fegetround() == FE_DOWNWARD); + assert(getround() == FE_DOWNWARD); + assert(fesetround(FE_UPWARD) == 0); + assert(getround() == FE_UPWARD); + assert(fegetround() == FE_UPWARD); + assert(fesetround(FE_TOWARDZERO) == 0); + assert(getround() == FE_TOWARDZERO); + assert(fegetround() == FE_TOWARDZERO); + assert(fesetround(FE_TONEAREST) == 0); + assert(getround() == FE_TONEAREST); + assert(feclearexcept(FE_ALL_EXCEPT) == 0); +} + +/* + * Test fegetenv() and fesetenv(). + * + * Prerequisites: fetestexcept(), feclearexcept(), fegetround(), fesetround() + */ +static void +test_fegsetenv(void) +{ + fenv_t env1, env2; + int excepts, i; + + for (i = 0; i < 1 << NEXCEPTS; i++) { + excepts = std_except_sets[i]; + + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(fegetround() == FE_TONEAREST); + assert(fegetenv(&env1) == 0); + + /* + * fe[gs]etenv() should be able to save and restore + * exception flags without the spurious inexact + * exceptions that afflict raiseexcept(). + */ + raiseexcept(excepts); + if ((excepts & (FE_UNDERFLOW | FE_OVERFLOW)) != 0 && + (excepts & FE_INEXACT) == 0) + assert(feclearexcept(FE_INEXACT) == 0); + + fesetround(FE_DOWNWARD); + assert(fegetenv(&env2) == 0); + assert(fesetenv(&env1) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(fegetround() == FE_TONEAREST); + + assert(fesetenv(&env2) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == excepts); + assert(fegetround() == FE_DOWNWARD); + assert(fesetenv(&env1) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + assert(fegetround() == FE_TONEAREST); + } +} + +/* + * Test fegetmask() and fesetmask(). + * + * Prerequisites: fetestexcept(), feraiseexcept() + */ +static void +test_fegsetmask(void) +{ + struct sigaction act; + int except, i, pass, raise, status; + + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = trap_handler; + for (pass = 0; pass < 2; pass++) { + for (i = 0; i < NEXCEPTS; i++) { + except = std_excepts[i]; + /* over/underflow may also raise inexact */ + if (except == FE_INEXACT) + raise = FE_DIVBYZERO | FE_INVALID; + else + raise = ALL_STD_EXCEPT ^ except; + + /* + * We need to fork a child process because + * there isn't a portable way to recover from + * a floating-point exception. + */ + switch(fork()) { + case 0: /* child */ + assert((fegetmask() & ALL_STD_EXCEPT) == 0); + assert((fesetmask(except) & ALL_STD_EXCEPT) == + 0); + assert(fegetmask() == except); + raiseexcept(raise); + assert(feraiseexcept(raise) == 0); + assert(fetestexcept(ALL_STD_EXCEPT) == raise); + + assert(sigaction(SIGFPE, &act, NULL) == 0); + switch (pass) { + case 0: + raiseexcept(except); + case 1: + feraiseexcept(except); + default: + assert(0); + } + assert(0); + default: /* parent */ + assert(wait(&status) > 0); + /* + * Avoid assert() here so that it's possible + * to examine a failed child's core dump. + */ + if (!WIFEXITED(status)) + errx(1, "child aborted\n"); + assert(WEXITSTATUS(status) == 0); + break; + case -1: /* error */ + assert(0); + } + } + } + assert(fetestexcept(FE_ALL_EXCEPT) == 0); +} + +/* + * Test feholdexcept() and feupdateenv(). + * + * Prerequisites: fetestexcept(), fegetround(), fesetround(), fesetmask() + */ +static void +test_feholdupdate(void) +{ + fenv_t env; + + struct sigaction act; + int except, i, pass, status, raise; + + sigemptyset(&act.sa_mask); + act.sa_flags = 0; + act.sa_handler = trap_handler; + for (pass = 0; pass < 2; pass++) { + for (i = 0; i < NEXCEPTS; i++) { + except = std_excepts[i]; + /* over/underflow may also raise inexact */ + if (except == FE_INEXACT) + raise = FE_DIVBYZERO | FE_INVALID; + else + raise = ALL_STD_EXCEPT ^ except; + + /* + * We need to fork a child process because + * there isn't a portable way to recover from + * a floating-point exception. + */ + switch(fork()) { + case 0: /* child */ + /* + * We don't want to cause a fatal exception in + * the child until the second pass, so we can + * check other properties of feupdateenv(). + */ + if (pass == 1) + assert((fesetmask(except) & + ALL_STD_EXCEPT) == 0); + raiseexcept(raise); + assert(fesetround(FE_DOWNWARD) == 0); + assert(feholdexcept(&env) == 0); + assert(fetestexcept(FE_ALL_EXCEPT) == 0); + raiseexcept(except); + assert(fesetround(FE_UPWARD) == 0); + + if (pass == 1) + assert(sigaction(SIGFPE, &act, NULL) == + 0); + assert(feupdateenv(&env) == 0); + assert(fegetround() == FE_DOWNWARD); + assert(fetestexcept(ALL_STD_EXCEPT) == + (except | raise)); + + assert(pass == 0); + _exit(0); + default: /* parent */ + assert(wait(&status) > 0); + /* + * Avoid assert() here so that it's possible + * to examine a failed child's core dump. + */ + if (!WIFEXITED(status)) + errx(1, "child aborted\n"); + assert(WEXITSTATUS(status) == 0); + break; + case -1: /* error */ + assert(0); + } + } + } + assert(fetestexcept(FE_ALL_EXCEPT) == 0); +} + +/* + * Raise a floating-point exception without relying on the standard + * library routines, which we are trying to test. + * + * XXX We can't raise an {over,under}flow without also raising an + * inexact exception. + */ +static void +raiseexcept(int excepts) +{ + volatile double d; + + /* + * With a compiler that supports the FENV_ACCESS pragma + * properly, simple expressions like '0.0 / 0.0' should + * be sufficient to generate traps. Unfortunately, we + * need to bring a volatile variable into the equation + * to prevent incorrect optimizations. + */ + if (excepts & FE_INVALID) { + d = 0.0; + d = 0.0 / d; + } + if (excepts & FE_DIVBYZERO) { + d = 0.0; + d = 1.0 / d; + } + if (excepts & FE_OVERFLOW) { + d = DBL_MAX; + d *= 2.0; + } + if (excepts & FE_UNDERFLOW) { + d = DBL_MIN; + d /= DBL_MAX; + } + if (excepts & FE_INEXACT) { + d = DBL_MIN; + d += 1.0; + } + + /* + * On the x86 (and some other architectures?) the FPU and + * integer units are decoupled. We need to execute an FWAIT + * or a floating-point instruction to get synchronous exceptions. + */ + d = 1.0; + d += 1.0; +} + +/* + * Determine the current rounding mode without relying on the fenv + * routines. This function may raise an inexact exception. + */ +static int +getround(void) +{ + volatile double d; + + /* + * This test works just as well with 0.0 - 0.0, except on ia64 + * where 0.0 - 0.0 gives the wrong sign when rounding downwards. + */ + d = 1.0; + d -= 1.0; + if (copysign(1.0, d) < 0.0) + return (FE_DOWNWARD); + + d = 1.0; + if (d + (DBL_EPSILON * 3.0 / 4.0) == 1.0) + return (FE_TOWARDZERO); + if (d + (DBL_EPSILON * 1.0 / 4.0) > 1.0) + return (FE_UPWARD); + + return (FE_TONEAREST); +} + +static void +trap_handler(int sig) +{ + + assert(sig == SIGFPE); + _exit(0); +}