| // 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) |
| */ |
| |
| /* NOTE: This file has been patched from the original DMD distribution to |
| * work with the GDC compiler. |
| */ |
| 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 (D_InlineAsm_X86) version = InlineAsm_X86_Any; |
| version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any; |
| |
| version (InlineAsm_X86_Any) version = InlineAsm_X87; |
| version (InlineAsm_X87) |
| { |
| static assert(real.mant_dig == 64); |
| version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC; |
| } |
| |
| 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 (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 (GNU) |
| { |
| // The compiler can unexpectedly rearrange floating point operations and |
| // access to the floating point status flags when optimizing. This means |
| // ieeeFlags tests cannot be reliably checked in optimized code. |
| // See https://github.com/ldc-developers/ldc/issues/888 |
| } |
| else |
| { |
| version = IeeeFlagsUnittest; |
| version = FloatingPointControlUnittest; |
| } |
| |
| 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. |
| 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 (GNU) |
| { |
| version (X86_Any) |
| { |
| ushort sw; |
| asm pure nothrow @nogc |
| { |
| "fstsw %0" : "=a" (sw); |
| } |
| // OR the result with the SSE2 status register (MXCSR). |
| if (haveSSE) |
| { |
| uint mxcsr; |
| asm pure nothrow @nogc |
| { |
| "stmxcsr %0" : "=m" (mxcsr); |
| } |
| return (sw | mxcsr) & EXCEPTIONS_MASK; |
| } |
| else |
| return sw & EXCEPTIONS_MASK; |
| } |
| else version (ARM) |
| { |
| version (ARM_SoftFloat) |
| return 0; |
| else |
| { |
| uint result = void; |
| asm pure nothrow @nogc |
| { |
| "vmrs %0, FPSCR; and %0, %0, #0x1F;" : "=r" (result); |
| } |
| return result; |
| } |
| } |
| else version (RISCV_Any) |
| { |
| version (D_SoftFloat) |
| return 0; |
| else |
| { |
| uint result = void; |
| asm pure nothrow @nogc |
| { |
| "frflags %0" : "=r" (result); |
| } |
| return result; |
| } |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| else |
| 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) |
| { |
| mixin(` |
| uint result = void; |
| asm pure nothrow @nogc |
| { |
| "frflags %0" : "=r" (result); |
| } |
| return result; |
| `); |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| |
| static void resetIeeeFlags() @trusted |
| { |
| version (GNU) |
| { |
| version (X86_Any) |
| { |
| asm nothrow @nogc |
| { |
| "fnclex"; |
| } |
| |
| // Also clear exception flags in MXCSR, SSE's control register. |
| if (haveSSE) |
| { |
| uint mxcsr; |
| asm nothrow @nogc |
| { |
| "stmxcsr %0" : "=m" (mxcsr); |
| } |
| mxcsr &= ~EXCEPTIONS_MASK; |
| asm nothrow @nogc |
| { |
| "ldmxcsr %0" : : "m" (mxcsr); |
| } |
| } |
| } |
| else version (ARM) |
| { |
| version (ARM_SoftFloat) |
| return; |
| else |
| { |
| uint old = FloatingPointControl.getControlState(); |
| old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html |
| asm nothrow @nogc |
| { |
| "vmsr FPSCR, %0" : : "r" (old); |
| } |
| } |
| } |
| else version (RISCV_Any) |
| { |
| version (D_SoftFloat) |
| return; |
| else |
| { |
| uint newValues = 0x0; |
| asm nothrow @nogc |
| { |
| "fsflags %0" : : "r" (newValues); |
| } |
| } |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| else |
| 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) |
| { |
| mixin(` |
| uint newValues = 0x0; |
| asm pure nothrow @nogc |
| { |
| "fsflags %0" : : "r" (newValues); |
| } |
| `); |
| } |
| 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 (IeeeFlagsUnittest) |
| @safe unittest |
| { |
| import std.math.traits : isNaN; |
| |
| static void func() { |
| int a = 10 * 10; |
| } |
| pragma(inline, false) static void blockopt(ref real x) {} |
| real a = 3.5; |
| // Set all the flags to zero |
| resetIeeeFlags(); |
| assert(!ieeeFlags.divByZero); |
| blockopt(a); // avoid constant propagation by the optimizer |
| // Perform a division by zero. |
| a /= 0.0L; |
| assert(a == real.infinity); |
| assert(ieeeFlags.divByZero); |
| blockopt(a); // avoid constant propagation by the optimizer |
| // 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); |
| } |
| |
| version (IeeeFlagsUnittest) |
| @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 trick -O. It would optimize away the |
| calculations if x were local to the function literals. */ |
| auto tests = [ |
| Test( |
| () { x = 1; x += 0.1L; }, |
| () => ieeeFlags.inexact |
| ), |
| Test( |
| () { x = T.min_normal; x /= T.max; }, |
| () => ieeeFlags.underflow |
| ), |
| Test( |
| () { x = T.max; x += T.max; }, |
| () => ieeeFlags.overflow |
| ), |
| Test( |
| () { x = 1; x /= 0; }, |
| () => ieeeFlags.divByZero |
| ), |
| Test( |
| () { x = 0; x /= 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(); |
| } |
| |
| /// |
| @safe unittest |
| { |
| pragma(inline, false) static void blockopt(ref real x) {} |
| resetIeeeFlags(); |
| real a = 3.5; |
| blockopt(a); // avoid constant propagation by the optimizer |
| a /= 0.0L; |
| blockopt(a); // avoid constant propagation by the optimizer |
| 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()); |
| } |
| |
| /// |
| @safe nothrow unittest |
| { |
| import std.math.traits : isNaN; |
| |
| pragma(inline, false) static void blockopt(ref real x) {} |
| resetIeeeFlags(); |
| real a = 3.5; |
| blockopt(a); // avoid constant propagation by the optimizer |
| |
| a /= 0.0L; |
| assert(a == real.infinity); |
| assert(ieeeFlags.divByZero); |
| blockopt(a); // avoid constant propagation by the optimizer |
| |
| 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 = 0x02, |
| underflowException = 0x04, |
| overflowException = 0x08, |
| 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 (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 (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 (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 (GNU) |
| { |
| version (X86_Any) |
| { |
| ControlState cont; |
| asm pure nothrow @nogc |
| { |
| "fstcw %0" : "=m" (cont); |
| } |
| return cont; |
| } |
| else version (AArch64) |
| { |
| asm pure nothrow @nogc |
| { |
| "mrs %0, FPCR;" : "=r" (cont); |
| } |
| return cont; |
| } |
| else version (ARM) |
| { |
| ControlState cont; |
| version (ARM_SoftFloat) |
| cont = 0; |
| else |
| { |
| asm pure nothrow @nogc |
| { |
| "vmrs %0, FPSCR" : "=r" (cont); |
| } |
| } |
| return cont; |
| } |
| else version (RISCV_Any) |
| { |
| version (D_SoftFloat) |
| return 0; |
| else |
| { |
| ControlState cont; |
| asm pure nothrow @nogc |
| { |
| "frcsr %0" : "=r" (cont); |
| } |
| return cont; |
| } |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| else |
| 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) |
| { |
| mixin(` |
| ControlState cont; |
| asm pure nothrow @nogc |
| { |
| "frcsr %0" : "=r" (cont); |
| } |
| return cont; |
| `); |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| |
| // Set the control register |
| package(std.math) static void setControlState(ControlState newState) @trusted |
| { |
| version (GNU) |
| { |
| version (X86_Any) |
| { |
| asm nothrow @nogc |
| { |
| "fclex; fldcw %0" : : "m" (newState); |
| } |
| |
| // Also update MXCSR, SSE's control register. |
| if (haveSSE) |
| { |
| uint mxcsr; |
| asm nothrow @nogc |
| { |
| "stmxcsr %0" : "=m" (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 %0" : : "m" (mxcsr); |
| } |
| } |
| } |
| else version (AArch64) |
| { |
| asm nothrow @nogc |
| { |
| "msr FPCR, %0;" : : "r" (newState); |
| } |
| } |
| else version (ARM) |
| { |
| version (ARM_SoftFloat) |
| return; |
| else |
| { |
| asm nothrow @nogc |
| { |
| "vmsr FPSCR, %0" : : "r" (newState); |
| } |
| } |
| } |
| else version (RISCV_Any) |
| { |
| version (D_SoftFloat) |
| return; |
| else |
| { |
| asm nothrow @nogc |
| { |
| "fscsr %0" : : "r" (newState); |
| } |
| } |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| else |
| 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) |
| { |
| mixin(` |
| asm pure nothrow @nogc |
| { |
| "fscsr %0" : : "r" (newState); |
| } |
| `); |
| } |
| else |
| assert(0, "Not yet supported"); |
| } |
| } |
| |
| /// |
| version (FloatingPointControlUnittest) |
| @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(); |
| } |
| |
| version (FloatingPointControlUnittest) |
| @safe unittest // rounding |
| { |
| import std.meta : AliasSeq; |
| |
| static T addRound(T)(uint rm) |
| { |
| pragma(inline, false) static void blockopt(ref T x) {} |
| pragma(inline, false); |
| FloatingPointControl fpctrl; |
| fpctrl.rounding = rm; |
| T x = 1; |
| blockopt(x); // avoid constant propagation by the optimizer |
| x += 0.1L; |
| return x; |
| } |
| |
| static T subRound(T)(uint rm) |
| { |
| pragma(inline, false) static void blockopt(ref T x) {} |
| pragma(inline, false); |
| FloatingPointControl fpctrl; |
| fpctrl.rounding = rm; |
| T x = -1; |
| blockopt(x); // avoid constant propagation by the optimizer |
| 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); |
| } |
| }} |
| } |
| |
| } |