thermite 0.2.1

High-performance, generic, ISA-portable SIMD library with a policy-configurable transcendental math library
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
/// Execution policy used for controlling performance/precision/size tradeoffs in mathematical functions.
pub trait Policy: core::fmt::Debug + Clone + Copy + PartialEq + Eq + PartialOrd + Ord + core::hash::Hash {
    /// The specific policy used. This is a constant to allow for dead-code elimination of branches.
    const POLICY: PolicyParameters;
}

/** Precision Policy, tradeoffs between precision and performance.

The precision policy modifies how functions are evaluated to provide extra precision
at the cost of performance, or sacrifice precision for extra performance.

For example, some functions have a generic solution that is technically correct but due to floating
point errors will not be very precise, and it's often better to fallback to another solution that
does not accrue such errors, at the cost of performance.
*/
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
#[repr(u32)]
pub enum PrecisionPolicy {
    /// Precision is not important, so prefer simpler or faster algorithms.
    Worst = 0,
    /// Precision is not that important, so prefer faster algorithms.
    Medium = 1,
    /// Precision is important, but not the focus, so avoid expensive fallbacks.
    Average = 2,
    /// Precision is very important, so do everything to improve it.
    Best = 3,
    /// Precision is the only factor, use infinite sums to compute reference solutions.
    Reference = 9,
}

impl PrecisionPolicy {
    pub const fn eq(self, other: PrecisionPolicy) -> bool {
        (self as u32) == (other as u32)
    }
    pub const fn gt(self, other: PrecisionPolicy) -> bool {
        (self as u32) > (other as u32)
    }
    pub const fn ge(self, other: PrecisionPolicy) -> bool {
        (self as u32) >= (other as u32)
    }
    pub const fn lt(self, other: PrecisionPolicy) -> bool {
        (self as u32) < (other as u32)
    }
    pub const fn le(self, other: PrecisionPolicy) -> bool {
        (self as u32) <= (other as u32)
    }

    /// Returns the multiple of `EPSILON` to use as the tolerance for this precision policy.
    #[inline(always)]
    pub const fn tolerance(self) -> crate::LargeInt {
        match self {
            PrecisionPolicy::Worst => 100_000,
            PrecisionPolicy::Medium => 10_000,
            PrecisionPolicy::Average => 100,
            PrecisionPolicy::Best => 20,
            PrecisionPolicy::Reference => 8,
        }
    }
}

/// Denormal/Subnormal numbers cause performance hiccups even in
/// well-behaved code. They are a side-effect of IEEE-754 gracefully degrading
/// with very small numbers, rather than immediately going to zero on underflow.
///
/// However, due to how some processors handle this, even simple operations on
/// denormal numbers can be over 100x slower. They do this because subnormal values
/// are valid IEEE754 values, and sometimes you want them to exist.
///
/// Most PrecisionPolicy's will default to `FlushToZero`, while the high performance oriented
/// policies will default to `Crush` for the faster happy path.
///
/// However, there are two crate features that influence the default behavior. `ignore_denormals`
/// will cause all policies to default to `Ignore`, and the `preserve_denormal` will
/// cause all policies to default to `Preserve`.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DenormalBehavior {
    /// Do nothing to remove or specially handle denormal values. This is good if the application
    /// being targeted will have denormals disabled on the hardware level, removing the
    /// performance penalty.
    Ignore,

    /// Use exact bitwise operations to flush denormals to zero. This has a non-zero performance
    /// cost, but is a good default since the cost is constant.
    FlushToZero,

    /// Uses a "crush denormals" trick of `(a - (a - x))` where `a` is a very small constant. This
    /// removes denormals and is very fast in the happy path where the number is NOT denormal,
    /// but will incur a heavy cost if the number is denormal.
    Crush,

    /// Actively try to preserve and handle denormal values. This has a non-zero performance cost,
    /// but is necessary if the application being targeted needs to handle denormal values correctly.
    Preserve,
}

impl DenormalBehavior {
    const fn preserve_any(a: Self, b: Self) -> Self {
        match (a, b) {
            (DenormalBehavior::Preserve, _) | (_, DenormalBehavior::Preserve) => DenormalBehavior::Preserve,
            _ => a,
        }
    }

    const fn select_default(crush: bool) -> Self {
        #[cfg(feature = "preserve_denormals")]
        return DenormalBehavior::Preserve;

        #[cfg(feature = "ignore_denormals")]
        return DenormalBehavior::Ignore;

        if crush {
            DenormalBehavior::Crush
        } else {
            DenormalBehavior::FlushToZero
        }
    }
}

/// Customizable Policy Parameters
pub struct PolicyParameters {
    /// If true, methods will check for infinity/NaN/invalid domain issues and give a well-formed standard result.
    ///
    /// If false, all of that work is avoided, and the result is undefined in those cases. Garbage in, garbage out.
    ///
    /// However, those checks can be expensive.
    pub check_overflow: bool,

    /// If true, unrolled and optimized versions of some algorithms will be used. These can be much faster than
    /// the linear variants. If code size is important, this will improve codegen when used with `opt-level=z`
    pub unroll_loops: bool,

    /// Controls if precision should be emphasized or de-emphasized.
    pub precision: PrecisionPolicy,

    /// If true, methods will not try to avoid extra work by branching. Some of the internal branches are expensive,
    /// but branchless may be desired in some cases, such as minimizing code size.
    pub avoid_branching: bool,

    /// Some special functions require many, many iterations of a function to converge on an accurate result.
    /// This parameter controls the maximum iterations allowed. Setting this too low may result in loss of precision.
    ///
    /// Note that this is the upper limit allowed for pathological cases, and many loops will
    /// terminate dynamically before this.
    pub max_iterations: usize,

    /// If true, use compensated algorithms where available (such as Kahan summation).
    ///
    /// This attribute will change depending on the precision policy selected, and selecting
    /// a new precision policy may overwrite this value. Apply combinators carefully.
    pub use_compensation: bool,

    /// Specifies how denormals are handled. See [`DenormalBehavior`] for more info.
    pub denormal_behavior: DenormalBehavior,
    // /// If NaN was an input or a result of internal computations, if true this will always return the same bit pattern of NaN
    // /// regardless of the input or intermediate NaN value. This is useful for testing and debugging, since it allows
    // /// for consistent NaN values that can be compared against.
    // pub strict_nan: bool,
}

impl PolicyParameters {
    /// Returns true if the policy says to avoid branches at the cost of precision
    #[inline(always)]
    pub const fn avoid_precision_branches(self) -> bool {
        self.avoid_branching && self.precision.le(PrecisionPolicy::Worst)
    }
}

/** Execution Policies (precision, performance, etc.)

To define a custom policy:
```rust,ignore
pub struct MyPolicy;

impl Policy for MyPolicy {
    const POLICY: Parameters = Parameters {
        check_overflow: false,
        unroll_loops: false,
        precision: PrecisionPolicy::Average,
        avoid_branching: true,
        max_iterations: 10000,
        use_compensation: true,
        denormal_behavior: DenormalBehavior::FlushToZero,
    };
}

let y = x.cbrt_p::<MyPolicy>();
```
*/
pub mod policies {
    use core::marker::PhantomData;

    use super::{DenormalBehavior, Policy, PolicyParameters, PrecisionPolicy};

    /// Policy adapter that increases the precision requires by one level,
    /// e.g.: `Worst` -> `Medium`, `Medium` -> `Average`, `Average` -> `Best`, `Best` -> `Reference`
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct ExtraPrecision<P: Policy>(PhantomData<P>);

    /// Policy adapter that decreases the precision required by one level,
    /// e.g.: `Reference` -> `Best`, `Best` -> `Average`, `Average` -> `Medium`, `Medium` -> `Worst`
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct LessPrecision<P: Policy>(PhantomData<P>);

    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct UseCompensation<P: Policy, const USE_COMPENSATION: bool>(PhantomData<P>);

    /// Policy adapter that modifies the base policy to change overflow checking.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct CheckOverflow<P: Policy, const CHECK_OVERFLOW: bool>(PhantomData<P>);

    /// Policy adapter that modifies the base policy to change loop unrolling.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct UnrollLoops<P: Policy, const UNROLL_LOOPS: bool>(PhantomData<P>);

    /// Policy adapter that modifies the base policy to change branching behavior.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct AvoidBranching<P: Policy, const AVOID_BRANCHING: bool>(PhantomData<P>);

    /// Preserves denormal values rather than flushing or crushing them.
    ///
    /// Reach for this on a *part* of a computation whose magnitude runs below the rest of
    /// it. The motivating case is the low word of a double-double: it sits ~53 binades
    /// under the value, so scaling can push it into the subnormal range while the value
    /// is still comfortably normal, and flushing it there costs the whole point of the
    /// representation.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct PreserveDenormals<P: Policy>(PhantomData<P>);

    /// Policy adapter that modifies the base policy to change the maximum number of iterations for numerical methods.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct MaxIterations<P: Policy, const MAX_ITERATIONS: usize>(PhantomData<P>);

    /// Policy for worst precision, which is the least precise and fastest.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct WorstPrecision<P: Policy>(PhantomData<P>);
    /// Policy for medium precision, which is a balance between performance and precision.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct MediumPrecision<P: Policy>(PhantomData<P>);
    /// Policy for average precision, which is more precise than medium but less than best.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct AveragePrecision<P: Policy>(PhantomData<P>);
    /// Policy for best precision, which is the most precise and may be slower.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct BestPrecision<P: Policy>(PhantomData<P>);
    /// Policy for reference precision, which is the most precise and may be very slow.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct ReferencePrecision<P: Policy>(PhantomData<P>);

    /// Takes the precision of the second policy only if it is less than the first policy,
    /// but otherwise uses the first policy's other parameters.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct CmpLessPrecision<A: Policy, B: Policy>(PhantomData<(A, B)>);

    /// Optimize for performance at the cost of precision and safety (doesn't handle special cases such as NaNs or overflow).
    ///
    /// On instruction sets with FMA, this usually doesn't hurt precision too much, but will still avoid overflow/underflow checking,
    /// which can result in undefined behavior.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct UltraPerformance;

    /// Optimize for performance at the cost of safety, but try to keep some precision.
    ///
    /// This avoids checking for special cases such as NaNs or overflow, but will still try to
    /// provide a reasonable result for most inputs.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct HighPerformance;

    /// Optimize for performance, ideally without losing precision.
    ///
    /// This is the default policy for the non-policy-specific math functions,
    /// and tries to provide as much precision and performance as possible.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct Performance;

    /// Optimize for precision, at the cost of performance if necessary.
    ///
    /// On instruction sets with FMA, performance may not be hurt too much.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct Precision;

    /// Optimize for code size, avoids hard-coded equations or loop unrolling.
    ///
    /// Performance is not a priority for this policy.
    ///
    /// Best used in conjunction with `opt-level=z`
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct Size;

    /// Calculates a reference value for operations where possible, which can be very expensive.
    #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
    pub struct Reference;

    const fn extra_precision(p: PrecisionPolicy) -> PrecisionPolicy {
        match p {
            PrecisionPolicy::Worst => PrecisionPolicy::Medium,
            PrecisionPolicy::Medium => PrecisionPolicy::Average,
            PrecisionPolicy::Average => PrecisionPolicy::Best,
            PrecisionPolicy::Best => PrecisionPolicy::Reference,
            PrecisionPolicy::Reference => PrecisionPolicy::Reference, // no change
        }
    }

    const fn less_precision(p: PrecisionPolicy) -> PrecisionPolicy {
        match p {
            PrecisionPolicy::Reference => PrecisionPolicy::Best,
            PrecisionPolicy::Best => PrecisionPolicy::Average,
            PrecisionPolicy::Average => PrecisionPolicy::Medium,
            PrecisionPolicy::Medium => PrecisionPolicy::Worst,
            PrecisionPolicy::Worst => PrecisionPolicy::Worst, // no change
        }
    }

    impl<P: Policy> Policy for ExtraPrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: extra_precision(P::POLICY.precision),
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.precision.ge(PrecisionPolicy::Average),
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for LessPrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: less_precision(P::POLICY.precision),
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.precision.gt(PrecisionPolicy::Average),
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy, const USE_COMPENSATION: bool> Policy for UseCompensation<P, USE_COMPENSATION> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: P::POLICY.precision,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: USE_COMPENSATION,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy, const CHECK_OVERFLOW: bool> Policy for CheckOverflow<P, CHECK_OVERFLOW> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: CHECK_OVERFLOW,
            unroll_loops: P::POLICY.unroll_loops,
            precision: P::POLICY.precision,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.use_compensation,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy, const UNROLL_LOOPS: bool> Policy for UnrollLoops<P, UNROLL_LOOPS> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: UNROLL_LOOPS,
            precision: P::POLICY.precision,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.use_compensation,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy, const AVOID_BRANCHING: bool> Policy for AvoidBranching<P, AVOID_BRANCHING> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: P::POLICY.precision,
            avoid_branching: AVOID_BRANCHING,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.use_compensation,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy, const MAX_ITERATIONS: usize> Policy for MaxIterations<P, MAX_ITERATIONS> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: P::POLICY.precision,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: MAX_ITERATIONS,
            use_compensation: P::POLICY.use_compensation,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for PreserveDenormals<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: P::POLICY.precision,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: P::POLICY.use_compensation,
            denormal_behavior: DenormalBehavior::Preserve,
        };
    }

    impl<P: Policy> Policy for WorstPrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: PrecisionPolicy::Worst,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: false,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for MediumPrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: PrecisionPolicy::Medium,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: false,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for AveragePrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: PrecisionPolicy::Average,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: false,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for BestPrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: PrecisionPolicy::Best,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: true,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<P: Policy> Policy for ReferencePrecision<P> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: P::POLICY.check_overflow,
            unroll_loops: P::POLICY.unroll_loops,
            precision: PrecisionPolicy::Reference,
            avoid_branching: P::POLICY.avoid_branching,
            max_iterations: P::POLICY.max_iterations,
            use_compensation: true,
            denormal_behavior: P::POLICY.denormal_behavior,
        };
    }

    impl<A: Policy, B: Policy> Policy for CmpLessPrecision<A, B> {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: A::POLICY.check_overflow,
            unroll_loops: A::POLICY.unroll_loops,
            precision: if B::POLICY.precision.lt(A::POLICY.precision) {
                B::POLICY.precision
            } else {
                A::POLICY.precision
            },
            avoid_branching: A::POLICY.avoid_branching,
            max_iterations: A::POLICY.max_iterations,
            use_compensation: A::POLICY.use_compensation && B::POLICY.use_compensation,
            denormal_behavior: DenormalBehavior::preserve_any(A::POLICY.denormal_behavior, B::POLICY.denormal_behavior),
        };
    }

    impl Policy for UltraPerformance {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: false,
            unroll_loops: true,
            precision: PrecisionPolicy::Worst,
            avoid_branching: true,
            max_iterations: 1000,
            use_compensation: false,
            denormal_behavior: DenormalBehavior::select_default(true),
        };
    }

    impl Policy for HighPerformance {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: false,
            unroll_loops: true,
            precision: PrecisionPolicy::Medium,
            avoid_branching: false,
            max_iterations: 10000,
            use_compensation: false,
            denormal_behavior: DenormalBehavior::select_default(true),
        };
    }

    impl Policy for Performance {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: true,
            unroll_loops: true,
            precision: PrecisionPolicy::Average,
            avoid_branching: false,
            max_iterations: 10000,
            use_compensation: false,
            denormal_behavior: DenormalBehavior::select_default(false),
        };
    }

    impl Policy for Precision {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: true,
            unroll_loops: true,
            precision: PrecisionPolicy::Best,
            avoid_branching: false,
            max_iterations: 50000,
            use_compensation: true,
            denormal_behavior: DenormalBehavior::select_default(false),
        };
    }

    impl Policy for Size {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: true,
            unroll_loops: false,
            precision: PrecisionPolicy::Average,

            // debatable, but for WASM it can't use
            // instruction-level parallelism anyway.
            avoid_branching: false,
            max_iterations: 10000,
            use_compensation: false,
            denormal_behavior: DenormalBehavior::select_default(true),
        };
    }

    impl Policy for Reference {
        const POLICY: PolicyParameters = PolicyParameters {
            check_overflow: true,
            unroll_loops: true,
            precision: PrecisionPolicy::Reference,
            avoid_branching: false,
            max_iterations: 100000,
            use_compensation: true,
            denormal_behavior: DenormalBehavior::select_default(false),
        };
    }
}

use policies::*;

#[cfg(all(feature = "spirv", target_arch = "spirv"))]
pub struct GpuDefault;

#[cfg(all(feature = "spirv", target_arch = "spirv"))]
impl Policy for GpuDefault {
    const POLICY: PolicyParameters = PolicyParameters {
        check_overflow: true,
        unroll_loops: true,
        precision: PrecisionPolicy::Average,
        avoid_branching: true,
        max_iterations: 10000,
        use_compensation: false,
        denormal_behavior: DenormalBehavior::select_default(true),
    };
}

/// The default math policy, which may be different depending on the platform.
///
/// For example, this defaults to [`Size`] on WASM. On most CPU platforms this
/// defaults to [`Performance`].
///
/// With the `strict_ieee754` feature the default becomes [`Precision`] (Best
/// tier): spec-exactness is the entire point of that feature, so the default
/// policy opts into the expensive-correctness paths (e.g. Payne-Hanek trig
/// range reduction) that the performance tiers deliberately skip.
pub type DefaultPolicy = cfg_select! {
    feature = "strict_ieee754" => Precision,
    all(feature = "wasm", any(target_arch = "wasm32", target_arch = "wasm64")) => Size,
    all(feature = "spirv", target_arch = "spirv") => GpuDefault,
    _ => Performance,
};