@@ -6,6 +6,7 @@ mod simd;
6
6
use std:: ops:: Neg ;
7
7
8
8
use rand:: Rng ;
9
+ use rand:: rngs:: StdRng ;
9
10
use rustc_abi:: Size ;
10
11
use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
11
12
use rustc_apfloat:: { self , Float , Round } ;
@@ -191,7 +192,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
191
192
let [ f] = check_intrinsic_arg_count ( args) ?;
192
193
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
193
194
194
- let res = fixed_float_value ( intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
195
+ let res = fixed_float_value ( this , intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
195
196
// Using host floats (but it's fine, these operations do not have
196
197
// guaranteed precision).
197
198
let host = f. to_host ( ) ;
@@ -235,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
235
236
let [ f] = check_intrinsic_arg_count ( args) ?;
236
237
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
237
238
238
- let res = fixed_float_value ( intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
239
+ let res = fixed_float_value ( this , intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
239
240
// Using host floats (but it's fine, these operations do not have
240
241
// guaranteed precision).
241
242
let host = f. to_host ( ) ;
@@ -324,7 +325,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
324
325
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
325
326
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
326
327
327
- let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
328
+ let res = fixed_float_value ( this , intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
328
329
// Using host floats (but it's fine, this operation does not have guaranteed precision).
329
330
let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
330
331
@@ -342,7 +343,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
342
343
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
343
344
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
344
345
345
- let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
346
+ let res = fixed_float_value ( this , intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
346
347
// Using host floats (but it's fine, this operation does not have guaranteed precision).
347
348
let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
348
349
@@ -501,45 +502,76 @@ fn apply_random_float_error_to_imm<'tcx>(
501
502
interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
502
503
}
503
504
505
+ /// Returns either a SNaN or a QNaN, with a randomly generated payload.
506
+ fn random_nan < S : Semantics > ( rng : & mut StdRng ) -> IeeeFloat < S > {
507
+ if rng. random ( ) {
508
+ IeeeFloat :: < S > :: snan ( Some ( rng. random ( ) ) )
509
+ } else {
510
+ IeeeFloat :: < S > :: qnan ( Some ( rng. random ( ) ) )
511
+ }
512
+ }
513
+
504
514
/// For the intrinsics:
505
515
/// - sinf32, sinf64
506
516
/// - cosf32, cosf64
507
517
/// - expf32, expf64, exp2f32, exp2f64
508
518
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
509
519
/// - powf32, powf64
510
520
///
521
+ /// # Note
522
+ ///
523
+ /// For `powf*` operations of the form:
524
+ ///
525
+ /// - `x^(±0)` where `x` is a SNaN
526
+ /// - `1^y` where `y` is SNaN
527
+ ///
528
+ /// The result is implementation-defined:
529
+ /// - musl returns for both `1.0`
530
+ /// - glibc returns for both `NaN`
531
+ ///
532
+ /// This discrepancy exists because SNaN handling is not consistently defined across platforms,
533
+ /// and the C standard leaves behavior for SNaNs unspecified.
534
+ ///
535
+ /// # Return
536
+ ///
511
537
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
512
538
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
513
539
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
514
540
/// implementation. Returns `None` if no specific value is guaranteed.
515
- fn fixed_float_value < S : Semantics > (
541
+ fn fixed_float_value < ' tcx , S : Semantics > (
542
+ ecx : & mut MiriInterpCx < ' tcx > ,
516
543
intrinsic_name : & str ,
517
544
args : & [ IeeeFloat < S > ] ,
518
545
) -> Option < IeeeFloat < S > > {
519
546
let one = IeeeFloat :: < S > :: one ( ) ;
520
- match ( intrinsic_name, args) {
547
+ Some ( match ( intrinsic_name, args) {
521
548
// cos(+- 0) = 1
522
- ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
549
+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => one,
523
550
524
551
// e^0 = 1
525
- ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
552
+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => one,
526
553
527
- // 1^y = 1 for any y, even a NaN.
528
- ( "powf32" | "powf64" , [ base, _] ) if * base == one => Some ( one) ,
554
+ // 1^y = 1 for any y, even a NaN
555
+ ( "powf32" | "powf64" , [ base, _] ) if * base == one => one,
529
556
530
557
// (-1)^(±INF) = 1
531
- ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => Some ( one) ,
532
-
533
- // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
534
- // the NaN. We should return either 1 or the NaN non-deterministically here.
535
- // But for now, just handle them all the same.
536
- // x^(±0) = 1 for any x, even a NaN
537
- ( "powf32" | "powf64" , [ _, exp] ) if exp. is_zero ( ) => Some ( one) ,
558
+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => one,
559
+
560
+ // x^(±0) = 1 for any x, even a NaN, *but* not a SNaN
561
+ ( "powf32" | "powf64" , [ base, exp] ) if exp. is_zero ( ) => {
562
+ // Handle both the musl and glibc cases non-deterministically.
563
+ if base. is_signaling ( ) {
564
+ let rng = ecx. machine . rng . get_mut ( ) ;
565
+ if rng. random ( ) { one } else { random_nan ( rng) }
566
+ } else {
567
+ one
568
+ }
569
+ }
538
570
539
571
// There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
540
572
// which are not affected by the applied error.
541
- _ => None ,
542
- }
573
+ _ => return None ,
574
+ } )
543
575
}
544
576
545
577
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
0 commit comments