From e49faac40e1d3faf382b5c8d343ee3cebcac97c5 Mon Sep 17 00:00:00 2001 From: David Truby Date: Tue, 28 Oct 2025 14:25:01 +0000 Subject: [PATCH 1/3] [lld][COFF] Add /linkreprofullpathrsp flag 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. --- lld/COFF/Driver.cpp | 33 +++++++++++++++++---- lld/COFF/Driver.h | 5 ++-- lld/COFF/Options.td | 3 ++ lld/test/COFF/linkreprofullpathrsp.test | 38 +++++++++++++++++++++++++ 4 files changed, 71 insertions(+), 8 deletions(-) create mode 100644 lld/test/COFF/linkreprofullpathrsp.test diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index 0e528de9c3652..e2ad2f1c1e993 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -318,7 +318,8 @@ void LinkerDriver::addBuffer(std::unique_ptr mb, } } -void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { +void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy, + llvm::raw_ostream *reproFile, bool defaultlib) { auto future = std::make_shared>( createFutureForFile(std::string(path))); std::string pathStr = std::string(path); @@ -356,8 +357,17 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy) { Err(ctx) << msg; else Err(ctx) << msg << "; did you mean '" << nearest << "'"; - } else + } else { + // Write full path to library to repro file if /linkreprofullpathrsp + // is specified. + if (reproFile) { + *reproFile << '"'; + if (defaultlib) + *reproFile << "/defaultlib:"; + *reproFile << pathStr << "\"\n"; + } ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy); + } }); } @@ -514,7 +524,7 @@ void LinkerDriver::parseDirectives(InputFile *file) { break; case OPT_defaultlib: if (std::optional path = findLibIfNew(arg->getValue())) - enqueuePath(*path, false, false); + enqueuePath(*path, false, false, nullptr); break; case OPT_entry: if (!arg->getValue()[0]) @@ -2204,6 +2214,17 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { config->incremental = false; } + // Handle /linkreprofullpathrsp. + std::unique_ptr reproFile; + 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 (errCount(ctx)) return; @@ -2245,11 +2266,11 @@ void LinkerDriver::linkerMain(ArrayRef argsArr) { break; case OPT_wholearchive_file: if (std::optional path = findFileIfNew(arg->getValue())) - enqueuePath(*path, true, inLib); + enqueuePath(*path, true, inLib, reproFile.get()); break; case OPT_INPUT: if (std::optional path = findFileIfNew(arg->getValue())) - enqueuePath(*path, isWholeArchive(*path), inLib); + enqueuePath(*path, isWholeArchive(*path), inLib, reproFile.get()); break; default: // Ignore other options. @@ -2289,7 +2310,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, false, reproFile.get(), true); run(); if (errorCount()) return; diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h index 14710d5853bcf..f4b72a317d077 100644 --- a/lld/COFF/Driver.h +++ b/lld/COFF/Driver.h @@ -88,11 +88,12 @@ class LinkerDriver { void enqueueArchiveMember(const Archive::Child &c, const Archive::Symbol &sym, StringRef parentName); - void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false); } + void enqueuePDB(StringRef Path) { enqueuePath(Path, false, false, nullptr); } MemoryBufferRef takeBuffer(std::unique_ptr mb); - void enqueuePath(StringRef path, bool wholeArchive, bool lazy); + void enqueuePath(StringRef path, bool wholeArchive, bool lazy, + raw_ostream *reproFile, bool defaultlib = false); // Returns a list of chunks of selected symbols. std::vector getChunks() const; diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td index d77478fc9c987..dca8a721dc4fd 100644 --- a/lld/COFF/Options.td +++ b/lld/COFF/Options.td @@ -74,6 +74,9 @@ def libpath : P<"libpath", "Additional library search path">; 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/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test new file mode 100644 index 0000000000000..c77f09eae9dcd --- /dev/null +++ b/lld/test/COFF/linkreprofullpathrsp.test @@ -0,0 +1,38 @@ +# REQUIRES: x86 +# Unsupported on Windows due to maximum path length limitations. + +# RUN: rm -rf %t.dir +# RUN: split-file %s %t.dir +# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj +# RUN: llvm-mc -filetype=obj -triple=i386-windows %t.dir/drectve.s -o %t.dir/drectve.obj +# RUN: echo '_main@0' > %t.order +# RUN: touch %t.def +# RUN: touch %t.cg + + + +Test link.exe-style /linkreprofullpathrsp: flag. +# RUN: mkdir -p %t.dir/build1 +# RUN: cd %t.dir/build1 +# RUN: lld-link %t.obj %p/Inputs/std32.lib /subsystem:console /defaultlib:%p/Inputs/library.lib \ +# RUN: /entry:main@0 /linkreprofullpathrsp:%t.rsp /out:%t.exe +# RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp + +# RSP: [[T]].obj +# RSP-NEXT: "[[P]]/Inputs/std32.lib" +# RSP-NEXT: "/defaultlib:[[P]]/Inputs/library.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" From f34b707062d1129f66da4387bb250a3d0407acf1 Mon Sep 17 00:00:00 2001 From: David Truby Date: Mon, 3 Nov 2025 15:08:11 +0000 Subject: [PATCH 2/3] Actually find the full path --- lld/COFF/Driver.cpp | 23 +++++++++++++++++------ lld/test/COFF/linkreprofullpathrsp.test | 14 +++++--------- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp index e2ad2f1c1e993..c26955fbbfe5b 100644 --- a/lld/COFF/Driver.cpp +++ b/lld/COFF/Driver.cpp @@ -318,6 +318,20 @@ void LinkerDriver::addBuffer(std::unique_ptr mb, } } +static void handleReproFile(COFFLinkerContext &ctx, raw_ostream &reproFile, + StringRef path, bool defaultlib) { + reproFile << '"'; + if (defaultlib) + reproFile << "/defaultlib:"; + 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 wholeArchive, bool lazy, llvm::raw_ostream *reproFile, bool defaultlib) { auto future = std::make_shared>( @@ -360,12 +374,9 @@ void LinkerDriver::enqueuePath(StringRef path, bool wholeArchive, bool lazy, } else { // Write full path to library to repro file if /linkreprofullpathrsp // is specified. - if (reproFile) { - *reproFile << '"'; - if (defaultlib) - *reproFile << "/defaultlib:"; - *reproFile << pathStr << "\"\n"; - } + if (reproFile) + handleReproFile(ctx, *reproFile, pathStr, defaultlib); + ctx.driver.addBuffer(std::move(mb), wholeArchive, lazy); } }); diff --git a/lld/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test index c77f09eae9dcd..5e09eff273a2b 100644 --- a/lld/test/COFF/linkreprofullpathrsp.test +++ b/lld/test/COFF/linkreprofullpathrsp.test @@ -1,26 +1,22 @@ # REQUIRES: x86 # Unsupported on Windows due to maximum path length limitations. +# UNSUPPORTED: system-windows # RUN: rm -rf %t.dir -# RUN: split-file %s %t.dir -# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj -# RUN: llvm-mc -filetype=obj -triple=i386-windows %t.dir/drectve.s -o %t.dir/drectve.obj -# RUN: echo '_main@0' > %t.order -# RUN: touch %t.def -# RUN: touch %t.cg - - Test link.exe-style /linkreprofullpathrsp: flag. # RUN: mkdir -p %t.dir/build1 # RUN: cd %t.dir/build1 # RUN: lld-link %t.obj %p/Inputs/std32.lib /subsystem:console /defaultlib:%p/Inputs/library.lib \ -# RUN: /entry:main@0 /linkreprofullpathrsp:%t.rsp /out:%t.exe +# RUN: /libpath:%p/Inputs /defaultlib:std64.lib ret42.lib /entry:main@0 /linkreprofullpathrsp:%t.rsp \ +# RUN: /out:%t.exe # RUN: FileCheck %s --check-prefix=RSP -DT=%t -DP=%p < %t.rsp # RSP: [[T]].obj # RSP-NEXT: "[[P]]/Inputs/std32.lib" +# RSP-NEXT: "[[P]]/Inputs/ret42.lib" # RSP-NEXT: "/defaultlib:[[P]]/Inputs/library.lib" +# RSP-NEXT: "/defaultlib:[[P]]/Inputs/std64.lib" #--- drectve.s .section .drectve, "yn" From 5070301464edb514c482e9cd5c28fa743b8627c5 Mon Sep 17 00:00:00 2001 From: David Truby Date: Mon, 3 Nov 2025 16:01:41 +0000 Subject: [PATCH 3/3] Fix failing test --- lld/test/COFF/linkreprofullpathrsp.test | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lld/test/COFF/linkreprofullpathrsp.test b/lld/test/COFF/linkreprofullpathrsp.test index 5e09eff273a2b..b099d4f217913 100644 --- a/lld/test/COFF/linkreprofullpathrsp.test +++ b/lld/test/COFF/linkreprofullpathrsp.test @@ -2,7 +2,8 @@ # Unsupported on Windows due to maximum path length limitations. # UNSUPPORTED: system-windows -# RUN: rm -rf %t.dir +# RUN: rm -rf %t.dir %t.obj +# RUN: yaml2obj %p/Inputs/hello32.yaml -o %t.obj Test link.exe-style /linkreprofullpathrsp: flag. # RUN: mkdir -p %t.dir/build1