Skip to content

Commit f9ed8c2

Browse files
authored
Merge pull request #4895 from folkertdev/f16-support
add `f16` support
2 parents 54cd1d5 + 5f090d0 commit f9ed8c2

File tree

8 files changed

+508
-294
lines changed

8 files changed

+508
-294
lines changed

src/tools/miri/src/helpers.rs

Lines changed: 0 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ use std::{cmp, iter};
55

66
use rand::RngCore;
77
use rustc_abi::{Align, ExternAbi, FieldIdx, FieldsShape, Size, Variants};
8-
use rustc_apfloat::Float;
98
use rustc_data_structures::fx::{FxBuildHasher, FxHashSet};
109
use rustc_hir::Safety;
1110
use rustc_hir::def::{DefKind, Namespace};
@@ -165,50 +164,6 @@ pub fn iter_exported_symbols<'tcx>(
165164
interp_ok(())
166165
}
167166

168-
/// Convert a softfloat type to its corresponding hostfloat type.
169-
pub trait ToHost {
170-
type HostFloat;
171-
fn to_host(self) -> Self::HostFloat;
172-
}
173-
174-
/// Convert a hostfloat type to its corresponding softfloat type.
175-
pub trait ToSoft {
176-
type SoftFloat;
177-
fn to_soft(self) -> Self::SoftFloat;
178-
}
179-
180-
impl ToHost for rustc_apfloat::ieee::Double {
181-
type HostFloat = f64;
182-
183-
fn to_host(self) -> Self::HostFloat {
184-
f64::from_bits(self.to_bits().try_into().unwrap())
185-
}
186-
}
187-
188-
impl ToSoft for f64 {
189-
type SoftFloat = rustc_apfloat::ieee::Double;
190-
191-
fn to_soft(self) -> Self::SoftFloat {
192-
Float::from_bits(self.to_bits().into())
193-
}
194-
}
195-
196-
impl ToHost for rustc_apfloat::ieee::Single {
197-
type HostFloat = f32;
198-
199-
fn to_host(self) -> Self::HostFloat {
200-
f32::from_bits(self.to_bits().try_into().unwrap())
201-
}
202-
}
203-
204-
impl ToSoft for f32 {
205-
type SoftFloat = rustc_apfloat::ieee::Single;
206-
207-
fn to_soft(self) -> Self::SoftFloat {
208-
Float::from_bits(self.to_bits().into())
209-
}
210-
}
211-
212167
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
213168
pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
214169
/// Checks if the given crate/module exists.

src/tools/miri/src/intrinsics/math.rs

Lines changed: 90 additions & 157 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
use rustc_apfloat::ieee::{DoubleS, HalfS, IeeeFloat, Semantics, SingleS};
12
use rustc_apfloat::{self, Float, FloatConvert, Round};
23
use rustc_middle::mir;
34
use rustc_middle::ty::{self, FloatTy};
45

5-
use self::helpers::{ToHost, ToSoft};
6+
use self::math::{HostFloatOperation, HostUnaryFloatOp, IeeeExt, host_unary_float_op};
67
use super::check_intrinsic_arg_count;
78
use crate::*;
89

@@ -12,12 +13,82 @@ fn sqrt<'tcx, F: Float + FloatConvert<F> + Into<Scalar>>(
1213
dest: &MPlaceTy<'tcx>,
1314
) -> InterpResult<'tcx> {
1415
let [f] = check_intrinsic_arg_count(args)?;
15-
let f = this.read_scalar(f)?;
16-
let f: F = f.to_float()?;
17-
// Sqrt is specified to be fully precise.
18-
let res = math::sqrt(f);
16+
math::sqrt_op::<F>(this, f, dest)
17+
}
18+
19+
/// Determine which float operation on which type this is.
20+
fn is_host_unary_float_op(intrinsic_name: &str) -> Option<(FloatTy, HostUnaryFloatOp)> {
21+
let (op, ty) = intrinsic_name.rsplit_once('f')?;
22+
23+
let float_ty = match ty {
24+
"16" => FloatTy::F16,
25+
"32" => FloatTy::F32,
26+
"64" => FloatTy::F64,
27+
"128" => FloatTy::F128,
28+
_ => return None,
29+
};
30+
31+
let host_float_op = match op {
32+
"sin" => HostUnaryFloatOp::Sin,
33+
"cos" => HostUnaryFloatOp::Cos,
34+
"exp" => HostUnaryFloatOp::Exp,
35+
"exp2" => HostUnaryFloatOp::Exp2,
36+
"log" => HostUnaryFloatOp::Log,
37+
"log10" => HostUnaryFloatOp::Log10,
38+
"log2" => HostUnaryFloatOp::Log2,
39+
_ => return None,
40+
};
41+
42+
Some((float_ty, host_float_op))
43+
}
44+
45+
fn pow_intrinsic<'tcx, S: Semantics>(
46+
this: &mut MiriInterpCx<'tcx>,
47+
args: &[OpTy<'tcx>],
48+
dest: &MPlaceTy<'tcx>,
49+
) -> InterpResult<'tcx, ()>
50+
where
51+
IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
52+
{
53+
let [f1, f2] = check_intrinsic_arg_count(args)?;
54+
let f1: IeeeFloat<S> = this.read_scalar(f1)?.to_float()?;
55+
let f2: IeeeFloat<S> = this.read_scalar(f2)?.to_float()?;
56+
57+
let res = math::fixed_float_value(this, "pow", &[f1, f2]).unwrap_or_else(|| {
58+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
59+
let res = f1.host_powf(f2);
60+
61+
// Apply a relative error of 4ULP to introduce some non-determinism
62+
// simulating imprecise implementations and optimizations.
63+
math::apply_random_float_error_ulp(this, res, 4)
64+
});
65+
let res = this.adjust_nan(res, &[f1, f2]);
66+
this.write_scalar(res, dest)?;
67+
interp_ok(())
68+
}
69+
fn powi_intrinsic<'tcx, S: Semantics>(
70+
this: &mut MiriInterpCx<'tcx>,
71+
args: &[OpTy<'tcx>],
72+
dest: &MPlaceTy<'tcx>,
73+
) -> InterpResult<'tcx, ()>
74+
where
75+
IeeeFloat<S>: HostFloatOperation + IeeeExt + Float + Into<Scalar>,
76+
{
77+
let [f, i] = check_intrinsic_arg_count(args)?;
78+
let f: IeeeFloat<S> = this.read_scalar(f)?.to_float()?;
79+
let i = this.read_scalar(i)?.to_i32()?;
80+
81+
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
82+
// Using host floats (but it's fine, this operation does not have guaranteed precision).
83+
let res = f.host_powi(i);
84+
85+
// Apply a relative error of 4ULP to introduce some non-determinism
86+
// simulating imprecise implementations and optimizations.
87+
math::apply_random_float_error_ulp(this, res, 4)
88+
});
1989
let res = this.adjust_nan(res, &[f]);
20-
this.write_scalar(res, dest)
90+
this.write_scalar(res, dest)?;
91+
interp_ok(())
2192
}
2293

2394
impl<'tcx> EvalContextExt<'tcx> for crate::MiriInterpCx<'tcx> {}
@@ -108,161 +179,23 @@ pub trait EvalContextExt<'tcx>: crate::MiriInterpCxExt<'tcx> {
108179
}
109180

110181
// Operations that need host floats.
111-
#[rustfmt::skip]
112-
| "sinf32"
113-
| "cosf32"
114-
| "expf32"
115-
| "exp2f32"
116-
| "logf32"
117-
| "log10f32"
118-
| "log2f32"
119-
=> {
182+
_ if let Some((float_ty, op)) = is_host_unary_float_op(intrinsic_name) => {
120183
let [f] = check_intrinsic_arg_count(args)?;
121-
let f = this.read_scalar(f)?.to_f32()?;
122-
123-
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
124-
// Using host floats (but it's fine, these operations do not have
125-
// guaranteed precision).
126-
let host = f.to_host();
127-
let res = match intrinsic_name {
128-
"sinf32" => host.sin(),
129-
"cosf32" => host.cos(),
130-
"expf32" => host.exp(),
131-
"exp2f32" => host.exp2(),
132-
"logf32" => host.ln(),
133-
"log10f32" => host.log10(),
134-
"log2f32" => host.log2(),
135-
_ => bug!(),
136-
};
137-
let res = res.to_soft();
138-
139-
// Apply a relative error of 4ULP to introduce some non-determinism
140-
// simulating imprecise implementations and optimizations.
141-
let res = math::apply_random_float_error_ulp(
142-
this,
143-
res,
144-
4,
145-
);
146-
147-
// Clamp the result to the guaranteed range of this function according to the C standard,
148-
// if any.
149-
math::clamp_float_value(intrinsic_name, res)
150-
});
151-
let res = this.adjust_nan(res, &[f]);
152-
this.write_scalar(res, dest)?;
153-
}
154-
155-
#[rustfmt::skip]
156-
| "sinf64"
157-
| "cosf64"
158-
| "expf64"
159-
| "exp2f64"
160-
| "logf64"
161-
| "log10f64"
162-
| "log2f64"
163-
=> {
164-
let [f] = check_intrinsic_arg_count(args)?;
165-
let f = this.read_scalar(f)?.to_f64()?;
166-
167-
let res = math::fixed_float_value(this, intrinsic_name, &[f]).unwrap_or_else(|| {
168-
// Using host floats (but it's fine, these operations do not have
169-
// guaranteed precision).
170-
let host = f.to_host();
171-
let res = match intrinsic_name {
172-
"sinf64" => host.sin(),
173-
"cosf64" => host.cos(),
174-
"expf64" => host.exp(),
175-
"exp2f64" => host.exp2(),
176-
"logf64" => host.ln(),
177-
"log10f64" => host.log10(),
178-
"log2f64" => host.log2(),
179-
_ => bug!(),
180-
};
181-
let res = res.to_soft();
182-
183-
// Apply a relative error of 4ULP to introduce some non-determinism
184-
// simulating imprecise implementations and optimizations.
185-
let res = math::apply_random_float_error_ulp(
186-
this,
187-
res,
188-
4,
189-
);
190-
191-
// Clamp the result to the guaranteed range of this function according to the C standard,
192-
// if any.
193-
math::clamp_float_value(intrinsic_name, res)
194-
});
195-
let res = this.adjust_nan(res, &[f]);
196-
this.write_scalar(res, dest)?;
197-
}
198-
199-
"powf32" => {
200-
let [f1, f2] = check_intrinsic_arg_count(args)?;
201-
let f1 = this.read_scalar(f1)?.to_f32()?;
202-
let f2 = this.read_scalar(f2)?.to_f32()?;
203-
204-
let res =
205-
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
206-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
207-
let res = f1.to_host().powf(f2.to_host()).to_soft();
208-
209-
// Apply a relative error of 4ULP to introduce some non-determinism
210-
// simulating imprecise implementations and optimizations.
211-
math::apply_random_float_error_ulp(this, res, 4)
212-
});
213-
let res = this.adjust_nan(res, &[f1, f2]);
214-
this.write_scalar(res, dest)?;
215-
}
216-
"powf64" => {
217-
let [f1, f2] = check_intrinsic_arg_count(args)?;
218-
let f1 = this.read_scalar(f1)?.to_f64()?;
219-
let f2 = this.read_scalar(f2)?.to_f64()?;
220-
221-
let res =
222-
math::fixed_float_value(this, intrinsic_name, &[f1, f2]).unwrap_or_else(|| {
223-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
224-
let res = f1.to_host().powf(f2.to_host()).to_soft();
225-
226-
// Apply a relative error of 4ULP to introduce some non-determinism
227-
// simulating imprecise implementations and optimizations.
228-
math::apply_random_float_error_ulp(this, res, 4)
229-
});
230-
let res = this.adjust_nan(res, &[f1, f2]);
231-
this.write_scalar(res, dest)?;
232-
}
233-
234-
"powif32" => {
235-
let [f, i] = check_intrinsic_arg_count(args)?;
236-
let f = this.read_scalar(f)?.to_f32()?;
237-
let i = this.read_scalar(i)?.to_i32()?;
238-
239-
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
240-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
241-
let res = f.to_host().powi(i).to_soft();
242-
243-
// Apply a relative error of 4ULP to introduce some non-determinism
244-
// simulating imprecise implementations and optimizations.
245-
math::apply_random_float_error_ulp(this, res, 4)
246-
});
247-
let res = this.adjust_nan(res, &[f]);
248-
this.write_scalar(res, dest)?;
184+
match float_ty {
185+
FloatTy::F16 => host_unary_float_op::<HalfS>(this, f, op, dest)?,
186+
FloatTy::F32 => host_unary_float_op::<SingleS>(this, f, op, dest)?,
187+
FloatTy::F64 => host_unary_float_op::<DoubleS>(this, f, op, dest)?,
188+
FloatTy::F128 => todo!("f128"), // FIXME(f128)
189+
};
249190
}
250-
"powif64" => {
251-
let [f, i] = check_intrinsic_arg_count(args)?;
252-
let f = this.read_scalar(f)?.to_f64()?;
253-
let i = this.read_scalar(i)?.to_i32()?;
254191

255-
let res = math::fixed_powi_value(this, f, i).unwrap_or_else(|| {
256-
// Using host floats (but it's fine, this operation does not have guaranteed precision).
257-
let res = f.to_host().powi(i).to_soft();
192+
"powf16" => pow_intrinsic::<HalfS>(this, args, dest)?,
193+
"powf32" => pow_intrinsic::<SingleS>(this, args, dest)?,
194+
"powf64" => pow_intrinsic::<DoubleS>(this, args, dest)?,
258195

259-
// Apply a relative error of 4ULP to introduce some non-determinism
260-
// simulating imprecise implementations and optimizations.
261-
math::apply_random_float_error_ulp(this, res, 4)
262-
});
263-
let res = this.adjust_nan(res, &[f]);
264-
this.write_scalar(res, dest)?;
265-
}
196+
"powif16" => powi_intrinsic::<HalfS>(this, args, dest)?,
197+
"powif32" => powi_intrinsic::<SingleS>(this, args, dest)?,
198+
"powif64" => powi_intrinsic::<DoubleS>(this, args, dest)?,
266199

267200
_ => return interp_ok(EmulateItemResult::NotSupported),
268201
}

0 commit comments

Comments
 (0)