[llvm] 8f6f6f4 - [llvm-libtool-darwin] Support universal outputs

Sameer Arora via llvm-commits llvm-commits at lists.llvm.org
Fri Aug 14 11:36:35 PDT 2020


Author: Sameer Arora
Date: 2020-08-14T11:32:07-07:00
New Revision: 8f6f6f407aa984a2d567aee34e3461e24829867c

URL: https://github.com/llvm/llvm-project/commit/8f6f6f407aa984a2d567aee34e3461e24829867c
DIFF: https://github.com/llvm/llvm-project/commit/8f6f6f407aa984a2d567aee34e3461e24829867c.diff

LOG: [llvm-libtool-darwin] Support universal outputs

Add support for producing universal binaries containing archives when
`llvm-libtool-darwin` is given inputs of multiple architectures.

Reviewed by jhenderson, smeenai

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

Added: 
    llvm/test/tools/llvm-libtool-darwin/universal-output.test

Modified: 
    llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp

Removed: 
    


################################################################################
diff  --git a/llvm/test/tools/llvm-libtool-darwin/universal-output.test b/llvm/test/tools/llvm-libtool-darwin/universal-output.test
new file mode 100644
index 000000000000..515c3e419278
--- /dev/null
+++ b/llvm/test/tools/llvm-libtool-darwin/universal-output.test
@@ -0,0 +1,116 @@
+## This test checks that a correct universal binary is produced when
+## llvm-libtool-darwin is given inputs for multiple architectures.
+
+## Check that the subtypes of cputype CPU_TYPE_ARM are stored in a fat file:
+# RUN: yaml2obj %s -o %t.armv6 -DTYPE=0xC -DSUBTYPE=0x6 -DSTRING=_armv6
+# RUN: yaml2obj %s -o %t.armv7 -DTYPE=0xC -DSUBTYPE=0x9 -DSTRING=_armv7
+
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.armv6 %t.armv7
+
+## Check that architectures are present in the universal output:
+# RUN: llvm-lipo -info %t.lib | \
+# RUN:   FileCheck %s --check-prefix=ARCHS -DFILE=%t.lib
+
+# ARCHS: Architectures in the fat file: [[FILE]] are: armv6 armv7
+
+## Check that the files with the same architecture are combined in an archive:
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.armv6 %t.armv6 %t.armv7
+# RUN: llvm-lipo -info %t.lib | \
+# RUN:   FileCheck %s --check-prefix=ARCHS -DFILE=%t.lib
+# RUN: llvm-objdump --all-headers %t.lib | \
+# RUN:   FileCheck %s --check-prefix=UNIVERSAL-MEMBERS -DFILE=%t.lib -DPREFIX=%basename_t.tmp --implicit-check-not=Archive
+
+# UNIVERSAL-MEMBERS:      Archive : [[FILE]] (architecture armv6)
+# UNIVERSAL-MEMBERS-NEXT: __.SYMDEF
+# UNIVERSAL-MEMBERS-NEXT: [[PREFIX]].armv6
+# UNIVERSAL-MEMBERS-NEXT: [[PREFIX]].armv6
+# UNIVERSAL-MEMBERS:      Archive : [[FILE]] (architecture armv7)
+# UNIVERSAL-MEMBERS-NEXT: __.SYMDEF
+# UNIVERSAL-MEMBERS-NEXT: [[PREFIX]].armv7
+
+## Check that the files extracted from a universal output are archives:
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.armv6 %t.armv7
+# RUN: llvm-lipo %t.lib -thin armv7 -output %t-extracted-v7.a
+# RUN: llvm-ar t %t-extracted-v7.a | \
+# RUN:   FileCheck %s --check-prefix=EXTRACT --implicit-check-not={{.}} -DPREFIX=%basename_t.tmp
+# RUN: llvm-nm --print-armap %t-extracted-v7.a | \
+# RUN:   FileCheck %s --check-prefix=EXTRACT-SYMBOLS -DPREFIX=%basename_t.tmp --match-full-lines
+
+# EXTRACT: [[PREFIX]].armv7
+
+# EXTRACT-SYMBOLS:      Archive map
+# EXTRACT-SYMBOLS-NEXT: _armv7 in [[PREFIX]].armv7
+# EXTRACT-SYMBOLS-EMPTY:
+
+## Check that the subtypes of cputype CPU_TYPE_X86_64 are stored in a fat file:
+# RUN: yaml2obj %s -o %t.x86_64 -DTYPE=0x01000007 -DSUBTYPE=0x3 -DSTRING=_x86_64
+# RUN: yaml2obj %s -o %t.x86_64_h -DTYPE=0x01000007 -DSUBTYPE=0x8 -DSTRING=_x86_64_h
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.x86_64 %t.x86_64_h
+# RUN: llvm-lipo -info %t.lib | \
+# RUN:   FileCheck %s --check-prefix=ARCHS-X86 -DFILE=%t.lib
+
+# ARCHS-X86: Architectures in the fat file: [[FILE]] are: x86_64 x86_64h
+
+## Check that the subtypes of cputype CPU_TYPE_ARM64 are stored in a fat file:
+## Testing it using llvm-objdump as, currently, there is no support for arm64e
+## under llvm/lib/Object/MachOObjectFile.cpp.
+# RUN: yaml2obj %s -o %t.arm64 -DTYPE=0x0100000C -DSUBTYPE=0x0 -DSTRING=_arm64all
+# RUN: yaml2obj %s -o %t.arm64e -DTYPE=0x0100000C -DSUBTYPE=0x2 -DSTRING=_arm64e
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.arm64 %t.arm64e
+# RUN: llvm-objdump --all-headers %t.lib | \
+# RUN:   FileCheck %s --check-prefix=UNIVERSAL-MEMBERS-ARM64 -DFILE=%t.lib -DPREFIX=%basename_t.tmp --implicit-check-not=Archive
+
+# UNIVERSAL-MEMBERS-ARM64:      Archive : [[FILE]] (architecture arm64)
+# UNIVERSAL-MEMBERS-ARM64-NEXT: __.SYMDEF
+# UNIVERSAL-MEMBERS-ARM64-NEXT: [[PREFIX]].arm64
+# UNIVERSAL-MEMBERS-ARM64:      Archive : [[FILE]]
+# UNIVERSAL-MEMBERS-ARM64-NEXT: __.SYMDEF
+# UNIVERSAL-MEMBERS-ARM64-NEXT: [[PREFIX]].arm64e
+
+## Check that 
diff erent cputypes are stored together in a fat file:
+# RUN: yaml2obj %s -o %t.ppc -DTYPE=0x12 -DSUBTYPE=0x0 -DSTRING=_ppcall
+# RUN: llvm-libtool-darwin -static -o %t.lib %t.armv6 %t.ppc
+# RUN: llvm-lipo -info %t.lib | \
+# RUN:   FileCheck %s --check-prefix=ARCHS-CPU -DFILE=%t.lib
+
+# ARCHS-CPU: Architectures in the fat file: [[FILE]] are: ppc armv6
+
+--- !mach-o
+FileHeader:
+  magic:           0xFEEDFACE
+  cputype:         [[TYPE]]
+  cpusubtype:      [[SUBTYPE]]
+  filetype:        0x00000001
+  ncmds:           2
+  sizeofcmds:      148
+  flags:           0x00002000
+LoadCommands:
+  - cmd:             LC_SEGMENT
+    cmdsize:         124
+    segname:         ''
+    vmaddr:          0
+    vmsize:          10
+    fileoff:         280
+    filesize:        10
+    maxprot:         7
+    initprot:        7
+    nsects:          1
+    flags:           0
+  - cmd:             LC_SYMTAB
+    cmdsize:         24
+    symoff:          292
+    nsyms:           1
+    stroff:          304
+    strsize:         8
+LinkEditData:
+  NameList:
+    - n_strx:          1
+      n_type:          0x0F
+      n_sect:          1
+      n_desc:          8
+      n_value:         0
+  StringTable:
+    - ''
+    - [[STRING]]
+    - ''
+...

diff  --git a/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp b/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp
index 5e0c356985db..e24167b28d78 100644
--- a/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp
+++ b/llvm/tools/llvm-libtool-darwin/llvm-libtool-darwin.cpp
@@ -14,16 +14,21 @@
 #include "llvm/Object/ArchiveWriter.h"
 #include "llvm/Object/MachO.h"
 #include "llvm/Object/MachOUniversal.h"
+#include "llvm/Object/MachOUniversalWriter.h"
 #include "llvm/Object/ObjectFile.h"
 #include "llvm/Support/CommandLine.h"
 #include "llvm/Support/InitLLVM.h"
 #include "llvm/Support/LineIterator.h"
 #include "llvm/Support/WithColor.h"
 #include "llvm/TextAPI/MachO/Architecture.h"
+#include <map>
 
 using namespace llvm;
 using namespace llvm::object;
 
+typedef std::map<uint64_t, std::vector<NewArchiveMember>>
+    MembersPerArchitectureMap;
+
 cl::OptionCategory LibtoolCategory("llvm-libtool-darwin Options");
 
 static cl::opt<std::string> OutputFile("o", cl::desc("Specify output filename"),
@@ -63,6 +68,8 @@ static cl::opt<std::string>
 
 struct Config {
   bool Deterministic = true; // Updated by 'D' and 'U' modifiers.
+  uint32_t ArchCPUType;
+  uint32_t ArchCPUSubtype;
 };
 
 static Error processFileList() {
@@ -113,36 +120,47 @@ static Error validateArchitectureName(StringRef ArchitectureName) {
   return Error::success();
 }
 
+static uint64_t getCPUID(uint32_t CPUType, uint32_t CPUSubtype) {
+  switch (CPUType) {
+  case MachO::CPU_TYPE_ARM:
+  case MachO::CPU_TYPE_ARM64:
+  case MachO::CPU_TYPE_ARM64_32:
+  case MachO::CPU_TYPE_X86_64:
+    // We consider CPUSubtype only for the above 4 CPUTypes to match cctools'
+    // libtool behavior.
+    return static_cast<uint64_t>(CPUType) << 32 | CPUSubtype;
+  default:
+    return CPUType;
+  }
+}
+
 // Check that a file's architecture [FileCPUType, FileCPUSubtype]
 // matches the architecture specified under -arch_only flag.
-static bool acceptFileArch(uint32_t FileCPUType, uint32_t FileCPUSubtype) {
-  uint32_t ArchCPUType, ArchCPUSubtype;
-  std::tie(ArchCPUType, ArchCPUSubtype) = MachO::getCPUTypeFromArchitecture(
-      MachO::getArchitectureFromName(ArchType));
-
-  if (ArchCPUType != FileCPUType)
+static bool acceptFileArch(uint32_t FileCPUType, uint32_t FileCPUSubtype,
+                           const Config &C) {
+  if (C.ArchCPUType != FileCPUType)
     return false;
 
-  switch (ArchCPUType) {
+  switch (C.ArchCPUType) {
   case MachO::CPU_TYPE_ARM:
   case MachO::CPU_TYPE_ARM64_32:
   case MachO::CPU_TYPE_X86_64:
-    return ArchCPUSubtype == FileCPUSubtype;
+    return C.ArchCPUSubtype == FileCPUSubtype;
 
   case MachO::CPU_TYPE_ARM64:
-    if (ArchCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL)
+    if (C.ArchCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL)
       return FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_ALL ||
              FileCPUSubtype == MachO::CPU_SUBTYPE_ARM64_V8;
     else
-      return ArchCPUSubtype == FileCPUSubtype;
+      return C.ArchCPUSubtype == FileCPUSubtype;
 
   default:
     return true;
   }
 }
 
-static Error verifyAndAddMachOObject(std::vector<NewArchiveMember> &Members,
-                                     NewArchiveMember Member) {
+static Error verifyAndAddMachOObject(MembersPerArchitectureMap &Members,
+                                     NewArchiveMember Member, const Config &C) {
   auto MBRef = Member.Buf->getMemBufferRef();
   Expected<std::unique_ptr<object::ObjectFile>> ObjOrErr =
       object::ObjectFile::createObjectFile(MBRef);
@@ -164,28 +182,29 @@ static Error verifyAndAddMachOObject(std::vector<NewArchiveMember> &Members,
 
   // If -arch_only is specified then skip this file if it doesn't match
   // the architecture specified.
-  if (!ArchType.empty() && !acceptFileArch(FileCPUType, FileCPUSubtype)) {
+  if (!ArchType.empty() && !acceptFileArch(FileCPUType, FileCPUSubtype, C)) {
     return Error::success();
   }
 
-  Members.push_back(std::move(Member));
+  uint64_t FileCPUID = getCPUID(FileCPUType, FileCPUSubtype);
+  Members[FileCPUID].push_back(std::move(Member));
   return Error::success();
 }
 
-static Error addChildMember(std::vector<NewArchiveMember> &Members,
+static Error addChildMember(MembersPerArchitectureMap &Members,
                             const object::Archive::Child &M, const Config &C) {
   Expected<NewArchiveMember> NMOrErr =
       NewArchiveMember::getOldMember(M, C.Deterministic);
   if (!NMOrErr)
     return NMOrErr.takeError();
 
-  if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr)))
+  if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C))
     return E;
 
   return Error::success();
 }
 
-static Error processArchive(std::vector<NewArchiveMember> &Members,
+static Error processArchive(MembersPerArchitectureMap &Members,
                             object::Archive &Lib, StringRef FileName,
                             const Config &C) {
   Error Err = Error::success();
@@ -199,7 +218,7 @@ static Error processArchive(std::vector<NewArchiveMember> &Members,
 }
 
 static Error
-addArchiveMembers(std::vector<NewArchiveMember> &Members,
+addArchiveMembers(MembersPerArchitectureMap &Members,
                   std::vector<std::unique_ptr<MemoryBuffer>> &ArchiveBuffers,
                   NewArchiveMember NM, StringRef FileName, const Config &C) {
   Expected<std::unique_ptr<Archive>> LibOrErr =
@@ -217,7 +236,7 @@ addArchiveMembers(std::vector<NewArchiveMember> &Members,
 }
 
 static Error addUniversalMembers(
-    std::vector<NewArchiveMember> &Members,
+    MembersPerArchitectureMap &Members,
     std::vector<std::unique_ptr<MemoryBuffer>> &UniversalBuffers,
     NewArchiveMember NM, StringRef FileName, const Config &C) {
   Expected<std::unique_ptr<MachOUniversalBinary>> BinaryOrErr =
@@ -235,7 +254,7 @@ static Error addUniversalMembers(
           NewArchiveMember(MachOObjOrErr->get()->getMemoryBufferRef());
       NewMember.MemberName = sys::path::filename(NewMember.MemberName);
 
-      if (Error E = verifyAndAddMachOObject(Members, std::move(NewMember)))
+      if (Error E = verifyAndAddMachOObject(Members, std::move(NewMember), C))
         return E;
       continue;
     }
@@ -264,7 +283,7 @@ static Error addUniversalMembers(
   return Error::success();
 }
 
-static Error addMember(std::vector<NewArchiveMember> &Members,
+static Error addMember(MembersPerArchitectureMap &Members,
                        std::vector<std::unique_ptr<MemoryBuffer>> &FileBuffers,
                        StringRef FileName, const Config &C) {
   Expected<NewArchiveMember> NMOrErr =
@@ -287,30 +306,80 @@ static Error addMember(std::vector<NewArchiveMember> &Members,
     return addUniversalMembers(Members, FileBuffers, std::move(*NMOrErr),
                                FileName, C);
 
-  if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr)))
+  if (Error E = verifyAndAddMachOObject(Members, std::move(*NMOrErr), C))
     return E;
   return Error::success();
 }
 
+static Expected<SmallVector<Slice, 2>>
+buildSlices(ArrayRef<OwningBinary<Archive>> OutputBinaries) {
+  SmallVector<Slice, 2> Slices;
+
+  for (const auto &OB : OutputBinaries) {
+    const Archive *A = OB.getBinary();
+    Expected<Slice> ArchiveSlice = Slice::create(A);
+    if (!ArchiveSlice)
+      return ArchiveSlice.takeError();
+    Slices.push_back(*ArchiveSlice);
+  }
+  return Slices;
+}
+
 static Error createStaticLibrary(const Config &C) {
-  std::vector<NewArchiveMember> NewMembers;
+  MembersPerArchitectureMap NewMembers;
   std::vector<std::unique_ptr<MemoryBuffer>> FileBuffers;
   for (StringRef FileName : InputFiles)
     if (Error E = addMember(NewMembers, FileBuffers, FileName, C))
       return E;
 
-  if (NewMembers.empty() && !ArchType.empty())
-    return createStringError(std::errc::invalid_argument,
-                             "no library created (no object files in input "
-                             "files matching -arch_only %s)",
-                             ArchType.c_str());
-
-  if (Error E =
-          writeArchive(OutputFile, NewMembers,
-                       /*WriteSymtab=*/true,
-                       /*Kind=*/object::Archive::K_DARWIN, C.Deterministic,
-                       /*Thin=*/false))
-    return E;
+  if (!ArchType.empty()) {
+    uint64_t ArchCPUID = getCPUID(C.ArchCPUType, C.ArchCPUSubtype);
+    if (NewMembers.find(ArchCPUID) == NewMembers.end())
+      return createStringError(std::errc::invalid_argument,
+                               "no library created (no object files in input "
+                               "files matching -arch_only %s)",
+                               ArchType.c_str());
+  }
+
+  if (NewMembers.size() == 1) {
+    if (Error E =
+            writeArchive(OutputFile, NewMembers.begin()->second,
+                         /*WriteSymtab=*/true,
+                         /*Kind=*/object::Archive::K_DARWIN, C.Deterministic,
+                         /*Thin=*/false))
+      return E;
+  } else {
+    SmallVector<OwningBinary<Archive>, 2> OutputBinaries;
+    for (const std::pair<const uint64_t, std::vector<NewArchiveMember>> &M :
+         NewMembers) {
+      Expected<std::unique_ptr<MemoryBuffer>> OutputBufferOrErr =
+          writeArchiveToBuffer(M.second,
+                               /*WriteSymtab=*/true,
+                               /*Kind=*/object::Archive::K_DARWIN,
+                               C.Deterministic,
+                               /*Thin=*/false);
+      if (!OutputBufferOrErr)
+        return OutputBufferOrErr.takeError();
+      std::unique_ptr<MemoryBuffer> &OutputBuffer = OutputBufferOrErr.get();
+
+      Expected<std::unique_ptr<Archive>> ArchiveOrError =
+          Archive::create(OutputBuffer->getMemBufferRef());
+      if (!ArchiveOrError)
+        return ArchiveOrError.takeError();
+      std::unique_ptr<Archive> &A = ArchiveOrError.get();
+
+      OutputBinaries.push_back(
+          OwningBinary<Archive>(std::move(A), std::move(OutputBuffer)));
+    }
+
+    Expected<SmallVector<Slice, 2>> Slices = buildSlices(OutputBinaries);
+    if (!Slices)
+      return Slices.takeError();
+
+    llvm::stable_sort(*Slices);
+    if (Error E = writeUniversalBinary(*Slices, OutputFile))
+      return E;
+  }
   return Error::success();
 }
 
@@ -332,10 +401,15 @@ static Expected<Config> parseCommandLine(int Argc, char **Argv) {
     return createStringError(std::errc::invalid_argument,
                              "no input files specified");
 
-  if (ArchType.getNumOccurrences())
+  if (ArchType.getNumOccurrences()) {
     if (Error E = validateArchitectureName(ArchType))
       return std::move(E);
 
+    std::tie(C.ArchCPUType, C.ArchCPUSubtype) =
+        MachO::getCPUTypeFromArchitecture(
+            MachO::getArchitectureFromName(ArchType));
+  }
+
   return C;
 }
 


        


More information about the llvm-commits mailing list