Skip to content

Commit e11a804

Browse files
extend fixed_float_value to accept powf + create fix_float_value fn for powi + a function that clamps a float based on the operation used
1 parent f4d65ad commit e11a804

File tree

1 file changed

+147
-93
lines changed
  • src/tools/miri/src/intrinsics

1 file changed

+147
-93
lines changed

src/tools/miri/src/intrinsics/mod.rs

Lines changed: 147 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use std::ops::Neg;
77

88
use rand::Rng;
99
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};
1212
use rustc_middle::mir;
1313
use rustc_middle::ty::{self, FloatTy, ScalarInt};
1414
use rustc_span::{Symbol, sym};
@@ -19,63 +19,6 @@ use self::simd::EvalContextExt as _;
1919
use crate::math::{IeeeExt, apply_random_float_error_ulp};
2020
use crate::*;
2121

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-
7922
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
8023
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
8124
fn call_intrinsic(
@@ -296,7 +239,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
296239
let [f] = check_intrinsic_arg_count(args)?;
297240
let f = this.read_scalar(f)?.to_f32()?;
298241

299-
let res = fixed_float_value(intrinsic_name, f).unwrap_or_else(||{
242+
let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{
300243
// Using host floats (but it's fine, these operations do not have
301244
// guaranteed precision).
302245
let host = f.to_host();
@@ -322,13 +265,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
322265

323266
// Clamp the result to the guaranteed range of this function according to the C standard,
324267
// 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)
332269
});
333270
let res = this.adjust_nan(res, &[f]);
334271
this.write_scalar(res, dest)?;
@@ -346,7 +283,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
346283
let [f] = check_intrinsic_arg_count(args)?;
347284
let f = this.read_scalar(f)?.to_f64()?;
348285

349-
let res = fixed_float_value(intrinsic_name, f).unwrap_or_else(||{
286+
let res = fixed_float_value(intrinsic_name, &[f]).unwrap_or_else(||{
350287
// Using host floats (but it's fine, these operations do not have
351288
// guaranteed precision).
352289
let host = f.to_host();
@@ -372,13 +309,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
372309

373310
// Clamp the result to the guaranteed range of this function according to the C standard,
374311
// 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)
382313
});
383314
let res = this.adjust_nan(res, &[f]);
384315
this.write_scalar(res, dest)?;
@@ -442,7 +373,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
442373
let f1 = this.read_scalar(f1)?.to_f32()?;
443374
let f2 = this.read_scalar(f2)?.to_f32()?;
444375

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+
});
446386
let res = this.adjust_nan(res, &[f1, f2]);
447387
this.write_scalar(res, dest)?;
448388
}
@@ -451,7 +391,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
451391
let f1 = this.read_scalar(f1)?.to_f64()?;
452392
let f2 = this.read_scalar(f2)?.to_f64()?;
453393

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+
});
455404
let res = this.adjust_nan(res, &[f1, f2]);
456405
this.write_scalar(res, dest)?;
457406
}
@@ -461,7 +410,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
461410
let f = this.read_scalar(f)?.to_f32()?;
462411
let i = this.read_scalar(i)?.to_i32()?;
463412

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+
});
465423
let res = this.adjust_nan(res, &[f]);
466424
this.write_scalar(res, dest)?;
467425
}
@@ -470,7 +428,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
470428
let f = this.read_scalar(f)?.to_f64()?;
471429
let i = this.read_scalar(i)?.to_i32()?;
472430

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+
});
474441
let res = this.adjust_nan(res, &[f]);
475442
this.write_scalar(res, dest)?;
476443
}
@@ -607,34 +574,121 @@ fn apply_random_float_error_to_imm<'tcx>(
607574
interp_ok(ImmTy::from_scalar_int(res, val.layout))
608575
}
609576

610-
/// For the operations:
577+
// TODO(lorrens): This can be moved to `helpers` when we implement the other intrinsics.
578+
/// For the intrinsics:
611579
/// - sinf32, sinf64
612580
/// - cosf32, cosf64
613581
/// - expf32, expf64, exp2f32, exp2f64
614582
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
583+
/// - powf32, powf64
615584
///
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.
618587
fn fixed_float_value<S: Semantics>(
619588
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
625595
let one = IeeeFloat::<S>::one();
626-
match intrinsic_name {
596+
match (intrinsic_name, args) {
627597
// 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
631608
#[rustfmt::skip]
632-
"logf32"
609+
("logf32"
633610
| "logf64"
634611
| "log10f32"
635612
| "log10f64"
636613
| "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
638626
_ => None,
639627
}
640628
}
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

Comments
 (0)