mirror of
https://github.com/dlang/phobos.git
synced 2025-04-27 21:51:40 +03:00

``` std/math/hardware.d(187):1:19: error: invalid operand for instruction movfcsr2gr $a0, $r2 ```
1062 lines
30 KiB
D
1062 lines
30 KiB
D
// Written in the D programming language.
|
|
|
|
/**
|
|
This is a submodule of $(MREF std, math).
|
|
|
|
It contains hardware support for floating point numbers.
|
|
|
|
Copyright: Copyright The D Language Foundation 2000 - 2011.
|
|
License: $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
|
|
Authors: $(HTTP digitalmars.com, Walter Bright), Don Clugston,
|
|
Conversion of CEPHES math library to D by Iain Buclaw and David Nadlinger
|
|
Source: $(PHOBOSSRC std/math/hardware.d)
|
|
*/
|
|
|
|
module std.math.hardware;
|
|
|
|
static import core.stdc.fenv;
|
|
|
|
version (X86) version = X86_Any;
|
|
version (X86_64) version = X86_Any;
|
|
version (PPC) version = PPC_Any;
|
|
version (PPC64) version = PPC_Any;
|
|
version (MIPS32) version = MIPS_Any;
|
|
version (MIPS64) version = MIPS_Any;
|
|
version (AArch64) version = ARM_Any;
|
|
version (ARM) version = ARM_Any;
|
|
version (S390) version = IBMZ_Any;
|
|
version (SPARC) version = SPARC_Any;
|
|
version (SPARC64) version = SPARC_Any;
|
|
version (SystemZ) version = IBMZ_Any;
|
|
version (RISCV32) version = RISCV_Any;
|
|
version (RISCV64) version = RISCV_Any;
|
|
version (LoongArch64) version = LoongArch_Any;
|
|
|
|
version (D_InlineAsm_X86) version = InlineAsm_X86_Any;
|
|
version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any;
|
|
|
|
version (X86_64) version = StaticallyHaveSSE;
|
|
version (X86) version (OSX) version = StaticallyHaveSSE;
|
|
|
|
version (StaticallyHaveSSE)
|
|
{
|
|
private enum bool haveSSE = true;
|
|
}
|
|
else version (X86)
|
|
{
|
|
static import core.cpuid;
|
|
private alias haveSSE = core.cpuid.sse;
|
|
}
|
|
|
|
version (D_SoftFloat)
|
|
{
|
|
// Some soft float implementations may support IEEE floating flags.
|
|
// The implementation here supports hardware flags only and is so currently
|
|
// only available for supported targets.
|
|
}
|
|
else version (X86_Any) version = IeeeFlagsSupport;
|
|
else version (PPC_Any) version = IeeeFlagsSupport;
|
|
else version (RISCV_Any) version = IeeeFlagsSupport;
|
|
else version (MIPS_Any) version = IeeeFlagsSupport;
|
|
else version (LoongArch_Any) version = IeeeFlagsSupport;
|
|
else version (ARM_Any) version = IeeeFlagsSupport;
|
|
|
|
// Struct FloatingPointControl is only available if hardware FP units are available.
|
|
version (D_HardFloat)
|
|
{
|
|
// FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport
|
|
version (IeeeFlagsSupport) version = FloatingPointControlSupport;
|
|
}
|
|
|
|
version (IeeeFlagsSupport)
|
|
{
|
|
|
|
/** IEEE exception status flags ('sticky bits')
|
|
|
|
These flags indicate that an exceptional floating-point condition has occurred.
|
|
They indicate that a NaN or an infinity has been generated, that a result
|
|
is inexact, or that a signalling NaN has been encountered. If floating-point
|
|
exceptions are enabled (unmasked), a hardware exception will be generated
|
|
instead of setting these flags.
|
|
*/
|
|
struct IeeeFlags
|
|
{
|
|
nothrow @nogc:
|
|
|
|
private:
|
|
// The x87 FPU status register is 16 bits.
|
|
// The Pentium SSE2 status register is 32 bits.
|
|
// The ARM and PowerPC FPSCR is a 32-bit register.
|
|
// The SPARC FSR is a 32bit register (64 bits for SPARC 7 & 8, but high bits are uninteresting).
|
|
// The RISC-V (32 & 64 bit) fcsr is 32-bit register.
|
|
// THe LoongArch fcsr (fcsr0) is a 32-bit register.
|
|
uint flags;
|
|
|
|
version (CRuntime_Microsoft)
|
|
{
|
|
// Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv).
|
|
// Applies to both x87 status word (16 bits) and SSE2 status word(32 bits).
|
|
enum : int
|
|
{
|
|
INEXACT_MASK = 0x20,
|
|
UNDERFLOW_MASK = 0x10,
|
|
OVERFLOW_MASK = 0x08,
|
|
DIVBYZERO_MASK = 0x04,
|
|
INVALID_MASK = 0x01,
|
|
|
|
EXCEPTIONS_MASK = 0b11_1111
|
|
}
|
|
// Don't bother about subnormals, they are not supported on most CPUs.
|
|
// SUBNORMAL_MASK = 0x02;
|
|
}
|
|
else
|
|
{
|
|
enum : int
|
|
{
|
|
INEXACT_MASK = core.stdc.fenv.FE_INEXACT,
|
|
UNDERFLOW_MASK = core.stdc.fenv.FE_UNDERFLOW,
|
|
OVERFLOW_MASK = core.stdc.fenv.FE_OVERFLOW,
|
|
DIVBYZERO_MASK = core.stdc.fenv.FE_DIVBYZERO,
|
|
INVALID_MASK = core.stdc.fenv.FE_INVALID,
|
|
EXCEPTIONS_MASK = core.stdc.fenv.FE_ALL_EXCEPT,
|
|
}
|
|
}
|
|
|
|
static uint getIeeeFlags() @trusted pure
|
|
{
|
|
version (InlineAsm_X86_Any)
|
|
{
|
|
ushort sw;
|
|
asm pure nothrow @nogc { fstsw sw; }
|
|
|
|
// OR the result with the SSE2 status register (MXCSR).
|
|
if (haveSSE)
|
|
{
|
|
uint mxcsr;
|
|
asm pure nothrow @nogc { stmxcsr mxcsr; }
|
|
return (sw | mxcsr) & EXCEPTIONS_MASK;
|
|
}
|
|
else return sw & EXCEPTIONS_MASK;
|
|
}
|
|
else version (SPARC)
|
|
{
|
|
/*
|
|
int retval;
|
|
asm pure nothrow @nogc { st %fsr, retval; }
|
|
return retval;
|
|
*/
|
|
assert(0, "Not yet supported");
|
|
}
|
|
else version (ARM)
|
|
{
|
|
assert(false, "Not yet supported.");
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
uint result = void;
|
|
asm pure nothrow @nogc
|
|
{
|
|
"frflags %0" : "=r" (result);
|
|
}
|
|
return result;
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
uint result = void;
|
|
asm pure nothrow @nogc
|
|
{
|
|
"movfcsr2gr %0, $fcsr2" : "=r" (result);
|
|
}
|
|
return result & EXCEPTIONS_MASK;
|
|
}
|
|
else
|
|
assert(0, "Not yet supported");
|
|
}
|
|
|
|
static void resetIeeeFlags() @trusted
|
|
{
|
|
version (InlineAsm_X86_Any)
|
|
{
|
|
asm nothrow @nogc
|
|
{
|
|
fnclex;
|
|
}
|
|
|
|
// Also clear exception flags in MXCSR, SSE's control register.
|
|
if (haveSSE)
|
|
{
|
|
uint mxcsr;
|
|
asm nothrow @nogc { stmxcsr mxcsr; }
|
|
mxcsr &= ~EXCEPTIONS_MASK;
|
|
asm nothrow @nogc { ldmxcsr mxcsr; }
|
|
}
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
uint newValues = 0x0;
|
|
asm pure nothrow @nogc
|
|
{
|
|
"fsflags %0" : : "r" (newValues);
|
|
}
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
asm nothrow @nogc
|
|
{
|
|
"movgr2fcsr $fcsr2,$r0";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* SPARC:
|
|
int tmpval;
|
|
asm pure nothrow @nogc { st %fsr, tmpval; }
|
|
tmpval &=0xFFFF_FC00;
|
|
asm pure nothrow @nogc { ld tmpval, %fsr; }
|
|
*/
|
|
assert(0, "Not yet supported");
|
|
}
|
|
}
|
|
|
|
public:
|
|
/**
|
|
* The result cannot be represented exactly, so rounding occurred.
|
|
* Example: `x = sin(0.1);`
|
|
*/
|
|
@property bool inexact() @safe const { return (flags & INEXACT_MASK) != 0; }
|
|
|
|
/**
|
|
* A zero was generated by underflow
|
|
* Example: `x = real.min*real.epsilon/2;`
|
|
*/
|
|
@property bool underflow() @safe const { return (flags & UNDERFLOW_MASK) != 0; }
|
|
|
|
/**
|
|
* An infinity was generated by overflow
|
|
* Example: `x = real.max*2;`
|
|
*/
|
|
@property bool overflow() @safe const { return (flags & OVERFLOW_MASK) != 0; }
|
|
|
|
/**
|
|
* An infinity was generated by division by zero
|
|
* Example: `x = 3/0.0;`
|
|
*/
|
|
@property bool divByZero() @safe const { return (flags & DIVBYZERO_MASK) != 0; }
|
|
|
|
/**
|
|
* A machine NaN was generated.
|
|
* Example: `x = real.infinity * 0.0;`
|
|
*/
|
|
@property bool invalid() @safe const { return (flags & INVALID_MASK) != 0; }
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@safe unittest
|
|
{
|
|
import std.math.traits : isNaN;
|
|
|
|
static void func() {
|
|
int a = 10 * 10;
|
|
}
|
|
real a = 3.5;
|
|
// Set all the flags to zero
|
|
resetIeeeFlags();
|
|
assert(!ieeeFlags.divByZero);
|
|
// Perform a division by zero.
|
|
a /= 0.0L;
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
// Create a NaN
|
|
a *= 0.0L;
|
|
assert(ieeeFlags.invalid);
|
|
assert(isNaN(a));
|
|
|
|
// Check that calling func() has no effect on the
|
|
// status flags.
|
|
IeeeFlags f = ieeeFlags;
|
|
func();
|
|
assert(ieeeFlags == f);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.math.traits : isNaN;
|
|
|
|
static void func() {
|
|
int a = 10 * 10;
|
|
}
|
|
real a = 3.5;
|
|
// Set all the flags to zero
|
|
resetIeeeFlags();
|
|
assert(!ieeeFlags.divByZero);
|
|
// Perform a division by zero.
|
|
a = forceDivOp(a, 0.0L);
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
// Create a NaN
|
|
a = forceMulOp(a, 0.0L);
|
|
assert(ieeeFlags.invalid);
|
|
assert(isNaN(a));
|
|
|
|
// Check that calling func() has no effect on the
|
|
// status flags.
|
|
IeeeFlags f = ieeeFlags;
|
|
func();
|
|
assert(ieeeFlags == f);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
static struct Test
|
|
{
|
|
void delegate() @trusted action;
|
|
bool function() @trusted ieeeCheck;
|
|
}
|
|
|
|
static foreach (T; AliasSeq!(float, double, real))
|
|
{{
|
|
T x; // Needs to be here to avoid `call without side effects` warning.
|
|
auto tests = [
|
|
Test(
|
|
() { x = forceAddOp!T(1, 0.1L); },
|
|
() => ieeeFlags.inexact
|
|
),
|
|
Test(
|
|
() { x = forceDivOp!T(T.min_normal, T.max); },
|
|
() => ieeeFlags.underflow
|
|
),
|
|
Test(
|
|
() { x = forceAddOp!T(T.max, T.max); },
|
|
() => ieeeFlags.overflow
|
|
),
|
|
Test(
|
|
() { x = forceDivOp!T(1, 0); },
|
|
() => ieeeFlags.divByZero
|
|
),
|
|
Test(
|
|
() { x = forceDivOp!T(0, 0); },
|
|
() => ieeeFlags.invalid
|
|
)
|
|
];
|
|
foreach (test; tests)
|
|
{
|
|
resetIeeeFlags();
|
|
assert(!test.ieeeCheck());
|
|
test.action();
|
|
assert(test.ieeeCheck());
|
|
}
|
|
}}
|
|
}
|
|
|
|
/// Set all of the floating-point status flags to false.
|
|
void resetIeeeFlags() @trusted nothrow @nogc
|
|
{
|
|
IeeeFlags.resetIeeeFlags();
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@safe unittest
|
|
{
|
|
resetIeeeFlags();
|
|
real a = 3.5;
|
|
a /= 0.0L;
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
|
|
resetIeeeFlags();
|
|
assert(!ieeeFlags.divByZero);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
resetIeeeFlags();
|
|
real a = 3.5;
|
|
a = forceDivOp(a, 0.0L);
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
|
|
resetIeeeFlags();
|
|
assert(!ieeeFlags.divByZero);
|
|
}
|
|
|
|
/// Returns: snapshot of the current state of the floating-point status flags
|
|
@property IeeeFlags ieeeFlags() @trusted pure nothrow @nogc
|
|
{
|
|
return IeeeFlags(IeeeFlags.getIeeeFlags());
|
|
}
|
|
|
|
///
|
|
version (StdDdoc)
|
|
@safe nothrow unittest
|
|
{
|
|
import std.math.traits : isNaN;
|
|
|
|
resetIeeeFlags();
|
|
real a = 3.5;
|
|
|
|
a /= 0.0L;
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
|
|
a *= 0.0L;
|
|
assert(isNaN(a));
|
|
assert(ieeeFlags.invalid);
|
|
}
|
|
|
|
@safe nothrow unittest
|
|
{
|
|
import std.math.traits : isNaN;
|
|
|
|
resetIeeeFlags();
|
|
real a = 3.5;
|
|
|
|
a = forceDivOp(a, 0.0L);
|
|
assert(a == real.infinity);
|
|
assert(ieeeFlags.divByZero);
|
|
|
|
a = forceMulOp(a, 0.0L);
|
|
assert(isNaN(a));
|
|
assert(ieeeFlags.invalid);
|
|
}
|
|
|
|
} // IeeeFlagsSupport
|
|
|
|
|
|
version (FloatingPointControlSupport)
|
|
{
|
|
|
|
/** Control the Floating point hardware
|
|
|
|
Change the IEEE754 floating-point rounding mode and the floating-point
|
|
hardware exceptions.
|
|
|
|
By default, the rounding mode is roundToNearest and all hardware exceptions
|
|
are disabled. For most applications, debugging is easier if the $(I division
|
|
by zero), $(I overflow), and $(I invalid operation) exceptions are enabled.
|
|
These three are combined into a $(I severeExceptions) value for convenience.
|
|
Note in particular that if $(I invalidException) is enabled, a hardware trap
|
|
will be generated whenever an uninitialized floating-point variable is used.
|
|
|
|
All changes are temporary. The previous state is restored at the
|
|
end of the scope.
|
|
|
|
|
|
Example:
|
|
----
|
|
{
|
|
FloatingPointControl fpctrl;
|
|
|
|
// Enable hardware exceptions for division by zero, overflow to infinity,
|
|
// invalid operations, and uninitialized floating-point variables.
|
|
fpctrl.enableExceptions(FloatingPointControl.severeExceptions);
|
|
|
|
// This will generate a hardware exception, if x is a
|
|
// default-initialized floating point variable:
|
|
real x; // Add `= 0` or even `= real.nan` to not throw the exception.
|
|
real y = x * 3.0;
|
|
|
|
// The exception is only thrown for default-uninitialized NaN-s.
|
|
// NaN-s with other payload are valid:
|
|
real z = y * real.nan; // ok
|
|
|
|
// The set hardware exceptions and rounding modes will be disabled when
|
|
// leaving this scope.
|
|
}
|
|
----
|
|
|
|
*/
|
|
struct FloatingPointControl
|
|
{
|
|
nothrow @nogc:
|
|
|
|
alias RoundingMode = uint; ///
|
|
|
|
version (StdDdoc)
|
|
{
|
|
enum : RoundingMode
|
|
{
|
|
/** IEEE rounding modes.
|
|
* The default mode is roundToNearest.
|
|
*
|
|
* roundingMask = A mask of all rounding modes.
|
|
*/
|
|
roundToNearest,
|
|
roundDown, /// ditto
|
|
roundUp, /// ditto
|
|
roundToZero, /// ditto
|
|
roundingMask, /// ditto
|
|
}
|
|
}
|
|
else version (CRuntime_Microsoft)
|
|
{
|
|
// Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv).
|
|
enum : RoundingMode
|
|
{
|
|
roundToNearest = 0x0000,
|
|
roundDown = 0x0400,
|
|
roundUp = 0x0800,
|
|
roundToZero = 0x0C00,
|
|
roundingMask = roundToNearest | roundDown
|
|
| roundUp | roundToZero,
|
|
}
|
|
}
|
|
else
|
|
{
|
|
enum : RoundingMode
|
|
{
|
|
roundToNearest = core.stdc.fenv.FE_TONEAREST,
|
|
roundDown = core.stdc.fenv.FE_DOWNWARD,
|
|
roundUp = core.stdc.fenv.FE_UPWARD,
|
|
roundToZero = core.stdc.fenv.FE_TOWARDZERO,
|
|
roundingMask = roundToNearest | roundDown
|
|
| roundUp | roundToZero,
|
|
}
|
|
}
|
|
|
|
/***
|
|
* Change the floating-point hardware rounding mode
|
|
*
|
|
* Changing the rounding mode in the middle of a function can interfere
|
|
* with optimizations of floating point expressions, as the optimizer assumes
|
|
* that the rounding mode does not change.
|
|
* It is best to change the rounding mode only at the
|
|
* beginning of the function, and keep it until the function returns.
|
|
* It is also best to add the line:
|
|
* ---
|
|
* pragma(inline, false);
|
|
* ---
|
|
* as the first line of the function so it will not get inlined.
|
|
* Params:
|
|
* newMode = the new rounding mode
|
|
*/
|
|
@property void rounding(RoundingMode newMode) @trusted
|
|
{
|
|
initialize();
|
|
setControlState((getControlState() & (-1 - roundingMask)) | (newMode & roundingMask));
|
|
}
|
|
|
|
/// Returns: the currently active rounding mode
|
|
@property static RoundingMode rounding() @trusted pure
|
|
{
|
|
return cast(RoundingMode)(getControlState() & roundingMask);
|
|
}
|
|
|
|
alias ExceptionMask = uint; ///
|
|
|
|
version (StdDdoc)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
/** IEEE hardware exceptions.
|
|
* By default, all exceptions are masked (disabled).
|
|
*
|
|
* severeExceptions = The overflow, division by zero, and invalid
|
|
* exceptions.
|
|
*/
|
|
subnormalException,
|
|
inexactException, /// ditto
|
|
underflowException, /// ditto
|
|
overflowException, /// ditto
|
|
divByZeroException, /// ditto
|
|
invalidException, /// ditto
|
|
severeExceptions, /// ditto
|
|
allExceptions, /// ditto
|
|
}
|
|
}
|
|
else version (ARM_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
subnormalException = 0x8000,
|
|
inexactException = 0x1000,
|
|
underflowException = 0x0800,
|
|
overflowException = 0x0400,
|
|
divByZeroException = 0x0200,
|
|
invalidException = 0x0100,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException | subnormalException,
|
|
}
|
|
}
|
|
else version (PPC_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x0008,
|
|
divByZeroException = 0x0010,
|
|
underflowException = 0x0020,
|
|
overflowException = 0x0040,
|
|
invalidException = 0x0080,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x01,
|
|
divByZeroException = 0x08,
|
|
underflowException = 0x02,
|
|
overflowException = 0x04,
|
|
invalidException = 0x10,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (HPPA)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x01,
|
|
underflowException = 0x02,
|
|
overflowException = 0x04,
|
|
divByZeroException = 0x08,
|
|
invalidException = 0x10,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x00,
|
|
divByZeroException = 0x01,
|
|
overflowException = 0x02,
|
|
underflowException = 0x04,
|
|
invalidException = 0x08,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (MIPS_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x0080,
|
|
divByZeroException = 0x0400,
|
|
overflowException = 0x0200,
|
|
underflowException = 0x0100,
|
|
invalidException = 0x0800,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (SPARC_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x0800000,
|
|
divByZeroException = 0x1000000,
|
|
overflowException = 0x4000000,
|
|
underflowException = 0x2000000,
|
|
invalidException = 0x8000000,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (IBMZ_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x08000000,
|
|
divByZeroException = 0x40000000,
|
|
overflowException = 0x20000000,
|
|
underflowException = 0x10000000,
|
|
invalidException = 0x80000000,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException,
|
|
}
|
|
}
|
|
else version (X86_Any)
|
|
{
|
|
enum : ExceptionMask
|
|
{
|
|
inexactException = 0x20,
|
|
underflowException = 0x10,
|
|
overflowException = 0x08,
|
|
divByZeroException = 0x04,
|
|
subnormalException = 0x02,
|
|
invalidException = 0x01,
|
|
severeExceptions = overflowException | divByZeroException
|
|
| invalidException,
|
|
allExceptions = severeExceptions | underflowException
|
|
| inexactException | subnormalException,
|
|
}
|
|
}
|
|
else
|
|
static assert(false, "Not implemented for this architecture");
|
|
|
|
version (ARM_Any)
|
|
{
|
|
static bool hasExceptionTraps_impl() @safe
|
|
{
|
|
auto oldState = getControlState();
|
|
// If exceptions are not supported, we set the bit but read it back as zero
|
|
// https://sourceware.org/ml/libc-ports/2012-06/msg00091.html
|
|
setControlState(oldState | divByZeroException);
|
|
immutable result = (getControlState() & allExceptions) != 0;
|
|
setControlState(oldState);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
/// Returns: true if the current FPU supports exception trapping
|
|
@property static bool hasExceptionTraps() @safe pure
|
|
{
|
|
version (X86_Any)
|
|
return true;
|
|
else version (PPC_Any)
|
|
return true;
|
|
else version (MIPS_Any)
|
|
return true;
|
|
else version (LoongArch_Any)
|
|
return true;
|
|
else version (ARM_Any)
|
|
{
|
|
// The hasExceptionTraps_impl function is basically pure,
|
|
// as it restores all global state
|
|
auto fptr = ( () @trusted => cast(bool function() @safe
|
|
pure nothrow @nogc)&hasExceptionTraps_impl)();
|
|
return fptr();
|
|
}
|
|
else
|
|
assert(0, "Not yet supported");
|
|
}
|
|
|
|
/// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together.
|
|
void enableExceptions(ExceptionMask exceptions) @trusted
|
|
{
|
|
assert(hasExceptionTraps);
|
|
initialize();
|
|
version (X86_Any)
|
|
setControlState(getControlState() & ~(exceptions & allExceptions));
|
|
else
|
|
setControlState(getControlState() | (exceptions & allExceptions));
|
|
}
|
|
|
|
/// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together.
|
|
void disableExceptions(ExceptionMask exceptions) @trusted
|
|
{
|
|
assert(hasExceptionTraps);
|
|
initialize();
|
|
version (X86_Any)
|
|
setControlState(getControlState() | (exceptions & allExceptions));
|
|
else
|
|
setControlState(getControlState() & ~(exceptions & allExceptions));
|
|
}
|
|
|
|
/// Returns: the exceptions which are currently enabled (unmasked)
|
|
@property static ExceptionMask enabledExceptions() @trusted pure
|
|
{
|
|
assert(hasExceptionTraps);
|
|
version (X86_Any)
|
|
return (getControlState() & allExceptions) ^ allExceptions;
|
|
else
|
|
return (getControlState() & allExceptions);
|
|
}
|
|
|
|
/// Clear all pending exceptions, then restore the original exception state and rounding mode.
|
|
~this() @trusted
|
|
{
|
|
clearExceptions();
|
|
if (initialized)
|
|
setControlState(savedState);
|
|
}
|
|
|
|
private:
|
|
ControlState savedState;
|
|
|
|
bool initialized = false;
|
|
|
|
version (ARM_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (HPPA)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (PPC_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (MIPS_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (SPARC_Any)
|
|
{
|
|
alias ControlState = ulong;
|
|
}
|
|
else version (IBMZ_Any)
|
|
{
|
|
alias ControlState = uint;
|
|
}
|
|
else version (X86_Any)
|
|
{
|
|
alias ControlState = ushort;
|
|
}
|
|
else
|
|
static assert(false, "Not implemented for this architecture");
|
|
|
|
void initialize() @safe
|
|
{
|
|
// BUG: This works around the absence of this() constructors.
|
|
if (initialized) return;
|
|
clearExceptions();
|
|
savedState = getControlState();
|
|
initialized = true;
|
|
}
|
|
|
|
// Clear all pending exceptions
|
|
static void clearExceptions() @safe
|
|
{
|
|
version (IeeeFlagsSupport)
|
|
resetIeeeFlags();
|
|
else
|
|
static assert(false, "Not implemented for this architecture");
|
|
}
|
|
|
|
// Read from the control register
|
|
package(std.math) static ControlState getControlState() @trusted pure
|
|
{
|
|
version (D_InlineAsm_X86)
|
|
{
|
|
short cont;
|
|
asm pure nothrow @nogc
|
|
{
|
|
xor EAX, EAX;
|
|
fstcw cont;
|
|
}
|
|
return cont;
|
|
}
|
|
else version (D_InlineAsm_X86_64)
|
|
{
|
|
short cont;
|
|
asm pure nothrow @nogc
|
|
{
|
|
xor RAX, RAX;
|
|
fstcw cont;
|
|
}
|
|
return cont;
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
ControlState cont;
|
|
asm pure nothrow @nogc
|
|
{
|
|
"frcsr %0" : "=r" (cont);
|
|
}
|
|
return cont;
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
ControlState cont;
|
|
asm pure nothrow @nogc
|
|
{
|
|
"movfcsr2gr %0, $fcsr0" : "=r" (cont);
|
|
}
|
|
cont &= (roundingMask | allExceptions);
|
|
return cont;
|
|
}
|
|
else
|
|
assert(0, "Not yet supported");
|
|
}
|
|
|
|
// Set the control register
|
|
package(std.math) static void setControlState(ControlState newState) @trusted
|
|
{
|
|
version (InlineAsm_X86_Any)
|
|
{
|
|
asm nothrow @nogc
|
|
{
|
|
fclex;
|
|
fldcw newState;
|
|
}
|
|
|
|
// Also update MXCSR, SSE's control register.
|
|
if (haveSSE)
|
|
{
|
|
uint mxcsr;
|
|
asm nothrow @nogc { stmxcsr mxcsr; }
|
|
|
|
/* In the FPU control register, rounding mode is in bits 10 and
|
|
11. In MXCSR it's in bits 13 and 14. */
|
|
mxcsr &= ~(roundingMask << 3); // delete old rounding mode
|
|
mxcsr |= (newState & roundingMask) << 3; // write new rounding mode
|
|
|
|
/* In the FPU control register, masks are bits 0 through 5.
|
|
In MXCSR they're 7 through 12. */
|
|
mxcsr &= ~(allExceptions << 7); // delete old masks
|
|
mxcsr |= (newState & allExceptions) << 7; // write new exception masks
|
|
|
|
asm nothrow @nogc { ldmxcsr mxcsr; }
|
|
}
|
|
}
|
|
else version (RISCV_Any)
|
|
{
|
|
asm pure nothrow @nogc
|
|
{
|
|
"fscsr %0" : : "r" (newState);
|
|
}
|
|
}
|
|
else version (LoongArch_Any)
|
|
{
|
|
asm nothrow @nogc
|
|
{
|
|
"movgr2fcsr $fcsr0,%0" :
|
|
: "r" (newState & (roundingMask | allExceptions));
|
|
}
|
|
}
|
|
else
|
|
assert(0, "Not yet supported");
|
|
}
|
|
}
|
|
|
|
///
|
|
@safe unittest
|
|
{
|
|
import std.math.rounding : lrint;
|
|
|
|
FloatingPointControl fpctrl;
|
|
|
|
fpctrl.rounding = FloatingPointControl.roundDown;
|
|
assert(lrint(1.5) == 1.0);
|
|
|
|
fpctrl.rounding = FloatingPointControl.roundUp;
|
|
assert(lrint(1.4) == 2.0);
|
|
|
|
fpctrl.rounding = FloatingPointControl.roundToNearest;
|
|
assert(lrint(1.5) == 2.0);
|
|
}
|
|
|
|
@safe unittest
|
|
{
|
|
void ensureDefaults()
|
|
{
|
|
assert(FloatingPointControl.rounding
|
|
== FloatingPointControl.roundToNearest);
|
|
if (FloatingPointControl.hasExceptionTraps)
|
|
assert(FloatingPointControl.enabledExceptions == 0);
|
|
}
|
|
|
|
{
|
|
FloatingPointControl ctrl;
|
|
}
|
|
ensureDefaults();
|
|
|
|
{
|
|
FloatingPointControl ctrl;
|
|
ctrl.rounding = FloatingPointControl.roundDown;
|
|
assert(FloatingPointControl.rounding == FloatingPointControl.roundDown);
|
|
}
|
|
ensureDefaults();
|
|
|
|
if (FloatingPointControl.hasExceptionTraps)
|
|
{
|
|
FloatingPointControl ctrl;
|
|
ctrl.enableExceptions(FloatingPointControl.divByZeroException
|
|
| FloatingPointControl.overflowException);
|
|
assert(ctrl.enabledExceptions ==
|
|
(FloatingPointControl.divByZeroException
|
|
| FloatingPointControl.overflowException));
|
|
|
|
ctrl.rounding = FloatingPointControl.roundUp;
|
|
assert(FloatingPointControl.rounding == FloatingPointControl.roundUp);
|
|
}
|
|
ensureDefaults();
|
|
}
|
|
|
|
@safe unittest // rounding
|
|
{
|
|
import std.meta : AliasSeq;
|
|
|
|
static T addRound(T)(uint rm)
|
|
{
|
|
pragma(inline, false);
|
|
FloatingPointControl fpctrl;
|
|
fpctrl.rounding = rm;
|
|
T x = 1;
|
|
x = forceAddOp(x, 0.1L);
|
|
return x;
|
|
}
|
|
|
|
static T subRound(T)(uint rm)
|
|
{
|
|
pragma(inline, false);
|
|
FloatingPointControl fpctrl;
|
|
fpctrl.rounding = rm;
|
|
T x = -1;
|
|
x = forceSubOp(x, 0.1L);
|
|
return x;
|
|
}
|
|
|
|
static foreach (T; AliasSeq!(float, double, real))
|
|
{{
|
|
/* Be careful with changing the rounding mode, it interferes
|
|
* with common subexpressions. Changing rounding modes should
|
|
* be done with separate functions that are not inlined.
|
|
*/
|
|
|
|
{
|
|
T u = addRound!(T)(FloatingPointControl.roundUp);
|
|
T d = addRound!(T)(FloatingPointControl.roundDown);
|
|
T z = addRound!(T)(FloatingPointControl.roundToZero);
|
|
|
|
assert(u > d);
|
|
assert(z == d);
|
|
}
|
|
|
|
{
|
|
T u = subRound!(T)(FloatingPointControl.roundUp);
|
|
T d = subRound!(T)(FloatingPointControl.roundDown);
|
|
T z = subRound!(T)(FloatingPointControl.roundToZero);
|
|
|
|
assert(u > d);
|
|
assert(z == u);
|
|
}
|
|
}}
|
|
}
|
|
|
|
} // FloatingPointControlSupport
|
|
|
|
version (StdUnittest)
|
|
{
|
|
// These helpers are intended to avoid constant propagation by the optimizer.
|
|
pragma(inline, false) private @safe
|
|
{
|
|
T forceAddOp(T)(T x, T y) { return x + y; }
|
|
T forceSubOp(T)(T x, T y) { return x - y; }
|
|
T forceMulOp(T)(T x, T y) { return x * y; }
|
|
T forceDivOp(T)(T x, T y) { return x / y; }
|
|
}
|
|
}
|