33mod atomic;
44mod simd;
55
6+ use std:: ops:: Neg ;
7+
68use rand:: Rng ;
79use rustc_abi:: Size ;
8- use rustc_apfloat:: { Float , Round } ;
10+ use rustc_apfloat:: ieee:: { Double , IeeeFloat , Semantics , Single } ;
11+ use rustc_apfloat:: { self , Category , Float , Round } ;
912use rustc_middle:: mir;
1013use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
1114use rustc_span:: { Symbol , sym} ;
1215
1316use self :: atomic:: EvalContextExt as _;
1417use self :: helpers:: { ToHost , ToSoft , check_intrinsic_arg_count} ;
1518use self :: simd:: EvalContextExt as _;
16- use crate :: math:: apply_random_float_error_ulp;
19+ use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
1720use crate :: * ;
1821
1922impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
@@ -235,30 +238,42 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
235238 => {
236239 let [ f] = check_intrinsic_arg_count ( args) ?;
237240 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
238- // Using host floats (but it's fine, these operations do not have
239- // guaranteed precision).
240- let host = f. to_host ( ) ;
241- let res = match intrinsic_name {
242- "sinf32" => host. sin ( ) ,
243- "cosf32" => host. cos ( ) ,
244- "expf32" => host. exp ( ) ,
245- "exp2f32" => host. exp2 ( ) ,
246- "logf32" => host. ln ( ) ,
247- "log10f32" => host. log10 ( ) ,
248- "log2f32" => host. log2 ( ) ,
249- _ => bug ! ( ) ,
250- } ;
251- let res = res. to_soft ( ) ;
252- // Apply a relative error of 16ULP to introduce some non-determinism
253- // simulating imprecise implementations and optimizations.
254- // FIXME: temporarily disabled as it breaks std tests.
255- // let res = apply_random_float_error_ulp(
256- // this,
257- // res,
258- // 4, // log2(16)
259- // );
241+
242+ let res = fixed_float_value ( intrinsic_name, f) . unwrap_or_else ( ||{
243+ // Using host floats (but it's fine, these operations do not have
244+ // guaranteed precision).
245+ let host = f. to_host ( ) ;
246+ let res = match intrinsic_name {
247+ "sinf32" => host. sin ( ) ,
248+ "cosf32" => host. cos ( ) ,
249+ "expf32" => host. exp ( ) ,
250+ "exp2f32" => host. exp2 ( ) ,
251+ "logf32" => host. ln ( ) ,
252+ "log10f32" => host. log10 ( ) ,
253+ "log2f32" => host. log2 ( ) ,
254+ _ => bug ! ( ) ,
255+ } ;
256+ let res = res. to_soft ( ) ;
257+
258+ // Apply a relative error of 4ULP to introduce some non-determinism
259+ // simulating imprecise implementations and optimizations.
260+ let res = apply_random_float_error_ulp (
261+ this,
262+ res,
263+ 2 , // log2(4)
264+ ) ;
265+
266+ match intrinsic_name {
267+ // sin and cos: [-1, 1]
268+ "sinf32" | "cosf32" => res. clamp ( Single :: one ( ) . neg ( ) , Single :: one ( ) ) ,
269+ // exp: [0, +INF]
270+ "expf32" | "exp2f32" => res. maximum ( Single :: ZERO ) ,
271+ _ => res,
272+ }
273+ } ) ;
260274 let res = this. adjust_nan ( res, & [ f] ) ;
261275 this. write_scalar ( res, dest) ?;
276+
262277 }
263278 #[ rustfmt:: skip]
264279 | "sinf64"
@@ -271,30 +286,43 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
271286 => {
272287 let [ f] = check_intrinsic_arg_count ( args) ?;
273288 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
274- // Using host floats (but it's fine, these operations do not have
275- // guaranteed precision).
276- let host = f. to_host ( ) ;
277- let res = match intrinsic_name {
278- "sinf64" => host. sin ( ) ,
279- "cosf64" => host. cos ( ) ,
280- "expf64" => host. exp ( ) ,
281- "exp2f64" => host. exp2 ( ) ,
282- "logf64" => host. ln ( ) ,
283- "log10f64" => host. log10 ( ) ,
284- "log2f64" => host. log2 ( ) ,
285- _ => bug ! ( ) ,
286- } ;
287- let res = res. to_soft ( ) ;
288- // Apply a relative error of 16ULP to introduce some non-determinism
289- // simulating imprecise implementations and optimizations.
290- // FIXME: temporarily disabled as it breaks std tests.
291- // let res = apply_random_float_error_ulp(
292- // this,
293- // res,
294- // 4, // log2(16)
295- // );
289+
290+ let res = fixed_float_value ( intrinsic_name, f) . unwrap_or_else ( ||{
291+ // Using host floats (but it's fine, these operations do not have
292+ // guaranteed precision).
293+ let host = f. to_host ( ) ;
294+ let res = match intrinsic_name {
295+ "sinf64" => host. sin ( ) ,
296+ "cosf64" => host. cos ( ) ,
297+ "expf64" => host. exp ( ) ,
298+ "exp2f64" => host. exp2 ( ) ,
299+ "logf64" => host. ln ( ) ,
300+ "log10f64" => host. log10 ( ) ,
301+ "log2f64" => host. log2 ( ) ,
302+ _ => bug ! ( ) ,
303+ } ;
304+ let res = res. to_soft ( ) ;
305+
306+ // Apply a relative error of 4ULP to introduce some non-determinism
307+ // simulating imprecise implementations and optimizations.
308+ let res = apply_random_float_error_ulp (
309+ this,
310+ res,
311+ 2 , // log2(4)
312+ ) ;
313+
314+ // Clamp values to the output range defined in IEEE 754 9.1.
315+ match intrinsic_name {
316+ // sin and cos: [-1, 1]
317+ "sinf64" | "cosf64" => res. clamp ( Double :: one ( ) . neg ( ) , Double :: one ( ) ) ,
318+ // exp: [0, +INF]
319+ "expf64" | "exp2f64" => res. maximum ( Double :: ZERO ) ,
320+ _ => res,
321+ }
322+ } ) ;
296323 let res = this. adjust_nan ( res, & [ f] ) ;
297324 this. write_scalar ( res, dest) ?;
325+
298326 }
299327
300328 "fmaf32" => {
@@ -350,43 +378,126 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
350378 }
351379
352380 "powf32" => {
353- // FIXME: apply random relative error but without altering behaviour of powf
354381 let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
355382 let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
356383 let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
357- // Using host floats (but it's fine, this operation does not have guaranteed precision).
358- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
384+
385+ let fixed_res = match ( f1. category ( ) , f2. category ( ) ) {
386+ // 1^y = 1 for any y even a NaN.
387+ // TODO: C Standard says any NaN, IEEE says not a Signaling NaN
388+ ( Category :: Normal , _) if f1 == 1.0f32 . to_soft ( ) => Some ( 1.0f32 . to_soft ( ) ) ,
389+
390+ // (-1)^(±INF) = 1
391+ ( Category :: Normal , Category :: Infinity ) if f1 == ( -1.0f32 ) . to_soft ( ) =>
392+ Some ( 1.0f32 . to_soft ( ) ) ,
393+
394+ // x^(±0) = 1 for any x, even a NaN
395+ ( _, Category :: Zero ) => Some ( 1.0f32 . to_soft ( ) ) ,
396+
397+ _ => None ,
398+ } ;
399+ let res = fixed_res. unwrap_or_else ( || {
400+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
401+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
402+ // Apply a relative error of 4ULP to introduce some non-determinism
403+ // simulating imprecise implementations and optimizations.
404+ apply_random_float_error_ulp (
405+ this, res, 2 , // log2(4)
406+ )
407+ } ) ;
359408 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
360409 this. write_scalar ( res, dest) ?;
361410 }
362411 "powf64" => {
363- // FIXME: apply random relative error but without altering behaviour of powf
364412 let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
365413 let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
366414 let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
367- // Using host floats (but it's fine, this operation does not have guaranteed precision).
368- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
415+
416+ let fixed_res = match ( f1. category ( ) , f2. category ( ) ) {
417+ // 1^y = 1 for any y even a NaN.
418+ // TODO: C says any NaN, IEEE says no a Sign NaN
419+ ( Category :: Normal , _) if f1 == 1.0f64 . to_soft ( ) => Some ( 1.0f64 . to_soft ( ) ) ,
420+
421+ // (-1)^(±INF) = 1
422+ ( Category :: Normal , Category :: Infinity ) if f1 == ( -1.0f64 ) . to_soft ( ) =>
423+ Some ( 1.0f64 . to_soft ( ) ) ,
424+
425+ // x^(±0) = 1 for any x, even a NaN
426+ ( _, Category :: Zero ) => Some ( 1.0f64 . to_soft ( ) ) ,
427+
428+ // TODO: pow has a lot of "edge" cases which mostly result in ±0 or ±INF
429+ // do we have to catch them all?
430+ _ => None ,
431+ } ;
432+ let res = fixed_res. unwrap_or_else ( || {
433+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
434+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
435+ // Apply a relative error of 4ULP to introduce some non-determinism
436+ // simulating imprecise implementations and optimizations.
437+ apply_random_float_error_ulp (
438+ this, res, 2 , // log2(4)
439+ )
440+ } ) ;
369441 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
370442 this. write_scalar ( res, dest) ?;
371443 }
372444
373445 "powif32" => {
374- // FIXME: apply random relative error but without altering behaviour of powi
375446 let [ f, i] = check_intrinsic_arg_count ( args) ?;
376447 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
377448 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
378- // Using host floats (but it's fine, this operation does not have guaranteed precision).
379- let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
449+
450+ let fixed_res = match ( f. category ( ) , i) {
451+ // Standard specifies we can only fix to 1 if input is is not a Signaling NaN.
452+ ( _, 0 ) if !f. is_signaling ( ) => Some ( 1.0f32 . to_soft ( ) ) ,
453+
454+ // TODO: Isn't this done by the implementation? And ULP error on 0.0 doesn't have an effect
455+ ( Category :: Zero , x) if x % 2 == 0 => Some ( 0.0f32 . to_soft ( ) ) ,
456+
457+ // ±0^x = ±0 with x an odd integer.
458+ ( Category :: Zero , x) if x % 2 != 0 => Some ( f) , // preserve sign of zero.
459+ _ => None ,
460+ } ;
461+ let res = fixed_res. unwrap_or_else ( || {
462+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
463+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
464+
465+ // Apply a relative error of 4ULP to introduce some non-determinism
466+ // simulating imprecise implementations and optimizations.
467+ apply_random_float_error_ulp (
468+ this, res, 2 , // log2(4)
469+ )
470+ } ) ;
380471 let res = this. adjust_nan ( res, & [ f] ) ;
381472 this. write_scalar ( res, dest) ?;
382473 }
383474 "powif64" => {
384- // FIXME: apply random relative error but without altering behaviour of powi
385475 let [ f, i] = check_intrinsic_arg_count ( args) ?;
386476 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
387477 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
388- // Using host floats (but it's fine, this operation does not have guaranteed precision).
389- let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
478+
479+ let fixed_res = match ( f. category ( ) , i) {
480+ // Standard specifies we can only fix to 1 if input is is not a Signaling NaN.
481+ ( _, 0 ) if !f. is_signaling ( ) => Some ( 1.0f64 . to_soft ( ) ) ,
482+
483+ // ±0^x = 0 with x an even integer.
484+ // TODO: Isn't this done by the implementation itself?
485+ ( Category :: Zero , x) if x % 2 == 0 => Some ( 0.0f64 . to_soft ( ) ) ,
486+
487+ // ±0^x = ±0 with x an odd integer.
488+ ( Category :: Zero , x) if x % 2 != 0 => Some ( f) , // preserve sign of zero.
489+ _ => None ,
490+ } ;
491+ let res = fixed_res. unwrap_or_else ( || {
492+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
493+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
494+
495+ // Apply a relative error of 4ULP to introduce some non-determinism
496+ // simulating imprecise implementations and optimizations.
497+ apply_random_float_error_ulp (
498+ this, res, 2 , // log2(4)
499+ )
500+ } ) ;
390501 let res = this. adjust_nan ( res, & [ f] ) ;
391502 this. write_scalar ( res, dest) ?;
392503 }
@@ -522,3 +633,36 @@ fn apply_random_float_error_to_imm<'tcx>(
522633
523634 interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
524635}
636+
637+ /// For the operations:
638+ /// - sinf32, sinf64
639+ /// - cosf32, cosf64
640+ /// - expf32, expf64, exp2f32, exp2f64
641+ /// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
642+ ///
643+ /// Returns Some(`output`) if the operation results in a defined fixed `output` when given `input`
644+ /// as an input, else None.
645+ fn fixed_float_value < S : Semantics > (
646+ intrinsic_name : & str ,
647+ input : IeeeFloat < S > ,
648+ ) -> Option < IeeeFloat < S > >
649+ where
650+ IeeeFloat < S > : std:: cmp:: PartialEq ,
651+ {
652+ // TODO: Should we really fix to ±0? Applying an error has no effect.
653+ let one = IeeeFloat :: < S > :: one ( ) ;
654+ match intrinsic_name {
655+ "sinf32" | "sinf64" if input. is_pos_zero ( ) => Some ( IeeeFloat :: < S > :: ZERO ) ,
656+ "sinf32" | "sinf64" if input. is_neg_zero ( ) => Some ( -IeeeFloat :: < S > :: ZERO ) ,
657+ "cosf32" | "cosf64" if input. is_zero ( ) => Some ( one) ,
658+ "expf32" | "expf64" | "exp2f32" | "exp2f64" if input. is_zero ( ) => Some ( one) ,
659+ #[ rustfmt:: skip]
660+ "logf32"
661+ | "logf64"
662+ | "log10f32"
663+ | "log10f64"
664+ | "log2f32"
665+ | "log2f64" if input == one => Some ( IeeeFloat :: < S > :: ZERO ) ,
666+ _ => None ,
667+ }
668+ }
0 commit comments