forked from llvm/llvm-project
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFunctionImport.cpp
2056 lines (1874 loc) · 86.6 KB
/
FunctionImport.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//===- FunctionImport.cpp - ThinLTO Summary-based Function Import ---------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//
//
// This file implements Function import based on summaries.
//
//===----------------------------------------------------------------------===//
#include "llvm/Transforms/IPO/FunctionImport.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/STLExtras.h"
#include "llvm/ADT/SetVector.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/Statistic.h"
#include "llvm/ADT/StringRef.h"
#include "llvm/Bitcode/BitcodeReader.h"
#include "llvm/IR/AutoUpgrade.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/GlobalAlias.h"
#include "llvm/IR/GlobalObject.h"
#include "llvm/IR/GlobalValue.h"
#include "llvm/IR/GlobalVariable.h"
#include "llvm/IR/Metadata.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/ModuleSummaryIndex.h"
#include "llvm/IRReader/IRReader.h"
#include "llvm/Linker/IRMover.h"
#include "llvm/ProfileData/PGOCtxProfReader.h"
#include "llvm/Support/Casting.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/Debug.h"
#include "llvm/Support/Errc.h"
#include "llvm/Support/Error.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/FileSystem.h"
#include "llvm/Support/JSON.h"
#include "llvm/Support/SourceMgr.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Transforms/IPO/Internalize.h"
#include "llvm/Transforms/Utils/Cloning.h"
#include "llvm/Transforms/Utils/FunctionImportUtils.h"
#include "llvm/Transforms/Utils/ValueMapper.h"
#include <cassert>
#include <memory>
#include <set>
#include <string>
#include <system_error>
#include <tuple>
#include <utility>
using namespace llvm;
#define DEBUG_TYPE "function-import"
STATISTIC(NumImportedFunctionsThinLink,
"Number of functions thin link decided to import");
STATISTIC(NumImportedHotFunctionsThinLink,
"Number of hot functions thin link decided to import");
STATISTIC(NumImportedCriticalFunctionsThinLink,
"Number of critical functions thin link decided to import");
STATISTIC(NumImportedGlobalVarsThinLink,
"Number of global variables thin link decided to import");
STATISTIC(NumImportedFunctions, "Number of functions imported in backend");
STATISTIC(NumImportedGlobalVars,
"Number of global variables imported in backend");
STATISTIC(NumImportedModules, "Number of modules imported from");
STATISTIC(NumDeadSymbols, "Number of dead stripped symbols in index");
STATISTIC(NumLiveSymbols, "Number of live symbols in index");
/// Limit on instruction count of imported functions.
static cl::opt<unsigned> ImportInstrLimit(
"import-instr-limit", cl::init(100), cl::Hidden, cl::value_desc("N"),
cl::desc("Only import functions with less than N instructions"));
static cl::opt<int> ImportCutoff(
"import-cutoff", cl::init(-1), cl::Hidden, cl::value_desc("N"),
cl::desc("Only import first N functions if N>=0 (default -1)"));
static cl::opt<bool>
ForceImportAll("force-import-all", cl::init(false), cl::Hidden,
cl::desc("Import functions with noinline attribute"));
static cl::opt<float>
ImportInstrFactor("import-instr-evolution-factor", cl::init(0.7),
cl::Hidden, cl::value_desc("x"),
cl::desc("As we import functions, multiply the "
"`import-instr-limit` threshold by this factor "
"before processing newly imported functions"));
static cl::opt<float> ImportHotInstrFactor(
"import-hot-evolution-factor", cl::init(1.0), cl::Hidden,
cl::value_desc("x"),
cl::desc("As we import functions called from hot callsite, multiply the "
"`import-instr-limit` threshold by this factor "
"before processing newly imported functions"));
static cl::opt<float> ImportHotMultiplier(
"import-hot-multiplier", cl::init(10.0), cl::Hidden, cl::value_desc("x"),
cl::desc("Multiply the `import-instr-limit` threshold for hot callsites"));
static cl::opt<float> ImportCriticalMultiplier(
"import-critical-multiplier", cl::init(100.0), cl::Hidden,
cl::value_desc("x"),
cl::desc(
"Multiply the `import-instr-limit` threshold for critical callsites"));
// FIXME: This multiplier was not really tuned up.
static cl::opt<float> ImportColdMultiplier(
"import-cold-multiplier", cl::init(0), cl::Hidden, cl::value_desc("N"),
cl::desc("Multiply the `import-instr-limit` threshold for cold callsites"));
static cl::opt<bool> PrintImports("print-imports", cl::init(false), cl::Hidden,
cl::desc("Print imported functions"));
static cl::opt<bool> PrintImportFailures(
"print-import-failures", cl::init(false), cl::Hidden,
cl::desc("Print information for functions rejected for importing"));
static cl::opt<bool> ComputeDead("compute-dead", cl::init(true), cl::Hidden,
cl::desc("Compute dead symbols"));
static cl::opt<bool> EnableImportMetadata(
"enable-import-metadata", cl::init(false), cl::Hidden,
cl::desc("Enable import metadata like 'thinlto_src_module' and "
"'thinlto_src_file'"));
/// Summary file to use for function importing when using -function-import from
/// the command line.
static cl::opt<std::string>
SummaryFile("summary-file",
cl::desc("The summary file to use for function importing."));
/// Used when testing importing from distributed indexes via opt
// -function-import.
static cl::opt<bool>
ImportAllIndex("import-all-index",
cl::desc("Import all external functions in index."));
/// This is a test-only option.
/// If this option is enabled, the ThinLTO indexing step will import each
/// function declaration as a fallback. In a real build this may increase ram
/// usage of the indexing step unnecessarily.
/// TODO: Implement selective import (based on combined summary analysis) to
/// ensure the imported function has a use case in the postlink pipeline.
static cl::opt<bool> ImportDeclaration(
"import-declaration", cl::init(false), cl::Hidden,
cl::desc("If true, import function declaration as fallback if the function "
"definition is not imported."));
/// Pass a workload description file - an example of workload would be the
/// functions executed to satisfy a RPC request. A workload is defined by a root
/// function and the list of functions that are (frequently) needed to satisfy
/// it. The module that defines the root will have all those functions imported.
/// The file contains a JSON dictionary. The keys are root functions, the values
/// are lists of functions to import in the module defining the root. It is
/// assumed -funique-internal-linkage-names was used, thus ensuring function
/// names are unique even for local linkage ones.
static cl::opt<std::string> WorkloadDefinitions(
"thinlto-workload-def",
cl::desc("Pass a workload definition. This is a file containing a JSON "
"dictionary. The keys are root functions, the values are lists of "
"functions to import in the module defining the root. It is "
"assumed -funique-internal-linkage-names was used, to ensure "
"local linkage functions have unique names. For example: \n"
"{\n"
" \"rootFunction_1\": [\"function_to_import_1\", "
"\"function_to_import_2\"], \n"
" \"rootFunction_2\": [\"function_to_import_3\", "
"\"function_to_import_4\"] \n"
"}"),
cl::Hidden);
extern cl::opt<std::string> UseCtxProfile;
namespace llvm {
extern cl::opt<bool> EnableMemProfContextDisambiguation;
}
// Load lazily a module from \p FileName in \p Context.
static std::unique_ptr<Module> loadFile(const std::string &FileName,
LLVMContext &Context) {
SMDiagnostic Err;
LLVM_DEBUG(dbgs() << "Loading '" << FileName << "'\n");
// Metadata isn't loaded until functions are imported, to minimize
// the memory overhead.
std::unique_ptr<Module> Result =
getLazyIRFileModule(FileName, Err, Context,
/* ShouldLazyLoadMetadata = */ true);
if (!Result) {
Err.print("function-import", errs());
report_fatal_error("Abort");
}
return Result;
}
static bool shouldSkipLocalInAnotherModule(const GlobalValueSummary *RefSummary,
size_t NumDefs,
StringRef ImporterModule) {
// We can import a local when there is one definition.
if (NumDefs == 1)
return false;
// In other cases, make sure we import the copy in the caller's module if the
// referenced value has local linkage. The only time a local variable can
// share an entry in the index is if there is a local with the same name in
// another module that had the same source file name (in a different
// directory), where each was compiled in their own directory so there was not
// distinguishing path.
return GlobalValue::isLocalLinkage(RefSummary->linkage()) &&
RefSummary->modulePath() != ImporterModule;
}
/// Given a list of possible callee implementation for a call site, qualify the
/// legality of importing each. The return is a range of pairs. Each pair
/// corresponds to a candidate. The first value is the ImportFailureReason for
/// that candidate, the second is the candidate.
static auto qualifyCalleeCandidates(
const ModuleSummaryIndex &Index,
ArrayRef<std::unique_ptr<GlobalValueSummary>> CalleeSummaryList,
StringRef CallerModulePath) {
return llvm::map_range(
CalleeSummaryList,
[&Index, CalleeSummaryList,
CallerModulePath](const std::unique_ptr<GlobalValueSummary> &SummaryPtr)
-> std::pair<FunctionImporter::ImportFailureReason,
const GlobalValueSummary *> {
auto *GVSummary = SummaryPtr.get();
if (!Index.isGlobalValueLive(GVSummary))
return {FunctionImporter::ImportFailureReason::NotLive, GVSummary};
if (GlobalValue::isInterposableLinkage(GVSummary->linkage()))
return {FunctionImporter::ImportFailureReason::InterposableLinkage,
GVSummary};
auto *Summary = dyn_cast<FunctionSummary>(GVSummary->getBaseObject());
// Ignore any callees that aren't actually functions. This could happen
// in the case of GUID hash collisions. It could also happen in theory
// for SamplePGO profiles collected on old versions of the code after
// renaming, since we synthesize edges to any inlined callees appearing
// in the profile.
if (!Summary)
return {FunctionImporter::ImportFailureReason::GlobalVar, GVSummary};
// If this is a local function, make sure we import the copy in the
// caller's module. The only time a local function can share an entry in
// the index is if there is a local with the same name in another module
// that had the same source file name (in a different directory), where
// each was compiled in their own directory so there was not
// distinguishing path.
// If the local function is from another module, it must be a reference
// due to indirect call profile data since a function pointer can point
// to a local in another module. Do the import from another module if
// there is only one entry in the list or when all files in the program
// are compiled with full path - in both cases the local function has
// unique PGO name and GUID.
if (shouldSkipLocalInAnotherModule(Summary, CalleeSummaryList.size(),
CallerModulePath))
return {
FunctionImporter::ImportFailureReason::LocalLinkageNotInModule,
GVSummary};
// Skip if it isn't legal to import (e.g. may reference unpromotable
// locals).
if (Summary->notEligibleToImport())
return {FunctionImporter::ImportFailureReason::NotEligible,
GVSummary};
return {FunctionImporter::ImportFailureReason::None, GVSummary};
});
}
/// Given a list of possible callee implementation for a call site, select one
/// that fits the \p Threshold for function definition import. If none are
/// found, the Reason will give the last reason for the failure (last, in the
/// order of CalleeSummaryList entries). While looking for a callee definition,
/// sets \p TooLargeOrNoInlineSummary to the last seen too-large or noinline
/// candidate; other modules may want to know the function summary or
/// declaration even if a definition is not needed.
///
/// FIXME: select "best" instead of first that fits. But what is "best"?
/// - The smallest: more likely to be inlined.
/// - The one with the least outgoing edges (already well optimized).
/// - One from a module already being imported from in order to reduce the
/// number of source modules parsed/linked.
/// - One that has PGO data attached.
/// - [insert you fancy metric here]
static const GlobalValueSummary *
selectCallee(const ModuleSummaryIndex &Index,
ArrayRef<std::unique_ptr<GlobalValueSummary>> CalleeSummaryList,
unsigned Threshold, StringRef CallerModulePath,
const GlobalValueSummary *&TooLargeOrNoInlineSummary,
FunctionImporter::ImportFailureReason &Reason) {
// Records the last summary with reason noinline or too-large.
TooLargeOrNoInlineSummary = nullptr;
auto QualifiedCandidates =
qualifyCalleeCandidates(Index, CalleeSummaryList, CallerModulePath);
for (auto QualifiedValue : QualifiedCandidates) {
Reason = QualifiedValue.first;
// Skip a summary if its import is not (proved to be) legal.
if (Reason != FunctionImporter::ImportFailureReason::None)
continue;
auto *Summary =
cast<FunctionSummary>(QualifiedValue.second->getBaseObject());
// Don't bother importing the definition if the chance of inlining it is
// not high enough (except under `--force-import-all`).
if ((Summary->instCount() > Threshold) && !Summary->fflags().AlwaysInline &&
!ForceImportAll) {
TooLargeOrNoInlineSummary = Summary;
Reason = FunctionImporter::ImportFailureReason::TooLarge;
continue;
}
// Don't bother importing the definition if we can't inline it anyway.
if (Summary->fflags().NoInline && !ForceImportAll) {
TooLargeOrNoInlineSummary = Summary;
Reason = FunctionImporter::ImportFailureReason::NoInline;
continue;
}
return Summary;
}
return nullptr;
}
namespace {
using EdgeInfo = std::tuple<const FunctionSummary *, unsigned /* Threshold */>;
} // anonymous namespace
FunctionImporter::ImportMapTy::AddDefinitionStatus
FunctionImporter::ImportMapTy::addDefinition(StringRef FromModule,
GlobalValue::GUID GUID) {
auto [It, Inserted] =
ImportMap[FromModule].try_emplace(GUID, GlobalValueSummary::Definition);
if (Inserted)
return AddDefinitionStatus::Inserted;
if (It->second == GlobalValueSummary::Definition)
return AddDefinitionStatus::NoChange;
It->second = GlobalValueSummary::Definition;
return AddDefinitionStatus::ChangedToDefinition;
}
void FunctionImporter::ImportMapTy::maybeAddDeclaration(
StringRef FromModule, GlobalValue::GUID GUID) {
ImportMap[FromModule].try_emplace(GUID, GlobalValueSummary::Declaration);
}
SmallVector<StringRef, 0>
FunctionImporter::ImportMapTy::getSourceModules() const {
SmallVector<StringRef, 0> Modules(make_first_range(ImportMap));
llvm::sort(Modules);
return Modules;
}
/// Import globals referenced by a function or other globals that are being
/// imported, if importing such global is possible.
class GlobalsImporter final {
const ModuleSummaryIndex &Index;
const GVSummaryMapTy &DefinedGVSummaries;
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing;
FunctionImporter::ImportMapTy &ImportList;
DenseMap<StringRef, FunctionImporter::ExportSetTy> *const ExportLists;
bool shouldImportGlobal(const ValueInfo &VI) {
const auto &GVS = DefinedGVSummaries.find(VI.getGUID());
if (GVS == DefinedGVSummaries.end())
return true;
// We should not skip import if the module contains a non-prevailing
// definition with interposable linkage type. This is required for
// correctness in the situation where there is a prevailing def available
// for import and marked read-only. In this case, the non-prevailing def
// will be converted to a declaration, while the prevailing one becomes
// internal, thus no definitions will be available for linking. In order to
// prevent undefined symbol link error, the prevailing definition must be
// imported.
// FIXME: Consider adding a check that the suitable prevailing definition
// exists and marked read-only.
if (VI.getSummaryList().size() > 1 &&
GlobalValue::isInterposableLinkage(GVS->second->linkage()) &&
!IsPrevailing(VI.getGUID(), GVS->second))
return true;
return false;
}
void
onImportingSummaryImpl(const GlobalValueSummary &Summary,
SmallVectorImpl<const GlobalVarSummary *> &Worklist) {
for (const auto &VI : Summary.refs()) {
if (!shouldImportGlobal(VI)) {
LLVM_DEBUG(
dbgs() << "Ref ignored! Target already in destination module.\n");
continue;
}
LLVM_DEBUG(dbgs() << " ref -> " << VI << "\n");
for (const auto &RefSummary : VI.getSummaryList()) {
const auto *GVS = dyn_cast<GlobalVarSummary>(RefSummary.get());
// Functions could be referenced by global vars - e.g. a vtable; but we
// don't currently imagine a reason those would be imported here, rather
// than as part of the logic deciding which functions to import (i.e.
// based on profile information). Should we decide to handle them here,
// we can refactor accordingly at that time.
if (!GVS || !Index.canImportGlobalVar(GVS, /* AnalyzeRefs */ true) ||
shouldSkipLocalInAnotherModule(GVS, VI.getSummaryList().size(),
Summary.modulePath()))
continue;
// If there isn't an entry for GUID, insert <GUID, Definition> pair.
// Otherwise, definition should take precedence over declaration.
if (ImportList.addDefinition(RefSummary->modulePath(), VI.getGUID()) !=
FunctionImporter::ImportMapTy::AddDefinitionStatus::Inserted)
break;
// Only update stat and exports if we haven't already imported this
// variable.
NumImportedGlobalVarsThinLink++;
// Any references made by this variable will be marked exported
// later, in ComputeCrossModuleImport, after import decisions are
// complete, which is more efficient than adding them here.
if (ExportLists)
(*ExportLists)[RefSummary->modulePath()].insert(VI);
// If variable is not writeonly we attempt to recursively analyze
// its references in order to import referenced constants.
if (!Index.isWriteOnly(GVS))
Worklist.emplace_back(GVS);
break;
}
}
}
public:
GlobalsImporter(
const ModuleSummaryIndex &Index, const GVSummaryMapTy &DefinedGVSummaries,
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing,
FunctionImporter::ImportMapTy &ImportList,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists)
: Index(Index), DefinedGVSummaries(DefinedGVSummaries),
IsPrevailing(IsPrevailing), ImportList(ImportList),
ExportLists(ExportLists) {}
void onImportingSummary(const GlobalValueSummary &Summary) {
SmallVector<const GlobalVarSummary *, 128> Worklist;
onImportingSummaryImpl(Summary, Worklist);
while (!Worklist.empty())
onImportingSummaryImpl(*Worklist.pop_back_val(), Worklist);
}
};
static const char *getFailureName(FunctionImporter::ImportFailureReason Reason);
/// Determine the list of imports and exports for each module.
class ModuleImportsManager {
protected:
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing;
const ModuleSummaryIndex &Index;
DenseMap<StringRef, FunctionImporter::ExportSetTy> *const ExportLists;
ModuleImportsManager(
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing,
const ModuleSummaryIndex &Index,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists = nullptr)
: IsPrevailing(IsPrevailing), Index(Index), ExportLists(ExportLists) {}
public:
virtual ~ModuleImportsManager() = default;
/// Given the list of globals defined in a module, compute the list of imports
/// as well as the list of "exports", i.e. the list of symbols referenced from
/// another module (that may require promotion).
virtual void
computeImportForModule(const GVSummaryMapTy &DefinedGVSummaries,
StringRef ModName,
FunctionImporter::ImportMapTy &ImportList);
static std::unique_ptr<ModuleImportsManager>
create(function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing,
const ModuleSummaryIndex &Index,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists =
nullptr);
};
/// A ModuleImportsManager that operates based on a workload definition (see
/// -thinlto-workload-def). For modules that do not define workload roots, it
/// applies the base ModuleImportsManager import policy.
class WorkloadImportsManager : public ModuleImportsManager {
// Keep a module name -> value infos to import association. We use it to
// determine if a module's import list should be done by the base
// ModuleImportsManager or by us.
StringMap<DenseSet<ValueInfo>> Workloads;
void
computeImportForModule(const GVSummaryMapTy &DefinedGVSummaries,
StringRef ModName,
FunctionImporter::ImportMapTy &ImportList) override {
auto SetIter = Workloads.find(ModName);
if (SetIter == Workloads.end()) {
LLVM_DEBUG(dbgs() << "[Workload] " << ModName
<< " does not contain the root of any context.\n");
return ModuleImportsManager::computeImportForModule(DefinedGVSummaries,
ModName, ImportList);
}
LLVM_DEBUG(dbgs() << "[Workload] " << ModName
<< " contains the root(s) of context(s).\n");
GlobalsImporter GVI(Index, DefinedGVSummaries, IsPrevailing, ImportList,
ExportLists);
auto &ValueInfos = SetIter->second;
SmallVector<EdgeInfo, 128> GlobWorklist;
for (auto &VI : llvm::make_early_inc_range(ValueInfos)) {
auto It = DefinedGVSummaries.find(VI.getGUID());
if (It != DefinedGVSummaries.end() &&
IsPrevailing(VI.getGUID(), It->second)) {
LLVM_DEBUG(
dbgs() << "[Workload] " << VI.name()
<< " has the prevailing variant already in the module "
<< ModName << ". No need to import\n");
continue;
}
auto Candidates =
qualifyCalleeCandidates(Index, VI.getSummaryList(), ModName);
const GlobalValueSummary *GVS = nullptr;
auto PotentialCandidates = llvm::map_range(
llvm::make_filter_range(
Candidates,
[&](const auto &Candidate) {
LLVM_DEBUG(dbgs() << "[Workflow] Candidate for " << VI.name()
<< " from " << Candidate.second->modulePath()
<< " ImportFailureReason: "
<< getFailureName(Candidate.first) << "\n");
return Candidate.first ==
FunctionImporter::ImportFailureReason::None;
}),
[](const auto &Candidate) { return Candidate.second; });
if (PotentialCandidates.empty()) {
LLVM_DEBUG(dbgs() << "[Workload] Not importing " << VI.name()
<< " because can't find eligible Callee. Guid is: "
<< Function::getGUID(VI.name()) << "\n");
continue;
}
/// We will prefer importing the prevailing candidate, if not, we'll
/// still pick the first available candidate. The reason we want to make
/// sure we do import the prevailing candidate is because the goal of
/// workload-awareness is to enable optimizations specializing the call
/// graph of that workload. Suppose a function is already defined in the
/// module, but it's not the prevailing variant. Suppose also we do not
/// inline it (in fact, if it were interposable, we can't inline it),
/// but we could specialize it to the workload in other ways. However,
/// the linker would drop it in the favor of the prevailing copy.
/// Instead, by importing the prevailing variant (assuming also the use
/// of `-avail-extern-to-local`), we keep the specialization. We could
/// alteranatively make the non-prevailing variant local, but the
/// prevailing one is also the one for which we would have previously
/// collected profiles, making it preferrable.
auto PrevailingCandidates = llvm::make_filter_range(
PotentialCandidates, [&](const auto *Candidate) {
return IsPrevailing(VI.getGUID(), Candidate);
});
if (PrevailingCandidates.empty()) {
GVS = *PotentialCandidates.begin();
if (!llvm::hasSingleElement(PotentialCandidates) &&
GlobalValue::isLocalLinkage(GVS->linkage()))
LLVM_DEBUG(
dbgs()
<< "[Workload] Found multiple non-prevailing candidates for "
<< VI.name()
<< ". This is unexpected. Are module paths passed to the "
"compiler unique for the modules passed to the linker?");
// We could in theory have multiple (interposable) copies of a symbol
// when there is no prevailing candidate, if say the prevailing copy was
// in a native object being linked in. However, we should in theory be
// marking all of these non-prevailing IR copies dead in that case, in
// which case they won't be candidates.
assert(GVS->isLive());
} else {
assert(llvm::hasSingleElement(PrevailingCandidates));
GVS = *PrevailingCandidates.begin();
}
auto ExportingModule = GVS->modulePath();
// We checked that for the prevailing case, but if we happen to have for
// example an internal that's defined in this module, it'd have no
// PrevailingCandidates.
if (ExportingModule == ModName) {
LLVM_DEBUG(dbgs() << "[Workload] Not importing " << VI.name()
<< " because its defining module is the same as the "
"current module\n");
continue;
}
LLVM_DEBUG(dbgs() << "[Workload][Including]" << VI.name() << " from "
<< ExportingModule << " : "
<< Function::getGUID(VI.name()) << "\n");
ImportList.addDefinition(ExportingModule, VI.getGUID());
GVI.onImportingSummary(*GVS);
if (ExportLists)
(*ExportLists)[ExportingModule].insert(VI);
}
LLVM_DEBUG(dbgs() << "[Workload] Done\n");
}
void loadFromJson() {
// Since the workload def uses names, we need a quick lookup
// name->ValueInfo.
StringMap<ValueInfo> NameToValueInfo;
StringSet<> AmbiguousNames;
for (auto &I : Index) {
ValueInfo VI = Index.getValueInfo(I);
if (!NameToValueInfo.insert(std::make_pair(VI.name(), VI)).second)
LLVM_DEBUG(AmbiguousNames.insert(VI.name()));
}
auto DbgReportIfAmbiguous = [&](StringRef Name) {
LLVM_DEBUG(if (AmbiguousNames.count(Name) > 0) {
dbgs() << "[Workload] Function name " << Name
<< " present in the workload definition is ambiguous. Consider "
"compiling with -funique-internal-linkage-names.";
});
};
std::error_code EC;
auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(WorkloadDefinitions);
if (std::error_code EC = BufferOrErr.getError()) {
report_fatal_error("Failed to open context file");
return;
}
auto Buffer = std::move(BufferOrErr.get());
std::map<std::string, std::vector<std::string>> WorkloadDefs;
json::Path::Root NullRoot;
// The JSON is supposed to contain a dictionary matching the type of
// WorkloadDefs. For example:
// {
// "rootFunction_1": ["function_to_import_1", "function_to_import_2"],
// "rootFunction_2": ["function_to_import_3", "function_to_import_4"]
// }
auto Parsed = json::parse(Buffer->getBuffer());
if (!Parsed)
report_fatal_error(Parsed.takeError());
if (!json::fromJSON(*Parsed, WorkloadDefs, NullRoot))
report_fatal_error("Invalid thinlto contextual profile format.");
for (const auto &Workload : WorkloadDefs) {
const auto &Root = Workload.first;
DbgReportIfAmbiguous(Root);
LLVM_DEBUG(dbgs() << "[Workload] Root: " << Root << "\n");
const auto &AllCallees = Workload.second;
auto RootIt = NameToValueInfo.find(Root);
if (RootIt == NameToValueInfo.end()) {
LLVM_DEBUG(dbgs() << "[Workload] Root " << Root
<< " not found in this linkage unit.\n");
continue;
}
auto RootVI = RootIt->second;
if (RootVI.getSummaryList().size() != 1) {
LLVM_DEBUG(dbgs() << "[Workload] Root " << Root
<< " should have exactly one summary, but has "
<< RootVI.getSummaryList().size() << ". Skipping.\n");
continue;
}
StringRef RootDefiningModule =
RootVI.getSummaryList().front()->modulePath();
LLVM_DEBUG(dbgs() << "[Workload] Root defining module for " << Root
<< " is : " << RootDefiningModule << "\n");
auto &Set = Workloads[RootDefiningModule];
for (const auto &Callee : AllCallees) {
LLVM_DEBUG(dbgs() << "[Workload] " << Callee << "\n");
DbgReportIfAmbiguous(Callee);
auto ElemIt = NameToValueInfo.find(Callee);
if (ElemIt == NameToValueInfo.end()) {
LLVM_DEBUG(dbgs() << "[Workload] " << Callee << " not found\n");
continue;
}
Set.insert(ElemIt->second);
}
}
}
void loadFromCtxProf() {
std::error_code EC;
auto BufferOrErr = MemoryBuffer::getFileOrSTDIN(UseCtxProfile);
if (std::error_code EC = BufferOrErr.getError()) {
report_fatal_error("Failed to open contextual profile file");
return;
}
auto Buffer = std::move(BufferOrErr.get());
PGOCtxProfileReader Reader(Buffer->getBuffer());
auto Ctx = Reader.loadContexts();
if (!Ctx) {
report_fatal_error("Failed to parse contextual profiles");
return;
}
const auto &CtxMap = *Ctx;
DenseSet<GlobalValue::GUID> ContainedGUIDs;
for (const auto &[RootGuid, Root] : CtxMap) {
// Avoid ContainedGUIDs to get in/out of scope. Reuse its memory for
// subsequent roots, but clear its contents.
ContainedGUIDs.clear();
auto RootVI = Index.getValueInfo(RootGuid);
if (!RootVI) {
LLVM_DEBUG(dbgs() << "[Workload] Root " << RootGuid
<< " not found in this linkage unit.\n");
continue;
}
if (RootVI.getSummaryList().size() != 1) {
LLVM_DEBUG(dbgs() << "[Workload] Root " << RootGuid
<< " should have exactly one summary, but has "
<< RootVI.getSummaryList().size() << ". Skipping.\n");
continue;
}
StringRef RootDefiningModule =
RootVI.getSummaryList().front()->modulePath();
LLVM_DEBUG(dbgs() << "[Workload] Root defining module for " << RootGuid
<< " is : " << RootDefiningModule << "\n");
auto &Set = Workloads[RootDefiningModule];
Root.getContainedGuids(ContainedGUIDs);
for (auto Guid : ContainedGUIDs)
if (auto VI = Index.getValueInfo(Guid))
Set.insert(VI);
}
}
public:
WorkloadImportsManager(
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing,
const ModuleSummaryIndex &Index,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists)
: ModuleImportsManager(IsPrevailing, Index, ExportLists) {
if (UseCtxProfile.empty() == WorkloadDefinitions.empty()) {
report_fatal_error(
"Pass only one of: -thinlto-pgo-ctx-prof or -thinlto-workload-def");
return;
}
if (!UseCtxProfile.empty())
loadFromCtxProf();
else
loadFromJson();
LLVM_DEBUG({
for (const auto &[Root, Set] : Workloads) {
dbgs() << "[Workload] Root: " << Root << " we have " << Set.size()
<< " distinct callees.\n";
for (const auto &VI : Set) {
dbgs() << "[Workload] Root: " << Root
<< " Would include: " << VI.getGUID() << "\n";
}
}
});
}
};
std::unique_ptr<ModuleImportsManager> ModuleImportsManager::create(
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
IsPrevailing,
const ModuleSummaryIndex &Index,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists) {
if (WorkloadDefinitions.empty() && UseCtxProfile.empty()) {
LLVM_DEBUG(dbgs() << "[Workload] Using the regular imports manager.\n");
return std::unique_ptr<ModuleImportsManager>(
new ModuleImportsManager(IsPrevailing, Index, ExportLists));
}
LLVM_DEBUG(dbgs() << "[Workload] Using the contextual imports manager.\n");
return std::make_unique<WorkloadImportsManager>(IsPrevailing, Index,
ExportLists);
}
static const char *
getFailureName(FunctionImporter::ImportFailureReason Reason) {
switch (Reason) {
case FunctionImporter::ImportFailureReason::None:
return "None";
case FunctionImporter::ImportFailureReason::GlobalVar:
return "GlobalVar";
case FunctionImporter::ImportFailureReason::NotLive:
return "NotLive";
case FunctionImporter::ImportFailureReason::TooLarge:
return "TooLarge";
case FunctionImporter::ImportFailureReason::InterposableLinkage:
return "InterposableLinkage";
case FunctionImporter::ImportFailureReason::LocalLinkageNotInModule:
return "LocalLinkageNotInModule";
case FunctionImporter::ImportFailureReason::NotEligible:
return "NotEligible";
case FunctionImporter::ImportFailureReason::NoInline:
return "NoInline";
}
llvm_unreachable("invalid reason");
}
/// Compute the list of functions to import for a given caller. Mark these
/// imported functions and the symbols they reference in their source module as
/// exported from their source module.
static void computeImportForFunction(
const FunctionSummary &Summary, const ModuleSummaryIndex &Index,
const unsigned Threshold, const GVSummaryMapTy &DefinedGVSummaries,
function_ref<bool(GlobalValue::GUID, const GlobalValueSummary *)>
isPrevailing,
SmallVectorImpl<EdgeInfo> &Worklist, GlobalsImporter &GVImporter,
FunctionImporter::ImportMapTy &ImportList,
DenseMap<StringRef, FunctionImporter::ExportSetTy> *ExportLists,
FunctionImporter::ImportThresholdsTy &ImportThresholds) {
GVImporter.onImportingSummary(Summary);
static int ImportCount = 0;
for (const auto &Edge : Summary.calls()) {
ValueInfo VI = Edge.first;
LLVM_DEBUG(dbgs() << " edge -> " << VI << " Threshold:" << Threshold
<< "\n");
if (ImportCutoff >= 0 && ImportCount >= ImportCutoff) {
LLVM_DEBUG(dbgs() << "ignored! import-cutoff value of " << ImportCutoff
<< " reached.\n");
continue;
}
if (DefinedGVSummaries.count(VI.getGUID())) {
// FIXME: Consider not skipping import if the module contains
// a non-prevailing def with interposable linkage. The prevailing copy
// can safely be imported (see shouldImportGlobal()).
LLVM_DEBUG(dbgs() << "ignored! Target already in destination module.\n");
continue;
}
auto GetBonusMultiplier = [](CalleeInfo::HotnessType Hotness) -> float {
if (Hotness == CalleeInfo::HotnessType::Hot)
return ImportHotMultiplier;
if (Hotness == CalleeInfo::HotnessType::Cold)
return ImportColdMultiplier;
if (Hotness == CalleeInfo::HotnessType::Critical)
return ImportCriticalMultiplier;
return 1.0;
};
const auto NewThreshold =
Threshold * GetBonusMultiplier(Edge.second.getHotness());
auto IT = ImportThresholds.insert(std::make_pair(
VI.getGUID(), std::make_tuple(NewThreshold, nullptr, nullptr)));
bool PreviouslyVisited = !IT.second;
auto &ProcessedThreshold = std::get<0>(IT.first->second);
auto &CalleeSummary = std::get<1>(IT.first->second);
auto &FailureInfo = std::get<2>(IT.first->second);
bool IsHotCallsite =
Edge.second.getHotness() == CalleeInfo::HotnessType::Hot;
bool IsCriticalCallsite =
Edge.second.getHotness() == CalleeInfo::HotnessType::Critical;
const FunctionSummary *ResolvedCalleeSummary = nullptr;
if (CalleeSummary) {
assert(PreviouslyVisited);
// Since the traversal of the call graph is DFS, we can revisit a function
// a second time with a higher threshold. In this case, it is added back
// to the worklist with the new threshold (so that its own callee chains
// can be considered with the higher threshold).
if (NewThreshold <= ProcessedThreshold) {
LLVM_DEBUG(
dbgs() << "ignored! Target was already imported with Threshold "
<< ProcessedThreshold << "\n");
continue;
}
// Update with new larger threshold.
ProcessedThreshold = NewThreshold;
ResolvedCalleeSummary = cast<FunctionSummary>(CalleeSummary);
} else {
// If we already rejected importing a callee at the same or higher
// threshold, don't waste time calling selectCallee.
if (PreviouslyVisited && NewThreshold <= ProcessedThreshold) {
LLVM_DEBUG(
dbgs() << "ignored! Target was already rejected with Threshold "
<< ProcessedThreshold << "\n");
if (PrintImportFailures) {
assert(FailureInfo &&
"Expected FailureInfo for previously rejected candidate");
FailureInfo->Attempts++;
}
continue;
}
FunctionImporter::ImportFailureReason Reason{};
// `SummaryForDeclImport` is an summary eligible for declaration import.
const GlobalValueSummary *SummaryForDeclImport = nullptr;
CalleeSummary =
selectCallee(Index, VI.getSummaryList(), NewThreshold,
Summary.modulePath(), SummaryForDeclImport, Reason);
if (!CalleeSummary) {
// There isn't a callee for definition import but one for declaration
// import.
if (ImportDeclaration && SummaryForDeclImport) {
StringRef DeclSourceModule = SummaryForDeclImport->modulePath();
// Note `ExportLists` only keeps track of exports due to imported
// definitions.
ImportList.maybeAddDeclaration(DeclSourceModule, VI.getGUID());
}
// Update with new larger threshold if this was a retry (otherwise
// we would have already inserted with NewThreshold above). Also
// update failure info if requested.
if (PreviouslyVisited) {
ProcessedThreshold = NewThreshold;
if (PrintImportFailures) {
assert(FailureInfo &&
"Expected FailureInfo for previously rejected candidate");
FailureInfo->Reason = Reason;
FailureInfo->Attempts++;
FailureInfo->MaxHotness =
std::max(FailureInfo->MaxHotness, Edge.second.getHotness());
}
} else if (PrintImportFailures) {
assert(!FailureInfo &&
"Expected no FailureInfo for newly rejected candidate");
FailureInfo = std::make_unique<FunctionImporter::ImportFailureInfo>(
VI, Edge.second.getHotness(), Reason, 1);
}
if (ForceImportAll) {
std::string Msg = std::string("Failed to import function ") +
VI.name().str() + " due to " +
getFailureName(Reason);
auto Error = make_error<StringError>(
Msg, make_error_code(errc::not_supported));
logAllUnhandledErrors(std::move(Error), errs(),
"Error importing module: ");
break;
} else {
LLVM_DEBUG(dbgs()
<< "ignored! No qualifying callee with summary found.\n");
continue;
}
}
// "Resolve" the summary
CalleeSummary = CalleeSummary->getBaseObject();
ResolvedCalleeSummary = cast<FunctionSummary>(CalleeSummary);
assert((ResolvedCalleeSummary->fflags().AlwaysInline || ForceImportAll ||
(ResolvedCalleeSummary->instCount() <= NewThreshold)) &&
"selectCallee() didn't honor the threshold");
auto ExportModulePath = ResolvedCalleeSummary->modulePath();
// Try emplace the definition entry, and update stats based on insertion
// status.
if (ImportList.addDefinition(ExportModulePath, VI.getGUID()) !=
FunctionImporter::ImportMapTy::AddDefinitionStatus::NoChange) {
NumImportedFunctionsThinLink++;
if (IsHotCallsite)
NumImportedHotFunctionsThinLink++;
if (IsCriticalCallsite)
NumImportedCriticalFunctionsThinLink++;
}
// Any calls/references made by this function will be marked exported
// later, in ComputeCrossModuleImport, after import decisions are
// complete, which is more efficient than adding them here.
if (ExportLists)
(*ExportLists)[ExportModulePath].insert(VI);
}
auto GetAdjustedThreshold = [](unsigned Threshold, bool IsHotCallsite) {
// Adjust the threshold for next level of imported functions.
// The threshold is different for hot callsites because we can then
// inline chains of hot calls.
if (IsHotCallsite)
return Threshold * ImportHotInstrFactor;
return Threshold * ImportInstrFactor;
};
const auto AdjThreshold = GetAdjustedThreshold(Threshold, IsHotCallsite);
ImportCount++;
// Insert the newly imported function to the worklist.
Worklist.emplace_back(ResolvedCalleeSummary, AdjThreshold);
}
}
void ModuleImportsManager::computeImportForModule(
const GVSummaryMapTy &DefinedGVSummaries, StringRef ModName,
FunctionImporter::ImportMapTy &ImportList) {
// Worklist contains the list of function imported in this module, for which
// we will analyse the callees and may import further down the callgraph.
SmallVector<EdgeInfo, 128> Worklist;
GlobalsImporter GVI(Index, DefinedGVSummaries, IsPrevailing, ImportList,
ExportLists);
FunctionImporter::ImportThresholdsTy ImportThresholds;
// Populate the worklist with the import for the functions in the current
// module