Skip to content

Commit 83e60f5

Browse files
committed
[lld/mac] Add --reproduce option
This adds support for ld.lld's --reproduce / lld-link's /reproduce: flag to the MachO port. This flag can be added to a link command to make the link write a tar file containing all inputs to the link and a response file containing the link command. This can be used to reproduce the link on another machine, which is useful for sharing bug report inputs or performance test loads. Since the linker is usually called through the clang driver and adding linker flags can be a bit cumbersome, setting the env var `LLD_REPRODUCE=foo.tar` triggers the feature as well. The file response.txt in the archive can be used with `ld64.lld.darwinnew $(cat response.txt)` as long as the contents are smaller than the command-line limit, or with `ld64.lld.darwinnew @response.txt` once D92149 is in. The support in this patch is sufficient to create a tar file for Chromium's base_unittests that can link after unpacking on a different machine. Differential Revision: https://reviews.llvm.org/D92274
1 parent 25d54ab commit 83e60f5

File tree

9 files changed

+136
-3
lines changed

9 files changed

+136
-3
lines changed

lld/Common/Reproduce.cpp

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,12 @@ std::string lld::toString(const opt::Arg &arg) {
5454
std::string k = std::string(arg.getSpelling());
5555
if (arg.getNumValues() == 0)
5656
return k;
57-
std::string v = quote(arg.getValue());
57+
std::string v;
58+
for (size_t i = 0; i < arg.getNumValues(); ++i) {
59+
if (i > 0)
60+
v.push_back(' ');
61+
v += quote(arg.getValue(i));
62+
}
5863
if (arg.getOption().getRenderStyle() == opt::Option::RenderJoinedStyle)
5964
return k + v;
6065
return k + " " + v;

lld/ELF/DriverUtils.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ std::string elf::createResponseFile(const opt::InputArgList &args) {
184184
// fail because the archive we are creating doesn't contain empty
185185
// directories for the output path (-o doesn't create directories).
186186
// Strip directories to prevent the issue.
187-
os << "-o " << quote(sys::path::filename(arg->getValue())) << "\n";
187+
os << "-o " << quote(path::filename(arg->getValue())) << "\n";
188188
break;
189189
case OPT_dynamic_list:
190190
case OPT_library_path:

lld/MachO/Driver.cpp

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
#include "llvm/Support/Host.h"
3838
#include "llvm/Support/MemoryBuffer.h"
3939
#include "llvm/Support/Path.h"
40+
#include "llvm/Support/TarWriter.h"
4041
#include "llvm/Support/TargetSelect.h"
4142

4243
#include <algorithm>
@@ -548,6 +549,12 @@ static void warnIfUnimplementedOption(const opt::Option &opt) {
548549
}
549550
}
550551

552+
static const char *getReproduceOption(opt::InputArgList &args) {
553+
if (auto *arg = args.getLastArg(OPT_reproduce))
554+
return arg->getValue();
555+
return getenv("LLD_REPRODUCE");
556+
}
557+
551558
bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
552559
raw_ostream &stdoutOS, raw_ostream &stderrOS) {
553560
lld::stdoutOS = &stdoutOS;
@@ -569,6 +576,20 @@ bool macho::link(llvm::ArrayRef<const char *> argsArr, bool canExitEarly,
569576
return true;
570577
}
571578

579+
if (const char *path = getReproduceOption(args)) {
580+
// Note that --reproduce is a debug option so you can ignore it
581+
// if you are trying to understand the whole picture of the code.
582+
Expected<std::unique_ptr<TarWriter>> errOrWriter =
583+
TarWriter::create(path, path::stem(path));
584+
if (errOrWriter) {
585+
tar = std::move(*errOrWriter);
586+
tar->append("response.txt", createResponseFile(args));
587+
tar->append("version.txt", getLLDVersion() + "\n");
588+
} else {
589+
error("--reproduce: " + toString(errOrWriter.takeError()));
590+
}
591+
}
592+
572593
config = make<Configuration>();
573594
symtab = make<SymbolTable>();
574595
target = createTargetInfo(args);

lld/MachO/Driver.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,8 @@ enum {
3535
#undef OPTION
3636
};
3737

38+
std::string createResponseFile(const llvm::opt::InputArgList &args);
39+
3840
// Check for both libfoo.dylib and libfoo.tbd (in that order).
3941
llvm::Optional<std::string> resolveDylibPath(llvm::StringRef path);
4042

lld/MachO/DriverUtils.cpp

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
#include "Driver.h"
1010
#include "InputFiles.h"
1111

12+
#include "lld/Common/Args.h"
1213
#include "lld/Common/ErrorHandler.h"
1314
#include "lld/Common/Memory.h"
15+
#include "lld/Common/Reproduce.h"
1416
#include "llvm/Option/Arg.h"
1517
#include "llvm/Option/ArgList.h"
1618
#include "llvm/Option/Option.h"
@@ -96,6 +98,55 @@ void MachOOptTable::printHelp(const char *argv0, bool showHidden) const {
9698
lld::outs() << "\n";
9799
}
98100

101+
static std::string rewritePath(StringRef s) {
102+
if (fs::exists(s))
103+
return relativeToRoot(s);
104+
return std::string(s);
105+
}
106+
107+
// Reconstructs command line arguments so that so that you can re-run
108+
// the same command with the same inputs. This is for --reproduce.
109+
std::string macho::createResponseFile(const opt::InputArgList &args) {
110+
SmallString<0> data;
111+
raw_svector_ostream os(data);
112+
113+
// Copy the command line to the output while rewriting paths.
114+
for (auto *arg : args) {
115+
switch (arg->getOption().getID()) {
116+
case OPT_reproduce:
117+
break;
118+
case OPT_INPUT:
119+
os << quote(rewritePath(arg->getValue())) << "\n";
120+
break;
121+
case OPT_o:
122+
os << "-o " << quote(path::filename(arg->getValue())) << "\n";
123+
break;
124+
case OPT_filelist:
125+
if (Optional<MemoryBufferRef> buffer = readFile(arg->getValue()))
126+
for (StringRef path : args::getLines(*buffer))
127+
os << quote(rewritePath(path)) << "\n";
128+
break;
129+
case OPT_force_load:
130+
case OPT_rpath:
131+
case OPT_syslibroot:
132+
case OPT_F:
133+
case OPT_L:
134+
case OPT_order_file:
135+
os << arg->getSpelling() << " " << quote(rewritePath(arg->getValue()))
136+
<< "\n";
137+
break;
138+
case OPT_sectcreate:
139+
os << arg->getSpelling() << " " << quote(arg->getValue(0)) << " "
140+
<< quote(arg->getValue(1)) << " "
141+
<< quote(rewritePath(arg->getValue(2))) << "\n";
142+
break;
143+
default:
144+
os << toString(*arg) << "\n";
145+
}
146+
}
147+
return std::string(data.str());
148+
}
149+
99150
Optional<std::string> macho::resolveDylibPath(StringRef path) {
100151
// TODO: if a tbd and dylib are both present, we should check to make sure
101152
// they are consistent.

lld/MachO/InputFiles.cpp

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,14 @@
5656

5757
#include "lld/Common/ErrorHandler.h"
5858
#include "lld/Common/Memory.h"
59+
#include "lld/Common/Reproduce.h"
5960
#include "llvm/ADT/iterator.h"
6061
#include "llvm/BinaryFormat/MachO.h"
6162
#include "llvm/LTO/LTO.h"
6263
#include "llvm/Support/Endian.h"
6364
#include "llvm/Support/MemoryBuffer.h"
6465
#include "llvm/Support/Path.h"
66+
#include "llvm/Support/TarWriter.h"
6567

6668
using namespace llvm;
6769
using namespace llvm::MachO;
@@ -71,6 +73,7 @@ using namespace lld;
7173
using namespace lld::macho;
7274

7375
std::vector<InputFile *> macho::inputFiles;
76+
std::unique_ptr<TarWriter> macho::tar;
7477

7578
// Open a given file path and return it as a memory-mapped file.
7679
Optional<MemoryBufferRef> macho::readFile(StringRef path) {
@@ -88,8 +91,11 @@ Optional<MemoryBufferRef> macho::readFile(StringRef path) {
8891
// If this is a regular non-fat file, return it.
8992
const char *buf = mbref.getBufferStart();
9093
auto *hdr = reinterpret_cast<const MachO::fat_header *>(buf);
91-
if (read32be(&hdr->magic) != MachO::FAT_MAGIC)
94+
if (read32be(&hdr->magic) != MachO::FAT_MAGIC) {
95+
if (tar)
96+
tar->append(relativeToRoot(path), mbref.getBuffer());
9297
return mbref;
98+
}
9399

94100
// Object files and archive files may be fat files, which contains
95101
// multiple real files for different CPU ISAs. Here, we search for a
@@ -112,6 +118,8 @@ Optional<MemoryBufferRef> macho::readFile(StringRef path) {
112118
uint32_t size = read32be(&arch[i].size);
113119
if (offset + size > mbref.getBufferSize())
114120
error(path + ": slice extends beyond end of file");
121+
if (tar)
122+
tar->append(relativeToRoot(path), mbref.getBuffer());
115123
return MemoryBufferRef(StringRef(buf + offset, size), path.copy(bAlloc));
116124
}
117125

lld/MachO/InputFiles.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ namespace llvm {
2727
namespace lto {
2828
class InputFile;
2929
} // namespace lto
30+
class TarWriter;
3031
} // namespace llvm
3132

3233
namespace lld {
@@ -36,6 +37,10 @@ class InputSection;
3637
class Symbol;
3738
struct Reloc;
3839

40+
// If --reproduce option is given, all input files are written
41+
// to this tar archive.
42+
extern std::unique_ptr<llvm::TarWriter> tar;
43+
3944
// If .subsections_via_symbols is set, each InputSection will be split along
4045
// symbol boundaries. The keys of a SubsectionMap represent the offsets of
4146
// each subsection from the start of the original pre-split InputSection.

lld/MachO/Options.td

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@ def no_color_diagnostics: Flag<["--"], "no-color-diagnostics">,
1414
def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
1515
HelpText<"Use colors in diagnostics (default: auto)">,
1616
MetaVarName<"[auto,always,never]">;
17+
def reproduce: Separate<["--"], "reproduce">;
18+
def reproduce_eq: Joined<["--"], "reproduce=">,
19+
Alias<!cast<Separate>(reproduce)>,
20+
HelpText<"Write tar file containing inputs and command to reproduce link">;
1721

1822

1923
// This is a complete Options.td compiled from Apple's ld(1) manpage

lld/test/MachO/reproduce.s

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# REQUIRES: x86
2+
3+
# RUN: rm -rf %t.dir
4+
# RUN: mkdir -p %t.dir/build1
5+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build1/foo.o
6+
# RUN: cd %t.dir
7+
# RUN: %lld -platform_version macos 10.10.0 11.0 build1/foo.o -o bar --reproduce repro1.tar
8+
# RUN: tar xOf repro1.tar repro1/%:t.dir/build1/foo.o > build1-foo.o
9+
# RUN: cmp build1/foo.o build1-foo.o
10+
11+
# RUN: tar xf repro1.tar repro1/response.txt repro1/version.txt
12+
# RUN: FileCheck %s --check-prefix=RSP1 < repro1/response.txt
13+
# RSP1: {{^}}-platform_version macos 10.10.0 11.0{{$}}
14+
# RSP1-NOT: {{^}}repro1{{[/\\]}}
15+
# RSP1-NEXT: {{[/\\]}}foo.o
16+
# RSP1-NEXT: -o bar
17+
# RSP1-NOT: --reproduce
18+
19+
# RUN: FileCheck %s --check-prefix=VERSION < repro1/version.txt
20+
# VERSION: LLD
21+
22+
# RUN: mkdir -p %t.dir/build2/a/b/c
23+
# RUN: llvm-mc -filetype=obj -triple=x86_64-apple-macos %s -o %t.dir/build2/foo.o
24+
# RUN: cd %t.dir/build2/a/b/c
25+
# RUN: echo ./../../../foo.o > %t.dir/build2/filelist
26+
# RUN: env LLD_REPRODUCE=repro2.tar %lld -filelist %t.dir/build2/filelist -o /dev/null
27+
# RUN: tar xOf repro2.tar repro2/%:t.dir/build2/foo.o > build2-foo.o
28+
# RUN: cmp %t.dir/build2/foo.o build2-foo.o
29+
30+
# RUN: tar xf repro2.tar repro2/response.txt repro2/version.txt
31+
# RUN: FileCheck %s --check-prefix=RSP2 < repro2/response.txt
32+
# RSP2-NOT: {{^}}repro2{{[/\\]}}
33+
# RSP2: {{[/\\]}}foo.o
34+
35+
.globl _main
36+
_main:
37+
ret

0 commit comments

Comments
 (0)