44
44
#include " llvm/Support/FileSystem.h"
45
45
#include " llvm/Support/Parallel.h"
46
46
#include " llvm/Support/Path.h"
47
+ #include " llvm/Support/Process.h"
47
48
#include " llvm/Support/TarWriter.h"
48
49
#include " llvm/Support/TargetSelect.h"
50
+ #include " llvm/Support/Threading.h"
49
51
#include " llvm/Support/TimeProfiler.h"
50
52
#include " llvm/TargetParser/Host.h"
51
53
#include " llvm/TextAPI/Architecture.h"
@@ -282,11 +284,117 @@ static void saveThinArchiveToRepro(ArchiveFile const *file) {
282
284
" : Archive::children failed: " + toString (std::move (e)));
283
285
}
284
286
285
- static InputFile *addFile (StringRef path, LoadType loadType,
286
- bool isLazy = false , bool isExplicit = true ,
287
- bool isBundleLoader = false ,
288
- bool isForceHidden = false ) {
289
- std::optional<MemoryBufferRef> buffer = readFile (path);
287
+ struct DeferredFile {
288
+ StringRef path;
289
+ bool isLazy;
290
+ MemoryBufferRef buffer;
291
+ };
292
+ using DeferredFiles = std::vector<DeferredFile>;
293
+
294
+ class SerialBackgroundQueue {
295
+ std::deque<std::function<void ()>> queue;
296
+ std::thread *running;
297
+ std::mutex mutex;
298
+
299
+ public:
300
+ void queueWork (std::function<void ()> work) {
301
+ mutex.lock ();
302
+ if (running && queue.empty ()) {
303
+ mutex.unlock ();
304
+ running->join ();
305
+ mutex.lock ();
306
+ delete running;
307
+ running = nullptr ;
308
+ }
309
+
310
+ if (work) {
311
+ queue.emplace_back (std::move (work));
312
+ if (!running)
313
+ running = new std::thread ([&]() {
314
+ while (true ) {
315
+ mutex.lock ();
316
+ if (queue.empty ()) {
317
+ mutex.unlock ();
318
+ break ;
319
+ }
320
+ auto work = std::move (queue.front ());
321
+ mutex.unlock ();
322
+ work ();
323
+ mutex.lock ();
324
+ queue.pop_front ();
325
+ mutex.unlock ();
326
+ }
327
+ });
328
+ }
329
+ mutex.unlock ();
330
+ }
331
+ };
332
+
333
+ // Most input files have been mapped but not yet paged in.
334
+ // This code forces the page-ins on multiple threads so
335
+ // the process is not stalled waiting on disk buffer i/o.
336
+ void multiThreadedPageInBackground (DeferredFiles &deferred) {
337
+ static const size_t pageSize = Process::getPageSizeEstimate ();
338
+ static const size_t largeArchive = 10 * 1024 * 1024 ;
339
+ #ifndef NDEBUG
340
+ using namespace std ::chrono;
341
+ std::atomic_int numDeferedFilesTouched = 0 ;
342
+ static std::atomic_uint64_t totalBytes = 0 ;
343
+ auto t0 = high_resolution_clock::now ();
344
+ #endif
345
+
346
+ auto preloadDeferredFile = [&](const DeferredFile &deferredFile) {
347
+ const StringRef &buff = deferredFile.buffer .getBuffer ();
348
+ if (buff.size () > largeArchive)
349
+ return ;
350
+ #ifndef NDEBUG
351
+ totalBytes += buff.size ();
352
+ numDeferedFilesTouched += 1 ;
353
+ #endif
354
+
355
+ // Reference all file's mmap'd pages to load them into memory.
356
+ for (const char *page = buff.data (), *end = page + buff.size (); page < end;
357
+ page += pageSize)
358
+ LLVM_ATTRIBUTE_UNUSED volatile char t = *page;
359
+ };
360
+ #if LLVM_ENABLE_THREADS
361
+ { // Create scope for waiting for the taskGroup
362
+ std::atomic_size_t index = 0 ;
363
+ llvm::parallel::TaskGroup taskGroup;
364
+ for (int w = 0 ; w < config->readWorkers ; w++)
365
+ taskGroup.spawn ([&index, &preloadDeferredFile, &deferred]() {
366
+ while (true ) {
367
+ size_t localIndex = index.fetch_add (1 );
368
+ if (localIndex >= deferred.size ())
369
+ break ;
370
+ preloadDeferredFile (deferred[localIndex]);
371
+ }
372
+ });
373
+ }
374
+ #endif
375
+ #ifndef NDEBUG
376
+ auto dt = high_resolution_clock::now () - t0;
377
+ if (Process::GetEnv (" LLD_MULTI_THREAD_PAGE" ))
378
+ llvm::dbgs () << " multiThreadedPageIn " << totalBytes << " /"
379
+ << numDeferedFilesTouched << " /" << deferred.size () << " /"
380
+ << duration_cast<milliseconds>(dt).count () / 1000 . << " \n " ;
381
+ #endif
382
+ }
383
+
384
+ static void multiThreadedPageIn (const DeferredFiles &deferred) {
385
+ static SerialBackgroundQueue pageInQueue;
386
+ pageInQueue.queueWork ([=]() {
387
+ DeferredFiles files = deferred;
388
+ multiThreadedPageInBackground (files);
389
+ });
390
+ }
391
+
392
+ static InputFile *processFile (std::optional<MemoryBufferRef> buffer,
393
+ DeferredFiles *archiveContents, StringRef path,
394
+ LoadType loadType, bool isLazy = false ,
395
+ bool isExplicit = true ,
396
+ bool isBundleLoader = false ,
397
+ bool isForceHidden = false ) {
290
398
if (!buffer)
291
399
return nullptr ;
292
400
MemoryBufferRef mbref = *buffer;
@@ -379,6 +487,8 @@ static InputFile *addFile(StringRef path, LoadType loadType,
379
487
continue ;
380
488
}
381
489
490
+ if (archiveContents)
491
+ archiveContents->push_back ({path, isLazy, *mb});
382
492
if (!hasObjCSection (*mb))
383
493
continue ;
384
494
if (Error e = file->fetch (c, " -ObjC" ))
@@ -390,7 +500,8 @@ static InputFile *addFile(StringRef path, LoadType loadType,
390
500
" : Archive::children failed: " + toString (std::move (e)));
391
501
}
392
502
}
393
- file->addLazySymbols ();
503
+ if (!archiveContents || archiveContents->empty ())
504
+ file->addLazySymbols ();
394
505
loadedArchives[path] = ArchiveFileInfo{file, isCommandLineLoad};
395
506
newFile = file;
396
507
break ;
@@ -441,6 +552,24 @@ static InputFile *addFile(StringRef path, LoadType loadType,
441
552
return newFile;
442
553
}
443
554
555
+ static InputFile *addFile (StringRef path, LoadType loadType,
556
+ bool isLazy = false , bool isExplicit = true ,
557
+ bool isBundleLoader = false ,
558
+ bool isForceHidden = false ) {
559
+ return processFile (readFile (path), nullptr , path, loadType, isLazy,
560
+ isExplicit, isBundleLoader, isForceHidden);
561
+ }
562
+
563
+ static void deferFile (StringRef path, bool isLazy, DeferredFiles &deferred) {
564
+ std::optional<MemoryBufferRef> buffer = readFile (path);
565
+ if (!buffer)
566
+ return ;
567
+ if (config->readWorkers )
568
+ deferred.push_back ({path, isLazy, *buffer});
569
+ else
570
+ processFile (buffer, nullptr , path, LoadType::CommandLine, isLazy);
571
+ }
572
+
444
573
static std::vector<StringRef> missingAutolinkWarnings;
445
574
static void addLibrary (StringRef name, bool isNeeded, bool isWeak,
446
575
bool isReexport, bool isHidden, bool isExplicit,
@@ -564,13 +693,14 @@ void macho::resolveLCLinkerOptions() {
564
693
}
565
694
}
566
695
567
- static void addFileList (StringRef path, bool isLazy) {
696
+ static void addFileList (StringRef path, bool isLazy,
697
+ DeferredFiles &deferredFiles) {
568
698
std::optional<MemoryBufferRef> buffer = readFile (path);
569
699
if (!buffer)
570
700
return ;
571
701
MemoryBufferRef mbref = *buffer;
572
702
for (StringRef path : args::getLines (mbref))
573
- addFile (rerootPath (path), LoadType::CommandLine, isLazy );
703
+ deferFile (rerootPath (path), isLazy, deferredFiles );
574
704
}
575
705
576
706
// We expect sub-library names of the form "libfoo", which will match a dylib
@@ -1222,14 +1352,16 @@ static void createFiles(const InputArgList &args) {
1222
1352
bool isLazy = false ;
1223
1353
// If we've processed an opening --start-lib, without a matching --end-lib
1224
1354
bool inLib = false ;
1355
+ DeferredFiles deferredFiles;
1356
+
1225
1357
for (const Arg *arg : args) {
1226
1358
const Option &opt = arg->getOption ();
1227
1359
warnIfDeprecatedOption (opt);
1228
1360
warnIfUnimplementedOption (opt);
1229
1361
1230
1362
switch (opt.getID ()) {
1231
1363
case OPT_INPUT:
1232
- addFile (rerootPath (arg->getValue ()), LoadType::CommandLine, isLazy );
1364
+ deferFile (rerootPath (arg->getValue ()), isLazy, deferredFiles );
1233
1365
break ;
1234
1366
case OPT_needed_library:
1235
1367
if (auto *dylibFile = dyn_cast_or_null<DylibFile>(
@@ -1249,7 +1381,7 @@ static void createFiles(const InputArgList &args) {
1249
1381
dylibFile->forceWeakImport = true ;
1250
1382
break ;
1251
1383
case OPT_filelist:
1252
- addFileList (arg->getValue (), isLazy);
1384
+ addFileList (arg->getValue (), isLazy, deferredFiles );
1253
1385
break ;
1254
1386
case OPT_force_load:
1255
1387
addFile (rerootPath (arg->getValue ()), LoadType::CommandLineForce);
@@ -1295,6 +1427,24 @@ static void createFiles(const InputArgList &args) {
1295
1427
break ;
1296
1428
}
1297
1429
}
1430
+
1431
+ if (config->readWorkers ) {
1432
+ multiThreadedPageIn (deferredFiles);
1433
+
1434
+ DeferredFiles archiveContents;
1435
+ std::vector<ArchiveFile *> archives;
1436
+ for (auto &file : deferredFiles) {
1437
+ auto inputFile = processFile (file.buffer , &archiveContents, file.path ,
1438
+ LoadType::CommandLine, file.isLazy );
1439
+ if (ArchiveFile *archive = dyn_cast<ArchiveFile>(inputFile))
1440
+ archives.push_back (archive);
1441
+ }
1442
+
1443
+ if (!archiveContents.empty ())
1444
+ multiThreadedPageIn (archiveContents);
1445
+ for (auto *archive : archives)
1446
+ archive->addLazySymbols ();
1447
+ }
1298
1448
}
1299
1449
1300
1450
static void gatherInputSections () {
@@ -1681,6 +1831,14 @@ bool link(ArrayRef<const char *> argsArr, llvm::raw_ostream &stdoutOS,
1681
1831
}
1682
1832
}
1683
1833
1834
+ if (auto *arg = args.getLastArg (OPT_read_workers)) {
1835
+ StringRef v (arg->getValue ());
1836
+ unsigned threads = 0 ;
1837
+ if (!llvm::to_integer (v, threads, 0 ) || threads < 0 )
1838
+ error (arg->getSpelling () + " : expected a positive integer, but got '" +
1839
+ arg->getValue () + " '" );
1840
+ config->readWorkers = threads;
1841
+ }
1684
1842
if (auto *arg = args.getLastArg (OPT_threads_eq)) {
1685
1843
StringRef v (arg->getValue ());
1686
1844
unsigned threads = 0 ;
0 commit comments