@@ -6,6 +6,7 @@ mod simd;
66use std:: ops:: Neg ;
77
88use rand:: Rng ;
9+ use rand:: rngs:: StdRng ;
910use rustc_abi:: Size ;
1011use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
1112use rustc_apfloat:: { self , Float , Round } ;
@@ -191,7 +192,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
191192 let [ f] = check_intrinsic_arg_count ( args) ?;
192193 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
193194
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 ( ||{
195196 // Using host floats (but it's fine, these operations do not have
196197 // guaranteed precision).
197198 let host = f. to_host ( ) ;
@@ -235,7 +236,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
235236 let [ f] = check_intrinsic_arg_count ( args) ?;
236237 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
237238
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 ( ||{
239240 // Using host floats (but it's fine, these operations do not have
240241 // guaranteed precision).
241242 let host = f. to_host ( ) ;
@@ -312,7 +313,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
312313 let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
313314 let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
314315
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 ( || {
316317 // Using host floats (but it's fine, this operation does not have guaranteed precision).
317318 let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
318319
@@ -330,7 +331,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
330331 let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
331332 let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
332333
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 ( || {
334335 // Using host floats (but it's fine, this operation does not have guaranteed precision).
335336 let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
336337
@@ -489,45 +490,76 @@ fn apply_random_float_error_to_imm<'tcx>(
489490 interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
490491}
491492
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+
492502/// For the intrinsics:
493503/// - sinf32, sinf64
494504/// - cosf32, cosf64
495505/// - expf32, expf64, exp2f32, exp2f64
496506/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
497507/// - powf32, powf64
498508///
509+ /// # Note
510+ ///
511+ /// For `powf*` operations of the form:
512+ ///
513+ /// - `x^(±0)` where `x` is a SNaN
514+ /// - `1^y` where `y` is SNaN
515+ ///
516+ /// The result is implementation-defined:
517+ /// - musl returns for both `1.0`
518+ /// - glibc returns for both `NaN`
519+ ///
520+ /// This discrepancy exists because SNaN handling is not consistently defined across platforms,
521+ /// and the C standard leaves behavior for SNaNs unspecified.
522+ ///
523+ /// # Return
524+ ///
499525/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
500526/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
501527/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
502528/// implementation. Returns `None` if no specific value is guaranteed.
503- fn fixed_float_value < S : Semantics > (
529+ fn fixed_float_value < ' tcx , S : Semantics > (
530+ ecx : & mut MiriInterpCx < ' tcx > ,
504531 intrinsic_name : & str ,
505532 args : & [ IeeeFloat < S > ] ,
506533) -> Option < IeeeFloat < S > > {
507534 let one = IeeeFloat :: < S > :: one ( ) ;
508- match ( intrinsic_name, args) {
535+ Some ( match ( intrinsic_name, args) {
509536 // cos(+- 0) = 1
510- ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
537+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => one,
511538
512539 // e^0 = 1
513- ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
540+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => one,
514541
515- // 1^y = 1 for any y, even a NaN.
516- ( "powf32" | "powf64" , [ base, _] ) if * base == one => Some ( one) ,
542+ // 1^y = 1 for any y, even a NaN
543+ ( "powf32" | "powf64" , [ base, _] ) if * base == one => one,
517544
518545 // (-1)^(±INF) = 1
519- ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => Some ( one) ,
520-
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) ,
546+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => one,
547+
548+ // x^(±0) = 1 for any x, even a NaN, *but* not a SNaN
549+ ( "powf32" | "powf64" , [ base, exp] ) if exp. is_zero ( ) => {
550+ // Handle both the musl and glibc cases non-deterministically.
551+ if base. is_signaling ( ) {
552+ let rng = ecx. machine . rng . get_mut ( ) ;
553+ if rng. random ( ) { one } else { random_nan ( rng) }
554+ } else {
555+ one
556+ }
557+ }
526558
527559 // There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
528560 // which are not affected by the applied error.
529- _ => None ,
530- }
561+ _ => return None ,
562+ } )
531563}
532564
533565/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
0 commit comments