[lld/mac] Add support for distributed ThinLTO
authorNico Weber <thakis@chromium.org>
Sun, 20 Nov 2022 16:59:16 +0000 (11:59 -0500)
committerNico Weber <thakis@chromium.org>
Fri, 13 Jan 2023 01:54:18 +0000 (20:54 -0500)
Adds support for the following flags:

* --thinlto-index-only, --thinlto-index-only=
* --thinlto-emit-imports-files
* --thinlto-emit-index-files
* --thinlto-object-suffix-replace=
* --thinlto-prefix-replace=

See https://blog.llvm.org/2016/06/thinlto-scalable-and-incremental-lto.html
for some words on --thinlto-index-only.

I don't really need the other flags, but they were in the vicinity
and _someone_ might need them, so I figured I'd add them too.

`-object_path_lto` now sets `c.AlwaysEmitRegularLTOObj` as in the other ports,
which means it can now only point to a filename for non-thin LTO.
I think that was the intent of D129705 anyways, so update
test/MachO/lto-object-path.ll to use a non-thin bitcode file for that test.

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

19 files changed:
lld/ELF/InputFiles.cpp
lld/MachO/Config.h
lld/MachO/Driver.cpp
lld/MachO/InputFiles.cpp
lld/MachO/InputFiles.h
lld/MachO/LTO.cpp
lld/MachO/LTO.h
lld/MachO/Options.td
lld/test/ELF/lto/thinlto-index-only.ll
lld/test/ELF/lto/thinlto-obj-path.ll
lld/test/ELF/lto/thinlto-prefix-replace.ll
lld/test/MachO/lto-object-path.ll
lld/test/MachO/thinlto-emit-imports.ll [new file with mode: 0644]
lld/test/MachO/thinlto-emit-index.ll [new file with mode: 0644]
lld/test/MachO/thinlto-index-file.ll [new file with mode: 0644]
lld/test/MachO/thinlto-index-only.ll [new file with mode: 0644]
lld/test/MachO/thinlto-obj-path.ll [new file with mode: 0644]
lld/test/MachO/thinlto-object-suffix-replace.ll [new file with mode: 0644]
lld/test/MachO/thinlto-prefix-replace.ll [new file with mode: 0644]

index 9ecc898..7dacdeb 100644 (file)
@@ -1773,9 +1773,7 @@ bool InputFile::shouldExtractForCommon(StringRef name) {
 }
 
 std::string elf::replaceThinLTOSuffix(StringRef path) {
-  StringRef suffix = config->thinLTOObjectSuffixReplace.first;
-  StringRef repl = config->thinLTOObjectSuffixReplace.second;
-
+  auto [suffix, repl] = config->thinLTOObjectSuffixReplace;
   if (path.consume_back(suffix))
     return (path + repl).str();
   return std::string(path);
index 6586824..220fb99 100644 (file)
@@ -133,6 +133,9 @@ struct Configuration {
   bool emitEncryptionInfo = false;
   bool emitInitOffsets = false;
   bool emitChainedFixups = false;
+  bool thinLTOEmitImportsFiles;
+  bool thinLTOEmitIndexFiles;
+  bool thinLTOIndexOnly;
   bool timeTraceEnabled = false;
   bool dataConst = false;
   bool dedupStrings = true;
@@ -164,6 +167,9 @@ struct Configuration {
   uint32_t ltoo = 2;
   llvm::CachePruningPolicy thinLTOCachePolicy;
   llvm::StringRef thinLTOCacheDir;
+  llvm::StringRef thinLTOIndexOnlyArg;
+  std::pair<llvm::StringRef, llvm::StringRef> thinLTOObjectSuffixReplace;
+  std::pair<llvm::StringRef, llvm::StringRef> thinLTOPrefixReplace;
   bool deadStripDylibs = false;
   bool demangle = false;
   bool deadStrip = false;
index 356fcde..fe8540e 100644 (file)
@@ -858,6 +858,20 @@ static const char *getReproduceOption(InputArgList &args) {
   return getenv("LLD_REPRODUCE");
 }
 
+// Parse options of the form "old;new".
+static std::pair<StringRef, StringRef> getOldNewOptions(opt::InputArgList &args,
+                                                        unsigned id) {
+  auto *arg = args.getLastArg(id);
+  if (!arg)
+    return {"", ""};
+
+  StringRef s = arg->getValue();
+  std::pair<StringRef, StringRef> ret = s.split(';');
+  if (ret.second.empty())
+    error(arg->getSpelling() + " expects 'old;new' format, but got " + s);
+  return ret;
+}
+
 static void parseClangOption(StringRef opt, const Twine &msg) {
   std::string err;
   raw_string_ostream os(err);
@@ -1549,6 +1563,25 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     error("--lto-O: invalid optimization level: " + Twine(config->ltoo));
   config->thinLTOCacheDir = args.getLastArgValue(OPT_cache_path_lto);
   config->thinLTOCachePolicy = getLTOCachePolicy(args);
+  config->thinLTOEmitImportsFiles = args.hasArg(OPT_thinlto_emit_imports_files);
+  config->thinLTOEmitIndexFiles = args.hasArg(OPT_thinlto_emit_index_files) ||
+                                  args.hasArg(OPT_thinlto_index_only) ||
+                                  args.hasArg(OPT_thinlto_index_only_eq);
+  config->thinLTOIndexOnly = args.hasArg(OPT_thinlto_index_only) ||
+                             args.hasArg(OPT_thinlto_index_only_eq);
+  config->thinLTOIndexOnlyArg = args.getLastArgValue(OPT_thinlto_index_only_eq);
+  config->thinLTOObjectSuffixReplace =
+      getOldNewOptions(args, OPT_thinlto_object_suffix_replace_eq);
+  config->thinLTOPrefixReplace =
+      getOldNewOptions(args, OPT_thinlto_prefix_replace_eq);
+  if (config->thinLTOEmitIndexFiles && !config->thinLTOIndexOnly) {
+    if (args.hasArg(OPT_thinlto_object_suffix_replace_eq))
+      error("--thinlto-object-suffix-replace is not supported with "
+            "--thinlto-emit-index-files");
+    else if (args.hasArg(OPT_thinlto_prefix_replace_eq))
+      error("--thinlto-prefix-replace is not supported with "
+            "--thinlto-emit-index-files");
+  }
   config->runtimePaths = args::getStrings(args, OPT_rpath);
   config->allLoad = args.hasFlag(OPT_all_load, OPT_noall_load, false);
   config->archMultiple = args.hasArg(OPT_arch_multiple);
@@ -1834,11 +1867,20 @@ bool macho::link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
     // explicitly exported. Do this before running LTO so that LTO can better
     // optimize.
     handleExplicitExports();
+
+    bool didCompileBitcodeFiles = compileBitcodeFiles();
+
+    // If --thinlto-index-only is given, we should create only "index
+    // files" and not object files. Index file creation is already done
+    // in compileBitcodeFiles, so we are done if that's the case.
+    if (config->thinLTOIndexOnly)
+      return errorCount() == 0;
+
     // LTO may emit a non-hidden (extern) object file symbol even if the
     // corresponding bitcode symbol is hidden. In particular, this happens for
     // cross-module references to hidden symbols under ThinLTO. Thus, if we
     // compiled any bitcode files, we must redo the symbol hiding.
-    if (compileBitcodeFiles())
+    if (didCompileBitcodeFiles)
       handleExplicitExports();
     replaceCommonSymbols();
 
index bb32ba3..50c6987 100644 (file)
@@ -2206,6 +2206,9 @@ BitcodeFile::BitcodeFile(MemoryBufferRef mb, StringRef archiveName,
     : InputFile(BitcodeKind, mb, lazy), forceHidden(forceHidden) {
   this->archiveName = std::string(archiveName);
   std::string path = mb.getBufferIdentifier().str();
+  if (config->thinLTOIndexOnly)
+    path = replaceThinLTOSuffix(mb.getBufferIdentifier());
+
   // ThinLTO assumes that all MemoryBufferRefs given to it have a unique
   // name. If two members with the same name are provided, this causes a
   // collision and ThinLTO can't proceed.
@@ -2246,6 +2249,13 @@ void BitcodeFile::parseLazy() {
   }
 }
 
+std::string macho::replaceThinLTOSuffix(StringRef path) {
+  auto [suffix, repl] = config->thinLTOObjectSuffixReplace;
+  if (path.consume_back(suffix))
+    return (path + repl).str();
+  return std::string(path);
+}
+
 void macho::extract(InputFile &file, StringRef reason) {
   if (!file.lazy)
     return;
index 66cf92b..66d46e4 100644 (file)
@@ -357,6 +357,7 @@ std::vector<const CommandType *> findCommands(const void *anyHdr,
   return detail::findCommands<CommandType>(anyHdr, 0, types...);
 }
 
+std::string replaceThinLTOSuffix(StringRef path);
 } // namespace macho
 
 std::string toString(const macho::InputFile *file);
index ba87688..565a66d 100644 (file)
@@ -17,6 +17,7 @@
 #include "lld/Common/CommonLinkerContext.h"
 #include "lld/Common/Strings.h"
 #include "lld/Common/TargetOptionsCommandFlags.h"
+#include "llvm/Bitcode/BitcodeWriter.h"
 #include "llvm/LTO/Config.h"
 #include "llvm/LTO/LTO.h"
 #include "llvm/Support/Caching.h"
@@ -31,6 +32,25 @@ using namespace llvm;
 using namespace llvm::MachO;
 using namespace llvm::sys;
 
+// Creates an empty file to store a list of object files for final
+// linking of distributed ThinLTO.
+static std::unique_ptr<raw_fd_ostream> openFile(StringRef file) {
+  std::error_code ec;
+  auto ret =
+      std::make_unique<raw_fd_ostream>(file, ec, sys::fs::OpenFlags::OF_None);
+  if (ec) {
+    error("cannot open " + file + ": " + ec.message());
+    return nullptr;
+  }
+  return ret;
+}
+
+static std::string getThinLTOOutputFile(StringRef modulePath) {
+  return lto::getThinLTOOutputFile(
+      std::string(modulePath), std::string(config->thinLTOPrefixReplace.first),
+      std::string(config->thinLTOPrefixReplace.second));
+}
+
 static lto::Config createConfig() {
   lto::Config c;
   c.Options = initTargetOptionsFromCodeGenFlags();
@@ -44,6 +64,9 @@ static lto::Config createConfig() {
   c.PreCodeGenPassesHook = [](legacy::PassManager &pm) {
     pm.add(createObjCARCContractPass());
   };
+
+  c.AlwaysEmitRegularLTOObj = !config->ltoObjPath.empty();
+
   c.TimeTraceEnabled = config->timeTraceEnabled;
   c.TimeTraceGranularity = config->timeTraceGranularity;
   c.OptLevel = config->ltoo;
@@ -67,13 +90,35 @@ static void saveOrHardlinkBuffer(StringRef buffer, const Twine &path,
 }
 
 BitcodeCompiler::BitcodeCompiler() {
-  lto::ThinBackend backend = lto::createInProcessThinBackend(
-      heavyweight_hardware_concurrency(config->thinLTOJobs));
+  // Initialize indexFile.
+  if (!config->thinLTOIndexOnlyArg.empty())
+    indexFile = openFile(config->thinLTOIndexOnlyArg);
+
+  // Initialize ltoObj.
+  lto::ThinBackend backend;
+  auto onIndexWrite = [&](StringRef S) { thinIndices.erase(S); };
+  if (config->thinLTOIndexOnly) {
+    backend = lto::createWriteIndexesThinBackend(
+        std::string(config->thinLTOPrefixReplace.first),
+        std::string(config->thinLTOPrefixReplace.second),
+        config->thinLTOEmitImportsFiles, indexFile.get(), onIndexWrite);
+  } else {
+    backend = lto::createInProcessThinBackend(
+        llvm::heavyweight_hardware_concurrency(config->thinLTOJobs),
+        onIndexWrite, config->thinLTOEmitIndexFiles,
+        config->thinLTOEmitImportsFiles);
+  }
+
   ltoObj = std::make_unique<lto::LTO>(createConfig(), backend);
 }
 
 void BitcodeCompiler::add(BitcodeFile &f) {
-  ArrayRef<lto::InputFile::Symbol> objSyms = f.obj->symbols();
+  lto::InputFile &obj = *f.obj;
+
+  if (config->thinLTOEmitIndexFiles)
+    thinIndices.insert(obj.getName());
+
+  ArrayRef<lto::InputFile::Symbol> objSyms = obj.symbols();
   std::vector<lto::SymbolResolution> resols;
   resols.reserve(objSyms.size());
 
@@ -117,6 +162,37 @@ void BitcodeCompiler::add(BitcodeFile &f) {
   checkError(ltoObj->add(std::move(f.obj), resols));
 }
 
+// If LazyObjFile has not been added to link, emit empty index files.
+// This is needed because this is what GNU gold plugin does and we have a
+// distributed build system that depends on that behavior.
+static void thinLTOCreateEmptyIndexFiles() {
+  DenseSet<StringRef> linkedBitCodeFiles;
+  for (InputFile *file : inputFiles)
+    if (auto *f = dyn_cast<BitcodeFile>(file))
+      if (!f->lazy)
+        linkedBitCodeFiles.insert(f->getName());
+
+  for (InputFile *file : inputFiles) {
+    if (auto *f = dyn_cast<BitcodeFile>(file)) {
+      if (!f->lazy)
+        continue;
+      if (linkedBitCodeFiles.contains(f->getName()))
+        continue;
+      std::string path =
+          replaceThinLTOSuffix(getThinLTOOutputFile(f->obj->getName()));
+      std::unique_ptr<raw_fd_ostream> os = openFile(path + ".thinlto.bc");
+      if (!os)
+        continue;
+
+      ModuleSummaryIndex m(/*HaveGVs=*/false);
+      m.setSkipModuleByDistributedBackend();
+      writeIndexToFile(m, *os);
+      if (config->thinLTOEmitImportsFiles)
+        openFile(path + ".imports");
+    }
+  }
+}
+
 // Merge all the bitcode files we have seen, codegen the result
 // and return the resulting ObjectFile(s).
 std::vector<ObjFile *> BitcodeCompiler::compile() {
@@ -142,8 +218,16 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
       },
       cache));
 
-  if (!config->thinLTOCacheDir.empty())
-    pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
+  // Emit empty index files for non-indexed files
+  for (StringRef s : thinIndices) {
+    std::string path = getThinLTOOutputFile(s);
+    openFile(path + ".thinlto.bc");
+    if (config->thinLTOEmitImportsFiles)
+      openFile(path + ".imports");
+  }
+
+  if (config->thinLTOEmitIndexFiles)
+    thinLTOCreateEmptyIndexFiles();
 
   // In ThinLTO mode, Clang passes a temporary directory in -object_path_lto,
   // while the argument is a single file in FullLTO mode.
@@ -162,6 +246,32 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
     }
   }
 
+  auto outputFilePath = [objPathIsDir](int i) {
+    SmallString<261> filePath("/tmp/lto.tmp");
+    if (!config->ltoObjPath.empty()) {
+      filePath = config->ltoObjPath;
+      if (objPathIsDir)
+        path::append(filePath, Twine(i) + "." +
+                                   getArchitectureName(config->arch()) +
+                                   ".lto.o");
+    }
+    return filePath;
+  };
+
+  // ThinLTO with index only option is required to generate only the index
+  // files. After that, we exit from linker and ThinLTO backend runs in a
+  // distributed environment.
+  if (config->thinLTOIndexOnly) {
+    if (!config->ltoObjPath.empty())
+      saveBuffer(buf[0], outputFilePath(0));
+    if (indexFile)
+      indexFile->close();
+    return {};
+  }
+
+  if (!config->thinLTOCacheDir.empty())
+    pruneCache(config->thinLTOCacheDir, config->thinLTOCachePolicy, files);
+
   std::vector<ObjFile *> ret;
   for (unsigned i = 0; i < maxTasks; ++i) {
     // Get the native object contents either from the cache or from memory.  Do
@@ -183,14 +293,9 @@ std::vector<ObjFile *> BitcodeCompiler::compile() {
       saveBuffer(objBuf,
                  config->outputFile + ((i == 0) ? "" : Twine(i)) + ".lto.o");
 
-    SmallString<261> filePath("/tmp/lto.tmp");
+    auto filePath = outputFilePath(i);
     uint32_t modTime = 0;
     if (!config->ltoObjPath.empty()) {
-      filePath = config->ltoObjPath;
-      if (objPathIsDir)
-        path::append(filePath, Twine(i) + "." +
-                                   getArchitectureName(config->arch()) +
-                                   ".lto.o");
       saveOrHardlinkBuffer(objBuf, filePath, cachePath);
       modTime = getModTime(filePath);
     }
index 3e5f1a6..f07b1e3 100644 (file)
@@ -9,8 +9,11 @@
 #ifndef LLD_MACHO_LTO_H
 #define LLD_MACHO_LTO_H
 
+#include "lld/Common/LLVM.h"
+#include "llvm/ADT/DenseSet.h"
 #include "llvm/ADT/SmallString.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/raw_ostream.h"
 #include <memory>
 #include <vector>
 
@@ -34,6 +37,8 @@ private:
   std::unique_ptr<llvm::lto::LTO> ltoObj;
   std::vector<llvm::SmallString<0>> buf;
   std::vector<std::unique_ptr<llvm::MemoryBuffer>> files;
+  std::unique_ptr<llvm::raw_fd_ostream> indexFile;
+  llvm::DenseSet<StringRef> thinIndices;
 };
 
 } // namespace lld::macho
index b0ccf13..f05835d 100644 (file)
@@ -28,9 +28,22 @@ def color_diagnostics_eq: Joined<["--"], "color-diagnostics=">,
 def threads_eq : Joined<["--"], "threads=">,
     HelpText<"Number of threads. '1' disables multi-threading. By default all available hardware threads are used">,
     Group<grp_lld>;
+def thinlto_emit_imports_files: Flag<["--"], "thinlto-emit-imports-files">,
+    Group<grp_lld>;
+def thinlto_emit_index_files: Flag<["--"], "thinlto-emit-index-files">,
+    Group<grp_lld>;
+def thinlto_index_only: Flag<["--"], "thinlto-index-only">,
+    Group<grp_lld>;
+def thinlto_index_only_eq: Joined<["--"], "thinlto-index-only=">,
+    Group<grp_lld>;
 def thinlto_jobs_eq : Joined<["--"], "thinlto-jobs=">,
     HelpText<"Number of ThinLTO jobs. Default to --threads=">,
     Group<grp_lld>;
+def thinlto_object_suffix_replace_eq:
+    Joined<["--"], "thinlto-object-suffix-replace=">,
+    Group<grp_lld>;
+def thinlto_prefix_replace_eq: Joined<["--"], "thinlto-prefix-replace=">,
+    Group<grp_lld>;
 def reproduce: Separate<["--"], "reproduce">,
     Group<grp_lld>;
 def reproduce_eq: Joined<["--"], "reproduce=">,
index b0124c4..dc84df9 100644 (file)
 
 ;; Ensure lld doesn't generates index files when --thinlto-index-only is not enabled.
 ; RUN: rm -f 1.o.thinlto.bc 2.o.thinlto.bc
-; RUN: ld.lld -shared 1.o 2.o -o 4
+; RUN: ld.lld -shared 1.o 2.o -o 5
 ; RUN: not ls 1.o.thinlto.bc
 ; RUN: not ls 2.o.thinlto.bc
 
 ;; Ensure lld generates an index and not a binary if requested.
-; RUN: rm -f 4
 ; RUN: ld.lld --plugin-opt=thinlto-index-only -shared 1.o 2.o -o 4
 ; RUN: llvm-bcanalyzer -dump 1.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND1
 ; RUN: llvm-bcanalyzer -dump 2.o.thinlto.bc | FileCheck %s --check-prefix=BACKEND2
 ; RUN: not test -e 4
 
-;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib
+;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib.
 ; RUN: rm -f 2.o.thinlto.bc 4
 ; RUN: ld.lld --plugin-opt=thinlto-index-only -shared 1.o 3.o --start-lib 2.o --end-lib -o 4
 ; RUN: llvm-dis < 2.o.thinlto.bc | grep -q '\^0 = module:'
index 20cfed7..4058dd0 100644 (file)
@@ -47,7 +47,7 @@
 
 ;; Ensure lld does not emit empty combined module in default.
 ; RUN: rm -fr objpath && mkdir objpath
-; RUN: ld.lld 1.bc 2.bc -o objpath/a.out --save-temps
+; RUN: ld.lld -shared 1.bc 2.bc -o objpath/a.out --save-temps
 ; RUN: ls objpath/a.out*.lto.* | count 2
 
 ; EMPTY:     file format elf64-x86-64
index dcdf93b..4a4cac3 100644 (file)
@@ -14,7 +14,7 @@
 ; RUN: ld.lld --thinlto-index-only --thinlto-prefix-replace="%t/oldpath/;%t/newpath/" -shared %t/oldpath/thinlto_prefix_replace.o -o %t/thinlto_prefix_replace
 ; RUN: ls %t/newpath/thinlto_prefix_replace.o.thinlto.bc
 
-; Ensure that lld generates error if prefix replace option does not have 'old;new' format
+; Ensure that lld generates error if prefix replace option does not have 'old;new' format.
 ; RUN: rm -f %t/newpath/thinlto_prefix_replace.o.thinlto.bc
 ; RUN: not ld.lld --plugin-opt=thinlto-index-only --plugin-opt=thinlto-prefix-replace=abc:def -shared %t/oldpath/thinlto_prefix_replace.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
 ; ERR: --plugin-opt=thinlto-prefix-replace= expects 'old;new' format, but got abc:def
index 8903175..d36e536 100644 (file)
@@ -3,6 +3,7 @@
 
 ; RUN: rm -rf %t; mkdir %t
 ; RUN: opt -thinlto-bc %s -o %t/test.o
+; RUN: opt %s -o %t/test-nonthin.o
 
 ; RUN: %lld %t/test.o -o %t/test
 ; RUN: llvm-nm -pa %t/test | FileCheck %s --check-prefixes CHECK,NOOBJPATH
@@ -28,7 +29,7 @@
 
 ;; check that the object path can be an existing file
 ; RUN: touch %t/lto-tmp.o
-; RUN: ZERO_AR_DATE=0 %lld %t/test.o -o %t/test -object_path_lto %t/lto-tmp.o
+; RUN: ZERO_AR_DATE=0 %lld %t/test-nonthin.o -o %t/test -object_path_lto %t/lto-tmp.o
 ; RUN: llvm-nm -pa %t/test | FileCheck %s --check-prefixes CHECK,OBJPATH-FILE -DFILE=%t/lto-tmp.o
 
 
diff --git a/lld/test/MachO/thinlto-emit-imports.ll b/lld/test/MachO/thinlto-emit-imports.ll
new file mode 100644 (file)
index 0000000..47a612b
--- /dev/null
@@ -0,0 +1,70 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+; Generate summary sections and test lld handling.
+; RUN: opt -module-summary %t/f.ll -o %t1.o
+; RUN: opt -module-summary %t/g.ll -o %t2.o
+
+; Include a file with an empty module summary index, to ensure that the expected
+; output files are created regardless, for a distributed build system.
+; RUN: opt -module-summary %t/empty.ll -o %t3.o
+
+; Ensure lld generates imports files if requested for distributed backends.
+; RUN: rm -f %t3.o.imports %t3.o.thinlto.bc
+; RUN: %lld --thinlto-index-only --thinlto-emit-imports-files -dylib %t1.o %t2.o %t3.o -o %t4
+
+; The imports file for this module contains the bitcode file for %t/g.ll
+; RUN: count 1 < %t1.o.imports
+; RUN: FileCheck %s --check-prefix=IMPORTS1 < %t1.o.imports
+; IMPORTS1: thinlto-emit-imports.ll.tmp2.o
+
+; The imports file for g.ll is empty as it does not import anything.
+; RUN: count 0 < %t2.o.imports
+
+; The imports file for empty.ll is empty but should exist.
+; RUN: count 0 < %t3.o.imports
+
+; The index file should be created even for the input with an empty summary.
+; RUN: ls %t3.o.thinlto.bc
+
+; Ensure lld generates error if unable to write to imports file.
+; RUN: rm -f %t3.o.imports
+; RUN: touch %t3.o.imports
+; RUN: chmod 400 %t3.o.imports
+; RUN: not %lld --thinlto-index-only --thinlto-emit-imports-files -dylib %t1.o %t2.o %t3.o -o /dev/null 2>&1 \
+; RUN:     | FileCheck -DMSG=%errc_EACCES %s --check-prefix=ERR
+; ERR: cannot open {{.*}}3.o.imports: [[MSG]]
+
+; Ensure lld doesn't generate import files when thinlto-index-only is not enabled
+; RUN: rm -f %t1.o.imports
+; RUN: rm -f %t2.o.imports
+; RUN: rm -f %t3.o.imports
+; RUN: %lld --thinlto-emit-imports-files -dylib %t1.o %t2.o %t3.o -o %t4
+; RUN: not ls %t1.o.imports
+; RUN: not ls %t2.o.imports
+; RUN: not ls %t3.o.imports
+
+;--- f.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+declare void @g(...)
+
+define void @f() {
+entry:
+  call void (...) @g()
+  ret void
+}
+
+;--- g.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @g() {
+entry:
+  ret void
+}
+
+;--- empty.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
diff --git a/lld/test/MachO/thinlto-emit-index.ll b/lld/test/MachO/thinlto-emit-index.ll
new file mode 100644 (file)
index 0000000..fa87609
--- /dev/null
@@ -0,0 +1,122 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+;; Mostly copied/updated from thinlto-index-only.ll
+;; First ensure that the ThinLTO handling in lld handles
+;; bitcode without summary sections gracefully and generates index file.
+; RUN: llvm-as %t/f.ll -o %t/1.o
+; RUN: llvm-as %t/g.ll -o %t/2.o
+; RUN: %lld --thinlto-emit-index-files -dylib %t/1.o %t/2.o -o %t/3
+; RUN: ls %t/2.o.thinlto.bc
+; RUN: ls %t/3
+; RUN: %lld -dylib %t/1.o %t/2.o -o %t/3
+; RUN: llvm-nm %t/3 | FileCheck %s --check-prefix=NM
+
+;; Basic ThinLTO tests.
+; RUN: opt -module-summary %t/f.ll -o %t/1.o
+; RUN: opt -module-summary %t/g.ll -o %t/2.o
+; RUN: opt -module-summary %t/empty.ll -o %t/3.o
+
+;; Ensure lld generates an index and also a binary if requested.
+; RUN: %lld --thinlto-emit-index-files -dylib %t/1.o %t/2.o -o %t/4
+; RUN: llvm-bcanalyzer -dump %t/1.o.thinlto.bc | FileCheck %s -DP1=%t/1.o -DP2=%t/2.o --check-prefix=BACKEND1
+; RUN: llvm-bcanalyzer -dump %t/2.o.thinlto.bc | FileCheck %s -DP2=%t/2.o --check-prefix=BACKEND2
+; RUN: ls %t/4
+
+;; Ensure lld generates an index and not a binary if both emit-index and index-only are present.
+; RUN: %lld --thinlto-emit-index-files --thinlto-index-only -dylib %t/1.o %t/2.o -o %t/5
+; RUN: not ls %t/5
+
+;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib
+; RUN: rm -f %t/2.o.thinlto.bc
+; RUN: %lld --thinlto-emit-index-files -dylib %t/1.o %t/3.o --start-lib %t/2.o --end-lib -o %t/6
+; RUN: llvm-dis < %t/2.o.thinlto.bc | grep -q '\^0 = module:'
+; RUN: ls %t/6
+
+;; Test that LLD generates an empty index even for lazy object file that is not added to link.
+;; Test that LLD also generates empty imports file with the --thinlto-emit-imports-files option.
+; RUN: rm -f %t/1.o.thinlto.bc %t/1.o.imports
+; RUN: %lld --thinlto-emit-index-files -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN: --thinlto-emit-imports-files -o %t/7
+; RUN: ls %t/7
+; RUN: ls %t/1.o.thinlto.bc
+; RUN: ls %t/1.o.imports
+
+;; Ensure LLD generates an empty index for each bitcode file even if all bitcode files are lazy.
+; RUN: rm -f %t/1.o.thinlto.bc
+; RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin /dev/null -o dummy.o
+; RUN: %lld --thinlto-emit-index-files -dylib dummy.o --start-lib %t/1.o --end-lib -o %t/8
+; RUN: ls %t/8
+; RUN: ls %t/1.o.thinlto.bc
+
+;; Test that LLD errors out when run with suffix replacement, or prefix replacement
+; RUN: not %lld --thinlto-emit-index-files -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN: --thinlto-prefix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR1
+; ERR1: --thinlto-prefix-replace is not supported with --thinlto-emit-index-files
+
+; RUN: not %lld --thinlto-emit-index-files -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN: --thinlto-object-suffix-replace="abc;xyz" 2>&1 | FileCheck %s --check-prefix=ERR2
+; ERR2: --thinlto-object-suffix-replace is not supported with --thinlto-emit-index-files
+
+;; But not when passed with index only as well
+; RUN: %lld --thinlto-emit-index-files -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN: --thinlto-prefix-replace="abc;xyz" --thinlto-index-only
+
+; RUN: %lld --thinlto-emit-index-files -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN: --thinlto-object-suffix-replace="abc;xyz" --thinlto-index-only
+
+; NM: T _f
+
+;; The backend index for this module contains summaries from itself and
+;; Inputs/thinlto.ll, as it imports from the latter.
+; BACKEND1: <MODULE_STRTAB_BLOCK
+; BACKEND1-NEXT: <ENTRY {{.*}} record string = '[[P1]]'
+; BACKEND1-NEXT: <ENTRY {{.*}} record string = '[[P2]]'
+; BACKEND1-NEXT: </MODULE_STRTAB_BLOCK
+; BACKEND1: <GLOBALVAL_SUMMARY_BLOCK
+; BACKEND1: <VERSION
+; BACKEND1: <FLAGS
+; BACKEND1: <VALUE_GUID op0={{1|2}} op1={{-3706093650706652785|-5300342847281564238}}
+; BACKEND1: <VALUE_GUID op0={{1|2}} op1={{-3706093650706652785|-5300342847281564238}}
+; BACKEND1: <COMBINED
+; BACKEND1: <COMBINED
+; BACKEND1: </GLOBALVAL_SUMMARY_BLOCK
+
+;; The backend index for Input/thinlto.ll contains summaries from itself only,
+;; as it does not import anything.
+; BACKEND2: <MODULE_STRTAB_BLOCK
+; BACKEND2-NEXT: <ENTRY {{.*}} record string = '[[P2]]'
+; BACKEND2-NEXT: </MODULE_STRTAB_BLOCK
+; BACKEND2-NEXT: <GLOBALVAL_SUMMARY_BLOCK
+; BACKEND2-NEXT: <VERSION
+; BACKEND2-NEXT: <FLAGS
+; BACKEND2-NEXT: <VALUE_GUID op0=1 op1=-5300342847281564238
+; BACKEND2-NEXT: <COMBINED
+; BACKEND2-NEXT: <BLOCK_COUNT op0=2/>
+; BACKEND2-NEXT: </GLOBALVAL_SUMMARY_BLOCK
+
+
+;--- f.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+declare void @g(...)
+
+define void @f() {
+entry:
+  call void (...) @g()
+  ret void
+}
+
+;--- g.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @g() {
+entry:
+  ret void
+}
+
+;--- empty.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
diff --git a/lld/test/MachO/thinlto-index-file.ll b/lld/test/MachO/thinlto-index-file.ll
new file mode 100644 (file)
index 0000000..944cf70
--- /dev/null
@@ -0,0 +1,38 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+; RUN: opt -module-summary %t/f.ll -o %t/1.o
+; RUN: opt -module-summary %t/g.ll -o %t/2.o
+; RUN: opt -module-summary %t/empty.ll -o %t/3.o
+
+;; Ensure lld writes linked files to linked objects file
+; RUN: %lld --thinlto-index-only=%t/1.txt -dylib %t/1.o %t/2.o %t/3.o -o /dev/null
+; RUN: FileCheck %s < %t/1.txt
+; CHECK: 1.o
+; CHECK: 2.o
+; CHECK: 3.o
+
+;--- f.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+declare void @g(...)
+
+define void @f() {
+entry:
+  call void (...) @g()
+  ret void
+}
+
+;--- g.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @g() {
+entry:
+  ret void
+}
+
+;--- empty.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
diff --git a/lld/test/MachO/thinlto-index-only.ll b/lld/test/MachO/thinlto-index-only.ll
new file mode 100644 (file)
index 0000000..13453c4
--- /dev/null
@@ -0,0 +1,120 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+;; First ensure that the ThinLTO handling in lld handles
+;; bitcode without summary sections gracefully and generates index file.
+; RUN: llvm-as %t/f.ll -o %t/1.o
+; RUN: llvm-as %t/g.ll -o %t/2.o
+; RUN: %lld --thinlto-index-only -dylib %t/1.o %t/2.o -o %t/3
+; RUN: ls %t/2.o.thinlto.bc
+; RUN: not test -e %t/3
+; RUN: %lld -dylib %t/1.o %t/2.o -o %t/3
+; RUN: llvm-nm %t/3 | FileCheck %s --check-prefix=NM
+
+;; Basic ThinLTO tests.
+; RUN: opt -module-summary %t/f.ll -o %t/1.o
+; RUN: opt -module-summary %t/g.ll -o %t/2.o
+; RUN: opt -module-summary %t/empty.ll -o %t/3.o
+
+;; Ensure lld doesn't generates index files when thinlto-index-only is not enabled.
+; RUN: rm -f %t/1.o.thinlto.bc %t/2.o.thinlto.bc %t/3.o.thinlto.bc
+; RUN: %lld -dylib %t/1.o %t/2.o %t/3.o -o %t/5
+; RUN: not ls %t/1.o.thinlto.bc
+; RUN: not ls %t/2.o.thinlto.bc
+; RUN: not ls %t/3.o.thinlto.bc
+
+;; Ensure lld generates an index and not a binary if requested.
+; RUN: %lld --thinlto-index-only -dylib %t/1.o %t/2.o -o %t/4
+; RUN: llvm-bcanalyzer -dump %t/1.o.thinlto.bc | FileCheck %s -DP1=%t/1.o -DP2=%t/2.o --check-prefix=BACKEND1
+; RUN: llvm-bcanalyzer -dump %t/2.o.thinlto.bc | FileCheck %s -DP2=%t/2.o --check-prefix=BACKEND2
+; RUN: not test -e %t/4
+
+;; Ensure lld generates an index even if the file is wrapped in --start-lib/--end-lib.
+; RUN: rm -f %t/2.o.thinlto.bc %t/4
+; RUN: %lld --thinlto-index-only -dylib %t/1.o %t/3.o --start-lib %t/2.o --end-lib -o %t/4
+; RUN: llvm-dis < %t/2.o.thinlto.bc | grep -q '\^0 = module:'
+; RUN: not test -e %t/4
+
+;; Test that LLD generates an empty index even for lazy object file that is not added to link.
+;; Test LLD generates empty imports file either because of thinlto-emit-imports-files option.
+; RUN: rm -f %t/1.o.thinlto.bc %t/1.o.imports
+; RUN: %lld --thinlto-index-only -dylib %t/2.o --start-lib %t/1.o --end-lib \
+; RUN:      --thinlto-emit-imports-files -o %t/3
+; RUN: ls %t/1.o.thinlto.bc
+; RUN: ls %t/1.o.imports
+
+;; Ensure LLD generates an empty index for each bitcode file even if all bitcode files are lazy.
+; RUN: rm -f %t/1.o.thinlto.bc
+; RUN: llvm-mc -filetype=obj -triple=x86_64-apple-darwin /dev/null -o dummy.o
+; RUN: %lld --thinlto-index-only -dylib dummy.o --start-lib %t/1.o --end-lib -o /dev/null
+; RUN: ls %t/1.o.thinlto.bc
+
+;; Ensure when the same bitcode object is given as both lazy and non-lazy,
+;; LLD does not generate an empty index for the lazy object.
+; RUN: rm -f %t/2.o.thinlto.bc
+; RUN: %lld --thinlto-index-only -dylib %t/1.o %t/2.o --start-lib %t/2.o --end-lib -o /dev/null
+; RUN: llvm-dis < %t/2.o.thinlto.bc | grep -q '\^0 = module:'
+; RUN: rm -f %t/2.o.thinlto.bc
+; RUN: %lld --thinlto-index-only -dylib --start-lib %t/2.o --end-lib %t/2.o %t/1.o -o /dev/null
+; RUN: llvm-dis < %t/2.o.thinlto.bc | grep -q '\^0 = module:'
+
+;; Ensure when the same lazy bitcode object is given multiple times,
+;; no empty index file is generated if one of the copies is linked.
+; RUN: rm -f %t/2.o.thinlto.bc
+; RUN: %lld --thinlto-index-only -dylib %t/1.o --start-lib %t/2.o --end-lib --start-lib %t/2.o --end-lib -o /dev/null
+; RUN: llvm-dis < %t/2.o.thinlto.bc | grep -q '\^0 = module:'
+
+; NM: T _f
+
+;; The backend index for this module contains summaries from itself and
+;; g.ll, as it imports from the latter.
+; BACKEND1: <MODULE_STRTAB_BLOCK
+; BACKEND1-NEXT: <ENTRY {{.*}} record string = '[[P1]]'
+; BACKEND1-NEXT: <ENTRY {{.*}} record string = '[[P2]]'
+; BACKEND1-NEXT: </MODULE_STRTAB_BLOCK
+; BACKEND1: <GLOBALVAL_SUMMARY_BLOCK
+; BACKEND1: <VERSION
+; BACKEND1: <FLAGS
+; BACKEND1: <VALUE_GUID op0={{1|2}} op1={{-3706093650706652785|-5300342847281564238}}
+; BACKEND1: <VALUE_GUID op0={{1|2}} op1={{-3706093650706652785|-5300342847281564238}}
+; BACKEND1: <COMBINED
+; BACKEND1: <COMBINED
+; BACKEND1: </GLOBALVAL_SUMMARY_BLOCK
+
+;; The backend index for g.ll contains summaries from itself only,
+;; as it does not import anything.
+; BACKEND2: <MODULE_STRTAB_BLOCK
+; BACKEND2-NEXT: <ENTRY {{.*}} record string = '[[P2]]'
+; BACKEND2-NEXT: </MODULE_STRTAB_BLOCK
+; BACKEND2-NEXT: <GLOBALVAL_SUMMARY_BLOCK
+; BACKEND2-NEXT: <VERSION
+; BACKEND2-NEXT: <FLAGS
+; BACKEND2-NEXT: <VALUE_GUID op0=1 op1=-5300342847281564238
+; BACKEND2-NEXT: <COMBINED
+; BACKEND2-NEXT: <BLOCK_COUNT op0=2/>
+; BACKEND2-NEXT: </GLOBALVAL_SUMMARY_BLOCK
+
+;--- f.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+declare void @g(...)
+
+define void @f() {
+entry:
+  call void (...) @g()
+  ret void
+}
+
+;--- g.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @g() {
+entry:
+  ret void
+}
+
+;--- empty.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
diff --git a/lld/test/MachO/thinlto-obj-path.ll b/lld/test/MachO/thinlto-obj-path.ll
new file mode 100644 (file)
index 0000000..16545a1
--- /dev/null
@@ -0,0 +1,46 @@
+; REQUIRES: x86
+; RUN: rm -rf %t; split-file %s %t
+
+; RUN: opt -module-summary %t/f.ll -o %t1.o
+; RUN: opt -module-summary %t/g.ll -o %t2.o
+
+;; Test to ensure that thinlto-index-only with obj-path creates the file.
+; RUN: rm -rf %t4
+; RUN: %lld --thinlto-index-only -object_path_lto %t4 -dylib %t1.o %t2.o -o /dev/null
+; RUN: llvm-readobj -h %t4/0.x86_64.lto.o | FileCheck %s
+; RUN: llvm-nm %t4/0.x86_64.lto.o 2>&1 | FileCheck --check-prefix=NM %s
+; RUN: llvm-readobj -h %t4/0.x86_64.lto.o | FileCheck %s
+
+;; Ensure lld emits empty combined module if specific obj-path.
+; RUN: rm -fr %t.dir/objpath && mkdir -p %t.dir/objpath
+; RUN: %lld -object_path_lto %t4.o -dylib %t1.o %t2.o -o %t.dir/objpath/a.out -save-temps
+; RUN: ls %t.dir/objpath/a.out*.lto.* | count 3
+
+;; Ensure lld does not emit empty combined module in default.
+; RUN: rm -fr %t.dir/objpath && mkdir -p %t.dir/objpath
+; RUN: %lld -dylib %t1.o %t2.o -o %t.dir/objpath/a.out -save-temps
+; RUN: ls %t.dir/objpath/a.out*.lto.* | count 2
+
+; NM: no symbols
+; CHECK: Format: Mach-O 64-bit x86-64
+
+;--- f.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+declare void @g(...)
+
+define void @f() {
+entry:
+  call void (...) @g()
+  ret void
+}
+
+;--- g.ll
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @g() {
+entry:
+  ret void
+}
diff --git a/lld/test/MachO/thinlto-object-suffix-replace.ll b/lld/test/MachO/thinlto-object-suffix-replace.ll
new file mode 100644 (file)
index 0000000..83f3f9c
--- /dev/null
@@ -0,0 +1,47 @@
+; REQUIRES: x86
+;; Test to make sure the thinlto-object-suffix-replace option is handled
+;; correctly.
+; RUN: rm -rf %t && mkdir %t && cd %t
+
+;; Generate bitcode file with summary, as well as a minimized bitcode without
+; the debug metadata for the thin link.
+; RUN: opt --thinlto-bc %s -thin-link-bitcode-file=1.thinlink.bc -o 1.o
+
+;; First perform the thin link on the normal bitcode file, and save the
+;; resulting index.
+; RUN: %lld --thinlto-index-only -dylib 1.o -o 3
+; RUN: cp 1.o.thinlto.bc 1.o.thinlto.bc.orig
+
+;; Next perform the thin link on the minimized bitcode file, and compare dump
+;; of the resulting index to the above dump to ensure they are identical.
+; RUN: rm -f 1.o.thinlto.bc
+;; Make sure it isn't inadvertently using the regular bitcode file.
+; RUN: rm -f 1.o
+; RUN: %lld --thinlto-index-only --thinlto-object-suffix-replace=".thinlink.bc;.o" \
+; RUN:   -dylib 1.thinlink.bc -o 3
+; RUN: cmp 1.o.thinlto.bc.orig 1.o.thinlto.bc
+
+;; Ensure lld generates error if object suffix replace option does not have 'old;new' format
+; RUN: rm -f 1.o.thinlto.bc
+; RUN: not %lld --thinlto-index-only --thinlto-object-suffix-replace="abc:def" -dylib 1.thinlink.bc \
+; RUN:   -o 3 2>&1 | FileCheck %s --check-prefix=ERR1
+; ERR1: --thinlto-object-suffix-replace= expects 'old;new' format, but got abc:def
+
+;; If filename does not end with old suffix, no suffix change should occur,
+;; so ".thinlto.bc" will simply be appended to the input file name.
+; RUN: rm -f 1.thinlink.bc.thinlto.bc
+; RUN: %lld --thinlto-index-only --thinlto-object-suffix-replace=".abc;.o" -dylib 1.thinlink.bc -o /dev/null
+; RUN: ls 1.thinlink.bc.thinlto.bc
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @f() {
+entry:
+  ret void
+}
+
+!llvm.dbg.cu = !{}
+
+!1 = !{i32 2, !"Debug Info Version", i32 3}
+!llvm.module.flags = !{!1}
diff --git a/lld/test/MachO/thinlto-prefix-replace.ll b/lld/test/MachO/thinlto-prefix-replace.ll
new file mode 100644 (file)
index 0000000..d46163f
--- /dev/null
@@ -0,0 +1,24 @@
+; REQUIRES: x86
+
+;; Check that changing the output path via thinlto-prefix-replace works
+; RUN: mkdir -p %t/oldpath
+; RUN: opt -module-summary %s -o %t/oldpath/thinlto_prefix_replace.o
+
+;; Ensure that there is no existing file at the new path, so we properly
+;; test the creation of the new file there.
+; RUN: rm -f %t/newpath/thinlto_prefix_replace.o.thinlto.bc
+; RUN: %lld --thinlto-index-only --thinlto-prefix-replace="%t/oldpath/;%t/newpath/" -dylib %t/oldpath/thinlto_prefix_replace.o -o %t/thinlto_prefix_replace
+; RUN: ls %t/newpath/thinlto_prefix_replace.o.thinlto.bc
+
+;; Ensure that lld generates error if prefix replace option does not have 'old;new' format.
+; RUN: rm -f %t/newpath/thinlto_prefix_replace.o.thinlto.bc
+; RUN: not %lld --thinlto-index-only --thinlto-prefix-replace=abc:def -dylib %t/oldpath/thinlto_prefix_replace.o -o /dev/null 2>&1 | FileCheck %s --check-prefix=ERR
+; ERR: --thinlto-prefix-replace= expects 'old;new' format, but got abc:def
+
+target datalayout = "e-m:o-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
+target triple = "x86_64-apple-darwin"
+
+define void @f() {
+entry:
+  ret void
+}