COFF: Implement /linkrepro flag.

This flag is implemented similarly to --reproduce in the ELF linker.

This patch implements /linkrepro by moving the cpio writer and associated
utility functions to lldCore, and using that implementation in both linkers.

One COFF-specific detail is that we store the object file from which the
resource files were created in our reproducer, rather than the resource
files themselves. This allows the reproducer to be used on non-Windows
systems for example.

Differential Revision: https://reviews.llvm.org/D22418

llvm-svn: 276719
diff --git a/lld/COFF/CMakeLists.txt b/lld/COFF/CMakeLists.txt
index 3319f39..659f89d 100644
--- a/lld/COFF/CMakeLists.txt
+++ b/lld/COFF/CMakeLists.txt
@@ -30,7 +30,9 @@
   Option
   Support
 
-  LINK_LIBS ${PTHREAD_LIB}
+  LINK_LIBS
+  lldCore
+  ${PTHREAD_LIB}
   )
 
 add_dependencies(lldCOFF COFFOptionsTableGen)
diff --git a/lld/COFF/Driver.cpp b/lld/COFF/Driver.cpp
index bb6a60e..9395568 100644
--- a/lld/COFF/Driver.cpp
+++ b/lld/COFF/Driver.cpp
@@ -69,6 +69,10 @@
 }
 
 static std::unique_ptr<InputFile> createFile(MemoryBufferRef MB) {
+  if (Driver->Cpio)
+    Driver->Cpio->append(relativeToRoot(MB.getBufferIdentifier()),
+                         MB.getBuffer());
+
   // File type is detected by contents, not by file extension.
   file_magic Magic = identify_magic(MB.getBuffer());
   if (Magic == file_magic::archive)
@@ -247,6 +251,37 @@
   return Config->DLL ? 0x10000000 : 0x400000;
 }
 
+static std::string createResponseFile(const llvm::opt::InputArgList &Args,
+                                      ArrayRef<MemoryBufferRef> MBs,
+                                      ArrayRef<StringRef> SearchPaths) {
+  SmallString<0> Data;
+  raw_svector_ostream OS(Data);
+
+  for (auto *Arg : Args) {
+    switch (Arg->getOption().getID()) {
+    case OPT_linkrepro:
+    case OPT_INPUT:
+    case OPT_defaultlib:
+    case OPT_libpath:
+      break;
+    default:
+      OS << stringize(Arg) << "\n";
+    }
+  }
+
+  for (StringRef Path : SearchPaths) {
+    std::string RelPath = relativeToRoot(Path);
+    OS << "/libpath:" << quote(RelPath) << "\n";
+  }
+
+  for (MemoryBufferRef MB : MBs) {
+    std::string InputPath = relativeToRoot(MB.getBufferIdentifier());
+    OS << quote(InputPath) << "\n";
+  }
+
+  return Data.str();
+}
+
 void LinkerDriver::link(llvm::ArrayRef<const char *> ArgsArr) {
   // If the first command line argument is "/lib", link.exe acts like lib.exe.
   // We call our own implementation of lib.exe that understands bitcode files.
@@ -273,6 +308,17 @@
     return;
   }
 
+  if (auto *Arg = Args.getLastArg(OPT_linkrepro)) {
+    SmallString<64> Path = StringRef(Arg->getValue());
+    llvm::sys::path::append(Path, "repro");
+    ErrorOr<CpioFile *> F = CpioFile::create(Path);
+    if (F)
+      Cpio.reset(*F);
+    else
+      llvm::errs() << "/linkrepro: failed to open " << Path
+                   << ".cpio: " << F.getError().message() << '\n';
+  }
+
   if (Args.filtered_begin(OPT_INPUT) == Args.filtered_end())
     fatal("no input files");
 
@@ -511,9 +557,16 @@
   if (!Resources.empty()) {
     std::unique_ptr<MemoryBuffer> MB = convertResToCOFF(Resources);
     Symtab.addFile(createFile(MB->getMemBufferRef()));
+
+    MBs.push_back(MB->getMemBufferRef());
     OwningMBs.push_back(std::move(MB)); // take ownership
   }
 
+  if (Cpio)
+    Cpio->append("response.txt",
+                 createResponseFile(Args, MBs,
+                                    ArrayRef<StringRef>(SearchPaths).slice(1)));
+
   // Handle /largeaddressaware
   if (Config->is64() || Args.hasArg(OPT_largeaddressaware))
     Config->LargeAddressAware = true;
diff --git a/lld/COFF/Driver.h b/lld/COFF/Driver.h
index 23969ee..0afde18 100644
--- a/lld/COFF/Driver.h
+++ b/lld/COFF/Driver.h
@@ -13,6 +13,7 @@
 #include "Config.h"
 #include "SymbolTable.h"
 #include "lld/Core/LLVM.h"
+#include "lld/Core/Reproduce.h"
 #include "llvm/ADT/Optional.h"
 #include "llvm/ADT/StringRef.h"
 #include "llvm/Object/COFF.h"
@@ -69,6 +70,8 @@
   // Used by the resolver to parse .drectve section contents.
   void parseDirectives(StringRef S);
 
+  std::unique_ptr<CpioFile> Cpio; // for /linkrepro
+
 private:
   llvm::BumpPtrAllocator AllocAux;
   llvm::StringSaver Alloc;
diff --git a/lld/COFF/Error.h b/lld/COFF/Error.h
index c9f64c6..519f02f 100644
--- a/lld/COFF/Error.h
+++ b/lld/COFF/Error.h
@@ -32,6 +32,12 @@
   return std::move(*E);
 }
 
+template <class T> T check(ErrorOr<T> EO) {
+  if (!EO)
+    fatal(EO.getError().message());
+  return std::move(*EO);
+}
+
 } // namespace coff
 } // namespace lld
 
diff --git a/lld/COFF/InputFiles.cpp b/lld/COFF/InputFiles.cpp
index ff26826..32057b4 100644
--- a/lld/COFF/InputFiles.cpp
+++ b/lld/COFF/InputFiles.cpp
@@ -9,6 +9,7 @@
 
 #include "Chunks.h"
 #include "Config.h"
+#include "Driver.h"
 #include "Error.h"
 #include "InputFiles.h"
 #include "Symbols.h"
@@ -93,9 +94,16 @@
   // Return an empty buffer if we have already returned the same buffer.
   if (Seen[C.getChildOffset()].test_and_set())
     return MemoryBufferRef();
-  return check(C.getMemoryBufferRef(),
-               "could not get the buffer for the member defining symbol " +
-                   Sym->getName());
+
+  MemoryBufferRef MB =
+      check(C.getMemoryBufferRef(),
+            "could not get the buffer for the member defining symbol " +
+                Sym->getName());
+  if (C.getParent()->isThin() && Driver->Cpio)
+    Driver->Cpio->append(relativeToRoot(check(C.getFullName())),
+                         MB.getBuffer());
+
+  return MB;
 }
 
 void ObjectFile::parse() {
diff --git a/lld/COFF/Options.td b/lld/COFF/Options.td
index e5c9c5b..28c498b 100644
--- a/lld/COFF/Options.td
+++ b/lld/COFF/Options.td
@@ -27,6 +27,7 @@
 def heap    : P<"heap", "Size of the heap">;
 def implib  : P<"implib", "Import library name">;
 def libpath : P<"libpath", "Additional library search path">;
+def linkrepro : P<"linkrepro", "Dump linker invocation and input files for debugging">;
 def machine : P<"machine", "Specify target platform">;
 def merge   : P<"merge", "Combine sections">;
 def mllvm   : P<"mllvm", "Options to pass to LLVM">;