Skip to content

Commit

Permalink
Auto merge of #43554 - eddyb:apfloat, r=<try>
Browse files Browse the repository at this point in the history
APFloat: Rewrite It In Rust and use it for deterministic floating-point CTFE.

As part of the CTFE initiative, we're forced to find a solution for floating-point operations.
By design, IEEE-754 does not explicitly define everything in a deterministic manner, and there is some variability between platforms, at the very least (e.g. NaN payloads).

If types are to evaluate constant expressions involving type (or in the future, const) generics, that evaluation needs to be *fully deterministic*, even across `rustc` host platforms.
That is, if `[T; T::X]` was used in a cross-compiled library, and the evaluation of `T::X` executed a floating-point operation, that operation has to be reproducible on *any other host*, only knowing `T` and the definition of the `X` associated const (as either AST or HIR).

Failure to uphold those rules allows an associated type (e.g. `<Foo as Iterator>::Item`) to be seen as two (or more) different types, depending on the current host, and such type safety violations typically allow writing of a `transmute` in safe code, given enough generics.

The options considered by @rust-lang/compiler were:
1. Ban floating-point operations in generic const-evaluation contexts
2. Emulate floating-point operations in an uniformly deterministic fashion

The former option may seem appealing at first, but floating-point operations *are allowed today*, so they can't be banned wholesale, a distinction has to be made between the code that already works, and future generic contexts. *Moreover*, every computation that succeeded *has to be cached*, otherwise the generic case can be reproduced without any generics. IMO there are too many ways it can go wrong, and a single violation can be enough for an unsoundness hole.
Not to mention we may end up really wanting floating-point operations *anyway*, in CTFE.

I went with the latter option, and seeing how LLVM *already* has a library for this exact purpose (as it needs to perform optimizations independently of host floating-point capabilities), i.e. `APFloat`, that was what I ended up basing this PR on.
But having been burned by the low reusability of bindings that link to LLVM, and because I would *rather* the floating-point operations to be wrong than not deterministic or not memory-safe (`APFloat` does far more pointer juggling than I'm comfortable with), I decided to RIIR.

This way, we have a guarantee of *no* `unsafe` code, a bit more control over the where native floating-point might accidentally be involved, and non-LLVM backends can share it.
I've also ported all the testcases over, *before* any functionality, to catch any mistakes.

Currently the PR replaces all CTFE operations to go through `apfloat::{IeeeSingle, IeeeDouble}`, keeping only the bits of the `f32` / `f64` memory representation in between operations.
Converting from a string also double-checks that `core::num` and `apfloat` agree on the interpretation of a floating-point number literal, in case either of them has any bugs left around.

r? @nikomatsakis
f? @nagisa @est31

<hr/>

Huge thanks to @edef1c for first demoing usable `APFloat` bindings and to @chandlerc for fielding my questions on IRC about `APFloat` peculiarities (also upstreaming some bugfixes).

**TODO**:
* should tests live in a separate file?
* maybe get more tests from some IEEE conformance testsuite
* maybe go through the trouble of making a new crate
* maybe describe the porting process and the decisions made (towards a rusty bias)
* clean up the API a bit, the method names could be more uniform (e.g. `rounding_add`) and `OpStatus` could be more monadic (a struct instead of `(T, OpStatus)`)
* add licensing information (moco lawyers checked off on this a while back FWIW)
  • Loading branch information
bors committed Jul 30, 2017
2 parents 489b792 + 08d9e09 commit 21ee79b
Show file tree
Hide file tree
Showing 12 changed files with 11,755 additions and 133 deletions.
1 change: 1 addition & 0 deletions src/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

55 changes: 27 additions & 28 deletions src/librustc_const_eval/eval.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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)),
Expand All @@ -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),
}
}
Expand Down Expand Up @@ -691,11 +694,7 @@ fn lit_to_const<'a, 'tcx>(lit: &ast::LitKind,

fn parse_float<'tcx>(num: &str, fty: ast::FloatTy)
-> Result<ConstFloat, ErrKind<'tcx>> {
let val = match fty {
ast::FloatTy::F32 => num.parse::<f32>().map(F32),
ast::FloatTy::F64 => num.parse::<f64>().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)")
})
Expand Down
1 change: 1 addition & 0 deletions src/librustc_const_math/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,4 @@ crate-type = ["dylib"]
[dependencies]
serialize = { path = "../libserialize" }
syntax = { path = "../libsyntax" }
rustc_bitflags = { path = "../librustc_bitflags" }
Loading

0 comments on commit 21ee79b

Please sign in to comment.