diff --git a/src/librustc_const_eval/eval.rs b/src/librustc_const_eval/eval.rs index 463f256fe6c6f..eb45fd9c0e0a4 100644 --- a/src/librustc_const_eval/eval.rs +++ b/src/librustc_const_eval/eval.rs @@ -26,6 +26,7 @@ use rustc::util::nodemap::DefIdMap; use syntax::abi::Abi; use syntax::ast; +use syntax::attr; use rustc::hir::{self, Expr}; use syntax_pos::Span; @@ -560,8 +561,15 @@ fn cast_const_int<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, ty::TyUint(ast::UintTy::Us) => { Ok(Integral(Usize(ConstUsize::new_truncating(v, tcx.sess.target.uint_type)))) }, - ty::TyFloat(ast::FloatTy::F64) => Ok(Float(F64(val.to_f64()))), - ty::TyFloat(ast::FloatTy::F32) => Ok(Float(F32(val.to_f32()))), + ty::TyFloat(fty) => { + if let Some(i) = val.to_u128() { + Ok(Float(ConstFloat::from_u128(i, fty))) + } else { + // The value must be negative, go through signed integers. + let i = val.to_u128_unchecked() as i128; + Ok(Float(ConstFloat::from_i128(i, fty))) + } + } ty::TyRawPtr(_) => Err(ErrKind::UnimplementedConstVal("casting an address to a raw ptr")), ty::TyChar => match val { U8(u) => Ok(Char(u as char)), @@ -574,30 +582,25 @@ fn cast_const_int<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, fn cast_const_float<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, val: ConstFloat, ty: Ty<'tcx>) -> CastResult<'tcx> { + let int_width = |ty| { + ty::layout::Integer::from_attr(tcx, ty).size().bits() as usize + }; match ty.sty { - ty::TyInt(_) | ty::TyUint(_) => { - let i = match val { - F32(f) if f >= 0.0 => U128(f as u128), - F64(f) if f >= 0.0 => U128(f as u128), - - F32(f) => I128(f as i128), - F64(f) => I128(f as i128) - }; - - if let (I128(_), &ty::TyUint(_)) = (i, &ty.sty) { - return Err(CannotCast); + ty::TyInt(ity) => { + if let Some(i) = val.to_i128(int_width(attr::SignedInt(ity))) { + cast_const_int(tcx, I128(i), ty) + } else { + Err(CannotCast) + } + } + ty::TyUint(uty) => { + if let Some(i) = val.to_u128(int_width(attr::UnsignedInt(uty))) { + cast_const_int(tcx, U128(i), ty) + } else { + Err(CannotCast) } - - cast_const_int(tcx, i, ty) } - ty::TyFloat(ast::FloatTy::F64) => Ok(Float(F64(match val { - F32(f) => f as f64, - F64(f) => f - }))), - ty::TyFloat(ast::FloatTy::F32) => Ok(Float(F32(match val { - F64(f) => f as f32, - F32(f) => f - }))), + ty::TyFloat(fty) => Ok(Float(val.convert(fty))), _ => Err(CannotCast), } } @@ -691,11 +694,7 @@ fn lit_to_const<'a, 'tcx>(lit: &ast::LitKind, fn parse_float<'tcx>(num: &str, fty: ast::FloatTy) -> Result> { - let val = match fty { - ast::FloatTy::F32 => num.parse::().map(F32), - ast::FloatTy::F64 => num.parse::().map(F64) - }; - val.map_err(|_| { + ConstFloat::from_str(num, fty).map_err(|_| { // FIXME(#31407) this is only necessary because float parsing is buggy UnimplementedConstVal("could not evaluate float literal (see issue #31407)") }) diff --git a/src/librustc_const_math/float.rs b/src/librustc_const_math/float.rs index f557edffbda46..b2e5e6f08507e 100644 --- a/src/librustc_const_math/float.rs +++ b/src/librustc_const_math/float.rs @@ -9,17 +9,23 @@ // except according to those terms. use std::cmp::Ordering; -use std::hash; -use std::mem::transmute; +use std::num::ParseFloatError; +use syntax::ast; + +use super::apfloat::{Float, IeeeSingle, IeeeDouble, OpStatus, Round}; use super::err::*; -#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)] +// Note that equality for `ConstFloat` means that the it is the same +// constant, not that the rust values are equal. In particular, `NaN +// == NaN` (at least if it's the same NaN; distinct encodings for NaN +// are considering unequal). +#[derive(Copy, Clone, PartialEq, Eq, Debug, Hash, RustcEncodable, RustcDecodable)] pub enum ConstFloat { - F32(f32), - F64(f64) + F32(u32), + F64(u64) } -pub use self::ConstFloat::*; +use self::ConstFloat::*; impl ConstFloat { /// Description of the type, not the value @@ -32,8 +38,8 @@ impl ConstFloat { pub fn is_nan(&self) -> bool { match *self { - F32(f) => f.is_nan(), - F64(f) => f.is_nan(), + F32(f) => IeeeSingle::from_bits(f as u128).is_nan(), + F64(f) => IeeeDouble::from_bits(f as u128).is_nan(), } } @@ -41,59 +47,124 @@ impl ConstFloat { pub fn try_cmp(self, rhs: Self) -> Result { match (self, rhs) { (F64(a), F64(b)) => { + let a = IeeeDouble::from_bits(a as u128); + let b = IeeeDouble::from_bits(b as u128); // This is pretty bad but it is the existing behavior. - Ok(if a == b { - Ordering::Equal - } else if a < b { - Ordering::Less - } else { - Ordering::Greater - }) + Ok(a.partial_cmp(&b).unwrap_or(Ordering::Greater)) } (F32(a), F32(b)) => { - Ok(if a == b { - Ordering::Equal - } else if a < b { - Ordering::Less - } else { - Ordering::Greater - }) + let a = IeeeSingle::from_bits(a as u128); + let b = IeeeSingle::from_bits(b as u128); + Ok(a.partial_cmp(&b).unwrap_or(Ordering::Greater)) } _ => Err(CmpBetweenUnequalTypes), } } -} -/// Note that equality for `ConstFloat` means that the it is the same -/// constant, not that the rust values are equal. In particular, `NaN -/// == NaN` (at least if it's the same NaN; distinct encodings for NaN -/// are considering unequal). -impl PartialEq for ConstFloat { - fn eq(&self, other: &Self) -> bool { - match (*self, *other) { - (F64(a), F64(b)) => { - unsafe{transmute::<_,u64>(a) == transmute::<_,u64>(b)} + pub fn from_i128(input: i128, fty: ast::FloatTy) -> Self { + match fty { + ast::FloatTy::F32 => { + let (r, _) = IeeeSingle::from_i128(input, Round::NearestTiesToEven); + F32(r.to_bits() as u32) } - (F32(a), F32(b)) => { - unsafe{transmute::<_,u32>(a) == transmute::<_,u32>(b)} + ast::FloatTy::F64 => { + let (r, _) = IeeeDouble::from_i128(input, Round::NearestTiesToEven); + F64(r.to_bits() as u64) } - _ => false } } -} -impl Eq for ConstFloat {} + pub fn from_u128(input: u128, fty: ast::FloatTy) -> Self { + match fty { + ast::FloatTy::F32 => { + let (r, _) = IeeeSingle::from_u128(input, Round::NearestTiesToEven); + F32(r.to_bits() as u32) + } + ast::FloatTy::F64 => { + let (r, _) = IeeeDouble::from_u128(input, Round::NearestTiesToEven); + F64(r.to_bits() as u64) + } + } + } -impl hash::Hash for ConstFloat { - fn hash(&self, state: &mut H) { - match *self { - F64(a) => { - unsafe { transmute::<_,u64>(a) }.hash(state) + pub fn from_str(num: &str, fty: ast::FloatTy) -> Result { + match fty { + ast::FloatTy::F32 => { + let rust_bits = num.parse::()?.to_bits(); + let apfloat = num.parse::().unwrap_or_else(|e| { + panic!("apfloat::IeeeSingle failed to parse `{}`: {:?}", num, e); + }); + let apfloat_bits = apfloat.to_bits() as u32; + assert!(rust_bits == apfloat_bits, + "apfloat::IeeeSingle gave different result for `{}`: \ + {}({:#x}) vs Rust's {}({:#x})", num, + F32(apfloat_bits), apfloat_bits, F32(rust_bits), rust_bits); + Ok(F32(apfloat_bits)) + } + ast::FloatTy::F64 => { + let rust_bits = num.parse::()?.to_bits(); + let apfloat = num.parse::().unwrap_or_else(|e| { + panic!("apfloat::IeeeDouble failed to parse `{}`: {:?}", num, e); + }); + let apfloat_bits = apfloat.to_bits() as u64; + assert!(rust_bits == apfloat_bits, + "apfloat::IeeeDouble gave different result for `{}`: \ + {}({:#x}) vs Rust's {}({:#x})", num, + F64(apfloat_bits), apfloat_bits, F64(rust_bits), rust_bits); + Ok(F64(apfloat_bits)) + } + } + } + + pub fn to_i128(self, width: usize) -> Option { + assert!(width <= 128); + let (r, fs) = match self { + F32(f) => { + IeeeSingle::from_bits(f as u128).to_i128(width, Round::TowardZero, &mut true) + } + F64(f) => { + IeeeDouble::from_bits(f as u128).to_i128(width, Round::TowardZero, &mut true) + } + }; + if fs.intersects(OpStatus::INVALID_OP) { + None + } else { + Some(r) + } + } + + pub fn to_u128(self, width: usize) -> Option { + assert!(width <= 128); + let (r, fs) = match self { + F32(f) => { + IeeeSingle::from_bits(f as u128).to_u128(width, Round::TowardZero, &mut true) + } + F64(f) => { + IeeeDouble::from_bits(f as u128).to_u128(width, Round::TowardZero, &mut true) + } + }; + if fs.intersects(OpStatus::INVALID_OP) { + None + } else { + Some(r) + } + } + + pub fn convert(self, fty: ast::FloatTy) -> Self { + match (self, fty) { + (F32(f), ast::FloatTy::F32) => F32(f), + (F64(f), ast::FloatTy::F64) => F64(f), + (F32(f), ast::FloatTy::F64) => { + let from = IeeeSingle::from_bits(f as u128); + let (to, _) = from.convert(Round::NearestTiesToEven, &mut false); + F64(IeeeDouble::to_bits(to) as u64) } - F32(a) => { - unsafe { transmute::<_,u32>(a) }.hash(state) + (F64(f), ast::FloatTy::F32) => { + let from = IeeeDouble::from_bits(f as u128); + let (to, _) = from.convert(Round::NearestTiesToEven, &mut false); + F32(IeeeSingle::to_bits(to) as u32) } } } @@ -102,8 +173,8 @@ impl hash::Hash for ConstFloat { impl ::std::fmt::Display for ConstFloat { fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> { match *self { - F32(f) => write!(fmt, "{}f32", f), - F64(f) => write!(fmt, "{}f64", f), + F32(f) => write!(fmt, "{}f32", IeeeSingle::from_bits(f as u128)), + F64(f) => write!(fmt, "{}f64", IeeeDouble::from_bits(f as u128)), } } } @@ -114,8 +185,16 @@ macro_rules! derive_binop { type Output = Result; fn $func(self, rhs: Self) -> Result { match (self, rhs) { - (F32(a), F32(b)) => Ok(F32(a.$func(b))), - (F64(a), F64(b)) => Ok(F64(a.$func(b))), + (F32(a), F32(b)) =>{ + let a = IeeeSingle::from_bits(a as u128); + let b = IeeeSingle::from_bits(b as u128); + Ok(F32(a.$func(b).to_bits() as u32)) + } + (F64(a), F64(b)) => { + let a = IeeeDouble::from_bits(a as u128); + let b = IeeeDouble::from_bits(b as u128); + Ok(F64(a.$func(b).to_bits() as u64)) + } _ => Err(UnequalTypes(Op::$op)), } } @@ -133,8 +212,8 @@ impl ::std::ops::Neg for ConstFloat { type Output = Self; fn neg(self) -> Self { match self { - F32(f) => F32(-f), - F64(f) => F64(-f), + F32(f) => F32((-IeeeSingle::from_bits(f as u128)).to_bits() as u32), + F64(f) => F64((-IeeeDouble::from_bits(f as u128)).to_bits() as u64), } } } diff --git a/src/librustc_const_math/int.rs b/src/librustc_const_math/int.rs index d97276da9bf34..65471416e8007 100644 --- a/src/librustc_const_math/int.rs +++ b/src/librustc_const_math/int.rs @@ -211,48 +211,6 @@ impl ConstInt { } } - pub fn to_f32(self) -> f32 { - match self { - I8(i) => i as f32, - I16(i) => i as f32, - I32(i) => i as f32, - I64(i) => i as f32, - I128(i) => i as f32, - Isize(Is16(i)) => i as f32, - Isize(Is32(i)) => i as f32, - Isize(Is64(i)) => i as f32, - U8(i) => i as f32, - U16(i) => i as f32, - U32(i) => i as f32, - U64(i) => i as f32, - U128(i) => i as f32, - Usize(Us16(i)) => i as f32, - Usize(Us32(i)) => i as f32, - Usize(Us64(i)) => i as f32, - } - } - - pub fn to_f64(self) -> f64 { - match self { - I8(i) => i as f64, - I16(i) => i as f64, - I32(i) => i as f64, - I64(i) => i as f64, - I128(i) => i as f64, - Isize(Is16(i)) => i as f64, - Isize(Is32(i)) => i as f64, - Isize(Is64(i)) => i as f64, - U8(i) => i as f64, - U16(i) => i as f64, - U32(i) => i as f64, - U64(i) => i as f64, - U128(i) => i as f64, - Usize(Us16(i)) => i as f64, - Usize(Us32(i)) => i as f64, - Usize(Us64(i)) => i as f64, - } - } - pub fn is_negative(&self) -> bool { match *self { I8(v) => v < 0, diff --git a/src/librustc_trans/mir/constant.rs b/src/librustc_trans/mir/constant.rs index 98e774a29877d..2a16cecb4fe6d 100644 --- a/src/librustc_trans/mir/constant.rs +++ b/src/librustc_trans/mir/constant.rs @@ -11,7 +11,7 @@ use llvm::{self, ValueRef}; use rustc::middle::const_val::{ConstEvalErr, ConstVal, ErrKind}; use rustc_const_math::ConstInt::*; -use rustc_const_math::ConstFloat::*; +use rustc_const_math::ConstFloat; use rustc_const_math::{ConstInt, ConstMathErr}; use rustc::hir::def_id::DefId; use rustc::infer::TransNormalize; @@ -95,8 +95,13 @@ impl<'tcx> Const<'tcx> { -> Const<'tcx> { let llty = type_of::type_of(ccx, ty); let val = match cv { - ConstVal::Float(F32(v)) => C_floating_f64(v as f64, llty), - ConstVal::Float(F64(v)) => C_floating_f64(v, llty), + ConstVal::Float(v) => { + let v_f64 = match v { + ConstFloat::F32(v) => f32::from_bits(v) as f64, + ConstFloat::F64(v) => f64::from_bits(v) + }; + C_floating_f64(v_f64, llty) + } ConstVal::Bool(v) => C_bool(ccx, v), ConstVal::Integral(ref i) => return Const::from_constint(ccx, i), ConstVal::Str(ref v) => C_str_slice(ccx, v.clone()),