Skip to content

Commit 887b8da

Browse files
committed
[GlobalDCE] Add !llvm.used.conditional support for conditionally used global variables
As part of the optimization work to emit dead-strippable symbols for Swift and enable GlobalDCE to remove those symbols when possible, we need to mark some Swift symbols as conditionally used based on a set of other symbols. Background info can be found here: https://bugs.swift.org/browse/SR-14509. This diff adds a module-level name metadata !llvm.used.conditional, which contains a list of metadata triplets, that describes the conditions under which we allow removal of globals even if they are mentioned in @llvm.used. See the included LangRef changes to details. The implementation is a strict addition, it does not change any semantics and does not affect IR or passes in any way, unless !llvm.used.conditional is present in the input IR.
1 parent d4a606b commit 887b8da

16 files changed

+470
-8
lines changed

llvm/docs/LangRef.rst

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7304,6 +7304,55 @@ For example, the following metadata section contains two library specifiers::
73047304
Each library specifier will be handled independently by the consuming linker.
73057305
The effect of the library specifiers are defined by the consuming linker.
73067306

7307+
.. _llvm_used_conditional:
7308+
7309+
Conditionally Used Globals
7310+
==========================
7311+
7312+
When a global variable is mentioned in the `@llvm.used` list, it's always going
7313+
to be retained, but sometimes it's desirable to allow such a global variable to
7314+
still be discardable by optimization passes. Using the `!llvm.used.conditional`
7315+
named metadata allows expressing *when* it is legal to discard a global (from
7316+
the `@llvm.used` list) in terms of liveness of other globals.
7317+
7318+
If `!llvm.used.conditional` named metadata is present in IR of a module, it's
7319+
expected to be a list of metadata triplets. Each triplet has the following form:
7320+
7321+
1. The first element is the "target", the global variable from `@llvm.used` that
7322+
we are allowing removal of. If the global variable is not present in the
7323+
`@llvm.used` list, then there is no effect from using a entry in
7324+
`!llvm.used.conditional` for it.
7325+
2. The second element is a "type", a boolean flag expressing what behavior do
7326+
we want if the list of "dependencies" (third element) contains more than one
7327+
element. `0` means if *any dependency* in the list is alive, the target symbol
7328+
must stay alive, otherwise removal is allowed (in other words: if all globals
7329+
from the dependency list are removed, the target can be removed too). `1`
7330+
means if *all dependencies* in the list are alive, the target must stay
7331+
alive, otherwise removal is allowed (in other words: if any global from the
7332+
dependency list is removed, the target can be removed too).
7333+
3. The third element is a list of "dependencies", a list of globals.
7334+
7335+
The following example expresses that the global `@record` (which is listed in
7336+
`@llvm.used` otherwise it would be trivially discarded as nothing references it)
7337+
is allowed to be optimized away, if *either of* `@a` or `@b` globals are
7338+
themselves removable::
7339+
7340+
@record = internal global [...] {
7341+
@a, @b
7342+
}
7343+
@llvm.used = appending global [...] [ ..., @record ]
7344+
7345+
!1 = !{
7346+
@record, ; target
7347+
1, ; type
7348+
!{ @a, @b } ; dependencies
7349+
}
7350+
!llvm.used.conditional = !{ !1 }
7351+
7352+
The semantics of `!llvm.used.conditional` are only *allowing an optimization*,
7353+
and are not requiring optimizations to follow them --- it is correct for any
7354+
optimization/transformation to ignore `!llvm.used.conditional` or even drop it.
7355+
73077356
.. _summary:
73087357

73097358
ThinLTO Summary

llvm/include/llvm/Transforms/IPO/GlobalDCE.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@ class GlobalDCEPass : public PassInfoMixin<GlobalDCEPass> {
5656
void UpdateGVDependencies(GlobalValue &GV);
5757
void MarkLive(GlobalValue &GV,
5858
SmallVectorImpl<GlobalValue *> *Updates = nullptr);
59+
void PropagateLivenessInGlobalValues();
5960
bool RemoveUnusedGlobalValue(GlobalValue &GV);
6061

6162
// Dead virtual function elimination.
@@ -65,6 +66,9 @@ class GlobalDCEPass : public PassInfoMixin<GlobalDCEPass> {
6566
void ScanVTableLoad(Function *Caller, Metadata *TypeId, uint64_t CallOffset);
6667

6768
void ComputeDependencies(Value *V, SmallPtrSetImpl<GlobalValue *> &U);
69+
70+
GlobalValue *TargetFromConditionalUsedIfLive(MDNode *M);
71+
void PropagateLivenessToConditionallyUsed(Module &M);
6872
};
6973

7074
}

llvm/include/llvm/Transforms/Utils/ModuleUtils.h

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
#ifndef LLVM_TRANSFORMS_UTILS_MODULEUTILS_H
1414
#define LLVM_TRANSFORMS_UTILS_MODULEUTILS_H
1515

16+
#include "llvm/ADT/SmallPtrSet.h"
1617
#include "llvm/ADT/SmallVector.h"
1718
#include "llvm/ADT/StringRef.h"
1819
#include <utility> // for std::pair
@@ -24,6 +25,7 @@ class Module;
2425
class Function;
2526
class FunctionCallee;
2627
class GlobalValue;
28+
class GlobalVariable;
2729
class Constant;
2830
class Value;
2931
class Type;
@@ -78,6 +80,10 @@ void appendToUsed(Module &M, ArrayRef<GlobalValue *> Values);
7880
/// Adds global values to the llvm.compiler.used list.
7981
void appendToCompilerUsed(Module &M, ArrayRef<GlobalValue *> Values);
8082

83+
/// Replaces llvm.used or llvm.compiler.used list with a new set of values.
84+
GlobalVariable *setUsedInitializer(GlobalVariable &V,
85+
const SmallPtrSetImpl<GlobalValue *> &Init);
86+
8187
/// Filter out potentially dead comdat functions where other entries keep the
8288
/// entire comdat group alive.
8389
///

llvm/lib/IR/Verifier.cpp

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -875,6 +875,34 @@ void Verifier::visitNamedMDNode(const NamedMDNode &NMD) {
875875

876876
visitMDNode(*MD, AreDebugLocsAllowed::Yes);
877877
}
878+
879+
if (NMD.getName() == "llvm.used.conditional") {
880+
for (const MDNode *MD : NMD.operands()) {
881+
Assert(MD->getNumOperands() == 3, "invalid llvm.used.conditional member");
882+
auto *TargetMD = MD->getOperand(0).get();
883+
if (TargetMD != nullptr) {
884+
Assert(mdconst::dyn_extract<GlobalValue>(TargetMD),
885+
"invalid llvm.used.conditional member");
886+
}
887+
auto *TypeMD = mdconst::extract_or_null<ConstantInt>(MD->getOperand(1));
888+
int64_t Type = TypeMD->getValue().getSExtValue();
889+
Assert(Type == 0 || Type == 1, "invalid llvm.used.conditional member");
890+
auto *DependenciesMD = dyn_cast<MDNode>(MD->getOperand(2).get());
891+
Assert(DependenciesMD, "invalid llvm.used.conditional member");
892+
Assert(DependenciesMD->getNumOperands() > 0,
893+
"invalid llvm.used.conditional member");
894+
for (auto &DependencyMD : DependenciesMD->operands()) {
895+
auto *Dependency = DependencyMD.get();
896+
if (!Dependency)
897+
continue; // Allow null, skip.
898+
auto *C =
899+
mdconst::dyn_extract<Constant>(Dependency)->stripPointerCasts();
900+
if (dyn_cast<UndefValue>(C))
901+
continue; // Allow undef, skip.
902+
Assert(isa<GlobalValue>(C), "invalid llvm.used.conditional member");
903+
}
904+
}
905+
}
878906
}
879907

880908
void Verifier::visitMDNode(const MDNode &MD, AreDebugLocsAllowed AllowLocs) {

llvm/lib/Transforms/IPO/GlobalDCE.cpp

Lines changed: 153 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "llvm/Transforms/IPO.h"
2929
#include "llvm/Transforms/Utils/CtorUtils.h"
3030
#include "llvm/Transforms/Utils/GlobalStatus.h"
31+
#include "llvm/Transforms/Utils/ModuleUtils.h"
3132

3233
using namespace llvm;
3334

@@ -158,6 +159,18 @@ void GlobalDCEPass::MarkLive(GlobalValue &GV,
158159
}
159160
}
160161

162+
void GlobalDCEPass::PropagateLivenessInGlobalValues() {
163+
// Propagate liveness from collected Global Values through the computed
164+
// dependencies.
165+
SmallVector<GlobalValue *, 8> NewLiveGVs{AliveGlobals.begin(),
166+
AliveGlobals.end()};
167+
while (!NewLiveGVs.empty()) {
168+
GlobalValue *LGV = NewLiveGVs.pop_back_val();
169+
for (auto *GVD : GVDependencies[LGV])
170+
MarkLive(*GVD, &NewLiveGVs);
171+
}
172+
}
173+
161174
/// Recursively iterate over the (sub-)constants in the vtable and look for
162175
/// vptrs, if their offset is within [RangeStart..RangeEnd), add them to VFuncs.
163176
static void FindVirtualFunctionsInVTable(Module &M, Constant *C,
@@ -334,6 +347,135 @@ void GlobalDCEPass::AddVirtualFunctionDependencies(Module &M) {
334347
<< " " << Entry.first->getName() << "\n";);
335348
}
336349

350+
static bool RemoveConditionalTargetsFromUsedList(Module &M) {
351+
auto *Used = M.getGlobalVariable("llvm.used");
352+
if (!Used)
353+
return false;
354+
355+
auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
356+
if (!UsedConditional)
357+
return false;
358+
if (UsedConditional->getNumOperands() == 0)
359+
return false;
360+
361+
// Construct a set of conditionally used targets.
362+
SmallPtrSet<GlobalValue *, 8> Targets;
363+
for (auto *M : UsedConditional->operands()) {
364+
assert(M->getNumOperands() == 3);
365+
auto *V = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
366+
if (!V)
367+
continue;
368+
Targets.insert(V);
369+
}
370+
371+
if (Targets.empty())
372+
return false;
373+
374+
// Now remove all targets from @llvm.used.
375+
SmallPtrSet<GlobalValue *, 8> NewUsedArray;
376+
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
377+
for (Value *Op : UsedList->operands()) {
378+
GlobalValue *G = cast<GlobalValue>(Op->stripPointerCasts());
379+
if (Targets.contains(G))
380+
continue;
381+
NewUsedArray.insert(G);
382+
}
383+
Used = setUsedInitializer(*Used, NewUsedArray);
384+
return true;
385+
}
386+
387+
// Parse one entry from !llvm.used.conditional list as a triplet of
388+
// { target, type, dependencies } and evaluate the conditional dependency, i.e.
389+
// check liveness of all dependencies and based on type conclude whether the
390+
// target is supposed to be declared alive. If yes, return the target, otherwise
391+
// return nullptr.
392+
GlobalValue *GlobalDCEPass::TargetFromConditionalUsedIfLive(MDNode *M) {
393+
assert(M->getNumOperands() == 3);
394+
auto *Target = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
395+
if (!Target)
396+
return nullptr;
397+
398+
auto *DependenciesMD = dyn_cast_or_null<MDNode>(M->getOperand(2).get());
399+
SmallPtrSet<GlobalValue *, 8> Dependencies;
400+
if (DependenciesMD == nullptr) {
401+
Dependencies.insert(nullptr);
402+
} else {
403+
for (auto &DependencyMD : DependenciesMD->operands()) {
404+
auto *Dependency = DependencyMD.get();
405+
if (!Dependency)
406+
continue; // Allow null, skip.
407+
auto *C =
408+
mdconst::extract_or_null<Constant>(Dependency)->stripPointerCasts();
409+
if (dyn_cast<UndefValue>(C))
410+
continue; // Allow undef, skip.
411+
Dependencies.insert(cast<GlobalValue>(C));
412+
}
413+
}
414+
415+
bool AllDependenciesAlive = Dependencies.empty() ? false : true;
416+
bool AnyDependencyAlive = false;
417+
for (auto *Dep : Dependencies) {
418+
bool Live = AliveGlobals.count(Dep) != 0;
419+
if (Live)
420+
AnyDependencyAlive = true;
421+
else
422+
AllDependenciesAlive = false;
423+
}
424+
425+
auto *Type = mdconst::extract_or_null<ConstantInt>(M->getOperand(1));
426+
switch (Type->getValue().getSExtValue()) {
427+
case 0:
428+
return AnyDependencyAlive ? Target : nullptr;
429+
case 1:
430+
return AllDependenciesAlive ? Target : nullptr;
431+
default:
432+
llvm_unreachable("bad !llvm.used.conditional type");
433+
}
434+
}
435+
436+
void GlobalDCEPass::PropagateLivenessToConditionallyUsed(Module &M) {
437+
auto *Used = M.getGlobalVariable("llvm.used");
438+
if (!Used)
439+
return;
440+
auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
441+
if (!UsedConditional)
442+
return;
443+
444+
SmallPtrSet<GlobalValue *, 8> NewUsedArray;
445+
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
446+
for (Value *Op : UsedList->operands()) {
447+
NewUsedArray.insert(cast<GlobalValue>(Op->stripPointerCasts()));
448+
}
449+
450+
// Repeat the liveness propagation iteraticely, one iteration might force
451+
// other conditionally used globals to become alive.
452+
while (true) {
453+
PropagateLivenessInGlobalValues();
454+
455+
unsigned OldSize = NewUsedArray.size();
456+
for (auto *M : UsedConditional->operands()) {
457+
auto *Target = TargetFromConditionalUsedIfLive(M);
458+
if (!Target) continue;
459+
460+
NewUsedArray.insert(Target);
461+
MarkLive(*Target);
462+
LLVM_DEBUG(dbgs() << "Conditionally used target alive: "
463+
<< Target->getName() << "\n");
464+
}
465+
466+
unsigned NewSize = NewUsedArray.size();
467+
LLVM_DEBUG(dbgs() << "Conditionally used iteration end, old size: "
468+
<< OldSize << " new size: " << NewSize << "\n");
469+
470+
// Stop the iteration once we reach a steady state (no new additions to
471+
// @llvm.used).
472+
if (NewSize == OldSize) break;
473+
}
474+
475+
Used = setUsedInitializer(*Used, NewUsedArray);
476+
MarkLive(*Used);
477+
}
478+
337479
PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
338480
bool Changed = false;
339481

@@ -363,6 +505,11 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
363505
// might call, if we have that information.
364506
AddVirtualFunctionDependencies(M);
365507

508+
// Process the !llvm.used.conditional list and (temporarily, see below)
509+
// remove all "targets" from @llvm.used. No effect if `!llvm.used.conditional`
510+
// is not present in the module.
511+
bool UsedConditionalPresent = RemoveConditionalTargetsFromUsedList(M);
512+
366513
// Loop over the module, adding globals which are obviously necessary.
367514
for (GlobalObject &GO : M.global_objects()) {
368515
Changed |= RemoveUnusedGlobalValue(GO);
@@ -396,16 +543,14 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
396543
UpdateGVDependencies(GIF);
397544
}
398545

399-
// Propagate liveness from collected Global Values through the computed
400-
// dependencies.
401-
SmallVector<GlobalValue *, 8> NewLiveGVs{AliveGlobals.begin(),
402-
AliveGlobals.end()};
403-
while (!NewLiveGVs.empty()) {
404-
GlobalValue *LGV = NewLiveGVs.pop_back_val();
405-
for (auto *GVD : GVDependencies[LGV])
406-
MarkLive(*GVD, &NewLiveGVs);
546+
// Step 2 of !llvm.used.conditional processing: If any conditionally used
547+
// "targets" are alive, put them back into @llvm.used.
548+
if (UsedConditionalPresent) {
549+
PropagateLivenessToConditionallyUsed(M);
407550
}
408551

552+
PropagateLivenessInGlobalValues();
553+
409554
// Now that all globals which are needed are in the AliveGlobals set, we loop
410555
// through the program, deleting those which are not alive.
411556
//

llvm/lib/Transforms/Utils/ModuleUtils.cpp

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,44 @@ void llvm::appendToCompilerUsed(Module &M, ArrayRef<GlobalValue *> Values) {
111111
appendToUsedList(M, "llvm.compiler.used", Values);
112112
}
113113

114+
static int compareNames(Constant *const *A, Constant *const *B) {
115+
Value *AStripped = (*A)->stripPointerCasts();
116+
Value *BStripped = (*B)->stripPointerCasts();
117+
return AStripped->getName().compare(BStripped->getName());
118+
}
119+
120+
GlobalVariable *
121+
llvm::setUsedInitializer(GlobalVariable &V,
122+
const SmallPtrSetImpl<GlobalValue *> &Init) {
123+
if (Init.empty()) {
124+
V.eraseFromParent();
125+
return nullptr;
126+
}
127+
128+
// Type of pointer to the array of pointers.
129+
PointerType *Int8PtrTy = Type::getInt8PtrTy(V.getContext(), 0);
130+
131+
SmallVector<Constant *, 8> UsedArray;
132+
for (GlobalValue *GV : Init) {
133+
Constant *Cast =
134+
ConstantExpr::getPointerBitCastOrAddrSpaceCast(GV, Int8PtrTy);
135+
UsedArray.push_back(Cast);
136+
}
137+
// Sort to get deterministic order.
138+
array_pod_sort(UsedArray.begin(), UsedArray.end(), compareNames);
139+
ArrayType *ATy = ArrayType::get(Int8PtrTy, UsedArray.size());
140+
141+
Module *M = V.getParent();
142+
V.removeFromParent();
143+
GlobalVariable *NV =
144+
new GlobalVariable(*M, ATy, false, GlobalValue::AppendingLinkage,
145+
ConstantArray::get(ATy, UsedArray), "");
146+
NV->takeName(&V);
147+
NV->setSection("llvm.metadata");
148+
delete &V;
149+
return NV;
150+
}
151+
114152
FunctionCallee
115153
llvm::declareSanitizerInitFunction(Module &M, StringRef InitName,
116154
ArrayRef<Type *> InitArgTypes) {

0 commit comments

Comments
 (0)