Limited support for arbitrary target reals

While parsing of floating-point literals and CTFE still operate with the
host LDC's real type, compile-time reals can in principle be emitted in
arbitrary precision via LLVM software conversion, therefore paving the way
for cross-compilation to all targets.

The representable constants are still limited by the compile-time real_t
precision. E.g., LDC on Windows with its 64-bit reals can't hold and emit
an 80-bit `real.max` when cross-compiling to a non-Windows x86(_64)
target; the compile-time value will silently overflow to infinity and
later be emitted as 80-bit infinity.

LDC on AArch64 with its 128-bit quad-precision reals on the other hand can
hold and emit reals for all targets, making it a universal cross-compiler
with quad-precision compile-time reals in hardware.

We don't use the strange 2x64-bit PPC double-double format (see
`getRealType()` in `ir/irtype.cpp`), but would more or less support it
(the type properties (max, min_normal...) still need to be determined;
LLVM isn't sure about those either...).
This commit is contained in:
Martin 2017-02-10 23:41:24 +01:00
parent c051d8d829
commit 0e71a760ae
7 changed files with 115 additions and 60 deletions

View file

@ -14,11 +14,17 @@
// Type used by the front-end for compile-time reals
#if IN_LLVM && _MSC_VER
// Make sure LDC built with MSVC uses double-precision compile-time reals,
// independent of whether it was built with DMD (80-bit reals) or LDC.
typedef double real_t;
#else
typedef longdouble real_t;
#endif
#if IN_LLVM
namespace llvm { class APFloat; }
#endif
// Compile-time floating-point helper
struct CTFloat
{
@ -42,6 +48,9 @@ struct CTFloat
static real_t ceil(real_t x);
static real_t trunc(real_t x);
static real_t round(real_t x);
// implemented in gen/ctfloat.cpp
static void toAPFloat(real_t src, llvm::APFloat &dst);
#endif
static bool isIdentical(real_t a, real_t b);

View file

@ -42,7 +42,7 @@ LLType *DtoComplexBaseType(Type *t) {
////////////////////////////////////////////////////////////////////////////////
LLConstant *DtoConstComplex(Type *_ty, longdouble re, longdouble im) {
LLConstant *DtoConstComplex(Type *_ty, real_t re, real_t im) {
Type *base = nullptr;
switch (_ty->toBasetype()->ty) {
default:

View file

@ -15,7 +15,6 @@
#define LDC_GEN_COMPLEX_H
#include "tokens.h"
#include "longdouble.h"
#include "dvalue.h"
struct Loc;
@ -30,7 +29,7 @@ class Value;
llvm::StructType *DtoComplexType(Type *t);
llvm::Type *DtoComplexBaseType(Type *t);
llvm::Constant *DtoConstComplex(Type *t, longdouble re, longdouble im);
llvm::Constant *DtoConstComplex(Type *t, real_t re, real_t im);
llvm::Constant *DtoComplexShuffleMask(unsigned a, unsigned b);

48
gen/ctfloat.cpp Normal file
View file

@ -0,0 +1,48 @@
//===-- ctfloat.cpp -------------------------------------------------------===//
//
// LDC the LLVM D compiler
//
// This file is distributed under the BSD-style LDC license. See the LICENSE
// file for details.
//
//===----------------------------------------------------------------------===//
#include "ctfloat.h"
#include "gen/llvm.h"
using llvm::APFloat;
void CTFloat::toAPFloat(const real_t src, APFloat &dst) {
if (sizeof(real_t) == 8) {
dst = APFloat(static_cast<double>(src));
return;
}
assert(sizeof(real_t) > 8 && "real_t < 64 bits?");
union {
real_t fp;
uint64_t bits[(sizeof(real_t) + 7) / 8];
} u;
u.fp = src;
#if LDC_LLVM_VER >= 400
const auto &x87DoubleExtended = APFloat::x87DoubleExtended();
const auto &IEEEquad = APFloat::IEEEquad();
const auto &PPCDoubleDouble = APFloat::PPCDoubleDouble();
#else
const auto &x87DoubleExtended = APFloat::x87DoubleExtended;
const auto &IEEEquad = APFloat::IEEEquad;
const auto &PPCDoubleDouble = APFloat::PPCDoubleDouble;
#endif
#if __i386__ || __x86_64__
dst = APFloat(x87DoubleExtended, APInt(80, 2, u.bits));
#elif __aarch64__
dst = APFloat(IEEEquad, APInt(128, 2, u.bits));
#elif __ppc__ || __ppc64__
dst = APFloat(PPCDoubleDouble, APInt(128, 2, u.bits));
#else
llvm_unreachable("Unknown host real_t type for compile-time reals");
#endif
}

View file

@ -56,29 +56,18 @@ void Target::_init() {
c_long_doublesize = realsize;
classinfosize = 0; // unused
const auto pTargetRealSemantics = &real->getFltSemantics();
const auto targetRealSemantics = &real->getFltSemantics();
#if LDC_LLVM_VER >= 400
const auto x87DoubleExtended = &APFloat::x87DoubleExtended();
const auto IEEEdouble = &APFloat::IEEEdouble();
const auto PPCDoubleDouble = &APFloat::PPCDoubleDouble();
const auto x87DoubleExtended = &APFloat::x87DoubleExtended();
const auto IEEEquad = &APFloat::IEEEquad();
#else
const auto x87DoubleExtended = &APFloat::x87DoubleExtended;
const auto IEEEdouble = &APFloat::IEEEdouble;
const auto PPCDoubleDouble = &APFloat::PPCDoubleDouble;
const auto x87DoubleExtended = &APFloat::x87DoubleExtended;
const auto IEEEquad = &APFloat::IEEEquad;
#endif
if (pTargetRealSemantics == x87DoubleExtended) {
real_max = CTFloat::parse("0x1.fffffffffffffffep+16383");
real_min_normal = CTFloat::parse("0x1p-16382");
real_epsilon = CTFloat::parse("0x1p-63");
RealProperties::dig = 18;
RealProperties::mant_dig = 64;
RealProperties::max_exp = 16384;
RealProperties::min_exp = -16381;
RealProperties::max_10_exp = 4932;
RealProperties::min_10_exp = -4932;
} else if (pTargetRealSemantics == IEEEdouble ||
pTargetRealSemantics == PPCDoubleDouble) {
if (targetRealSemantics == IEEEdouble) {
real_max = CTFloat::parse("0x1.fffffffffffffp+1023");
real_min_normal = CTFloat::parse("0x1p-1022");
real_epsilon = CTFloat::parse("0x1p-52");
@ -88,7 +77,30 @@ void Target::_init() {
RealProperties::min_exp = -1021;
RealProperties::max_10_exp = 308;
RealProperties::min_10_exp = -307;
} else if (targetRealSemantics == x87DoubleExtended) {
real_max = CTFloat::parse("0x1.fffffffffffffffep+16383");
real_min_normal = CTFloat::parse("0x1p-16382");
real_epsilon = CTFloat::parse("0x1p-63");
RealProperties::dig = 18;
RealProperties::mant_dig = 64;
RealProperties::max_exp = 16384;
RealProperties::min_exp = -16381;
RealProperties::max_10_exp = 4932;
RealProperties::min_10_exp = -4931;
} else if (targetRealSemantics == IEEEquad) {
// FIXME: hex constants
real_max = CTFloat::parse("1.18973149535723176508575932662800702e+4932");
real_min_normal =
CTFloat::parse("3.36210314311209350626267781732175260e-4932");
real_epsilon = CTFloat::parse("1.92592994438723585305597794258492732e-34");
RealProperties::dig = 33;
RealProperties::mant_dig = 113;
RealProperties::max_exp = 16384;
RealProperties::min_exp = -16381;
RealProperties::max_10_exp = 4932;
RealProperties::min_10_exp = -4931;
} else {
// rely on host compiler
real_max = RealProperties::host_max();
real_min_normal = RealProperties::host_min_normal();
real_epsilon = RealProperties::host_epsilon();

View file

@ -411,51 +411,38 @@ llvm::ConstantInt *DtoConstUbyte(unsigned char i) {
return LLConstantInt::get(LLType::getInt8Ty(gIR->context()), i, false);
}
LLConstant *DtoConstFP(Type *t, longdouble value) {
#if LDC_LLVM_VER >= 400
auto &x87DoubleExtended = APFloat::x87DoubleExtended();
auto &IEEEquad = APFloat::IEEEquad();
auto &PPCDoubleDouble = APFloat::PPCDoubleDouble();
#else
auto &x87DoubleExtended = APFloat::x87DoubleExtended;
auto &IEEEquad = APFloat::IEEEquad;
auto &PPCDoubleDouble = APFloat::PPCDoubleDouble;
#endif
LLConstant *DtoConstFP(Type *t, const real_t value) {
LLType *llty = DtoType(t);
assert(llty->isFloatingPointTy());
if (llty == LLType::getFloatTy(gIR->context()) ||
llty == LLType::getDoubleTy(gIR->context())) {
return LLConstantFP::get(llty, value);
}
if (llty == LLType::getX86_FP80Ty(gIR->context())) {
uint64_t bits[] = {0, 0};
bits[0] = *reinterpret_cast<uint64_t *>(&value);
bits[1] =
*reinterpret_cast<uint16_t *>(reinterpret_cast<uint64_t *>(&value) + 1);
assert(sizeof(real_t) >= 8 && "real_t < 64 bits?");
if (llty->isFloatTy()) {
// let host narrow to single-precision target
return LLConstantFP::get(gIR->context(),
APFloat(x87DoubleExtended, APInt(80, 2, bits)));
}
if (llty == LLType::getFP128Ty(gIR->context())) {
union {
longdouble ld;
uint64_t bits[2];
} t;
t.ld = value;
return LLConstantFP::get(gIR->context(),
APFloat(IEEEquad, APInt(128, 2, t.bits)));
}
if (llty == LLType::getPPC_FP128Ty(gIR->context())) {
uint64_t bits[] = {0, 0};
bits[0] = *reinterpret_cast<uint64_t *>(&value);
bits[1] =
*reinterpret_cast<uint16_t *>(reinterpret_cast<uint64_t *>(&value) + 1);
return LLConstantFP::get(gIR->context(),
APFloat(PPCDoubleDouble, APInt(128, 2, bits)));
APFloat(static_cast<float>(value)));
}
llvm_unreachable("Unknown floating point type encountered");
if (llty->isDoubleTy()) {
// let host (potentially) narrow to double-precision target
return LLConstantFP::get(gIR->context(),
APFloat(static_cast<double>(value)));
}
// host real_t => target real
// 1) represent host real_t as llvm::APFloat
const auto &targetRealSemantics = llty->getFltSemantics();
APFloat v(targetRealSemantics, APFloat::uninitialized);
CTFloat::toAPFloat(value, v);
// 2) convert to target real
if (&v.getSemantics() != &targetRealSemantics) {
bool ignored;
v.convert(targetRealSemantics, APFloat::rmNearestTiesToEven, &ignored);
}
return LLConstantFP::get(gIR->context(), v);
}
////////////////////////////////////////////////////////////////////////////////

View file

@ -90,7 +90,7 @@ LLConstantInt *DtoConstSize_t(uint64_t);
LLConstantInt *DtoConstUint(unsigned i);
LLConstantInt *DtoConstInt(int i);
LLConstantInt *DtoConstUbyte(unsigned char i);
LLConstant *DtoConstFP(Type *t, longdouble value);
LLConstant *DtoConstFP(Type *t, real_t value);
LLConstant *DtoConstString(const char *);
LLConstant *DtoConstBool(bool);