Skip to content

[GlobalDCE] Add !llvm.used.conditional support for conditionally used global variables #3396

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions llvm/docs/LangRef.rst
Original file line number Diff line number Diff line change
Expand Up @@ -7304,6 +7304,55 @@ For example, the following metadata section contains two library specifiers::
Each library specifier will be handled independently by the consuming linker.
The effect of the library specifiers are defined by the consuming linker.

.. _llvm_used_conditional:

Conditionally Used Globals
==========================

When a global variable is mentioned in the `@llvm.used` list, it's always going
to be retained, but sometimes it's desirable to allow such a global variable to
still be discardable by optimization passes. Using the `!llvm.used.conditional`
named metadata allows expressing *when* it is legal to discard a global (from
the `@llvm.used` list) in terms of liveness of other globals.

If `!llvm.used.conditional` named metadata is present in IR of a module, it's
expected to be a list of metadata triplets. Each triplet has the following form:

1. The first element is the "target", the global variable from `@llvm.used` that
we are allowing removal of. If the global variable is not present in the
`@llvm.used` list, then there is no effect from using a entry in
`!llvm.used.conditional` for it.
2. The second element is a "type", a boolean flag expressing what behavior do
we want if the list of "dependencies" (third element) contains more than one
element. `0` means if *any dependency* in the list is alive, the target symbol
must stay alive, otherwise removal is allowed (in other words: if all globals
from the dependency list are removed, the target can be removed too). `1`
means if *all dependencies* in the list are alive, the target must stay
alive, otherwise removal is allowed (in other words: if any global from the
dependency list is removed, the target can be removed too).
3. The third element is a list of "dependencies", a list of globals.

The following example expresses that the global `@record` (which is listed in
`@llvm.used` otherwise it would be trivially discarded as nothing references it)
is allowed to be optimized away, if *either of* `@a` or `@b` globals are
themselves removable::

@record = internal global [...] {
@a, @b
}
@llvm.used = appending global [...] [ ..., @record ]

!1 = !{
@record, ; target
1, ; type
!{ @a, @b } ; dependencies
}
!llvm.used.conditional = !{ !1 }

The semantics of `!llvm.used.conditional` are only *allowing an optimization*,
and are not requiring optimizations to follow them --- it is correct for any
optimization/transformation to ignore `!llvm.used.conditional` or even drop it.

.. _summary:

ThinLTO Summary
Expand Down
4 changes: 4 additions & 0 deletions llvm/include/llvm/Transforms/IPO/GlobalDCE.h
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ class GlobalDCEPass : public PassInfoMixin<GlobalDCEPass> {
void UpdateGVDependencies(GlobalValue &GV);
void MarkLive(GlobalValue &GV,
SmallVectorImpl<GlobalValue *> *Updates = nullptr);
void PropagateLivenessInGlobalValues();
bool RemoveUnusedGlobalValue(GlobalValue &GV);

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

void ComputeDependencies(Value *V, SmallPtrSetImpl<GlobalValue *> &U);

GlobalValue *TargetFromConditionalUsedIfLive(MDNode *M);
void PropagateLivenessToConditionallyUsed(Module &M);
};

}
Expand Down
6 changes: 6 additions & 0 deletions llvm/include/llvm/Transforms/Utils/ModuleUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
#ifndef LLVM_TRANSFORMS_UTILS_MODULEUTILS_H
#define LLVM_TRANSFORMS_UTILS_MODULEUTILS_H

#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringRef.h"
#include <utility> // for std::pair
Expand All @@ -24,6 +25,7 @@ class Module;
class Function;
class FunctionCallee;
class GlobalValue;
class GlobalVariable;
class Constant;
class Value;
class Type;
Expand Down Expand Up @@ -78,6 +80,10 @@ void appendToUsed(Module &M, ArrayRef<GlobalValue *> Values);
/// Adds global values to the llvm.compiler.used list.
void appendToCompilerUsed(Module &M, ArrayRef<GlobalValue *> Values);

/// Replaces llvm.used or llvm.compiler.used list with a new set of values.
GlobalVariable *setUsedInitializer(GlobalVariable &V,
const SmallPtrSetImpl<GlobalValue *> &Init);

/// Filter out potentially dead comdat functions where other entries keep the
/// entire comdat group alive.
///
Expand Down
28 changes: 28 additions & 0 deletions llvm/lib/IR/Verifier.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -875,6 +875,34 @@ void Verifier::visitNamedMDNode(const NamedMDNode &NMD) {

visitMDNode(*MD, AreDebugLocsAllowed::Yes);
}

if (NMD.getName() == "llvm.used.conditional") {
for (const MDNode *MD : NMD.operands()) {
Assert(MD->getNumOperands() == 3, "invalid llvm.used.conditional member");
auto *TargetMD = MD->getOperand(0).get();
if (TargetMD != nullptr) {
Assert(mdconst::dyn_extract<GlobalValue>(TargetMD),
"invalid llvm.used.conditional member");
}
auto *TypeMD = mdconst::extract_or_null<ConstantInt>(MD->getOperand(1));
int64_t Type = TypeMD->getValue().getSExtValue();
Assert(Type == 0 || Type == 1, "invalid llvm.used.conditional member");
auto *DependenciesMD = dyn_cast<MDNode>(MD->getOperand(2).get());
Assert(DependenciesMD, "invalid llvm.used.conditional member");
Assert(DependenciesMD->getNumOperands() > 0,
"invalid llvm.used.conditional member");
for (auto &DependencyMD : DependenciesMD->operands()) {
auto *Dependency = DependencyMD.get();
if (!Dependency)
continue; // Allow null, skip.
auto *C =
mdconst::dyn_extract<Constant>(Dependency)->stripPointerCasts();
if (dyn_cast<UndefValue>(C))
continue; // Allow undef, skip.
Assert(isa<GlobalValue>(C), "invalid llvm.used.conditional member");
}
}
}
}

void Verifier::visitMDNode(const MDNode &MD, AreDebugLocsAllowed AllowLocs) {
Expand Down
161 changes: 153 additions & 8 deletions llvm/lib/Transforms/IPO/GlobalDCE.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
#include "llvm/Transforms/IPO.h"
#include "llvm/Transforms/Utils/CtorUtils.h"
#include "llvm/Transforms/Utils/GlobalStatus.h"
#include "llvm/Transforms/Utils/ModuleUtils.h"

using namespace llvm;

Expand Down Expand Up @@ -158,6 +159,18 @@ void GlobalDCEPass::MarkLive(GlobalValue &GV,
}
}

void GlobalDCEPass::PropagateLivenessInGlobalValues() {
// Propagate liveness from collected Global Values through the computed
// dependencies.
SmallVector<GlobalValue *, 8> NewLiveGVs{AliveGlobals.begin(),
AliveGlobals.end()};
while (!NewLiveGVs.empty()) {
GlobalValue *LGV = NewLiveGVs.pop_back_val();
for (auto *GVD : GVDependencies[LGV])
MarkLive(*GVD, &NewLiveGVs);
}
}

/// Recursively iterate over the (sub-)constants in the vtable and look for
/// vptrs, if their offset is within [RangeStart..RangeEnd), add them to VFuncs.
static void FindVirtualFunctionsInVTable(Module &M, Constant *C,
Expand Down Expand Up @@ -334,6 +347,135 @@ void GlobalDCEPass::AddVirtualFunctionDependencies(Module &M) {
<< " " << Entry.first->getName() << "\n";);
}

static bool RemoveConditionalTargetsFromUsedList(Module &M) {
auto *Used = M.getGlobalVariable("llvm.used");
if (!Used)
return false;

auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
if (!UsedConditional)
return false;
if (UsedConditional->getNumOperands() == 0)
return false;

// Construct a set of conditionally used targets.
SmallPtrSet<GlobalValue *, 8> Targets;
for (auto *M : UsedConditional->operands()) {
assert(M->getNumOperands() == 3);
auto *V = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
if (!V)
continue;
Targets.insert(V);
}

if (Targets.empty())
return false;

// Now remove all targets from @llvm.used.
SmallPtrSet<GlobalValue *, 8> NewUsedArray;
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
for (Value *Op : UsedList->operands()) {
GlobalValue *G = cast<GlobalValue>(Op->stripPointerCasts());
if (Targets.contains(G))
continue;
NewUsedArray.insert(G);
}
Used = setUsedInitializer(*Used, NewUsedArray);
return true;
}

// Parse one entry from !llvm.used.conditional list as a triplet of
// { target, type, dependencies } and evaluate the conditional dependency, i.e.
// check liveness of all dependencies and based on type conclude whether the
// target is supposed to be declared alive. If yes, return the target, otherwise
// return nullptr.
GlobalValue *GlobalDCEPass::TargetFromConditionalUsedIfLive(MDNode *M) {
assert(M->getNumOperands() == 3);
auto *Target = mdconst::extract_or_null<GlobalValue>(M->getOperand(0));
if (!Target)
return nullptr;

auto *DependenciesMD = dyn_cast_or_null<MDNode>(M->getOperand(2).get());
SmallPtrSet<GlobalValue *, 8> Dependencies;
if (DependenciesMD == nullptr) {
Dependencies.insert(nullptr);
} else {
for (auto &DependencyMD : DependenciesMD->operands()) {
auto *Dependency = DependencyMD.get();
if (!Dependency)
continue; // Allow null, skip.
auto *C =
mdconst::extract_or_null<Constant>(Dependency)->stripPointerCasts();
if (dyn_cast<UndefValue>(C))
continue; // Allow undef, skip.
Dependencies.insert(cast<GlobalValue>(C));
}
}

bool AllDependenciesAlive = Dependencies.empty() ? false : true;
bool AnyDependencyAlive = false;
for (auto *Dep : Dependencies) {
bool Live = AliveGlobals.count(Dep) != 0;
if (Live)
AnyDependencyAlive = true;
else
AllDependenciesAlive = false;
}

auto *Type = mdconst::extract_or_null<ConstantInt>(M->getOperand(1));
switch (Type->getValue().getSExtValue()) {
case 0:
return AnyDependencyAlive ? Target : nullptr;
case 1:
return AllDependenciesAlive ? Target : nullptr;
default:
llvm_unreachable("bad !llvm.used.conditional type");
}
}

void GlobalDCEPass::PropagateLivenessToConditionallyUsed(Module &M) {
auto *Used = M.getGlobalVariable("llvm.used");
if (!Used)
return;
auto *UsedConditional = M.getNamedMetadata("llvm.used.conditional");
if (!UsedConditional)
return;

SmallPtrSet<GlobalValue *, 8> NewUsedArray;
const ConstantArray *UsedList = cast<ConstantArray>(Used->getInitializer());
for (Value *Op : UsedList->operands()) {
NewUsedArray.insert(cast<GlobalValue>(Op->stripPointerCasts()));
}

// Repeat the liveness propagation iteraticely, one iteration might force
// other conditionally used globals to become alive.
while (true) {
PropagateLivenessInGlobalValues();

unsigned OldSize = NewUsedArray.size();
for (auto *M : UsedConditional->operands()) {
auto *Target = TargetFromConditionalUsedIfLive(M);
if (!Target) continue;

NewUsedArray.insert(Target);
MarkLive(*Target);
LLVM_DEBUG(dbgs() << "Conditionally used target alive: "
<< Target->getName() << "\n");
}

unsigned NewSize = NewUsedArray.size();
LLVM_DEBUG(dbgs() << "Conditionally used iteration end, old size: "
<< OldSize << " new size: " << NewSize << "\n");

// Stop the iteration once we reach a steady state (no new additions to
// @llvm.used).
if (NewSize == OldSize) break;
}

Used = setUsedInitializer(*Used, NewUsedArray);
MarkLive(*Used);
}

PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
bool Changed = false;

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

// Process the !llvm.used.conditional list and (temporarily, see below)
// remove all "targets" from @llvm.used. No effect if `!llvm.used.conditional`
// is not present in the module.
bool UsedConditionalPresent = RemoveConditionalTargetsFromUsedList(M);

// Loop over the module, adding globals which are obviously necessary.
for (GlobalObject &GO : M.global_objects()) {
Changed |= RemoveUnusedGlobalValue(GO);
Expand Down Expand Up @@ -396,16 +543,14 @@ PreservedAnalyses GlobalDCEPass::run(Module &M, ModuleAnalysisManager &MAM) {
UpdateGVDependencies(GIF);
}

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

PropagateLivenessInGlobalValues();

// Now that all globals which are needed are in the AliveGlobals set, we loop
// through the program, deleting those which are not alive.
//
Expand Down
38 changes: 38 additions & 0 deletions llvm/lib/Transforms/Utils/ModuleUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,44 @@ void llvm::appendToCompilerUsed(Module &M, ArrayRef<GlobalValue *> Values) {
appendToUsedList(M, "llvm.compiler.used", Values);
}

static int compareNames(Constant *const *A, Constant *const *B) {
Value *AStripped = (*A)->stripPointerCasts();
Value *BStripped = (*B)->stripPointerCasts();
return AStripped->getName().compare(BStripped->getName());
}

GlobalVariable *
llvm::setUsedInitializer(GlobalVariable &V,
const SmallPtrSetImpl<GlobalValue *> &Init) {
if (Init.empty()) {
V.eraseFromParent();
return nullptr;
}

// Type of pointer to the array of pointers.
PointerType *Int8PtrTy = Type::getInt8PtrTy(V.getContext(), 0);

SmallVector<Constant *, 8> UsedArray;
for (GlobalValue *GV : Init) {
Constant *Cast =
ConstantExpr::getPointerBitCastOrAddrSpaceCast(GV, Int8PtrTy);
UsedArray.push_back(Cast);
}
// Sort to get deterministic order.
array_pod_sort(UsedArray.begin(), UsedArray.end(), compareNames);
ArrayType *ATy = ArrayType::get(Int8PtrTy, UsedArray.size());

Module *M = V.getParent();
V.removeFromParent();
GlobalVariable *NV =
new GlobalVariable(*M, ATy, false, GlobalValue::AppendingLinkage,
ConstantArray::get(ATy, UsedArray), "");
NV->takeName(&V);
NV->setSection("llvm.metadata");
delete &V;
return NV;
}

FunctionCallee
llvm::declareSanitizerInitFunction(Module &M, StringRef InitName,
ArrayRef<Type *> InitArgTypes) {
Expand Down
Loading