| //===- GlobalSplit.cpp - global variable splitter -------------------------===// | |
| // | |
| // 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 pass uses inrange annotations on GEP indices to split globals where | |
| // beneficial. Clang currently attaches these annotations to references to | |
| // virtual table globals under the Itanium ABI for the benefit of the | |
| // whole-program virtual call optimization and control flow integrity passes. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Transforms/IPO/GlobalSplit.h" | |
| #include "llvm/ADT/SmallVector.h" | |
| #include "llvm/ADT/StringExtras.h" | |
| #include "llvm/IR/Constant.h" | |
| #include "llvm/IR/Constants.h" | |
| #include "llvm/IR/DataLayout.h" | |
| #include "llvm/IR/Function.h" | |
| #include "llvm/IR/GlobalValue.h" | |
| #include "llvm/IR/GlobalVariable.h" | |
| #include "llvm/IR/Intrinsics.h" | |
| #include "llvm/IR/LLVMContext.h" | |
| #include "llvm/IR/Metadata.h" | |
| #include "llvm/IR/Module.h" | |
| #include "llvm/IR/Operator.h" | |
| #include "llvm/IR/Type.h" | |
| #include "llvm/IR/User.h" | |
| #include "llvm/Support/Casting.h" | |
| #include <cstdint> | |
| #include <vector> | |
| using namespace llvm; | |
| static bool splitGlobal(GlobalVariable &GV) { | |
| // If the address of the global is taken outside of the module, we cannot | |
| // apply this transformation. | |
| if (!GV.hasLocalLinkage()) | |
| return false; | |
| // We currently only know how to split ConstantStructs. | |
| auto *Init = dyn_cast_or_null<ConstantStruct>(GV.getInitializer()); | |
| if (!Init) | |
| return false; | |
| const DataLayout &DL = GV.getDataLayout(); | |
| const StructLayout *SL = DL.getStructLayout(Init->getType()); | |
| ArrayRef<TypeSize> MemberOffsets = SL->getMemberOffsets(); | |
| unsigned IndexWidth = DL.getIndexTypeSizeInBits(GV.getType()); | |
| // Verify that each user of the global is an inrange getelementptr constant, | |
| // and collect information on how it relates to the global. | |
| struct GEPInfo { | |
| GEPOperator *GEP; | |
| unsigned MemberIndex; | |
| APInt MemberRelativeOffset; | |
| GEPInfo(GEPOperator *GEP, unsigned MemberIndex, APInt MemberRelativeOffset) | |
| : GEP(GEP), MemberIndex(MemberIndex), | |
| MemberRelativeOffset(std::move(MemberRelativeOffset)) {} | |
| }; | |
| SmallVector<GEPInfo> Infos; | |
| for (User *U : GV.users()) { | |
| auto *GEP = dyn_cast<GEPOperator>(U); | |
| if (!GEP) | |
| return false; | |
| std::optional<ConstantRange> InRange = GEP->getInRange(); | |
| if (!InRange) | |
| return false; | |
| APInt Offset(IndexWidth, 0); | |
| if (!GEP->accumulateConstantOffset(DL, Offset)) | |
| return false; | |
| // Determine source-relative inrange. | |
| ConstantRange SrcInRange = InRange->sextOrTrunc(IndexWidth).add(Offset); | |
| // Check that the GEP offset is in the range (treating upper bound as | |
| // inclusive here). | |
| if (!SrcInRange.contains(Offset) && SrcInRange.getUpper() != Offset) | |
| return false; | |
| // Find which struct member the range corresponds to. | |
| if (SrcInRange.getLower().uge(SL->getSizeInBytes())) | |
| return false; | |
| unsigned MemberIndex = | |
| SL->getElementContainingOffset(SrcInRange.getLower().getZExtValue()); | |
| TypeSize MemberStart = MemberOffsets[MemberIndex]; | |
| TypeSize MemberEnd = MemberIndex == MemberOffsets.size() - 1 | |
| ? SL->getSizeInBytes() | |
| : MemberOffsets[MemberIndex + 1]; | |
| // Verify that the range matches that struct member. | |
| if (SrcInRange.getLower() != MemberStart || | |
| SrcInRange.getUpper() != MemberEnd) | |
| return false; | |
| Infos.emplace_back(GEP, MemberIndex, Offset - MemberStart); | |
| } | |
| SmallVector<MDNode *, 2> Types; | |
| GV.getMetadata(LLVMContext::MD_type, Types); | |
| IntegerType *Int32Ty = Type::getInt32Ty(GV.getContext()); | |
| std::vector<GlobalVariable *> SplitGlobals(Init->getNumOperands()); | |
| for (unsigned I = 0; I != Init->getNumOperands(); ++I) { | |
| // Build a global representing this split piece. | |
| auto *SplitGV = | |
| new GlobalVariable(*GV.getParent(), Init->getOperand(I)->getType(), | |
| GV.isConstant(), GlobalValue::PrivateLinkage, | |
| Init->getOperand(I), GV.getName() + "." + utostr(I)); | |
| SplitGlobals[I] = SplitGV; | |
| unsigned SplitBegin = SL->getElementOffset(I); | |
| unsigned SplitEnd = (I == Init->getNumOperands() - 1) | |
| ? SL->getSizeInBytes() | |
| : SL->getElementOffset(I + 1); | |
| // Rebuild type metadata, adjusting by the split offset. | |
| // FIXME: See if we can use DW_OP_piece to preserve debug metadata here. | |
| for (MDNode *Type : Types) { | |
| uint64_t ByteOffset = cast<ConstantInt>( | |
| cast<ConstantAsMetadata>(Type->getOperand(0))->getValue()) | |
| ->getZExtValue(); | |
| // Type metadata may be attached one byte after the end of the vtable, for | |
| // classes without virtual methods in Itanium ABI. AFAIK, it is never | |
| // attached to the first byte of a vtable. Subtract one to get the right | |
| // slice. | |
| // This is making an assumption that vtable groups are the only kinds of | |
| // global variables that !type metadata can be attached to, and that they | |
| // are either Itanium ABI vtable groups or contain a single vtable (i.e. | |
| // Microsoft ABI vtables). | |
| uint64_t AttachedTo = (ByteOffset == 0) ? ByteOffset : ByteOffset - 1; | |
| if (AttachedTo < SplitBegin || AttachedTo >= SplitEnd) | |
| continue; | |
| SplitGV->addMetadata( | |
| LLVMContext::MD_type, | |
| *MDNode::get(GV.getContext(), | |
| {ConstantAsMetadata::get( | |
| ConstantInt::get(Int32Ty, ByteOffset - SplitBegin)), | |
| Type->getOperand(1)})); | |
| } | |
| if (GV.hasMetadata(LLVMContext::MD_vcall_visibility)) | |
| SplitGV->setVCallVisibilityMetadata(GV.getVCallVisibility()); | |
| } | |
| for (const GEPInfo &Info : Infos) { | |
| assert(Info.MemberIndex < SplitGlobals.size() && "Invalid member"); | |
| auto *NewGEP = ConstantExpr::getPtrAdd( | |
| SplitGlobals[Info.MemberIndex], | |
| ConstantInt::get(GV.getContext(), Info.MemberRelativeOffset), | |
| Info.GEP->isInBounds()); | |
| Info.GEP->replaceAllUsesWith(NewGEP); | |
| } | |
| // Finally, remove the original global. Any remaining uses refer to invalid | |
| // elements of the global, so replace with poison. | |
| if (!GV.use_empty()) | |
| GV.replaceAllUsesWith(PoisonValue::get(GV.getType())); | |
| GV.eraseFromParent(); | |
| return true; | |
| } | |
| static bool splitGlobals(Module &M) { | |
| // First, see if the module uses either of the llvm.type.test or | |
| // llvm.type.checked.load intrinsics, which indicates that splitting globals | |
| // may be beneficial. | |
| Function *TypeTestFunc = | |
| Intrinsic::getDeclarationIfExists(&M, Intrinsic::type_test); | |
| Function *TypeCheckedLoadFunc = | |
| Intrinsic::getDeclarationIfExists(&M, Intrinsic::type_checked_load); | |
| Function *TypeCheckedLoadRelativeFunc = Intrinsic::getDeclarationIfExists( | |
| &M, Intrinsic::type_checked_load_relative); | |
| if ((!TypeTestFunc || TypeTestFunc->use_empty()) && | |
| (!TypeCheckedLoadFunc || TypeCheckedLoadFunc->use_empty()) && | |
| (!TypeCheckedLoadRelativeFunc || | |
| TypeCheckedLoadRelativeFunc->use_empty())) | |
| return false; | |
| bool Changed = false; | |
| for (GlobalVariable &GV : llvm::make_early_inc_range(M.globals())) | |
| Changed |= splitGlobal(GV); | |
| return Changed; | |
| } | |
| PreservedAnalyses GlobalSplitPass::run(Module &M, ModuleAnalysisManager &AM) { | |
| if (!splitGlobals(M)) | |
| return PreservedAnalyses::all(); | |
| return PreservedAnalyses::none(); | |
| } |