Skip to content

Commit 3ad5863

Browse files
authored
Merge pull request #4484 from RalfJung/math-shims
move math shims to their own files, and some refactoring in fixed_float_value
2 parents 4864027 + 57ecfc6 commit 3ad5863

File tree

6 files changed

+603
-532
lines changed

6 files changed

+603
-532
lines changed

src/intrinsics/math.rs

Lines changed: 311 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,311 @@
1+
use rand::Rng;
2+
use rustc_apfloat::{self, Float, Round};
3+
use rustc_middle::mir;
4+
use rustc_middle::ty::{self, FloatTy};
5+
6+
use self::helpers::{ToHost, ToSoft};
7+
use super::check_intrinsic_arg_count;
8+
use crate::*;
9+
10+
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
11+
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
12+
fn emulate_math_intrinsic(
13+
&mut self,
14+
intrinsic_name: &str,
15+
_generic_args: ty::GenericArgsRef<'tcx>,
16+
args: &[OpTy<'tcx>],
17+
dest: &MPlaceTy<'tcx>,
18+
) -> InterpResult<'tcx, EmulateItemResult> {
19+
let this = self.eval_context_mut();
20+
21+
match intrinsic_name {
22+
// Operations we can do with soft-floats.
23+
"sqrtf32" => {
24+
let [f] = check_intrinsic_arg_count(args)?;
25+
let f = this.read_scalar(f)?.to_f32()?;
26+
// Sqrt is specified to be fully precise.
27+
let res = math::sqrt(f);
28+
let res = this.adjust_nan(res, &[f]);
29+
this.write_scalar(res, dest)?;
30+
}
31+
"sqrtf64" => {
32+
let [f] = check_intrinsic_arg_count(args)?;
33+
let f = this.read_scalar(f)?.to_f64()?;
34+
// Sqrt is specified to be fully precise.
35+
let res = math::sqrt(f);
36+
let res = this.adjust_nan(res, &[f]);
37+
this.write_scalar(res, dest)?;
38+
}
39+
40+
"fmaf32" => {
41+
let [a, b, c] = check_intrinsic_arg_count(args)?;
42+
let a = this.read_scalar(a)?.to_f32()?;
43+
let b = this.read_scalar(b)?.to_f32()?;
44+
let c = this.read_scalar(c)?.to_f32()?;
45+
let res = a.mul_add(b, c).value;
46+
let res = this.adjust_nan(res, &[a, b, c]);
47+
this.write_scalar(res, dest)?;
48+
}
49+
"fmaf64" => {
50+
let [a, b, c] = check_intrinsic_arg_count(args)?;
51+
let a = this.read_scalar(a)?.to_f64()?;
52+
let b = this.read_scalar(b)?.to_f64()?;
53+
let c = this.read_scalar(c)?.to_f64()?;
54+
let res = a.mul_add(b, c).value;
55+
let res = this.adjust_nan(res, &[a, b, c]);
56+
this.write_scalar(res, dest)?;
57+
}
58+
59+
"fmuladdf32" => {
60+
let [a, b, c] = check_intrinsic_arg_count(args)?;
61+
let a = this.read_scalar(a)?.to_f32()?;
62+
let b = this.read_scalar(b)?.to_f32()?;
63+
let c = this.read_scalar(c)?.to_f32()?;
64+
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
65+
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
66+
let res = this.adjust_nan(res, &[a, b, c]);
67+
this.write_scalar(res, dest)?;
68+
}
69+
"fmuladdf64" => {
70+
let [a, b, c] = check_intrinsic_arg_count(args)?;
71+
let a = this.read_scalar(a)?.to_f64()?;
72+
let b = this.read_scalar(b)?.to_f64()?;
73+
let c = this.read_scalar(c)?.to_f64()?;
74+
let fuse: bool = this.machine.float_nondet && this.machine.rng.get_mut().random();
75+
let res = if fuse { a.mul_add(b, c).value } else { ((a * b).value + c).value };
76+
let res = this.adjust_nan(res, &[a, b, c]);
77+
this.write_scalar(res, dest)?;
78+
}
79+
80+
#[rustfmt::skip]
81+
| "fadd_fast"
82+
| "fsub_fast"
83+
| "fmul_fast"
84+
| "fdiv_fast"
85+
| "frem_fast"
86+
=> {
87+
let [a, b] = check_intrinsic_arg_count(args)?;
88+
let a = this.read_immediate(a)?;
89+
let b = this.read_immediate(b)?;
90+
let op = match intrinsic_name {
91+
"fadd_fast" => mir::BinOp::Add,
92+
"fsub_fast" => mir::BinOp::Sub,
93+
"fmul_fast" => mir::BinOp::Mul,
94+
"fdiv_fast" => mir::BinOp::Div,
95+
"frem_fast" => mir::BinOp::Rem,
96+
_ => bug!(),
97+
};
98+
let float_finite = |x: &ImmTy<'tcx>| -> InterpResult<'tcx, bool> {
99+
let ty::Float(fty) = x.layout.ty.kind() else {
100+
bug!("float_finite: non-float input type {}", x.layout.ty)
101+
};
102+
interp_ok(match fty {
103+
FloatTy::F16 => x.to_scalar().to_f16()?.is_finite(),
104+
FloatTy::F32 => x.to_scalar().to_f32()?.is_finite(),
105+
FloatTy::F64 => x.to_scalar().to_f64()?.is_finite(),
106+
FloatTy::F128 => x.to_scalar().to_f128()?.is_finite(),
107+
})
108+
};
109+
match (float_finite(&a)?, float_finite(&b)?) {
110+
(false, false) => throw_ub_format!(
111+
"`{intrinsic_name}` intrinsic called with non-finite value as both parameters",
112+
),
113+
(false, _) => throw_ub_format!(
114+
"`{intrinsic_name}` intrinsic called with non-finite value as first parameter",
115+
),
116+
(_, false) => throw_ub_format!(
117+
"`{intrinsic_name}` intrinsic called with non-finite value as second parameter",
118+
),
119+
_ => {}
120+
}
121+
let res = this.binary_op(op, &a, &b)?;
122+
// This cannot be a NaN so we also don't have to apply any non-determinism.
123+
// (Also, `binary_op` already called `generate_nan` if needed.)
124+
if !float_finite(&res)? {
125+
throw_ub_format!("`{intrinsic_name}` intrinsic produced non-finite value as result");
126+
}
127+
// Apply a relative error of 4ULP to simulate non-deterministic precision loss
128+
// due to optimizations.
129+
let res = math::apply_random_float_error_to_imm(this, res, 4)?;
130+
this.write_immediate(*res, dest)?;
131+
}
132+
133+
"float_to_int_unchecked" => {
134+
let [val] = check_intrinsic_arg_count(args)?;
135+
let val = this.read_immediate(val)?;
136+
137+
let res = this
138+
.float_to_int_checked(&val, dest.layout, Round::TowardZero)?
139+
.ok_or_else(|| {
140+
err_ub_format!(
141+
"`float_to_int_unchecked` intrinsic called on {val} which cannot be represented in target type `{:?}`",
142+
dest.layout.ty
143+
)
144+
})?;
145+
146+
this.write_immediate(*res, dest)?;
147+
}
148+
149+
// Operations that need host floats.
150+
#[rustfmt::skip]
151+
| "sinf32"
152+
| "cosf32"
153+
| "expf32"
154+
| "exp2f32"
155+
| "logf32"
156+
| "log10f32"
157+
| "log2f32"
158+
=> {
159+
let [f] = check_intrinsic_arg_count(args)?;
160+
let f = this.read_scalar(f)?.to_f32()?;
161+
162+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
163+
// Using host floats (but it's fine, these operations do not have
164+
// guaranteed precision).
165+
let host = f.to_host();
166+
let res = match intrinsic_name {
167+
"sinf32" => host.sin(),
168+
"cosf32" => host.cos(),
169+
"expf32" => host.exp(),
170+
"exp2f32" => host.exp2(),
171+
"logf32" => host.ln(),
172+
"log10f32" => host.log10(),
173+
"log2f32" => host.log2(),
174+
_ => bug!(),
175+
};
176+
let res = res.to_soft();
177+
178+
// Apply a relative error of 4ULP to introduce some non-determinism
179+
// simulating imprecise implementations and optimizations.
180+
let res = math::apply_random_float_error_ulp(
181+
this,
182+
res,
183+
4,
184+
);
185+
186+
// Clamp the result to the guaranteed range of this function according to the C standard,
187+
// if any.
188+
math::clamp_float_value(intrinsic_name, res)
189+
});
190+
let res = this.adjust_nan(res, &[f]);
191+
this.write_scalar(res, dest)?;
192+
}
193+
194+
#[rustfmt::skip]
195+
| "sinf64"
196+
| "cosf64"
197+
| "expf64"
198+
| "exp2f64"
199+
| "logf64"
200+
| "log10f64"
201+
| "log2f64"
202+
=> {
203+
let [f] = check_intrinsic_arg_count(args)?;
204+
let f = this.read_scalar(f)?.to_f64()?;
205+
206+
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
207+
// Using host floats (but it's fine, these operations do not have
208+
// guaranteed precision).
209+
let host = f.to_host();
210+
let res = match intrinsic_name {
211+
"sinf64" => host.sin(),
212+
"cosf64" => host.cos(),
213+
"expf64" => host.exp(),
214+
"exp2f64" => host.exp2(),
215+
"logf64" => host.ln(),
216+
"log10f64" => host.log10(),
217+
"log2f64" => host.log2(),
218+
_ => bug!(),
219+
};
220+
let res = res.to_soft();
221+
222+
// Apply a relative error of 4ULP to introduce some non-determinism
223+
// simulating imprecise implementations and optimizations.
224+
let res = math::apply_random_float_error_ulp(
225+
this,
226+
res,
227+
4,
228+
);
229+
230+
// Clamp the result to the guaranteed range of this function according to the C standard,
231+
// if any.
232+
math::clamp_float_value(intrinsic_name, res)
233+
});
234+
let res = this.adjust_nan(res, &[f]);
235+
this.write_scalar(res, dest)?;
236+
}
237+
238+
"powf32" => {
239+
let [f1, f2] = check_intrinsic_arg_count(args)?;
240+
let f1 = this.read_scalar(f1)?.to_f32()?;
241+
let f2 = this.read_scalar(f2)?.to_f32()?;
242+
243+
let res =
244+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
245+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
246+
let res = f1.to_host().powf(f2.to_host()).to_soft();
247+
248+
// Apply a relative error of 4ULP to introduce some non-determinism
249+
// simulating imprecise implementations and optimizations.
250+
math::apply_random_float_error_ulp(this, res, 4)
251+
});
252+
let res = this.adjust_nan(res, &[f1, f2]);
253+
this.write_scalar(res, dest)?;
254+
}
255+
"powf64" => {
256+
let [f1, f2] = check_intrinsic_arg_count(args)?;
257+
let f1 = this.read_scalar(f1)?.to_f64()?;
258+
let f2 = this.read_scalar(f2)?.to_f64()?;
259+
260+
let res =
261+
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
262+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
263+
let res = f1.to_host().powf(f2.to_host()).to_soft();
264+
265+
// Apply a relative error of 4ULP to introduce some non-determinism
266+
// simulating imprecise implementations and optimizations.
267+
math::apply_random_float_error_ulp(this, res, 4)
268+
});
269+
let res = this.adjust_nan(res, &[f1, f2]);
270+
this.write_scalar(res, dest)?;
271+
}
272+
273+
"powif32" => {
274+
let [f, i] = check_intrinsic_arg_count(args)?;
275+
let f = this.read_scalar(f)?.to_f32()?;
276+
let i = this.read_scalar(i)?.to_i32()?;
277+
278+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
279+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
280+
let res = f.to_host().powi(i).to_soft();
281+
282+
// Apply a relative error of 4ULP to introduce some non-determinism
283+
// simulating imprecise implementations and optimizations.
284+
math::apply_random_float_error_ulp(this, res, 4)
285+
});
286+
let res = this.adjust_nan(res, &[f]);
287+
this.write_scalar(res, dest)?;
288+
}
289+
"powif64" => {
290+
let [f, i] = check_intrinsic_arg_count(args)?;
291+
let f = this.read_scalar(f)?.to_f64()?;
292+
let i = this.read_scalar(i)?.to_i32()?;
293+
294+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
295+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
296+
let res = f.to_host().powi(i).to_soft();
297+
298+
// Apply a relative error of 4ULP to introduce some non-determinism
299+
// simulating imprecise implementations and optimizations.
300+
math::apply_random_float_error_ulp(this, res, 4)
301+
});
302+
let res = this.adjust_nan(res, &[f]);
303+
this.write_scalar(res, dest)?;
304+
}
305+
306+
_ => return interp_ok(EmulateItemResult::NotSupported),
307+
}
308+
309+
interp_ok(EmulateItemResult::NeedsReturn)
310+
}
311+
}

0 commit comments

Comments
 (0)