33mod atomic;
44mod simd;
55
6+ use std:: ops:: Neg ;
7+
68use rand:: Rng ;
79use rustc_abi:: Size ;
8- use rustc_apfloat:: { Float , Round } ;
10+ use rustc_apfloat:: ieee:: { IeeeFloat , Semantics } ;
11+ use rustc_apfloat:: { self , Float , Round } ;
912use rustc_middle:: mir;
10- use rustc_middle:: ty:: { self , FloatTy } ;
13+ use rustc_middle:: ty:: { self , FloatTy , ScalarInt } ;
1114use rustc_span:: { Symbol , sym} ;
1215
1316use self :: atomic:: EvalContextExt as _;
1417use self :: helpers:: { ToHost , ToSoft , check_intrinsic_arg_count} ;
1518use self :: simd:: EvalContextExt as _;
16- use crate :: math:: apply_random_float_error_to_imm ;
19+ use crate :: math:: { IeeeExt , apply_random_float_error_ulp } ;
1720use crate :: * ;
1821
1922impl < ' tcx > EvalContextExt < ' tcx > for crate :: MiriInterpCx < ' tcx > { }
@@ -187,31 +190,39 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
187190 => {
188191 let [ f] = check_intrinsic_arg_count ( args) ?;
189192 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+ } ) ;
212222 let res = this. adjust_nan ( res, & [ f] ) ;
213223 this. write_scalar ( res, dest) ?;
214224 }
225+
215226 #[ rustfmt:: skip]
216227 | "sinf64"
217228 | "cosf64"
@@ -223,28 +234,35 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
223234 => {
224235 let [ f] = check_intrinsic_arg_count ( args) ?;
225236 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+ } ) ;
248266 let res = this. adjust_nan ( res, & [ f] ) ;
249267 this. write_scalar ( res, dest) ?;
250268 }
@@ -302,43 +320,75 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
302320 }
303321
304322 "powf32" => {
305- // FIXME: apply random relative error but without altering behaviour of powf
306323 let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
307324 let f1 = this. read_scalar ( f1) ?. to_f32 ( ) ?;
308325 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+ } ) ;
311337 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
312338 this. write_scalar ( res, dest) ?;
313339 }
314340 "powf64" => {
315- // FIXME: apply random relative error but without altering behaviour of powf
316341 let [ f1, f2] = check_intrinsic_arg_count ( args) ?;
317342 let f1 = this. read_scalar ( f1) ?. to_f64 ( ) ?;
318343 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+ } ) ;
321355 let res = this. adjust_nan ( res, & [ f1, f2] ) ;
322356 this. write_scalar ( res, dest) ?;
323357 }
324358
325359 "powif32" => {
326- // FIXME: apply random relative error but without altering behaviour of powi
327360 let [ f, i] = check_intrinsic_arg_count ( args) ?;
328361 let f = this. read_scalar ( f) ?. to_f32 ( ) ?;
329362 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+ } ) ;
332374 let res = this. adjust_nan ( res, & [ f] ) ;
333375 this. write_scalar ( res, dest) ?;
334376 }
335377 "powif64" => {
336- // FIXME: apply random relative error but without altering behaviour of powi
337378 let [ f, i] = check_intrinsic_arg_count ( args) ?;
338379 let f = this. read_scalar ( f) ?. to_f64 ( ) ?;
339380 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+ } ) ;
342392 let res = this. adjust_nan ( res, & [ f] ) ;
343393 this. write_scalar ( res, dest) ?;
344394 }
@@ -425,3 +475,97 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
425475 interp_ok ( EmulateItemResult :: NeedsReturn )
426476 }
427477}
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