| //===--- ExpandMemCmp.cpp - Expand memcmp() to load/stores ----------------===// | |
| // | |
| // 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 tries to expand memcmp() calls into optimally-sized loads and | |
| // compares for the target. | |
| // | |
| //===----------------------------------------------------------------------===// | |
| #include "llvm/Transforms/Scalar/ExpandMemCmp.h" | |
| #include "ScalarOptions.h" | |
| #include "llvm/ADT/Statistic.h" | |
| #include "llvm/Analysis/ConstantFolding.h" | |
| #include "llvm/Analysis/DomTreeUpdater.h" | |
| #include "llvm/Analysis/LazyBlockFrequencyInfo.h" | |
| #include "llvm/Analysis/ProfileSummaryInfo.h" | |
| #include "llvm/Analysis/TargetLibraryInfo.h" | |
| #include "llvm/Analysis/TargetTransformInfo.h" | |
| #include "llvm/Analysis/ValueTracking.h" | |
| #include "llvm/IR/Dominators.h" | |
| #include "llvm/IR/IRBuilder.h" | |
| #include "llvm/IR/InstIterator.h" | |
| #include "llvm/IR/PatternMatch.h" | |
| #include "llvm/IR/ProfDataUtils.h" | |
| #include "llvm/Transforms/Utils/BasicBlockUtils.h" | |
| #include "llvm/Transforms/Utils/Local.h" | |
| #include "llvm/Transforms/Utils/SizeOpts.h" | |
| #include <optional> | |
| using namespace llvm; | |
| using namespace llvm::PatternMatch; | |
| #define DEBUG_TYPE "expand-memcmp" | |
| STATISTIC(NumMemCmpCalls, "Number of memcmp calls"); | |
| STATISTIC(NumMemCmpNotConstant, "Number of memcmp calls without constant size"); | |
| STATISTIC(NumMemCmpGreaterThanMax, | |
| "Number of memcmp calls with size greater than max size"); | |
| STATISTIC(NumMemCmpInlined, "Number of inlined memcmp calls"); | |
| namespace { | |
| // Return the known alignment of the pointer argument \p ArgNo of \p CI, | |
| // combining the alignment of the underlying pointer value with any align | |
| // attribute on the call site itself. | |
| static Align getMemCmpArgAlignment(const CallInst *CI, unsigned ArgNo, | |
| const DataLayout &DL) { | |
| Align A = CI->getArgOperand(ArgNo)->getPointerAlignment(DL); | |
| if (MaybeAlign ParamAlign = CI->getParamAlign(ArgNo)) | |
| A = std::max(A, *ParamAlign); | |
| return A; | |
| } | |
| // This class provides helper functions to expand a memcmp library call into an | |
| // inline expansion. | |
| class MemCmpExpansion { | |
| struct LoadPair { | |
| Value *Lhs = nullptr; | |
| Value *Rhs = nullptr; | |
| }; | |
| struct ResultBlock { | |
| BasicBlock *BB = nullptr; | |
| PHINode *PhiSrc1 = nullptr; | |
| PHINode *PhiSrc2 = nullptr; | |
| ResultBlock() = default; | |
| }; | |
| CallInst *const CI = nullptr; | |
| ResultBlock ResBlock; | |
| const uint64_t Size; | |
| unsigned MaxLoadSize = 0; | |
| const uint64_t NumLoadsPerBlock; | |
| const unsigned MaxBytesPerBlock; | |
| unsigned MaxBlockSize = 0; | |
| std::vector<BasicBlock *> LoadCmpBlocks; | |
| BasicBlock *EndBlock = nullptr; | |
| PHINode *PhiRes = nullptr; | |
| const bool IsUsedForZeroCmp; | |
| const DataLayout &DL; | |
| const TargetTransformInfo &TTI; | |
| // The known common alignment of the two source pointers. | |
| const Align CommonAlign; | |
| DomTreeUpdater *DTU = nullptr; | |
| IRBuilder<> Builder; | |
| // Represents the decomposition in blocks of the expansion. For example, | |
| // comparing 33 bytes on X86+sse can be done with 2x16-byte loads and | |
| // 1x1-byte load, which would be represented as [{16, 0}, {16, 16}, {1, 32}. | |
| struct LoadEntry { | |
| LoadEntry(unsigned LoadSize, uint64_t Offset) | |
| : LoadSize(LoadSize), Offset(Offset) { | |
| } | |
| // The size of the load for this block, in bytes. | |
| unsigned LoadSize; | |
| // The offset of this load from the base pointer, in bytes. | |
| uint64_t Offset; | |
| }; | |
| using LoadEntryVector = SmallVector<LoadEntry, 8>; | |
| LoadEntryVector LoadSequence; | |
| void createLoadCmpBlocks(); | |
| void createResultBlock(); | |
| void setupResultBlockPHINodes(); | |
| void setupEndBlockPHINodes(); | |
| Value *getCompareLoadPairs(unsigned BlockIndex, unsigned &LoadIndex); | |
| LoadPair getPackedLoadPair(unsigned BlockIndex, unsigned &LoadIndex); | |
| void emitLoadCompareBlock(unsigned BlockIndex, unsigned &LoadIndex); | |
| void emitLoadCompareBlockMultipleLoads(unsigned BlockIndex, | |
| unsigned &LoadIndex); | |
| void emitLoadCompareByteBlock(unsigned BlockIndex, unsigned OffsetBytes); | |
| void emitMemCmpResultBlock(); | |
| Value *getMemCmpExpansionZeroCase(); | |
| Value *getMemCmpEqZeroOneBlock(); | |
| Value *getMemCmpOneBlock(); | |
| Value *getMemCmpOneBlockMultipleLoads(); | |
| Value *getMemCmpResult(const LoadPair &Loads); | |
| LoadPair getLoadPair(Type *LoadSizeType, Type *BSwapSizeType, | |
| Type *CmpSizeType, unsigned OffsetBytes); | |
| // Return true if a load of `LoadSize` bytes at `Offset` from the base | |
| // pointers is accessible on the target: either it is naturally aligned given | |
| // the known common base alignment, or the target allows a misaligned access | |
| // of that width. | |
| bool isAccessAllowed(unsigned LoadSize, uint64_t Offset) const; | |
| static LoadEntryVector | |
| computeGreedyLoadSequence(uint64_t Size, llvm::ArrayRef<unsigned> LoadSizes, | |
| unsigned MaxNumLoads); | |
| LoadEntryVector computeOverlappingLoadSequence(uint64_t Size, | |
| unsigned MaxLoadSize, | |
| unsigned MaxNumLoads) const; | |
| void optimiseLoadSequence( | |
| LoadEntryVector &LoadSequence, | |
| const TargetTransformInfo::MemCmpExpansionOptions &Options, | |
| bool IsUsedForZeroCmp) const; | |
| public: | |
| MemCmpExpansion(CallInst *CI, uint64_t Size, | |
| const TargetTransformInfo::MemCmpExpansionOptions &Options, | |
| const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout, | |
| DomTreeUpdater *DTU, const TargetTransformInfo &TTI, | |
| Align CommonAlign, unsigned MaxBytesPerBlock); | |
| unsigned getNumBlocks(); | |
| unsigned getNumLoadsInBlock(unsigned LoadIndex) const; | |
| unsigned getNumBytesInBlock(unsigned LoadIndex, unsigned NumLoads) const; | |
| uint64_t getNumLoads() const { return LoadSequence.size(); } | |
| Value *getMemCmpExpansion(); | |
| }; | |
| // Return true if a load of `LoadSize` bytes at `Offset` from the base pointers | |
| // is accessible on the target: either it is naturally aligned given the known | |
| // common base alignment, or the target allows a misaligned access of that | |
| // width. We query whether the access is *allowed*, not whether it is *fast*, | |
| // matching the historical behavior of forming unaligned loads whenever the | |
| // target permits them. | |
| static bool isAccessAllowed(const CallInst *CI, const TargetTransformInfo &TTI, | |
| Align CommonAlign, unsigned LoadSize, | |
| uint64_t Offset) { | |
| // The access is naturally aligned when the known alignment is at least the | |
| // load width. LoadSize is not necessarily a power of two here: some targets | |
| // like RISC-V add non-power-of-two load sizes for vector memcmp, so compare | |
| // against the raw width rather than constructing an Align, which would | |
| // require a power of two. | |
| Align AccessAlign = commonAlignment(CommonAlign, Offset); | |
| if (AccessAlign.value() >= LoadSize) | |
| return true; | |
| unsigned AS = CI->getArgOperand(0)->getType()->getPointerAddressSpace(); | |
| return TTI.allowsMisalignedMemoryAccesses(CI->getContext(), LoadSize * 8, AS, | |
| AccessAlign); | |
| } | |
| // Return true if a load of `LoadSize` bytes at `Offset` from the base pointers | |
| // is accessible on the target given the known common base alignment. This gates | |
| // the (power-of-two) overlapping loads; tail expansions are always legalized by | |
| // the backend and skip this check. | |
| bool MemCmpExpansion::isAccessAllowed(unsigned LoadSize, | |
| uint64_t Offset) const { | |
| return ::isAccessAllowed(CI, TTI, CommonAlign, LoadSize, Offset); | |
| } | |
| MemCmpExpansion::LoadEntryVector | |
| MemCmpExpansion::computeGreedyLoadSequence(uint64_t Size, | |
| llvm::ArrayRef<unsigned> LoadSizes, | |
| const unsigned MaxNumLoads) { | |
| LoadEntryVector LoadSequence; | |
| uint64_t Offset = 0; | |
| while (Size && !LoadSizes.empty()) { | |
| const unsigned LoadSize = LoadSizes.front(); | |
| const uint64_t NumLoadsForThisSize = Size / LoadSize; | |
| if (LoadSequence.size() + NumLoadsForThisSize > MaxNumLoads) { | |
| // Do not expand if the total number of loads is larger than what the | |
| // target allows. Note that it's important that we exit before completing | |
| // the expansion to avoid using a ton of memory to store the expansion for | |
| // large sizes. | |
| return {}; | |
| } | |
| if (NumLoadsForThisSize > 0) { | |
| for (uint64_t I = 0; I < NumLoadsForThisSize; ++I) { | |
| LoadSequence.push_back({LoadSize, Offset}); | |
| Offset += LoadSize; | |
| } | |
| Size = Size % LoadSize; | |
| } | |
| LoadSizes = LoadSizes.drop_front(); | |
| } | |
| return LoadSequence; | |
| } | |
| MemCmpExpansion::LoadEntryVector | |
| MemCmpExpansion::computeOverlappingLoadSequence( | |
| uint64_t Size, const unsigned MaxLoadSize, | |
| const unsigned MaxNumLoads) const { | |
| // These are already handled by the greedy approach. | |
| if (Size < 2 || MaxLoadSize < 2) | |
| return {}; | |
| // We try to do as many non-overlapping loads as possible starting from the | |
| // beginning. | |
| const uint64_t NumNonOverlappingLoads = Size / MaxLoadSize; | |
| assert(NumNonOverlappingLoads && "there must be at least one load"); | |
| // There remain 0 to (MaxLoadSize - 1) bytes to load, this will be done with | |
| // an overlapping load. | |
| Size = Size - NumNonOverlappingLoads * MaxLoadSize; | |
| // Bail if we do not need an overloapping store, this is already handled by | |
| // the greedy approach. | |
| if (Size == 0) | |
| return {}; | |
| // Bail if the number of loads (non-overlapping + potential overlapping one) | |
| // is larger than the max allowed. | |
| if ((NumNonOverlappingLoads + 1) > MaxNumLoads) | |
| return {}; | |
| // Add non-overlapping loads. | |
| LoadEntryVector LoadSequence; | |
| uint64_t Offset = 0; | |
| for (uint64_t I = 0; I < NumNonOverlappingLoads; ++I) { | |
| LoadSequence.push_back({MaxLoadSize, Offset}); | |
| Offset += MaxLoadSize; | |
| } | |
| // Add the last overlapping load. Its offset is not a multiple of the load | |
| // size, so it may be misaligned; bail if the target cannot access it. | |
| assert(Size > 0 && Size < MaxLoadSize && "broken invariant"); | |
| uint64_t OverlapOffset = Offset - (MaxLoadSize - Size); | |
| if (!isAccessAllowed(MaxLoadSize, OverlapOffset)) | |
| return {}; | |
| LoadSequence.push_back({MaxLoadSize, OverlapOffset}); | |
| return LoadSequence; | |
| } | |
| void MemCmpExpansion::optimiseLoadSequence( | |
| LoadEntryVector &LoadSequence, | |
| const TargetTransformInfo::MemCmpExpansionOptions &Options, | |
| bool IsUsedForZeroCmp) const { | |
| // This part of code attempts to optimize the LoadSequence by merging allowed | |
| // subsequences into single loads of allowed sizes from | |
| // `MemCmpExpansionOptions::AllowedTailExpansions`. If it is for zero | |
| // comparison or if no allowed tail expansions are specified, we exit early. | |
| if (IsUsedForZeroCmp || Options.AllowedTailExpansions.empty()) | |
| return; | |
| while (LoadSequence.size() >= 2) { | |
| auto Last = LoadSequence[LoadSequence.size() - 1]; | |
| auto PreLast = LoadSequence[LoadSequence.size() - 2]; | |
| // Exit the loop if the two sequences are not contiguous | |
| if (PreLast.Offset + PreLast.LoadSize != Last.Offset) | |
| break; | |
| auto LoadSize = Last.LoadSize + PreLast.LoadSize; | |
| if (find(Options.AllowedTailExpansions, LoadSize) == | |
| Options.AllowedTailExpansions.end()) | |
| break; | |
| // A merged load wider than MaxLoadSize can only be emitted when it is the | |
| // sole load (getMemCmpOneBlock); in a multi-block expansion | |
| // emitLoadCompareBlock requires every load to fit in MaxLoadSize (the | |
| // result-block phis are sized to it). The per-call-site alignment filter | |
| // can shrink MaxLoadSize, so stop merging when the result would still be | |
| // multi-block and the merged load exceeds it. | |
| if (LoadSize > MaxLoadSize && LoadSequence.size() > 2) | |
| break; | |
| // Remove the last two sequences and replace with the combined sequence | |
| LoadSequence.pop_back(); | |
| LoadSequence.pop_back(); | |
| LoadSequence.emplace_back(LoadSize, PreLast.Offset); | |
| } | |
| } | |
| // Initialize the basic block structure required for expansion of memcmp call | |
| // with given maximum load size and memcmp size parameter. | |
| // This structure includes: | |
| // 1. A list of load compare blocks - LoadCmpBlocks. | |
| // 2. An EndBlock, split from original instruction point, which is the block to | |
| // return from. | |
| // 3. ResultBlock, block to branch to for early exit when a | |
| // LoadCmpBlock finds a difference. | |
| MemCmpExpansion::MemCmpExpansion( | |
| CallInst *const CI, uint64_t Size, | |
| const TargetTransformInfo::MemCmpExpansionOptions &Options, | |
| const bool IsUsedForZeroCmp, const DataLayout &TheDataLayout, | |
| DomTreeUpdater *DTU, const TargetTransformInfo &TTI, Align CommonAlign, | |
| unsigned MaxBytesPerBlock) | |
| : CI(CI), Size(Size), NumLoadsPerBlock(Options.NumLoadsPerBlock), | |
| MaxBytesPerBlock(MaxBytesPerBlock), IsUsedForZeroCmp(IsUsedForZeroCmp), | |
| DL(TheDataLayout), TTI(TTI), CommonAlign(CommonAlign), DTU(DTU), | |
| Builder(CI) { | |
| assert(Size > 0 && "zero blocks"); | |
| assert(NumLoadsPerBlock > 0 && "zero loads per block"); | |
| // Scale the max size down if the target can load more bytes than we need. | |
| llvm::ArrayRef<unsigned> LoadSizes(Options.LoadSizes); | |
| while (!LoadSizes.empty() && LoadSizes.front() > Size) { | |
| LoadSizes = LoadSizes.drop_front(); | |
| } | |
| assert(!LoadSizes.empty() && "cannot load Size bytes"); | |
| MaxLoadSize = LoadSizes.front(); | |
| // Compute the decomposition. | |
| LoadSequence = | |
| computeGreedyLoadSequence(Size, LoadSizes, Options.MaxNumLoads); | |
| assert(LoadSequence.size() <= Options.MaxNumLoads && "broken invariant"); | |
| // If we allow overlapping loads and the load sequence is not already optimal, | |
| // use overlapping loads. | |
| if (Options.AllowOverlappingLoads && | |
| (LoadSequence.empty() || LoadSequence.size() > 2)) { | |
| auto OverlappingLoads = | |
| computeOverlappingLoadSequence(Size, MaxLoadSize, Options.MaxNumLoads); | |
| if (!OverlappingLoads.empty() && | |
| (LoadSequence.empty() || | |
| OverlappingLoads.size() < LoadSequence.size())) { | |
| LoadSequence = OverlappingLoads; | |
| } | |
| } | |
| assert(LoadSequence.size() <= Options.MaxNumLoads && "broken invariant"); | |
| optimiseLoadSequence(LoadSequence, Options, IsUsedForZeroCmp); | |
| unsigned LoadIndex = 0; | |
| while (LoadIndex < getNumLoads()) { | |
| unsigned NumLoads = getNumLoadsInBlock(LoadIndex); | |
| MaxBlockSize = | |
| std::max(MaxBlockSize, getNumBytesInBlock(LoadIndex, NumLoads)); | |
| LoadIndex += NumLoads; | |
| } | |
| if (MaxBlockSize) | |
| MaxBlockSize = PowerOf2Ceil(MaxBlockSize); | |
| } | |
| unsigned MemCmpExpansion::getNumLoadsInBlock(unsigned LoadIndex) const { | |
| if (IsUsedForZeroCmp) | |
| return std::min<uint64_t>(getNumLoads() - LoadIndex, NumLoadsPerBlock); | |
| unsigned NumLoads = 0; | |
| unsigned NumBytes = 0; | |
| while (LoadIndex + NumLoads < getNumLoads() && NumLoads < NumLoadsPerBlock && | |
| NumBytes + LoadSequence[LoadIndex + NumLoads].LoadSize <= | |
| MaxBytesPerBlock) { | |
| NumBytes += LoadSequence[LoadIndex + NumLoads].LoadSize; | |
| ++NumLoads; | |
| } | |
| assert(NumLoads && "at least one load must fit in a block"); | |
| return NumLoads; | |
| } | |
| unsigned MemCmpExpansion::getNumBytesInBlock(unsigned LoadIndex, | |
| unsigned NumLoads) const { | |
| unsigned NumBytes = 0; | |
| for (unsigned I = 0; I != NumLoads; ++I) | |
| NumBytes += LoadSequence[LoadIndex + I].LoadSize; | |
| return NumBytes; | |
| } | |
| unsigned MemCmpExpansion::getNumBlocks() { | |
| unsigned NumBlocks = 0; | |
| for (unsigned LoadIndex = 0; LoadIndex < getNumLoads(); ++NumBlocks) | |
| LoadIndex += getNumLoadsInBlock(LoadIndex); | |
| return NumBlocks; | |
| } | |
| void MemCmpExpansion::createLoadCmpBlocks() { | |
| for (unsigned i = 0; i < getNumBlocks(); i++) { | |
| BasicBlock *BB = BasicBlock::Create(CI->getContext(), "loadbb", | |
| EndBlock->getParent(), EndBlock); | |
| LoadCmpBlocks.push_back(BB); | |
| } | |
| } | |
| void MemCmpExpansion::createResultBlock() { | |
| ResBlock.BB = BasicBlock::Create(CI->getContext(), "res_block", | |
| EndBlock->getParent(), EndBlock); | |
| } | |
| MemCmpExpansion::LoadPair MemCmpExpansion::getLoadPair(Type *LoadSizeType, | |
| Type *BSwapSizeType, | |
| Type *CmpSizeType, | |
| unsigned OffsetBytes) { | |
| // Get the memory source at offset `OffsetBytes`. | |
| Value *LhsSource = CI->getArgOperand(0); | |
| Value *RhsSource = CI->getArgOperand(1); | |
| Align LhsAlign = getMemCmpArgAlignment(CI, 0, DL); | |
| Align RhsAlign = getMemCmpArgAlignment(CI, 1, DL); | |
| if (OffsetBytes > 0) { | |
| auto *ByteType = Type::getInt8Ty(CI->getContext()); | |
| LhsSource = Builder.CreateConstGEP1_64(ByteType, LhsSource, OffsetBytes); | |
| RhsSource = Builder.CreateConstGEP1_64(ByteType, RhsSource, OffsetBytes); | |
| LhsAlign = commonAlignment(LhsAlign, OffsetBytes); | |
| RhsAlign = commonAlignment(RhsAlign, OffsetBytes); | |
| } | |
| // Create a constant or a load from the source. | |
| Value *Lhs = nullptr; | |
| if (auto *C = dyn_cast<Constant>(LhsSource)) | |
| Lhs = ConstantFoldLoadFromConstPtr(C, LoadSizeType, DL); | |
| if (!Lhs) | |
| Lhs = Builder.CreateAlignedLoad(LoadSizeType, LhsSource, LhsAlign); | |
| Value *Rhs = nullptr; | |
| if (auto *C = dyn_cast<Constant>(RhsSource)) | |
| Rhs = ConstantFoldLoadFromConstPtr(C, LoadSizeType, DL); | |
| if (!Rhs) | |
| Rhs = Builder.CreateAlignedLoad(LoadSizeType, RhsSource, RhsAlign); | |
| // Zero extend if Byte Swap intrinsic has different type | |
| if (BSwapSizeType && LoadSizeType != BSwapSizeType) { | |
| Lhs = Builder.CreateZExt(Lhs, BSwapSizeType); | |
| Rhs = Builder.CreateZExt(Rhs, BSwapSizeType); | |
| } | |
| // Swap bytes if required. | |
| if (BSwapSizeType) { | |
| Function *Bswap = Intrinsic::getOrInsertDeclaration( | |
| CI->getModule(), Intrinsic::bswap, BSwapSizeType); | |
| Lhs = Builder.CreateCall(Bswap, Lhs); | |
| Rhs = Builder.CreateCall(Bswap, Rhs); | |
| } | |
| // Zero extend if required. | |
| if (CmpSizeType != nullptr && CmpSizeType != Lhs->getType()) { | |
| Lhs = Builder.CreateZExt(Lhs, CmpSizeType); | |
| Rhs = Builder.CreateZExt(Rhs, CmpSizeType); | |
| } | |
| return {Lhs, Rhs}; | |
| } | |
| // This function creates the IR instructions for loading and comparing 1 byte. | |
| // It loads 1 byte from each source of the memcmp parameters with the given | |
| // GEPIndex. It then subtracts the two loaded values and adds this result to the | |
| // final phi node for selecting the memcmp result. | |
| void MemCmpExpansion::emitLoadCompareByteBlock(unsigned BlockIndex, | |
| unsigned OffsetBytes) { | |
| BasicBlock *BB = LoadCmpBlocks[BlockIndex]; | |
| Builder.SetInsertPoint(BB); | |
| const LoadPair Loads = | |
| getLoadPair(Type::getInt8Ty(CI->getContext()), nullptr, | |
| Type::getInt32Ty(CI->getContext()), OffsetBytes); | |
| Value *Diff = Builder.CreateSub(Loads.Lhs, Loads.Rhs); | |
| PhiRes->addIncoming(Diff, BB); | |
| if (BlockIndex < (LoadCmpBlocks.size() - 1)) { | |
| // Early exit branch if difference found to EndBlock. Otherwise, continue to | |
| // next LoadCmpBlock, | |
| Value *Cmp = Builder.CreateICmp(ICmpInst::ICMP_NE, Diff, | |
| ConstantInt::get(Diff->getType(), 0)); | |
| Builder.CreateCondBr(Cmp, EndBlock, LoadCmpBlocks[BlockIndex + 1]); | |
| if (DTU) | |
| DTU->applyUpdates( | |
| {{DominatorTree::Insert, BB, EndBlock}, | |
| {DominatorTree::Insert, BB, LoadCmpBlocks[BlockIndex + 1]}}); | |
| } else { | |
| // The last block has an unconditional branch to EndBlock. | |
| Builder.CreateBr(EndBlock); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, BB, EndBlock}}); | |
| } | |
| } | |
| /// Generate an equality comparison for one or more pairs of loaded values. | |
| /// This is used in the case where the memcmp() call is compared equal or not | |
| /// equal to zero. | |
| Value *MemCmpExpansion::getCompareLoadPairs(unsigned BlockIndex, | |
| unsigned &LoadIndex) { | |
| assert(LoadIndex < getNumLoads() && | |
| "getCompareLoadPairs() called with no remaining loads"); | |
| std::vector<Value *> XorList, OrList; | |
| Value *Diff = nullptr; | |
| const unsigned NumLoads = getNumLoadsInBlock(LoadIndex); | |
| // For a single-block expansion, start inserting before the memcmp call. | |
| if (LoadCmpBlocks.empty()) | |
| Builder.SetInsertPoint(CI); | |
| else | |
| Builder.SetInsertPoint(LoadCmpBlocks[BlockIndex]); | |
| Value *Cmp = nullptr; | |
| // If we have multiple loads per block, we need to generate a composite | |
| // comparison using xor+or. The type for the combinations is the largest load | |
| // type. | |
| IntegerType *const MaxLoadType = | |
| NumLoads == 1 ? nullptr | |
| : IntegerType::get(CI->getContext(), MaxLoadSize * 8); | |
| for (unsigned i = 0; i < NumLoads; ++i, ++LoadIndex) { | |
| const LoadEntry &CurLoadEntry = LoadSequence[LoadIndex]; | |
| const LoadPair Loads = getLoadPair( | |
| IntegerType::get(CI->getContext(), CurLoadEntry.LoadSize * 8), nullptr, | |
| MaxLoadType, CurLoadEntry.Offset); | |
| if (NumLoads != 1) { | |
| // If we have multiple loads per block, we need to generate a composite | |
| // comparison using xor+or. | |
| Diff = Builder.CreateXor(Loads.Lhs, Loads.Rhs); | |
| Diff = Builder.CreateZExt(Diff, MaxLoadType); | |
| XorList.push_back(Diff); | |
| } else { | |
| // If there's only one load per block, we just compare the loaded values. | |
| Cmp = Builder.CreateICmpNE(Loads.Lhs, Loads.Rhs); | |
| } | |
| } | |
| auto pairWiseOr = [&](std::vector<Value *> &InList) -> std::vector<Value *> { | |
| std::vector<Value *> OutList; | |
| for (unsigned i = 0; i < InList.size() - 1; i = i + 2) { | |
| Value *Or = Builder.CreateOr(InList[i], InList[i + 1]); | |
| OutList.push_back(Or); | |
| } | |
| if (InList.size() % 2 != 0) | |
| OutList.push_back(InList.back()); | |
| return OutList; | |
| }; | |
| if (!Cmp) { | |
| // Pairwise OR the XOR results. | |
| OrList = pairWiseOr(XorList); | |
| // Pairwise OR the OR results until one result left. | |
| while (OrList.size() != 1) { | |
| OrList = pairWiseOr(OrList); | |
| } | |
| assert(Diff && "Failed to find comparison diff"); | |
| Cmp = Builder.CreateICmpNE(OrList[0], ConstantInt::get(Diff->getType(), 0)); | |
| } | |
| return Cmp; | |
| } | |
| MemCmpExpansion::LoadPair | |
| MemCmpExpansion::getPackedLoadPair(unsigned BlockIndex, unsigned &LoadIndex) { | |
| assert(LoadIndex < getNumLoads() && | |
| "getPackedLoadPair() called with no remaining loads"); | |
| if (LoadCmpBlocks.empty()) | |
| Builder.SetInsertPoint(CI); | |
| else | |
| Builder.SetInsertPoint(LoadCmpBlocks[BlockIndex]); | |
| const unsigned NumLoads = getNumLoadsInBlock(LoadIndex); | |
| const unsigned NumBytes = getNumBytesInBlock(LoadIndex, NumLoads); | |
| // Pack the loads so that the byte at the lowest address occupies the most | |
| // significant bits. An unsigned comparison of the packed values therefore | |
| // has the same lexicographic ordering as memcmp. | |
| auto *BlockType = IntegerType::get(CI->getContext(), MaxBlockSize * 8); | |
| Value *PackedLhs = ConstantInt::get(BlockType, 0); | |
| Value *PackedRhs = ConstantInt::get(BlockType, 0); | |
| unsigned RemainingBytes = NumBytes; | |
| for (unsigned I = 0; I != NumLoads; ++I, ++LoadIndex) { | |
| const LoadEntry &Entry = LoadSequence[LoadIndex]; | |
| auto *LoadType = IntegerType::get(CI->getContext(), Entry.LoadSize * 8); | |
| auto *BSwapType = DL.isLittleEndian() && Entry.LoadSize != 1 | |
| ? IntegerType::get(CI->getContext(), | |
| PowerOf2Ceil(Entry.LoadSize * 8)) | |
| : nullptr; | |
| LoadPair Loads = getLoadPair(LoadType, BSwapType, BlockType, Entry.Offset); | |
| if (BSwapType && BSwapType->getIntegerBitWidth() != Entry.LoadSize * 8) { | |
| unsigned Padding = BSwapType->getIntegerBitWidth() - Entry.LoadSize * 8; | |
| Loads.Lhs = Builder.CreateLShr(Loads.Lhs, Padding); | |
| Loads.Rhs = Builder.CreateLShr(Loads.Rhs, Padding); | |
| } | |
| RemainingBytes -= Entry.LoadSize; | |
| unsigned Shift = RemainingBytes * 8; | |
| PackedLhs = | |
| Builder.CreateOr(PackedLhs, Builder.CreateShl(Loads.Lhs, Shift)); | |
| PackedRhs = | |
| Builder.CreateOr(PackedRhs, Builder.CreateShl(Loads.Rhs, Shift)); | |
| } | |
| return {PackedLhs, PackedRhs}; | |
| } | |
| void MemCmpExpansion::emitLoadCompareBlockMultipleLoads(unsigned BlockIndex, | |
| unsigned &LoadIndex) { | |
| Value *Cmp = getCompareLoadPairs(BlockIndex, LoadIndex); | |
| BasicBlock *NextBB = (BlockIndex == (LoadCmpBlocks.size() - 1)) | |
| ? EndBlock | |
| : LoadCmpBlocks[BlockIndex + 1]; | |
| // Early exit branch if difference found to ResultBlock. Otherwise, | |
| // continue to next LoadCmpBlock or EndBlock. | |
| BasicBlock *BB = Builder.GetInsertBlock(); | |
| CondBrInst *CmpBr = Builder.CreateCondBr(Cmp, ResBlock.BB, NextBB); | |
| setExplicitlyUnknownBranchWeightsIfProfiled(*CmpBr, DEBUG_TYPE, | |
| CI->getFunction()); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, BB, ResBlock.BB}, | |
| {DominatorTree::Insert, BB, NextBB}}); | |
| // Add a phi edge for the last LoadCmpBlock to Endblock with a value of 0 | |
| // since early exit to ResultBlock was not taken (no difference was found in | |
| // any of the bytes). | |
| if (BlockIndex == LoadCmpBlocks.size() - 1) { | |
| Value *Zero = ConstantInt::get(Type::getInt32Ty(CI->getContext()), 0); | |
| PhiRes->addIncoming(Zero, LoadCmpBlocks[BlockIndex]); | |
| } | |
| } | |
| // This function creates the IR intructions for loading and comparing using the | |
| // given LoadSize. It loads the number of bytes specified by LoadSize from each | |
| // source of the memcmp parameters. It then does a subtract to see if there was | |
| // a difference in the loaded values. If a difference is found, it branches | |
| // with an early exit to the ResultBlock for calculating which source was | |
| // larger. Otherwise, it falls through to the either the next LoadCmpBlock or | |
| // the EndBlock if this is the last LoadCmpBlock. Loading 1 byte is handled with | |
| // a special case through emitLoadCompareByteBlock. The special handling can | |
| // simply subtract the loaded values and add it to the result phi node. | |
| void MemCmpExpansion::emitLoadCompareBlock(unsigned BlockIndex, | |
| unsigned &LoadIndex) { | |
| const unsigned NumLoads = getNumLoadsInBlock(LoadIndex); | |
| if (NumLoads == 1 && LoadSequence[LoadIndex].LoadSize == 1) { | |
| MemCmpExpansion::emitLoadCompareByteBlock(BlockIndex, | |
| LoadSequence[LoadIndex].Offset); | |
| ++LoadIndex; | |
| return; | |
| } | |
| LoadPair Loads; | |
| if (NumLoads == 1) { | |
| const LoadEntry &Entry = LoadSequence[LoadIndex++]; | |
| auto *LoadType = IntegerType::get(CI->getContext(), Entry.LoadSize * 8); | |
| auto *BSwapType = DL.isLittleEndian() | |
| ? IntegerType::get(CI->getContext(), | |
| PowerOf2Ceil(Entry.LoadSize * 8)) | |
| : nullptr; | |
| auto *CmpType = IntegerType::get(CI->getContext(), MaxBlockSize * 8); | |
| Builder.SetInsertPoint(LoadCmpBlocks[BlockIndex]); | |
| Loads = getLoadPair(LoadType, BSwapType, CmpType, Entry.Offset); | |
| } else { | |
| Loads = getPackedLoadPair(BlockIndex, LoadIndex); | |
| } | |
| // Add the loaded values to the phi nodes for calculating memcmp result only | |
| // if result is not used in a zero equality. | |
| if (!IsUsedForZeroCmp) { | |
| ResBlock.PhiSrc1->addIncoming(Loads.Lhs, LoadCmpBlocks[BlockIndex]); | |
| ResBlock.PhiSrc2->addIncoming(Loads.Rhs, LoadCmpBlocks[BlockIndex]); | |
| } | |
| Value *Cmp = Builder.CreateICmp(ICmpInst::ICMP_EQ, Loads.Lhs, Loads.Rhs); | |
| BasicBlock *NextBB = (BlockIndex == (LoadCmpBlocks.size() - 1)) | |
| ? EndBlock | |
| : LoadCmpBlocks[BlockIndex + 1]; | |
| // Early exit branch if difference found to ResultBlock. Otherwise, continue | |
| // to next LoadCmpBlock or EndBlock. | |
| BasicBlock *BB = Builder.GetInsertBlock(); | |
| CondBrInst *CmpBr = Builder.CreateCondBr(Cmp, NextBB, ResBlock.BB); | |
| setExplicitlyUnknownBranchWeightsIfProfiled(*CmpBr, DEBUG_TYPE, | |
| CI->getFunction()); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, BB, NextBB}, | |
| {DominatorTree::Insert, BB, ResBlock.BB}}); | |
| // Add a phi edge for the last LoadCmpBlock to Endblock with a value of 0 | |
| // since early exit to ResultBlock was not taken (no difference was found in | |
| // any of the bytes). | |
| if (BlockIndex == LoadCmpBlocks.size() - 1) { | |
| Value *Zero = ConstantInt::get(Type::getInt32Ty(CI->getContext()), 0); | |
| PhiRes->addIncoming(Zero, LoadCmpBlocks[BlockIndex]); | |
| } | |
| } | |
| // This function populates the ResultBlock with a sequence to calculate the | |
| // memcmp result. It compares the two loaded source values and returns -1 if | |
| // src1 < src2 and 1 if src1 > src2. | |
| void MemCmpExpansion::emitMemCmpResultBlock() { | |
| // Special case: if memcmp result is used in a zero equality, result does not | |
| // need to be calculated and can simply return 1. | |
| if (IsUsedForZeroCmp) { | |
| BasicBlock::iterator InsertPt = ResBlock.BB->getFirstInsertionPt(); | |
| Builder.SetInsertPoint(InsertPt); | |
| Value *Res = ConstantInt::get(Type::getInt32Ty(CI->getContext()), 1); | |
| PhiRes->addIncoming(Res, ResBlock.BB); | |
| Builder.CreateBr(EndBlock); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, ResBlock.BB, EndBlock}}); | |
| return; | |
| } | |
| BasicBlock::iterator InsertPt = ResBlock.BB->getFirstInsertionPt(); | |
| Builder.SetInsertPoint(InsertPt); | |
| Value *Cmp = Builder.CreateICmp(ICmpInst::ICMP_ULT, ResBlock.PhiSrc1, | |
| ResBlock.PhiSrc2); | |
| Value *Res = | |
| Builder.CreateSelect(Cmp, Constant::getAllOnesValue(Builder.getInt32Ty()), | |
| ConstantInt::get(Builder.getInt32Ty(), 1)); | |
| setExplicitlyUnknownBranchWeightsIfProfiled(*cast<Instruction>(Res), | |
| DEBUG_TYPE, CI->getFunction()); | |
| PhiRes->addIncoming(Res, ResBlock.BB); | |
| Builder.CreateBr(EndBlock); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, ResBlock.BB, EndBlock}}); | |
| } | |
| void MemCmpExpansion::setupResultBlockPHINodes() { | |
| Type *MaxLoadType = IntegerType::get(CI->getContext(), MaxBlockSize * 8); | |
| Builder.SetInsertPoint(ResBlock.BB); | |
| ResBlock.PhiSrc1 = Builder.CreatePHI(MaxLoadType, getNumBlocks(), "phi.src1"); | |
| ResBlock.PhiSrc2 = Builder.CreatePHI(MaxLoadType, getNumBlocks(), "phi.src2"); | |
| } | |
| void MemCmpExpansion::setupEndBlockPHINodes() { | |
| Builder.SetInsertPoint(EndBlock->begin()); | |
| PhiRes = Builder.CreatePHI(Type::getInt32Ty(CI->getContext()), 2, "phi.res"); | |
| } | |
| Value *MemCmpExpansion::getMemCmpExpansionZeroCase() { | |
| unsigned LoadIndex = 0; | |
| // This loop populates each of the LoadCmpBlocks with the IR sequence to | |
| // handle multiple loads per block. | |
| for (unsigned I = 0; I < getNumBlocks(); ++I) { | |
| emitLoadCompareBlockMultipleLoads(I, LoadIndex); | |
| } | |
| emitMemCmpResultBlock(); | |
| return PhiRes; | |
| } | |
| /// A memcmp expansion that compares equality with 0 and only has one block of | |
| /// load and compare can bypass the compare, branch, and phi IR that is required | |
| /// in the general case. | |
| Value *MemCmpExpansion::getMemCmpEqZeroOneBlock() { | |
| unsigned LoadIndex = 0; | |
| Value *Cmp = getCompareLoadPairs(0, LoadIndex); | |
| assert(LoadIndex == getNumLoads() && "some entries were not consumed"); | |
| return Builder.CreateZExt(Cmp, Type::getInt32Ty(CI->getContext())); | |
| } | |
| /// A memcmp expansion that only has one block of load and compare can bypass | |
| /// the compare, branch, and phi IR that is required in the general case. | |
| /// This function also analyses users of memcmp, and if there is only one user | |
| /// from which we can conclude that only 2 out of 3 memcmp outcomes really | |
| /// matter, then it generates more efficient code with only one comparison. | |
| Value *MemCmpExpansion::getMemCmpOneBlock() { | |
| bool NeedsBSwap = DL.isLittleEndian() && Size != 1; | |
| Type *LoadSizeType = IntegerType::get(CI->getContext(), Size * 8); | |
| Type *BSwapSizeType = | |
| NeedsBSwap ? IntegerType::get(CI->getContext(), PowerOf2Ceil(Size * 8)) | |
| : nullptr; | |
| Type *MaxLoadType = | |
| IntegerType::get(CI->getContext(), | |
| std::max(MaxLoadSize, (unsigned)PowerOf2Ceil(Size)) * 8); | |
| // The i8 and i16 cases don't need compares. We zext the loaded values and | |
| // subtract them to get the suitable negative, zero, or positive i32 result. | |
| if (Size == 1 || Size == 2) { | |
| const LoadPair Loads = getLoadPair(LoadSizeType, BSwapSizeType, | |
| Builder.getInt32Ty(), /*Offset*/ 0); | |
| return Builder.CreateSub(Loads.Lhs, Loads.Rhs); | |
| } | |
| const LoadPair Loads = getLoadPair(LoadSizeType, BSwapSizeType, MaxLoadType, | |
| /*Offset*/ 0); | |
| return getMemCmpResult(Loads); | |
| } | |
| Value *MemCmpExpansion::getMemCmpOneBlockMultipleLoads() { | |
| unsigned LoadIndex = 0; | |
| LoadPair Loads = getPackedLoadPair(/*BlockIndex=*/0, LoadIndex); | |
| assert(LoadIndex == getNumLoads() && "some entries were not consumed"); | |
| return getMemCmpResult(Loads); | |
| } | |
| Value *MemCmpExpansion::getMemCmpResult(const LoadPair &Loads) { | |
| // If a user of memcmp cares only about two outcomes, for example: | |
| // bool result = memcmp(a, b, NBYTES) > 0; | |
| // We can generate more optimal code with a smaller number of operations | |
| if (CI->hasOneUser()) { | |
| auto *UI = cast<Instruction>(*CI->user_begin()); | |
| CmpPredicate Pred = ICmpInst::Predicate::BAD_ICMP_PREDICATE; | |
| bool NeedsZExt = false; | |
| // This is a special case because instead of checking if the result is less | |
| // than zero: | |
| // bool result = memcmp(a, b, NBYTES) < 0; | |
| // Compiler is clever enough to generate the following code: | |
| // bool result = memcmp(a, b, NBYTES) >> 31; | |
| if (match(UI, | |
| m_LShr(m_Value(), | |
| m_SpecificInt(CI->getType()->getIntegerBitWidth() - 1)))) { | |
| Pred = ICmpInst::ICMP_SLT; | |
| NeedsZExt = true; | |
| } else if (match(UI, m_SpecificICmp(ICmpInst::ICMP_SGT, m_Specific(CI), | |
| m_AllOnes()))) { | |
| // Adjust predicate as if it compared with 0. | |
| Pred = ICmpInst::ICMP_SGE; | |
| } else if (match(UI, m_SpecificICmp(ICmpInst::ICMP_SLT, m_Specific(CI), | |
| m_One()))) { | |
| // Adjust predicate as if it compared with 0. | |
| Pred = ICmpInst::ICMP_SLE; | |
| } else { | |
| // In case of a successful match this call will set `Pred` variable | |
| match(UI, m_ICmp(Pred, m_Specific(CI), m_Zero())); | |
| } | |
| // Generate new code and remove the original memcmp call and the user | |
| if (ICmpInst::isSigned(Pred)) { | |
| Value *Cmp = Builder.CreateICmp(ICmpInst::getUnsignedPredicate(Pred), | |
| Loads.Lhs, Loads.Rhs); | |
| auto *Result = NeedsZExt ? Builder.CreateZExt(Cmp, UI->getType()) : Cmp; | |
| UI->replaceAllUsesWith(Result); | |
| UI->eraseFromParent(); | |
| CI->eraseFromParent(); | |
| return nullptr; | |
| } | |
| } | |
| // The result of memcmp is negative, zero, or positive. | |
| return Builder.CreateIntrinsic(Builder.getInt32Ty(), Intrinsic::ucmp, | |
| {Loads.Lhs, Loads.Rhs}); | |
| } | |
| // This function expands the memcmp call into an inline expansion and returns | |
| // the memcmp result. Returns nullptr if the memcmp is already replaced. | |
| Value *MemCmpExpansion::getMemCmpExpansion() { | |
| // Create the basic block framework for a multi-block expansion. | |
| if (getNumBlocks() != 1) { | |
| BasicBlock *StartBlock = CI->getParent(); | |
| EndBlock = SplitBlock(StartBlock, CI, DTU, /*LI=*/nullptr, | |
| /*MSSAU=*/nullptr, "endblock"); | |
| setupEndBlockPHINodes(); | |
| createResultBlock(); | |
| // If return value of memcmp is not used in a zero equality, we need to | |
| // calculate which source was larger. The calculation requires the | |
| // two loaded source values of each load compare block. | |
| // These will be saved in the phi nodes created by setupResultBlockPHINodes. | |
| if (!IsUsedForZeroCmp) setupResultBlockPHINodes(); | |
| // Create the number of required load compare basic blocks. | |
| createLoadCmpBlocks(); | |
| // Update the terminator added by SplitBlock to branch to the first | |
| // LoadCmpBlock. | |
| StartBlock->getTerminator()->setSuccessor(0, LoadCmpBlocks[0]); | |
| if (DTU) | |
| DTU->applyUpdates({{DominatorTree::Insert, StartBlock, LoadCmpBlocks[0]}, | |
| {DominatorTree::Delete, StartBlock, EndBlock}}); | |
| } | |
| Builder.SetCurrentDebugLocation(CI->getDebugLoc()); | |
| if (IsUsedForZeroCmp) | |
| return getNumBlocks() == 1 ? getMemCmpEqZeroOneBlock() | |
| : getMemCmpExpansionZeroCase(); | |
| if (getNumBlocks() == 1) | |
| return getNumLoads() == 1 ? getMemCmpOneBlock() | |
| : getMemCmpOneBlockMultipleLoads(); | |
| unsigned LoadIndex = 0; | |
| for (unsigned I = 0; I < getNumBlocks(); ++I) { | |
| emitLoadCompareBlock(I, LoadIndex); | |
| } | |
| emitMemCmpResultBlock(); | |
| return PhiRes; | |
| } | |
| // This function checks to see if an expansion of memcmp can be generated. | |
| // It checks for constant compare size that is less than the max inline size. | |
| // If an expansion cannot occur, returns false to leave as a library call. | |
| // Otherwise, the library call is replaced with a new IR instruction sequence. | |
| /// We want to transform: | |
| /// %call = call signext i32 @memcmp(i8* %0, i8* %1, i64 15) | |
| /// To: | |
| /// loadbb: | |
| /// %0 = bitcast i32* %buffer2 to i8* | |
| /// %1 = bitcast i32* %buffer1 to i8* | |
| /// %2 = bitcast i8* %1 to i64* | |
| /// %3 = bitcast i8* %0 to i64* | |
| /// %4 = load i64, i64* %2 | |
| /// %5 = load i64, i64* %3 | |
| /// %6 = call i64 @llvm.bswap.i64(i64 %4) | |
| /// %7 = call i64 @llvm.bswap.i64(i64 %5) | |
| /// %8 = sub i64 %6, %7 | |
| /// %9 = icmp ne i64 %8, 0 | |
| /// br i1 %9, label %res_block, label %loadbb1 | |
| /// res_block: ; preds = %loadbb2, | |
| /// %loadbb1, %loadbb | |
| /// %phi.src1 = phi i64 [ %6, %loadbb ], [ %22, %loadbb1 ], [ %36, %loadbb2 ] | |
| /// %phi.src2 = phi i64 [ %7, %loadbb ], [ %23, %loadbb1 ], [ %37, %loadbb2 ] | |
| /// %10 = icmp ult i64 %phi.src1, %phi.src2 | |
| /// %11 = select i1 %10, i32 -1, i32 1 | |
| /// br label %endblock | |
| /// loadbb1: ; preds = %loadbb | |
| /// %12 = bitcast i32* %buffer2 to i8* | |
| /// %13 = bitcast i32* %buffer1 to i8* | |
| /// %14 = bitcast i8* %13 to i32* | |
| /// %15 = bitcast i8* %12 to i32* | |
| /// %16 = getelementptr i32, i32* %14, i32 2 | |
| /// %17 = getelementptr i32, i32* %15, i32 2 | |
| /// %18 = load i32, i32* %16 | |
| /// %19 = load i32, i32* %17 | |
| /// %20 = call i32 @llvm.bswap.i32(i32 %18) | |
| /// %21 = call i32 @llvm.bswap.i32(i32 %19) | |
| /// %22 = zext i32 %20 to i64 | |
| /// %23 = zext i32 %21 to i64 | |
| /// %24 = sub i64 %22, %23 | |
| /// %25 = icmp ne i64 %24, 0 | |
| /// br i1 %25, label %res_block, label %loadbb2 | |
| /// loadbb2: ; preds = %loadbb1 | |
| /// %26 = bitcast i32* %buffer2 to i8* | |
| /// %27 = bitcast i32* %buffer1 to i8* | |
| /// %28 = bitcast i8* %27 to i16* | |
| /// %29 = bitcast i8* %26 to i16* | |
| /// %30 = getelementptr i16, i16* %28, i16 6 | |
| /// %31 = getelementptr i16, i16* %29, i16 6 | |
| /// %32 = load i16, i16* %30 | |
| /// %33 = load i16, i16* %31 | |
| /// %34 = call i16 @llvm.bswap.i16(i16 %32) | |
| /// %35 = call i16 @llvm.bswap.i16(i16 %33) | |
| /// %36 = zext i16 %34 to i64 | |
| /// %37 = zext i16 %35 to i64 | |
| /// %38 = sub i64 %36, %37 | |
| /// %39 = icmp ne i64 %38, 0 | |
| /// br i1 %39, label %res_block, label %loadbb3 | |
| /// loadbb3: ; preds = %loadbb2 | |
| /// %40 = bitcast i32* %buffer2 to i8* | |
| /// %41 = bitcast i32* %buffer1 to i8* | |
| /// %42 = getelementptr i8, i8* %41, i8 14 | |
| /// %43 = getelementptr i8, i8* %40, i8 14 | |
| /// %44 = load i8, i8* %42 | |
| /// %45 = load i8, i8* %43 | |
| /// %46 = zext i8 %44 to i32 | |
| /// %47 = zext i8 %45 to i32 | |
| /// %48 = sub i32 %46, %47 | |
| /// br label %endblock | |
| /// endblock: ; preds = %res_block, | |
| /// %loadbb3 | |
| /// %phi.res = phi i32 [ %48, %loadbb3 ], [ %11, %res_block ] | |
| /// ret i32 %phi.res | |
| static bool expandMemCmp(const ScalarOptions &Opts, CallInst *CI, | |
| const TargetTransformInfo *TTI, const DataLayout *DL, | |
| ProfileSummaryInfo *PSI, BlockFrequencyInfo *BFI, | |
| DomTreeUpdater *DTU, const bool IsBCmp) { | |
| NumMemCmpCalls++; | |
| // Early exit from expansion if -Oz. | |
| if (CI->getFunction()->hasMinSize()) | |
| return false; | |
| // Early exit from expansion if size is not a constant. | |
| ConstantInt *SizeCast = dyn_cast<ConstantInt>(CI->getArgOperand(2)); | |
| if (!SizeCast) { | |
| NumMemCmpNotConstant++; | |
| return false; | |
| } | |
| const uint64_t SizeVal = SizeCast->getZExtValue(); | |
| if (SizeVal == 0) { | |
| return false; | |
| } | |
| // TTI call to check if target would like to expand memcmp. Also, get the | |
| // available load sizes. | |
| const bool IsUsedForZeroCmp = | |
| IsBCmp || isOnlyUsedInZeroEqualityComparison(CI); | |
| bool OptForSize = llvm::shouldOptimizeForSize(CI->getParent(), PSI, BFI); | |
| auto Options = TTI->enableMemCmpExpansion(OptForSize, | |
| IsUsedForZeroCmp); | |
| if (!Options) return false; | |
| if (Opts.memcmp_num_loads_per_block) | |
| Options.NumLoadsPerBlock = *Opts.memcmp_num_loads_per_block; | |
| if (OptForSize && Opts.max_loads_per_memcmp_opt_size) | |
| Options.MaxNumLoads = *Opts.max_loads_per_memcmp_opt_size; | |
| if (!OptForSize && Opts.max_loads_per_memcmp) | |
| Options.MaxNumLoads = *Opts.max_loads_per_memcmp; | |
| // Keep only the load sizes the target can access at the base alignment: | |
| // either the access is naturally aligned, or the target allows a misaligned | |
| // access of that width. This lets strict-alignment targets expand compares | |
| // whose pointers happen to be sufficiently aligned, while still falling back | |
| // to the libcall when no load size fits. Because the greedy load sequence | |
| // only places a load of size S at an offset that is a multiple of S, a size | |
| // kept here is always accessible in that sequence; overlapping loads and | |
| // merged tail expansions are checked separately against their actual offsets | |
| // in MemCmpExpansion. | |
| const Align CommonAlign = std::min(getMemCmpArgAlignment(CI, 0, *DL), | |
| getMemCmpArgAlignment(CI, 1, *DL)); | |
| // Remember the target's preferred load width before filtering inaccessible | |
| // sizes. It is also the maximum width of the value formed by packing the | |
| // surviving loads in one ordering-compare block. | |
| const unsigned MaxBytesPerBlock = Options.LoadSizes.front(); | |
| llvm::erase_if(Options.LoadSizes, [&](unsigned LoadSize) { | |
| return !isAccessAllowed(CI, *TTI, CommonAlign, LoadSize, /*Offset=*/0); | |
| }); | |
| // If the filter removed every load size, bail out to the libcall: the | |
| // MemCmpExpansion constructor asserts that at least one load size remains. | |
| // In practice all in-tree targets include a byte load size, which is | |
| // accessible at any alignment and therefore always survives the filter. | |
| if (Options.LoadSizes.empty()) | |
| return false; | |
| MemCmpExpansion Expansion(CI, SizeVal, Options, IsUsedForZeroCmp, *DL, DTU, | |
| *TTI, CommonAlign, MaxBytesPerBlock); | |
| // Don't expand if this will require more loads than desired by the target. | |
| if (Expansion.getNumLoads() == 0) { | |
| NumMemCmpGreaterThanMax++; | |
| return false; | |
| } | |
| NumMemCmpInlined++; | |
| if (Value *Res = Expansion.getMemCmpExpansion()) { | |
| // Replace call with result of expansion and erase call. | |
| CI->replaceAllUsesWith(Res); | |
| CI->eraseFromParent(); | |
| } | |
| return true; | |
| } | |
| static PreservedAnalyses runImpl(Function &F, const TargetLibraryInfo *TLI, | |
| const TargetTransformInfo *TTI, | |
| ProfileSummaryInfo *PSI, | |
| BlockFrequencyInfo *BFI, DominatorTree *DT) { | |
| std::optional<DomTreeUpdater> DTU; | |
| if (DT) | |
| DTU.emplace(DT, DomTreeUpdater::UpdateStrategy::Lazy); | |
| const DataLayout& DL = F.getDataLayout(); | |
| SmallVector<std::pair<CallInst *, LibFunc>, 8> MemCmpCalls; | |
| for (Instruction &I : instructions(F)) { | |
| if (auto *CI = dyn_cast<CallInst>(&I)) { | |
| LibFunc Func = TLI->getLibFunc(*CI); | |
| if (Func == LibFunc_memcmp || Func == LibFunc_bcmp) | |
| MemCmpCalls.push_back({CI, Func}); | |
| } | |
| } | |
| const ScalarOptions &Opts = | |
| F.getContext().getLibraryOptions().get<ScalarOptions>(); | |
| bool MadeChanges = false; | |
| for (const auto &[CI, Func] : MemCmpCalls) { | |
| if (expandMemCmp(Opts, CI, TTI, &DL, PSI, BFI, DTU ? &*DTU : nullptr, | |
| Func == LibFunc_bcmp)) | |
| MadeChanges = true; | |
| } | |
| if (MadeChanges) | |
| for (BasicBlock &BB : F) | |
| SimplifyInstructionsInBlock(&BB); | |
| if (!MadeChanges) | |
| return PreservedAnalyses::all(); | |
| PreservedAnalyses PA; | |
| PA.preserve<DominatorTreeAnalysis>(); | |
| return PA; | |
| } | |
| } // namespace | |
| PreservedAnalyses ExpandMemCmpPass::run(Function &F, | |
| FunctionAnalysisManager &FAM) { | |
| // Don't expand memcmp in sanitized functions — sanitizers intercept memcmp | |
| // calls to check for memory errors, and expanding would bypass that. | |
| if (F.hasFnAttribute(Attribute::SanitizeAddress) || | |
| F.hasFnAttribute(Attribute::SanitizeMemory) || | |
| F.hasFnAttribute(Attribute::SanitizeThread) || | |
| F.hasFnAttribute(Attribute::SanitizeHWAddress)) | |
| return PreservedAnalyses::all(); | |
| const auto &TLI = FAM.getResult<TargetLibraryAnalysis>(F); | |
| const auto &TTI = FAM.getResult<TargetIRAnalysis>(F); | |
| auto *PSI = FAM.getResult<ModuleAnalysisManagerFunctionProxy>(F) | |
| .getCachedResult<ProfileSummaryAnalysis>(*F.getParent()); | |
| BlockFrequencyInfo *BFI = (PSI && PSI->hasProfileSummary()) | |
| ? &FAM.getResult<BlockFrequencyAnalysis>(F) | |
| : nullptr; | |
| auto *DT = FAM.getCachedResult<DominatorTreeAnalysis>(F); | |
| return runImpl(F, &TLI, &TTI, PSI, BFI, DT); | |
| } |