Skip to content

Commit fd479ca

Browse files
Move float non determinism helpers to math.rs
1 parent 71289c3 commit fd479ca

File tree

2 files changed

+140
-134
lines changed

2 files changed

+140
-134
lines changed

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

Lines changed: 27 additions & 133 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,8 @@
33
mod atomic;
44
mod simd;
55

6-
use std::ops::Neg;
7-
86
use rand::Rng;
97
use rustc_abi::Size;
10-
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
118
use rustc_apfloat::{self, Float, Round};
129
use rustc_middle::mir;
1310
use rustc_middle::ty::{self, FloatTy};
@@ -16,7 +13,7 @@ use rustc_span::{Symbol, sym};
1613
use self::atomic::EvalContextExt as _;
1714
use self::helpers::{ToHost, ToSoft};
1815
use self::simd::EvalContextExt as _;
19-
use crate::math::{IeeeExt, apply_random_float_error_ulp};
16+
use crate::math::apply_random_float_error_ulp;
2017
use crate::*;
2118

2219
/// Check that the number of args is what we expect.
@@ -209,7 +206,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
209206
let [f] = check_intrinsic_arg_count(args)?;
210207
let f = this.read_scalar(f)?.to_f32()?;
211208

212-
let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
209+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
213210
// Using host floats (but it's fine, these operations do not have
214211
// guaranteed precision).
215212
let host = f.to_host();
@@ -227,15 +224,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
227224

228225
// Apply a relative error of 4ULP to introduce some non-determinism
229226
// simulating imprecise implementations and optimizations.
230-
let res = apply_random_float_error_ulp(
227+
let res = math::apply_random_float_error_ulp(
231228
this,
232229
res,
233230
4,
234231
);
235232

236233
// Clamp the result to the guaranteed range of this function according to the C standard,
237234
// if any.
238-
clamp_float_value(intrinsic_name, res)
235+
math::clamp_float_value(intrinsic_name, res)
239236
});
240237
let res = this.adjust_nan(res, &[f]);
241238
this.write_scalar(res, dest)?;
@@ -253,7 +250,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
253250
let [f] = check_intrinsic_arg_count(args)?;
254251
let f = this.read_scalar(f)?.to_f64()?;
255252

256-
let res = fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
253+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
257254
// Using host floats (but it's fine, these operations do not have
258255
// guaranteed precision).
259256
let host = f.to_host();
@@ -271,15 +268,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
271268

272269
// Apply a relative error of 4ULP to introduce some non-determinism
273270
// simulating imprecise implementations and optimizations.
274-
let res = apply_random_float_error_ulp(
271+
let res = math::apply_random_float_error_ulp(
275272
this,
276273
res,
277274
4,
278275
);
279276

280277
// Clamp the result to the guaranteed range of this function according to the C standard,
281278
// if any.
282-
clamp_float_value(intrinsic_name, res)
279+
math::clamp_float_value(intrinsic_name, res)
283280
});
284281
let res = this.adjust_nan(res, &[f]);
285282
this.write_scalar(res, dest)?;
@@ -330,14 +327,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
330327
let f1 = this.read_scalar(f1)?.to_f32()?;
331328
let f2 = this.read_scalar(f2)?.to_f32()?;
332329

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();
330+
let res =
331+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
332+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
333+
let res = f1.to_host().powf(f2.to_host()).to_soft();
336334

337-
// Apply a relative error of 4ULP to introduce some non-determinism
338-
// simulating imprecise implementations and optimizations.
339-
apply_random_float_error_ulp(this, res, 4)
340-
});
335+
// Apply a relative error of 4ULP to introduce some non-determinism
336+
// simulating imprecise implementations and optimizations.
337+
math::apply_random_float_error_ulp(this, res, 4)
338+
});
341339
let res = this.adjust_nan(res, &[f1, f2]);
342340
this.write_scalar(res, dest)?;
343341
}
@@ -346,14 +344,15 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
346344
let f1 = this.read_scalar(f1)?.to_f64()?;
347345
let f2 = this.read_scalar(f2)?.to_f64()?;
348346

349-
let res = fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
350-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
351-
let res = f1.to_host().powf(f2.to_host()).to_soft();
347+
let res =
348+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
349+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
350+
let res = f1.to_host().powf(f2.to_host()).to_soft();
352351

353-
// Apply a relative error of 4ULP to introduce some non-determinism
354-
// simulating imprecise implementations and optimizations.
355-
apply_random_float_error_ulp(this, res, 4)
356-
});
352+
// Apply a relative error of 4ULP to introduce some non-determinism
353+
// simulating imprecise implementations and optimizations.
354+
math::apply_random_float_error_ulp(this, res, 4)
355+
});
357356
let res = this.adjust_nan(res, &[f1, f2]);
358357
this.write_scalar(res, dest)?;
359358
}
@@ -363,7 +362,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
363362
let f = this.read_scalar(f)?.to_f32()?;
364363
let i = this.read_scalar(i)?.to_i32()?;
365364

366-
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
365+
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
367366
// Using host floats (but it's fine, this operation does not have guaranteed precision).
368367
let res = f.to_host().powi(i).to_soft();
369368

@@ -379,13 +378,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
379378
let f = this.read_scalar(f)?.to_f64()?;
380379
let i = this.read_scalar(i)?.to_i32()?;
381380

382-
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
381+
let res = math::fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
383382
// Using host floats (but it's fine, this operation does not have guaranteed precision).
384383
let res = f.to_host().powi(i).to_soft();
385384

386385
// Apply a relative error of 4ULP to introduce some non-determinism
387386
// simulating imprecise implementations and optimizations.
388-
apply_random_float_error_ulp(this, res, 4)
387+
math::apply_random_float_error_ulp(this, res, 4)
389388
});
390389
let res = this.adjust_nan(res, &[f]);
391390
this.write_scalar(res, dest)?;
@@ -440,7 +439,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
440439
}
441440
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
442441
// due to optimizations.
443-
let res = crate::math::apply_random_float_error_to_imm(this, res, 4)?;
442+
let res = math::apply_random_float_error_to_imm(this, res, 4)?;
444443
this.write_immediate(*res, dest)?;
445444
}
446445

@@ -477,108 +476,3 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
477476
interp_ok(EmulateItemResult::NeedsReturn)
478477
}
479478
}
480-
481-
/// For the intrinsics:
482-
/// - sinf32, sinf64
483-
/// - cosf32, cosf64
484-
/// - expf32, expf64, exp2f32, exp2f64
485-
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
486-
/// - powf32, powf64
487-
///
488-
/// # Return
489-
///
490-
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
491-
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
492-
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
493-
/// implementation. Returns `None` if no specific value is guaranteed.
494-
///
495-
/// # Note
496-
///
497-
/// For `powf*` operations of the form:
498-
///
499-
/// - `(SNaN)^(±0)`
500-
/// - `1^(SNaN)`
501-
///
502-
/// The result is implementation-defined:
503-
/// - musl returns for both `1.0`
504-
/// - glibc returns for both `NaN`
505-
///
506-
/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
507-
/// and the C standard leaves behavior for SNaNs unspecified.
508-
///
509-
/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
510-
fn fixed_float_value<S: Semantics>(
511-
ecx: &mut MiriInterpCx<'_>,
512-
intrinsic_name: &str,
513-
args: &[IeeeFloat<S>],
514-
) -> Option<IeeeFloat<S>> {
515-
let one = IeeeFloat::<S>::one();
516-
Some(match (intrinsic_name, args) {
517-
// cos(+- 0) = 1
518-
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
519-
520-
// e^0 = 1
521-
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
522-
523-
// (-1)^(±INF) = 1
524-
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
525-
526-
// 1^y = 1 for any y, even a NaN
527-
("powf32" | "powf64", [base, exp]) if *base == one => {
528-
let rng = ecx.machine.rng.get_mut();
529-
// SNaN exponents get special treatment: they might return 1, or a NaN.
530-
let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
531-
// Handle both the musl and glibc cases non-deterministically.
532-
if return_nan { ecx.generate_nan(args) } else { one }
533-
}
534-
535-
// x^(±0) = 1 for any x, even a NaN
536-
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
537-
let rng = ecx.machine.rng.get_mut();
538-
// SNaN bases get special treatment: they might return 1, or a NaN.
539-
let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
540-
// Handle both the musl and glibc cases non-deterministically.
541-
if return_nan { ecx.generate_nan(args) } else { one }
542-
}
543-
544-
// There are a lot of cases for fixed outputs according to the C Standard, but these are
545-
// mainly INF or zero which are not affected by the applied error.
546-
_ => return None,
547-
})
548-
}
549-
550-
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
551-
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
552-
fn fixed_powi_float_value<S: Semantics>(
553-
ecx: &mut MiriInterpCx<'_>,
554-
base: IeeeFloat<S>,
555-
exp: i32,
556-
) -> Option<IeeeFloat<S>> {
557-
Some(match exp {
558-
0 => {
559-
let one = IeeeFloat::<S>::one();
560-
let rng = ecx.machine.rng.get_mut();
561-
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
562-
// For SNaN treatment, we are consistent with `powf`above.
563-
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
564-
// but for now we are maximally conservative.)
565-
if return_nan { ecx.generate_nan(&[base]) } else { one }
566-
}
567-
568-
_ => return None,
569-
})
570-
}
571-
572-
/// Given an floating-point operation and a floating-point value, clamps the result to the output
573-
/// range of the given operation.
574-
fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
575-
match intrinsic_name {
576-
// sin and cos: [-1, 1]
577-
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
578-
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
579-
// exp: [0, +INF]
580-
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
581-
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
582-
_ => val,
583-
}
584-
}

src/tools/miri/src/math.rs

Lines changed: 113 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
1+
use std::ops::Neg;
2+
13
use rand::Rng as _;
24
use rustc_apfloat::Float as _;
3-
use rustc_apfloat::ieee::IeeeFloat;
5+
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
46
use rustc_middle::ty::{self, FloatTy, ScalarInt};
57

68
use crate::*;
@@ -105,6 +107,116 @@ pub(crate) fn apply_random_float_error_to_imm<'tcx>(
105107
interp_ok(ImmTy::from_scalar_int(res, val.layout))
106108
}
107109

110+
/// Given an floating-point operation and a floating-point value, clamps the result to the output
111+
/// range of the given operation.
112+
pub(crate) fn clamp_float_value<S: Semantics>(
113+
intrinsic_name: &str,
114+
val: IeeeFloat<S>,
115+
) -> IeeeFloat<S> {
116+
match intrinsic_name {
117+
// sin and cos: [-1, 1]
118+
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
119+
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
120+
// exp: [0, +INF]
121+
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
122+
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
123+
_ => val,
124+
}
125+
}
126+
127+
/// For the intrinsics:
128+
/// - sinf32, sinf64
129+
/// - cosf32, cosf64
130+
/// - expf32, expf64, exp2f32, exp2f64
131+
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
132+
/// - powf32, powf64
133+
///
134+
/// # Return
135+
///
136+
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
137+
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
138+
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
139+
/// implementation. Returns `None` if no specific value is guaranteed.
140+
///
141+
/// # Note
142+
///
143+
/// For `powf*` operations of the form:
144+
///
145+
/// - `(SNaN)^(±0)`
146+
/// - `1^(SNaN)`
147+
///
148+
/// The result is implementation-defined:
149+
/// - musl returns for both `1.0`
150+
/// - glibc returns for both `NaN`
151+
///
152+
/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
153+
/// and the C standard leaves behavior for SNaNs unspecified.
154+
///
155+
/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
156+
pub(crate) fn fixed_float_value<S: Semantics>(
157+
ecx: &mut MiriInterpCx<'_>,
158+
intrinsic_name: &str,
159+
args: &[IeeeFloat<S>],
160+
) -> Option<IeeeFloat<S>> {
161+
let this = ecx.eval_context_mut();
162+
let one = IeeeFloat::<S>::one();
163+
Some(match (intrinsic_name, args) {
164+
// cos(+- 0) = 1
165+
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
166+
167+
// e^0 = 1
168+
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
169+
170+
// (-1)^(±INF) = 1
171+
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
172+
173+
// 1^y = 1 for any y, even a NaN
174+
("powf32" | "powf64", [base, exp]) if *base == one => {
175+
let rng = this.machine.rng.get_mut();
176+
// SNaN exponents get special treatment: they might return 1, or a NaN.
177+
let return_nan = exp.is_signaling() && this.machine.float_nondet && rng.random();
178+
// Handle both the musl and glibc cases non-deterministically.
179+
if return_nan { this.generate_nan(args) } else { one }
180+
}
181+
182+
// x^(±0) = 1 for any x, even a NaN
183+
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
184+
let rng = this.machine.rng.get_mut();
185+
// SNaN bases get special treatment: they might return 1, or a NaN.
186+
let return_nan = base.is_signaling() && this.machine.float_nondet && rng.random();
187+
// Handle both the musl and glibc cases non-deterministically.
188+
if return_nan { this.generate_nan(args) } else { one }
189+
}
190+
191+
// There are a lot of cases for fixed outputs according to the C Standard, but these are
192+
// mainly INF or zero which are not affected by the applied error.
193+
_ => return None,
194+
})
195+
}
196+
197+
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
198+
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
199+
pub(crate) fn fixed_powi_float_value<S: Semantics>(
200+
ecx: &mut MiriInterpCx<'_>,
201+
base: IeeeFloat<S>,
202+
exp: i32,
203+
) -> Option<IeeeFloat<S>> {
204+
let this = ecx.eval_context_mut();
205+
Some(match exp {
206+
0 => {
207+
let one = IeeeFloat::<S>::one();
208+
let rng = this.machine.rng.get_mut();
209+
let return_nan = this.machine.float_nondet && rng.random() && base.is_signaling();
210+
// For SNaN treatment, we are consistent with `powf`above.
211+
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
212+
// but for now we are maximally conservative.)
213+
if return_nan { this.generate_nan(&[base]) } else { one }
214+
}
215+
216+
_ => return None,
217+
})
218+
}
219+
108220
pub(crate) fn sqrt<S: rustc_apfloat::ieee::Semantics>(x: IeeeFloat<S>) -> IeeeFloat<S> {
109221
match x.category() {
110222
// preserve zero sign

0 commit comments

Comments
 (0)