Skip to content

Commit 2700da5

Browse files
committed
[LLD] Allow usage of LLD as a library
As discussed in llvm#53475 this patch allows using LLD-as-a-lib. It also lets clients link only the drivers that they want (see unit tests). This also adds the unit test infra as in the other LLVM projects. Among the test coverage, I've added the original issue from @krzysz00, see: https://github.com/ROCmSoftwarePlatform/D108850-lld-bug-reproduction Important note: this doesn't allow (yet) linking in parallel. This will come a bit later, in subsequent patches, for COFF at last. Differential revision: https://reviews.llvm.org/D119049
1 parent eb3d21b commit 2700da5

24 files changed

+560
-209
lines changed

lld/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,8 @@ add_subdirectory(Common)
191191
add_subdirectory(tools/lld)
192192

193193
if (LLVM_INCLUDE_TESTS)
194+
add_custom_target(LLDUnitTests)
195+
llvm_add_unittests(LLD_UNITTESTS_ADDED)
194196
add_subdirectory(test)
195197
endif()
196198

lld/COFF/Driver.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ namespace lld::coff {
6464

6565
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
6666
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
67-
// This driver-specific context will be freed later by lldMain().
67+
// This driver-specific context will be freed later by unsafeLldMain().
6868
auto *ctx = new COFFLinkerContext;
6969

7070
ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);

lld/Common/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ set_source_files_properties("${version_inc}"
2323
add_lld_library(lldCommon
2424
Args.cpp
2525
CommonLinkerContext.cpp
26+
DriverDispatcher.cpp
2627
DWARF.cpp
2728
ErrorHandler.cpp
2829
Filesystem.cpp

lld/Common/DriverDispatcher.cpp

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,203 @@
1+
//===- DriverDispatcher.cpp - Support using LLD as a library --------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "lld/Common/CommonLinkerContext.h"
10+
#include "lld/Common/Driver.h"
11+
#include "lld/Common/ErrorHandler.h"
12+
#include "lld/Common/Memory.h"
13+
#include "llvm/ADT/STLExtras.h"
14+
#include "llvm/ADT/SmallVector.h"
15+
#include "llvm/ADT/StringSwitch.h"
16+
#include "llvm/ADT/Twine.h"
17+
#include "llvm/Support/CommandLine.h"
18+
#include "llvm/Support/CrashRecoveryContext.h"
19+
#include "llvm/Support/InitLLVM.h"
20+
#include "llvm/Support/Path.h"
21+
#include "llvm/Support/Process.h"
22+
#include "llvm/TargetParser/Host.h"
23+
#include "llvm/TargetParser/Triple.h"
24+
#include <cstdlib>
25+
26+
using namespace lld;
27+
using namespace llvm;
28+
using namespace llvm::sys;
29+
30+
static void err(const Twine &s) { llvm::errs() << s << "\n"; }
31+
32+
static Flavor getFlavor(StringRef s) {
33+
return StringSwitch<Flavor>(s)
34+
.CasesLower("ld", "ld.lld", "gnu", Gnu)
35+
.CasesLower("wasm", "ld-wasm", Wasm)
36+
.CaseLower("link", WinLink)
37+
.CasesLower("ld64", "ld64.lld", "darwin", Darwin)
38+
.Default(Invalid);
39+
}
40+
41+
static cl::TokenizerCallback getDefaultQuotingStyle() {
42+
if (Triple(sys::getProcessTriple()).getOS() == Triple::Win32)
43+
return cl::TokenizeWindowsCommandLine;
44+
return cl::TokenizeGNUCommandLine;
45+
}
46+
47+
static bool isPETargetName(StringRef s) {
48+
return s == "i386pe" || s == "i386pep" || s == "thumb2pe" || s == "arm64pe";
49+
}
50+
51+
static std::optional<bool> isPETarget(llvm::ArrayRef<const char *> args) {
52+
for (auto it = args.begin(); it + 1 != args.end(); ++it) {
53+
if (StringRef(*it) != "-m")
54+
continue;
55+
return isPETargetName(*(it + 1));
56+
}
57+
58+
// Expand response files (arguments in the form of @<filename>)
59+
// to allow detecting the -m argument from arguments in them.
60+
SmallVector<const char *, 256> expandedArgs(args.data(),
61+
args.data() + args.size());
62+
BumpPtrAllocator a;
63+
StringSaver saver(a);
64+
cl::ExpansionContext ectx(saver.getAllocator(), getDefaultQuotingStyle());
65+
if (Error e = ectx.expandResponseFiles(expandedArgs)) {
66+
err(toString(std::move(e)));
67+
return std::nullopt;
68+
}
69+
70+
for (auto it = expandedArgs.begin(); it + 1 != expandedArgs.end(); ++it) {
71+
if (StringRef(*it) != "-m")
72+
continue;
73+
return isPETargetName(*(it + 1));
74+
}
75+
76+
#ifdef LLD_DEFAULT_LD_LLD_IS_MINGW
77+
return true;
78+
#else
79+
return false;
80+
#endif
81+
}
82+
83+
static Flavor parseProgname(StringRef progname) {
84+
// Use GNU driver for "ld" by default.
85+
if (progname == "ld")
86+
return Gnu;
87+
88+
// Progname may be something like "lld-gnu". Parse it.
89+
SmallVector<StringRef, 3> v;
90+
progname.split(v, "-");
91+
for (StringRef s : v)
92+
if (Flavor f = getFlavor(s))
93+
return f;
94+
return Invalid;
95+
}
96+
97+
static Flavor
98+
parseFlavorWithoutMinGW(llvm::SmallVectorImpl<const char *> &argsV) {
99+
// Parse -flavor option.
100+
if (argsV.size() > 1 && argsV[1] == StringRef("-flavor")) {
101+
if (argsV.size() <= 2) {
102+
err("missing arg value for '-flavor'");
103+
return Invalid;
104+
}
105+
Flavor f = getFlavor(argsV[2]);
106+
if (f == Invalid) {
107+
err("Unknown flavor: " + StringRef(argsV[2]));
108+
return Invalid;
109+
}
110+
argsV.erase(argsV.begin() + 1, argsV.begin() + 3);
111+
return f;
112+
}
113+
114+
// Deduct the flavor from argv[0].
115+
StringRef arg0 = path::filename(argsV[0]);
116+
if (arg0.ends_with_insensitive(".exe"))
117+
arg0 = arg0.drop_back(4);
118+
Flavor f = parseProgname(arg0);
119+
if (f == Invalid) {
120+
err("lld is a generic driver.\n"
121+
"Invoke ld.lld (Unix), ld64.lld (macOS), lld-link (Windows), wasm-ld"
122+
" (WebAssembly) instead");
123+
return Invalid;
124+
}
125+
return f;
126+
}
127+
128+
static Flavor parseFlavor(llvm::SmallVectorImpl<const char *> &argsV) {
129+
Flavor f = parseFlavorWithoutMinGW(argsV);
130+
if (f == Gnu) {
131+
auto isPE = isPETarget(argsV);
132+
if (!isPE)
133+
return Invalid;
134+
if (*isPE)
135+
return MinGW;
136+
}
137+
return f;
138+
}
139+
140+
static Driver whichDriver(llvm::SmallVectorImpl<const char *> &argsV,
141+
llvm::ArrayRef<DriverDef> drivers) {
142+
Flavor f = parseFlavor(argsV);
143+
auto it =
144+
llvm::find_if(drivers, [=](auto &driverdef) { return driverdef.f == f; });
145+
if (it == drivers.end()) {
146+
// Driver is invalid or not available in this build.
147+
return [](llvm::ArrayRef<const char *>, llvm::raw_ostream &,
148+
llvm::raw_ostream &, bool, bool) { return false; };
149+
}
150+
return it->d;
151+
}
152+
153+
namespace lld {
154+
bool inTestOutputDisabled = false;
155+
156+
/// Universal linker main(). This linker emulates the gnu, darwin, or
157+
/// windows linker based on the argv[0] or -flavor option.
158+
int unsafeLldMain(llvm::ArrayRef<const char *> args,
159+
llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
160+
llvm::ArrayRef<DriverDef> drivers, bool exitEarly) {
161+
SmallVector<const char *, 256> argsV(args);
162+
Driver d = whichDriver(argsV, drivers);
163+
// Run the driver. If an error occurs, false will be returned.
164+
int r = !d(argsV, stdoutOS, stderrOS, exitEarly, inTestOutputDisabled);
165+
// At this point 'r' is either 1 for error, and 0 for no error.
166+
167+
// Call exit() if we can to avoid calling destructors.
168+
if (exitEarly)
169+
exitLld(r);
170+
171+
// Delete the global context and clear the global context pointer, so that it
172+
// cannot be accessed anymore.
173+
CommonLinkerContext::destroy();
174+
175+
return r;
176+
}
177+
} // namespace lld
178+
179+
Result lld::lldMain(llvm::ArrayRef<const char *> args,
180+
llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
181+
llvm::ArrayRef<DriverDef> drivers) {
182+
int r = 0;
183+
{
184+
// The crash recovery is here only to be able to recover from arbitrary
185+
// control flow when fatal() is called (through setjmp/longjmp or
186+
// __try/__except).
187+
llvm::CrashRecoveryContext crc;
188+
if (!crc.RunSafely([&]() {
189+
r = unsafeLldMain(args, stdoutOS, stderrOS, drivers,
190+
/*exitEarly=*/false);
191+
}))
192+
return {crc.RetCode, /*canRunAgain=*/false};
193+
}
194+
195+
// Cleanup memory and reset everything back in pristine condition. This path
196+
// is only taken when LLD is in test, or when it is used as a library.
197+
llvm::CrashRecoveryContext crc;
198+
if (!crc.RunSafely([&]() { CommonLinkerContext::destroy(); })) {
199+
// The memory is corrupted beyond any possible recovery.
200+
return {r, /*canRunAgain=*/false};
201+
}
202+
return {r, /*canRunAgain=*/true};
203+
}

lld/ELF/Driver.cpp

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -107,10 +107,11 @@ void Ctx::reset() {
107107
needsTlsLd.store(false, std::memory_order_relaxed);
108108
}
109109

110-
bool elf::link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
111-
llvm::raw_ostream &stderrOS, bool exitEarly,
112-
bool disableOutput) {
113-
// This driver-specific context will be freed later by lldMain().
110+
namespace lld {
111+
namespace elf {
112+
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
113+
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
114+
// This driver-specific context will be freed later by unsafeLldMain().
114115
auto *ctx = new CommonLinkerContext;
115116

116117
ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
@@ -147,6 +148,8 @@ bool elf::link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
147148

148149
return errorCount() == 0;
149150
}
151+
} // namespace elf
152+
} // namespace lld
150153

151154
// Parses a linker -m option.
152155
static std::tuple<ELFKind, uint16_t, uint8_t> parseEmulation(StringRef emul) {

lld/MachO/Driver.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1367,9 +1367,10 @@ static void handleExplicitExports() {
13671367
}
13681368
}
13691369

1370-
bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
1371-
llvm::raw_ostream &stderrOS, bool exitEarly,
1372-
bool disableOutput) {
1370+
namespace lld {
1371+
namespace macho {
1372+
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
1373+
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
13731374
// This driver-specific context will be freed later by lldMain().
13741375
auto *ctx = new CommonLinkerContext;
13751376

@@ -1968,3 +1969,5 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
19681969

19691970
return errorCount() == 0;
19701971
}
1972+
} // namespace macho
1973+
} // namespace lld

lld/MinGW/Driver.cpp

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -157,11 +157,17 @@ searchLibrary(StringRef name, ArrayRef<StringRef> searchPaths, bool bStatic) {
157157
return "";
158158
}
159159

160+
namespace lld {
161+
namespace coff {
162+
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
163+
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
164+
}
165+
166+
namespace mingw {
160167
// Convert Unix-ish command line arguments to Windows-ish ones and
161168
// then call coff::link.
162-
bool mingw::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
163-
llvm::raw_ostream &stderrOS, bool exitEarly,
164-
bool disableOutput) {
169+
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
170+
llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
165171
auto *ctx = new CommonLinkerContext;
166172
ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
167173

@@ -482,3 +488,5 @@ bool mingw::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
482488

483489
return coff::link(vec, stdoutOS, stderrOS, exitEarly, disableOutput);
484490
}
491+
} // namespace mingw
492+
} // namespace lld

lld/docs/NewLLD.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ The ELF Linker as a Library
55
---------------------------
66

77
You can embed LLD to your program by linking against it and calling the linker's
8-
entry point function lld::elf::link.
8+
entry point function `lld::lldMain`.
99

1010
The current policy is that it is your responsibility to give trustworthy object
1111
files. The function is guaranteed to return as long as you do not pass corrupted

lld/docs/index.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ Features
3636
external linkers. All you have to do is to construct object files
3737
and command line arguments just like you would do to invoke an
3838
external linker and then call the linker's main function,
39-
``lld::elf::link``, from your code.
39+
``lld::lldMain``, from your code.
4040

4141
- It is small. We are using LLVM libObject library to read from object
4242
files, so it is not a completely fair comparison, but as of February

0 commit comments

Comments
 (0)