[lld] [llvm] [lld][ELF] Support virtual filesystems for library inputs (PR #226321)
Chinmay Deshpande via llvm-commits
llvm-commits at lists.llvm.org
Sun Sep 27 11:12:33 PDT 2026
https://github.com/chinmaydd updated https://github.com/llvm/llvm-project/pull/226321
>From 2f8fdbee36e170a8992fb64bd4b6afa1de54916d Mon Sep 17 00:00:00 2001
From: Chinmay Deshpande <chdeshpa at amd.com>
Date: Thu, 24 Sep 2026 18:36:19 -0400
Subject: [PATCH] [lld][ELF] Support virtual filesystems for library inputs
Allow ELF library callers to supply an `llvm::vfs::FileSystem` to
`lld::lldMain`. Use it for objects, bitcode, regular and thin archives,
linker scripts, response files, library searches, and IR-level LTO profiles.
This enables embedded users such as AMD COMGR to avoid staging linker inputs
on disk. For example, COMGR's `linkToRelocatable` writes in-memory data
objects to temporary files before calling `linkWithLLD`. A COMGR follow-up
can instead expose those buffers through an `InMemoryFileSystem` and pass
it to `lldMain`.
Retain the filesystem for the invocation and preserve logical filenames when
backing buffers have different identifiers, including during ThinLTO imports.
Profile loading and sample-profile cache-key reads use the supplied
filesystem.
Calls that omit the filesystem retain their existing behavior. The shared
`lld::Driver` callback gains a filesystem argument; non-ELF drivers reject
a non-null filesystem. Outputs, persistent caches, native plugins, and
machine-code profiles continue to use the host filesystem. Inputs consumed
by external tools must remain accessible to those tools.
Validation:
- LLD suite: 2,101 passed, 1,177 unsupported.
- LLVM LTO and profile suites: 686 passed, 26 unsupported.
- Two new LLVM regression tests cover ThinLTO module identifiers and VFS
sample profiles in custom pass pipelines; both reproduced failures before
the fixes and pass afterward.
- `git-clang-format` and `git diff --check` are clean.
Change-Id: If84a752b180c207274da8ac8b018470eebb51e6f
---
lld/COFF/Driver.cpp | 10 +-
lld/Common/DriverDispatcher.cpp | 33 +-
lld/Common/Reproduce.cpp | 7 +-
lld/ELF/BPSectionOrderer.cpp | 2 +-
lld/ELF/Config.h | 4 +-
lld/ELF/Driver.cpp | 40 ++-
lld/ELF/Driver.h | 2 +-
lld/ELF/DriverUtils.cpp | 26 +-
lld/ELF/InputFiles.cpp | 14 +-
lld/ELF/LTO.cpp | 6 +-
lld/ELF/ScriptLexer.cpp | 3 +-
lld/ELF/ScriptParser.cpp | 6 +-
lld/MachO/Driver.cpp | 9 +-
lld/MinGW/Driver.cpp | 13 +-
lld/docs/NewLLD.md | 14 +
lld/docs/ReleaseNotes.md | 9 +
.../lld/Common/BPSectionOrdererBase.inc | 9 +-
lld/include/lld/Common/Driver.h | 13 +-
lld/include/lld/Common/Reproduce.h | 4 +
lld/tools/lld/lld.cpp | 3 +-
lld/unittests/AsLibAll/AllDrivers.cpp | 17 +
lld/unittests/AsLibELF/CMakeLists.txt | 3 +
lld/unittests/AsLibELF/VirtualFileSystem.cpp | 328 ++++++++++++++++++
lld/wasm/Driver.cpp | 8 +-
llvm/include/llvm/LTO/Config.h | 7 +
llvm/lib/LTO/LTO.cpp | 9 +-
llvm/lib/LTO/LTOBackend.cpp | 11 +-
llvm/lib/Passes/PassBuilderPipelines.cpp | 6 +-
llvm/lib/Passes/PassRegistry.def | 7 +-
llvm/unittests/CMakeLists.txt | 1 +
llvm/unittests/LTO/CMakeLists.txt | 18 +
llvm/unittests/LTO/VirtualFileSystemTest.cpp | 142 ++++++++
32 files changed, 709 insertions(+), 75 deletions(-)
create mode 100644 lld/unittests/AsLibELF/VirtualFileSystem.cpp
create mode 100644 llvm/unittests/LTO/CMakeLists.txt
create mode 100644 llvm/unittests/LTO/VirtualFileSystemTest.cpp
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index ecbb5e9fff4e1..97763eef2e513 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -19,6 +19,7 @@
#include "Writer.h"
#include "lld/Common/Args.h"
#include "lld/Common/CommonLinkerContext.h"
+#include "lld/Common/Driver.h"
#include "lld/Common/Filesystem.h"
#include "lld/Common/Timer.h"
#include "lld/Common/Version.h"
@@ -83,9 +84,14 @@ COFFSyncStream coff::Fatal(COFFLinkerContext &ctx) {
uint64_t coff::errCount(COFFLinkerContext &ctx) { return ctx.e.errorCount; }
namespace lld::coff {
-
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+ if (fs) {
+ stderrOS << "lld: error: a virtual filesystem is only supported by the ELF "
+ "driver\n";
+ return false;
+ }
// This driver-specific context will be freed later by unsafeLldMain().
auto *ctx = new COFFLinkerContext;
diff --git a/lld/Common/DriverDispatcher.cpp b/lld/Common/DriverDispatcher.cpp
index 0b71c08094863..8e02242ee7c42 100644
--- a/lld/Common/DriverDispatcher.cpp
+++ b/lld/Common/DriverDispatcher.cpp
@@ -48,7 +48,9 @@ static bool isPETargetName(StringRef s) {
s == "arm64ecpe" || s == "arm64xpe" || s == "mipspe";
}
-static std::optional<bool> isPETarget(llvm::ArrayRef<const char *> args) {
+static std::optional<bool> isPETarget(llvm::ArrayRef<const char *> args,
+ vfs::FileSystem *fs,
+ raw_ostream &stderrOS) {
for (auto it = args.begin(); it + 1 != args.end(); ++it) {
if (StringRef(*it) != "-m")
continue;
@@ -61,9 +63,9 @@ static std::optional<bool> isPETarget(llvm::ArrayRef<const char *> args) {
args.data() + args.size());
BumpPtrAllocator a;
StringSaver saver(a);
- cl::ExpansionContext ectx(saver.getAllocator(), getDefaultQuotingStyle());
+ cl::ExpansionContext ectx(saver.getAllocator(), getDefaultQuotingStyle(), fs);
if (Error e = ectx.expandResponseFiles(expandedArgs)) {
- err(toString(std::move(e)));
+ stderrOS << toString(std::move(e)) << '\n';
return std::nullopt;
}
@@ -124,10 +126,11 @@ parseFlavorWithoutMinGW(llvm::SmallVectorImpl<const char *> &argsV) {
return f;
}
-static Flavor parseFlavor(llvm::SmallVectorImpl<const char *> &argsV) {
+static Flavor parseFlavor(llvm::SmallVectorImpl<const char *> &argsV,
+ vfs::FileSystem *fs, raw_ostream &stderrOS) {
Flavor f = parseFlavorWithoutMinGW(argsV);
if (f == Gnu) {
- auto isPE = isPETarget(argsV);
+ auto isPE = isPETarget(argsV, fs, stderrOS);
if (!isPE)
return Invalid;
if (*isPE)
@@ -137,14 +140,16 @@ static Flavor parseFlavor(llvm::SmallVectorImpl<const char *> &argsV) {
}
static Driver whichDriver(llvm::SmallVectorImpl<const char *> &argsV,
- llvm::ArrayRef<DriverDef> drivers) {
- Flavor f = parseFlavor(argsV);
+ llvm::ArrayRef<DriverDef> drivers,
+ vfs::FileSystem *fs, raw_ostream &stderrOS) {
+ Flavor f = parseFlavor(argsV, fs, stderrOS);
auto it =
llvm::find_if(drivers, [=](auto &driverdef) { return driverdef.f == f; });
if (it == drivers.end()) {
// Driver is invalid or not available in this build.
return [](llvm::ArrayRef<const char *>, llvm::raw_ostream &,
- llvm::raw_ostream &, bool, bool) { return false; };
+ llvm::raw_ostream &, bool, bool,
+ IntrusiveRefCntPtr<vfs::FileSystem>) { return false; };
}
return it->d;
}
@@ -156,11 +161,12 @@ bool inTestOutputDisabled = false;
/// windows linker based on the argv[0] or -flavor option.
int unsafeLldMain(llvm::ArrayRef<const char *> args,
llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
- llvm::ArrayRef<DriverDef> drivers, bool exitEarly) {
+ llvm::ArrayRef<DriverDef> drivers, bool exitEarly,
+ IntrusiveRefCntPtr<vfs::FileSystem> fs) {
SmallVector<const char *, 256> argsV(args);
- Driver d = whichDriver(argsV, drivers);
+ Driver d = whichDriver(argsV, drivers, fs.get(), stderrOS);
// Run the driver. If an error occurs, false will be returned.
- int r = !d(argsV, stdoutOS, stderrOS, exitEarly, inTestOutputDisabled);
+ int r = !d(argsV, stdoutOS, stderrOS, exitEarly, inTestOutputDisabled, fs);
// At this point 'r' is either 1 for error, and 0 for no error.
// Call exit() if we can to avoid calling destructors.
@@ -177,7 +183,8 @@ int unsafeLldMain(llvm::ArrayRef<const char *> args,
Result lld::lldMain(llvm::ArrayRef<const char *> args,
llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
- llvm::ArrayRef<DriverDef> drivers) {
+ llvm::ArrayRef<DriverDef> drivers,
+ IntrusiveRefCntPtr<vfs::FileSystem> fs) {
int r = 0;
{
// The crash recovery is here only to be able to recover from arbitrary
@@ -186,7 +193,7 @@ Result lld::lldMain(llvm::ArrayRef<const char *> args,
llvm::CrashRecoveryContext crc;
if (!crc.RunSafely([&]() {
r = unsafeLldMain(args, stdoutOS, stderrOS, drivers,
- /*exitEarly=*/false);
+ /*exitEarly=*/false, fs);
}))
return {crc.RetCode, /*canRunAgain=*/false};
}
diff --git a/lld/Common/Reproduce.cpp b/lld/Common/Reproduce.cpp
index 6488549f6bff3..7c3b1a6f1b6b8 100644
--- a/lld/Common/Reproduce.cpp
+++ b/lld/Common/Reproduce.cpp
@@ -11,6 +11,7 @@
#include "llvm/Support/Error.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/Path.h"
+#include "llvm/Support/VirtualFileSystem.h"
using namespace lld;
using namespace llvm;
@@ -22,8 +23,12 @@ using namespace llvm::sys;
// Returned string is a forward slash separated path even on Windows to avoid
// a mess with backslash-as-escape and backslash-as-path-separator.
std::string lld::relativeToRoot(StringRef path) {
+ return relativeToRoot(path, *vfs::getRealFileSystem());
+}
+
+std::string lld::relativeToRoot(StringRef path, vfs::FileSystem &fs) {
SmallString<128> abs = path;
- if (fs::make_absolute(abs))
+ if (fs.makeAbsolute(abs))
return std::string(path);
path::remove_dots(abs, /*remove_dot_dot=*/true);
diff --git a/lld/ELF/BPSectionOrderer.cpp b/lld/ELF/BPSectionOrderer.cpp
index 0ce7ceff4348b..b4f097d7c1768 100644
--- a/lld/ELF/BPSectionOrderer.cpp
+++ b/lld/ELF/BPSectionOrderer.cpp
@@ -98,5 +98,5 @@ DenseMap<const InputSectionBase *, int> elf::runBalancedPartitioning(
return orderer.computeOrder(profilePath, compressionSortSpecs,
forFunctionCompression, forDataCompression,
compressionSortStartupFunctions, verbose,
- sections, rootSymbolToSectionIdxs);
+ sections, rootSymbolToSectionIdxs, *ctx.fs);
}
diff --git a/lld/ELF/Config.h b/lld/ELF/Config.h
index a43d5d509a80c..773570f9d5caf 100644
--- a/lld/ELF/Config.h
+++ b/lld/ELF/Config.h
@@ -29,6 +29,7 @@
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/GlobPattern.h"
#include "llvm/Support/TarWriter.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include <atomic>
#include <memory>
#include <mutex>
@@ -652,6 +653,7 @@ struct InStruct {
};
struct Ctx : CommonLinkerContext {
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs;
Config arg;
LinkerDriver driver;
LinkerScript *script;
@@ -782,7 +784,7 @@ struct Ctx : CommonLinkerContext {
// STT_SECTION symbol associated to the .toc input section.
llvm::DenseSet<std::pair<const Symbol *, uint64_t>> ppc64noTocRelax;
- Ctx();
+ explicit Ctx(llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = nullptr);
llvm::raw_fd_ostream openAuxiliaryFile(llvm::StringRef, std::error_code &);
diff --git a/lld/ELF/Driver.cpp b/lld/ELF/Driver.cpp
index 4e5a5eb8664d7..eb9194992df8a 100644
--- a/lld/ELF/Driver.cpp
+++ b/lld/ELF/Driver.cpp
@@ -95,7 +95,8 @@ ELFSyncStream elf::InternalErr(Ctx &ctx, const uint8_t *buf) {
return s;
}
-Ctx::Ctx() : driver(*this) {}
+Ctx::Ctx(IntrusiveRefCntPtr<vfs::FileSystem> fs)
+ : fs(fs ? std::move(fs) : vfs::getRealFileSystem()), driver(*this) {}
llvm::raw_fd_ostream Ctx::openAuxiliaryFile(llvm::StringRef filename,
std::error_code &ec) {
@@ -127,9 +128,10 @@ static void initContext(Ctx &ctx, LinkerScript &script, StringRef arg0) {
namespace lld {
namespace elf {
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ IntrusiveRefCntPtr<vfs::FileSystem> fs) {
// This driver-specific context will be freed later by unsafeLldMain().
- auto *context = new Ctx;
+ auto *context = new Ctx(std::move(fs));
Ctx &ctx = *context;
LinkerScript script(ctx);
ctx.e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
@@ -203,22 +205,38 @@ std::vector<std::pair<MemoryBufferRef, uint64_t>> static getArchiveMembers(
Error err = Error::success();
bool addToTar = file->isThin() && ctx.tar;
for (const Archive::Child &c : file->children(err)) {
+ auto getMember = [&]() -> Expected<MemoryBufferRef> {
+ if (!file->isThin())
+ return c.getMemoryBufferRef();
+ Expected<std::string> name = c.getFullName();
+ if (!name)
+ return name.takeError();
+ auto buffer = ctx.fs->getBufferForFile(*name, /*FileSize=*/-1,
+ /*RequiresNullTerminator=*/false,
+ /*IsVolatile=*/false,
+ /*IsText=*/false);
+ if (!buffer)
+ return createFileError(*name, buffer.getError());
+ Expected<StringRef> memberName = c.getName();
+ if (!memberName)
+ return memberName.takeError();
+ MemoryBufferRef ref((*buffer)->getBuffer(), *memberName);
+ job.thinBufs.push_back(std::move(*buffer));
+ return ref;
+ };
MemoryBufferRef mbref =
- CHECK(c.getMemoryBufferRef(),
+ CHECK(getMember(),
mb.getBufferIdentifier() +
": could not get the buffer for a child of the archive");
if (addToTar)
- job.tarEntries.emplace_back(relativeToRoot(check(c.getFullName())),
- mbref.getBuffer());
+ job.tarEntries.emplace_back(
+ relativeToRoot(check(c.getFullName()), *ctx.fs), mbref.getBuffer());
v.push_back(std::make_pair(mbref, c.getChildOffset()));
}
if (err)
Fatal(ctx) << mb.getBufferIdentifier()
<< ": Archive::children failed: " << std::move(err);
- // Take ownership of memory buffers created for members of thin archives.
- job.thinBufs = file->takeThinBuffers();
-
return v;
}
@@ -689,7 +707,7 @@ void LinkerDriver::linkerMain(ArrayRef<const char *> argsArr) {
TarWriter::create(path, path::stem(path));
if (errOrWriter) {
ctx.tar = std::move(*errOrWriter);
- ctx.tar->append("response.txt", createResponseFile(args));
+ ctx.tar->append("response.txt", createResponseFile(ctx, args));
ctx.tar->append("version.txt", getLLDVersion() + "\n");
StringRef ltoSampleProfile = args.getLastArgValue(OPT_lto_sample_profile);
if (!ltoSampleProfile.empty())
@@ -3247,7 +3265,7 @@ static void postParseObjectFile(ELFFileBase *file) {
// relocatable ELF file to embed in an ELF Dynamic Debugging section in the
// output. See llvm/docs/DynamicDebugging.md for more details.
template <class ELFT> static void linkDynamicDebug(Ctx &ctx) {
- Ctx dctx;
+ Ctx dctx(ctx.fs);
LinkerScript script(dctx);
dctx.e.initialize(ctx.e.outs(), ctx.e.errs(), ctx.e.exitEarly,
ctx.e.disableOutput);
diff --git a/lld/ELF/Driver.h b/lld/ELF/Driver.h
index 473b5eca77c52..d8d7208990aff 100644
--- a/lld/ELF/Driver.h
+++ b/lld/ELF/Driver.h
@@ -33,7 +33,7 @@ enum {
};
void printHelp(Ctx &ctx);
-std::string createResponseFile(const llvm::opt::InputArgList &args);
+std::string createResponseFile(Ctx &ctx, const llvm::opt::InputArgList &args);
std::optional<std::string> findFromSearchPaths(Ctx &, StringRef path);
std::optional<std::string> searchScript(Ctx &, StringRef path);
diff --git a/lld/ELF/DriverUtils.cpp b/lld/ELF/DriverUtils.cpp
index 7bee46d8534bf..226435abae86b 100644
--- a/lld/ELF/DriverUtils.cpp
+++ b/lld/ELF/DriverUtils.cpp
@@ -106,7 +106,10 @@ opt::InputArgList ELFOptTable::parse(Ctx &ctx, ArrayRef<const char *> argv) {
// Expand response files (arguments in the form of @<filename>)
// and then parse the argument again.
- cl::ExpandResponseFiles(ctx.saver, getQuotingStyle(ctx, args), vec);
+ cl::ExpansionContext ectx(ctx.saver.getAllocator(),
+ getQuotingStyle(ctx, args), ctx.fs.get());
+ if (Error e = ectx.expandResponseFiles(vec))
+ ErrAlways(ctx) << toString(std::move(e));
concatLTOPluginOptions(ctx, vec);
args = this->ParseArgs(vec, missingIndex, missingCount);
@@ -139,15 +142,15 @@ void elf::printHelp(Ctx &ctx) {
outs << ctx.arg.progName << ": supported targets: elf\n";
}
-static std::string rewritePath(StringRef s) {
- if (fs::exists(s))
- return relativeToRoot(s);
+static std::string rewritePath(Ctx &ctx, StringRef s) {
+ if (ctx.fs->exists(s))
+ return relativeToRoot(s, *ctx.fs);
return std::string(s);
}
// Reconstructs command line arguments so that so that you can re-run
// the same command with the same inputs. This is for --reproduce.
-std::string elf::createResponseFile(const opt::InputArgList &args) {
+std::string elf::createResponseFile(Ctx &ctx, const opt::InputArgList &args) {
SmallString<0> data;
raw_svector_ostream os(data);
os << "--chroot .\n";
@@ -158,7 +161,7 @@ std::string elf::createResponseFile(const opt::InputArgList &args) {
case OPT_reproduce:
break;
case OPT_INPUT:
- os << quote(rewritePath(arg->getValue())) << "\n";
+ os << quote(rewritePath(ctx, arg->getValue())) << "\n";
break;
case OPT_o:
case OPT_Map:
@@ -175,7 +178,8 @@ std::string elf::createResponseFile(const opt::InputArgList &args) {
os << quote(path::filename(arg->getValue())) << '\n';
break;
case OPT_lto_sample_profile:
- os << arg->getSpelling() << quote(rewritePath(arg->getValue())) << "\n";
+ os << arg->getSpelling() << quote(rewritePath(ctx, arg->getValue()))
+ << "\n";
break;
case OPT_call_graph_ordering_file:
case OPT_default_script:
@@ -190,8 +194,8 @@ std::string elf::createResponseFile(const opt::InputArgList &args) {
case OPT_symbol_ordering_file:
case OPT_sysroot:
case OPT_version_script:
- os << arg->getSpelling() << " " << quote(rewritePath(arg->getValue()))
- << "\n";
+ os << arg->getSpelling() << " "
+ << quote(rewritePath(ctx, arg->getValue())) << "\n";
break;
default:
os << toString(*arg) << "\n";
@@ -210,7 +214,7 @@ static std::optional<std::string> findFile(Ctx &ctx, StringRef path1,
else
path::append(s, path1, path2);
- if (fs::exists(s))
+ if (ctx.fs->exists(s))
return std::string(s);
return std::nullopt;
}
@@ -249,7 +253,7 @@ std::optional<std::string> elf::searchLibrary(Ctx &ctx, StringRef name) {
// look for the script in the '-L' search paths. This matches the behaviour of
// '-T', --version-script=, and linker script INPUT() command in ld.bfd.
std::optional<std::string> elf::searchScript(Ctx &ctx, StringRef name) {
- if (fs::exists(name))
+ if (ctx.fs->exists(name))
return name.str();
return findFromSearchPaths(ctx, name);
}
diff --git a/lld/ELF/InputFiles.cpp b/lld/ELF/InputFiles.cpp
index 4af0898cef607..5140c866661e0 100644
--- a/lld/ELF/InputFiles.cpp
+++ b/lld/ELF/InputFiles.cpp
@@ -245,18 +245,22 @@ std::optional<MemoryBufferRef> elf::readFile(Ctx &ctx, StringRef path) {
Log(ctx) << path;
ctx.arg.dependencyFiles.insert(llvm::CachedHashString(path));
- auto mbOrErr = MemoryBuffer::getFile(path, /*IsText=*/false,
- /*RequiresNullTerminator=*/false);
+ auto mbOrErr =
+ ctx.fs->getBufferForFile(path, /*FileSize=*/-1,
+ /*RequiresNullTerminator=*/false,
+ /*IsVolatile=*/false, /*IsText=*/false);
if (auto ec = mbOrErr.getError()) {
ErrAlways(ctx) << "cannot open " << path << ": " << ec.message();
return std::nullopt;
}
- MemoryBufferRef mbref = (*mbOrErr)->getMemBufferRef();
+ // A VFS buffer's identifier need not be the requested filename. Preserve the
+ // logical path for diagnostics and for resolving script and archive members.
+ MemoryBufferRef mbref((*mbOrErr)->getBuffer(), ctx.saver.save(path));
ctx.memoryBuffers.push_back(std::move(*mbOrErr)); // take MB ownership
if (ctx.tar)
- ctx.tar->append(relativeToRoot(path), mbref.getBuffer());
+ ctx.tar->append(relativeToRoot(path, *ctx.fs), mbref.getBuffer());
return mbref;
}
@@ -385,7 +389,7 @@ static void addDependentLibrary(Ctx &ctx, StringRef specifier,
ctx.driver.addFile(ctx.saver.save(*s), /*withLOption=*/true);
else if (std::optional<std::string> s = findFromSearchPaths(ctx, specifier))
ctx.driver.addFile(ctx.saver.save(*s), /*withLOption=*/true);
- else if (fs::exists(specifier))
+ else if (ctx.fs->exists(specifier))
ctx.driver.addFile(specifier, /*withLOption=*/false);
else
ErrAlways(ctx)
diff --git a/lld/ELF/LTO.cpp b/lld/ELF/LTO.cpp
index f5ce6f2c2fd4b..bd5d24ee94b49 100644
--- a/lld/ELF/LTO.cpp
+++ b/lld/ELF/LTO.cpp
@@ -55,6 +55,7 @@ static std::string getThinLTOOutputFile(Ctx &ctx, StringRef modulePath) {
static lto::Config createConfig(Ctx &ctx) {
lto::Config c;
+ c.FS = ctx.fs;
// LLD supports the new relocations and address-significance tables.
c.Options = initTargetOptionsFromCodeGenFlags();
@@ -82,7 +83,10 @@ static lto::Config createConfig(Ctx &ctx) {
c.Options.BBSections = BasicBlockSection::None;
} else {
ErrorOr<std::unique_ptr<MemoryBuffer>> MBOrErr =
- MemoryBuffer::getFile(ctx.arg.ltoBasicBlockSections.str());
+ ctx.fs->getBufferForFile(ctx.arg.ltoBasicBlockSections,
+ /*FileSize=*/-1,
+ /*RequiresNullTerminator=*/true,
+ /*IsVolatile=*/false, /*IsText=*/false);
if (!MBOrErr) {
ErrAlways(ctx) << "cannot open " << ctx.arg.ltoBasicBlockSections << ":"
<< MBOrErr.getError().message();
diff --git a/lld/ELF/ScriptLexer.cpp b/lld/ELF/ScriptLexer.cpp
index 3a7207def2c0b..3a1a256b97547 100644
--- a/lld/ELF/ScriptLexer.cpp
+++ b/lld/ELF/ScriptLexer.cpp
@@ -44,7 +44,8 @@ ScriptLexer::Buffer::Buffer(Ctx &ctx, MemoryBufferRef mb)
return;
StringRef path = filename;
for (; !path.empty(); path = sys::path::parent_path(path)) {
- if (!sys::fs::equivalent(ctx.arg.sysroot, path))
+ auto equivalent = ctx.fs->equivalent(ctx.arg.sysroot, path);
+ if (!equivalent || !*equivalent)
continue;
isUnderSysroot = true;
return;
diff --git a/lld/ELF/ScriptParser.cpp b/lld/ELF/ScriptParser.cpp
index 0c54fde488292..f6ac7a334209d 100644
--- a/lld/ELF/ScriptParser.cpp
+++ b/lld/ELF/ScriptParser.cpp
@@ -320,7 +320,7 @@ void ScriptParser::addFile(StringRef s) {
if (curBuf.isUnderSysroot && s.starts_with("/")) {
SmallString<128> pathData;
StringRef path = (ctx.arg.sysroot + s).toStringRef(pathData);
- if (sys::fs::exists(path))
+ if (ctx.fs->exists(path))
ctx.driver.addFile(ctx.saver.save(path), /*withLOption=*/false);
else
setError("cannot find " + s + " inside " + ctx.arg.sysroot);
@@ -346,13 +346,13 @@ void ScriptParser::addFile(StringRef s) {
if (!directory.empty()) {
SmallString<0> path(directory);
sys::path::append(path, s);
- if (sys::fs::exists(path)) {
+ if (ctx.fs->exists(path)) {
ctx.driver.addFile(ctx.saver.save(path.str()), /*withLOption=*/false);
return;
}
}
// Then search in the current working directory.
- if (sys::fs::exists(s)) {
+ if (ctx.fs->exists(s)) {
ctx.driver.addFile(s, /*withLOption=*/false);
} else {
// Finally, search in the list of library paths.
diff --git a/lld/MachO/Driver.cpp b/lld/MachO/Driver.cpp
index 5603dcba5a38a..9b50a97ab1991 100644
--- a/lld/MachO/Driver.cpp
+++ b/lld/MachO/Driver.cpp
@@ -26,6 +26,7 @@
#include "lld/Common/Args.h"
#include "lld/Common/CommonLinkerContext.h"
+#include "lld/Common/Driver.h"
#include "lld/Common/ErrorHandler.h"
#include "lld/Common/LLVM.h"
#include "lld/Common/Memory.h"
@@ -1762,7 +1763,13 @@ static void computeColdness() {
namespace lld {
namespace macho {
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+ if (fs) {
+ stderrOS << "lld: error: a virtual filesystem is only supported by the ELF "
+ "driver\n";
+ return false;
+ }
// This driver-specific context will be freed later by lldMain().
auto *ctx = new CommonLinkerContext;
diff --git a/lld/MinGW/Driver.cpp b/lld/MinGW/Driver.cpp
index 53cce5b5c0277..f2c7b20f043ec 100644
--- a/lld/MinGW/Driver.cpp
+++ b/lld/MinGW/Driver.cpp
@@ -153,14 +153,21 @@ static bool isI386Target(const opt::InputArgList &args,
namespace lld {
namespace coff {
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput);
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs);
}
namespace mingw {
// Convert Unix-ish command line arguments to Windows-ish ones and
// then call coff::link.
bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+ if (fs) {
+ stderrOS << "lld: error: a virtual filesystem is only supported by the ELF "
+ "driver\n";
+ return false;
+ }
auto *ctx = new CommonLinkerContext;
ctx->e.initialize(stdoutOS, stderrOS, exitEarly, disableOutput);
@@ -614,7 +621,7 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
// The context will be re-created in the COFF driver.
lld::CommonLinkerContext::destroy();
- return coff::link(vec, stdoutOS, stderrOS, exitEarly, disableOutput);
+ return coff::link(vec, stdoutOS, stderrOS, exitEarly, disableOutput, fs);
}
} // namespace mingw
} // namespace lld
diff --git a/lld/docs/NewLLD.md b/lld/docs/NewLLD.md
index a6bfff794466f..a725ff856113c 100644
--- a/lld/docs/NewLLD.md
+++ b/lld/docs/NewLLD.md
@@ -5,6 +5,20 @@
You can embed LLD to your program by linking against it and calling the linker's
entry point function `lld::lldMain`.
+The ELF driver accepts an optional `llvm::vfs::FileSystem` as the last argument
+to `lld::lldMain`. It uses this filesystem for objects, bitcode, archives
+(including external members of thin archives), linker scripts, response files,
+library searches, and IR-level LTO profiles. Relative input paths are resolved
+against the filesystem's working directory. An `llvm::vfs::OverlayFileSystem`
+can combine in-memory inputs with files on disk.
+
+LLD retains the filesystem for the invocation. It must support concurrent reads
+when threading is enabled. Omitting it selects the real filesystem. Output
+files, the persistent LTO cache, native plugins, and machine-code profile
+loaders still use the real filesystem. Inputs passed to external tools, such as
+distributed ThinLTO backends, must be accessible to those tools. Other linker
+drivers currently reject an explicitly supplied filesystem.
+
The current policy is that it is your responsibility to give trustworthy object
files. The function is guaranteed to return as long as you do not pass corrupted
or malicious object files. A corrupted file could cause a fatal error or SEGV.
diff --git a/lld/docs/ReleaseNotes.md b/lld/docs/ReleaseNotes.md
index 827d09bec5776..146e87c972568 100644
--- a/lld/docs/ReleaseNotes.md
+++ b/lld/docs/ReleaseNotes.md
@@ -29,8 +29,17 @@ from the [LLVM releases web site](https://llvm.org/releases/).
### ELF Improvements
+* Library callers can supply an `llvm::vfs::FileSystem` to `lld::lldMain` for
+ ELF input files, including archives, scripts, response files, and IR-level LTO
+ profiles.
+ Output files continue to use the real filesystem.
+
### Breaking changes
+* The `lld::Driver` callback takes an additional
+ `llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>` argument. Callers using
+ `LLD_HAS_DRIVER` receive the updated declarations automatically.
+
### COFF Improvements
### MinGW Improvements
diff --git a/lld/include/lld/Common/BPSectionOrdererBase.inc b/lld/include/lld/Common/BPSectionOrdererBase.inc
index b2e87d6c240fc..0dd0743dc61fc 100644
--- a/lld/include/lld/Common/BPSectionOrdererBase.inc
+++ b/lld/include/lld/Common/BPSectionOrdererBase.inc
@@ -68,7 +68,8 @@ template <class D> struct BPOrderer {
bool compressionSortStartupFunctions, bool verbose,
llvm::ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
- &rootSymbolToSectionIdxs)
+ &rootSymbolToSectionIdxs,
+ llvm::vfs::FileSystem &fs = *llvm::vfs::getRealFileSystem())
-> llvm::DenseMap<const Section *, int>;
std::optional<StringRef> static getResolvedLinkageName(StringRef name) {
@@ -164,7 +165,8 @@ auto BPOrderer<D>::computeOrder(
bool compressionSortStartupFunctions, bool verbose,
ArrayRef<Section *> sections,
const DenseMap<CachedHashStringRef, std::set<unsigned>>
- &rootSymbolToSectionIdxs) -> DenseMap<const Section *, int> {
+ &rootSymbolToSectionIdxs,
+ vfs::FileSystem &fs) -> DenseMap<const Section *, int> {
TimeTraceScope timeScope("Setup Balanced Partitioning");
DenseMap<const void *, uint64_t> sectionToIdx;
for (auto [i, isec] : llvm::enumerate(sections))
@@ -179,8 +181,7 @@ auto BPOrderer<D>::computeOrder(
DenseSet<CachedHashStringRef> uniqueResolvedProfiledFunctions;
std::unique_ptr<InstrProfReader> reader;
if (!profilePath.empty()) {
- auto fs = vfs::getRealFileSystem();
- auto readerOrErr = InstrProfReader::create(profilePath, *fs);
+ auto readerOrErr = InstrProfReader::create(profilePath, fs);
if (!readerOrErr) {
lld::error(toString(readerOrErr.takeError()));
return DenseMap<const Section *, int>{};
diff --git a/lld/include/lld/Common/Driver.h b/lld/include/lld/Common/Driver.h
index 8520e6e7e257c..f021bff4ae298 100644
--- a/lld/include/lld/Common/Driver.h
+++ b/lld/include/lld/Common/Driver.h
@@ -10,6 +10,7 @@
#define LLD_COMMON_DRIVER_H
#include "llvm/ADT/ArrayRef.h"
+#include "llvm/Support/VirtualFileSystemFwd.h"
#include "llvm/Support/raw_ostream.h"
namespace lld {
@@ -23,7 +24,8 @@ enum Flavor {
};
using Driver = bool (*)(llvm::ArrayRef<const char *>, llvm::raw_ostream &,
- llvm::raw_ostream &, bool, bool);
+ llvm::raw_ostream &, bool, bool,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem>);
struct DriverDef {
Flavor f;
@@ -41,8 +43,12 @@ struct Result {
// and re-entry would not be possible anymore. Use exitLld() in that case to
// properly exit your application and avoid intermittent crashes on exit caused
// by cleanup.
+// The optional filesystem is used for input files. Currently only the ELF
+// driver supports a non-null filesystem. Output files use the real filesystem.
+// The filesystem must support concurrent reads when threading is enabled.
Result lldMain(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, llvm::ArrayRef<DriverDef> drivers);
+ llvm::raw_ostream &stderrOS, llvm::ArrayRef<DriverDef> drivers,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = nullptr);
} // namespace lld
// With this macro, library users must specify which drivers they use, provide
@@ -52,7 +58,8 @@ Result lldMain(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
namespace lld { \
namespace name { \
bool link(llvm::ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS, \
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput); \
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput, \
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = nullptr); \
} \
}
diff --git a/lld/include/lld/Common/Reproduce.h b/lld/include/lld/Common/Reproduce.h
index 734d9e4011d19..2c30c4949c743 100644
--- a/lld/include/lld/Common/Reproduce.h
+++ b/lld/include/lld/Common/Reproduce.h
@@ -15,6 +15,9 @@
namespace llvm {
namespace opt { class Arg; }
+namespace vfs {
+class FileSystem;
+}
}
namespace lld {
@@ -23,6 +26,7 @@ namespace lld {
// beginning /. For example, "../foo.o" is converted to "home/john/foo.o",
// assuming that the current directory is "/home/john/bar".
std::string relativeToRoot(StringRef path);
+std::string relativeToRoot(StringRef path, llvm::vfs::FileSystem &fs);
// Quote a given string if it contains a space character.
std::string quote(StringRef s);
diff --git a/lld/tools/lld/lld.cpp b/lld/tools/lld/lld.cpp
index 53f0cbc6d9814..1f6b8e59ac359 100644
--- a/lld/tools/lld/lld.cpp
+++ b/lld/tools/lld/lld.cpp
@@ -53,7 +53,8 @@ extern bool inTestOutputDisabled;
// LLD-as-lib scenarios.
int unsafeLldMain(llvm::ArrayRef<const char *> args,
llvm::raw_ostream &stdoutOS, llvm::raw_ostream &stderrOS,
- llvm::ArrayRef<DriverDef> drivers, bool exitEarly);
+ llvm::ArrayRef<DriverDef> drivers, bool exitEarly,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs = nullptr);
} // namespace lld
// When in lit tests, tells how many times the LLD tool should re-execute the
diff --git a/lld/unittests/AsLibAll/AllDrivers.cpp b/lld/unittests/AsLibAll/AllDrivers.cpp
index fef3d8ca6f016..f0b7d3f434603 100644
--- a/lld/unittests/AsLibAll/AllDrivers.cpp
+++ b/lld/unittests/AsLibAll/AllDrivers.cpp
@@ -11,6 +11,7 @@
//===----------------------------------------------------------------------===//
#include "lld/Common/Driver.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include "gmock/gmock.h"
LLD_HAS_DRIVER(coff)
@@ -33,3 +34,19 @@ TEST(AsLib, AllDrivers) {
EXPECT_TRUE(lldInvoke({"lld-link"}));
EXPECT_TRUE(lldInvoke({"wasm-ld"}));
}
+
+TEST(AsLib, VirtualFileSystemRequiresELF) {
+ auto fs = llvm::makeIntrusiveRefCnt<llvm::vfs::InMemoryFileSystem>();
+ for (const char *name : {"ld64.lld", "lld-link", "wasm-ld", "ld.lld"}) {
+ std::vector<const char *> args = {name, "--version"};
+ if (llvm::StringRef(name) == "ld.lld")
+ args.insert(args.end(), {"-m", "i386pep"});
+ std::string diagnostics;
+ llvm::raw_string_ostream err(diagnostics);
+ lld::Result r = lld::lldMain(args, llvm::nulls(), err, LLD_ALL_DRIVERS, fs);
+ EXPECT_NE(r.retCode, 0);
+ EXPECT_TRUE(r.canRunAgain);
+ EXPECT_THAT(diagnostics,
+ testing::HasSubstr("only supported by the ELF driver"));
+ }
+}
diff --git a/lld/unittests/AsLibELF/CMakeLists.txt b/lld/unittests/AsLibELF/CMakeLists.txt
index 70a3f027076cd..e5108b8501473 100644
--- a/lld/unittests/AsLibELF/CMakeLists.txt
+++ b/lld/unittests/AsLibELF/CMakeLists.txt
@@ -2,10 +2,13 @@
# This test covers a more singular case where only one LLD driver is used in the
# target application executable.
+set(LLVM_LINK_COMPONENTS AsmParser)
+
add_lld_unittests(LLDAsLibELFTests
OutputStream.cpp
ROCm.cpp
SomeDrivers.cpp
+ VirtualFileSystem.cpp
)
target_link_libraries(LLDAsLibELFTests
diff --git a/lld/unittests/AsLibELF/VirtualFileSystem.cpp b/lld/unittests/AsLibELF/VirtualFileSystem.cpp
new file mode 100644
index 0000000000000..9e0239d4dcb74
--- /dev/null
+++ b/lld/unittests/AsLibELF/VirtualFileSystem.cpp
@@ -0,0 +1,328 @@
+//===- VirtualFileSystem.cpp ----------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/VirtualFileSystem.h"
+#include "lld/Common/Driver.h"
+#include "llvm/ADT/STLExtras.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Analysis/ModuleSummaryAnalysis.h"
+#include "llvm/Analysis/ProfileSummaryInfo.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
+#include "llvm/IR/Module.h"
+#include "llvm/Object/ArchiveWriter.h"
+#include "llvm/Object/ObjectFile.h"
+#include "llvm/Support/FileSystem.h"
+#include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Path.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Transforms/Utils/AssignGUID.h"
+#include "gmock/gmock.h"
+#include <cstdlib>
+
+using namespace llvm;
+using testing::HasSubstr;
+
+LLD_HAS_DRIVER(elf)
+
+namespace {
+bool hasAMDGPUTarget() {
+#define LLVM_TARGET(TargetName) \
+ if (StringRef(#TargetName) == "AMDGPU") \
+ return true;
+#include "llvm/Config/Targets.def"
+ return false;
+}
+
+class VirtualFileSystemTest : public testing::Test {
+protected:
+ SmallString<256> tempDir;
+ SmallString<256> inputDir;
+ SmallString<256> outputPath;
+ IntrusiveRefCntPtr<vfs::InMemoryFileSystem> fs =
+ makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+ std::unique_ptr<MemoryBuffer> object;
+ std::string diagnostics;
+
+ void SetUp() override {
+ ASSERT_FALSE(sys::fs::createUniqueDirectory("lld-vfs", tempDir));
+ inputDir = tempDir;
+ sys::path::append(inputDir, "virtual");
+ ASSERT_FALSE(fs->setCurrentWorkingDirectory(inputDir));
+ outputPath = tempDir;
+ sys::path::append(outputPath, "out.so");
+
+ const char *sourceDir = std::getenv("LLD_SRC_DIR");
+ ASSERT_NE(sourceDir, nullptr);
+ SmallString<256> path(sourceDir);
+ sys::path::append(path, "unittests", "AsLibELF", "Inputs", "kernel1.o");
+ auto buffer = MemoryBuffer::getFile(path);
+ ASSERT_TRUE(buffer) << buffer.getError().message();
+ object = std::move(*buffer);
+ }
+
+ void TearDown() override {
+ if (!tempDir.empty())
+ EXPECT_FALSE(sys::fs::remove_directories(tempDir));
+ }
+
+ void addFile(StringRef name, StringRef contents) {
+ // Deliberately leave the buffer identifier empty: paths must be resolved
+ // using the virtual filename, independently of the backing buffer's name.
+ ASSERT_TRUE(fs->addFile(name, 0, MemoryBuffer::getMemBufferCopy(contents)));
+ }
+
+ void addObject(StringRef name = "input.o") {
+ addFile(name, object->getBuffer());
+ SmallString<256> path(name);
+ ASSERT_FALSE(fs->makeAbsolute(path));
+ ASSERT_FALSE(sys::fs::exists(path));
+ }
+
+ lld::Result link(ArrayRef<const char *> inputs, vfs::FileSystem *inputFS) {
+ SmallVector<const char *> args = {"ld.lld", "--threads=2", "-o",
+ outputPath.c_str()};
+ if (!llvm::is_contained(inputs, StringRef("-r")))
+ args.push_back("-shared");
+ args.append(inputs.begin(), inputs.end());
+ diagnostics.clear();
+ raw_string_ostream err(diagnostics);
+ return lld::lldMain(args, nulls(), err, {{lld::Gnu, &lld::elf::link}},
+ inputFS);
+ }
+
+ void expectSuccess(ArrayRef<const char *> inputs,
+ vfs::FileSystem *inputFS = nullptr,
+ StringRef symbol = "main_kernel") {
+ lld::Result result = link(inputs, inputFS ? inputFS : fs.get());
+ ASSERT_EQ(result.retCode, 0) << diagnostics;
+ ASSERT_TRUE(result.canRunAgain);
+ auto output = object::ObjectFile::createObjectFile(outputPath);
+ ASSERT_TRUE(bool(output)) << toString(output.takeError());
+ EXPECT_EQ(output->getBinary()->getArch(), Triple::amdgpu);
+ bool found = false;
+ for (const object::SymbolRef &sym : output->getBinary()->symbols()) {
+ auto name = sym.getName();
+ ASSERT_TRUE(bool(name)) << toString(name.takeError());
+ found |= *name == symbol;
+ }
+ EXPECT_TRUE(found) << "missing symbol " << symbol;
+ }
+
+ void addArchive(bool thin) {
+ SmallVector<NewArchiveMember, 1> members;
+ members.emplace_back(object->getMemBufferRef());
+ members.back().MemberName = "member.o";
+ auto archive = writeArchiveToBuffer(
+ members, SymtabWritingMode::NormalSymtab, object::Archive::K_GNU,
+ /*Deterministic=*/true, thin);
+ ASSERT_TRUE(bool(archive)) << toString(archive.takeError());
+ ASSERT_TRUE(fs->addFile("lib/libtest.a", 0, std::move(*archive)));
+ if (thin)
+ addObject("lib/member.o");
+ }
+
+ void addBitcode(bool thin) {
+ LLVMContext context;
+ SMDiagnostic error;
+ auto module = parseAssemblyString(R"(
+ target triple = "amdgpu9.00-amd-amdhsa"
+ target datalayout = "e-m:e-p:64:64-p1:64:64-p2:32:32-p3:32:32-p4:64:64-p5:32:32-p6:32:32-p15:32:32-i64:64-v16:16-v24:32-v32:32-v48:64-v96:128-v192:256-v256:256-v512:512-v1024:1024-v2048:2048-n32:64-S32-A5"
+ define void @f() {
+ ret void
+ }
+ )",
+ error, context);
+ ASSERT_TRUE(module);
+ SmallString<0> bitcode;
+ raw_svector_ostream stream(bitcode);
+ if (thin) {
+ AssignGUIDPass::runOnModule(*module);
+ ProfileSummaryInfo psi(*module);
+ ModuleSummaryIndex index =
+ buildModuleSummaryIndex(*module, nullptr, &psi);
+ WriteBitcodeToFile(*module, stream, /*ShouldPreserveUseListOrder=*/false,
+ &index, /*GenerateHash=*/true);
+ } else {
+ WriteBitcodeToFile(*module, stream);
+ }
+ addFile("input.bc", bitcode);
+ }
+};
+
+TEST_F(VirtualFileSystemTest, Object) {
+ addObject();
+ // Also exercise ELF selection when ld.lld defaults to the MinGW driver.
+ expectSuccess({"-m", "elf64_amdgpu", "input.o"});
+}
+
+TEST_F(VirtualFileSystemTest, RelocatableOutput) {
+ addObject();
+ expectSuccess({"-m", "elf64_amdgpu", "-r", "input.o"});
+}
+
+TEST_F(VirtualFileSystemTest, ArchiveSearch) {
+ addArchive(/*thin=*/false);
+ expectSuccess({"-m", "elf64_amdgpu", "--whole-archive", "-Llib", "-ltest"});
+ expectSuccess(
+ {"-m", "elf64_amdgpu", "--whole-archive", "-Llib", "-l:libtest.a"});
+ expectSuccess({"-m", "elf64_amdgpu", "-u", "main_kernel", "-Llib", "-ltest"});
+}
+
+TEST_F(VirtualFileSystemTest, ThinArchive) {
+ addArchive(/*thin=*/true);
+ expectSuccess({"-m", "elf64_amdgpu", "--whole-archive", "lib/libtest.a"});
+ expectSuccess({"-m", "elf64_amdgpu", "-u", "main_kernel", "-Llib", "-ltest"});
+}
+
+TEST_F(VirtualFileSystemTest, BitcodeAndSampleProfile) {
+ if (!hasAMDGPUTarget())
+ GTEST_SKIP() << "AMDGPU backend not built";
+ addBitcode(/*thin=*/false);
+ addFile("profile.prof", "f:0:0\n");
+ expectSuccess({"-m", "elf64_amdgpu", "--plugin-opt=mcpu=gfx900", "input.bc",
+ "--lto-sample-profile=profile.prof"},
+ nullptr, "f");
+}
+
+TEST_F(VirtualFileSystemTest, ThinLTOCacheUsesVirtualProfileContents) {
+ if (!hasAMDGPUTarget())
+ GTEST_SKIP() << "AMDGPU backend not built";
+ addBitcode(/*thin=*/true);
+ addFile("profile.prof", "f:0:0\n");
+ SmallString<256> cacheDir(tempDir);
+ sys::path::append(cacheDir, "cache");
+ std::string cacheArg = ("--thinlto-cache-dir=" + cacheDir).str();
+ auto linkWithCache = [&] {
+ expectSuccess({"-m", "elf64_amdgpu", "--plugin-opt=mcpu=gfx900", "input.bc",
+ "--lto-sample-profile=profile.prof", cacheArg.c_str()},
+ nullptr, "f");
+ };
+ auto countEntries = [&] {
+ unsigned count = 0;
+ std::error_code ec;
+ for (sys::fs::directory_iterator it(cacheDir, ec), end; it != end && !ec;
+ it.increment(ec))
+ if (sys::path::filename(it->path()).starts_with("llvmcache-"))
+ ++count;
+ EXPECT_FALSE(ec);
+ return count;
+ };
+ linkWithCache();
+ unsigned entries = countEntries();
+ ASSERT_GT(entries, 0u);
+ linkWithCache();
+ EXPECT_EQ(countEntries(), entries);
+
+ // Keep the paths and bitcode identical, but change the virtual profile.
+ fs = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+ ASSERT_FALSE(fs->setCurrentWorkingDirectory(inputDir));
+ addBitcode(/*thin=*/true);
+ addFile("profile.prof", "f:100:0\n 1: 100\n");
+ linkWithCache();
+ EXPECT_GT(countEntries(), entries);
+}
+
+TEST_F(VirtualFileSystemTest, ScriptSearchAndInclude) {
+ addObject("scripts/member.o");
+ addFile("scripts/main.ld", "INCLUDE fragment.ld\n");
+ addFile("scripts/fragment.ld", "GROUP(member.o)\n");
+ expectSuccess({"-m", "elf64_amdgpu", "-Lscripts", "-Tmain.ld"});
+}
+
+TEST_F(VirtualFileSystemTest, ScriptUnderSysroot) {
+ addObject("sysroot/lib/member.o");
+ addFile("sysroot/main.ld", "INPUT(/lib/member.o)\n");
+ SmallString<256> sysroot(inputDir);
+ sys::path::append(sysroot, "sysroot");
+ expectSuccess({"-m", "elf64_amdgpu", "--sysroot", sysroot.c_str(),
+ "-Tsysroot/main.ld"});
+}
+
+TEST_F(VirtualFileSystemTest, VersionScript) {
+ addObject();
+ addFile("scripts/versions", "VERSION_1 { global: *; };\n");
+ expectSuccess({"-m", "elf64_amdgpu", "input.o", "-Lscripts",
+ "--version-script=versions"});
+}
+
+TEST_F(VirtualFileSystemTest, NestedResponseFiles) {
+ addObject("input with spaces.o");
+ addFile("outer.rsp", "@inner.rsp\n");
+ addFile("inner.rsp", "-m elf64_amdgpu \"input with spaces.o\"\n");
+ // Both flavor selection and ELF option parsing must expand the response
+ // files through the supplied filesystem.
+ expectSuccess({"@outer.rsp"});
+}
+
+TEST_F(VirtualFileSystemTest, MissingResponseFile) {
+ // An explicit emulation skips response-file expansion in flavor selection.
+ // The ELF parser must report failures through the supplied error stream.
+ lld::Result result = link({"-m", "elf64_amdgpu", "@missing.rsp"}, fs.get());
+ EXPECT_NE(result.retCode, 0);
+ EXPECT_TRUE(result.canRunAgain);
+ EXPECT_THAT(diagnostics, HasSubstr("missing.rsp"));
+
+ result = link({"@missing.rsp"}, fs.get());
+ EXPECT_NE(result.retCode, 0);
+ EXPECT_TRUE(result.canRunAgain);
+ EXPECT_THAT(diagnostics, HasSubstr("missing.rsp"));
+}
+
+TEST_F(VirtualFileSystemTest, ReentryUsesNewFilesystem) {
+ addObject();
+ expectSuccess({"-m", "elf64_amdgpu", "input.o"});
+
+ auto empty = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+ ASSERT_FALSE(empty->setCurrentWorkingDirectory(inputDir));
+ lld::Result result = link({"-m", "elf64_amdgpu", "input.o"}, empty.get());
+ EXPECT_NE(result.retCode, 0);
+ EXPECT_TRUE(result.canRunAgain);
+ EXPECT_THAT(diagnostics, HasSubstr("cannot open input.o"));
+
+ expectSuccess({"-m", "elf64_amdgpu", "input.o"});
+}
+
+TEST_F(VirtualFileSystemTest, OverlayPrecedenceAndFallback) {
+ SmallString<256> diskPath(tempDir);
+ sys::path::append(diskPath, "disk.o");
+ {
+ std::error_code ec;
+ raw_fd_ostream out(diskPath, ec);
+ ASSERT_FALSE(ec);
+ out << object->getBuffer();
+ }
+ auto overlay =
+ makeIntrusiveRefCnt<vfs::OverlayFileSystem>(vfs::getRealFileSystem());
+ overlay->pushOverlay(fs);
+ expectSuccess({"-m", "elf64_amdgpu", diskPath.c_str()}, overlay.get());
+
+ addFile(diskPath, "this is not an object file");
+ lld::Result result =
+ link({"-m", "elf64_amdgpu", diskPath.c_str()}, overlay.get());
+ EXPECT_NE(result.retCode, 0);
+ EXPECT_TRUE(result.canRunAgain);
+ EXPECT_THAT(diagnostics, HasSubstr("unknown directive"));
+}
+
+TEST_F(VirtualFileSystemTest, ReproducerUsesVirtualWorkingDirectory) {
+ addObject();
+ SmallString<256> tarPath(tempDir);
+ sys::path::append(tarPath, "repro.tar");
+ expectSuccess(
+ {"-m", "elf64_amdgpu", "input.o", "--reproduce", tarPath.c_str()});
+ auto tar = MemoryBuffer::getFile(tarPath);
+ ASSERT_TRUE(tar) << tar.getError().message();
+ // Both response.txt and the archived member must include the virtual
+ // working directory, which does not exist on the host filesystem.
+ StringRef contents = (*tar)->getBuffer();
+ size_t first = contents.find("virtual/input.o");
+ ASSERT_NE(first, StringRef::npos);
+ EXPECT_NE(contents.find("virtual/input.o", first + 1), StringRef::npos);
+}
+} // namespace
diff --git a/lld/wasm/Driver.cpp b/lld/wasm/Driver.cpp
index 1de9851d5ed3e..91ab1f5f02b41 100644
--- a/lld/wasm/Driver.cpp
+++ b/lld/wasm/Driver.cpp
@@ -126,7 +126,13 @@ static bool hasZOption(opt::InputArgList &args, StringRef key) {
} // anonymous namespace
bool link(ArrayRef<const char *> args, llvm::raw_ostream &stdoutOS,
- llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput) {
+ llvm::raw_ostream &stderrOS, bool exitEarly, bool disableOutput,
+ llvm::IntrusiveRefCntPtr<llvm::vfs::FileSystem> fs) {
+ if (fs) {
+ stderrOS << "lld: error: a virtual filesystem is only supported by the ELF "
+ "driver\n";
+ return false;
+ }
// This driver-specific context will be freed later by unsafeLldMain().
auto *context = new CommonLinkerContext;
diff --git a/llvm/include/llvm/LTO/Config.h b/llvm/include/llvm/LTO/Config.h
index 7907156a75fbc..dc3482f49184c 100644
--- a/llvm/include/llvm/LTO/Config.h
+++ b/llvm/include/llvm/LTO/Config.h
@@ -23,6 +23,7 @@
#include "llvm/Passes/PassBuilder.h"
#include "llvm/Support/CodeGen.h"
#include "llvm/Support/Compiler.h"
+#include "llvm/Support/VirtualFileSystem.h"
#include "llvm/Target/TargetOptions.h"
#include <functional>
@@ -47,6 +48,12 @@ struct Config {
};
// Note: when adding fields here, consider whether they need to be added to
// computeLTOCacheKey in LTO.cpp.
+ /// Filesystem for IR-level profiles and imported modules. Must be non-null
+ /// and support concurrent reads when using parallel backends.
+ /// Output files, the on-disk cache, and machine-code profiles use the real
+ /// filesystem.
+ IntrusiveRefCntPtr<vfs::FileSystem> FS = vfs::getRealFileSystem();
+
std::string CPU;
TargetOptions Options;
std::vector<std::string> MAttrs;
diff --git a/llvm/lib/LTO/LTO.cpp b/llvm/lib/LTO/LTO.cpp
index 4594c52fb5f6e..a75ce01476347 100644
--- a/llvm/lib/LTO/LTO.cpp
+++ b/llvm/lib/LTO/LTO.cpp
@@ -375,12 +375,17 @@ std::string llvm::computeLTOCacheKey(
AddUint64(V);
if (!Conf.SampleProfile.empty()) {
- auto FileOrErr = MemoryBuffer::getFile(Conf.SampleProfile);
+ auto FileOrErr = Conf.FS->getBufferForFile(
+ Conf.SampleProfile, /*FileSize=*/-1, /*RequiresNullTerminator=*/true,
+ /*IsVolatile=*/false, /*IsText=*/false);
if (FileOrErr) {
Hasher.update(FileOrErr.get()->getBuffer());
if (!Conf.ProfileRemapping.empty()) {
- FileOrErr = MemoryBuffer::getFile(Conf.ProfileRemapping);
+ FileOrErr = Conf.FS->getBufferForFile(
+ Conf.ProfileRemapping, /*FileSize=*/-1,
+ /*RequiresNullTerminator=*/true, /*IsVolatile=*/false,
+ /*IsText=*/false);
if (FileOrErr)
Hasher.update(FileOrErr.get()->getBuffer());
}
diff --git a/llvm/lib/LTO/LTOBackend.cpp b/llvm/lib/LTO/LTOBackend.cpp
index d9a6551139d37..9d8eb28b19bbf 100644
--- a/llvm/lib/LTO/LTOBackend.cpp
+++ b/llvm/lib/LTO/LTOBackend.cpp
@@ -294,7 +294,7 @@ static void runNewPMPasses(const Config &Conf, Module &Mod, TargetMachine *TM,
StandardInstrumentations SI(Mod.getContext(), Conf.DebugPassManager,
Conf.VerifyEach);
SI.registerCallbacks(PIC, &MAM);
- PassBuilder PB(TM, Conf.PTO, PGOOpt, &PIC);
+ PassBuilder PB(TM, Conf.PTO, PGOOpt, &PIC, Conf.FS);
RegisterPassPlugins(Conf, PB);
@@ -747,13 +747,18 @@ Error lto::thinBackend(const Config &Conf, unsigned Task, AddStreamFn AddStream,
}
ErrorOr<std::unique_ptr<llvm::MemoryBuffer>> MBOrErr =
- llvm::MemoryBuffer::getFile(Identifier);
+ Conf.FS->getBufferForFile(Identifier, /*FileSize=*/-1,
+ /*RequiresNullTerminator=*/true,
+ /*IsVolatile=*/false, /*IsText=*/false);
if (!MBOrErr)
return Expected<std::unique_ptr<llvm::Module>>(make_error<StringError>(
Twine("Error loading imported file ") + Identifier + " : ",
MBOrErr.getError()));
- Expected<BitcodeModule> BMOrErr = findThinLTOModule(**MBOrErr);
+ // VFS buffers may have a different identifier from the module path in the
+ // combined index. Keep that path for summary lookup and local promotion.
+ Expected<BitcodeModule> BMOrErr =
+ findThinLTOModule(MemoryBufferRef((*MBOrErr)->getBuffer(), Identifier));
if (!BMOrErr)
return Expected<std::unique_ptr<llvm::Module>>(make_error<StringError>(
Twine("Error loading imported file ") + Identifier + " : " +
diff --git a/llvm/lib/Passes/PassBuilderPipelines.cpp b/llvm/lib/Passes/PassBuilderPipelines.cpp
index 8e532b5980e74..18de66074e66e 100644
--- a/llvm/lib/Passes/PassBuilderPipelines.cpp
+++ b/llvm/lib/Passes/PassBuilderPipelines.cpp
@@ -2077,9 +2077,9 @@ PassBuilder::buildLTODefaultPipeline(OptimizationLevel Level,
if (PGOOpt && PGOOpt->Action == PGOOptions::SampleUse) {
// Load sample profile before running the LTO optimization pipeline.
- MPM.addPass(SampleProfileLoaderPass(PGOOpt->ProfileFile,
- PGOOpt->ProfileRemappingFile,
- ThinOrFullLTOPhase::FullLTOPostLink));
+ MPM.addPass(SampleProfileLoaderPass(
+ PGOOpt->ProfileFile, PGOOpt->ProfileRemappingFile,
+ ThinOrFullLTOPhase::FullLTOPostLink, FS));
// Cache ProfileSummaryAnalysis once to avoid the potential need to insert
// RequireAnalysisPass for PSI before subsequent non-module passes.
MPM.addPass(RequireAnalysisPass<ProfileSummaryAnalysis, Module>());
diff --git a/llvm/lib/Passes/PassRegistry.def b/llvm/lib/Passes/PassRegistry.def
index af4ce5029551d..f6e6fd9bb5d27 100644
--- a/llvm/lib/Passes/PassRegistry.def
+++ b/llvm/lib/Passes/PassRegistry.def
@@ -137,7 +137,7 @@ MODULE_PASS("openmp-opt-postlink",
MODULE_PASS("partial-inliner", PartialInlinerPass())
MODULE_PASS("pgo-icall-prom", PGOIndirectCallPromotion())
MODULE_PASS("pgo-instr-gen", PGOInstrumentationGen())
-MODULE_PASS("pgo-instr-use", PGOInstrumentationUse())
+MODULE_PASS("pgo-instr-use", PGOInstrumentationUse("", "", false, FS))
MODULE_PASS("pre-isel-intrinsic-lowering", PreISelIntrinsicLoweringPass(TM))
MODULE_PASS("print", PrintModulePass(errs()))
MODULE_PASS("print-callgraph", CallGraphPrinterPass(errs()))
@@ -164,7 +164,8 @@ MODULE_PASS("rewrite-statepoints-for-gc", RewriteStatepointsForGC())
MODULE_PASS("rewrite-symbols", RewriteSymbolPass())
MODULE_PASS("rpo-function-attrs", ReversePostOrderFunctionAttrsPass())
MODULE_PASS("rtsan", RealtimeSanitizerPass())
-MODULE_PASS("sample-profile", SampleProfileLoaderPass())
+MODULE_PASS("sample-profile",
+ SampleProfileLoaderPass("", "", ThinOrFullLTOPhase::None, FS))
MODULE_PASS("sancov-module", SanitizerCoveragePass())
MODULE_PASS("sanmd-module", SanitizerBinaryMetadataPass())
// TODO: Rename (or parameterize by optimization level)
@@ -248,7 +249,7 @@ MODULE_PASS_WITH_PARAMS(
parseIPSCCPOptions, "no-func-spec;func-spec")
MODULE_PASS_WITH_PARAMS(
"memprof-use", "MemProfUsePass",
- [](std::string Opts) { return MemProfUsePass(Opts); },
+ [this](std::string Opts) { return MemProfUsePass(Opts, FS); },
parseMemProfUsePassOptions, "profile-filename=S")
MODULE_PASS_WITH_PARAMS(
"msan", "MemorySanitizerPass",
diff --git a/llvm/unittests/CMakeLists.txt b/llvm/unittests/CMakeLists.txt
index c6ce09b3d67f2..c18243e224d88 100644
--- a/llvm/unittests/CMakeLists.txt
+++ b/llvm/unittests/CMakeLists.txt
@@ -53,6 +53,7 @@ add_subdirectory(InterfaceStub)
add_subdirectory(IR)
add_subdirectory(LineEditor)
add_subdirectory(Linker)
+add_subdirectory(LTO)
add_subdirectory(MC)
add_subdirectory(MI)
add_subdirectory(MIR)
diff --git a/llvm/unittests/LTO/CMakeLists.txt b/llvm/unittests/LTO/CMakeLists.txt
new file mode 100644
index 0000000000000..b74096bf0cd9e
--- /dev/null
+++ b/llvm/unittests/LTO/CMakeLists.txt
@@ -0,0 +1,18 @@
+set(LLVM_LINK_COMPONENTS
+ Analysis
+ AsmParser
+ BitReader
+ BitWriter
+ Core
+ LTO
+ Support
+ TargetParser
+ TransformUtils
+ nativecodegen
+)
+
+add_llvm_unittest(LTOTests
+ VirtualFileSystemTest.cpp
+)
+
+target_link_libraries(LTOTests PRIVATE LLVMTestingSupport)
diff --git a/llvm/unittests/LTO/VirtualFileSystemTest.cpp b/llvm/unittests/LTO/VirtualFileSystemTest.cpp
new file mode 100644
index 0000000000000..6281c139e6ccf
--- /dev/null
+++ b/llvm/unittests/LTO/VirtualFileSystemTest.cpp
@@ -0,0 +1,142 @@
+//===- VirtualFileSystemTest.cpp ------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "llvm/Support/VirtualFileSystem.h"
+#include "llvm/ADT/SmallString.h"
+#include "llvm/Analysis/ModuleSummaryAnalysis.h"
+#include "llvm/Analysis/ProfileSummaryInfo.h"
+#include "llvm/AsmParser/Parser.h"
+#include "llvm/Bitcode/BitcodeReader.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
+#include "llvm/IR/Constants.h"
+#include "llvm/IR/DiagnosticPrinter.h"
+#include "llvm/IR/Module.h"
+#include "llvm/LTO/LTOBackend.h"
+#include "llvm/Support/CommandLine.h"
+#include "llvm/Support/SaveAndRestore.h"
+#include "llvm/Support/SourceMgr.h"
+#include "llvm/Support/TargetSelect.h"
+#include "llvm/TargetParser/Host.h"
+#include "llvm/Testing/Support/Error.h"
+#include "llvm/Transforms/Utils/AssignGUID.h"
+#include "gtest/gtest.h"
+
+using namespace llvm;
+
+namespace {
+
+TEST(LTOVirtualFileSystemTest, ImportUsesLogicalModuleIdentifier) {
+ if (InitializeNativeTarget())
+ GTEST_SKIP() << "Native target not built";
+
+ LLVMContext Context;
+ Context.enableDebugTypeODRUniquing();
+ SMDiagnostic Err;
+ auto Source = parseAssemblyString(R"(
+ @local = internal global i32 42
+ define i32 @callee() {
+ %v = load i32, ptr @local
+ ret i32 %v
+ }
+ )",
+ Err, Context);
+ ASSERT_TRUE(Source);
+ Source->setTargetTriple(Triple(sys::getDefaultTargetTriple()));
+ AssignGUIDPass::runOnModule(*Source);
+ ProfileSummaryInfo PSI(*Source);
+ ModuleSummaryIndex Summary = buildModuleSummaryIndex(*Source, nullptr, &PSI);
+ SmallString<0> Bitcode;
+ raw_svector_ostream OS(Bitcode);
+ WriteBitcodeToFile(*Source, OS, /*ShouldPreserveUseListOrder=*/false,
+ &Summary, /*GenerateHash=*/true);
+
+ StringRef Path = "callee.bc";
+ ModuleSummaryIndex CombinedIndex(/*HaveGVs=*/false);
+ ASSERT_THAT_ERROR(
+ readModuleSummaryIndex(MemoryBufferRef(Bitcode, Path), CombinedIndex),
+ Succeeded());
+ CombinedIndex.addModule("main.bc");
+ FunctionImporter::ImportIDTable ImportIDs;
+ FunctionImporter::ImportMapTy Imports(ImportIDs);
+ Imports.addDefinition(Path, Source->getFunction("callee")->getGUID());
+ Imports.addDefinition(Path, Source->getNamedGlobal("local")->getGUID());
+ std::string PromotedName = ModuleSummaryIndex::getGlobalNameForLocal(
+ "local", CombinedIndex.getModuleHash(Path));
+
+ // Neither the empty identifier nor an unrelated backing filename may
+ // replace the module path used in the combined index.
+ for (StringRef BufferName : {StringRef(), StringRef("backing.bc")}) {
+ SCOPED_TRACE(BufferName.str());
+ auto FS = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(FS->addFile(
+ Path, 0, MemoryBuffer::getMemBufferCopy(Bitcode, BufferName)));
+ Module Dest("main.bc", Context);
+ Dest.setTargetTriple(Source->getTargetTriple());
+
+ lto::Config Conf;
+ Conf.FS = FS;
+ bool Imported = false;
+ Conf.PostImportModuleHook = [&](unsigned, const Module &M) {
+ Imported = true;
+ const Function *Callee = M.getFunction("callee");
+ EXPECT_TRUE(Callee && !Callee->isDeclaration());
+ const GlobalVariable *Local = M.getNamedGlobal(PromotedName);
+ EXPECT_TRUE(Local && Local->hasInitializer());
+ if (Local && Local->hasInitializer())
+ EXPECT_EQ(cast<ConstantInt>(Local->getInitializer())->getZExtValue(),
+ 42u);
+ return false;
+ };
+ ASSERT_THAT_ERROR(lto::thinBackend(Conf, 0, nullptr, Dest, CombinedIndex,
+ Imports, GVSummaryMapTy(),
+ /*ModuleMap=*/nullptr,
+ /*CodeGenOnly=*/false, {}),
+ Succeeded());
+ EXPECT_TRUE(Imported);
+ }
+}
+
+TEST(LTOVirtualFileSystemTest, CustomPipelineUsesVirtualSampleProfile) {
+ if (InitializeNativeTarget())
+ GTEST_SKIP() << "Native target not built";
+
+ LLVMContext Context;
+ std::string Diagnostics;
+ Context.setDiagnosticHandlerCallBack(
+ [](const DiagnosticInfo *DI, void *Opaque) {
+ raw_string_ostream OS(*static_cast<std::string *>(Opaque));
+ DiagnosticPrinterRawOStream Printer(OS);
+ DI->print(Printer);
+ },
+ &Diagnostics);
+ SMDiagnostic Err;
+ auto M = parseAssemblyString("define void @f() { ret void }", Err, Context);
+ ASSERT_TRUE(M);
+ M->setTargetTriple(Triple(sys::getDefaultTargetTriple()));
+
+ // The explicitly named pass uses -sample-profile-file, independently of
+ // Config::SampleProfile, which configures the default LTO pipeline.
+ auto *ProfileOption = static_cast<cl::opt<std::string> *>(
+ cl::getRegisteredOptions().lookup("sample-profile-file"));
+ ASSERT_NE(ProfileOption, nullptr);
+ SaveAndRestore<std::string> RestoreProfile(ProfileOption->getValue(),
+ "virtual-profile.prof");
+ auto FS = makeIntrusiveRefCnt<vfs::InMemoryFileSystem>();
+ ASSERT_TRUE(FS->addFile("virtual-profile.prof", 0,
+ MemoryBuffer::getMemBufferCopy("f:0:0\n")));
+ lto::Config Conf;
+ Conf.FS = FS;
+ Conf.OptPipeline = "sample-profile";
+ Conf.PostOptModuleHook = [](unsigned, const Module &) { return false; };
+ ModuleSummaryIndex Index(/*HaveGVs=*/false);
+ ASSERT_THAT_ERROR(lto::backend(Conf, nullptr, 1, *M, Index, {}), Succeeded());
+ EXPECT_TRUE(Diagnostics.empty()) << Diagnostics;
+ EXPECT_NE(M->getProfileSummary(/*IsCS=*/false), nullptr);
+}
+
+} // namespace
More information about the llvm-commits
mailing list