Skip to content

Commit 6cdc7b4

Browse files
committed
[GlobalDCE] Add !llvm.used.metadata 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 44d5b40 commit 6cdc7b4

14 files changed

+426
-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
@@ -54,6 +54,7 @@ class GlobalDCEPass : public PassInfoMixin<GlobalDCEPass> {
5454
void UpdateGVDependencies(GlobalValue &GV);
5555
void MarkLive(GlobalValue &GV,
5656
SmallVectorImpl<GlobalValue *> *Updates = nullptr);
57+
void PropagateLivenessInGlobalValues();
5758
bool RemoveUnusedGlobalValue(GlobalValue &GV);
5859

5960
// Dead virtual function elimination.
@@ -63,6 +64,9 @@ class GlobalDCEPass : public PassInfoMixin<GlobalDCEPass> {
6364
void ScanVTableLoad(Function *Caller, Metadata *TypeId, uint64_t CallOffset);
6465

6566
void ComputeDependencies(Value *V, SmallPtrSetImpl<GlobalValue *> &U);
67+
68+
GlobalValue *TargetFromConditionalUsedIfLive(MDNode *M);
69+
void PropagateLivenessToConditionallyUsed(Module &M);
6670
};
6771

6872
}

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: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -841,6 +841,32 @@ void Verifier::visitNamedMDNode(const NamedMDNode &NMD) {
841841

842842
visitMDNode(*MD, AreDebugLocsAllowed::Yes);
843843
}
844+
845+
if (NMD.getName() == "llvm.used.conditional") {
846+
for (const MDNode *MD : NMD.operands()) {
847+
Assert(MD->getNumOperands() == 3, "invalid llvm.used.conditional member");
848+
auto *TargetMD = MD->getOperand(0).get();
849+
if (TargetMD != nullptr) {
850+
Assert(mdconst::dyn_extract<GlobalValue>(TargetMD),
851+
"invalid llvm.used.conditional member");
852+
}
853+
auto *TypeMD = mdconst::extract_or_null<ConstantInt>(MD->getOperand(1));
854+
int64_t Type = TypeMD->getValue().getSExtValue();
855+
Assert(Type == 0 || Type == 1, "invalid llvm.used.conditional member");
856+
auto *DependenciesMD = dyn_cast<MDNode>(MD->getOperand(2).get());
857+
Assert(DependenciesMD, "invalid llvm.used.conditional member");
858+
Assert(DependenciesMD->getNumOperands() > 0,
859+
"invalid llvm.used.conditional member");
860+
for (auto &DependencyMD : DependenciesMD->operands()) {
861+
auto *Dependency = DependencyMD.get();
862+
if (Dependency != nullptr) {
863+
auto *C =
864+
mdconst::dyn_extract<Constant>(Dependency)->stripPointerCasts();
865+
Assert(isa<GlobalValue>(C), "invalid llvm.used.conditional member");
866+
}
867+
}
868+
}
869+
}
844870
}
845871

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

llvm/lib/Transforms/IPO/GlobalDCE.cpp

Lines changed: 147 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

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

161+
void GlobalDCEPass::PropagateLivenessInGlobalValues() {
162+
// Propagate liveness from collected Global Values through the computed
163+
// dependencies.
164+
SmallVector<GlobalValue *, 8> NewLiveGVs{AliveGlobals.begin(),
165+
AliveGlobals.end()};
166+
while (!NewLiveGVs.empty()) {
167+
GlobalValue *LGV = NewLiveGVs.pop_back_val();
168+
for (auto *GVD : GVDependencies[LGV])
169+
MarkLive(*GVD, &NewLiveGVs);
170+
}
171+
}
172+
160173
void GlobalDCEPass::ScanVTables(Module &M) {
161174
SmallVector<MDNode *, 2> Types;
162175
LLVM_DEBUG(dbgs() << "Building type info -> vtable map\n");
@@ -286,6 +299,129 @@ void GlobalDCEPass::AddVirtualFunctionDependencies(Module &M) {
286299
);
287300
}
288301

302+
static bool RemoveConditionalTargetsFromUsedList(Module &M) {
303+
auto *Used = M.getGlobalVariable("llvm.used");
304+
if (!Used)
305+
return false;
306+
307+
auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
308+
if (!UsedConditional)
309+
return false;
310+
if (UsedConditional->getNumOperands() == 0)
311+
return false;
312+
313+
// Construct a set of conditionally used targets.
314+
SmallPtrSet<GlobalValue *, 8> Targets;
315+
for (auto *M : UsedConditional->operands()) {
316+
assert(M->getNumOperands() == 3);
317+
auto *V = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
318+
if (!V)
319+
continue;
320+
Targets.insert(V);
321+
}
322+
323+
if (Targets.empty())
324+
return false;
325+
326+
// Now remove all targets from @llvm.used.
327+
SmallPtrSet<GlobalValue *, 8> NewUsedArray;
328+
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
329+
for (Value *Op : UsedList->operands()) {
330+
GlobalValue *G = cast<GlobalValue>(Op->stripPointerCasts());
331+
if (Targets.contains(G))
332+
continue;
333+
NewUsedArray.insert(G);
334+
}
335+
Used = setUsedInitializer(*Used, NewUsedArray);
336+
return true;
337+
}
338+
339+
// Parse one entry from !llvm.used.conditional list as a triplet of
340+
// { target, type, dependencies } and evaluate the conditional dependency, i.e.
341+
// check liveness of all dependencies and based on type conclude whether the
342+
// target is supposed to be declared alive. If yes, return the target, otherwise
343+
// return nullptr.
344+
GlobalValue *GlobalDCEPass::TargetFromConditionalUsedIfLive(MDNode *M) {
345+
assert(M->getNumOperands() == 3);
346+
auto *Target = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
347+
if (!Target)
348+
return nullptr;
349+
350+
auto *DependenciesMD = dyn_cast_or_null<MDNode>(M->getOperand(2).get());
351+
SmallPtrSet<GlobalValue *, 8> Dependencies;
352+
if (DependenciesMD == nullptr) {
353+
Dependencies.insert(nullptr);
354+
} else {
355+
for (auto &DependencyMD : DependenciesMD->operands()) {
356+
auto *Dependency = DependencyMD.get();
357+
if (!Dependency)
358+
continue;
359+
auto *C =
360+
mdconst::extract_or_null<Constant>(Dependency)->stripPointerCasts();
361+
Dependencies.insert(cast<GlobalValue>(C));
362+
}
363+
}
364+
365+
bool AllDependenciesAlive = true;
366+
bool AnyDependencyAlive = false;
367+
for (auto *Dep : Dependencies) {
368+
bool Live = AliveGlobals.count(Dep) != 0;
369+
if (Live)
370+
AnyDependencyAlive = true;
371+
else
372+
AllDependenciesAlive = false;
373+
}
374+
375+
auto *Type = mdconst::extract_or_null<ConstantInt>(M->getOperand(1));
376+
switch (Type->getValue().getSExtValue()) {
377+
case 0:
378+
return AnyDependencyAlive ? Target : nullptr;
379+
case 1:
380+
return AllDependenciesAlive ? Target : nullptr;
381+
default:
382+
llvm_unreachable("bad !llvm.used.conditional type");
383+
}
384+
}
385+
386+
void GlobalDCEPass::PropagateLivenessToConditionallyUsed(Module &M) {
387+
auto *Used = M.getGlobalVariable("llvm.used");
388+
auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
389+
390+
SmallPtrSet<GlobalValue *, 8> NewUsedArray;
391+
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
392+
for (Value *Op : UsedList->operands()) {
393+
NewUsedArray.insert(cast<GlobalValue>(Op->stripPointerCasts()));
394+
}
395+
396+
// Repeat the liveness propagation iteraticely, one iteration might force
397+
// other conditionally used globals to become alive.
398+
while (true) {
399+
PropagateLivenessInGlobalValues();
400+
401+
unsigned OldSize = NewUsedArray.size();
402+
for (auto *M : UsedConditional->operands()) {
403+
auto *Target = TargetFromConditionalUsedIfLive(M);
404+
if (!Target) continue;
405+
406+
NewUsedArray.insert(Target);
407+
MarkLive(*Target);
408+
LLVM_DEBUG(dbgs() << "Conditionally used target alive: "
409+
<< Target->getName() << "\n");
410+
}
411+
412+
unsigned NewSize = NewUsedArray.size();
413+
LLVM_DEBUG(dbgs() << "Conditionally used iteration end, old size: "
414+
<< OldSize << " new size: " << NewSize << "\n");
415+
416+
// Stop the iteration once we reach a steady state (no new additions to
417+
// @llvm.used).
418+
if (NewSize == OldSize) break;
419+
}
420+
421+
Used = setUsedInitializer(*Used, NewUsedArray);
422+
MarkLive(*Used);
423+
}
424+
289425
PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
290426
bool Changed = false;
291427

@@ -315,6 +451,11 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
315451
// might call, if we have that information.
316452
AddVirtualFunctionDependencies(M);
317453

454+
// Process the !llvm.used.conditional list and (temporarily, see below)
455+
// remove all "targets" from @llvm.used. No effect if `!llvm.used.conditional`
456+
// is not present in the module.
457+
bool UsedConditionalPresent = RemoveConditionalTargetsFromUsedList(M);
458+
318459
// Loop over the module, adding globals which are obviously necessary.
319460
for (GlobalObject &GO : M.global_objects()) {
320461
Changed |= RemoveUnusedGlobalValue(GO);
@@ -348,16 +489,14 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
348489
UpdateGVDependencies(GIF);
349490
}
350491

351-
// Propagate liveness from collected Global Values through the computed
352-
// dependencies.
353-
SmallVector<GlobalValue *, 8> NewLiveGVs{AliveGlobals.begin(),
354-
AliveGlobals.end()};
355-
while (!NewLiveGVs.empty()) {
356-
GlobalValue *LGV = NewLiveGVs.pop_back_val();
357-
for (auto *GVD : GVDependencies[LGV])
358-
MarkLive(*GVD, &NewLiveGVs);
492+
// Step 2 of !llvm.used.conditional processing: If any conditionally used
493+
// "targets" are alive, put them back into @llvm.used.
494+
if (UsedConditionalPresent) {
495+
PropagateLivenessToConditionallyUsed(M);
359496
}
360497

498+
PropagateLivenessInGlobalValues();
499+
361500
// Now that all globals which are needed are in the AliveGlobals set, we loop
362501
// through the program, deleting those which are not alive.
363502
//

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)