Introduce support for trapping math

The feenableexcept() and fedisableexcept() APIs are now provided which
let you detect when NaNs appear the moment it happens from anywhere in
your program. Tests have also been added for the mission critical math
functions expf() and erff(), whose perfect operation has been assured.
See examples/trapping.c to see how to use this powerful functionality.
This commit is contained in:
Justine Tunney 2024-04-30 13:32:23 -07:00
parent 403bc25412
commit 5c6877b02b
No known key found for this signature in database
GPG key ID: BE714B4575D6E328
13 changed files with 576 additions and 2 deletions

View file

@ -43,14 +43,55 @@ struct FpuStackEntry {
};
struct thatispacked FpuState {
/* 8087 FPU Control Word
IM: Invalid Operation
DM: Denormal Operand
ZM: Zero Divide
OM: Overflow
UM: Underflow
PM: Precision
PC: Precision Control
{float,,double,long double}
RC: Rounding Control
{even, -, +, 0}
drr
0b0000001001111111 */
uint16_t cwd;
/* 8087 FPU Status Word */
uint16_t swd;
uint16_t ftw;
uint16_t fop;
uint64_t rip;
uint64_t rdp;
/* SSE CONTROL AND STATUS REGISTER
IE: Invalid Operation Flag
DE: Denormal Flag
ZE: Divide-by-Zero Flag
OE: Overflow Flag
UE: Underflow Flag
PE: Precision Flag
DAZ: Denormals Are Zeros
IM: Invalid Operation Mask
DM: Denormal Operation Mask
ZM: Divide-by-Zero Mask
OM: Overflow Mask
UM: Underflow Mask
PM: Precision Mask
RC: Rounding Control
{even, -, +, 0}
FTZ: Flush To Zero
reserved
0b00000000000000000001111110000000 */
uint32_t mxcsr;
uint32_t mxcr_mask;
struct FpuStackEntry st[8];
struct XmmRegister xmm[16];
uint32_t __padding[24];

View file

@ -5,6 +5,7 @@ COSMOPOLITAN_C_START_
errno_t cosmo_once(_Atomic(uint32_t) *, void (*)(void));
int systemvpe(const char *, char *const[], char *const[]) libcesque;
char *GetProgramExecutableName(void);
void unleaf(void);
COSMOPOLITAN_C_END_
#endif /* COSMOPOLITAN_LIBC_COSMO_H_ */

View file

@ -0,0 +1,92 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/runtime/fenv.h"
/**
* Disables floating point exception trapping, e.g.
*
* feenableexcept(FE_INVALID | FE_DIVBYZERO |
* FE_OVERFLOW | FE_UNDERFLOW);
*
* When trapping is enabled, something should handle SIGFPE. Calling
* ShowCrashReports() at startup will install a generic handler with
* backtraces and the symbol of the `si->si_code` which UNIX defines
*
* - `FPE_INTOVF`: integer overflow
* - `FPE_INTDIV`: integer divide by zero
* - `FPE_FLTDIV`: floating point divide by zero
* - `FPE_FLTOVF`: floating point overflow
* - `FPE_FLTUND`: floating point underflow
* - `FPE_FLTRES`: floating point inexact
* - `FPE_FLTINV`: invalid floating point operation
* - `FPE_FLTSUB`: subscript out of range
*
* It's important to not use the `-ffast-math` or `-Ofast` flags when
* compiling code that needs to be debugged. Using `-fsignaling-nans`
* will also help, since GCC doesn't enable that by default.
*
* @param excepts may bitwise-or the following:
* - `FE_INVALID`
* - `FE_DIVBYZERO`
* - `FE_OVERFLOW`
* - `FE_UNDERFLOW`
* - `FE_INEXACT`
* - `FE_ALL_EXCEPT` (all of the above)
* @see fetestexcept() if you don't want to deal with signals
* @see feenableexcept() to turn it on in the first place
*/
int fedisableexcept(int excepts) {
// limit to what we know
excepts &= FE_ALL_EXCEPT;
#ifdef __x86_64__
#ifndef NOX87
// configure 8087 fpu control word
// setting the bits enables suppression
unsigned short x87cw;
asm("fstcw\t%0" : "=m"(x87cw));
x87cw |= excepts;
asm("fldcw\t%0" : /* no inputs */ : "m"(x87cw));
#endif
// configure modern sse control word
// setting the bits enables suppression
unsigned mxcsr;
asm("stmxcsr\t%0" : "=m"(mxcsr));
mxcsr |= excepts << 7;
asm("ldmxcsr\t%0" : /* no inputs */ : "m"(mxcsr));
return 0;
#elif defined(__aarch64__)
unsigned fpcr;
unsigned fpcr2;
fpcr = __builtin_aarch64_get_fpcr();
fpcr2 = fpcr & ~(excepts << 8);
if (fpcr != fpcr2)
__builtin_aarch64_set_fpcr(fpcr2);
return (fpcr >> 8) & FE_ALL_EXCEPT;
#else
return -1;
#endif
}

View file

@ -0,0 +1,98 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/runtime/fenv.h"
/**
* Enables floating point exception trapping, e.g.
*
* feenableexcept(FE_INVALID | FE_DIVBYZERO |
* FE_OVERFLOW | FE_UNDERFLOW);
*
* When trapping is enabled, something should handle SIGFPE. Calling
* ShowCrashReports() at startup will install a generic handler with
* backtraces and the symbol of the `si->si_code` which UNIX defines
*
* - `FPE_INTOVF`: integer overflow
* - `FPE_INTDIV`: integer divide by zero
* - `FPE_FLTDIV`: floating point divide by zero
* - `FPE_FLTOVF`: floating point overflow
* - `FPE_FLTUND`: floating point underflow
* - `FPE_FLTRES`: floating point inexact
* - `FPE_FLTINV`: invalid floating point operation
* - `FPE_FLTSUB`: subscript out of range
*
* It's important to not use the `-ffast-math` or `-Ofast` flags when
* compiling code that needs to be debugged. Using `-fsignaling-nans`
* will also help, since GCC doesn't enable that by default.
*
* @param excepts may bitwise-or the following:
* - `FE_INVALID`
* - `FE_DIVBYZERO`
* - `FE_OVERFLOW`
* - `FE_UNDERFLOW`
* - `FE_INEXACT`
* - `FE_ALL_EXCEPT` (all of the above)
* @see fetestexcept() if you don't want to deal with signals
* @see fedisableexcept() to turn it back off again
*/
int feenableexcept(int excepts) {
// limit to what we know
excepts &= FE_ALL_EXCEPT;
#ifdef __x86_64__
#ifndef NOX87
// configure 8087 fpu control word
// celaring the bits disables suppression
unsigned short x87cw;
asm("fstcw\t%0" : "=m"(x87cw));
x87cw &= ~excepts;
asm("fldcw\t%0" : /* no inputs */ : "m"(x87cw));
#endif
// configure modern sse control word
// clearing the bits disables suppression
unsigned mxcsr;
asm("stmxcsr\t%0" : "=m"(mxcsr));
mxcsr &= ~(excepts << 7);
asm("ldmxcsr\t%0" : /* no inputs */ : "m"(mxcsr));
return 0;
#elif defined(__aarch64__)
unsigned fpcr;
unsigned fpcr2;
unsigned updated_fpcr;
fpcr = __builtin_aarch64_get_fpcr();
fpcr2 = fpcr | (excepts << 8);
if (fpcr != fpcr2) {
__builtin_aarch64_set_fpcr(fpcr2);
// floating point exception trapping is optional in aarch64
updated_fpcr = __builtin_aarch64_get_fpsr();
if (fpcr2 & ~updated_fpcr)
return -1;
}
return (fpcr >> 8) & FE_ALL_EXCEPT;
#else
return -1;
#endif
}

32
libc/intrin/unleaf.c Normal file
View file

@ -0,0 +1,32 @@
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
vi: set et ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi
Copyright 2024 Justine Alexandra Roberts Tunney
Permission to use, copy, modify, and/or distribute this software for
any purpose with or without fee is hereby granted, provided that the
above copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL
DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR
PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
PERFORMANCE OF THIS SOFTWARE.
*/
#include "libc/cosmo.h"
/**
* Does nothing.
*
* Calling this function will force the compiler to generate a stack
* frame. This ensures backtraces will work better in a few critical
* routines.
*/
void unleaf(void) {
// TODO: We should make ShowCrashReports() so __math_invalidf()
// doesn't have to call this in order for the actual math
// function to show up in the backtrace.
}

View file

@ -75,6 +75,19 @@ cosmo: push %rbp
#ifdef __FAST_MATH__
push %rax
stmxcsr (%rsp)
//
// Enable hardware optimizations in violation of the IEEE standard.
//
// - 0x0040 enables "DAZ: Denormals Are Zeros" in MXCSR. This causes the
// processor to turn denormal inputs into zero, before computing them.
// See Intel Manual Vol. 1 §10.2.3.4
//
// - 0x8000 enables "FTZ: Flush To Zero" in MXCSR. This means a floating
// point operation that results in underflow will be set to zero, with
// the same sign, rather than producing a denormalized output. It will
// happen only if underflow trapping hasnt been enabled. See the Intel
// Manual Vol. 1 §10.2.3.3.
//
orl $0x8040,(%rsp)
ldmxcsr (%rsp)
pop %rax

View file

@ -81,6 +81,8 @@ int fesetenv(const fenv_t *);
int fesetexceptflag(const fexcept_t *, int);
int fesetround(int);
int fetestexcept(int);
int feenableexcept(int);
int fedisableexcept(int);
int feupdateenv(const fenv_t *);
int __flt_rounds(void);
int __fesetround(int);

View file

@ -2,6 +2,12 @@
#define COSMOPOLITAN_LIBC_THREAD_THREADS_H_
COSMOPOLITAN_C_START_
#if !defined(__cplusplus) && \
(!(defined(__GNUC__) && __GNUC__ >= 13) || \
!(defined(__STDC_VERSION__) && __STDC_VERSION__ > 201710L))
#define thread_local _Thread_local
#endif
#define TSS_DTOR_ITERATIONS 4
enum {

View file

@ -26,6 +26,7 @@
*/
#include "libc/errno.h"
#include "libc/cosmo.h"
#include "libc/tinymath/arm.internal.h"
#if WANT_ERRNO
@ -45,6 +46,7 @@ with_errnof (float y, int e)
dontinline static float
xflowf (uint32_t sign, float y)
{
unleaf();
y = eval_as_float (opt_barrier_float (sign ? -y : y) * y);
return with_errnof (y, ERANGE);
}
@ -74,6 +76,7 @@ __math_oflowf (uint32_t sign)
float
__math_divzerof (uint32_t sign)
{
unleaf();
float y = opt_barrier_float (sign ? -1.0f : 1.0f) / 0.0f;
return with_errnof (y, ERANGE);
}
@ -81,6 +84,7 @@ __math_divzerof (uint32_t sign)
dontinstrument float
__math_invalidf (float x)
{
unleaf();
float y = (x - x) / (x - x);
return isnan (x) ? y : with_errnof (y, EDOM);
}