diff options
Diffstat (limited to 'llvm/tools/llvm-lipo')
-rw-r--r-- | llvm/tools/llvm-lipo/LipoOpts.td | 5 | ||||
-rw-r--r-- | llvm/tools/llvm-lipo/llvm-lipo.cpp | 222 |
2 files changed, 209 insertions, 18 deletions
diff --git a/llvm/tools/llvm-lipo/LipoOpts.td b/llvm/tools/llvm-lipo/LipoOpts.td index e3cbe2dfa8e..e2a73768733 100644 --- a/llvm/tools/llvm-lipo/LipoOpts.td +++ b/llvm/tools/llvm-lipo/LipoOpts.td @@ -23,6 +23,11 @@ def thin : Option<["-", "--"], "thin", KIND_SEPARATE>, HelpText<"Create a thin output file of specified arch_type from the " "fat input file. Requires -output option">; +def create : Option<["-", "--"], "create", KIND_FLAG>, + Group<action_group>, + HelpText<"Create a universal binary output file from the input " + "files. Requires -output option">; + def output : Option<["-", "--"], "output", KIND_SEPARATE>, HelpText<"Create output file with specified name">; def o : JoinedOrSeparate<["-"], "o">, Alias<output>; diff --git a/llvm/tools/llvm-lipo/llvm-lipo.cpp b/llvm/tools/llvm-lipo/llvm-lipo.cpp index ea0d427e019..65135bec951 100644 --- a/llvm/tools/llvm-lipo/llvm-lipo.cpp +++ b/llvm/tools/llvm-lipo/llvm-lipo.cpp @@ -80,6 +80,7 @@ enum class LipoAction { PrintArchs, VerifyArch, ThinArch, + CreateUniversal, }; struct Config { @@ -90,6 +91,14 @@ struct Config { LipoAction ActionToPerform; }; +struct Slice { + const MachOObjectFile *ObjectFile; + // Requires Alignment field to store slice alignment values from universal + // binaries. Also needed to order the slices using compareSlices, so the total + // file size can be calculated before creating the output buffer. + uint32_t Alignment; +}; + } // end namespace static void validateArchitectureName(StringRef ArchitectureName) { @@ -108,7 +117,7 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) { Config C; LipoOptTable T; unsigned MissingArgumentIndex, MissingArgumentCount; - llvm::opt::InputArgList InputArgs = + opt::InputArgList InputArgs = T.ParseArgs(ArgsArr, MissingArgumentIndex, MissingArgumentCount); if (MissingArgumentCount) @@ -186,6 +195,12 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) { C.ActionToPerform = LipoAction::ThinArch; return C; + case LIPO_create: + if (C.OutputFile.empty()) + reportError("create expects a single output file to be specified"); + C.ActionToPerform = LipoAction::CreateUniversal; + return C; + default: reportError("llvm-lipo action unspecified"); } @@ -195,8 +210,7 @@ static SmallVector<OwningBinary<Binary>, 1> readInputBinaries(ArrayRef<std::string> InputFiles) { SmallVector<OwningBinary<Binary>, 1> InputBinaries; for (StringRef InputFile : InputFiles) { - Expected<OwningBinary<llvm::object::Binary>> BinaryOrErr = - createBinary(InputFile); + Expected<OwningBinary<Binary>> BinaryOrErr = createBinary(InputFile); if (!BinaryOrErr) reportError(InputFile, BinaryOrErr.takeError()); // TODO: Add compatibility for archive files @@ -241,33 +255,35 @@ static void verifyArch(ArrayRef<OwningBinary<Binary>> InputBinaries, exit(EXIT_SUCCESS); } -static void printArchOrUnknown(const MachOObjectFile *ObjectFile) { - // Prints trailing space and unknown in this format for compatibility with - // cctools lipo. - const std::string ObjectArch = ObjectFile->getArchTriple().getArchName(); - if (ObjectArch.empty()) - outs() << "unknown(" << ObjectFile->getHeader().cputype << "," - << ObjectFile->getHeader().cpusubtype << ") "; - else - outs() << ObjectArch + " "; +// Returns a string of the given Object file's architecture type +// Unknown architectures formatted unknown(CPUType,CPUSubType) for compatibility +// with cctools lipo +static std::string getArchString(const MachOObjectFile &ObjectFile) { + const Triple T = ObjectFile.getArchTriple(); + const StringRef ObjectArch = T.getArchName(); + if (!ObjectArch.empty()) + return ObjectArch; + return ("unknown(" + Twine(ObjectFile.getHeader().cputype) + "," + + Twine(ObjectFile.getHeader().cpusubtype & ~MachO::CPU_SUBTYPE_MASK) + + ")") + .str(); } LLVM_ATTRIBUTE_NORETURN static void printArchs(ArrayRef<OwningBinary<Binary>> InputBinaries) { + // Prints trailing space for compatibility with cctools lipo. assert(InputBinaries.size() == 1 && "Incorrect number of input binaries"); const Binary *InputBinary = InputBinaries.front().getBinary(); if (auto UO = dyn_cast<MachOUniversalBinary>(InputBinary)) { - for (MachOUniversalBinary::object_iterator I = UO->begin_objects(), - E = UO->end_objects(); - I != E; ++I) { + for (const auto &O : UO->objects()) { Expected<std::unique_ptr<MachOObjectFile>> BinaryOrError = - I->getAsObjectFile(); + O.getAsObjectFile(); if (!BinaryOrError) reportError(InputBinary->getFileName(), BinaryOrError.takeError()); - printArchOrUnknown(BinaryOrError.get().get()); + outs() << getArchString(*BinaryOrError.get().get()) << " "; } } else if (auto O = dyn_cast<MachOObjectFile>(InputBinary)) { - printArchOrUnknown(O); + outs() << getArchString(*O) << " "; } else { llvm_unreachable("Unexpected binary format"); } @@ -314,6 +330,173 @@ static void extractSlice(ArrayRef<OwningBinary<Binary>> InputBinaries, exit(EXIT_SUCCESS); } +static void checkArchDuplicates(const ArrayRef<Slice> &Slices) { + DenseMap<uint64_t, const MachOObjectFile *> CPUIds; + auto CPUIDForSlice = [](const Slice &S) { + return static_cast<uint64_t>(S.ObjectFile->getHeader().cputype) << 32 | + S.ObjectFile->getHeader().cpusubtype; + }; + for (const auto &S : Slices) { + auto Entry = CPUIds.try_emplace(CPUIDForSlice(S), S.ObjectFile); + if (!Entry.second) + reportError(Entry.first->second->getFileName() + " and " + + S.ObjectFile->getFileName() + " have the same architecture " + + getArchString(*S.ObjectFile) + + " and therefore cannot be in the same universal binary"); + } +} + +static uint32_t calculateAlignment(const MachOObjectFile *ObjectFile) { + // TODO: Implement getAlign() and remove hard coding + // Will be implemented in a follow-up. + + switch (ObjectFile->getHeader().cputype) { + case MachO::CPU_TYPE_I386: + case MachO::CPU_TYPE_X86_64: + case MachO::CPU_TYPE_POWERPC: + case MachO::CPU_TYPE_POWERPC64: + return 12; // log2 value of page size(4k) for x86 and PPC + case MachO::CPU_TYPE_ARM: + case MachO::CPU_TYPE_ARM64: + case MachO::CPU_TYPE_ARM64_32: + return 14; // log2 value of page size(16k) for Darwin ARM + default: + return 12; + } +} + +// This function replicates ordering from cctools lipo for consistency +static bool compareSlices(const Slice &Lhs, const Slice &Rhs) { + if (Lhs.ObjectFile->getHeader().cputype == + Rhs.ObjectFile->getHeader().cputype) + return Lhs.ObjectFile->getHeader().cpusubtype < + Rhs.ObjectFile->getHeader().cpusubtype; + + // force arm64-family to follow after all other slices for compatibility + // with cctools lipo + if (Lhs.ObjectFile->getHeader().cputype == MachO::CPU_TYPE_ARM64) + return false; + if (Rhs.ObjectFile->getHeader().cputype == MachO::CPU_TYPE_ARM64) + return true; + + // Sort by alignment to minimize file size + return Lhs.Alignment < Rhs.Alignment; +} + +// Updates vector ExtractedObjects with the MachOObjectFiles extracted from +// Universal Binary files to transfer ownership. +static SmallVector<Slice, 2> buildSlices( + ArrayRef<OwningBinary<Binary>> InputBinaries, + SmallVectorImpl<std::unique_ptr<MachOObjectFile>> &ExtractedObjects) { + SmallVector<Slice, 2> Slices; + for (auto &IB : InputBinaries) { + const Binary *InputBinary = IB.getBinary(); + if (auto UO = dyn_cast<MachOUniversalBinary>(InputBinary)) { + for (const auto &O : UO->objects()) { + Expected<std::unique_ptr<MachOObjectFile>> BinaryOrError = + O.getAsObjectFile(); + if (!BinaryOrError) + reportError(InputBinary->getFileName(), BinaryOrError.takeError()); + ExtractedObjects.push_back(std::move(BinaryOrError.get())); + Slices.push_back(Slice{ExtractedObjects.back().get(), O.getAlign()}); + } + } else if (auto O = dyn_cast<MachOObjectFile>(InputBinary)) { + Slices.push_back(Slice{O, calculateAlignment(O)}); + } else { + llvm_unreachable("Unexpected binary format"); + } + } + return Slices; +} + +static SmallVector<MachO::fat_arch, 2> +buildFatArchList(ArrayRef<Slice> Slices) { + SmallVector<MachO::fat_arch, 2> FatArchList; + uint64_t Offset = + sizeof(MachO::fat_header) + Slices.size() * sizeof(MachO::fat_arch); + + for (size_t Index = 0, Size = Slices.size(); Index < Size; ++Index) { + Offset = alignTo(Offset, 1 << Slices[Index].Alignment); + const MachOObjectFile *ObjectFile = Slices[Index].ObjectFile; + if (Offset > UINT32_MAX) + reportError("fat file too large to be created because the offset " + "field in struct fat_arch is only 32-bits and the offset " + + Twine(Offset) + " for " + ObjectFile->getFileName() + + " for architecture " + getArchString(*ObjectFile) + + "exceeds that."); + + MachO::fat_arch FatArch; + FatArch.cputype = ObjectFile->getHeader().cputype; + FatArch.cpusubtype = ObjectFile->getHeader().cpusubtype; + FatArch.offset = Offset; + FatArch.size = ObjectFile->getMemoryBufferRef().getBufferSize(); + FatArch.align = Slices[Index].Alignment; + Offset += FatArch.size; + FatArchList.push_back(FatArch); + } + return FatArchList; +} + +static void createUniversalBinary(SmallVectorImpl<Slice> &Slices, + StringRef OutputFileName) { + MachO::fat_header FatHeader; + FatHeader.magic = MachO::FAT_MAGIC; + FatHeader.nfat_arch = Slices.size(); + + stable_sort(Slices, compareSlices); + SmallVector<MachO::fat_arch, 2> FatArchList = buildFatArchList(Slices); + + const bool IsExecutable = any_of(Slices, [](Slice S) { + return sys::fs::can_execute(S.ObjectFile->getFileName()); + }); + const uint64_t OutputFileSize = + FatArchList.back().offset + FatArchList.back().size; + Expected<std::unique_ptr<FileOutputBuffer>> OutFileOrError = + FileOutputBuffer::create(OutputFileName, OutputFileSize, + IsExecutable ? FileOutputBuffer::F_executable + : 0); + if (!OutFileOrError) + reportError(OutputFileName, OutFileOrError.takeError()); + std::unique_ptr<FileOutputBuffer> OutFile = std::move(OutFileOrError.get()); + std::memset(OutFile->getBufferStart(), 0, OutputFileSize); + + if (sys::IsLittleEndianHost) + MachO::swapStruct(FatHeader); + std::memcpy(OutFile->getBufferStart(), &FatHeader, sizeof(MachO::fat_header)); + + for (size_t Index = 0, Size = Slices.size(); Index < Size; ++Index) { + MemoryBufferRef BufferRef = Slices[Index].ObjectFile->getMemoryBufferRef(); + std::copy(BufferRef.getBufferStart(), BufferRef.getBufferEnd(), + OutFile->getBufferStart() + FatArchList[Index].offset); + } + + // FatArchs written after Slices in order reduce the number of swaps for the + // LittleEndian case + if (sys::IsLittleEndianHost) + for (MachO::fat_arch &FA : FatArchList) + MachO::swapStruct(FA); + std::memcpy(OutFile->getBufferStart() + sizeof(MachO::fat_header), + FatArchList.begin(), + sizeof(MachO::fat_arch) * FatArchList.size()); + + if (Error E = OutFile->commit()) + reportError(OutputFileName, std::move(E)); +} + +LLVM_ATTRIBUTE_NORETURN +static void createUniversalBinary(ArrayRef<OwningBinary<Binary>> InputBinaries, + StringRef OutputFileName) { + assert(InputBinaries.size() >= 1 && "Incorrect number of input binaries"); + assert(!OutputFileName.empty() && "Create expects a single output file"); + + SmallVector<std::unique_ptr<MachOObjectFile>, 1> ExtractedObjects; + SmallVector<Slice, 1> Slices = buildSlices(InputBinaries, ExtractedObjects); + checkArchDuplicates(Slices); + createUniversalBinary(Slices, OutputFileName); + + exit(EXIT_SUCCESS); +} + int main(int argc, char **argv) { InitLLVM X(argc, argv); Config C = parseLipoOptions(makeArrayRef(argv + 1, argc)); @@ -330,6 +513,9 @@ int main(int argc, char **argv) { case LipoAction::ThinArch: extractSlice(InputBinaries, C.ThinArchType, C.OutputFile); break; + case LipoAction::CreateUniversal: + createUniversalBinary(InputBinaries, C.OutputFile); + break; } return EXIT_SUCCESS; } |