Embed linker directives in ELF and Mach-O object files (#3259)

Resolves #3245 by adding `pragma(lib, <name>)` library names to
`llvm.dependent-libraries` for ELF object files.

For Mach-O, embed appropriate linker options for `pragma(lib)` and
support generic `pragma(linkerDirective, <flag>, ...)` as well.
This commit is contained in:
Martin Kinkelin 2020-01-14 23:12:55 +01:00 committed by GitHub
parent 656a216c22
commit 82f6d4fb85
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 128 additions and 50 deletions

View file

@ -1851,25 +1851,27 @@ version (IN_LLVM)
const(char)* arg1str = null; const(char)* arg1str = null;
} }
if (global.params.mscoff) // IN_LLVM: extended pragma(linkerDirective) support - not just for COFF
// object files, and not restricted to a single string arg
if (pd.ident == Id.linkerDirective)
{ {
if (pd.ident == Id.linkerDirective) if (!pd.args || pd.args.dim == 0)
pd.error("one or more string arguments expected for pragma(linkerDirective)");
else
{ {
if (!pd.args || pd.args.dim != 1) for (size_t i = 0; i < pd.args.dim; ++i)
pd.error("one string argument expected for pragma(linkerDirective)");
else
{ {
auto se = semanticString(sc, (*pd.args)[0], "linker directive"); auto se = semanticString(sc, (*pd.args)[i], "linker directive");
if (!se) if (!se)
goto Lnodecl; break;
(*pd.args)[0] = se; (*pd.args)[i] = se;
if (global.params.verbose) if (global.params.verbose)
message("linkopt %.*s", cast(int)se.len, se.peekString().ptr); message("linkopt %.*s", cast(int)se.len, se.peekString().ptr);
} }
goto Lnodecl;
} }
goto Lnodecl;
} }
if (pd.ident == Id.msg) else if (pd.ident == Id.msg)
{ {
if (pd.args) if (pd.args)
{ {

View file

@ -101,10 +101,12 @@ namespace {
#if LDC_LLVM_VER < 500 #if LDC_LLVM_VER < 500
/// Add the Linker Options module flag. /// Add the Linker Options module flag.
/// If the flag is already present, merge it with the new data. /// If the flag is already present, merge it with the new data.
void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) { void emitLinkerOptions(IRState &irs) {
llvm::Module &M = irs.module;
llvm::LLVMContext &ctx = irs.context();
if (!M.getModuleFlag("Linker Options")) { if (!M.getModuleFlag("Linker Options")) {
M.addModuleFlag(llvm::Module::AppendUnique, "Linker Options", M.addModuleFlag(llvm::Module::AppendUnique, "Linker Options",
llvm::MDNode::get(ctx, irs.LinkerMetadataArgs)); llvm::MDNode::get(ctx, irs.linkerOptions));
} else { } else {
// Merge the Linker Options with the pre-existing one // Merge the Linker Options with the pre-existing one
// (this can happen when passing a .bc file on the commandline) // (this can happen when passing a .bc file on the commandline)
@ -121,10 +123,10 @@ void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) {
// If we reach here, we found the Linker Options flag. // If we reach here, we found the Linker Options flag.
// Add the old Linker Options to our LinkerMetadataArgs list. // Add the old Linker Options to our linkerOptions list.
auto *oldLinkerOptions = llvm::cast<llvm::MDNode>(flag->getOperand(2)); auto *oldLinkerOptions = llvm::cast<llvm::MDNode>(flag->getOperand(2));
for (const auto &Option : oldLinkerOptions->operands()) { for (const auto &Option : oldLinkerOptions->operands()) {
irs.LinkerMetadataArgs.push_back(Option); irs.linkerOptions.push_back(Option);
} }
// Replace Linker Options with a newly created list. // Replace Linker Options with a newly created list.
@ -132,7 +134,7 @@ void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) {
llvm::ConstantAsMetadata::get(llvm::ConstantInt::get( llvm::ConstantAsMetadata::get(llvm::ConstantInt::get(
llvm::Type::getInt32Ty(ctx), llvm::Module::AppendUnique)), llvm::Type::getInt32Ty(ctx), llvm::Module::AppendUnique)),
llvm::MDString::get(ctx, "Linker Options"), llvm::MDString::get(ctx, "Linker Options"),
llvm::MDNode::get(ctx, irs.LinkerMetadataArgs)}; llvm::MDNode::get(ctx, irs.linkerOptions)};
moduleFlags->setOperand(i, llvm::MDNode::get(ctx, Ops)); moduleFlags->setOperand(i, llvm::MDNode::get(ctx, Ops));
break; break;
@ -140,10 +142,12 @@ void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) {
} }
} }
#else #else
/// Add the "llvm.linker.options" metadata. void addLinkerMetadata(llvm::Module &M, const char *name,
/// If the metadata is already present, merge it with the new data. llvm::ArrayRef<llvm::MDNode *> newOperands) {
void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) { if (newOperands.empty())
auto *linkerOptionsMD = M.getOrInsertNamedMetadata("llvm.linker.options"); return;
llvm::NamedMDNode *node = M.getOrInsertNamedMetadata(name);
// Add the new operands in front of the existing ones, such that linker // Add the new operands in front of the existing ones, such that linker
// options of .bc files passed on the cmdline are put _after_ the compiled .d // options of .bc files passed on the cmdline are put _after_ the compiled .d
@ -151,17 +155,25 @@ void emitLinkerOptions(IRState &irs, llvm::Module &M, llvm::LLVMContext &ctx) {
// Temporarily store metadata nodes that are already present // Temporarily store metadata nodes that are already present
llvm::SmallVector<llvm::MDNode *, 5> oldMDNodes; llvm::SmallVector<llvm::MDNode *, 5> oldMDNodes;
for (auto *MD : linkerOptionsMD->operands()) for (auto *MD : node->operands())
oldMDNodes.push_back(MD); oldMDNodes.push_back(MD);
// Clear the list and add the new metadata nodes. // Clear the list and add the new metadata nodes.
linkerOptionsMD->clearOperands(); node->clearOperands();
for (auto *MD : irs.LinkerMetadataArgs) for (auto *MD : newOperands)
linkerOptionsMD->addOperand(MD); node->addOperand(MD);
// Re-add metadata nodes that were already present // Re-add metadata nodes that were already present
for (auto *MD : oldMDNodes) for (auto *MD : oldMDNodes)
linkerOptionsMD->addOperand(MD); node->addOperand(MD);
}
/// Add the "llvm.{linker.options,dependent-libraries}" metadata.
/// If the metadata is already present, merge it with the new data.
void emitLinkerOptions(IRState &irs) {
llvm::Module &M = irs.module;
addLinkerMetadata(M, "llvm.linker.options", irs.linkerOptions);
addLinkerMetadata(M, "llvm.dependent-libraries", irs.linkerDependentLibs);
} }
#endif #endif
@ -253,7 +265,7 @@ void CodeGenerator::writeAndFreeLLModule(const char *filename) {
generateBitcodeForDynamicCompile(ir_); generateBitcodeForDynamicCompile(ir_);
emitLLVMUsedArray(*ir_); emitLLVMUsedArray(*ir_);
emitLinkerOptions(*ir_, ir_->module, ir_->context()); emitLinkerOptions(*ir_);
// Issue #1829: make sure all replaced global variables are replaced // Issue #1829: make sure all replaced global variables are replaced
// everywhere. // everywhere.

View file

@ -433,21 +433,23 @@ public:
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
static llvm::StringRef getPragmaStringArg(PragmaDeclaration *decl) { static llvm::StringRef getPragmaStringArg(PragmaDeclaration *decl,
assert(decl->args && decl->args->length == 1); d_size_t i = 0) {
Expression *e = (*decl->args)[0]; assert(decl->args && decl->args->length > i);
assert(e->op == TOKstring); auto se = (*decl->args)[i]->isStringExp();
StringExp *se = static_cast<StringExp *>(e); assert(se);
DString str = se->peekString(); DString str = se->peekString();
return {str.ptr, str.length}; return {str.ptr, str.length};
} }
void visit(PragmaDeclaration *decl) override { void visit(PragmaDeclaration *decl) override {
const auto &triple = *global.params.targetTriple;
if (decl->ident == Id::lib) { if (decl->ident == Id::lib) {
assert(!irs->dcomputetarget); assert(!irs->dcomputetarget);
llvm::StringRef name = getPragmaStringArg(decl); llvm::StringRef name = getPragmaStringArg(decl);
if (global.params.targetTriple->isWindowsGNUEnvironment()) { if (triple.isWindowsGNUEnvironment()) {
if (name.endswith(".lib")) { if (name.endswith(".lib")) {
// On MinGW, strip the .lib suffix, if any, to improve compatibility // On MinGW, strip the .lib suffix, if any, to improve compatibility
// with code written for DMD (we pass the name to GCC via -l, just as // with code written for DMD (we pass the name to GCC via -l, just as
@ -463,10 +465,7 @@ public:
} }
} }
// With LLVM 3.3 or later we can place the library name in the object if (triple.isWindowsMSVCEnvironment()) {
// file. This seems to be supported only on Windows.
if (global.params.targetTriple->isWindowsMSVCEnvironment()) {
// Win32: /DEFAULTLIB:"curl"
if (name.endswith(".a")) { if (name.endswith(".a")) {
name = name.drop_back(2); name = name.drop_back(2);
} }
@ -474,12 +473,10 @@ public:
name = name.drop_back(4); name = name.drop_back(4);
} }
// embed linker directive in COFF object file; don't push to
// global.params.linkswitches
std::string arg = ("/DEFAULTLIB:\"" + name + "\"").str(); std::string arg = ("/DEFAULTLIB:\"" + name + "\"").str();
gIR->addLinkerOption(llvm::StringRef(arg));
// Embed library name as linker option in object file
auto Value = llvm::MDString::get(gIR->context(), arg);
gIR->LinkerMetadataArgs.push_back(
llvm::MDNode::get(gIR->context(), Value));
} else { } else {
size_t const n = name.size() + 3; size_t const n = name.size() + 3;
char *arg = static_cast<char *>(mem.xmalloc(n)); char *arg = static_cast<char *>(mem.xmalloc(n));
@ -488,14 +485,25 @@ public:
memcpy(arg + 2, name.data(), name.size()); memcpy(arg + 2, name.data(), name.size());
arg[n - 1] = 0; arg[n - 1] = 0;
global.params.linkswitches.push(arg); global.params.linkswitches.push(arg);
if (triple.isOSBinFormatMachO()) {
// embed linker directive in Mach-O object file too
gIR->addLinkerOption(llvm::StringRef(arg));
} else if (triple.isOSBinFormatELF()) {
// embed library name as dependent library in ELF object file too
// (supported by LLD v9+)
gIR->addLinkerDependentLib(name);
}
} }
} else if (decl->ident == Id::linkerDirective) { } else if (decl->ident == Id::linkerDirective) {
if (global.params.targetTriple->isWindowsMSVCEnvironment()) { // embed in object file (if supported)
// Embed directly as linker option in object file if (triple.isWindowsMSVCEnvironment() || triple.isOSBinFormatMachO()) {
llvm::StringRef directive = getPragmaStringArg(decl); assert(decl->args);
auto Value = llvm::MDString::get(gIR->context(), directive); llvm::SmallVector<llvm::StringRef, 2> args;
gIR->LinkerMetadataArgs.push_back( args.reserve(decl->args->length);
llvm::MDNode::get(gIR->context(), Value)); for (d_size_t i = 0; i < decl->args->length; ++i)
args.push_back(getPragmaStringArg(decl, i));
gIR->addLinkerOption(args);
} }
} }
visit(static_cast<AttribDeclaration *>(decl)); visit(static_cast<AttribDeclaration *>(decl));

View file

@ -192,6 +192,21 @@ void IRState::setStructLiteralConstant(StructLiteralExp *sle,
//////////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////////////
void IRState::addLinkerOption(llvm::ArrayRef<llvm::StringRef> options) {
llvm::SmallVector<llvm::Metadata *, 2> mdStrings;
mdStrings.reserve(options.size());
for (const auto &s : options)
mdStrings.push_back(llvm::MDString::get(context(), s));
linkerOptions.push_back(llvm::MDNode::get(context(), mdStrings));
}
void IRState::addLinkerDependentLib(llvm::StringRef libraryName) {
auto n = llvm::MDString::get(context(), libraryName);
linkerDependentLibs.push_back(llvm::MDNode::get(context(), n));
}
////////////////////////////////////////////////////////////////////////////////
IRBuilder<> *IRBuilderHelper::operator->() { IRBuilder<> *IRBuilderHelper::operator->() {
IRBuilder<> &b = state->scope().builder; IRBuilder<> &b = state->scope().builder;
assert(b.GetInsertBlock() != NULL); assert(b.GetInsertBlock() != NULL);

View file

@ -252,11 +252,16 @@ public:
/// Vector of options passed to the linker as metadata in object file. /// Vector of options passed to the linker as metadata in object file.
#if LDC_LLVM_VER >= 500 #if LDC_LLVM_VER >= 500
llvm::SmallVector<llvm::MDNode *, 5> LinkerMetadataArgs; llvm::SmallVector<llvm::MDNode *, 5> linkerOptions;
llvm::SmallVector<llvm::MDNode *, 5> linkerDependentLibs;
#else #else
llvm::SmallVector<llvm::Metadata *, 5> LinkerMetadataArgs; llvm::SmallVector<llvm::Metadata *, 5> linkerOptions;
llvm::SmallVector<llvm::Metadata *, 5> linkerDependentLibs;
#endif #endif
void addLinkerOption(llvm::ArrayRef<llvm::StringRef> options);
void addLinkerDependentLib(llvm::StringRef libraryName);
// MS C++ compatible type descriptors // MS C++ compatible type descriptors
llvm::DenseMap<size_t, llvm::StructType *> TypeDescriptorTypeMap; llvm::DenseMap<size_t, llvm::StructType *> TypeDescriptorTypeMap;
llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *> TypeDescriptorMap; llvm::DenseMap<llvm::Constant *, llvm::GlobalVariable *> TypeDescriptorMap;

View file

@ -245,8 +245,7 @@ void DtoDefineNakedFunction(FuncDeclaration *fd) {
// Embed a linker switch telling the MS linker to export the naked function. // Embed a linker switch telling the MS linker to export the naked function.
// This mimics the effect of the dllexport attribute for regular functions. // This mimics the effect of the dllexport attribute for regular functions.
const auto linkerSwitch = std::string("/EXPORT:") + mangle; const auto linkerSwitch = std::string("/EXPORT:") + mangle;
auto Value = llvm::MDString::get(gIR->context(), linkerSwitch); gIR->addLinkerOption(llvm::StringRef(linkerSwitch));
gIR->LinkerMetadataArgs.push_back(llvm::MDNode::get(gIR->context(), Value));
} }
gIR->funcGenStates.pop_back(); gIR->funcGenStates.pop_back();

View file

@ -0,0 +1,11 @@
// RUN: %ldc -mtriple=x86_64-linux-gnu -output-ll -of=%t.ll %s && FileCheck %s < %t.ll
// REQUIRES: atleast_llvm500, target_X86
// CHECK: !llvm.dependent-libraries = !{!0}
// CHECK: !0 = !{!"mylib"}
pragma(lib, "mylib");
// silently ignored because not (yet?) embeddable in ELF object file:
pragma(linkerDirective, "-myflag");
pragma(linkerDirective, "-framework", "CoreFoundation");

View file

@ -0,0 +1,13 @@
// RUN: %ldc -mtriple=x86_64-apple-darwin -output-ll -of=%t.ll %s && FileCheck %s < %t.ll
// REQUIRES: atleast_llvm500, target_X86
// CHECK: !llvm.linker.options = !{!0, !1, !2}
// CHECK: !0 = !{!"-lmylib"}
pragma(lib, "mylib");
// CHECK: !1 = !{!"-myflag"}
pragma(linkerDirective, "-myflag");
// CHECK: !2 = !{!"-framework", !"CoreFoundation"}
pragma(linkerDirective, "-framework", "CoreFoundation");

View file

@ -0,0 +1,13 @@
// RUN: %ldc -mtriple=x86_64-pc-windows-msvc -output-ll -of=%t.ll %s && FileCheck %s < %t.ll
// REQUIRES: atleast_llvm500, target_X86
// CHECK: !llvm.linker.options = !{!0, !1, !2}
// CHECK: !0 = !{!"/DEFAULTLIB:\22mylib\22"}
pragma(lib, "mylib");
// CHECK: !1 = !{!"-myflag"}
pragma(linkerDirective, "-myflag");
// CHECK: !2 = !{!"-framework", !"CoreFoundation"}
pragma(linkerDirective, "-framework", "CoreFoundation");