1// Written in the D programming language.
2
3/**
4This is a submodule of $(MREF std, math).
5
6It contains hardware support for floating point numbers.
7
8Copyright: Copyright The D Language Foundation 2000 - 2011.
9License:   $(HTTP www.boost.org/LICENSE_1_0.txt, Boost License 1.0).
10Authors:   $(HTTP digitalmars.com, Walter Bright), Don Clugston,
11           Conversion of CEPHES math library to D by Iain Buclaw and David Nadlinger
12Source: $(PHOBOSSRC std/math/hardware.d)
13 */
14
15/* NOTE: This file has been patched from the original DMD distribution to
16 * work with the GDC compiler.
17 */
18module std.math.hardware;
19
20static import core.stdc.fenv;
21
22version (X86)       version = X86_Any;
23version (X86_64)    version = X86_Any;
24version (PPC)       version = PPC_Any;
25version (PPC64)     version = PPC_Any;
26version (MIPS32)    version = MIPS_Any;
27version (MIPS64)    version = MIPS_Any;
28version (AArch64)   version = ARM_Any;
29version (ARM)       version = ARM_Any;
30version (S390)      version = IBMZ_Any;
31version (SPARC)     version = SPARC_Any;
32version (SPARC64)   version = SPARC_Any;
33version (SystemZ)   version = IBMZ_Any;
34version (RISCV32)   version = RISCV_Any;
35version (RISCV64)   version = RISCV_Any;
36
37version (D_InlineAsm_X86)    version = InlineAsm_X86_Any;
38version (D_InlineAsm_X86_64) version = InlineAsm_X86_Any;
39
40version (InlineAsm_X86_Any) version = InlineAsm_X87;
41version (InlineAsm_X87)
42{
43    static assert(real.mant_dig == 64);
44    version (CRuntime_Microsoft) version = InlineAsm_X87_MSVC;
45}
46
47version (X86_64) version = StaticallyHaveSSE;
48version (X86) version (OSX) version = StaticallyHaveSSE;
49
50version (StaticallyHaveSSE)
51{
52    private enum bool haveSSE = true;
53}
54else version (X86)
55{
56    static import core.cpuid;
57    private alias haveSSE = core.cpuid.sse;
58}
59
60version (D_SoftFloat)
61{
62    // Some soft float implementations may support IEEE floating flags.
63    // The implementation here supports hardware flags only and is so currently
64    // only available for supported targets.
65}
66else version (X86_Any)   version = IeeeFlagsSupport;
67else version (PPC_Any)   version = IeeeFlagsSupport;
68else version (RISCV_Any) version = IeeeFlagsSupport;
69else version (MIPS_Any)  version = IeeeFlagsSupport;
70else version (ARM_Any)   version = IeeeFlagsSupport;
71
72// Struct FloatingPointControl is only available if hardware FP units are available.
73version (D_HardFloat)
74{
75    // FloatingPointControl.clearExceptions() depends on version IeeeFlagsSupport
76    version (IeeeFlagsSupport) version = FloatingPointControlSupport;
77}
78
79version (GNU)
80{
81    // The compiler can unexpectedly rearrange floating point operations and
82    // access to the floating point status flags when optimizing. This means
83    // ieeeFlags tests cannot be reliably checked in optimized code.
84    // See https://github.com/ldc-developers/ldc/issues/888
85}
86else
87{
88    version = IeeeFlagsUnittest;
89    version = FloatingPointControlUnittest;
90}
91
92version (IeeeFlagsSupport)
93{
94
95/** IEEE exception status flags ('sticky bits')
96
97 These flags indicate that an exceptional floating-point condition has occurred.
98 They indicate that a NaN or an infinity has been generated, that a result
99 is inexact, or that a signalling NaN has been encountered. If floating-point
100 exceptions are enabled (unmasked), a hardware exception will be generated
101 instead of setting these flags.
102 */
103struct IeeeFlags
104{
105nothrow @nogc:
106
107private:
108    // The x87 FPU status register is 16 bits.
109    // The Pentium SSE2 status register is 32 bits.
110    // The ARM and PowerPC FPSCR is a 32-bit register.
111    // The SPARC FSR is a 32bit register (64 bits for SPARC 7 & 8, but high bits are uninteresting).
112    // The RISC-V (32 & 64 bit) fcsr is 32-bit register.
113    uint flags;
114
115    version (CRuntime_Microsoft)
116    {
117        // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv).
118        // Applies to both x87 status word (16 bits) and SSE2 status word(32 bits).
119        enum : int
120        {
121            INEXACT_MASK   = 0x20,
122            UNDERFLOW_MASK = 0x10,
123            OVERFLOW_MASK  = 0x08,
124            DIVBYZERO_MASK = 0x04,
125            INVALID_MASK   = 0x01,
126
127            EXCEPTIONS_MASK = 0b11_1111
128        }
129        // Don't bother about subnormals, they are not supported on most CPUs.
130        //  SUBNORMAL_MASK = 0x02;
131    }
132    else
133    {
134        enum : int
135        {
136            INEXACT_MASK    = core.stdc.fenv.FE_INEXACT,
137            UNDERFLOW_MASK  = core.stdc.fenv.FE_UNDERFLOW,
138            OVERFLOW_MASK   = core.stdc.fenv.FE_OVERFLOW,
139            DIVBYZERO_MASK  = core.stdc.fenv.FE_DIVBYZERO,
140            INVALID_MASK    = core.stdc.fenv.FE_INVALID,
141            EXCEPTIONS_MASK = core.stdc.fenv.FE_ALL_EXCEPT,
142        }
143    }
144
145    static uint getIeeeFlags() @trusted pure
146    {
147        version (GNU)
148        {
149            version (X86_Any)
150            {
151                ushort sw;
152                asm pure nothrow @nogc
153                {
154                    "fstsw %0" : "=a" (sw);
155                }
156                // OR the result with the SSE2 status register (MXCSR).
157                if (haveSSE)
158                {
159                    uint mxcsr;
160                    asm pure nothrow @nogc
161                    {
162                        "stmxcsr %0" : "=m" (mxcsr);
163                    }
164                    return (sw | mxcsr) & EXCEPTIONS_MASK;
165                }
166                else
167                    return sw & EXCEPTIONS_MASK;
168            }
169            else version (ARM)
170            {
171                version (ARM_SoftFloat)
172                    return 0;
173                else
174                {
175                    uint result = void;
176                    asm pure nothrow @nogc
177                    {
178                        "vmrs %0, FPSCR; and %0, %0, #0x1F;" : "=r" (result);
179                    }
180                    return result;
181                }
182            }
183            else version (RISCV_Any)
184            {
185                version (D_SoftFloat)
186                    return 0;
187                else
188                {
189                    uint result = void;
190                    asm pure nothrow @nogc
191                    {
192                        "frflags %0" : "=r" (result);
193                    }
194                    return result;
195                }
196            }
197            else
198                assert(0, "Not yet supported");
199        }
200        else
201        version (InlineAsm_X86_Any)
202        {
203            ushort sw;
204            asm pure nothrow @nogc { fstsw sw; }
205
206            // OR the result with the SSE2 status register (MXCSR).
207            if (haveSSE)
208            {
209                uint mxcsr;
210                asm pure nothrow @nogc { stmxcsr mxcsr; }
211                return (sw | mxcsr) & EXCEPTIONS_MASK;
212            }
213            else return sw & EXCEPTIONS_MASK;
214        }
215        else version (SPARC)
216        {
217           /*
218               int retval;
219               asm pure nothrow @nogc { st %fsr, retval; }
220               return retval;
221            */
222           assert(0, "Not yet supported");
223        }
224        else version (ARM)
225        {
226            assert(false, "Not yet supported.");
227        }
228        else version (RISCV_Any)
229        {
230            mixin(`
231            uint result = void;
232            asm pure nothrow @nogc
233            {
234                "frflags %0" : "=r" (result);
235            }
236            return result;
237            `);
238        }
239        else
240            assert(0, "Not yet supported");
241    }
242
243    static void resetIeeeFlags() @trusted
244    {
245        version (GNU)
246        {
247            version (X86_Any)
248            {
249                asm nothrow @nogc
250                {
251                    "fnclex";
252                }
253
254                // Also clear exception flags in MXCSR, SSE's control register.
255                if (haveSSE)
256                {
257                    uint mxcsr;
258                    asm nothrow @nogc
259                    {
260                        "stmxcsr %0" : "=m" (mxcsr);
261                    }
262                    mxcsr &= ~EXCEPTIONS_MASK;
263                    asm nothrow @nogc
264                    {
265                        "ldmxcsr %0" : : "m" (mxcsr);
266                    }
267                }
268            }
269            else version (ARM)
270            {
271                version (ARM_SoftFloat)
272                    return;
273                else
274                {
275                    uint old = FloatingPointControl.getControlState();
276                    old &= ~0b11111; // http://infocenter.arm.com/help/topic/com.arm.doc.ddi0408i/Chdfifdc.html
277                    asm nothrow @nogc
278                    {
279                        "vmsr FPSCR, %0" : : "r" (old);
280                    }
281                }
282            }
283            else version (RISCV_Any)
284            {
285                version (D_SoftFloat)
286                    return;
287                else
288                {
289                    uint newValues = 0x0;
290                    asm nothrow @nogc
291                    {
292                        "fsflags %0" : : "r" (newValues);
293                    }
294                }
295            }
296            else
297                assert(0, "Not yet supported");
298        }
299        else
300        version (InlineAsm_X86_Any)
301        {
302            asm nothrow @nogc
303            {
304                fnclex;
305            }
306
307            // Also clear exception flags in MXCSR, SSE's control register.
308            if (haveSSE)
309            {
310                uint mxcsr;
311                asm nothrow @nogc { stmxcsr mxcsr; }
312                mxcsr &= ~EXCEPTIONS_MASK;
313                asm nothrow @nogc { ldmxcsr mxcsr; }
314            }
315        }
316        else version (RISCV_Any)
317        {
318            mixin(`
319            uint newValues = 0x0;
320            asm pure nothrow @nogc
321            {
322                "fsflags %0" : : "r" (newValues);
323            }
324            `);
325        }
326        else
327        {
328            /* SPARC:
329              int tmpval;
330              asm pure nothrow @nogc { st %fsr, tmpval; }
331              tmpval &=0xFFFF_FC00;
332              asm pure nothrow @nogc { ld tmpval, %fsr; }
333            */
334           assert(0, "Not yet supported");
335        }
336    }
337
338public:
339    /**
340     * The result cannot be represented exactly, so rounding occurred.
341     * Example: `x = sin(0.1);`
342     */
343    @property bool inexact() @safe const { return (flags & INEXACT_MASK) != 0; }
344
345    /**
346     * A zero was generated by underflow
347     * Example: `x = real.min*real.epsilon/2;`
348     */
349    @property bool underflow() @safe const { return (flags & UNDERFLOW_MASK) != 0; }
350
351    /**
352     * An infinity was generated by overflow
353     * Example: `x = real.max*2;`
354     */
355    @property bool overflow() @safe const { return (flags & OVERFLOW_MASK) != 0; }
356
357    /**
358     * An infinity was generated by division by zero
359     * Example: `x = 3/0.0;`
360     */
361    @property bool divByZero() @safe const { return (flags & DIVBYZERO_MASK) != 0; }
362
363    /**
364     * A machine NaN was generated.
365     * Example: `x = real.infinity * 0.0;`
366     */
367    @property bool invalid() @safe const { return (flags & INVALID_MASK) != 0; }
368}
369
370///
371version (IeeeFlagsUnittest)
372@safe unittest
373{
374    import std.math.traits : isNaN;
375
376    static void func() {
377        int a = 10 * 10;
378    }
379    pragma(inline, false) static void blockopt(ref real x) {}
380    real a = 3.5;
381    // Set all the flags to zero
382    resetIeeeFlags();
383    assert(!ieeeFlags.divByZero);
384    blockopt(a); // avoid constant propagation by the optimizer
385    // Perform a division by zero.
386    a /= 0.0L;
387    assert(a == real.infinity);
388    assert(ieeeFlags.divByZero);
389    blockopt(a); // avoid constant propagation by the optimizer
390    // Create a NaN
391    a *= 0.0L;
392    assert(ieeeFlags.invalid);
393    assert(isNaN(a));
394
395    // Check that calling func() has no effect on the
396    // status flags.
397    IeeeFlags f = ieeeFlags;
398    func();
399    assert(ieeeFlags == f);
400}
401
402version (IeeeFlagsUnittest)
403@safe unittest
404{
405    import std.meta : AliasSeq;
406
407    static struct Test
408    {
409        void delegate() @trusted action;
410        bool function() @trusted ieeeCheck;
411    }
412
413    static foreach (T; AliasSeq!(float, double, real))
414    {{
415        T x; /* Needs to be here to trick -O. It would optimize away the
416            calculations if x were local to the function literals. */
417        auto tests = [
418            Test(
419                () { x = 1; x += 0.1L; },
420                () => ieeeFlags.inexact
421            ),
422            Test(
423                () { x = T.min_normal; x /= T.max; },
424                () => ieeeFlags.underflow
425            ),
426            Test(
427                () { x = T.max; x += T.max; },
428                () => ieeeFlags.overflow
429            ),
430            Test(
431                () { x = 1; x /= 0; },
432                () => ieeeFlags.divByZero
433            ),
434            Test(
435                () { x = 0; x /= 0; },
436                () => ieeeFlags.invalid
437            )
438        ];
439        foreach (test; tests)
440        {
441            resetIeeeFlags();
442            assert(!test.ieeeCheck());
443            test.action();
444            assert(test.ieeeCheck());
445        }
446    }}
447}
448
449/// Set all of the floating-point status flags to false.
450void resetIeeeFlags() @trusted nothrow @nogc
451{
452    IeeeFlags.resetIeeeFlags();
453}
454
455///
456@safe unittest
457{
458    pragma(inline, false) static void blockopt(ref real x) {}
459    resetIeeeFlags();
460    real a = 3.5;
461    blockopt(a); // avoid constant propagation by the optimizer
462    a /= 0.0L;
463    blockopt(a); // avoid constant propagation by the optimizer
464    assert(a == real.infinity);
465    assert(ieeeFlags.divByZero);
466
467    resetIeeeFlags();
468    assert(!ieeeFlags.divByZero);
469}
470
471/// Returns: snapshot of the current state of the floating-point status flags
472@property IeeeFlags ieeeFlags() @trusted pure nothrow @nogc
473{
474   return IeeeFlags(IeeeFlags.getIeeeFlags());
475}
476
477///
478@safe nothrow unittest
479{
480    import std.math.traits : isNaN;
481
482    pragma(inline, false) static void blockopt(ref real x) {}
483    resetIeeeFlags();
484    real a = 3.5;
485    blockopt(a); // avoid constant propagation by the optimizer
486
487    a /= 0.0L;
488    assert(a == real.infinity);
489    assert(ieeeFlags.divByZero);
490    blockopt(a); // avoid constant propagation by the optimizer
491
492    a *= 0.0L;
493    assert(isNaN(a));
494    assert(ieeeFlags.invalid);
495}
496
497} // IeeeFlagsSupport
498
499
500version (FloatingPointControlSupport)
501{
502
503/** Control the Floating point hardware
504
505  Change the IEEE754 floating-point rounding mode and the floating-point
506  hardware exceptions.
507
508  By default, the rounding mode is roundToNearest and all hardware exceptions
509  are disabled. For most applications, debugging is easier if the $(I division
510  by zero), $(I overflow), and $(I invalid operation) exceptions are enabled.
511  These three are combined into a $(I severeExceptions) value for convenience.
512  Note in particular that if $(I invalidException) is enabled, a hardware trap
513  will be generated whenever an uninitialized floating-point variable is used.
514
515  All changes are temporary. The previous state is restored at the
516  end of the scope.
517
518
519Example:
520----
521{
522    FloatingPointControl fpctrl;
523
524    // Enable hardware exceptions for division by zero, overflow to infinity,
525    // invalid operations, and uninitialized floating-point variables.
526    fpctrl.enableExceptions(FloatingPointControl.severeExceptions);
527
528    // This will generate a hardware exception, if x is a
529    // default-initialized floating point variable:
530    real x; // Add `= 0` or even `= real.nan` to not throw the exception.
531    real y = x * 3.0;
532
533    // The exception is only thrown for default-uninitialized NaN-s.
534    // NaN-s with other payload are valid:
535    real z = y * real.nan; // ok
536
537    // The set hardware exceptions and rounding modes will be disabled when
538    // leaving this scope.
539}
540----
541
542 */
543struct FloatingPointControl
544{
545nothrow @nogc:
546
547    alias RoundingMode = uint; ///
548
549    version (StdDdoc)
550    {
551        enum : RoundingMode
552        {
553            /** IEEE rounding modes.
554             * The default mode is roundToNearest.
555             *
556             *  roundingMask = A mask of all rounding modes.
557             */
558            roundToNearest,
559            roundDown, /// ditto
560            roundUp, /// ditto
561            roundToZero, /// ditto
562            roundingMask, /// ditto
563        }
564    }
565    else version (CRuntime_Microsoft)
566    {
567        // Microsoft uses hardware-incompatible custom constants in fenv.h (core.stdc.fenv).
568        enum : RoundingMode
569        {
570            roundToNearest = 0x0000,
571            roundDown      = 0x0400,
572            roundUp        = 0x0800,
573            roundToZero    = 0x0C00,
574            roundingMask   = roundToNearest | roundDown
575                             | roundUp | roundToZero,
576        }
577    }
578    else
579    {
580        enum : RoundingMode
581        {
582            roundToNearest = core.stdc.fenv.FE_TONEAREST,
583            roundDown      = core.stdc.fenv.FE_DOWNWARD,
584            roundUp        = core.stdc.fenv.FE_UPWARD,
585            roundToZero    = core.stdc.fenv.FE_TOWARDZERO,
586            roundingMask   = roundToNearest | roundDown
587                             | roundUp | roundToZero,
588        }
589    }
590
591    /***
592     * Change the floating-point hardware rounding mode
593     *
594     * Changing the rounding mode in the middle of a function can interfere
595     * with optimizations of floating point expressions, as the optimizer assumes
596     * that the rounding mode does not change.
597     * It is best to change the rounding mode only at the
598     * beginning of the function, and keep it until the function returns.
599     * It is also best to add the line:
600     * ---
601     * pragma(inline, false);
602     * ---
603     * as the first line of the function so it will not get inlined.
604     * Params:
605     *    newMode = the new rounding mode
606     */
607    @property void rounding(RoundingMode newMode) @trusted
608    {
609        initialize();
610        setControlState((getControlState() & (-1 - roundingMask)) | (newMode & roundingMask));
611    }
612
613    /// Returns: the currently active rounding mode
614    @property static RoundingMode rounding() @trusted pure
615    {
616        return cast(RoundingMode)(getControlState() & roundingMask);
617    }
618
619    alias ExceptionMask = uint; ///
620
621    version (StdDdoc)
622    {
623        enum : ExceptionMask
624        {
625            /** IEEE hardware exceptions.
626             *  By default, all exceptions are masked (disabled).
627             *
628             *  severeExceptions = The overflow, division by zero, and invalid
629             *  exceptions.
630             */
631            subnormalException,
632            inexactException, /// ditto
633            underflowException, /// ditto
634            overflowException, /// ditto
635            divByZeroException, /// ditto
636            invalidException, /// ditto
637            severeExceptions, /// ditto
638            allExceptions, /// ditto
639        }
640    }
641    else version (ARM_Any)
642    {
643        enum : ExceptionMask
644        {
645            subnormalException    = 0x8000,
646            inexactException      = 0x1000,
647            underflowException    = 0x0800,
648            overflowException     = 0x0400,
649            divByZeroException    = 0x0200,
650            invalidException      = 0x0100,
651            severeExceptions   = overflowException | divByZeroException
652                                 | invalidException,
653            allExceptions      = severeExceptions | underflowException
654                                 | inexactException | subnormalException,
655        }
656    }
657    else version (PPC_Any)
658    {
659        enum : ExceptionMask
660        {
661            inexactException      = 0x0008,
662            divByZeroException    = 0x0010,
663            underflowException    = 0x0020,
664            overflowException     = 0x0040,
665            invalidException      = 0x0080,
666            severeExceptions   = overflowException | divByZeroException
667                                 | invalidException,
668            allExceptions      = severeExceptions | underflowException
669                                 | inexactException,
670        }
671    }
672    else version (RISCV_Any)
673    {
674        enum : ExceptionMask
675        {
676            inexactException      = 0x01,
677            divByZeroException    = 0x02,
678            underflowException    = 0x04,
679            overflowException     = 0x08,
680            invalidException      = 0x10,
681            severeExceptions   = overflowException | divByZeroException
682                                 | invalidException,
683            allExceptions      = severeExceptions | underflowException
684                                 | inexactException,
685        }
686    }
687    else version (HPPA)
688    {
689        enum : ExceptionMask
690        {
691            inexactException      = 0x01,
692            underflowException    = 0x02,
693            overflowException     = 0x04,
694            divByZeroException    = 0x08,
695            invalidException      = 0x10,
696            severeExceptions   = overflowException | divByZeroException
697                                 | invalidException,
698            allExceptions      = severeExceptions | underflowException
699                                 | inexactException,
700        }
701    }
702    else version (MIPS_Any)
703    {
704        enum : ExceptionMask
705        {
706            inexactException      = 0x0080,
707            divByZeroException    = 0x0400,
708            overflowException     = 0x0200,
709            underflowException    = 0x0100,
710            invalidException      = 0x0800,
711            severeExceptions   = overflowException | divByZeroException
712                                 | invalidException,
713            allExceptions      = severeExceptions | underflowException
714                                 | inexactException,
715        }
716    }
717    else version (SPARC_Any)
718    {
719        enum : ExceptionMask
720        {
721            inexactException      = 0x0800000,
722            divByZeroException    = 0x1000000,
723            overflowException     = 0x4000000,
724            underflowException    = 0x2000000,
725            invalidException      = 0x8000000,
726            severeExceptions   = overflowException | divByZeroException
727                                 | invalidException,
728            allExceptions      = severeExceptions | underflowException
729                                 | inexactException,
730        }
731    }
732    else version (IBMZ_Any)
733    {
734        enum : ExceptionMask
735        {
736            inexactException      = 0x08000000,
737            divByZeroException    = 0x40000000,
738            overflowException     = 0x20000000,
739            underflowException    = 0x10000000,
740            invalidException      = 0x80000000,
741            severeExceptions   = overflowException | divByZeroException
742                                 | invalidException,
743            allExceptions      = severeExceptions | underflowException
744                                 | inexactException,
745        }
746    }
747    else version (X86_Any)
748    {
749        enum : ExceptionMask
750        {
751            inexactException      = 0x20,
752            underflowException    = 0x10,
753            overflowException     = 0x08,
754            divByZeroException    = 0x04,
755            subnormalException    = 0x02,
756            invalidException      = 0x01,
757            severeExceptions   = overflowException | divByZeroException
758                                 | invalidException,
759            allExceptions      = severeExceptions | underflowException
760                                 | inexactException | subnormalException,
761        }
762    }
763    else
764        static assert(false, "Not implemented for this architecture");
765
766    version (ARM_Any)
767    {
768        static bool hasExceptionTraps_impl() @safe
769        {
770            auto oldState = getControlState();
771            // If exceptions are not supported, we set the bit but read it back as zero
772            // https://sourceware.org/ml/libc-ports/2012-06/msg00091.html
773            setControlState(oldState | divByZeroException);
774            immutable result = (getControlState() & allExceptions) != 0;
775            setControlState(oldState);
776            return result;
777        }
778    }
779
780    /// Returns: true if the current FPU supports exception trapping
781    @property static bool hasExceptionTraps() @safe pure
782    {
783        version (X86_Any)
784            return true;
785        else version (PPC_Any)
786            return true;
787        else version (MIPS_Any)
788            return true;
789        else version (ARM_Any)
790        {
791            // The hasExceptionTraps_impl function is basically pure,
792            // as it restores all global state
793            auto fptr = ( () @trusted => cast(bool function() @safe
794                pure nothrow @nogc)&hasExceptionTraps_impl)();
795            return fptr();
796        }
797        else
798            assert(0, "Not yet supported");
799    }
800
801    /// Enable (unmask) specific hardware exceptions. Multiple exceptions may be ORed together.
802    void enableExceptions(ExceptionMask exceptions) @trusted
803    {
804        assert(hasExceptionTraps);
805        initialize();
806        version (X86_Any)
807            setControlState(getControlState() & ~(exceptions & allExceptions));
808        else
809            setControlState(getControlState() | (exceptions & allExceptions));
810    }
811
812    /// Disable (mask) specific hardware exceptions. Multiple exceptions may be ORed together.
813    void disableExceptions(ExceptionMask exceptions) @trusted
814    {
815        assert(hasExceptionTraps);
816        initialize();
817        version (X86_Any)
818            setControlState(getControlState() | (exceptions & allExceptions));
819        else
820            setControlState(getControlState() & ~(exceptions & allExceptions));
821    }
822
823    /// Returns: the exceptions which are currently enabled (unmasked)
824    @property static ExceptionMask enabledExceptions() @trusted pure
825    {
826        assert(hasExceptionTraps);
827        version (X86_Any)
828            return (getControlState() & allExceptions) ^ allExceptions;
829        else
830            return (getControlState() & allExceptions);
831    }
832
833    ///  Clear all pending exceptions, then restore the original exception state and rounding mode.
834    ~this() @trusted
835    {
836        clearExceptions();
837        if (initialized)
838            setControlState(savedState);
839    }
840
841private:
842    ControlState savedState;
843
844    bool initialized = false;
845
846    version (ARM_Any)
847    {
848        alias ControlState = uint;
849    }
850    else version (HPPA)
851    {
852        alias ControlState = uint;
853    }
854    else version (PPC_Any)
855    {
856        alias ControlState = uint;
857    }
858    else version (RISCV_Any)
859    {
860        alias ControlState = uint;
861    }
862    else version (MIPS_Any)
863    {
864        alias ControlState = uint;
865    }
866    else version (SPARC_Any)
867    {
868        alias ControlState = ulong;
869    }
870    else version (IBMZ_Any)
871    {
872        alias ControlState = uint;
873    }
874    else version (X86_Any)
875    {
876        alias ControlState = ushort;
877    }
878    else
879        static assert(false, "Not implemented for this architecture");
880
881    void initialize() @safe
882    {
883        // BUG: This works around the absence of this() constructors.
884        if (initialized) return;
885        clearExceptions();
886        savedState = getControlState();
887        initialized = true;
888    }
889
890    // Clear all pending exceptions
891    static void clearExceptions() @safe
892    {
893        version (IeeeFlagsSupport)
894            resetIeeeFlags();
895        else
896            static assert(false, "Not implemented for this architecture");
897    }
898
899    // Read from the control register
900    package(std.math) static ControlState getControlState() @trusted pure
901    {
902        version (GNU)
903        {
904            version (X86_Any)
905            {
906                ControlState cont;
907                asm pure nothrow @nogc
908                {
909                    "fstcw %0" : "=m" (cont);
910                }
911                return cont;
912            }
913            else version (AArch64)
914            {
915                ControlState cont;
916                asm pure nothrow @nogc
917                {
918                    "mrs %0, FPCR;" : "=r" (cont);
919                }
920                return cont;
921            }
922            else version (ARM)
923            {
924                ControlState cont;
925                version (ARM_SoftFloat)
926                   cont = 0;
927                else
928                {
929                    asm pure nothrow @nogc
930                    {
931                        "vmrs %0, FPSCR" : "=r" (cont);
932                    }
933                }
934                return cont;
935            }
936            else version (RISCV_Any)
937            {
938                version (D_SoftFloat)
939                    return 0;
940                else
941                {
942                    ControlState cont;
943                    asm pure nothrow @nogc
944                    {
945                        "frcsr %0" : "=r" (cont);
946                    }
947                    return cont;
948                }
949            }
950            else
951                assert(0, "Not yet supported");
952        }
953        else
954        version (D_InlineAsm_X86)
955        {
956            short cont;
957            asm pure nothrow @nogc
958            {
959                xor EAX, EAX;
960                fstcw cont;
961            }
962            return cont;
963        }
964        else version (D_InlineAsm_X86_64)
965        {
966            short cont;
967            asm pure nothrow @nogc
968            {
969                xor RAX, RAX;
970                fstcw cont;
971            }
972            return cont;
973        }
974        else version (RISCV_Any)
975        {
976            mixin(`
977            ControlState cont;
978            asm pure nothrow @nogc
979            {
980                "frcsr %0" : "=r" (cont);
981            }
982            return cont;
983            `);
984        }
985        else
986            assert(0, "Not yet supported");
987    }
988
989    // Set the control register
990    package(std.math) static void setControlState(ControlState newState) @trusted
991    {
992        version (GNU)
993        {
994            version (X86_Any)
995            {
996                asm nothrow @nogc
997                {
998                    "fclex; fldcw %0" : : "m" (newState);
999                }
1000
1001                // Also update MXCSR, SSE's control register.
1002                if (haveSSE)
1003                {
1004                    uint mxcsr;
1005                    asm nothrow @nogc
1006                    {
1007                        "stmxcsr %0" : "=m" (mxcsr);
1008                    }
1009
1010                    /* In the FPU control register, rounding mode is in bits 10 and
1011                       11. In MXCSR it's in bits 13 and 14. */
1012                    mxcsr &= ~(roundingMask << 3);             // delete old rounding mode
1013                    mxcsr |= (newState & roundingMask) << 3;   // write new rounding mode
1014
1015                    /* In the FPU control register, masks are bits 0 through 5.
1016                       In MXCSR they're 7 through 12. */
1017                    mxcsr &= ~(allExceptions << 7);            // delete old masks
1018                    mxcsr |= (newState & allExceptions) << 7;  // write new exception masks
1019
1020                    asm nothrow @nogc
1021                    {
1022                        "ldmxcsr %0" : : "m" (mxcsr);
1023                    }
1024                }
1025            }
1026            else version (AArch64)
1027            {
1028                asm nothrow @nogc
1029                {
1030                    "msr FPCR, %0;" : : "r" (newState);
1031                }
1032            }
1033            else version (ARM)
1034            {
1035                version (ARM_SoftFloat)
1036                   return;
1037                else
1038                {
1039                    asm nothrow @nogc
1040                    {
1041                        "vmsr FPSCR, %0" : : "r" (newState);
1042                    }
1043                }
1044            }
1045            else version (RISCV_Any)
1046            {
1047                version (D_SoftFloat)
1048                    return;
1049                else
1050                {
1051                    asm nothrow @nogc
1052                    {
1053                        "fscsr %0" : : "r" (newState);
1054                    }
1055                }
1056            }
1057            else
1058                assert(0, "Not yet supported");
1059        }
1060        else
1061        version (InlineAsm_X86_Any)
1062        {
1063            asm nothrow @nogc
1064            {
1065                fclex;
1066                fldcw newState;
1067            }
1068
1069            // Also update MXCSR, SSE's control register.
1070            if (haveSSE)
1071            {
1072                uint mxcsr;
1073                asm nothrow @nogc { stmxcsr mxcsr; }
1074
1075                /* In the FPU control register, rounding mode is in bits 10 and
1076                11. In MXCSR it's in bits 13 and 14. */
1077                mxcsr &= ~(roundingMask << 3);             // delete old rounding mode
1078                mxcsr |= (newState & roundingMask) << 3;   // write new rounding mode
1079
1080                /* In the FPU control register, masks are bits 0 through 5.
1081                In MXCSR they're 7 through 12. */
1082                mxcsr &= ~(allExceptions << 7);            // delete old masks
1083                mxcsr |= (newState & allExceptions) << 7;  // write new exception masks
1084
1085                asm nothrow @nogc { ldmxcsr mxcsr; }
1086            }
1087        }
1088        else version (RISCV_Any)
1089        {
1090            mixin(`
1091            asm pure nothrow @nogc
1092            {
1093                "fscsr %0" : : "r" (newState);
1094            }
1095            `);
1096        }
1097        else
1098            assert(0, "Not yet supported");
1099    }
1100}
1101
1102///
1103version (FloatingPointControlUnittest)
1104@safe unittest
1105{
1106    import std.math.rounding : lrint;
1107
1108    FloatingPointControl fpctrl;
1109
1110    fpctrl.rounding = FloatingPointControl.roundDown;
1111    assert(lrint(1.5) == 1.0);
1112
1113    fpctrl.rounding = FloatingPointControl.roundUp;
1114    assert(lrint(1.4) == 2.0);
1115
1116    fpctrl.rounding = FloatingPointControl.roundToNearest;
1117    assert(lrint(1.5) == 2.0);
1118}
1119
1120@safe unittest
1121{
1122    void ensureDefaults()
1123    {
1124        assert(FloatingPointControl.rounding
1125               == FloatingPointControl.roundToNearest);
1126        if (FloatingPointControl.hasExceptionTraps)
1127            assert(FloatingPointControl.enabledExceptions == 0);
1128    }
1129
1130    {
1131        FloatingPointControl ctrl;
1132    }
1133    ensureDefaults();
1134
1135    {
1136        FloatingPointControl ctrl;
1137        ctrl.rounding = FloatingPointControl.roundDown;
1138        assert(FloatingPointControl.rounding == FloatingPointControl.roundDown);
1139    }
1140    ensureDefaults();
1141
1142    if (FloatingPointControl.hasExceptionTraps)
1143    {
1144        FloatingPointControl ctrl;
1145        ctrl.enableExceptions(FloatingPointControl.divByZeroException
1146                              | FloatingPointControl.overflowException);
1147        assert(ctrl.enabledExceptions ==
1148               (FloatingPointControl.divByZeroException
1149                | FloatingPointControl.overflowException));
1150
1151        ctrl.rounding = FloatingPointControl.roundUp;
1152        assert(FloatingPointControl.rounding == FloatingPointControl.roundUp);
1153    }
1154    ensureDefaults();
1155}
1156
1157version (FloatingPointControlUnittest)
1158@safe unittest // rounding
1159{
1160    import std.meta : AliasSeq;
1161
1162    static T addRound(T)(uint rm)
1163    {
1164        pragma(inline, false) static void blockopt(ref T x) {}
1165        pragma(inline, false);
1166        FloatingPointControl fpctrl;
1167        fpctrl.rounding = rm;
1168        T x = 1;
1169        blockopt(x); // avoid constant propagation by the optimizer
1170        x += 0.1L;
1171        return x;
1172    }
1173
1174    static T subRound(T)(uint rm)
1175    {
1176        pragma(inline, false) static void blockopt(ref T x) {}
1177        pragma(inline, false);
1178        FloatingPointControl fpctrl;
1179        fpctrl.rounding = rm;
1180        T x = -1;
1181        blockopt(x); // avoid constant propagation by the optimizer
1182        x -= 0.1L;
1183        return x;
1184    }
1185
1186    static foreach (T; AliasSeq!(float, double, real))
1187    {{
1188        /* Be careful with changing the rounding mode, it interferes
1189         * with common subexpressions. Changing rounding modes should
1190         * be done with separate functions that are not inlined.
1191         */
1192
1193        {
1194            T u = addRound!(T)(FloatingPointControl.roundUp);
1195            T d = addRound!(T)(FloatingPointControl.roundDown);
1196            T z = addRound!(T)(FloatingPointControl.roundToZero);
1197
1198            assert(u > d);
1199            assert(z == d);
1200        }
1201
1202        {
1203            T u = subRound!(T)(FloatingPointControl.roundUp);
1204            T d = subRound!(T)(FloatingPointControl.roundDown);
1205            T z = subRound!(T)(FloatingPointControl.roundToZero);
1206
1207            assert(u > d);
1208            assert(z == u);
1209        }
1210    }}
1211}
1212
1213}
1214