|
1 | 1 | //! Configuration for skipping or changing the result for individual test cases (inputs) rather
|
2 | 2 | //! than ignoring entire tests.
|
3 | 3 |
|
4 |
| -use crate::{CheckCtx, Float, Int, TestResult}; |
| 4 | +use core::f32; |
| 5 | + |
| 6 | +use crate::{CheckBasis, CheckCtx, Float, Int, TestResult}; |
5 | 7 |
|
6 | 8 | /// Type implementing [`IgnoreCase`].
|
7 | 9 | pub struct SpecialCase;
|
@@ -49,47 +51,189 @@ pub trait MaybeOverride<Input> {
|
49 | 51 |
|
50 | 52 | impl MaybeOverride<(f32,)> for SpecialCase {
|
51 | 53 | fn check_float<F: Float>(
|
52 |
| - _input: (f32,), |
| 54 | + input: (f32,), |
53 | 55 | actual: F,
|
54 | 56 | expected: F,
|
55 | 57 | _ulp: &mut u32,
|
56 | 58 | ctx: &CheckCtx,
|
57 | 59 | ) -> Option<TestResult> {
|
| 60 | + if ctx.basis == CheckBasis::Musl { |
| 61 | + if ctx.fname == "acoshf" && input.0 < -1.0 { |
| 62 | + // acoshf is undefined for x <= 1.0, but we return a random result at lower |
| 63 | + // values. |
| 64 | + return XFAIL; |
| 65 | + } |
| 66 | + |
| 67 | + if ctx.fname == "sincosf" { |
| 68 | + let factor_frac_pi_2 = input.0.abs() / f32::consts::FRAC_PI_2; |
| 69 | + if (factor_frac_pi_2 - factor_frac_pi_2.round()).abs() < 1e-2 { |
| 70 | + // we have a bad approximation near multiples of pi/2 |
| 71 | + return XFAIL; |
| 72 | + } |
| 73 | + } |
| 74 | + |
| 75 | + if ctx.fname == "expm1f" && input.0 > 80.0 && actual.is_infinite() { |
| 76 | + // we return infinity but the number is representable |
| 77 | + return XFAIL; |
| 78 | + } |
| 79 | + |
| 80 | + if ctx.fname == "sinhf" && input.0.abs() > 80.0 && actual.is_nan() { |
| 81 | + // we return some NaN that should be real values or infinite |
| 82 | + // doesn't seem to happen on x86 |
| 83 | + return XFAIL; |
| 84 | + } |
| 85 | + |
| 86 | + if ctx.fname == "lgammaf" || ctx.fname == "lgammaf_r" && input.0 < 0.0 { |
| 87 | + // loggamma should not be defined for x < 0, yet we both return results |
| 88 | + return XFAIL; |
| 89 | + } |
| 90 | + } |
| 91 | + |
58 | 92 | maybe_check_nan_bits(actual, expected, ctx)
|
59 | 93 | }
|
60 | 94 | }
|
61 | 95 |
|
62 | 96 | impl MaybeOverride<(f64,)> for SpecialCase {
|
63 | 97 | fn check_float<F: Float>(
|
64 |
| - _input: (f64,), |
| 98 | + input: (f64,), |
65 | 99 | actual: F,
|
66 | 100 | expected: F,
|
67 | 101 | _ulp: &mut u32,
|
68 | 102 | ctx: &CheckCtx,
|
69 | 103 | ) -> Option<TestResult> {
|
| 104 | + if ctx.basis == CheckBasis::Musl { |
| 105 | + if cfg!(target_arch = "x86") && ctx.fname == "acosh" && input.0 < 1.0 { |
| 106 | + // The function is undefined, both implementations return random results |
| 107 | + return SKIP; |
| 108 | + } |
| 109 | + |
| 110 | + if cfg!(x86_no_sse) |
| 111 | + && ctx.fname == "ceil" |
| 112 | + && input.0 < 0.0 |
| 113 | + && input.0 > -1.0 |
| 114 | + && expected == F::ZERO |
| 115 | + && actual == F::ZERO |
| 116 | + { |
| 117 | + // musl returns -0.0, we return +0.0 |
| 118 | + return XFAIL; |
| 119 | + } |
| 120 | + |
| 121 | + if ctx.fname == "lgamma" || ctx.fname == "lgamma_r" && input.0 < 0.0 { |
| 122 | + // loggamma should not be defined for x < 0, yet we both return results |
| 123 | + return XFAIL; |
| 124 | + } |
| 125 | + } |
| 126 | + |
70 | 127 | maybe_check_nan_bits(actual, expected, ctx)
|
71 | 128 | }
|
72 | 129 | }
|
73 | 130 |
|
74 |
| -impl MaybeOverride<(f32, f32)> for SpecialCase {} |
75 |
| -impl MaybeOverride<(f64, f64)> for SpecialCase {} |
76 |
| -impl MaybeOverride<(f32, f32, f32)> for SpecialCase {} |
77 |
| -impl MaybeOverride<(f64, f64, f64)> for SpecialCase {} |
78 |
| -impl MaybeOverride<(i32, f32)> for SpecialCase {} |
79 |
| -impl MaybeOverride<(i32, f64)> for SpecialCase {} |
80 |
| -impl MaybeOverride<(f32, i32)> for SpecialCase {} |
81 |
| -impl MaybeOverride<(f64, i32)> for SpecialCase {} |
82 |
| - |
83 | 131 | /// Check NaN bits if the function requires it
|
84 | 132 | fn maybe_check_nan_bits<F: Float>(actual: F, expected: F, ctx: &CheckCtx) -> Option<TestResult> {
|
85 |
| - if !(ctx.canonical_name == "abs" || ctx.canonical_name == "copysigh") { |
| 133 | + if !(ctx.canonical_name == "fabs" || ctx.canonical_name == "copysign") { |
86 | 134 | return None;
|
87 | 135 | }
|
88 | 136 |
|
| 137 | + // LLVM currently uses x87 instructions which quieten signalling NaNs to handle the i686 |
| 138 | + // `extern "C"` `f32`/`f64` return ABI. |
| 139 | + // LLVM issue <https://github.com/llvm/llvm-project/issues/66803> |
| 140 | + // Rust issue <https://github.com/rust-lang/rust/issues/115567> |
| 141 | + if cfg!(target_arch = "x86") && ctx.basis == CheckBasis::Musl { |
| 142 | + return SKIP; |
| 143 | + } |
| 144 | + |
89 | 145 | // abs and copysign require signaling NaNs to be propagated, so verify bit equality.
|
90 | 146 | if actual.to_bits() == expected.to_bits() {
|
91 | 147 | return SKIP;
|
92 | 148 | } else {
|
93 | 149 | Some(Err(anyhow::anyhow!("NaNs have different bitpatterns")))
|
94 | 150 | }
|
95 | 151 | }
|
| 152 | + |
| 153 | +impl MaybeOverride<(f32, f32)> for SpecialCase { |
| 154 | + fn check_float<F: Float>( |
| 155 | + input: (f32, f32), |
| 156 | + _actual: F, |
| 157 | + expected: F, |
| 158 | + _ulp: &mut u32, |
| 159 | + ctx: &CheckCtx, |
| 160 | + ) -> Option<TestResult> { |
| 161 | + maybe_skip_min_max_nan(input, expected, ctx) |
| 162 | + } |
| 163 | +} |
| 164 | +impl MaybeOverride<(f64, f64)> for SpecialCase { |
| 165 | + fn check_float<F: Float>( |
| 166 | + input: (f64, f64), |
| 167 | + _actual: F, |
| 168 | + expected: F, |
| 169 | + _ulp: &mut u32, |
| 170 | + ctx: &CheckCtx, |
| 171 | + ) -> Option<TestResult> { |
| 172 | + maybe_skip_min_max_nan(input, expected, ctx) |
| 173 | + } |
| 174 | +} |
| 175 | + |
| 176 | +/// Musl propagates NaNs if one is provided as the input, but we return the other input. |
| 177 | +// F1 and F2 are always the same type, this is just to please generics |
| 178 | +fn maybe_skip_min_max_nan<F1: Float, F2: Float>( |
| 179 | + input: (F1, F1), |
| 180 | + expected: F2, |
| 181 | + ctx: &CheckCtx, |
| 182 | +) -> Option<TestResult> { |
| 183 | + if (ctx.canonical_name == "fmax" || ctx.canonical_name == "fmin") |
| 184 | + && (input.0.is_nan() || input.1.is_nan()) |
| 185 | + && expected.is_nan() |
| 186 | + { |
| 187 | + return XFAIL; |
| 188 | + } else { |
| 189 | + None |
| 190 | + } |
| 191 | +} |
| 192 | + |
| 193 | +impl MaybeOverride<(i32, f32)> for SpecialCase { |
| 194 | + fn check_float<F: Float>( |
| 195 | + input: (i32, f32), |
| 196 | + _actual: F, |
| 197 | + _expected: F, |
| 198 | + ulp: &mut u32, |
| 199 | + ctx: &CheckCtx, |
| 200 | + ) -> Option<TestResult> { |
| 201 | + bessel_prec_dropoff(input, ulp, ctx) |
| 202 | + } |
| 203 | +} |
| 204 | +impl MaybeOverride<(i32, f64)> for SpecialCase { |
| 205 | + fn check_float<F: Float>( |
| 206 | + input: (i32, f64), |
| 207 | + _actual: F, |
| 208 | + _expected: F, |
| 209 | + ulp: &mut u32, |
| 210 | + ctx: &CheckCtx, |
| 211 | + ) -> Option<TestResult> { |
| 212 | + bessel_prec_dropoff(input, ulp, ctx) |
| 213 | + } |
| 214 | +} |
| 215 | + |
| 216 | +/// Our bessel functions blow up with large N values |
| 217 | +fn bessel_prec_dropoff<F: Float>( |
| 218 | + input: (i32, F), |
| 219 | + ulp: &mut u32, |
| 220 | + ctx: &CheckCtx, |
| 221 | +) -> Option<TestResult> { |
| 222 | + if ctx.canonical_name == "jn" { |
| 223 | + if input.0 > 4000 { |
| 224 | + return XFAIL; |
| 225 | + } else if input.0 > 2000 { |
| 226 | + // *ulp = 20_000; |
| 227 | + *ulp = 20000; |
| 228 | + } else if input.0 > 1000 { |
| 229 | + *ulp = 4000; |
| 230 | + } |
| 231 | + } |
| 232 | + |
| 233 | + None |
| 234 | +} |
| 235 | + |
| 236 | +impl MaybeOverride<(f32, f32, f32)> for SpecialCase {} |
| 237 | +impl MaybeOverride<(f64, f64, f64)> for SpecialCase {} |
| 238 | +impl MaybeOverride<(f32, i32)> for SpecialCase {} |
| 239 | +impl MaybeOverride<(f64, i32)> for SpecialCase {} |
0 commit comments