@@ -80,6 +80,7 @@ enum class LipoAction {
80
80
PrintArchs,
81
81
VerifyArch,
82
82
ThinArch,
83
+ CreateUniversal,
83
84
};
84
85
85
86
struct Config {
@@ -90,6 +91,14 @@ struct Config {
90
91
LipoAction ActionToPerform;
91
92
};
92
93
94
+ struct Slice {
95
+ const MachOObjectFile *ObjectFile;
96
+ // Requires Alignment field to store slice alignment values from universal
97
+ // binaries. Also needed to order the slices using compareSlices, so the total
98
+ // file size can be calculated before creating the output buffer.
99
+ uint32_t Alignment;
100
+ };
101
+
93
102
} // end namespace
94
103
95
104
static void validateArchitectureName (StringRef ArchitectureName) {
@@ -108,7 +117,7 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) {
108
117
Config C;
109
118
LipoOptTable T;
110
119
unsigned MissingArgumentIndex, MissingArgumentCount;
111
- llvm:: opt::InputArgList InputArgs =
120
+ opt::InputArgList InputArgs =
112
121
T.ParseArgs (ArgsArr, MissingArgumentIndex, MissingArgumentCount);
113
122
114
123
if (MissingArgumentCount)
@@ -186,6 +195,12 @@ static Config parseLipoOptions(ArrayRef<const char *> ArgsArr) {
186
195
C.ActionToPerform = LipoAction::ThinArch;
187
196
return C;
188
197
198
+ case LIPO_create:
199
+ if (C.OutputFile .empty ())
200
+ reportError (" create expects a single output file to be specified" );
201
+ C.ActionToPerform = LipoAction::CreateUniversal;
202
+ return C;
203
+
189
204
default :
190
205
reportError (" llvm-lipo action unspecified" );
191
206
}
@@ -195,8 +210,7 @@ static SmallVector<OwningBinary<Binary>, 1>
195
210
readInputBinaries (ArrayRef<std::string> InputFiles) {
196
211
SmallVector<OwningBinary<Binary>, 1 > InputBinaries;
197
212
for (StringRef InputFile : InputFiles) {
198
- Expected<OwningBinary<llvm::object::Binary>> BinaryOrErr =
199
- createBinary (InputFile);
213
+ Expected<OwningBinary<Binary>> BinaryOrErr = createBinary (InputFile);
200
214
if (!BinaryOrErr)
201
215
reportError (InputFile, BinaryOrErr.takeError ());
202
216
// TODO: Add compatibility for archive files
@@ -241,33 +255,35 @@ static void verifyArch(ArrayRef<OwningBinary<Binary>> InputBinaries,
241
255
exit (EXIT_SUCCESS);
242
256
}
243
257
244
- static void printArchOrUnknown (const MachOObjectFile *ObjectFile) {
245
- // Prints trailing space and unknown in this format for compatibility with
246
- // cctools lipo.
247
- const std::string ObjectArch = ObjectFile->getArchTriple ().getArchName ();
248
- if (ObjectArch.empty ())
249
- outs () << " unknown(" << ObjectFile->getHeader ().cputype << " ,"
250
- << ObjectFile->getHeader ().cpusubtype << " ) " ;
251
- else
252
- outs () << ObjectArch + " " ;
258
+ // Returns a string of the given Object file's architecture type
259
+ // Unknown architectures formatted unknown(CPUType,CPUSubType) for compatibility
260
+ // with cctools lipo
261
+ static std::string getArchString (const MachOObjectFile &ObjectFile) {
262
+ const Triple T = ObjectFile.getArchTriple ();
263
+ const StringRef ObjectArch = T.getArchName ();
264
+ if (!ObjectArch.empty ())
265
+ return ObjectArch;
266
+ return (" unknown(" + Twine (ObjectFile.getHeader ().cputype ) + " ," +
267
+ Twine (ObjectFile.getHeader ().cpusubtype & ~MachO::CPU_SUBTYPE_MASK) +
268
+ " )" )
269
+ .str ();
253
270
}
254
271
255
272
LLVM_ATTRIBUTE_NORETURN
256
273
static void printArchs (ArrayRef<OwningBinary<Binary>> InputBinaries) {
274
+ // Prints trailing space for compatibility with cctools lipo.
257
275
assert (InputBinaries.size () == 1 && " Incorrect number of input binaries" );
258
276
const Binary *InputBinary = InputBinaries.front ().getBinary ();
259
277
if (auto UO = dyn_cast<MachOUniversalBinary>(InputBinary)) {
260
- for (MachOUniversalBinary::object_iterator I = UO->begin_objects (),
261
- E = UO->end_objects ();
262
- I != E; ++I) {
278
+ for (const auto &O : UO->objects ()) {
263
279
Expected<std::unique_ptr<MachOObjectFile>> BinaryOrError =
264
- I-> getAsObjectFile ();
280
+ O. getAsObjectFile ();
265
281
if (!BinaryOrError)
266
282
reportError (InputBinary->getFileName (), BinaryOrError.takeError ());
267
- printArchOrUnknown ( BinaryOrError.get ().get ());
283
+ outs () << getArchString (* BinaryOrError.get ().get ()) << " " ;
268
284
}
269
285
} else if (auto O = dyn_cast<MachOObjectFile>(InputBinary)) {
270
- printArchOrUnknown (O) ;
286
+ outs () << getArchString (*O) << " " ;
271
287
} else {
272
288
llvm_unreachable (" Unexpected binary format" );
273
289
}
@@ -314,6 +330,173 @@ static void extractSlice(ArrayRef<OwningBinary<Binary>> InputBinaries,
314
330
exit (EXIT_SUCCESS);
315
331
}
316
332
333
+ static void checkArchDuplicates (const ArrayRef<Slice> &Slices) {
334
+ DenseMap<uint64_t , const MachOObjectFile *> CPUIds;
335
+ auto CPUIDForSlice = [](const Slice &S) {
336
+ return static_cast <uint64_t >(S.ObjectFile ->getHeader ().cputype ) << 32 |
337
+ S.ObjectFile ->getHeader ().cpusubtype ;
338
+ };
339
+ for (const auto &S : Slices) {
340
+ auto Entry = CPUIds.try_emplace (CPUIDForSlice (S), S.ObjectFile );
341
+ if (!Entry.second )
342
+ reportError (Entry.first ->second ->getFileName () + " and " +
343
+ S.ObjectFile ->getFileName () + " have the same architecture " +
344
+ getArchString (*S.ObjectFile ) +
345
+ " and therefore cannot be in the same universal binary" );
346
+ }
347
+ }
348
+
349
+ static uint32_t calculateAlignment (const MachOObjectFile *ObjectFile) {
350
+ // TODO: Implement getAlign() and remove hard coding
351
+ // Will be implemented in a follow-up.
352
+
353
+ switch (ObjectFile->getHeader ().cputype ) {
354
+ case MachO::CPU_TYPE_I386:
355
+ case MachO::CPU_TYPE_X86_64:
356
+ case MachO::CPU_TYPE_POWERPC:
357
+ case MachO::CPU_TYPE_POWERPC64:
358
+ return 12 ; // log2 value of page size(4k) for x86 and PPC
359
+ case MachO::CPU_TYPE_ARM:
360
+ case MachO::CPU_TYPE_ARM64:
361
+ case MachO::CPU_TYPE_ARM64_32:
362
+ return 14 ; // log2 value of page size(16k) for Darwin ARM
363
+ default :
364
+ return 12 ;
365
+ }
366
+ }
367
+
368
+ // This function replicates ordering from cctools lipo for consistency
369
+ static bool compareSlices (const Slice &Lhs, const Slice &Rhs) {
370
+ if (Lhs.ObjectFile ->getHeader ().cputype ==
371
+ Rhs.ObjectFile ->getHeader ().cputype )
372
+ return Lhs.ObjectFile ->getHeader ().cpusubtype <
373
+ Rhs.ObjectFile ->getHeader ().cpusubtype ;
374
+
375
+ // force arm64-family to follow after all other slices for compatibility
376
+ // with cctools lipo
377
+ if (Lhs.ObjectFile ->getHeader ().cputype == MachO::CPU_TYPE_ARM64)
378
+ return false ;
379
+ if (Rhs.ObjectFile ->getHeader ().cputype == MachO::CPU_TYPE_ARM64)
380
+ return true ;
381
+
382
+ // Sort by alignment to minimize file size
383
+ return Lhs.Alignment < Rhs.Alignment ;
384
+ }
385
+
386
+ // Updates vector ExtractedObjects with the MachOObjectFiles extracted from
387
+ // Universal Binary files to transfer ownership.
388
+ static SmallVector<Slice, 2 > buildSlices (
389
+ ArrayRef<OwningBinary<Binary>> InputBinaries,
390
+ SmallVectorImpl<std::unique_ptr<MachOObjectFile>> &ExtractedObjects) {
391
+ SmallVector<Slice, 2 > Slices;
392
+ for (auto &IB : InputBinaries) {
393
+ const Binary *InputBinary = IB.getBinary ();
394
+ if (auto UO = dyn_cast<MachOUniversalBinary>(InputBinary)) {
395
+ for (const auto &O : UO->objects ()) {
396
+ Expected<std::unique_ptr<MachOObjectFile>> BinaryOrError =
397
+ O.getAsObjectFile ();
398
+ if (!BinaryOrError)
399
+ reportError (InputBinary->getFileName (), BinaryOrError.takeError ());
400
+ ExtractedObjects.push_back (std::move (BinaryOrError.get ()));
401
+ Slices.push_back (Slice{ExtractedObjects.back ().get (), O.getAlign ()});
402
+ }
403
+ } else if (auto O = dyn_cast<MachOObjectFile>(InputBinary)) {
404
+ Slices.push_back (Slice{O, calculateAlignment (O)});
405
+ } else {
406
+ llvm_unreachable (" Unexpected binary format" );
407
+ }
408
+ }
409
+ return Slices;
410
+ }
411
+
412
+ static SmallVector<MachO::fat_arch, 2 >
413
+ buildFatArchList (ArrayRef<Slice> Slices) {
414
+ SmallVector<MachO::fat_arch, 2 > FatArchList;
415
+ uint64_t Offset =
416
+ sizeof (MachO::fat_header) + Slices.size () * sizeof (MachO::fat_arch);
417
+
418
+ for (size_t Index = 0 , Size = Slices.size (); Index < Size ; ++Index) {
419
+ Offset = alignTo (Offset, 1 << Slices[Index].Alignment );
420
+ const MachOObjectFile *ObjectFile = Slices[Index].ObjectFile ;
421
+ if (Offset > UINT32_MAX)
422
+ reportError (" fat file too large to be created because the offset "
423
+ " field in struct fat_arch is only 32-bits and the offset " +
424
+ Twine (Offset) + " for " + ObjectFile->getFileName () +
425
+ " for architecture " + getArchString (*ObjectFile) +
426
+ " exceeds that." );
427
+
428
+ MachO::fat_arch FatArch;
429
+ FatArch.cputype = ObjectFile->getHeader ().cputype ;
430
+ FatArch.cpusubtype = ObjectFile->getHeader ().cpusubtype ;
431
+ FatArch.offset = Offset;
432
+ FatArch.size = ObjectFile->getMemoryBufferRef ().getBufferSize ();
433
+ FatArch.align = Slices[Index].Alignment ;
434
+ Offset += FatArch.size ;
435
+ FatArchList.push_back (FatArch);
436
+ }
437
+ return FatArchList;
438
+ }
439
+
440
+ static void createUniversalBinary (SmallVectorImpl<Slice> &Slices,
441
+ StringRef OutputFileName) {
442
+ MachO::fat_header FatHeader;
443
+ FatHeader.magic = MachO::FAT_MAGIC;
444
+ FatHeader.nfat_arch = Slices.size ();
445
+
446
+ stable_sort (Slices, compareSlices);
447
+ SmallVector<MachO::fat_arch, 2 > FatArchList = buildFatArchList (Slices);
448
+
449
+ const bool IsExecutable = any_of (Slices, [](Slice S) {
450
+ return sys::fs::can_execute (S.ObjectFile ->getFileName ());
451
+ });
452
+ const uint64_t OutputFileSize =
453
+ FatArchList.back ().offset + FatArchList.back ().size ;
454
+ Expected<std::unique_ptr<FileOutputBuffer>> OutFileOrError =
455
+ FileOutputBuffer::create (OutputFileName, OutputFileSize,
456
+ IsExecutable ? FileOutputBuffer::F_executable
457
+ : 0 );
458
+ if (!OutFileOrError)
459
+ reportError (OutputFileName, OutFileOrError.takeError ());
460
+ std::unique_ptr<FileOutputBuffer> OutFile = std::move (OutFileOrError.get ());
461
+ std::memset (OutFile->getBufferStart (), 0 , OutputFileSize);
462
+
463
+ if (sys::IsLittleEndianHost)
464
+ MachO::swapStruct (FatHeader);
465
+ std::memcpy (OutFile->getBufferStart (), &FatHeader, sizeof (MachO::fat_header));
466
+
467
+ for (size_t Index = 0 , Size = Slices.size (); Index < Size ; ++Index) {
468
+ MemoryBufferRef BufferRef = Slices[Index].ObjectFile ->getMemoryBufferRef ();
469
+ std::copy (BufferRef.getBufferStart (), BufferRef.getBufferEnd (),
470
+ OutFile->getBufferStart () + FatArchList[Index].offset );
471
+ }
472
+
473
+ // FatArchs written after Slices in order reduce the number of swaps for the
474
+ // LittleEndian case
475
+ if (sys::IsLittleEndianHost)
476
+ for (MachO::fat_arch &FA : FatArchList)
477
+ MachO::swapStruct (FA);
478
+ std::memcpy (OutFile->getBufferStart () + sizeof (MachO::fat_header),
479
+ FatArchList.begin (),
480
+ sizeof (MachO::fat_arch) * FatArchList.size ());
481
+
482
+ if (Error E = OutFile->commit ())
483
+ reportError (OutputFileName, std::move (E));
484
+ }
485
+
486
+ LLVM_ATTRIBUTE_NORETURN
487
+ static void createUniversalBinary (ArrayRef<OwningBinary<Binary>> InputBinaries,
488
+ StringRef OutputFileName) {
489
+ assert (InputBinaries.size () >= 1 && " Incorrect number of input binaries" );
490
+ assert (!OutputFileName.empty () && " Create expects a single output file" );
491
+
492
+ SmallVector<std::unique_ptr<MachOObjectFile>, 1 > ExtractedObjects;
493
+ SmallVector<Slice, 1 > Slices = buildSlices (InputBinaries, ExtractedObjects);
494
+ checkArchDuplicates (Slices);
495
+ createUniversalBinary (Slices, OutputFileName);
496
+
497
+ exit (EXIT_SUCCESS);
498
+ }
499
+
317
500
int main (int argc, char **argv) {
318
501
InitLLVM X (argc, argv);
319
502
Config C = parseLipoOptions (makeArrayRef (argv + 1 , argc));
@@ -330,6 +513,9 @@ int main(int argc, char **argv) {
330
513
case LipoAction::ThinArch:
331
514
extractSlice (InputBinaries, C.ThinArchType , C.OutputFile );
332
515
break ;
516
+ case LipoAction::CreateUniversal:
517
+ createUniversalBinary (InputBinaries, C.OutputFile );
518
+ break ;
333
519
}
334
520
return EXIT_SUCCESS;
335
521
}
0 commit comments