@@ -7,8 +7,8 @@ use std::ops::Neg;
77
88use rand:: Rng ;
99use rustc_abi:: Size ;
10- use rustc_apfloat:: ieee:: { Double , IeeeFloat , Semantics , Single } ;
11- use rustc_apfloat:: { self , Float , Round } ;
10+ use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
11+ use rustc_apfloat:: { self , Category , Float , Round } ;
1212use rustc_middle:: mir;
1313use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
1414use rustc_span:: { Symbol , sym} ;
@@ -19,63 +19,6 @@ use self::simd::EvalContextExt as _;
1919use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
2020use crate :: * ;
2121
22- macro_rules! pow_impl {
23- ( $this: expr, powf( $f1: expr, $f2: expr) ) => { {
24- let host1 = $f1. to_host( ) ;
25- let host2 = $f2. to_host( ) ;
26-
27- let fixed_res = match ( host1, host2) {
28- // 1^y = 1 for any y even a NaN.
29- ( one @ 1.0 , _) => Some ( one) ,
30-
31- // (-1)^(±INF) = 1
32- ( -1.0 , exp) if exp. is_infinite( ) => Some ( 1.0 ) ,
33-
34- // x^(±0) = 1 for any x, even a NaN
35- ( _, 0.0 ) => Some ( 1.0 ) ,
36-
37- _ => None ,
38- } ;
39- fixed_res. map_or_else( || {
40- // Using host floats (but it's fine, this operation does not have guaranteed precision).
41- let res = host1. powf( host2) . to_soft( ) ;
42- // Apply a relative error of 4ULP to introduce some non-determinism
43- // simulating imprecise implementations and optimizations.
44- apply_random_float_error_ulp(
45- $this, res, 2 , // log2(4)
46- )
47- } , ToSoft :: to_soft)
48- } } ;
49- ( $this: expr, powi( $f: expr, $i: expr) ) => { {
50- let host = $f. to_host( ) ;
51- let exp = $i;
52-
53- let fixed_res = match ( host, exp) {
54- // ±0^x = ±0 with x an odd integer.
55- ( zero @ 0.0 , x) if x % 2 != 0 => Some ( zero) , // preserve sign of zero.
56-
57- // ±0^x = +0 with x an even integer.
58- ( 0.0 , x) if x % 2 == 0 => Some ( 0.0 ) ,
59-
60- // x^0 = 1:
61- // Standard specifies we can only fix to 1 if x is is not a Signaling NaN.
62- // TODO: How to do this in normal rust?
63- ( _, 0 ) /* if !f.is_signaling() */ => Some ( 1.0 ) ,
64-
65- _ => None ,
66- } ;
67- fixed_res. map_or_else( || {
68- // Using host floats (but it's fine, this operation does not have guaranteed precision).
69- let res = host. powi( exp) . to_soft( ) ;
70- // Apply a relative error of 4ULP to introduce some non-determinism
71- // simulating imprecise implementations and optimizations.
72- apply_random_float_error_ulp(
73- $this, res, 2 , // log2(4)
74- )
75- } , ToSoft :: to_soft)
76- } } ;
77- }
78-
7922impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
8023pub trait EvalContextExt < ' tcx > : crate :: MiriInterpCxExt < ' tcx > {
8124 fn call_intrinsic (
@@ -296,7 +239,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
296239 let [ f] = check_intrinsic_arg_count ( args) ?;
297240 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
298241
299- let res = fixed_float_value ( intrinsic_name, f ) . unwrap_or_else ( ||{
242+ let res = fixed_float_value ( intrinsic_name, & [ f ] ) . unwrap_or_else ( ||{
300243 // Using host floats (but it's fine, these operations do not have
301244 // guaranteed precision).
302245 let host = f. to_host ( ) ;
@@ -322,13 +265,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
322265
323266 // Clamp the result to the guaranteed range of this function according to the C standard,
324267 // if any.
325- match intrinsic_name {
326- // sin and cos: [-1, 1]
327- "sinf32" | "cosf32" => res. clamp ( Single :: one ( ) . neg ( ) , Single :: one ( ) ) ,
328- // exp: [0, +INF]
329- "expf32" | "exp2f32" => res. maximum ( Single :: ZERO ) ,
330- _ => res,
331- }
268+ clamp_float_value ( intrinsic_name, res)
332269 } ) ;
333270 let res = this. adjust_nan ( res, & [ f] ) ;
334271 this. write_scalar ( res, dest) ?;
@@ -346,7 +283,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
346283 let [ f] = check_intrinsic_arg_count ( args) ?;
347284 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
348285
349- let res = fixed_float_value ( intrinsic_name, f ) . unwrap_or_else ( ||{
286+ let res = fixed_float_value ( intrinsic_name, & [ f ] ) . unwrap_or_else ( ||{
350287 // Using host floats (but it's fine, these operations do not have
351288 // guaranteed precision).
352289 let host = f. to_host ( ) ;
@@ -372,13 +309,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
372309
373310 // Clamp the result to the guaranteed range of this function according to the C standard,
374311 // if any.
375- match intrinsic_name {
376- // sin and cos: [-1, 1]
377- "sinf64" | "cosf64" => res. clamp ( Double :: one ( ) . neg ( ) , Double :: one ( ) ) ,
378- // exp: [0, +INF]
379- "expf64" | "exp2f64" => res. maximum ( Double :: ZERO ) ,
380- _ => res,
381- }
312+ clamp_float_value ( intrinsic_name, res)
382313 } ) ;
383314 let res = this. adjust_nan ( res, & [ f] ) ;
384315 this. write_scalar ( res, dest) ?;
@@ -442,7 +373,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
442373 let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
443374 let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
444375
445- let res = pow_impl ! ( this, powf( f1, f2) ) ;
376+ let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
377+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
378+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
379+
380+ // Apply a relative error of 4ULP to introduce some non-determinism
381+ // simulating imprecise implementations and optimizations.
382+ apply_random_float_error_ulp (
383+ this, res, 2 , // log2(4)
384+ )
385+ } ) ;
446386 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
447387 this. write_scalar ( res, dest) ?;
448388 }
@@ -451,7 +391,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
451391 let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
452392 let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
453393
454- let res = pow_impl ! ( this, powf( f1, f2) ) ;
394+ let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
395+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
396+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
397+
398+ // Apply a relative error of 4ULP to introduce some non-determinism
399+ // simulating imprecise implementations and optimizations.
400+ apply_random_float_error_ulp (
401+ this, res, 2 , // log2(4)
402+ )
403+ } ) ;
455404 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
456405 this. write_scalar ( res, dest) ?;
457406 }
@@ -461,7 +410,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
461410 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
462411 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
463412
464- let res = pow_impl ! ( this, powi( f, i) ) ;
413+ let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
414+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
415+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
416+
417+ // Apply a relative error of 4ULP to introduce some non-determinism
418+ // simulating imprecise implementations and optimizations.
419+ apply_random_float_error_ulp (
420+ this, res, 2 , // log2(4)
421+ )
422+ } ) ;
465423 let res = this. adjust_nan ( res, & [ f] ) ;
466424 this. write_scalar ( res, dest) ?;
467425 }
@@ -470,7 +428,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
470428 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
471429 let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
472430
473- let res = pow_impl ! ( this, powi( f, i) ) ;
431+ let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
432+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
433+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
434+
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+ } ) ;
474441 let res = this. adjust_nan ( res, & [ f] ) ;
475442 this. write_scalar ( res, dest) ?;
476443 }
@@ -607,34 +574,121 @@ fn apply_random_float_error_to_imm<'tcx>(
607574 interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
608575}
609576
610- /// For the operations:
577+ // TODO(lorrens): This can be moved to `helpers` when we implement the other intrinsics.
578+ /// For the intrinsics:
611579/// - sinf32, sinf64
612580/// - cosf32, cosf64
613581/// - expf32, expf64, exp2f32, exp2f64
614582/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
583+ /// - powf32, powf64
615584///
616- /// Returns Some(`output`) if the operation results in a defined fixed `output` when given `input `
617- /// as an input , else None.
585+ /// Returns Some(`output`) if the operation results in a defined fixed `output` specified in the C standard when given `args `
586+ /// as arguments , else None.
618587fn fixed_float_value < S : Semantics > (
619588 intrinsic_name : & str ,
620- input : IeeeFloat < S > ,
621- ) -> Option < IeeeFloat < S > >
622- where
623- IeeeFloat < S > : std:: cmp:: PartialEq ,
624- {
589+ args : & [ IeeeFloat < S > ] ,
590+ ) -> Option < IeeeFloat < S > > {
591+ // TODO: not sure about this pattern matching stuff. It's definitly cleaner than if-else chains
592+ // Error code 0158 explains this: https://doc.rust-lang.org/stable/error_codes/E0158.html
593+ // The only reason I did this is to use the same function for powf as for sin/cos/exp/log
594+ // TODO: I can't fit powi logic in this because of the exponent being a i32 -> seperate fn "fixed_powi_float_value" for now
625595 let one = IeeeFloat :: < S > :: one ( ) ;
626- match intrinsic_name {
596+ match ( intrinsic_name, args ) {
627597 // sin(+- 0) = +- 0.
628- "sinf32" | "sinf64" if input. is_zero ( ) => Some ( input) ,
629- "cosf32" | "cosf64" if input. is_zero ( ) => Some ( one) ,
630- "expf32" | "expf64" | "exp2f32" | "exp2f64" if input. is_zero ( ) => Some ( one) ,
598+ ( "sinf32" | "sinf64" , [ input] ) if input. is_zero ( ) => Some ( * input) ,
599+
600+ // cos(+- 0) = 1
601+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
602+
603+ // e^0 = 1
604+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] )
605+ if input. is_zero ( ) => Some ( one) ,
606+
607+ // log(1) = 0
631608 #[ rustfmt:: skip]
632- "logf32"
609+ ( "logf32"
633610 | "logf64"
634611 | "log10f32"
635612 | "log10f64"
636613 | "log2f32"
637- | "log2f64" if input == one => Some ( IeeeFloat :: < S > :: ZERO ) ,
614+ | "log2f64" , [ input] ) if * input == one => Some ( IeeeFloat :: < S > :: ZERO ) ,
615+
616+ // 1^y = 1 for any y, even a NaN.
617+ ( "powf32" | "powf64" , [ base, _] ) if * base == one => Some ( one) ,
618+
619+ // (-1)^(±INF) = 1
620+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => Some ( one) ,
621+
622+ // x^(±0) = 1 for any x, even a NaN
623+ ( "powf32" | "powf64" , [ _, exp] ) if exp. is_zero ( ) => Some ( one) ,
624+
625+ // C standard doesn't specify or invalid combination
638626 _ => None ,
639627 }
640628}
629+
630+ /// Returns Some(`output`) if powi results in a fixed value specified in the C standard when doing `base^exp` else None.
631+ fn fixed_powi_float_value < S : Semantics > ( base : IeeeFloat < S > , exp : i32 ) -> Option < IeeeFloat < S > > {
632+ match ( base. category ( ) , exp) {
633+ // ±0^x = ±0 with x an odd integer.
634+ ( Category :: Zero , x) if x % 2 != 0 => Some ( base) , // preserve sign of zero.
635+
636+ // ±0^x = +0 with x an even integer.
637+ ( Category :: Zero , x) if x % 2 == 0 => Some ( IeeeFloat :: < S > :: ZERO ) ,
638+
639+ // x^y = 1, if y is not a Signaling NaN
640+ ( _, 0 ) if !base. is_signaling ( ) => Some ( IeeeFloat :: < S > :: one ( ) ) ,
641+
642+ _ => None ,
643+ }
644+ }
645+
646+ /// Given an floating-point operation and a floating-point value, clamps the result to the output
647+ /// range of the given operation.
648+ fn clamp_float_value < S : Semantics > ( intrinsic_name : & str , val : IeeeFloat < S > ) -> IeeeFloat < S > {
649+ match intrinsic_name {
650+ // sin and cos: [-1, 1]
651+ "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
652+ val. clamp ( IeeeFloat :: < S > :: one ( ) . neg ( ) , IeeeFloat :: < S > :: one ( ) ) ,
653+ // exp: [0, +INF]
654+ "expf32" | "exp2f32" | "expf64" | "exp2f64" => val. maximum ( IeeeFloat :: < S > :: ZERO ) ,
655+ _ => val,
656+ }
657+ }
658+
659+ // TODO: clean up when I'm sure this is not needed, because powf is now included in fixed_float_value
660+ // fn powf_impl<'tcx, S: Semantics, Op>(
661+ // ecx: &mut MiriInterpCx<'tcx>,
662+ // base: IeeeFloat<S>,
663+ // exp: IeeeFloat<S>,
664+ // op: Op,
665+ // ) -> IeeeFloat<S>
666+ // where
667+ // IeeeFloat<S>: ToHost,
668+ // Op: Fn(IeeeFloat<S>, IeeeFloat<S>) -> IeeeFloat<S>,
669+ // {
670+ // let one = IeeeFloat::<S>::one();
671+ // let fixed_res = match (base.category(), exp.category()) {
672+ // // 1^y = 1 for any y, even a NaN.
673+ // (Category::Normal, _) if base == one => Some(one),
674+
675+ // // (-1)^(±INF) = 1
676+ // (Category::Normal, Category::Infinity) if base == -one => Some(one),
677+
678+ // // x^(±0) = 1 for any x, even a NaN
679+ // (_, Category::Zero) => Some(one),
680+
681+ // // TODO: pow has a lot of "edge" cases which mostly result in ±0 or ±INF
682+ // // do we have to catch them all?
683+ // _ => None,
684+ // };
685+
686+ // fixed_res.unwrap_or_else(|| {
687+ // let res = op(base, exp);
688+ // // Apply a relative error of 4ULP to introduce some non-determinism
689+ // // simulating imprecise implementations and optimizations.
690+ // apply_random_float_error_ulp(
691+ // ecx, res, 2, // log2(4)
692+ // )
693+ // })
694+ // }
0 commit comments