Skip to content

Commit dc357d5

Browse files
committed
Auto merge of #145116 - Kobzol:revert-143906, r=lqd
Revert #143906 This reverts commit 71f04692c32e181ab566c01942f1418dec8662d4, reversing changes made to 995ca3e532b48b689567533e6b736675e38b741e. Reverts rust-lang/rust#143906, which was merged in rust-lang/rust#145043. It seems like it is causing test failures on CI that block merges (rust-lang/rust#144787 (comment). try-job: x86_64-gnu-aux
2 parents 3b733da + f792378 commit dc357d5

File tree

5 files changed

+312
-513
lines changed

5 files changed

+312
-513
lines changed

src/intrinsics/mod.rs

Lines changed: 164 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,20 @@
33
mod atomic;
44
mod simd;
55

6+
use std::ops::Neg;
7+
68
use rand::Rng;
79
use rustc_abi::Size;
10+
use rustc_apfloat::ieee::{IeeeFloat, Semantics};
811
use rustc_apfloat::{self, Float, Round};
912
use rustc_middle::mir;
10-
use rustc_middle::ty::{self, FloatTy};
13+
use rustc_middle::ty::{self, FloatTy, ScalarInt};
1114
use rustc_span::{Symbol, sym};
1215

1316
use self::atomic::EvalContextExt as _;
1417
use self::helpers::{ToHost, ToSoft};
1518
use self::simd::EvalContextExt as _;
19+
use crate::math::{IeeeExt, apply_random_float_error_ulp};
1620
use crate::*;
1721

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

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

224228
// Apply a relative error of 4ULP to introduce some non-determinism
225229
// simulating imprecise implementations and optimizations.
226-
let res = math::apply_random_float_error_ulp(
230+
let res = apply_random_float_error_ulp(
227231
this,
228232
res,
229233
2, // log2(4)
230234
);
231235

232236
// Clamp the result to the guaranteed range of this function according to the C standard,
233237
// if any.
234-
math::clamp_float_value(intrinsic_name, res)
238+
clamp_float_value(intrinsic_name, res)
235239
});
236240
let res = this.adjust_nan(res, &[f]);
237241
this.write_scalar(res, dest)?;
@@ -249,7 +253,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
249253
let [f] = check_intrinsic_arg_count(args)?;
250254
let f = this.read_scalar(f)?.to_f64()?;
251255

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

268272
// Apply a relative error of 4ULP to introduce some non-determinism
269273
// simulating imprecise implementations and optimizations.
270-
let res = math::apply_random_float_error_ulp(
274+
let res = apply_random_float_error_ulp(
271275
this,
272276
res,
273277
2, // log2(4)
274278
);
275279

276280
// Clamp the result to the guaranteed range of this function according to the C standard,
277281
// if any.
278-
math::clamp_float_value(intrinsic_name, res)
282+
clamp_float_value(intrinsic_name, res)
279283
});
280284
let res = this.adjust_nan(res, &[f]);
281285
this.write_scalar(res, dest)?;
@@ -326,17 +330,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
326330
let f1 = this.read_scalar(f1)?.to_f32()?;
327331
let f2 = this.read_scalar(f2)?.to_f32()?;
328332

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

334-
// Apply a relative error of 4ULP to introduce some non-determinism
335-
// simulating imprecise implementations and optimizations.
336-
math::apply_random_float_error_ulp(
337-
this, res, 2, // log2(4)
338-
)
339-
});
337+
// Apply a relative error of 4ULP to introduce some non-determinism
338+
// simulating imprecise implementations and optimizations.
339+
apply_random_float_error_ulp(
340+
this, res, 2, // log2(4)
341+
)
342+
});
340343
let res = this.adjust_nan(res, &[f1, f2]);
341344
this.write_scalar(res, dest)?;
342345
}
@@ -345,17 +348,16 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
345348
let f1 = this.read_scalar(f1)?.to_f64()?;
346349
let f2 = this.read_scalar(f2)?.to_f64()?;
347350

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

353-
// Apply a relative error of 4ULP to introduce some non-determinism
354-
// simulating imprecise implementations and optimizations.
355-
math::apply_random_float_error_ulp(
356-
this, res, 2, // log2(4)
357-
)
358-
});
355+
// Apply a relative error of 4ULP to introduce some non-determinism
356+
// simulating imprecise implementations and optimizations.
357+
apply_random_float_error_ulp(
358+
this, res, 2, // log2(4)
359+
)
360+
});
359361
let res = this.adjust_nan(res, &[f1, f2]);
360362
this.write_scalar(res, dest)?;
361363
}
@@ -365,13 +367,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
365367
let f = this.read_scalar(f)?.to_f32()?;
366368
let i = this.read_scalar(i)?.to_i32()?;
367369

368-
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
370+
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
369371
// Using host floats (but it's fine, this operation does not have guaranteed precision).
370372
let res = f.to_host().powi(i).to_soft();
371373

372374
// Apply a relative error of 4ULP to introduce some non-determinism
373375
// simulating imprecise implementations and optimizations.
374-
math::apply_random_float_error_ulp(
376+
apply_random_float_error_ulp(
375377
this, res, 2, // log2(4)
376378
)
377379
});
@@ -383,13 +385,13 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
383385
let f = this.read_scalar(f)?.to_f64()?;
384386
let i = this.read_scalar(i)?.to_i32()?;
385387

386-
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
388+
let res = fixed_powi_float_value(this, f, i).unwrap_or_else(|| {
387389
// Using host floats (but it's fine, this operation does not have guaranteed precision).
388390
let res = f.to_host().powi(i).to_soft();
389391

390392
// Apply a relative error of 4ULP to introduce some non-determinism
391393
// simulating imprecise implementations and optimizations.
392-
math::apply_random_float_error_ulp(
394+
apply_random_float_error_ulp(
393395
this, res, 2, // log2(4)
394396
)
395397
});
@@ -446,7 +448,7 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
446448
}
447449
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
448450
// due to optimizations.
449-
let res = math::apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
451+
let res = apply_random_float_error_to_imm(this, res, 2 /* log2(4) */)?;
450452
this.write_immediate(*res, dest)?;
451453
}
452454

@@ -483,3 +485,133 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
483485
interp_ok(EmulateItemResult::NeedsReturn)
484486
}
485487
}
488+
489+
/// Applies a random ULP floating point error to `val` and returns the new value.
490+
/// So if you want an X ULP error, `ulp_exponent` should be log2(X).
491+
///
492+
/// Will fail if `val` is not a floating point number.
493+
fn apply_random_float_error_to_imm<'tcx>(
494+
ecx: &mut MiriInterpCx<'tcx>,
495+
val: ImmTy<'tcx>,
496+
ulp_exponent: u32,
497+
) -> InterpResult<'tcx, ImmTy<'tcx>> {
498+
let scalar = val.to_scalar_int()?;
499+
let res: ScalarInt = match val.layout.ty.kind() {
500+
ty::Float(FloatTy::F16) =>
501+
apply_random_float_error_ulp(ecx, scalar.to_f16(), ulp_exponent).into(),
502+
ty::Float(FloatTy::F32) =>
503+
apply_random_float_error_ulp(ecx, scalar.to_f32(), ulp_exponent).into(),
504+
ty::Float(FloatTy::F64) =>
505+
apply_random_float_error_ulp(ecx, scalar.to_f64(), ulp_exponent).into(),
506+
ty::Float(FloatTy::F128) =>
507+
apply_random_float_error_ulp(ecx, scalar.to_f128(), ulp_exponent).into(),
508+
_ => bug!("intrinsic called with non-float input type"),
509+
};
510+
511+
interp_ok(ImmTy::from_scalar_int(res, val.layout))
512+
}
513+
514+
/// For the intrinsics:
515+
/// - sinf32, sinf64
516+
/// - cosf32, cosf64
517+
/// - expf32, expf64, exp2f32, exp2f64
518+
/// - logf32, logf64, log2f32, log2f64, log10f32, log10f64
519+
/// - powf32, powf64
520+
///
521+
/// # Return
522+
///
523+
/// Returns `Some(output)` if the `intrinsic` results in a defined fixed `output` specified in the C standard
524+
/// (specifically, C23 annex F.10) when given `args` as arguments. Outputs that are unaffected by a relative error
525+
/// (such as INF and zero) are not handled here, they are assumed to be handled by the underlying
526+
/// implementation. Returns `None` if no specific value is guaranteed.
527+
///
528+
/// # Note
529+
///
530+
/// For `powf*` operations of the form:
531+
///
532+
/// - `(SNaN)^(±0)`
533+
/// - `1^(SNaN)`
534+
///
535+
/// The result is implementation-defined:
536+
/// - musl returns for both `1.0`
537+
/// - glibc returns for both `NaN`
538+
///
539+
/// This discrepancy exists because SNaN handling is not consistently defined across platforms,
540+
/// and the C standard leaves behavior for SNaNs unspecified.
541+
///
542+
/// Miri chooses to adhere to both implementations and returns either one of them non-deterministically.
543+
fn fixed_float_value<S: Semantics>(
544+
ecx: &mut MiriInterpCx<'_>,
545+
intrinsic_name: &str,
546+
args: &[IeeeFloat<S>],
547+
) -> Option<IeeeFloat<S>> {
548+
let one = IeeeFloat::<S>::one();
549+
Some(match (intrinsic_name, args) {
550+
// cos(+- 0) = 1
551+
("cosf32" | "cosf64", [input]) if input.is_zero() => one,
552+
553+
// e^0 = 1
554+
("expf32" | "expf64" | "exp2f32" | "exp2f64", [input]) if input.is_zero() => one,
555+
556+
// (-1)^(±INF) = 1
557+
("powf32" | "powf64", [base, exp]) if *base == -one && exp.is_infinite() => one,
558+
559+
// 1^y = 1 for any y, even a NaN
560+
("powf32" | "powf64", [base, exp]) if *base == one => {
561+
let rng = ecx.machine.rng.get_mut();
562+
// SNaN exponents get special treatment: they might return 1, or a NaN.
563+
let return_nan = exp.is_signaling() && ecx.machine.float_nondet && rng.random();
564+
// Handle both the musl and glibc cases non-deterministically.
565+
if return_nan { ecx.generate_nan(args) } else { one }
566+
}
567+
568+
// x^(±0) = 1 for any x, even a NaN
569+
("powf32" | "powf64", [base, exp]) if exp.is_zero() => {
570+
let rng = ecx.machine.rng.get_mut();
571+
// SNaN bases get special treatment: they might return 1, or a NaN.
572+
let return_nan = base.is_signaling() && ecx.machine.float_nondet && rng.random();
573+
// Handle both the musl and glibc cases non-deterministically.
574+
if return_nan { ecx.generate_nan(args) } else { one }
575+
}
576+
577+
// There are a lot of cases for fixed outputs according to the C Standard, but these are
578+
// mainly INF or zero which are not affected by the applied error.
579+
_ => return None,
580+
})
581+
}
582+
583+
/// Returns `Some(output)` if `powi` (called `pown` in C) results in a fixed value specified in the
584+
/// C standard (specifically, C23 annex F.10.4.6) when doing `base^exp`. Otherwise, returns `None`.
585+
fn fixed_powi_float_value<S: Semantics>(
586+
ecx: &mut MiriInterpCx<'_>,
587+
base: IeeeFloat<S>,
588+
exp: i32,
589+
) -> Option<IeeeFloat<S>> {
590+
Some(match exp {
591+
0 => {
592+
let one = IeeeFloat::<S>::one();
593+
let rng = ecx.machine.rng.get_mut();
594+
let return_nan = ecx.machine.float_nondet && rng.random() && base.is_signaling();
595+
// For SNaN treatment, we are consistent with `powf`above.
596+
// (We wouldn't have two, unlike powf all implementations seem to agree for powi,
597+
// but for now we are maximally conservative.)
598+
if return_nan { ecx.generate_nan(&[base]) } else { one }
599+
}
600+
601+
_ => return None,
602+
})
603+
}
604+
605+
/// Given an floating-point operation and a floating-point value, clamps the result to the output
606+
/// range of the given operation.
607+
fn clamp_float_value<S: Semantics>(intrinsic_name: &str, val: IeeeFloat<S>) -> IeeeFloat<S> {
608+
match intrinsic_name {
609+
// sin and cos: [-1, 1]
610+
"sinf32" | "cosf32" | "sinf64" | "cosf64" =>
611+
val.clamp(IeeeFloat::<S>::one().neg(), IeeeFloat::<S>::one()),
612+
// exp: [0, +INF]
613+
"expf32" | "exp2f32" | "expf64" | "exp2f64" =>
614+
IeeeFloat::<S>::maximum(val, IeeeFloat::<S>::ZERO),
615+
_ => val,
616+
}
617+
}

src/lib.rs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
#![feature(derive_coerce_pointee)]
1919
#![feature(arbitrary_self_types)]
2020
#![feature(iter_advance_by)]
21-
#![feature(f16)]
22-
#![feature(f128)]
2321
// Configure clippy and other lints
2422
#![allow(
2523
clippy::collapsible_else_if,

0 commit comments

Comments
 (0)