@@ -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 ( ) ;
@@ -312,7 +313,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
312
313
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
313
314
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
314
315
315
- let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
316
+ let res = fixed_float_value ( this , intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
316
317
// Using host floats (but it's fine, this operation does not have guaranteed precision).
317
318
let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
318
319
@@ -330,7 +331,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
330
331
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
331
332
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
332
333
333
- let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
334
+ let res = fixed_float_value ( this , intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
334
335
// Using host floats (but it's fine, this operation does not have guaranteed precision).
335
336
let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
336
337
@@ -349,7 +350,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
349
350
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
350
351
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
351
352
352
- let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
353
+ let res = fixed_powi_float_value ( this , f, i) . unwrap_or_else ( || {
353
354
// Using host floats (but it's fine, this operation does not have guaranteed precision).
354
355
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
355
356
@@ -367,7 +368,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
367
368
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
368
369
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
369
370
370
- let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
371
+ let res = fixed_powi_float_value ( this , f, i) . unwrap_or_else ( || {
371
372
// Using host floats (but it's fine, this operation does not have guaranteed precision).
372
373
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
373
374
@@ -489,56 +490,97 @@ fn apply_random_float_error_to_imm<'tcx>(
489
490
interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
490
491
}
491
492
493
+ /// Returns either a SNaN or a QNaN, with a randomly generated payload.
494
+ fn random_nan < S : Semantics > ( rng : & mut StdRng ) -> IeeeFloat < S > {
495
+ if rng. random ( ) {
496
+ IeeeFloat :: < S > :: snan ( Some ( rng. random ( ) ) )
497
+ } else {
498
+ IeeeFloat :: < S > :: qnan ( Some ( rng. random ( ) ) )
499
+ }
500
+ }
501
+
492
502
/// For the intrinsics:
493
503
/// - sinf32, sinf64
494
504
/// - cosf32, cosf64
495
505
/// - expf32, expf64, exp2f32, exp2f64
496
506
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
497
507
/// - powf32, powf64
498
508
///
509
+ /// # Return
510
+ ///
499
511
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
500
512
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
501
513
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
502
514
/// implementation. Returns `None` if no specific value is guaranteed.
515
+ ///
516
+ /// # Note
517
+ ///
518
+ /// For `powf*` operations of the form:
519
+ ///
520
+ /// - `(SNaN)^(±0)`
521
+ /// - `1^(SNaN)`
522
+ ///
523
+ /// The result is implementation-defined:
524
+ /// - musl returns for both `1.0`
525
+ /// - glibc returns for both `NaN`
526
+ ///
527
+ /// This discrepancy exists because SNaN handling is not consistently defined across platforms,
528
+ /// and the C standard leaves behavior for SNaNs unspecified.
529
+ ///
530
+ /// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
503
531
fn fixed_float_value < S : Semantics > (
532
+ ecx : & mut MiriInterpCx < ' _ > ,
504
533
intrinsic_name : & str ,
505
534
args : & [ IeeeFloat < S > ] ,
506
535
) -> Option < IeeeFloat < S > > {
507
536
let one = IeeeFloat :: < S > :: one ( ) ;
508
- match ( intrinsic_name, args) {
537
+ Some ( match ( intrinsic_name, args) {
509
538
// cos(+- 0) = 1
510
- ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
539
+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => one,
511
540
512
541
// e^0 = 1
513
- ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
514
-
515
- // 1^y = 1 for any y, even a NaN.
516
- ( "powf32" | "powf64" , [ base, _] ) if * base == one => Some ( one) ,
542
+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => one,
517
543
518
544
// (-1)^(±INF) = 1
519
- ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => Some ( one) ,
545
+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => one,
520
546
521
- // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
522
- // the NaN. We should return either 1 or the NaN non-deterministically here.
523
- // But for now, just handle them all the same.
524
- // x^(±0) = 1 for any x, even a NaN
525
- ( "powf32" | "powf64" , [ _, exp] ) if exp. is_zero ( ) => Some ( one) ,
547
+ // 1^y = 1 for any y, even a NaN, *but* not a SNaN
548
+ ( "powf32" | "powf64" , [ base, exp] ) if * base == one => {
549
+ let rng = ecx. machine . rng . get_mut ( ) ;
550
+ // Handle both the musl and glibc cases non-deterministically.
551
+ if !exp. is_signaling ( ) || rng. random ( ) { one } else { random_nan ( rng) }
552
+ }
553
+
554
+ // x^(±0) = 1 for any x, even a NaN, *but* not a SNaN
555
+ ( "powf32" | "powf64" , [ base, exp] ) if exp. is_zero ( ) => {
556
+ let rng = ecx. machine . rng . get_mut ( ) ;
557
+ // Handle both the musl and glibc cases non-deterministically.
558
+ if !base. is_signaling ( ) || rng. random ( ) { one } else { random_nan ( rng) }
559
+ }
526
560
527
561
// There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
528
562
// which are not affected by the applied error.
529
- _ => None ,
530
- }
563
+ _ => return None ,
564
+ } )
531
565
}
532
566
533
567
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
534
568
/// (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
535
- fn fixed_powi_float_value < S : Semantics > ( base : IeeeFloat < S > , exp : i32 ) -> Option < IeeeFloat < S > > {
536
- match ( base. category ( ) , exp) {
537
- // x^0 = 1, if x is not a Signaling NaN
538
- // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
539
- // the NaN. We should return either 1 or the NaN non-deterministically here.
540
- // But for now, just handle them all the same.
541
- ( _, 0 ) => Some ( IeeeFloat :: < S > :: one ( ) ) ,
569
+ // TODO: I'm not sure what I should document here about pown(1, SNaN) since musl and glibc do the same and the C standard is explicit here.
570
+ fn fixed_powi_float_value < S : Semantics > (
571
+ ecx : & mut MiriInterpCx < ' _ > ,
572
+ base : IeeeFloat < S > ,
573
+ exp : i32 ,
574
+ ) -> Option < IeeeFloat < S > > {
575
+ match exp {
576
+ 0 => {
577
+ let one = IeeeFloat :: < S > :: one ( ) ;
578
+ let rng = ecx. machine . rng . get_mut ( ) ;
579
+ Some (
580
+ // Handle both the musl and glibc powf cases non-deterministically.
581
+ if !base. is_signaling ( ) || rng. random ( ) { one } else { random_nan ( rng) } ,
582
+ )
583
+ }
542
584
543
585
_ => None ,
544
586
}
0 commit comments