@@ -7,8 +7,8 @@ use std::ops::Neg;
7
7
8
8
use rand:: Rng ;
9
9
use 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 } ;
12
12
use rustc_middle:: mir;
13
13
use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
14
14
use rustc_span:: { Symbol , sym} ;
@@ -19,63 +19,6 @@ use self::simd::EvalContextExt as _;
19
19
use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
20
20
use crate :: * ;
21
21
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
-
79
22
impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
80
23
pub trait EvalContextExt < ' tcx > : crate :: MiriInterpCxExt < ' tcx > {
81
24
fn call_intrinsic (
@@ -296,7 +239,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
296
239
let [ f] = check_intrinsic_arg_count ( args) ?;
297
240
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
298
241
299
- let res = fixed_float_value ( intrinsic_name, f ) . unwrap_or_else ( ||{
242
+ let res = fixed_float_value ( intrinsic_name, & [ f ] ) . unwrap_or_else ( ||{
300
243
// Using host floats (but it's fine, these operations do not have
301
244
// guaranteed precision).
302
245
let host = f. to_host ( ) ;
@@ -322,13 +265,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
322
265
323
266
// Clamp the result to the guaranteed range of this function according to the C standard,
324
267
// 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)
332
269
} ) ;
333
270
let res = this. adjust_nan ( res, & [ f] ) ;
334
271
this. write_scalar ( res, dest) ?;
@@ -346,7 +283,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
346
283
let [ f] = check_intrinsic_arg_count ( args) ?;
347
284
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
348
285
349
- let res = fixed_float_value ( intrinsic_name, f ) . unwrap_or_else ( ||{
286
+ let res = fixed_float_value ( intrinsic_name, & [ f ] ) . unwrap_or_else ( ||{
350
287
// Using host floats (but it's fine, these operations do not have
351
288
// guaranteed precision).
352
289
let host = f. to_host ( ) ;
@@ -372,13 +309,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
372
309
373
310
// Clamp the result to the guaranteed range of this function according to the C standard,
374
311
// 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)
382
313
} ) ;
383
314
let res = this. adjust_nan ( res, & [ f] ) ;
384
315
this. write_scalar ( res, dest) ?;
@@ -442,7 +373,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
442
373
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
443
374
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
444
375
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
+ } ) ;
446
386
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
447
387
this. write_scalar ( res, dest) ?;
448
388
}
@@ -451,7 +391,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
451
391
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
452
392
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
453
393
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
+ } ) ;
455
404
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
456
405
this. write_scalar ( res, dest) ?;
457
406
}
@@ -461,7 +410,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
461
410
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
462
411
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
463
412
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
+ } ) ;
465
423
let res = this. adjust_nan ( res, & [ f] ) ;
466
424
this. write_scalar ( res, dest) ?;
467
425
}
@@ -470,7 +428,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
470
428
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
471
429
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
472
430
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
+ } ) ;
474
441
let res = this. adjust_nan ( res, & [ f] ) ;
475
442
this. write_scalar ( res, dest) ?;
476
443
}
@@ -607,34 +574,121 @@ fn apply_random_float_error_to_imm<'tcx>(
607
574
interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
608
575
}
609
576
610
- /// For the operations:
577
+ // TODO(lorrens): This can be moved to `helpers` when we implement the other intrinsics.
578
+ /// For the intrinsics:
611
579
/// - sinf32, sinf64
612
580
/// - cosf32, cosf64
613
581
/// - expf32, expf64, exp2f32, exp2f64
614
582
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
583
+ /// - powf32, powf64
615
584
///
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.
618
587
fn fixed_float_value < S : Semantics > (
619
588
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
625
595
let one = IeeeFloat :: < S > :: one ( ) ;
626
- match intrinsic_name {
596
+ match ( intrinsic_name, args ) {
627
597
// 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
631
608
#[ rustfmt:: skip]
632
- "logf32"
609
+ ( "logf32"
633
610
| "logf64"
634
611
| "log10f32"
635
612
| "log10f64"
636
613
| "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
638
626
_ => None ,
639
627
}
640
628
}
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