3
3
mod atomic;
4
4
mod simd;
5
5
6
+ use std:: ops:: Neg ;
7
+
6
8
use rand:: Rng ;
7
9
use rustc_abi:: Size ;
10
+ use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
8
11
use rustc_apfloat:: { self , Float , Round } ;
9
12
use rustc_middle:: mir;
10
- use rustc_middle:: ty:: { self , FloatTy } ;
13
+ use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
11
14
use rustc_span:: { Symbol , sym} ;
12
15
13
16
use self :: atomic:: EvalContextExt as _;
14
17
use self :: helpers:: { ToHost , ToSoft } ;
15
18
use self :: simd:: EvalContextExt as _;
19
+ use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
16
20
use crate :: * ;
17
21
18
22
/// Check that the number of args is what we expect.
@@ -205,7 +209,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
205
209
let [ f] = check_intrinsic_arg_count ( args) ?;
206
210
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
207
211
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 ( || {
209
213
// Using host floats (but it's fine, these operations do not have
210
214
// guaranteed precision).
211
215
let host = f. to_host ( ) ;
@@ -223,15 +227,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223
227
224
228
// Apply a relative error of 4ULP to introduce some non-determinism
225
229
// simulating imprecise implementations and optimizations.
226
- let res = math :: apply_random_float_error_ulp (
230
+ let res = apply_random_float_error_ulp (
227
231
this,
228
232
res,
229
233
2 , // log2(4)
230
234
) ;
231
235
232
236
// Clamp the result to the guaranteed range of this function according to the C standard,
233
237
// if any.
234
- math :: clamp_float_value ( intrinsic_name, res)
238
+ clamp_float_value ( intrinsic_name, res)
235
239
} ) ;
236
240
let res = this. adjust_nan ( res, & [ f] ) ;
237
241
this. write_scalar ( res, dest) ?;
@@ -249,7 +253,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
249
253
let [ f] = check_intrinsic_arg_count ( args) ?;
250
254
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
251
255
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 ( || {
253
257
// Using host floats (but it's fine, these operations do not have
254
258
// guaranteed precision).
255
259
let host = f. to_host ( ) ;
@@ -267,15 +271,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
267
271
268
272
// Apply a relative error of 4ULP to introduce some non-determinism
269
273
// simulating imprecise implementations and optimizations.
270
- let res = math :: apply_random_float_error_ulp (
274
+ let res = apply_random_float_error_ulp (
271
275
this,
272
276
res,
273
277
2 , // log2(4)
274
278
) ;
275
279
276
280
// Clamp the result to the guaranteed range of this function according to the C standard,
277
281
// if any.
278
- math :: clamp_float_value ( intrinsic_name, res)
282
+ clamp_float_value ( intrinsic_name, res)
279
283
} ) ;
280
284
let res = this. adjust_nan ( res, & [ f] ) ;
281
285
this. write_scalar ( res, dest) ?;
@@ -326,17 +330,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
326
330
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
327
331
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
328
332
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 ( ) ;
333
336
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
+ } ) ;
340
343
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
341
344
this. write_scalar ( res, dest) ?;
342
345
}
@@ -345,17 +348,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
345
348
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
346
349
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
347
350
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 ( ) ;
352
354
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
+ } ) ;
359
361
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
360
362
this. write_scalar ( res, dest) ?;
361
363
}
@@ -365,13 +367,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
365
367
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
366
368
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
367
369
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 ( || {
369
371
// Using host floats (but it's fine, this operation does not have guaranteed precision).
370
372
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
371
373
372
374
// Apply a relative error of 4ULP to introduce some non-determinism
373
375
// simulating imprecise implementations and optimizations.
374
- math :: apply_random_float_error_ulp (
376
+ apply_random_float_error_ulp (
375
377
this, res, 2 , // log2(4)
376
378
)
377
379
} ) ;
@@ -383,13 +385,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
383
385
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
384
386
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
385
387
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 ( || {
387
389
// Using host floats (but it's fine, this operation does not have guaranteed precision).
388
390
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
389
391
390
392
// Apply a relative error of 4ULP to introduce some non-determinism
391
393
// simulating imprecise implementations and optimizations.
392
- math :: apply_random_float_error_ulp (
394
+ apply_random_float_error_ulp (
393
395
this, res, 2 , // log2(4)
394
396
)
395
397
} ) ;
@@ -446,7 +448,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
446
448
}
447
449
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
448
450
// 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) */ ) ?;
450
452
this. write_immediate ( * res, dest) ?;
451
453
}
452
454
@@ -483,3 +485,133 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483
485
interp_ok ( EmulateItemResult :: NeedsReturn )
484
486
}
485
487
}
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