Index: llvm/include/llvm/Transforms/Utils/CtorUtils.h =================================================================== --- llvm/include/llvm/Transforms/Utils/CtorUtils.h +++ llvm/include/llvm/Transforms/Utils/CtorUtils.h @@ -24,8 +24,8 @@ /// Call "ShouldRemove" for every entry in M's global_ctor list and remove the /// entries for which it returns true. Return true if anything changed. -bool optimizeGlobalCtorsList(Module &M, - function_ref ShouldRemove); +bool optimizeGlobalCtorsList( + Module &M, function_ref ShouldRemove); } // End llvm namespace Index: llvm/include/llvm/Transforms/Utils/Evaluator.h =================================================================== --- llvm/include/llvm/Transforms/Utils/Evaluator.h +++ llvm/include/llvm/Transforms/Utils/Evaluator.h @@ -38,8 +38,9 @@ /// fails, the evaluation object should not be reused. class Evaluator { public: - Evaluator(const DataLayout &DL, const TargetLibraryInfo *TLI) - : DL(DL), TLI(TLI) { + Evaluator(const DataLayout &DL, const TargetLibraryInfo *TLI, + GlobalVariable *ComdatGV = nullptr) + : DL(DL), TLI(TLI), ComdatGV(ComdatGV) { ValueStack.emplace_back(); } @@ -126,6 +127,12 @@ const DataLayout &DL; const TargetLibraryInfo *TLI; + + /// If non-null, the function being evaluated is in the comdat group of this + /// global. Typically, that means it is the code to dynamically initailize it. + /// We can update the value of this global even if it has vague + /// (linkonce/weak/odr) linkage. + GlobalVariable *ComdatGV; }; } // end namespace llvm Index: llvm/lib/Transforms/IPO/GlobalDCE.cpp =================================================================== --- llvm/lib/Transforms/IPO/GlobalDCE.cpp +++ llvm/lib/Transforms/IPO/GlobalDCE.cpp @@ -76,8 +76,9 @@ return new GlobalDCELegacyPass(); } -/// Returns true if F is effectively empty. -static bool isEmptyFunction(Function *F) { +/// Returns true if F is effectively empty. Even if this is an initializer for +/// some comdat global variable, we can eliminate it. +static bool isEmptyFunction(Function *F, GlobalVariable *GV) { BasicBlock &Entry = F->getEntryBlock(); for (auto &I : Entry) { if (isa(I)) Index: llvm/lib/Transforms/IPO/GlobalOpt.cpp =================================================================== --- llvm/lib/Transforms/IPO/GlobalOpt.cpp +++ llvm/lib/Transforms/IPO/GlobalOpt.cpp @@ -2537,10 +2537,11 @@ /// Evaluate static constructors in the function, if we can. Return true if we /// can, false otherwise. -static bool EvaluateStaticConstructor(Function *F, const DataLayout &DL, +static bool EvaluateStaticConstructor(Function *F, GlobalVariable *ComdatGV, + const DataLayout &DL, TargetLibraryInfo *TLI) { // Call the function. - Evaluator Eval(DL, TLI); + Evaluator Eval(DL, TLI, ComdatGV); Constant *RetValDummy; bool EvalSuccess = Eval.EvaluateFunction(F, RetValDummy, SmallVector()); @@ -2928,9 +2929,10 @@ NotDiscardableComdats); // Optimize global_ctors list. - LocalChange |= optimizeGlobalCtorsList(M, [&](Function *F) { - return EvaluateStaticConstructor(F, DL, TLI); - }); + LocalChange |= + optimizeGlobalCtorsList(M, [&](Function *F, GlobalVariable *GV) { + return EvaluateStaticConstructor(F, GV, DL, TLI); + }); // Optimize non-address-taken globals. LocalChange |= OptimizeGlobalVars(M, TLI, LookupDomTree, Index: llvm/lib/Transforms/Utils/CtorUtils.cpp =================================================================== --- llvm/lib/Transforms/Utils/CtorUtils.cpp +++ llvm/lib/Transforms/Utils/CtorUtils.cpp @@ -13,6 +13,7 @@ #include "llvm/Transforms/Utils/CtorUtils.h" #include "llvm/ADT/BitVector.h" +#include "llvm/ADT/Triple.h" #include "llvm/IR/Constants.h" #include "llvm/IR/Function.h" #include "llvm/IR/GlobalVariable.h" @@ -61,21 +62,6 @@ GCL->eraseFromParent(); } -/// Given a llvm.global_ctors list that we can understand, -/// return a list of the functions and null terminator as a vector. -static std::vector parseGlobalCtors(GlobalVariable *GV) { - if (GV->getInitializer()->isNullValue()) - return std::vector(); - ConstantArray *CA = cast(GV->getInitializer()); - std::vector Result; - Result.reserve(CA->getNumOperands()); - for (auto &V : CA->operands()) { - ConstantStruct *CS = cast(V); - Result.push_back(dyn_cast(CS->getOperand(1))); - } - return Result; -} - /// Find the llvm.global_ctors list, verifying that all initializers have an /// init priority of 65535. static GlobalVariable *findGlobalCtors(Module &M) { @@ -115,38 +101,47 @@ /// Call "ShouldRemove" for every entry in M's global_ctor list and remove the /// entries for which it returns true. Return true if anything changed. bool llvm::optimizeGlobalCtorsList( - Module &M, function_ref ShouldRemove) { + Module &M, function_ref ShouldRemove) { GlobalVariable *GlobalCtors = findGlobalCtors(M); if (!GlobalCtors) return false; - std::vector Ctors = parseGlobalCtors(GlobalCtors); - if (Ctors.empty()) - return false; + // Check if we can assume that all initializers across the program for a vague + // linkage global will be in the comdat group of the global. This allows us to + // optimize dynamic initializers of globals with vague linkage, because we + // know that if the global in the current TU prevails, no other initializers + // will run, preventing double initialization. The Microsoft C++ ABI provides + // this guarantee. + bool AssumeComdatInitializers = + Triple(M.getTargetTriple()).isWindowsMSVCEnvironment(); + + ConstantArray *Ctors = cast(GlobalCtors->getInitializer()); bool MadeChange = false; // Loop over global ctors, optimizing them when we can. - unsigned NumCtors = Ctors.size(); + unsigned NumCtors = Ctors->getNumOperands(); BitVector CtorsToRemove(NumCtors); - for (unsigned i = 0; i != Ctors.size() && NumCtors > 0; ++i) { - Function *F = Ctors[i]; - // Found a null terminator in the middle of the list, prune off the rest of - // the list. - if (!F) + for (unsigned i = 0; i != NumCtors; ++i) { + ConstantStruct *CS = cast(Ctors->getOperand(i)); + Function *F = dyn_cast(CS->getOperand(1)); + + // If we can assume that initializers are always in the global's comdat + // group, then we can modify the global being initialized. Pass the global + // down to the evaluator so it knows it can be modified. + GlobalVariable *GV = nullptr; + if (AssumeComdatInitializers) + GV = dyn_cast(CS->getOperand(2)->stripPointerCasts()); + + // Ignore null initializers and external functions. + if (!F || F->empty()) continue; LLVM_DEBUG(dbgs() << "Optimizing Global Constructor: " << *F << "\n"); - // We cannot simplify external ctor functions. - if (F->empty()) - continue; - // If we can evaluate the ctor at compile time, do. - if (ShouldRemove(F)) { - Ctors[i] = nullptr; + if (ShouldRemove(F, GV)) { CtorsToRemove.set(i); - NumCtors--; MadeChange = true; continue; } Index: llvm/lib/Transforms/Utils/Evaluator.cpp =================================================================== --- llvm/lib/Transforms/Utils/Evaluator.cpp +++ llvm/lib/Transforms/Utils/Evaluator.cpp @@ -124,20 +124,28 @@ return isSimpleEnoughValueToCommitHelper(C, SimpleConstants, DL); } +/// Check if this is a global we can update. The global can be updated if it has +/// a unique initializer (i.e. internal or strong external) or if the +/// initializer we are evaluating is in the same comdat group as the global. +static bool canUpdateGlobal(GlobalVariable *GV, GlobalVariable *ComdatGV) { + return GV == ComdatGV || GV->hasUniqueInitializer(); +} + /// Return true if this constant is simple enough for us to understand. In /// particular, if it is a cast to anything other than from one pointer type to /// another pointer type, we punt. We basically just support direct accesses to /// globals and GEP's of globals. This should be kept up to date with /// CommitValueTo. -static bool isSimpleEnoughPointerToCommit(Constant *C) { +static bool isSimpleEnoughPointerToCommit(Constant *C, + GlobalVariable *ComdatGV) { // Conservatively, avoid aggregate types. This is because we don't // want to worry about them partially overlapping other stores. if (!cast(C->getType())->getElementType()->isSingleValueType()) return false; - if (GlobalVariable *GV = dyn_cast(C)) - // Do not allow weak/*_odr/linkonce linkage or external globals. - return GV->hasUniqueInitializer(); + if (GlobalVariable *GV = dyn_cast(C)) { + return canUpdateGlobal(GV, ComdatGV); + } if (ConstantExpr *CE = dyn_cast(C)) { // Handle a constantexpr gep. @@ -145,9 +153,7 @@ isa(CE->getOperand(0)) && cast(CE)->isInBounds()) { GlobalVariable *GV = cast(CE->getOperand(0)); - // Do not allow weak/*_odr/linkonce/dllimport/dllexport linkage or - // external globals. - if (!GV->hasUniqueInitializer()) + if (!canUpdateGlobal(GV, ComdatGV)) return false; // The first index must be zero. @@ -166,9 +172,7 @@ // operand to the value operand. } else if (CE->getOpcode() == Instruction::BitCast && isa(CE->getOperand(0))) { - // Do not allow weak/*_odr/linkonce/dllimport/dllexport linkage or - // external globals. - return cast(CE->getOperand(0))->hasUniqueInitializer(); + return canUpdateGlobal(cast(CE->getOperand(0)), ComdatGV); } } @@ -307,7 +311,7 @@ Ptr = FoldedPtr; LLVM_DEBUG(dbgs() << "; To: " << *Ptr << "\n"); } - if (!isSimpleEnoughPointerToCommit(Ptr)) { + if (!isSimpleEnoughPointerToCommit(Ptr, ComdatGV)) { // If this is too complex for us to commit, reject it. LLVM_DEBUG( dbgs() << "Pointer is too complex for us to evaluate store."); Index: llvm/test/Transforms/GlobalOpt/comdat-global.ll =================================================================== --- /dev/null +++ llvm/test/Transforms/GlobalOpt/comdat-global.ll @@ -0,0 +1,76 @@ +; RUN: opt -globalopt -S < %s | FileCheck %s + +target triple = "x86_64-pc-windows-msvc19.14.26433" + +$basic = comdat any +$basic_init = comdat any + +$cross = comdat any +$cross_init = comdat any + +$intern = comdat any +$intern_init = comdat any + +$struct = comdat any +$struct_init = comdat any + +@basic = weak_odr global i8 0, comdat +; CHECK: @basic = weak_odr local_unnamed_addr global i8 42, comdat + +@cross = weak_odr global i8 0, comdat +; CHECK: @cross = weak_odr global i8 0, comdat + +@intern = weak_odr global i8 0, comdat +@intern_other_internal = internal global i8 0 +; CHECK: @intern = weak_odr local_unnamed_addr global i8 42, comdat +; CHECK: @intern_other_internal = internal global i8 13 + +@struct = weak_odr global { i8, i8 } zeroinitializer, comdat +; CHECK: @struct = weak_odr local_unnamed_addr global { i8, i8 } { i8 13, i8 42 }, comdat + +@llvm.global_ctors = appending global [4 x { i32, void ()*, i8* }] [ + { i32, void ()*, i8* } { i32 65535, void ()* @basic_init, i8* @basic }, + { i32, void ()*, i8* } { i32 65535, void ()* @cross_init, i8* @cross }, + { i32, void ()*, i8* } { i32 65535, void ()* @intern_init, i8* @intern }, + { i32, void ()*, i8* } { i32 65535, void ()* @struct_init, i8* bitcast ({ i8, i8 }* @struct to i8*) } +] + +@llvm.used = appending global [1 x i8*] [ i8* @intern_other_internal ] + +; CHECK: @llvm.global_ctors = appending global [{{[0-9]*}} x { i32, void ()*, i8* }] [ +; CHECK-SAME: { i32 65535, void ()* @cross_init, i8* @cross } +; CHECK-SAME: ] + +; GlobalOpt should fire for this. +define linkonce_odr void @basic_init() comdat { + store i8 42, i8* @basic + ret void +} +; CHECK-NOT: define {{.*}}@basic_init() + +; GlobalOpt should not fire, it can't store to a separate weak_odr global. +define linkonce_odr void @cross_init() comdat { + store i8 13, i8* @basic + store i8 42, i8* @cross + ret void +} +; CHECK: define {{.*}}@cross_init() + +; We can store to two globals if one is internal (hasUniqueInitializer). +define linkonce_odr void @intern_init() comdat { + store i8 13, i8* @intern_other_internal + store i8 42, i8* @intern + ret void +} +; CHECK-NOT: define {{.*}}@intern_init() + + +; GlobalOpt works on structs, which are the common case for C++ initializers. +define linkonce_odr void @struct_init() comdat { + store i8 13, i8* getelementptr ({ i8, i8 }, { i8, i8 }* @struct, i32 0, i32 0) + store i8 42, i8* getelementptr ({ i8, i8 }, { i8, i8 }* @struct, i32 0, i32 1) + ret void +} +; CHECK-NOT: define {{.*}}@struct_init() + + Index: llvm/test/Transforms/GlobalOpt/preserve-comdats.ll =================================================================== --- llvm/test/Transforms/GlobalOpt/preserve-comdats.ll +++ llvm/test/Transforms/GlobalOpt/preserve-comdats.ll @@ -14,7 +14,10 @@ ; CHECK: @llvm.global_ctors = appending global [1 x { i32, void ()*, i8* }] ; CHECK: [{ i32, void ()*, i8* } { i32 65535, void ()* @init_comdat_global, i8* @comdat_global }] +declare void @do_not_optimize() + define void @init_comdat_global() { + call void @do_not_optimize() store i8 42, i8* @comdat_global ret void }