Auto merge of #34006 - eddyb:mir-const-fixes, r=nikomatsakis

[MIR] Fix double-rounding of float constants and ignore NaN sign in tests.

Fixes #32805 by handling f32 and f64 separately in rustc_const_eval.

Also removes `#[rustc_no_mir]` from a couple libstd tests by ignoring NaN sign.
Turns out that runtime evaluation of `0.0 / 0.0` produces a NaN with the sign bit set,
whereas LLVM constant folds it to a NaN with the sign bit unset, which we were testing for.
This commit is contained in:
bors 2016-06-06 05:08:50 -07:00
commit 763f9234b0
10 changed files with 302 additions and 107 deletions

View File

@ -12,14 +12,12 @@ use syntax::parse::token::InternedString;
use syntax::ast;
use std::rc::Rc;
use hir::def_id::DefId;
use std::hash;
use std::mem::transmute;
use rustc_const_math::*;
use self::ConstVal::*;
#[derive(Clone, Debug, RustcEncodable, RustcDecodable)]
#[derive(Clone, Debug, Hash, RustcEncodable, RustcDecodable, Eq, PartialEq)]
pub enum ConstVal {
Float(f64),
Float(ConstFloat),
Integral(ConstInt),
Str(InternedString),
ByteStr(Rc<Vec<u8>>),
@ -36,55 +34,10 @@ pub enum ConstVal {
Dummy,
}
impl hash::Hash for ConstVal {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
match *self {
Float(a) => unsafe { transmute::<_,u64>(a) }.hash(state),
Integral(a) => a.hash(state),
Str(ref a) => a.hash(state),
ByteStr(ref a) => a.hash(state),
Bool(a) => a.hash(state),
Struct(a) => a.hash(state),
Tuple(a) => a.hash(state),
Function(a) => a.hash(state),
Array(a, n) => { a.hash(state); n.hash(state) },
Repeat(a, n) => { a.hash(state); n.hash(state) },
Char(c) => c.hash(state),
Dummy => ().hash(state),
}
}
}
/// Note that equality for `ConstVal` 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 ConstVal {
fn eq(&self, other: &ConstVal) -> bool {
match (self, other) {
(&Float(a), &Float(b)) => unsafe{transmute::<_,u64>(a) == transmute::<_,u64>(b)},
(&Integral(a), &Integral(b)) => a == b,
(&Str(ref a), &Str(ref b)) => a == b,
(&ByteStr(ref a), &ByteStr(ref b)) => a == b,
(&Bool(a), &Bool(b)) => a == b,
(&Struct(a), &Struct(b)) => a == b,
(&Tuple(a), &Tuple(b)) => a == b,
(&Function(a), &Function(b)) => a == b,
(&Array(a, an), &Array(b, bn)) => (a == b) && (an == bn),
(&Repeat(a, an), &Repeat(b, bn)) => (a == b) && (an == bn),
(&Char(a), &Char(b)) => a == b,
(&Dummy, &Dummy) => true, // FIXME: should this be false?
_ => false,
}
}
}
impl Eq for ConstVal { }
impl ConstVal {
pub fn description(&self) -> &'static str {
match *self {
Float(_) => "float",
Float(f) => f.description(),
Integral(i) => i.description(),
Str(_) => "string literal",
ByteStr(_) => "byte string literal",

View File

@ -621,18 +621,19 @@ pub fn eval_const_expr_partial<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
match (eval_const_expr_partial(tcx, &a, ty_hint, fn_args)?,
eval_const_expr_partial(tcx, &b, b_ty, fn_args)?) {
(Float(a), Float(b)) => {
use std::cmp::Ordering::*;
match op.node {
hir::BiAdd => Float(a + b),
hir::BiSub => Float(a - b),
hir::BiMul => Float(a * b),
hir::BiDiv => Float(a / b),
hir::BiRem => Float(a % b),
hir::BiEq => Bool(a == b),
hir::BiLt => Bool(a < b),
hir::BiLe => Bool(a <= b),
hir::BiNe => Bool(a != b),
hir::BiGe => Bool(a >= b),
hir::BiGt => Bool(a > b),
hir::BiAdd => Float(math!(e, a + b)),
hir::BiSub => Float(math!(e, a - b)),
hir::BiMul => Float(math!(e, a * b)),
hir::BiDiv => Float(math!(e, a / b)),
hir::BiRem => Float(math!(e, a % b)),
hir::BiEq => Bool(math!(e, a.try_cmp(b)) == Equal),
hir::BiLt => Bool(math!(e, a.try_cmp(b)) == Less),
hir::BiLe => Bool(math!(e, a.try_cmp(b)) != Greater),
hir::BiNe => Bool(math!(e, a.try_cmp(b)) != Equal),
hir::BiGe => Bool(math!(e, a.try_cmp(b)) != Less),
hir::BiGt => Bool(math!(e, a.try_cmp(b)) == Greater),
_ => signal!(e, InvalidOpForFloats(op.node)),
}
}
@ -1078,13 +1079,13 @@ fn cast_const_int<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, val: ConstInt, ty: ty::
}
},
ty::TyFloat(ast::FloatTy::F64) => match val.erase_type() {
Infer(u) => Ok(Float(u as f64)),
InferSigned(i) => Ok(Float(i as f64)),
Infer(u) => Ok(Float(F64(u as f64))),
InferSigned(i) => Ok(Float(F64(i as f64))),
_ => bug!("ConstInt::erase_type returned something other than Infer/InferSigned"),
},
ty::TyFloat(ast::FloatTy::F32) => match val.erase_type() {
Infer(u) => Ok(Float(u as f32 as f64)),
InferSigned(i) => Ok(Float(i as f32 as f64)),
Infer(u) => Ok(Float(F32(u as f32))),
InferSigned(i) => Ok(Float(F32(i as f32))),
_ => bug!("ConstInt::erase_type returned something other than Infer/InferSigned"),
},
ty::TyRawPtr(_) => Err(ErrKind::UnimplementedConstVal("casting an address to a raw ptr")),
@ -1097,13 +1098,35 @@ fn cast_const_int<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, val: ConstInt, ty: ty::
}
}
fn cast_const_float<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>, f: f64, ty: ty::Ty) -> CastResult {
fn cast_const_float<'a, 'tcx>(tcx: TyCtxt<'a, 'tcx, 'tcx>,
val: ConstFloat,
ty: ty::Ty) -> CastResult {
match ty.sty {
ty::TyInt(_) if f >= 0.0 => cast_const_int(tcx, Infer(f as u64), ty),
ty::TyInt(_) => cast_const_int(tcx, InferSigned(f as i64), ty),
ty::TyUint(_) if f >= 0.0 => cast_const_int(tcx, Infer(f as u64), ty),
ty::TyFloat(ast::FloatTy::F64) => Ok(Float(f)),
ty::TyFloat(ast::FloatTy::F32) => Ok(Float(f as f32 as f64)),
ty::TyInt(_) | ty::TyUint(_) => {
let i = match val {
F32(f) if f >= 0.0 => Infer(f as u64),
FInfer { f64: f, .. } |
F64(f) if f >= 0.0 => Infer(f as u64),
F32(f) => InferSigned(f as i64),
FInfer { f64: f, .. } |
F64(f) => InferSigned(f as i64)
};
if let (InferSigned(_), &ty::TyUint(_)) = (i, &ty.sty) {
return Err(CannotCast);
}
cast_const_int(tcx, i, ty)
}
ty::TyFloat(ast::FloatTy::F64) => Ok(Float(F64(match val {
F32(f) => f as f64,
FInfer { f64: f, .. } | F64(f) => f
}))),
ty::TyFloat(ast::FloatTy::F32) => Ok(Float(F32(match val {
F64(f) => f as f32,
FInfer { f32: f, .. } | F32(f) => f
}))),
_ => Err(CannotCast),
}
}
@ -1161,33 +1184,43 @@ fn lit_to_const<'a, 'tcx>(lit: &ast::LitKind,
infer(Infer(n), tcx, &ty::TyUint(ity)).map(Integral)
},
LitKind::Float(ref n, _) |
LitKind::Float(ref n, fty) => {
Ok(Float(parse_float(n, Some(fty), span)))
}
LitKind::FloatUnsuffixed(ref n) => {
if let Ok(x) = n.parse::<f64>() {
Ok(Float(x))
} else {
// FIXME(#31407) this is only necessary because float parsing is buggy
span_bug!(span, "could not evaluate float literal (see issue #31407)");
}
let fty_hint = match ty_hint.map(|t| &t.sty) {
Some(&ty::TyFloat(fty)) => Some(fty),
_ => None
};
Ok(Float(parse_float(n, fty_hint, span)))
}
LitKind::Bool(b) => Ok(Bool(b)),
LitKind::Char(c) => Ok(Char(c)),
}
}
fn parse_float(num: &str, fty_hint: Option<ast::FloatTy>, span: Span) -> ConstFloat {
let val = match fty_hint {
Some(ast::FloatTy::F32) => num.parse::<f32>().map(F32),
Some(ast::FloatTy::F64) => num.parse::<f64>().map(F64),
None => {
num.parse::<f32>().and_then(|f32| {
num.parse::<f64>().map(|f64| {
FInfer { f32: f32, f64: f64 }
})
})
}
};
val.unwrap_or_else(|_| {
// FIXME(#31407) this is only necessary because float parsing is buggy
span_bug!(span, "could not evaluate float literal (see issue #31407)");
})
}
pub fn compare_const_vals(a: &ConstVal, b: &ConstVal) -> Option<Ordering> {
match (a, b) {
(&Integral(a), &Integral(b)) => a.try_cmp(b).ok(),
(&Float(a), &Float(b)) => {
// This is pretty bad but it is the existing behavior.
Some(if a == b {
Ordering::Equal
} else if a < b {
Ordering::Less
} else {
Ordering::Greater
})
}
(&Float(a), &Float(b)) => a.try_cmp(b).ok(),
(&Str(ref a), &Str(ref b)) => Some(a.cmp(b)),
(&Bool(a), &Bool(b)) => Some(a.cmp(&b)),
(&ByteStr(ref a), &ByteStr(ref b)) => Some(a.cmp(b)),

View File

@ -45,17 +45,17 @@ impl ConstMathErr {
use self::Op::*;
match *self {
NotInRange => "inferred value out of range",
CmpBetweenUnequalTypes => "compared two integrals of different types",
UnequalTypes(Add) => "tried to add two integrals of different types",
UnequalTypes(Sub) => "tried to subtract two integrals of different types",
UnequalTypes(Mul) => "tried to multiply two integrals of different types",
UnequalTypes(Div) => "tried to divide two integrals of different types",
CmpBetweenUnequalTypes => "compared two values of different types",
UnequalTypes(Add) => "tried to add two values of different types",
UnequalTypes(Sub) => "tried to subtract two values of different types",
UnequalTypes(Mul) => "tried to multiply two values of different types",
UnequalTypes(Div) => "tried to divide two values of different types",
UnequalTypes(Rem) => {
"tried to calculate the remainder of two integrals of different types"
"tried to calculate the remainder of two values of different types"
},
UnequalTypes(BitAnd) => "tried to bitand two integrals of different types",
UnequalTypes(BitOr) => "tried to bitor two integrals of different types",
UnequalTypes(BitXor) => "tried to xor two integrals of different types",
UnequalTypes(BitAnd) => "tried to bitand two values of different types",
UnequalTypes(BitOr) => "tried to bitor two values of different types",
UnequalTypes(BitXor) => "tried to xor two values of different types",
UnequalTypes(_) => unreachable!(),
Overflow(Add) => "attempted to add with overflow",
Overflow(Sub) => "attempted to subtract with overflow",

View File

@ -0,0 +1,173 @@
// Copyright 2015 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use std::cmp::Ordering;
use std::hash;
use std::mem::transmute;
use super::err::*;
#[derive(Copy, Clone, Debug, RustcEncodable, RustcDecodable)]
pub enum ConstFloat {
F32(f32),
F64(f64),
// When the type isn't known, we have to operate on both possibilities.
FInfer {
f32: f32,
f64: f64
}
}
pub use self::ConstFloat::*;
impl ConstFloat {
/// Description of the type, not the value
pub fn description(&self) -> &'static str {
match *self {
FInfer {..} => "float",
F32(_) => "f32",
F64(_) => "f64",
}
}
pub fn is_nan(&self) -> bool {
match *self {
F32(f) => f.is_nan(),
F64(f) => f.is_nan(),
FInfer { f32, f64 } => f32.is_nan() || f64.is_nan()
}
}
/// Compares the values if they are of the same type
pub fn try_cmp(self, rhs: Self) -> Result<Ordering, ConstMathErr> {
match (self, rhs) {
(F64(a), F64(b)) |
(F64(a), FInfer { f64: b, .. }) |
(FInfer { f64: a, .. }, F64(b)) |
(FInfer { f64: a, .. }, FInfer { f64: b, .. }) => {
// 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
})
}
(F32(a), F32(b)) |
(F32(a), FInfer { f32: b, .. }) |
(FInfer { f32: a, .. }, F32(b)) => {
Ok(if a == b {
Ordering::Equal
} else if a < b {
Ordering::Less
} else {
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)) |
(F64(a), FInfer { f64: b, .. }) |
(FInfer { f64: a, .. }, F64(b)) |
(FInfer { f64: a, .. }, FInfer { f64: b, .. }) => {
unsafe{transmute::<_,u64>(a) == transmute::<_,u64>(b)}
}
(F32(a), F32(b)) => {
unsafe{transmute::<_,u32>(a) == transmute::<_,u32>(b)}
}
_ => false
}
}
}
impl Eq for ConstFloat {}
impl hash::Hash for ConstFloat {
fn hash<H: hash::Hasher>(&self, state: &mut H) {
match *self {
F64(a) | FInfer { f64: a, .. } => {
unsafe { transmute::<_,u64>(a) }.hash(state)
}
F32(a) => {
unsafe { transmute::<_,u32>(a) }.hash(state)
}
}
}
}
impl ::std::fmt::Display for ConstFloat {
fn fmt(&self, fmt: &mut ::std::fmt::Formatter) -> Result<(), ::std::fmt::Error> {
match *self {
FInfer { f64, .. } => write!(fmt, "{}", f64),
F32(f) => write!(fmt, "{}f32", f),
F64(f) => write!(fmt, "{}f64", f),
}
}
}
macro_rules! derive_binop {
($op:ident, $func:ident) => {
impl ::std::ops::$op for ConstFloat {
type Output = Result<Self, ConstMathErr>;
fn $func(self, rhs: Self) -> Result<Self, ConstMathErr> {
match (self, rhs) {
(F32(a), F32(b)) |
(F32(a), FInfer { f32: b, .. }) |
(FInfer { f32: a, .. }, F32(b)) => Ok(F32(a.$func(b))),
(F64(a), F64(b)) |
(FInfer { f64: a, .. }, F64(b)) |
(F64(a), FInfer { f64: b, .. }) => Ok(F64(a.$func(b))),
(FInfer { f32: a32, f64: a64 },
FInfer { f32: b32, f64: b64 }) => Ok(FInfer {
f32: a32.$func(b32),
f64: a64.$func(b64)
}),
_ => Err(UnequalTypes(Op::$op)),
}
}
}
}
}
derive_binop!(Add, add);
derive_binop!(Sub, sub);
derive_binop!(Mul, mul);
derive_binop!(Div, div);
derive_binop!(Rem, rem);
impl ::std::ops::Neg for ConstFloat {
type Output = Self;
fn neg(self) -> Self {
match self {
F32(f) => F32(-f),
F64(f) => F64(-f),
FInfer { f32, f64 } => FInfer {
f32: -f32,
f64: -f64
}
}
}
}

View File

@ -32,11 +32,13 @@
extern crate serialize as rustc_serialize; // used by deriving
mod float;
mod int;
mod us;
mod is;
mod err;
pub use float::*;
pub use int::*;
pub use us::*;
pub use is::*;

View File

@ -56,7 +56,7 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
}
// Returns a zero literal operand for the appropriate type, works for
// bool, char, integers and floats.
// bool, char and integers.
pub fn zero_literal(&mut self, span: Span, ty: Ty<'tcx>) -> Operand<'tcx> {
let literal = match ty.sty {
ty::TyBool => {
@ -93,7 +93,6 @@ impl<'a, 'gcx, 'tcx> Builder<'a, 'gcx, 'tcx> {
Literal::Value { value: ConstVal::Integral(val) }
}
ty::TyFloat(_) => Literal::Value { value: ConstVal::Float(0.0) },
_ => {
span_bug!(span, "Invalid type for zero_literal: `{:?}`", ty)
}

View File

@ -12,6 +12,7 @@ use llvm::{self, ValueRef};
use rustc::middle::const_val::ConstVal;
use rustc_const_eval::ErrKind;
use rustc_const_math::ConstInt::*;
use rustc_const_math::ConstFloat::*;
use rustc_const_math::ConstMathErr;
use rustc::hir::def_id::DefId;
use rustc::infer::TransNormalize;
@ -63,7 +64,9 @@ impl<'tcx> Const<'tcx> {
-> Const<'tcx> {
let llty = type_of::type_of(ccx, ty);
let val = match cv {
ConstVal::Float(v) => C_floating_f64(v, llty),
ConstVal::Float(F32(v)) => C_floating_f64(v as f64, llty),
ConstVal::Float(F64(v)) => C_floating_f64(v, llty),
ConstVal::Float(FInfer {..}) => bug!("MIR must not use `{:?}`", cv),
ConstVal::Bool(v) => C_bool(ccx, v),
ConstVal::Integral(I8(v)) => C_integral(Type::i8(ccx), v as u64, true),
ConstVal::Integral(I16(v)) => C_integral(Type::i16(ccx), v as u64, true),
@ -81,14 +84,14 @@ impl<'tcx> Const<'tcx> {
let u = v.as_u64(ccx.tcx().sess.target.uint_type);
C_integral(Type::int(ccx), u, false)
},
ConstVal::Integral(Infer(v)) => C_integral(llty, v as u64, false),
ConstVal::Integral(InferSigned(v)) => C_integral(llty, v as u64, true),
ConstVal::Integral(Infer(_)) |
ConstVal::Integral(InferSigned(_)) => bug!("MIR must not use `{:?}`", cv),
ConstVal::Str(ref v) => C_str_slice(ccx, v.clone()),
ConstVal::ByteStr(ref v) => consts::addr_of(ccx, C_bytes(ccx, v), 1, "byte_str"),
ConstVal::Struct(_) | ConstVal::Tuple(_) |
ConstVal::Array(..) | ConstVal::Repeat(..) |
ConstVal::Function(_) => {
bug!("MIR must not use {:?} (which refers to a local ID)", cv)
bug!("MIR must not use `{:?}` (which refers to a local ID)", cv)
}
ConstVal::Char(c) => C_integral(Type::char(ccx), c as u64, false),
ConstVal::Dummy => bug!(),

View File

@ -1382,7 +1382,6 @@ mod tests {
}
#[test]
#[rustc_no_mir] // FIXME #27840 MIR NAN ends up negative.
fn test_integer_decode() {
assert_eq!(3.14159265359f32.integer_decode(), (13176795, -22, 1));
assert_eq!((-8573.5918555f32).integer_decode(), (8779358, -10, -1));
@ -1391,7 +1390,11 @@ mod tests {
assert_eq!((-0f32).integer_decode(), (0, -150, -1));
assert_eq!(INFINITY.integer_decode(), (8388608, 105, 1));
assert_eq!(NEG_INFINITY.integer_decode(), (8388608, 105, -1));
assert_eq!(NAN.integer_decode(), (12582912, 105, 1));
// Ignore the "sign" (quiet / signalling flag) of NAN.
// It can vary between runtime operations and LLVM folding.
let (nan_m, nan_e, _nan_s) = NAN.integer_decode();
assert_eq!((nan_m, nan_e), (12582912, 105));
}
#[test]

View File

@ -1277,7 +1277,6 @@ mod tests {
}
#[test]
#[rustc_no_mir] // FIXME #27840 MIR NAN ends up negative.
fn test_integer_decode() {
assert_eq!(3.14159265359f64.integer_decode(), (7074237752028906, -51, 1));
assert_eq!((-8573.5918555f64).integer_decode(), (4713381968463931, -39, -1));
@ -1286,7 +1285,11 @@ mod tests {
assert_eq!((-0f64).integer_decode(), (0, -1075, -1));
assert_eq!(INFINITY.integer_decode(), (4503599627370496, 972, 1));
assert_eq!(NEG_INFINITY.integer_decode(), (4503599627370496, 972, -1));
assert_eq!(NAN.integer_decode(), (6755399441055744, 972, 1));
// Ignore the "sign" (quiet / signalling flag) of NAN.
// It can vary between runtime operations and LLVM folding.
let (nan_m, nan_e, _nan_s) = NAN.integer_decode();
assert_eq!((nan_m, nan_e), (6755399441055744, 972));
}
#[test]

View File

@ -0,0 +1,26 @@
// Copyright 2016 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
#![feature(rustc_attrs)]
#[rustc_mir]
fn const_mir() -> f32 { 9007199791611905.0 }
#[rustc_no_mir]
fn const_old() -> f32 { 9007199791611905.0 }
fn main() {
let original = "9007199791611905.0"; // (1<<53)+(1<<29)+1
let expected = "9007200000000000";
assert_eq!(const_mir().to_string(), expected);
assert_eq!(const_old().to_string(), expected);
assert_eq!(original.parse::<f32>().unwrap().to_string(), expected);
}