[lld-macho] Support loading of zippered dylibs

ld64 can emit dylibs that support more than one platform (typically macOS and
macCatalyst). This diff allows LLD to read in those dylibs. Note that this is a
super bare-bones implementation -- in particular, I haven't added support for
LLD to emit those multi-platform dylibs, nor have I added a variety of
validation checks that ld64 does. Until we have a use-case for emitting zippered
dylibs, I think this is good enough.

Fixes PR49597.

Reviewed By: #lld-macho, oontvoo

Differential Revision: https://reviews.llvm.org/D101954
diff --git a/lld/MachO/InputFiles.cpp b/lld/MachO/InputFiles.cpp
index adc1bb2..7df2161 100644
--- a/lld/MachO/InputFiles.cpp
+++ b/lld/MachO/InputFiles.cpp
@@ -102,41 +102,42 @@
   return VersionTuple(major, minor, subMinor);
 }
 
-static Optional<PlatformInfo> getPlatformInfo(const InputFile *input) {
+static std::vector<PlatformInfo> getPlatformInfos(const InputFile *input) {
   if (!isa<ObjFile>(input) && !isa<DylibFile>(input))
-    return None;
+    return {};
 
   const char *hdr = input->mb.getBufferStart();
 
-  PlatformInfo platformInfo;
-  if (const auto *cmd =
-          findCommand<build_version_command>(hdr, LC_BUILD_VERSION)) {
-    platformInfo.target.Platform = static_cast<PlatformKind>(cmd->platform);
-    platformInfo.minimum = decodeVersion(cmd->minos);
-    return platformInfo;
+  std::vector<PlatformInfo> platformInfos;
+  for (auto *cmd : findCommands<build_version_command>(hdr, LC_BUILD_VERSION)) {
+    PlatformInfo info;
+    info.target.Platform = static_cast<PlatformKind>(cmd->platform);
+    info.minimum = decodeVersion(cmd->minos);
+    platformInfos.emplace_back(std::move(info));
   }
-  if (const auto *cmd = findCommand<version_min_command>(
-          hdr, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
-          LC_VERSION_MIN_TVOS, LC_VERSION_MIN_WATCHOS)) {
+  for (auto *cmd : findCommands<version_min_command>(
+           hdr, LC_VERSION_MIN_MACOSX, LC_VERSION_MIN_IPHONEOS,
+           LC_VERSION_MIN_TVOS, LC_VERSION_MIN_WATCHOS)) {
+    PlatformInfo info;
     switch (cmd->cmd) {
     case LC_VERSION_MIN_MACOSX:
-      platformInfo.target.Platform = PlatformKind::macOS;
+      info.target.Platform = PlatformKind::macOS;
       break;
     case LC_VERSION_MIN_IPHONEOS:
-      platformInfo.target.Platform = PlatformKind::iOS;
+      info.target.Platform = PlatformKind::iOS;
       break;
     case LC_VERSION_MIN_TVOS:
-      platformInfo.target.Platform = PlatformKind::tvOS;
+      info.target.Platform = PlatformKind::tvOS;
       break;
     case LC_VERSION_MIN_WATCHOS:
-      platformInfo.target.Platform = PlatformKind::watchOS;
+      info.target.Platform = PlatformKind::watchOS;
       break;
     }
-    platformInfo.minimum = decodeVersion(cmd->version);
-    return platformInfo;
+    info.minimum = decodeVersion(cmd->version);
+    platformInfos.emplace_back(std::move(info));
   }
 
-  return None;
+  return platformInfos;
 }
 
 static PlatformKind removeSimulator(PlatformKind platform) {
@@ -153,22 +154,33 @@
 }
 
 static bool checkCompatibility(const InputFile *input) {
-  Optional<PlatformInfo> platformInfo = getPlatformInfo(input);
-  if (!platformInfo)
+  std::vector<PlatformInfo> platformInfos = getPlatformInfos(input);
+  if (platformInfos.empty())
     return true;
 
-  if (removeSimulator(config->platform()) !=
-      removeSimulator(platformInfo->target.Platform)) {
-    error(toString(input) + " has platform " +
-          getPlatformName(platformInfo->target.Platform) +
+  auto it = find_if(platformInfos, [&](const PlatformInfo &info) {
+    return removeSimulator(info.target.Platform) ==
+           removeSimulator(config->platform());
+  });
+  if (it == platformInfos.end()) {
+    std::string platformNames;
+    raw_string_ostream os(platformNames);
+    interleave(
+        platformInfos, os,
+        [&](const PlatformInfo &info) {
+          os << getPlatformName(info.target.Platform);
+        },
+        "/");
+    error(toString(input) + " has platform " + platformNames +
           Twine(", which is different from target platform ") +
           getPlatformName(config->platform()));
     return false;
   }
-  if (platformInfo->minimum <= config->platformInfo.minimum)
+
+  if (it->minimum <= config->platformInfo.minimum)
     return true;
-  error(toString(input) + " has version " +
-        platformInfo->minimum.getAsString() +
+
+  error(toString(input) + " has version " + it->minimum.getAsString() +
         ", which is newer than target minimum of " +
         config->platformInfo.minimum.getAsString());
   return false;
diff --git a/lld/MachO/InputFiles.h b/lld/MachO/InputFiles.h
index d57e174..6e58601 100644
--- a/lld/MachO/InputFiles.h
+++ b/lld/MachO/InputFiles.h
@@ -187,20 +187,42 @@
 
 llvm::Optional<MemoryBufferRef> readFile(StringRef path);
 
-// anyHdr should be a pointer to either mach_header or mach_header_64
-template <class CommandType = llvm::MachO::load_command, class... Types>
-const CommandType *findCommand(const void *anyHdr, Types... types) {
+namespace detail {
+
+template <class CommandType, class... Types>
+std::vector<const CommandType *>
+findCommands(const void *anyHdr, size_t maxCommands, Types... types) {
+  std::vector<const CommandType *> cmds;
   std::initializer_list<uint32_t> typesList{types...};
   const auto *hdr = reinterpret_cast<const llvm::MachO::mach_header *>(anyHdr);
   const uint8_t *p =
       reinterpret_cast<const uint8_t *>(hdr) + target->headerSize;
   for (uint32_t i = 0, n = hdr->ncmds; i < n; ++i) {
     auto *cmd = reinterpret_cast<const CommandType *>(p);
-    if (llvm::is_contained(typesList, cmd->cmd))
-      return cmd;
+    if (llvm::is_contained(typesList, cmd->cmd)) {
+      cmds.push_back(cmd);
+      if (cmds.size() == maxCommands)
+        return cmds;
+    }
     p += cmd->cmdsize;
   }
-  return nullptr;
+  return cmds;
+}
+
+} // namespace detail
+
+// anyHdr should be a pointer to either mach_header or mach_header_64
+template <class CommandType = llvm::MachO::load_command, class... Types>
+const CommandType *findCommand(const void *anyHdr, Types... types) {
+  std::vector<const CommandType *> cmds =
+      detail::findCommands<CommandType>(anyHdr, 1, types...);
+  return cmds.size() ? cmds[0] : nullptr;
+}
+
+template <class CommandType = llvm::MachO::load_command, class... Types>
+std::vector<const CommandType *> findCommands(const void *anyHdr,
+                                              Types... types) {
+  return detail::findCommands<CommandType>(anyHdr, 0, types...);
 }
 
 } // namespace macho
diff --git a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libSystem.tbd b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libSystem.tbd
index b333678..7169059 100644
--- a/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libSystem.tbd
+++ b/lld/test/MachO/Inputs/MacOSX.sdk/usr/lib/libSystem.tbd
@@ -1,64 +1,72 @@
 --- !tapi-tbd
 tbd-version:      4
-targets:          [ x86_64-macos, arm64-macos ]
+targets:          [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
 uuids:
   - target:       x86_64-macos
     value:        00000000-0000-0000-0000-000000000000
+  - target:       x86_64-maccatalyst
+    value:        00000000-0000-0000-0000-000000000000
   - target:       arm64-macos
     value:        00000000-0000-0000-0000-000000000001
 install-name:     '/usr/lib/libSystem.dylib'
 current-version:  0001.001.1
 reexported-libraries:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     libraries:    [ '/usr/lib/system/libdyld.dylib',
                     '/usr/lib/system/libsystem_c.dylib',
                     '/usr/lib/system/libsystem_m.dylib' ]
 --- !tapi-tbd
 tbd-version:      4
-targets:          [ x86_64-macos, arm64-macos ]
+targets:          [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
 uuids:
   - target:       x86_64-macos
     value:        00000000-0000-0000-0000-000000000002
+  - target:       x86_64-maccatalyst
+    value:        00000000-0000-0000-0000-000000000000
   - target:       arm64-macos
     value:        00000000-0000-0000-0000-000000000003
 install-name:     '/usr/lib/system/libdyld.dylib'
 current-version:  0001.001.1
 parent-umbrella:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     umbrella:     System
 exports:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     symbols:      [ dyld_stub_binder, __tlv_bootstrap ]
 --- !tapi-tbd
 tbd-version:      4
-targets:          [ x86_64-macos, arm64-macos ]
+targets:          [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
 uuids:
   - target:       x86_64-macos
     value:        00000000-0000-0000-0000-000000000003
+  - target:       x86_64-maccatalyst
+    value:        00000000-0000-0000-0000-000000000000
   - target:       arm64-macos
     value:        00000000-0000-0000-0000-000000000004
 install-name:     '/usr/lib/system/libsystem_c.dylib'
 current-version:  0001.001.1
 parent-umbrella:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     umbrella:     System
 exports:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     symbols:      [ ]
 --- !tapi-tbd
 tbd-version:      4
-targets:          [ x86_64-macos, arm64-macos ]
+targets:          [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
 uuids:
   - target:       x86_64-macos
     value:        00000000-0000-0000-0000-000000000004
+  - target:       x86_64-maccatalyst
+    value:        00000000-0000-0000-0000-000000000000
   - target:       arm64-macos
     value:        00000000-0000-0000-0000-000000000005
 install-name:     '/usr/lib/system/libsystem_m.dylib'
 current-version:  0001.001.1
 parent-umbrella:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     umbrella:     System
 exports:
-  - targets:      [ x86_64-macos, arm64-macos ]
+  - targets:      [ x86_64-macos, x86_64-maccatalyst, arm64-macos ]
     symbols:      [ ___nan ]
 ...
diff --git a/lld/test/MachO/zippered.yaml b/lld/test/MachO/zippered.yaml
new file mode 100644
index 0000000..40fd1ef
--- /dev/null
+++ b/lld/test/MachO/zippered.yaml
@@ -0,0 +1,64 @@
+# REQUIRES: x86
+# RUN: rm -rf %t; mkdir %t
+# RUN: yaml2obj %s > %t/test.dylib
+# RUN: echo "" | llvm-mc -filetype=obj -triple=x86_64-apple-macos10.15 -o %t/test_macos.o
+# RUN: echo "" | llvm-mc -filetype=obj -triple=x86_64-apple-ios13.15.0-macabi -o %t/test_maccatalyst.o
+# RUN: echo "" | llvm-mc -filetype=obj -triple=x86_64-apple-ios13.15.0 -o %t/test_ios.o
+
+# RUN: %lld -lSystem -dylib %t/test.dylib %t/test_macos.o -o /dev/null
+# RUN: %lld -lSystem -dylib -platform_version mac-catalyst 13.15.0 14.0 %t/test.dylib %t/test_maccatalyst.o -o /dev/null
+
+# RUN: not %lld -lSystem -dylib -platform_version ios 13.15.0 14.0 %t/test.dylib %t/test_ios.o -o /dev/null 2>&1 | FileCheck %s
+# CHECK: test.dylib has platform macOS/macCatalyst, which is different from target platform iOS
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACF
+  cputype:         0x1000007
+  cpusubtype:      0x3
+  filetype:        0x6
+  ncmds:           4
+  sizeofcmds:      600
+  flags:           0x100085
+  reserved:        0x0
+LoadCommands:
+  - cmd:             LC_ID_DYLIB
+    cmdsize:         32
+    dylib:
+      name:            24
+      timestamp:       1
+      current_version: 0
+      compatibility_version: 0
+    PayloadString:   test
+    ZeroPadBytes:    4
+  - cmd:             LC_DYLD_INFO_ONLY
+    cmdsize:         48
+    rebase_off:      0
+    rebase_size:     0
+    bind_off:        0
+    bind_size:       0
+    weak_bind_off:   0
+    weak_bind_size:  0
+    lazy_bind_off:   0
+    lazy_bind_size:  0
+    export_off:      0
+    export_size:     0
+  - cmd:             LC_BUILD_VERSION
+    cmdsize:         32
+    platform:        1
+    minos:           659200
+    sdk:             720896
+    ntools:          1
+    Tools:
+      - tool:            3
+        version:         39913472
+  - cmd:             LC_BUILD_VERSION
+    cmdsize:         32
+    platform:        6
+    minos:           855808
+    sdk:             917504
+    ntools:          1
+    Tools:
+      - tool:            3
+        version:         39913472
+...