From 0f3ba5a2085212978b60470e18dbd38b44daf80e Mon Sep 17 00:00:00 2001 From: David Truby Date: Thu, 8 Jan 2026 14:35:47 +0100 Subject: [PATCH] [lld][COFF] Add /linkreprofullpathrsp flag (#174971) This patch adds the /linkreprofullpathrsp flag with the same behaviour as link.exe. This flag emits a file containing the full paths to each object passed to the link line. This is used in particular when linking Arm64X binaries, as you need the full path to all the Arm64 objects that were used in a standard Arm64 build. See: https://learn.microsoft.com/en-us/cpp/build/reference/link-repro-full-path-rsp for the Microsoft documentation of the flag. Relands #165449 --- lld/COFF/Driver.cpp | 57 ++++++++++++++++++++----- lld/COFF/Driver.h | 13 +++++- lld/COFF/Options.td | 3 ++ lld/docs/ReleaseNotes.rst | 2 + lld/test/COFF/linkreprofullpathrsp.test | 43 +++++++++++++++++++ 5 files changed, 105 insertions(+), 13 deletions(-) create mode 100644 lld/test/COFF/linkreprofullpathrsp.test diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 121cb32c5f09..a5ce5829eada 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -340,7 +340,26 @@ void LinkerDriver::addBuffer(std::unique_ptr mb, } } -void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { +void LinkerDriver::handleReproFile(StringRef path, InputOpt inputOpt) { + if (!reproFile) + return; + + *reproFile << '"'; + if (inputOpt == InputOpt::DefaultLib) + *reproFile << "/defaultlib:"; + else if (inputOpt == InputOpt::WholeArchive) + *reproFile << "/wholearchive:"; + + SmallString<128> absPath = path; + std::error_code ec = sys::fs::make_absolute(absPath); + if (ec) + Err(ctx) << "cannot find absolute path for reproFile for " << absPath + << ": " << ec.message(); + sys::path::remove_dots(absPath, true); + *reproFile << absPath << "\"\n"; +} + +void LinkerDriver::enqueuePath(StringRef path, bool lazy, InputOpt inputOpt) { auto future = std::make_shared>( createFutureForFile(std::string(path))); std::string pathStr = std::string(path); @@ -378,8 +397,11 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { Err(ctx) << msg; else Err(ctx) << msg << "; did you mean '" << nearest << "'"; - } else - ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy); + } else { + handleReproFile(pathStr, inputOpt); + ctx.driver.addBuffer(std::move(mb), inputOpt == InputOpt::WholeArchive, + lazy); + } }); } @@ -428,10 +450,9 @@ void LinkerDriver::enqueueArchiveMember(const Archive::Child &c, StringRef parentName) { auto reportBufferError = [=](Error &&e) { - StringRef childName = - CHECK(c.getName(), - "could not get child name for archive " + parentName + - " while loading symbol " + toCOFFString(ctx, sym)); + StringRef childName = CHECK( + c.getName(), "could not get child name for archive " + parentName + + " while loading symbol " + toCOFFString(ctx, sym)); Fatal(ctx) << "could not get the buffer for the member defining symbol " << &sym << ": " << parentName << "(" << childName << "): " << std::move(e); @@ -537,7 +558,7 @@ void LinkerDriver::parseDirectives(InputFile *file) { break; case OPT_defaultlib: if (std::optional path = findLibIfNew(arg->getValue())) - enqueuePath(*path, false, false); + enqueuePath(*path, false, InputOpt::DefaultLib); break; case OPT_entry: if (!arg->getValue()[0]) @@ -1638,6 +1659,15 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { } } } + // Handle /linkreprofullpathrsp + if (auto *arg = args.getLastArg(OPT_linkreprofullpathrsp)) { + std::error_code ec; + reproFile = std::make_unique(arg->getValue(), ec); + if (ec) { + Err(ctx) << "cannot open " << arg->getValue() << ": " << ec.message(); + reproFile.reset(); + } + } if (!args.hasArg(OPT_INPUT, OPT_wholearchive_file)) { if (args.hasArg(OPT_deffile)) @@ -2280,11 +2310,13 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { break; case OPT_wholearchive_file: if (std::optional path = findFileIfNew(arg->getValue())) - enqueuePath(*path, true, inLib); + enqueuePath(*path, inLib, InputOpt::WholeArchive); break; case OPT_INPUT: if (std::optional path = findFileIfNew(arg->getValue())) - enqueuePath(*path, isWholeArchive(*path), inLib); + enqueuePath(*path, inLib, + isWholeArchive(*path) ? InputOpt::WholeArchive + : InputOpt::None); break; default: // Ignore other options. @@ -2324,7 +2356,7 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { // addWinSysRootLibSearchPaths(), which is why they are in a separate loop. for (auto *arg : args.filtered(OPT_defaultlib)) if (std::optional path = findLibIfNew(arg->getValue())) - enqueuePath(*path, false, false); + enqueuePath(*path, false, InputOpt::DefaultLib); run(); if (errorCount()) return; @@ -2890,6 +2922,9 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { if (config->showTiming) ctx.rootTimer.print(); + // Clean up /linkreprofullpathrsp file + reproFile.reset(); + if (config->timeTraceEnabled) { // Manually stop the topmost "COFF link" scope, since we're shutting down. timeTraceProfilerEnd(); diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 14710d5853bc..e0c447cfc7f8 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -88,11 +88,13 @@ public: void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym, StringRef parentName); - void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); } + enum class InputOpt { None, DefaultLib, WholeArchive }; + void enqueuePDB(StringRef Path) { enqueuePath(Path, false); } MemoryBufferRef takeBuffer(std::unique_ptr mb); - void enqueuePath(StringRef path, bool wholeArchive, bool lazy); + void enqueuePath(StringRef path, bool lazy, + InputOpt inputOpt = InputOpt::None); // Returns a list of chunks of selected symbols. std::vector getChunks() const; @@ -138,6 +140,10 @@ private: // std::string getImportName(bool asLib); + // Write fullly resolved path to repro file if /linkreprofullpathrsp + // is specified. + void handleReproFile(StringRef path, InputOpt inputOpt); + void createImportLibrary(bool asLib); // Used by the resolver to parse .drectve section contents. @@ -193,6 +199,9 @@ private: int sdkMajor = 0; llvm::SmallString<128> windowsSdkLibPath; + // For linkreprofullpathrsp + std::unique_ptr reproFile; + // Functions below this line are defined in DriverUtils.cpp. void printHelp(const char *argv0); diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index 2e790baa5a00..32e55f57fb69 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -75,6 +75,9 @@ def link : F<"link">, HelpText<"Ignored for compatibility">; def linkrepro : Joined<["/", "-", "/?", "-?"], "linkrepro:">, MetaVarName<"directory">, HelpText<"Write repro.tar containing inputs and command to reproduce link">; +def linkreprofullpathrsp : Joined<["/", "-", "/?", "-?"], "linkreprofullpathrsp:">, + MetaVarName<"directory">, + HelpText<"Write .rsp file containing inputs used to link with full paths">; def lldignoreenv : F<"lldignoreenv">, HelpText<"Ignore environment variables like %LIB%">; def lldltocache : P<"lldltocache", diff --git a/lld/docs/ReleaseNotes.rst b/lld/docs/ReleaseNotes.rst index 31958c65c18a..b65bcca07b14 100644 --- a/lld/docs/ReleaseNotes.rst +++ b/lld/docs/ReleaseNotes.rst @@ -40,6 +40,8 @@ COFF Improvements * ``/fat-lto-objects`` addded to support FatLTO. Without ``/fat-lto-objects`` or with ``/fat-lto-objects:no``, LLD will link LLVM FatLTO objects using the relocatable object file. (`#165529 `_) +* ``/linkreprofullpathrsp`` prints the full path to each object passed to the link line to a file. + (`#174971 `_) MinGW Improvements ------------------ diff --git a/lld/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test new file mode 100644 index 000000000000..66f2e2ba2f85 --- /dev/null +++ b/lld/test/COFF/linkreprofullpathrsp.test @@ -0,0 +1,43 @@ +# REQUIRES: x86 + +# RUN: rm -rf %t.dir %t.obj +# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj +# RUN: yaml2obj %p/Inputs/empty.yaml -o %t.archive.obj +# RUN: rm -f %t.archive.lib +# RUN: llvm-ar rcs %t.archive.lib %t.archive.obj +# RUN: llvm-pdbutil yaml2pdb %S/Inputs/pdb-type-server-simple-ts.yaml -pdb %t.pdb + + +Test link.exe-style /linkreprofullpathrsp: flag. +# RUN: mkdir -p %t.dir/build1 +# RUN: cd %t.dir/build1 +# RUN: lld-link /subsystem:console %t.obj %p/Inputs/std32.lib /defaultlib:%p/Inputs/library.lib \ +# RUN: /libpath:%p/Inputs /defaultlib:std64.lib ret42.lib /entry:main@0 /linkreprofullpathrsp:%t.rsp \ +# RUN: %t.pdb /wholearchive:%t.archive.lib /out:%t.exe /timestamp:0 +# # RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp + +# RUN: lld-link /subsystem:console @%t.rsp /out:%t2.exe /entry:main@0 /timestamp:0 +# RUN: diff %t.exe %t2.exe + +# RSP: "[[T]].obj" +# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}std32.lib" +# RSP-NEXT: "[[P]]{{[/\\]}}Inputs{{[/\\]}}ret42.lib" +# RSP-NEXT: "[[T]].pdb" +# RSP-NEXT: "/wholearchive:[[T]].archive.lib" +# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}library.lib" +# RSP-NEXT: "/defaultlib:[[P]]{{[/\\]}}Inputs{{[/\\]}}std64.lib" + +#--- drectve.s + .section .drectve, "yn" + .ascii "/defaultlib:std32" + +#--- archive.s + .text + .intel_syntax noprefix + .globl exportfn3 + .p2align 4 +exportfn3: + ret + + .section .drectve,"yni" + .ascii " /EXPORT:exportfn3"