@@ -21,6 +21,18 @@ struct Args {
21
21
#[ arg( long) ]
22
22
strict_hard_nan_sign : bool ,
23
23
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
+
24
36
#[ command( subcommand) ]
25
37
command : Option < Commands > ,
26
38
}
@@ -309,12 +321,66 @@ where
309
321
// Allow using CLI flags to toggle whether differences vs hardware are
310
322
// erased (by copying e.g. signs from the `rustc_apfloat` result) or kept.
311
323
// 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
+
312
329
let rs_apf_bits = out. rs_apf . to_bits_u128 ( ) ;
313
330
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
+ }
318
384
}
319
385
}
320
386
}
0 commit comments