3
3
mod atomic;
4
4
mod simd;
5
5
6
- use std:: ops:: Neg ;
7
-
8
6
use rand:: Rng ;
9
7
use rustc_abi:: Size ;
10
- use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
11
8
use rustc_apfloat:: { self , Float , Round } ;
12
9
use rustc_middle:: mir;
13
- use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
10
+ use rustc_middle:: ty:: { self , FloatTy } ;
14
11
use rustc_span:: { Symbol , sym} ;
15
12
16
13
use self :: atomic:: EvalContextExt as _;
17
14
use self :: helpers:: { ToHost , ToSoft , check_intrinsic_arg_count} ;
18
15
use self :: simd:: EvalContextExt as _;
19
- use crate :: math:: { IeeeExt , apply_random_float_error_ulp} ;
16
+ use crate :: math:: apply_random_float_error_ulp;
20
17
use crate :: * ;
21
18
22
19
impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
@@ -191,7 +188,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
191
188
let [ f] = check_intrinsic_arg_count ( args) ?;
192
189
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
193
190
194
- let res = fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
191
+ let res = math :: fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
195
192
// Using host floats (but it's fine, these operations do not have
196
193
// guaranteed precision).
197
194
let host = f. to_host ( ) ;
@@ -209,15 +206,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
209
206
210
207
// Apply a relative error of 4ULP to introduce some non-determinism
211
208
// simulating imprecise implementations and optimizations.
212
- let res = apply_random_float_error_ulp (
209
+ let res = math :: apply_random_float_error_ulp (
213
210
this,
214
211
res,
215
212
2 , // log2(4)
216
213
) ;
217
214
218
215
// Clamp the result to the guaranteed range of this function according to the C standard,
219
216
// if any.
220
- clamp_float_value ( intrinsic_name, res)
217
+ math :: clamp_float_value ( intrinsic_name, res)
221
218
} ) ;
222
219
let res = this. adjust_nan ( res, & [ f] ) ;
223
220
this. write_scalar ( res, dest) ?;
@@ -235,7 +232,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
235
232
let [ f] = check_intrinsic_arg_count ( args) ?;
236
233
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
237
234
238
- let res = fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
235
+ let res = math :: fixed_float_value ( this, intrinsic_name, & [ f] ) . unwrap_or_else ( || {
239
236
// Using host floats (but it's fine, these operations do not have
240
237
// guaranteed precision).
241
238
let host = f. to_host ( ) ;
@@ -253,15 +250,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
253
250
254
251
// Apply a relative error of 4ULP to introduce some non-determinism
255
252
// simulating imprecise implementations and optimizations.
256
- let res = apply_random_float_error_ulp (
253
+ let res = math :: apply_random_float_error_ulp (
257
254
this,
258
255
res,
259
256
2 , // log2(4)
260
257
) ;
261
258
262
259
// Clamp the result to the guaranteed range of this function according to the C standard,
263
260
// if any.
264
- clamp_float_value ( intrinsic_name, res)
261
+ math :: clamp_float_value ( intrinsic_name, res)
265
262
} ) ;
266
263
let res = this. adjust_nan ( res, & [ f] ) ;
267
264
this. write_scalar ( res, dest) ?;
@@ -312,16 +309,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
312
309
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
313
310
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
314
311
315
- let res = fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
316
- // Using host floats (but it's fine, this operation does not have guaranteed precision).
317
- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
312
+ let res =
313
+ math:: fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
314
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
315
+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
318
316
319
- // Apply a relative error of 4ULP to introduce some non-determinism
320
- // simulating imprecise implementations and optimizations.
321
- apply_random_float_error_ulp (
322
- this, res, 2 , // log2(4)
323
- )
324
- } ) ;
317
+ // Apply a relative error of 4ULP to introduce some non-determinism
318
+ // simulating imprecise implementations and optimizations.
319
+ math :: apply_random_float_error_ulp (
320
+ this, res, 2 , // log2(4)
321
+ )
322
+ } ) ;
325
323
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
326
324
this. write_scalar ( res, dest) ?;
327
325
}
@@ -330,16 +328,17 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
330
328
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
331
329
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
332
330
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 ( ) ;
331
+ let res =
332
+ math:: fixed_float_value ( this, intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
333
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
334
+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
336
335
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
- } ) ;
336
+ // Apply a relative error of 4ULP to introduce some non-determinism
337
+ // simulating imprecise implementations and optimizations.
338
+ math :: apply_random_float_error_ulp (
339
+ this, res, 2 , // log2(4)
340
+ )
341
+ } ) ;
343
342
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
344
343
this. write_scalar ( res, dest) ?;
345
344
}
@@ -349,7 +348,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
349
348
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
350
349
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
351
350
352
- let res = fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
351
+ let res = math :: fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
353
352
// Using host floats (but it's fine, this operation does not have guaranteed precision).
354
353
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
355
354
@@ -367,13 +366,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
367
366
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
368
367
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
369
368
370
- let res = fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
369
+ let res = math :: fixed_powi_float_value ( this, f, i) . unwrap_or_else ( || {
371
370
// Using host floats (but it's fine, this operation does not have guaranteed precision).
372
371
let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
373
372
374
373
// Apply a relative error of 4ULP to introduce some non-determinism
375
374
// simulating imprecise implementations and optimizations.
376
- apply_random_float_error_ulp (
375
+ math :: apply_random_float_error_ulp (
377
376
this, res, 2 , // log2(4)
378
377
)
379
378
} ) ;
@@ -430,7 +429,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
430
429
}
431
430
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
432
431
// due to optimizations.
433
- let res = apply_random_float_error_to_imm ( this, res, 2 /* log2(4) */ ) ?;
432
+ let res = math :: apply_random_float_error_to_imm ( this, res, 2 /* log2(4) */ ) ?;
434
433
this. write_immediate ( * res, dest) ?;
435
434
}
436
435
@@ -467,133 +466,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
467
466
interp_ok ( EmulateItemResult :: NeedsReturn )
468
467
}
469
468
}
470
-
471
- /// Applies a random ULP floating point error to `val` and returns the new value.
472
- /// So if you want an X ULP error, `ulp_exponent` should be log2(X).
473
- ///
474
- /// Will fail if `val` is not a floating point number.
475
- fn apply_random_float_error_to_imm < ' tcx > (
476
- ecx : & mut MiriInterpCx < ' tcx > ,
477
- val : ImmTy < ' tcx > ,
478
- ulp_exponent : u32 ,
479
- ) -> InterpResult < ' tcx , ImmTy < ' tcx > > {
480
- let scalar = val. to_scalar_int ( ) ?;
481
- let res: ScalarInt = match val. layout . ty . kind ( ) {
482
- ty:: Float ( FloatTy :: F16 ) =>
483
- apply_random_float_error_ulp ( ecx, scalar. to_f16 ( ) , ulp_exponent) . into ( ) ,
484
- ty:: Float ( FloatTy :: F32 ) =>
485
- apply_random_float_error_ulp ( ecx, scalar. to_f32 ( ) , ulp_exponent) . into ( ) ,
486
- ty:: Float ( FloatTy :: F64 ) =>
487
- apply_random_float_error_ulp ( ecx, scalar. to_f64 ( ) , ulp_exponent) . into ( ) ,
488
- ty:: Float ( FloatTy :: F128 ) =>
489
- apply_random_float_error_ulp ( ecx, scalar. to_f128 ( ) , ulp_exponent) . into ( ) ,
490
- _ => bug ! ( "intrinsic called with non-float input type" ) ,
491
- } ;
492
-
493
- interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
494
- }
495
-
496
- /// For the intrinsics:
497
- /// - sinf32, sinf64
498
- /// - cosf32, cosf64
499
- /// - expf32, expf64, exp2f32, exp2f64
500
- /// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
501
- /// - powf32, powf64
502
- ///
503
- /// # Return
504
- ///
505
- /// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
506
- /// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
507
- /// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
508
- /// implementation. Returns `None` if no specific value is guaranteed.
509
- ///
510
- /// # Note
511
- ///
512
- /// For `powf*` operations of the form:
513
- ///
514
- /// - `(SNaN)^(±0)`
515
- /// - `1^(SNaN)`
516
- ///
517
- /// The result is implementation-defined:
518
- /// - musl returns for both `1.0`
519
- /// - glibc returns for both `NaN`
520
- ///
521
- /// This discrepancy exists because SNaN handling is not consistently defined across platforms,
522
- /// and the C standard leaves behavior for SNaNs unspecified.
523
- ///
524
- /// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
525
- fn fixed_float_value < S : Semantics > (
526
- ecx : & mut MiriInterpCx < ' _ > ,
527
- intrinsic_name : & str ,
528
- args : & [ IeeeFloat < S > ] ,
529
- ) -> Option < IeeeFloat < S > > {
530
- let one = IeeeFloat :: < S > :: one ( ) ;
531
- Some ( match ( intrinsic_name, args) {
532
- // cos(+- 0) = 1
533
- ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => one,
534
-
535
- // e^0 = 1
536
- ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => one,
537
-
538
- // (-1)^(±INF) = 1
539
- ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => one,
540
-
541
- // 1^y = 1 for any y, even a NaN
542
- ( "powf32" | "powf64" , [ base, exp] ) if * base == one => {
543
- let rng = ecx. machine . rng . get_mut ( ) ;
544
- // SNaN exponents get special treatment: they might return 1, or a NaN.
545
- let return_nan = exp. is_signaling ( ) && ecx. machine . float_nondet && rng. random ( ) ;
546
- // Handle both the musl and glibc cases non-deterministically.
547
- if return_nan { ecx. generate_nan ( args) } else { one }
548
- }
549
-
550
- // x^(±0) = 1 for any x, even a NaN
551
- ( "powf32" | "powf64" , [ base, exp] ) if exp. is_zero ( ) => {
552
- let rng = ecx. machine . rng . get_mut ( ) ;
553
- // SNaN bases get special treatment: they might return 1, or a NaN.
554
- let return_nan = base. is_signaling ( ) && ecx. machine . float_nondet && rng. random ( ) ;
555
- // Handle both the musl and glibc cases non-deterministically.
556
- if return_nan { ecx. generate_nan ( args) } else { one }
557
- }
558
-
559
- // There are a lot of cases for fixed outputs according to the C Standard, but these are
560
- // mainly INF or zero which are not affected by the applied error.
561
- _ => return None ,
562
- } )
563
- }
564
-
565
- /// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
566
- /// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
567
- fn fixed_powi_float_value < S : Semantics > (
568
- ecx : & mut MiriInterpCx < ' _ > ,
569
- base : IeeeFloat < S > ,
570
- exp : i32 ,
571
- ) -> Option < IeeeFloat < S > > {
572
- Some ( match exp {
573
- 0 => {
574
- let one = IeeeFloat :: < S > :: one ( ) ;
575
- let rng = ecx. machine . rng . get_mut ( ) ;
576
- let return_nan = ecx. machine . float_nondet && rng. random ( ) && base. is_signaling ( ) ;
577
- // For SNaN treatment, we are consistent with `powf`above.
578
- // (We wouldn't have two, unlike powf all implementations seem to agree for powi,
579
- // but for now we are maximally conservative.)
580
- if return_nan { ecx. generate_nan ( & [ base] ) } else { one }
581
- }
582
-
583
- _ => return None ,
584
- } )
585
- }
586
-
587
- /// Given an floating-point operation and a floating-point value, clamps the result to the output
588
- /// range of the given operation.
589
- fn clamp_float_value < S : Semantics > ( intrinsic_name : & str , val : IeeeFloat < S > ) -> IeeeFloat < S > {
590
- match intrinsic_name {
591
- // sin and cos: [-1, 1]
592
- "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
593
- val. clamp ( IeeeFloat :: < S > :: one ( ) . neg ( ) , IeeeFloat :: < S > :: one ( ) ) ,
594
- // exp: [0, +INF]
595
- "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
596
- IeeeFloat :: < S > :: maximum ( val, IeeeFloat :: < S > :: ZERO ) ,
597
- _ => val,
598
- }
599
- }
0 commit comments