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 ;
8
- use rustc_apfloat:: { Float , Round } ;
10
+ use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
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 , check_intrinsic_arg_count} ;
15
18
use self :: simd:: EvalContextExt as _;
16
- use crate :: math:: apply_random_float_error_to_imm ;
19
+ use crate :: math:: { IeeeExt , apply_random_float_error_ulp } ;
17
20
use crate :: * ;
18
21
19
22
impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
@@ -187,31 +190,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
187
190
=> {
188
191
let [ f] = check_intrinsic_arg_count ( args) ?;
189
192
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
190
- // Using host floats (but it's fine, these operations do not have
191
- // guaranteed precision).
192
- let host = f. to_host ( ) ;
193
- let res = match intrinsic_name {
194
- "sinf32" => host. sin ( ) ,
195
- "cosf32" => host. cos ( ) ,
196
- "expf32" => host. exp ( ) ,
197
- "exp2f32" => host. exp2 ( ) ,
198
- "logf32" => host. ln ( ) ,
199
- "log10f32" => host. log10 ( ) ,
200
- "log2f32" => host. log2 ( ) ,
201
- _ => bug ! ( ) ,
202
- } ;
203
- let res = res. to_soft ( ) ;
204
- // Apply a relative error of 16ULP to introduce some non-determinism
205
- // simulating imprecise implementations and optimizations.
206
- // FIXME: temporarily disabled as it breaks std tests.
207
- // let res = apply_random_float_error_ulp(
208
- // this,
209
- // res,
210
- // 4, // log2(16)
211
- // );
193
+
194
+ let res = fixed_float_value ( intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
195
+ // Using host floats (but it's fine, these operations do not have
196
+ // guaranteed precision).
197
+ let host = f. to_host ( ) ;
198
+ let res = match intrinsic_name {
199
+ "sinf32" => host. sin ( ) ,
200
+ "cosf32" => host. cos ( ) ,
201
+ "expf32" => host. exp ( ) ,
202
+ "exp2f32" => host. exp2 ( ) ,
203
+ "logf32" => host. ln ( ) ,
204
+ "log10f32" => host. log10 ( ) ,
205
+ "log2f32" => host. log2 ( ) ,
206
+ _ => bug ! ( ) ,
207
+ } ;
208
+ let res = res. to_soft ( ) ;
209
+
210
+ // Apply a relative error of 4ULP to introduce some non-determinism
211
+ // simulating imprecise implementations and optimizations.
212
+ let res = apply_random_float_error_ulp (
213
+ this,
214
+ res,
215
+ 2 , // log2(4)
216
+ ) ;
217
+
218
+ // Clamp the result to the guaranteed range of this function according to the C standard,
219
+ // if any.
220
+ clamp_float_value ( intrinsic_name, res)
221
+ } ) ;
212
222
let res = this. adjust_nan ( res, & [ f] ) ;
213
223
this. write_scalar ( res, dest) ?;
214
224
}
225
+
215
226
#[ rustfmt:: skip]
216
227
| "sinf64"
217
228
| "cosf64"
@@ -223,28 +234,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223
234
=> {
224
235
let [ f] = check_intrinsic_arg_count ( args) ?;
225
236
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
226
- // Using host floats (but it's fine, these operations do not have
227
- // guaranteed precision).
228
- let host = f. to_host ( ) ;
229
- let res = match intrinsic_name {
230
- "sinf64" => host. sin ( ) ,
231
- "cosf64" => host. cos ( ) ,
232
- "expf64" => host. exp ( ) ,
233
- "exp2f64" => host. exp2 ( ) ,
234
- "logf64" => host. ln ( ) ,
235
- "log10f64" => host. log10 ( ) ,
236
- "log2f64" => host. log2 ( ) ,
237
- _ => bug ! ( ) ,
238
- } ;
239
- let res = res. to_soft ( ) ;
240
- // Apply a relative error of 16ULP to introduce some non-determinism
241
- // simulating imprecise implementations and optimizations.
242
- // FIXME: temporarily disabled as it breaks std tests.
243
- // let res = apply_random_float_error_ulp(
244
- // this,
245
- // res,
246
- // 4, // log2(16)
247
- // );
237
+
238
+ let res = fixed_float_value ( intrinsic_name, & [ f] ) . unwrap_or_else ( ||{
239
+ // Using host floats (but it's fine, these operations do not have
240
+ // guaranteed precision).
241
+ let host = f. to_host ( ) ;
242
+ let res = match intrinsic_name {
243
+ "sinf64" => host. sin ( ) ,
244
+ "cosf64" => host. cos ( ) ,
245
+ "expf64" => host. exp ( ) ,
246
+ "exp2f64" => host. exp2 ( ) ,
247
+ "logf64" => host. ln ( ) ,
248
+ "log10f64" => host. log10 ( ) ,
249
+ "log2f64" => host. log2 ( ) ,
250
+ _ => bug ! ( ) ,
251
+ } ;
252
+ let res = res. to_soft ( ) ;
253
+
254
+ // Apply a relative error of 4ULP to introduce some non-determinism
255
+ // simulating imprecise implementations and optimizations.
256
+ let res = apply_random_float_error_ulp (
257
+ this,
258
+ res,
259
+ 2 , // log2(4)
260
+ ) ;
261
+
262
+ // Clamp the result to the guaranteed range of this function according to the C standard,
263
+ // if any.
264
+ clamp_float_value ( intrinsic_name, res)
265
+ } ) ;
248
266
let res = this. adjust_nan ( res, & [ f] ) ;
249
267
this. write_scalar ( res, dest) ?;
250
268
}
@@ -302,43 +320,75 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
302
320
}
303
321
304
322
"powf32" => {
305
- // FIXME: apply random relative error but without altering behaviour of powf
306
323
let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
307
324
let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
308
325
let f2 = this. read_scalar ( f2) ?. to_f32 ( ) ?;
309
- // Using host floats (but it's fine, this operation does not have guaranteed precision).
310
- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
326
+
327
+ let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
328
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
329
+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
330
+
331
+ // Apply a relative error of 4ULP to introduce some non-determinism
332
+ // simulating imprecise implementations and optimizations.
333
+ apply_random_float_error_ulp (
334
+ this, res, 2 , // log2(4)
335
+ )
336
+ } ) ;
311
337
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
312
338
this. write_scalar ( res, dest) ?;
313
339
}
314
340
"powf64" => {
315
- // FIXME: apply random relative error but without altering behaviour of powf
316
341
let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
317
342
let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
318
343
let f2 = this. read_scalar ( f2) ?. to_f64 ( ) ?;
319
- // Using host floats (but it's fine, this operation does not have guaranteed precision).
320
- let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
344
+
345
+ let res = fixed_float_value ( intrinsic_name, & [ f1, f2] ) . unwrap_or_else ( || {
346
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
347
+ let res = f1. to_host ( ) . powf ( f2. to_host ( ) ) . to_soft ( ) ;
348
+
349
+ // Apply a relative error of 4ULP to introduce some non-determinism
350
+ // simulating imprecise implementations and optimizations.
351
+ apply_random_float_error_ulp (
352
+ this, res, 2 , // log2(4)
353
+ )
354
+ } ) ;
321
355
let res = this. adjust_nan ( res, & [ f1, f2] ) ;
322
356
this. write_scalar ( res, dest) ?;
323
357
}
324
358
325
359
"powif32" => {
326
- // FIXME: apply random relative error but without altering behaviour of powi
327
360
let [ f, i] = check_intrinsic_arg_count ( args) ?;
328
361
let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
329
362
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
330
- // Using host floats (but it's fine, this operation does not have guaranteed precision).
331
- let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
363
+
364
+ let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
365
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
366
+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
367
+
368
+ // Apply a relative error of 4ULP to introduce some non-determinism
369
+ // simulating imprecise implementations and optimizations.
370
+ apply_random_float_error_ulp (
371
+ this, res, 2 , // log2(4)
372
+ )
373
+ } ) ;
332
374
let res = this. adjust_nan ( res, & [ f] ) ;
333
375
this. write_scalar ( res, dest) ?;
334
376
}
335
377
"powif64" => {
336
- // FIXME: apply random relative error but without altering behaviour of powi
337
378
let [ f, i] = check_intrinsic_arg_count ( args) ?;
338
379
let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
339
380
let i = this. read_scalar ( i) ?. to_i32 ( ) ?;
340
- // Using host floats (but it's fine, this operation does not have guaranteed precision).
341
- let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
381
+
382
+ let res = fixed_powi_float_value ( f, i) . unwrap_or_else ( || {
383
+ // Using host floats (but it's fine, this operation does not have guaranteed precision).
384
+ let res = f. to_host ( ) . powi ( i) . to_soft ( ) ;
385
+
386
+ // Apply a relative error of 4ULP to introduce some non-determinism
387
+ // simulating imprecise implementations and optimizations.
388
+ apply_random_float_error_ulp (
389
+ this, res, 2 , // log2(4)
390
+ )
391
+ } ) ;
342
392
let res = this. adjust_nan ( res, & [ f] ) ;
343
393
this. write_scalar ( res, dest) ?;
344
394
}
@@ -425,3 +475,97 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
425
475
interp_ok ( EmulateItemResult :: NeedsReturn )
426
476
}
427
477
}
478
+
479
+ /// Applies a random ULP floating point error to `val` and returns the new value.
480
+ /// So if you want an X ULP error, `ulp_exponent` should be log2(X).
481
+ ///
482
+ /// Will fail if `val` is not a floating point number.
483
+ fn apply_random_float_error_to_imm < ' tcx > (
484
+ ecx : & mut MiriInterpCx < ' tcx > ,
485
+ val : ImmTy < ' tcx > ,
486
+ ulp_exponent : u32 ,
487
+ ) -> InterpResult < ' tcx , ImmTy < ' tcx > > {
488
+ let scalar = val. to_scalar_int ( ) ?;
489
+ let res: ScalarInt = match val. layout . ty . kind ( ) {
490
+ ty:: Float ( FloatTy :: F16 ) =>
491
+ apply_random_float_error_ulp ( ecx, scalar. to_f16 ( ) , ulp_exponent) . into ( ) ,
492
+ ty:: Float ( FloatTy :: F32 ) =>
493
+ apply_random_float_error_ulp ( ecx, scalar. to_f32 ( ) , ulp_exponent) . into ( ) ,
494
+ ty:: Float ( FloatTy :: F64 ) =>
495
+ apply_random_float_error_ulp ( ecx, scalar. to_f64 ( ) , ulp_exponent) . into ( ) ,
496
+ ty:: Float ( FloatTy :: F128 ) =>
497
+ apply_random_float_error_ulp ( ecx, scalar. to_f128 ( ) , ulp_exponent) . into ( ) ,
498
+ _ => bug ! ( "intrinsic called with non-float input type" ) ,
499
+ } ;
500
+
501
+ interp_ok ( ImmTy :: from_scalar_int ( res, val. layout ) )
502
+ }
503
+
504
+ /// For the intrinsics:
505
+ /// - sinf32, sinf64
506
+ /// - cosf32, cosf64
507
+ /// - expf32, expf64, exp2f32, exp2f64
508
+ /// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
509
+ /// - powf32, powf64
510
+ ///
511
+ /// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
512
+ /// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
513
+ /// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
514
+ /// implementation. Returns `None` if no specific value is guaranteed.
515
+ fn fixed_float_value < S : Semantics > (
516
+ intrinsic_name : & str ,
517
+ args : & [ IeeeFloat < S > ] ,
518
+ ) -> Option < IeeeFloat < S > > {
519
+ let one = IeeeFloat :: < S > :: one ( ) ;
520
+ match ( intrinsic_name, args) {
521
+ // cos(+- 0) = 1
522
+ ( "cosf32" | "cosf64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
523
+
524
+ // e^0 = 1
525
+ ( "expf32" | "expf64" | "exp2f32" | "exp2f64" , [ input] ) if input. is_zero ( ) => Some ( one) ,
526
+
527
+ // 1^y = 1 for any y, even a NaN.
528
+ ( "powf32" | "powf64" , [ base, _] ) if * base == one => Some ( one) ,
529
+
530
+ // (-1)^(±INF) = 1
531
+ ( "powf32" | "powf64" , [ base, exp] ) if * base == -one && exp. is_infinite ( ) => Some ( one) ,
532
+
533
+ // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
534
+ // the NaN. We should return either 1 or the NaN non-deterministically here.
535
+ // But for now, just handle them all the same.
536
+ // x^(±0) = 1 for any x, even a NaN
537
+ ( "powf32" | "powf64" , [ _, exp] ) if exp. is_zero ( ) => Some ( one) ,
538
+
539
+ // There are a lot of cases for fixed outputs according to the C Standard, but these are mainly INF or zero
540
+ // which are not affected by the applied error.
541
+ _ => None ,
542
+ }
543
+ }
544
+
545
+ /// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the C standard
546
+ /// (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
547
+ fn fixed_powi_float_value < S : Semantics > ( base : IeeeFloat < S > , exp : i32 ) -> Option < IeeeFloat < S > > {
548
+ match ( base. category ( ) , exp) {
549
+ // x^0 = 1, if x is not a Signaling NaN
550
+ // FIXME(#4286): The C ecosystem is inconsistent with handling sNaN's, some return 1 others propogate
551
+ // the NaN. We should return either 1 or the NaN non-deterministically here.
552
+ // But for now, just handle them all the same.
553
+ ( _, 0 ) => Some ( IeeeFloat :: < S > :: one ( ) ) ,
554
+
555
+ _ => None ,
556
+ }
557
+ }
558
+
559
+ /// Given an floating-point operation and a floating-point value, clamps the result to the output
560
+ /// range of the given operation.
561
+ fn clamp_float_value < S : Semantics > ( intrinsic_name : & str , val : IeeeFloat < S > ) -> IeeeFloat < S > {
562
+ match intrinsic_name {
563
+ // sin and cos: [-1, 1]
564
+ "sinf32" | "cosf32" | "sinf64" | "cosf64" =>
565
+ val. clamp ( IeeeFloat :: < S > :: one ( ) . neg ( ) , IeeeFloat :: < S > :: one ( ) ) ,
566
+ // exp: [0, +INF]
567
+ "expf32" | "exp2f32" | "expf64" | "exp2f64" =>
568
+ IeeeFloat :: < S > :: maximum ( val, IeeeFloat :: < S > :: ZERO ) ,
569
+ _ => val,
570
+ }
571
+ }
0 commit comments