|
| 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