Skip to content

Commit f4d65ad

Browse files
deduplicate code of pow functions to use a macro instead
1 parent 86bb950 commit f4d65ad

File tree

1 file changed

+62
-90
lines changed
  • src/tools/miri/src/intrinsics

1 file changed

+62
-90
lines changed

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

Lines changed: 62 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ use std::ops::Neg;
88
use rand::Rng;
99
use rustc_abi::Size;
1010
use rustc_apfloat::ieee::{Double, IeeeFloat, Semantics, Single};
11-
use rustc_apfloat::{self, Category, Float, Round};
11+
use rustc_apfloat::{self, Float, Round};
1212
use rustc_middle::mir;
1313
use rustc_middle::ty::{self, FloatTy, ScalarInt};
1414
use rustc_span::{Symbol, sym};
@@ -19,6 +19,63 @@ 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+
2279
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
2380
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
2481
fn call_intrinsic(
@@ -385,28 +442,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
385442
let f1 = this.read_scalar(f1)?.to_f32()?;
386443
let f2 = this.read_scalar(f2)?.to_f32()?;
387444

388-
let fixed_res = match (f1.category(), f2.category()) {
389-
// 1^y = 1 for any y, even a NaN.
390-
(Category::Normal, _) if f1 == 1.0f32.to_soft() => Some(1.0f32.to_soft()),
391-
392-
// (-1)^(±INF) = 1
393-
(Category::Normal, Category::Infinity) if f1 == (-1.0f32).to_soft() =>
394-
Some(1.0f32.to_soft()),
395-
396-
// x^(±0) = 1 for any x, even a NaN
397-
(_, Category::Zero) => Some(1.0f32.to_soft()),
398-
399-
_ => None,
400-
};
401-
let res = fixed_res.unwrap_or_else(|| {
402-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
403-
let res = f1.to_host().powf(f2.to_host()).to_soft();
404-
// Apply a relative error of 4ULP to introduce some non-determinism
405-
// simulating imprecise implementations and optimizations.
406-
apply_random_float_error_ulp(
407-
this, res, 2, // log2(4)
408-
)
409-
});
445+
let res = pow_impl!(this, powf(f1, f2));
410446
let res = this.adjust_nan(res, &[f1, f2]);
411447
this.write_scalar(res, dest)?;
412448
}
@@ -415,30 +451,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
415451
let f1 = this.read_scalar(f1)?.to_f64()?;
416452
let f2 = this.read_scalar(f2)?.to_f64()?;
417453

418-
let fixed_res = match (f1.category(), f2.category()) {
419-
// 1^y = 1 for any y even a NaN.
420-
(Category::Normal, _) if f1 == 1.0f64.to_soft() => Some(1.0f64.to_soft()),
421-
422-
// (-1)^(±INF) = 1
423-
(Category::Normal, Category::Infinity) if f1 == (-1.0f64).to_soft() =>
424-
Some(1.0f64.to_soft()),
425-
426-
// x^(±0) = 1 for any x, even a NaN
427-
(_, Category::Zero) => Some(1.0f64.to_soft()),
428-
429-
// TODO: pow has a lot of "edge" cases which mostly result in ±0 or ±INF
430-
// do we have to catch them all?
431-
_ => None,
432-
};
433-
let res = fixed_res.unwrap_or_else(|| {
434-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
435-
let res = f1.to_host().powf(f2.to_host()).to_soft();
436-
// Apply a relative error of 4ULP to introduce some non-determinism
437-
// simulating imprecise implementations and optimizations.
438-
apply_random_float_error_ulp(
439-
this, res, 2, // log2(4)
440-
)
441-
});
454+
let res = pow_impl!(this, powf(f1, f2));
442455
let res = this.adjust_nan(res, &[f1, f2]);
443456
this.write_scalar(res, dest)?;
444457
}
@@ -448,27 +461,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
448461
let f = this.read_scalar(f)?.to_f32()?;
449462
let i = this.read_scalar(i)?.to_i32()?;
450463

451-
let fixed_res = match (f.category(), i) {
452-
// Standard specifies we can only fix to 1 if input is is not a Signaling NaN.
453-
(_, 0) if !f.is_signaling() => Some(1.0f32.to_soft()),
454-
455-
// TODO: Isn't this done by the implementation? And ULP error on 0.0 doesn't have an effect
456-
(Category::Zero, x) if x % 2 == 0 => Some(0.0f32.to_soft()),
457-
458-
// ±0^x = ±0 with x an odd integer.
459-
(Category::Zero, x) if x % 2 != 0 => Some(f), // preserve sign of zero.
460-
_ => None,
461-
};
462-
let res = fixed_res.unwrap_or_else(|| {
463-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
464-
let res = f.to_host().powi(i).to_soft();
465-
466-
// Apply a relative error of 4ULP to introduce some non-determinism
467-
// simulating imprecise implementations and optimizations.
468-
apply_random_float_error_ulp(
469-
this, res, 2, // log2(4)
470-
)
471-
});
464+
let res = pow_impl!(this, powi(f, i));
472465
let res = this.adjust_nan(res, &[f]);
473466
this.write_scalar(res, dest)?;
474467
}
@@ -477,28 +470,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
477470
let f = this.read_scalar(f)?.to_f64()?;
478471
let i = this.read_scalar(i)?.to_i32()?;
479472

480-
let fixed_res = match (f.category(), i) {
481-
// Standard specifies we can only fix to 1 if input is is not a Signaling NaN.
482-
(_, 0) if !f.is_signaling() => Some(1.0f64.to_soft()),
483-
484-
// ±0^x = 0 with x an even integer.
485-
// TODO: Isn't this done by the implementation itself?
486-
(Category::Zero, x) if x % 2 == 0 => Some(0.0f64.to_soft()),
487-
488-
// ±0^x = ±0 with x an odd integer.
489-
(Category::Zero, x) if x % 2 != 0 => Some(f), // preserve sign of zero.
490-
_ => None,
491-
};
492-
let res = fixed_res.unwrap_or_else(|| {
493-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
494-
let res = f.to_host().powi(i).to_soft();
495-
496-
// Apply a relative error of 4ULP to introduce some non-determinism
497-
// simulating imprecise implementations and optimizations.
498-
apply_random_float_error_ulp(
499-
this, res, 2, // log2(4)
500-
)
501-
});
473+
let res = pow_impl!(this, powi(f, i));
502474
let res = this.adjust_nan(res, &[f]);
503475
this.write_scalar(res, dest)?;
504476
}

0 commit comments

Comments
 (0)