Skip to content

Commit b3a5da8

Browse files
committed
fuzz: add two more flags to control NaN strictness (vs hardware) and hide known differences.
1 parent 6b93eab commit b3a5da8

File tree

1 file changed

+70
-4
lines changed

1 file changed

+70
-4
lines changed

fuzz/src/main.rs

Lines changed: 70 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,18 @@ struct Args {
2121
#[arg(long)]
2222
strict_hard_nan_sign: bool,
2323

24+
/// Disable erasure of "which NaN input propagates" mismatches with hardware floating-point operations
25+
#[arg(long)]
26+
strict_hard_nan_input_choice: bool,
27+
28+
/// Hide FMA NaN mismatches for `a * b + NaN` when `a * b` generates a new NaN
29+
// HACK(eddyb) this is opt-in, not opt-out, because the APFloat behavior, of
30+
// generating a new NaN (instead of propagating the existing one) is dubious,
31+
// and may end up changing over time, so the only purpose this serves is to
32+
// enable fuzzing against hardware without wasting time on these mismatches.
33+
#[arg(long)]
34+
ignore_fma_nan_generate_vs_propagate: bool,
35+
2436
#[command(subcommand)]
2537
command: Option<Commands>,
2638
}
@@ -309,12 +321,66 @@ where
309321
// Allow using CLI flags to toggle whether differences vs hardware are
310322
// erased (by copying e.g. signs from the `rustc_apfloat` result) or kept.
311323
// FIXME(eddyb) figure out how much we can really validate against hardware.
324+
let mut strict_nan_bits_mask = !0;
325+
if !cli_args.strict_hard_nan_sign {
326+
strict_nan_bits_mask &= !sign_bit_mask;
327+
};
328+
312329
let rs_apf_bits = out.rs_apf.to_bits_u128();
313330
if is_nan(out_hard_bits) && is_nan(rs_apf_bits) {
314-
for (strict, bit_mask) in [(cli_args.strict_hard_nan_sign, sign_bit_mask)] {
315-
if !strict {
316-
out_hard_bits &= !bit_mask;
317-
out_hard_bits |= rs_apf_bits & bit_mask;
331+
out_hard_bits &= strict_nan_bits_mask;
332+
out_hard_bits |= rs_apf_bits & !strict_nan_bits_mask;
333+
334+
// There is still a NaN payload difference, check if they both
335+
// are propagated inputs (verbatim or at most "quieted" if SNaN),
336+
// because in some cases with multiple NaN inputs, something
337+
// (hardware or even e.g. LLVM passes or instruction selection)
338+
// along the way from Rust code to final results, can end up
339+
// causing a different input NaN to get propagated to the result.
340+
if !cli_args.strict_hard_nan_input_choice && out_hard_bits != rs_apf_bits {
341+
let out_nan_is_propagated_input = |out_nan_bits| {
342+
assert!(is_nan(out_nan_bits));
343+
let mut found_any_matching_inputs = false;
344+
self.map(F::to_bits_u128).map(|in_bits| {
345+
// NOTE(eddyb) this `is_nan` check is important, as
346+
// `INFINITY.to_bits() | qnan_bit_mask == NAN.to_bits()`,
347+
// i.e. seeting the QNaN is more than enough to turn
348+
// a non-NaN (infinities, specifically) into a NaN.
349+
if is_nan(in_bits) {
350+
// Make sure to "quiet" (i.e. turn SNaN into QNaN)
351+
// the input first, as propagation does (in the
352+
// default exception handling mode, at least).
353+
if (in_bits | qnan_bit_mask) & strict_nan_bits_mask
354+
== out_nan_bits & strict_nan_bits_mask
355+
{
356+
found_any_matching_inputs = true;
357+
}
358+
}
359+
});
360+
found_any_matching_inputs
361+
};
362+
if out_nan_is_propagated_input(out_hard_bits)
363+
&& out_nan_is_propagated_input(rs_apf_bits)
364+
{
365+
out_hard_bits = rs_apf_bits;
366+
}
367+
}
368+
369+
// HACK(eddyb) last chance to hide a NaN payload difference,
370+
// in this case for FMAs of the form `a * b + NaN`, when `a * b`
371+
// generates a new NaN (which hardware can ignore in favor of the
372+
// existing NaN, but APFloat returns the fresh new NaN instead).
373+
if cli_args.ignore_fma_nan_generate_vs_propagate && out_hard_bits != rs_apf_bits {
374+
if let FuzzOp::MulAdd(a, b, c) = self.map(F::to_bits_u128) {
375+
if !is_nan(a)
376+
&& !is_nan(b)
377+
&& is_nan(c)
378+
&& out_hard_bits & strict_nan_bits_mask
379+
== (c | qnan_bit_mask) & strict_nan_bits_mask
380+
&& rs_apf_bits == F::RustcApFloat::NAN.to_bits()
381+
{
382+
out_hard_bits = rs_apf_bits;
383+
}
318384
}
319385
}
320386
}

0 commit comments

Comments
 (0)