33mod atomic;
44mod simd;
55
6+ use std:: ops:: Neg ;
7+
68use rand:: Rng ;
79use rustc_abi:: Size ;
10+ use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
811use rustc_apfloat:: { self , Float , Round } ;
912use rustc_middle:: mir;
10- use rustc_middle:: ty:: { self , FloatTy } ;
13+ use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
1114use rustc_span:: { Symbol , sym} ;
1215
1316use self :: atomic:: EvalContextExt as _;
1417use self :: helpers:: { ToHost , ToSoft } ;
1518use self :: simd:: EvalContextExt as _;
19+ use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
1620use crate :: * ;
1721
1822/// Check that the number of args is what we expect.
@@ -205,7 +209,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
205209 let [ f] = check_intrinsic_arg_count ( args) ?;
206210 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
207211
208- let res = math :: fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
212+ let res = fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
209213 // Using host floats (but it's fine, these operations do not have
210214 // guaranteed precision).
211215 let host = f. to_host ( ) ;
@@ -223,15 +227,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223227
224228 // Apply a relative error of 4ULP to introduce some non-determinism
225229 // simulating imprecise implementations and optimizations.
226- let res = math :: apply_random_float_error_ulp (
230+ let res = apply_random_float_error_ulp (
227231 this,
228232 res,
229233 2 , // log2(4)
230234 ) ;
231235
232236 // Clamp the result to the guaranteed range of this function according to the C standard,
233237 // if any.
234- math :: clamp_float_value ( intrinsic_name, res)
238+ clamp_float_value ( intrinsic_name, res)
235239 } ) ;
236240 let res = this. adjust_nan ( res, & [ f] ) ;
237241 this. write_scalar ( res, dest) ?;
@@ -249,7 +253,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
249253 let [ f] = check_intrinsic_arg_count ( args) ?;
250254 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
251255
252- let res = math :: fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
256+ let res = fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
253257 // Using host floats (but it's fine, these operations do not have
254258 // guaranteed precision).
255259 let host = f. to_host ( ) ;
@@ -267,15 +271,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
267271
268272 // Apply a relative error of 4ULP to introduce some non-determinism
269273 // simulating imprecise implementations and optimizations.
270- let res = math :: apply_random_float_error_ulp (
274+ let res = apply_random_float_error_ulp (
271275 this,
272276 res,
273277 2 , // log2(4)
274278 ) ;
275279
276280 // Clamp the result to the guaranteed range of this function according to the C standard,
277281 // if any.
278- math :: clamp_float_value ( intrinsic_name, res)
282+ clamp_float_value ( intrinsic_name, res)
279283 } ) ;
280284 let res = this. adjust_nan ( res, & [ f] ) ;
281285 this. write_scalar ( res, dest) ?;
@@ -326,17 +330,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
326330 let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
327331 let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
328332
329- let res =
330- math:: fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
331- // Using host floats (but it's fine, this operation does not have guaranteed precision).
332- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
333+ let res = fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
334+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
335+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
333336
334- // Apply a relative error of 4ULP to introduce some non-determinism
335- // simulating imprecise implementations and optimizations.
336- math :: apply_random_float_error_ulp (
337- this, res, 2 , // log2(4)
338- )
339- } ) ;
337+ // Apply a relative error of 4ULP to introduce some non-determinism
338+ // simulating imprecise implementations and optimizations.
339+ apply_random_float_error_ulp (
340+ this, res, 2 , // log2(4)
341+ )
342+ } ) ;
340343 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
341344 this. write_scalar ( res, dest) ?;
342345 }
@@ -345,17 +348,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
345348 let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
346349 let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
347350
348- let res =
349- math:: fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
350- // Using host floats (but it's fine, this operation does not have guaranteed precision).
351- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
351+ let res = fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
352+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
353+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
352354
353- // Apply a relative error of 4ULP to introduce some non-determinism
354- // simulating imprecise implementations and optimizations.
355- math :: apply_random_float_error_ulp (
356- this, res, 2 , // log2(4)
357- )
358- } ) ;
355+ // Apply a relative error of 4ULP to introduce some non-determinism
356+ // simulating imprecise implementations and optimizations.
357+ apply_random_float_error_ulp (
358+ this, res, 2 , // log2(4)
359+ )
360+ } ) ;
359361 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
360362 this. write_scalar ( res, dest) ?;
361363 }
@@ -365,13 +367,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
365367 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
366368 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
367369
368- let res = math :: fixed_powi_value ( this, f, i) . unwrap_or_else ( || {
370+ let res = fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
369371 // Using host floats (but it's fine, this operation does not have guaranteed precision).
370372 let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
371373
372374 // Apply a relative error of 4ULP to introduce some non-determinism
373375 // simulating imprecise implementations and optimizations.
374- math :: apply_random_float_error_ulp (
376+ apply_random_float_error_ulp (
375377 this, res, 2 , // log2(4)
376378 )
377379 } ) ;
@@ -383,13 +385,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
383385 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
384386 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
385387
386- let res = math :: fixed_powi_value ( this, f, i) . unwrap_or_else ( || {
388+ let res = fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
387389 // Using host floats (but it's fine, this operation does not have guaranteed precision).
388390 let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
389391
390392 // Apply a relative error of 4ULP to introduce some non-determinism
391393 // simulating imprecise implementations and optimizations.
392- math :: apply_random_float_error_ulp (
394+ apply_random_float_error_ulp (
393395 this, res, 2 , // log2(4)
394396 )
395397 } ) ;
@@ -446,7 +448,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
446448 }
447449 // Apply a relative error of 4ULP to simulate non-deterministic precision loss
448450 // due to optimizations.
449- let res = math :: apply_random_float_error_to_imm ( this, res, 2 /* log2(4) */ ) ?;
451+ let res = apply_random_float_error_to_imm ( this, res, 2 /* log2(4) */ ) ?;
450452 this. write_immediate ( * res, dest) ?;
451453 }
452454
@@ -483,3 +485,133 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483485 interp_ok ( EmulateItemResult :: NeedsReturn )
484486 }
485487}
488+
489+ /// Applies a random ULP floating point error to `val` and returns the new value.
490+ /// So if you want an X ULP error, `ulp_exponent` should be log2(X).
491+ ///
492+ /// Will fail if `val` is not a floating point number.
493+ fn apply_random_float_error_to_imm < ' tcx > (
494+ ecx : & mut MiriInterpCx < ' tcx > ,
495+ val : ImmTy < ' tcx > ,
496+ ulp_exponent : u32 ,
497+ ) -> InterpResult < ' tcx , ImmTy < ' tcx > > {
498+ let scalar = val. to_scalar_int ( ) ?;
499+ let res: ScalarInt = match val. layout . ty . kind ( ) {
500+ ty:: Float ( FloatTy :: F16 ) =>
501+ apply_random_float_error_ulp ( ecx, scalar. to_f16 ( ) , ulp_exponent) . into ( ) ,
502+ ty:: Float ( FloatTy :: F32 ) =>
503+ apply_random_float_error_ulp ( ecx, scalar. to_f32 ( ) , ulp_exponent) . into ( ) ,
504+ ty:: Float ( FloatTy :: F64 ) =>
505+ apply_random_float_error_ulp ( ecx, scalar. to_f64 ( ) , ulp_exponent) . into ( ) ,
506+ ty:: Float ( FloatTy :: F128 ) =>
507+ apply_random_float_error_ulp ( ecx, scalar. to_f128 ( ) , ulp_exponent) . into ( ) ,
508+ _ => bug ! ( "intrinsic called with non-float input type" ) ,
509+ } ;
510+
511+ interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
512+ }
513+
514+ /// For the intrinsics:
515+ /// - sinf32, sinf64
516+ /// - cosf32, cosf64
517+ /// - expf32, expf64, exp2f32, exp2f64
518+ /// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
519+ /// - powf32, powf64
520+ ///
521+ /// # Return
522+ ///
523+ /// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
524+ /// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
525+ /// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
526+ /// implementation. Returns `None` if no specific value is guaranteed.
527+ ///
528+ /// # Note
529+ ///
530+ /// For `powf*` operations of the form:
531+ ///
532+ /// - `(SNaN)^(±0)`
533+ /// - `1^(SNaN)`
534+ ///
535+ /// The result is implementation-defined:
536+ /// - musl returns for both `1.0`
537+ /// - glibc returns for both `NaN`
538+ ///
539+ /// This discrepancy exists because SNaN handling is not consistently defined across platforms,
540+ /// and the C standard leaves behavior for SNaNs unspecified.
541+ ///
542+ /// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
543+ fn fixed_float_value < S : Semantics > (
544+ ecx : & mut MiriInterpCx < ' _ > ,
545+ intrinsic_name : & str ,
546+ args : & [ IeeeFloat < S > ] ,
547+ ) -> Option < IeeeFloat < S > > {
548+ let one = IeeeFloat :: < S > :: one ( ) ;
549+ Some ( match ( intrinsic_name, args) {
550+ // cos(+- 0) = 1
551+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => one,
552+
553+ // e^0 = 1
554+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => one,
555+
556+ // (-1)^(±INF) = 1
557+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => one,
558+
559+ // 1^y = 1 for any y, even a NaN
560+ ( "powf32" | "powf64" , [ base, exp] ) if * base == one => {
561+ let rng = ecx. machine . rng . get_mut ( ) ;
562+ // SNaN exponents get special treatment: they might return 1, or a NaN.
563+ let return_nan = exp. is_signaling ( ) && ecx. machine . float_nondet && rng. random ( ) ;
564+ // Handle both the musl and glibc cases non-deterministically.
565+ if return_nan { ecx. generate_nan ( args) } else { one }
566+ }
567+
568+ // x^(±0) = 1 for any x, even a NaN
569+ ( "powf32" | "powf64" , [ base, exp] ) if exp. is_zero ( ) => {
570+ let rng = ecx. machine . rng . get_mut ( ) ;
571+ // SNaN bases get special treatment: they might return 1, or a NaN.
572+ let return_nan = base. is_signaling ( ) && ecx. machine . float_nondet && rng. random ( ) ;
573+ // Handle both the musl and glibc cases non-deterministically.
574+ if return_nan { ecx. generate_nan ( args) } else { one }
575+ }
576+
577+ // There are a lot of cases for fixed outputs according to the C Standard, but these are
578+ // mainly INF or zero which are not affected by the applied error.
579+ _ => return None ,
580+ } )
581+ }
582+
583+ /// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
584+ /// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
585+ fn fixed_powi_float_value < S : Semantics > (
586+ ecx : & mut MiriInterpCx < ' _ > ,
587+ base : IeeeFloat < S > ,
588+ exp : i32 ,
589+ ) -> Option < IeeeFloat < S > > {
590+ Some ( match exp {
591+ 0 => {
592+ let one = IeeeFloat :: < S > :: one ( ) ;
593+ let rng = ecx. machine . rng . get_mut ( ) ;
594+ let return_nan = ecx. machine . float_nondet && rng. random ( ) && base. is_signaling ( ) ;
595+ // For SNaN treatment, we are consistent with `powf`above.
596+ // (We wouldn't have two, unlike powf all implementations seem to agree for powi,
597+ // but for now we are maximally conservative.)
598+ if return_nan { ecx. generate_nan ( & [ base] ) } else { one }
599+ }
600+
601+ _ => return None ,
602+ } )
603+ }
604+
605+ /// Given an floating-point operation and a floating-point value, clamps the result to the output
606+ /// range of the given operation.
607+ fn clamp_float_value < S : Semantics > ( intrinsic_name : & str , val : IeeeFloat < S > ) -> IeeeFloat < S > {
608+ match intrinsic_name {
609+ // sin and cos: [-1, 1]
610+ "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
611+ val. clamp ( IeeeFloat :: < S > :: one ( ) . neg ( ) , IeeeFloat :: < S > :: one ( ) ) ,
612+ // exp: [0, +INF]
613+ "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
614+ IeeeFloat :: < S > :: maximum ( val, IeeeFloat :: < S > :: ZERO ) ,
615+ _ => val,
616+ }
617+ }
0 commit comments