Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
- ldc2.conf: `%%ldcversion%%` placeholder added, allowing to refer to version-specific directories.

#### Platform support
- Supports LLVM 15 - 19.
- Initial compiler and runtime support for ppc64 and ppc64le systems that use IEEE 754R 128-bit floating-point as the default 128-bit floating-point format. (#4833)

#### Bug fixes
- Building multi-file D applications with control-flow protection will no longer cause LDC to throw an internal compiler error. (#4828)
Expand Down
23 changes: 22 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,28 @@ if( CMAKE_COMPILER_IS_GNUCXX
endif()
# Do not use doubledouble on ppc
if( CMAKE_SYSTEM_PROCESSOR MATCHES "ppc|powerpc")
append("-mlong-double-64" LDC_CXXFLAGS)
# this extra test checks if the system uses IEEE 754R long double
# if that is supported, then there is no need to turn off 128-bit double type
CHECK_CXX_SOURCE_COMPILES(
"#include <float.h>\nint main(){static_assert(LDBL_MANT_DIG == 113, \"not IEEE long double\");return 0;}\n"
HAS_IEEE_LONG_DOUBLE
)
CHECK_CXX_SOURCE_COMPILES(
"#include <float.h>\nint main(){static_assert(LDBL_MANT_DIG == 106, \"not IBM long double\");return 0;}\n"
HAS_IBM_LONG_DOUBLE
)
if ( HAS_IEEE_LONG_DOUBLE )
set(LLVM_CXXFLAGS "${LLVM_CXXFLAGS} -mabi=ieeelongdouble")
message(
WARNING
"Your system defaults to IEEE 128-bit ABI."
"You will need to include -mabi=ieeelongdouble (GDMD) or --real-precision=quad (LDC) in DFLAGS."
"You need to also make sure your host D libraries are built using IEEE 128-bit ABI."
)
elseif ( NOT HAS_IBM_LONG_DOUBLE )
# usually the case for musl/uclibc
set(LLVM_CXXFLAGS "${LLVM_CXXFLAGS} -mlong-double-64")
endif()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, you check the default behavior of the C++ compiler, so we shouldn't need any explicit C++ flags. [Unless you wanna override the LLVM flags.] The D host compiler might need a flag though, to make sure it's real matches the C++ long double, at least for the IEEE-quad case, which needs explicit opting it in with (new) LDC compilers.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, I admit that might be a mistake. I will fix this.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's a bit more complicated than that - host druntime and Phobos need to be (pre)compiled with the same ABI setting, matching the C++/LLVM one.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually it's a bit more complicated than that - host druntime and Phobos need to be (pre)compiled with the same ABI setting, matching the C++/LLVM one.

This one might need to be documented. According to my testing, changing the host druntime files in include directory is enough to get new LDC bootstrapped (using GDC/GDMD).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah. But as adding some D flag wouldn't be sufficient then anyway, we probably don't need to try to add some here in CMake anymore. Host C++ and D compilers need to target the same ABI when building LDC; if the D one needs tweaking via extra flag, host druntime and Phobos need to be built accordingly and selected too, as in a cross-compile scenario.

[You were probably lucky that a few binding adaptations in the source files were enough to fix up host druntime, without having to rebuild the library.]

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done. CMake files fixed

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You now affect the new druntime and Phobos builds for the just-built LDC, compiling them with the same ABI setting as the compiler itself. It doesn't affect (and cannot) host druntime and Phobos (from GDC in your case), which need to be precompiled with the same ABI setting (as they are linked into the LDC executable).

Now suppose LDC was built on a PPC system with (default) IEEE quad ABI. New druntime and Phobos would be compiled with -real-precision=quad. But when the user runs it with ldc2 hello.d, he'd get a hello object file compiled with default doubledouble ABI, linked with druntime and Phobos using the IEEE ABI. As the D real mangling isn't affected by its precision, the user wouldn't get undefined symbols, but most likely just corrupt floating-point values at runtime.

So a distro package maintainer would in that case need to make sure the compiler defaults to the same ABI as the bundled precompiled druntime and Phobos. E.g., by adding a PPC section in ldc2.conf, adding -real-precision=quad as default switch.

So as said, I think it'd be best to just remove all of this CMake flag fiddling for PPC - it's not enough, the user/package maintainer has to get involved and provide explicit flags in case the default host compiler ABIs diverge, and/or a non-default ABI is desired. As long as using GDC as host compiler, the compilers most likely default to the same ABI. [And there's probably a long way to go until LDC can build itself on such platforms, that requires full C ABI compatibility (ABI rewrites to help LLVM do the right thing) and full C-style variadic arguments support.]

Copy link
Member

@kinke kinke Feb 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What we could also do: for a native ppc compiler build, default to the real precision of the host. So a native IEEE-quad build would default to -real-precision=quad (incl. druntime and Phobos automatically precompiled with that ABI setting).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I have now put these inside ldc2*.conf files.

endif()
if(UNIX)
append("-DLDC_POSIX" LDC_CXXFLAGS)
Expand Down
6 changes: 4 additions & 2 deletions driver/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -681,8 +681,10 @@ void registerPredefinedTargetVersions() {
VersionCondition::addPredefinedGlobalIdent("PPC64");
registerPredefinedFloatABI("PPC_SoftFloat", "PPC_HardFloat");
if (triple.getOS() == llvm::Triple::Linux) {
VersionCondition::addPredefinedGlobalIdent(
triple.getArch() == llvm::Triple::ppc64 ? "ELFv1" : "ELFv2");
const llvm::SmallVector<llvm::StringRef> features{};
const std::string abi = getABI(triple, features);
VersionCondition::addPredefinedGlobalIdent(abi == "elfv1" ? "ELFv1"
: "ELFv2");
}
break;
case llvm::Triple::arm:
Expand Down
68 changes: 65 additions & 3 deletions gen/abi/ppc64le.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,84 @@
// The ABI implementation used for 64 bit little-endian PowerPC targets.
//
// The PowerOpen 64bit ELF v2 ABI can be found here:
// https://members.openpowerfoundation.org/document/dl/576
// https://files.openpower.foundation/s/cfA2oFPXbbZwEBK/download/64biteflv2abi-v1.5.pdf
//===----------------------------------------------------------------------===//

#include <llvm/IR/IntrinsicsPowerPC.h>

#include "gen/abi/abi.h"
#include "gen/abi/generic.h"
#include "gen/dvalue.h"
#include "gen/funcgenstate.h"
#include "gen/irstate.h"
#include "gen/llvmhelpers.h"
#include "gen/tollvm.h"
#include "target.h"

using namespace dmd;

struct LongDoubleRewrite : ABIRewrite {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need this rewrite. The D real values (Tfloat80 etc.) are already IR-emitted as doubledouble or IEEE quad by the changes in target.cpp. The compiler represents all compile-time floating-point values via real_t, which is the D host compiler's real on non-x86. Compile-time real_t and the target real diverging can easily happen during cross-compilation (e.g., x87 real_t for x86 compilers cross-compiling to quad real for Linux AArch64). There's according APFloat conversion happening for IR emission already. The resulting limitations, incl. dangers of compile-time over/underflows when cross-compiling to a target with greater real precision, are mentioned in https://wiki.dlang.org/Cross-compiling_with_LDC#Limitations.

inline bool shouldRewrite(Type *ty) {
const auto baseTy = ty->toBasetype()->ty;
return baseTy == TY::Tfloat80 || baseTy == TY::Tcomplex80 ||
baseTy == TY::Timaginary80;
}

LLValue *put(DValue *dv, bool, bool) override {
if (shouldRewrite(dv->type)) {
auto *dconst = llvm::dyn_cast<llvm::ConstantFP>(DtoRVal(dv));
if (dconst) {
// try to CTFE the conversion
// (ppc_convert_f128_to_ppcf128 intrinsics do not perform the conversion
// during the compile time)
bool ignored;
auto apfloat = dconst->getValue();
apfloat.convert(llvm::APFloat::PPCDoubleDouble(),
llvm::APFloat::rmNearestTiesToEven, &ignored);
return llvm::ConstantFP::get(gIR->context(), apfloat);
}
const auto convertFunc = llvm::Intrinsic::getDeclaration(
&gIR->module, llvm::Intrinsic::ppc_convert_f128_to_ppcf128);
const auto ret = gIR->funcGen().callOrInvoke(
convertFunc, convertFunc->getFunctionType(), {DtoRVal(dv)});
return ret;
}
return DtoRVal(dv);
}

LLValue *getLVal(Type *dty, LLValue *v) override {
// inverse operation of method "put"
auto dstType = DtoType(dty);
if (shouldRewrite(dty) && dstType->isFP128Ty()) {
const auto convertFunc = llvm::Intrinsic::getDeclaration(
&gIR->module, llvm::Intrinsic::ppc_convert_ppcf128_to_f128);
const auto retType = LLType::getFP128Ty(gIR->context());
const auto buffer = DtoRawAlloca(retType, 16);
const auto ret = gIR->funcGen().callOrInvoke(
convertFunc, convertFunc->getFunctionType(), {v});
DtoStore(ret, buffer);
return buffer;
}
// dual-ABI situation: if the destination type is already correct, we just store it
const auto buffer = DtoRawAlloca(dstType, 16);
DtoStore(v, buffer);
return buffer;
}

LLType *type(Type *ty) override {
return LLType::getPPC_FP128Ty(gIR->context());
}
};

struct PPC64LETargetABI : TargetABI {
HFVAToArray hfvaToArray;
CompositeToArray64 compositeToArray64;
IntegerRewrite integerRewrite;
LongDoubleRewrite longDoubleRewrite;
bool useIEEE128;

explicit PPC64LETargetABI() : hfvaToArray(8) {}
explicit PPC64LETargetABI()
: hfvaToArray(8), useIEEE128(target.RealProperties.mant_dig == 113) {}

bool passByVal(TypeFunction *, Type *t) override {
t = t->toBasetype();
Expand All @@ -51,9 +111,11 @@ struct PPC64LETargetABI : TargetABI {
} else {
compositeToArray64.applyTo(arg);
}
} else if (ty->isintegral()) {
} else if (ty->isintegral() && !ty->isTypeVector()) {
arg.attrs.addAttribute(ty->isunsigned() ? LLAttribute::ZExt
: LLAttribute::SExt);
} else if (!useIEEE128 && longDoubleRewrite.shouldRewrite(arg.type)) {
longDoubleRewrite.applyTo(arg);
}
}
};
Expand Down
18 changes: 18 additions & 0 deletions gen/modules.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
#include "dmd/statement.h"
#include "dmd/target.h"
#include "dmd/template.h"
#include "driver/cl_options.h"
#include "driver/cl_options_instrumentation.h"
#include "driver/timetrace.h"
#include "gen/abi/abi.h"
Expand Down Expand Up @@ -404,6 +405,23 @@ void addModuleFlags(llvm::Module &m) {
opts::fCFProtection == opts::CFProtectionType::Full) {
m.setModuleFlag(ModuleMinFlag, "cf-protection-branch", ConstantOneMetadata);
}

// Target specific flags
const auto ModuleErrFlag = llvm::Module::Error;
switch (global.params.targetTriple->getArch()) {
case llvm::Triple::ppc64:
case llvm::Triple::ppc64le:
if (target.RealProperties.mant_dig == 113) {
const auto ConstantIEEE128String = llvm::MDString::get(gIR->context(), "ieeequad");
m.setModuleFlag(ModuleErrFlag, "float-abi", ConstantIEEE128String);
} else {
const auto ConstantIBM128String = llvm::MDString::get(gIR->context(), "doubledouble");
m.setModuleFlag(ModuleErrFlag, "float-abi", ConstantIBM128String);
}
break;
default:
break;
}
}

} // anonymous namespace
Expand Down
71 changes: 70 additions & 1 deletion gen/target.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,31 @@
using namespace dmd;
using llvm::APFloat;

enum class RealTypeEncoding : uint8_t { Double, IEEEQuad, Platform };
static llvm::cl::opt<RealTypeEncoding, false> realTypeEncoding{
"real-precision", llvm::cl::ZeroOrMore,
llvm::cl::init(RealTypeEncoding::Platform),
llvm::cl::desc("Set the precision of the `real` type"),
llvm::cl::values(
clEnumValN(RealTypeEncoding::Double, "double",
"Use double precision (64-bit)"),
clEnumValN(RealTypeEncoding::IEEEQuad, "quad",
"Use IEEE quad precision (128-bit)"),
clEnumValN(RealTypeEncoding::Platform, "platform",
"Use platform-specific precision (target-specific)"))};

namespace {
// Returns the LL type to be used for D `real` (C `long double`).
llvm::Type *getRealType(const llvm::Triple &triple) {
using llvm::Triple;

auto &ctx = getGlobalContext();

// If user specified double precision, use it unconditionally.
if (realTypeEncoding == RealTypeEncoding::Double) {
return LLType::getDoubleTy(ctx);
}

// Android: x86 targets follow ARM, with emulated quad precision for x64
if (triple.getEnvironment() == llvm::Triple::Android) {
return triple.isArch64Bit() ? LLType::getFP128Ty(ctx)
Expand Down Expand Up @@ -64,9 +82,20 @@ llvm::Type *getRealType(const llvm::Triple &triple) {
case Triple::wasm64:
return LLType::getFP128Ty(ctx);

case Triple::ppc64:
case Triple::ppc64le:
if (triple.isMusl()) { // Musl uses double
return LLType::getDoubleTy(ctx);
}
// dual-ABI complications: PPC only has IEEE 128-bit quad precision on
// Linux, IBM double-double is available on both AIX and Linux.
return triple.isOSLinux() && realTypeEncoding == RealTypeEncoding::IEEEQuad
? LLType::getFP128Ty(ctx)
: LLType::getPPC_FP128Ty(ctx);

default:
// 64-bit double precision for all other targets
// FIXME: PowerPC, SystemZ, ...
// FIXME: SystemZ, ...
return LLType::getDoubleTy(ctx);
}
}
Expand Down Expand Up @@ -99,6 +128,20 @@ void Target::_init(const Param &params) {
os = OS_Freestanding;
}

if (triple.isPPC64() && realTypeEncoding == RealTypeEncoding::IEEEQuad) {
if (triple.isGNUEnvironment()) {
// Only GLibc needs this for IEEELongDouble
if (!triple.isLittleEndian()) {
warning(Loc(), "float ABI 'ieeelongdouble' is not well-supported "
"on big-endian POWER systems");
}
} else {
warning(
Loc(),
"float ABI 'ieeelongdouble' is not supported by the target system");
}
}

osMajor = triple.getOSMajorVersion();

ptrsize = gDataLayout->getPointerSize();
Expand Down Expand Up @@ -156,6 +199,7 @@ void Target::_init(const Param &params) {
const auto IEEEdouble = &APFloat::IEEEdouble();
const auto x87DoubleExtended = &APFloat::x87DoubleExtended();
const auto IEEEquad = &APFloat::IEEEquad();
const auto PPCDoubleDouble = &APFloat::PPCDoubleDouble();
bool isOutOfRange = false;

RealProperties.nan = CTFloat::nan;
Expand Down Expand Up @@ -197,6 +241,18 @@ void Target::_init(const Param &params) {
RealProperties.min_exp = -16381;
RealProperties.max_10_exp = 4932;
RealProperties.min_10_exp = -4931;
} else if (targetRealSemantics == PPCDoubleDouble) {
RealProperties.max =
CTFloat::parse("0x1.fffffffffffff7ffffffffffff8p1023", isOutOfRange);
RealProperties.min_normal = CTFloat::parse("0x1p-969", isOutOfRange);
RealProperties.epsilon =
CTFloat::parse("0x0.000000000000000000000000008p-969", isOutOfRange);
RealProperties.dig = 31;
RealProperties.mant_dig = 106;
RealProperties.max_exp = 1024;
RealProperties.min_exp = -968;
RealProperties.max_10_exp = 308;
RealProperties.min_10_exp = -291;
} else {
// leave initialized with host real_t values
warning(Loc(), "unknown properties for target `real` type, relying on D "
Expand Down Expand Up @@ -240,6 +296,19 @@ const char *TargetCPP::typeMangle(Type *t) {
// `long double` on Android/x64 is __float128 and mangled as `g`
bool isAndroidX64 = triple.getEnvironment() == llvm::Triple::Android &&
triple.getArch() == llvm::Triple::x86_64;
if (triple.getArch() == llvm::Triple::ppc64 ||
triple.getArch() == llvm::Triple::ppc64le) {
if (target.RealProperties.mant_dig == 113 &&
triple.getEnvironment() == llvm::Triple::GNU) {
return "u9__ieee128";
}
if (target.RealProperties.mant_dig == 106) {
// IBM long double
return "g";
}
// fall back to 64-bit double type
return "e";
}
return isAndroidX64 ? "g" : "e";
}
return nullptr;
Expand Down
4 changes: 2 additions & 2 deletions gen/toir.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1858,8 +1858,8 @@ class ToElemVisitor : public Visitor {

LLValue *b = DtoRVal(DtoCast(e->loc, u, Type::tbool));

LLConstant *zero = DtoConstBool(false);
b = p->ir->CreateICmpEQ(b, zero);
LLConstant *one = DtoConstBool(true);
b = p->ir->CreateXor(b, one);

result = zextBool(b, e->type);
}
Expand Down
7 changes: 7 additions & 0 deletions ldc2.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,13 @@ default:
rpath = "@SHARED_LIBS_RPATH@";
};

"^powerpc64le-.*linux-gnu$":
{
// default to IEEE quad precision
// if your platform does not support this, feel free to remove it.
switches = ["--real-precision=quad"]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Arrays are not cumulative in ldc2.conf so what you did removed -defaultlib=druntime-ldc from the default switches for ppc64le

};

"^wasm(32|64)-":
{
switches = [
Expand Down
7 changes: 7 additions & 0 deletions ldc2_install.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ default:
rpath = "@SHARED_LIBS_INSTALL_RPATH@";
};

"^powerpc64le-.*linux-gnu$":
{
// default to IEEE quad precision
// if your platform does not support this, feel free to remove it.
switches = ["--real-precision=quad"]
};

"^wasm(32|64)-":
{
switches = [
Expand Down
7 changes: 7 additions & 0 deletions ldc2_phobos.conf.in
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ default:
rpath = "@SHARED_LIBS_RPATH@";
};

"^powerpc64le-.*linux-gnu$":
{
// default to IEEE quad precision
// if your platform does not support this, feel free to remove it.
switches = ["--real-precision=quad"]
};

"^wasm(32|64)-":
{
switches = [
Expand Down
6 changes: 5 additions & 1 deletion runtime/druntime/src/core/atomic.d
Original file line number Diff line number Diff line change
Expand Up @@ -652,9 +652,13 @@ version (LDC)
version (D_LP64)
{
version (PPC64)
enum has128BitCAS = false;
{
enum has128BitCAS = real.mant_dig == 113;
}
else
{
enum has128BitCAS = true;
}
}
else
enum has128BitCAS = false;
Expand Down
5 changes: 5 additions & 0 deletions runtime/druntime/src/core/stdc/config.d
Original file line number Diff line number Diff line change
Expand Up @@ -651,3 +651,8 @@ package(core) template muslRedirTime64Mangle(string name, string redirectedName)
else
enum muslRedirTime64Mangle = name;
}

version (PPC64)
enum PPCUseIEEE128 = real.mant_dig == 113;
else
enum PPCUseIEEE128 = false;
Loading
Loading