//===-- SCCP.cpp ----------------------------------------------------------===// // // 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 file implements Interprocedural Sparse Conditional Constant Propagation. // //===----------------------------------------------------------------------===// #include "llvm/Transforms/IPO/SCCP.h" #include "llvm/ADT/SetVector.h" #include "llvm/Analysis/AssumptionCache.h" #include "llvm/Analysis/LoopInfo.h" #include "llvm/Analysis/PostDominators.h" #include "llvm/Analysis/TargetLibraryInfo.h" #include "llvm/Analysis/TargetTransformInfo.h" #include "llvm/Analysis/ValueLattice.h" #include "llvm/Analysis/ValueLatticeUtils.h" #include "llvm/Analysis/ValueTracking.h" #include "llvm/InitializePasses.h" #include "llvm/IR/Constants.h" #include "llvm/IR/IntrinsicInst.h" #include "llvm/Support/CommandLine.h" #include "llvm/Support/ModRef.h" #include "llvm/Transforms/IPO.h" #include "llvm/Transforms/IPO/FunctionSpecialization.h" #include "llvm/Transforms/Scalar/SCCP.h" #include "llvm/Transforms/Utils/Local.h" #include "llvm/Transforms/Utils/SCCPSolver.h" using namespace llvm; #define DEBUG_TYPE "sccp" STATISTIC(NumInstRemoved, "Number of instructions removed"); STATISTIC(NumArgsElimed ,"Number of arguments constant propagated"); STATISTIC(NumGlobalConst, "Number of globals found to be constant"); STATISTIC(NumDeadBlocks , "Number of basic blocks unreachable"); STATISTIC(NumInstReplaced, "Number of instructions replaced with (simpler) instruction"); static cl::opt FuncSpecializationMaxIters( "func-specialization-max-iters", cl::init(1), cl::Hidden, cl::desc( "The maximum number of iterations function specialization is run")); static void findReturnsToZap(Function &F, SmallVector &ReturnsToZap, SCCPSolver &Solver) { // We can only do this if we know that nothing else can call the function. if (!Solver.isArgumentTrackedFunction(&F)) return; if (Solver.mustPreserveReturn(&F)) { LLVM_DEBUG( dbgs() << "Can't zap returns of the function : " << F.getName() << " due to present musttail or \"clang.arc.attachedcall\" call of " "it\n"); return; } assert( all_of(F.users(), [&Solver](User *U) { if (isa(U) && !Solver.isBlockExecutable(cast(U)->getParent())) return true; // Non-callsite uses are not impacted by zapping. Also, constant // uses (like blockaddresses) could stuck around, without being // used in the underlying IR, meaning we do not have lattice // values for them. if (!isa(U)) return true; if (U->getType()->isStructTy()) { return all_of(Solver.getStructLatticeValueFor(U), [](const ValueLatticeElement &LV) { return !SCCPSolver::isOverdefined(LV); }); } // We don't consider assume-like intrinsics to be actual address // captures. if (auto *II = dyn_cast(U)) { if (II->isAssumeLikeIntrinsic()) return true; } return !SCCPSolver::isOverdefined(Solver.getLatticeValueFor(U)); }) && "We can only zap functions where all live users have a concrete value"); for (BasicBlock &BB : F) { if (CallInst *CI = BB.getTerminatingMustTailCall()) { LLVM_DEBUG(dbgs() << "Can't zap return of the block due to present " << "musttail call : " << *CI << "\n"); (void)CI; return; } if (auto *RI = dyn_cast(BB.getTerminator())) if (!isa(RI->getOperand(0))) ReturnsToZap.push_back(RI); } } static bool runIPSCCP( Module &M, const DataLayout &DL, FunctionAnalysisManager *FAM, std::function GetTLI, std::function GetTTI, std::function GetAC, function_ref getAnalysis, bool IsFuncSpecEnabled) { SCCPSolver Solver(DL, GetTLI, M.getContext()); FunctionSpecializer Specializer(Solver, M, FAM, GetTLI, GetTTI, GetAC); // Loop over all functions, marking arguments to those with their addresses // taken or that are external as overdefined. for (Function &F : M) { if (F.isDeclaration()) continue; Solver.addAnalysis(F, getAnalysis(F)); // Determine if we can track the function's return values. If so, add the // function to the solver's set of return-tracked functions. if (canTrackReturnsInterprocedurally(&F)) Solver.addTrackedFunction(&F); // Determine if we can track the function's arguments. If so, add the // function to the solver's set of argument-tracked functions. if (canTrackArgumentsInterprocedurally(&F)) { Solver.addArgumentTrackedFunction(&F); continue; } // Assume the function is called. Solver.markBlockExecutable(&F.front()); // Assume nothing about the incoming arguments. for (Argument &AI : F.args()) Solver.markOverdefined(&AI); } // Determine if we can track any of the module's global variables. If so, add // the global variables we can track to the solver's set of tracked global // variables. for (GlobalVariable &G : M.globals()) { G.removeDeadConstantUsers(); if (canTrackGlobalVariableInterprocedurally(&G)) Solver.trackValueOfGlobalVariable(&G); } // Solve for constants. Solver.solveWhileResolvedUndefsIn(M); if (IsFuncSpecEnabled) { unsigned Iters = 0; while (Iters++ < FuncSpecializationMaxIters && Specializer.run()); } // Iterate over all of the instructions in the module, replacing them with // constants if we have found them to be of constant values. bool MadeChanges = false; for (Function &F : M) { if (F.isDeclaration()) continue; SmallVector BlocksToErase; if (Solver.isBlockExecutable(&F.front())) { bool ReplacedPointerArg = false; for (Argument &Arg : F.args()) { if (!Arg.use_empty() && Solver.tryToReplaceWithConstant(&Arg)) { ReplacedPointerArg |= Arg.getType()->isPointerTy(); ++NumArgsElimed; } } // If we replaced an argument, we may now also access a global (currently // classified as "other" memory). Update memory attribute to reflect this. if (ReplacedPointerArg) { auto UpdateAttrs = [&](AttributeList AL) { MemoryEffects ME = AL.getMemoryEffects(); if (ME == MemoryEffects::unknown()) return AL; ME |= MemoryEffects(MemoryEffects::Other, ME.getModRef(MemoryEffects::ArgMem)); return AL.addFnAttribute( F.getContext(), Attribute::getWithMemoryEffects(F.getContext(), ME)); }; F.setAttributes(UpdateAttrs(F.getAttributes())); for (User *U : F.users()) { auto *CB = dyn_cast(U); if (!CB || CB->getCalledFunction() != &F) continue; CB->setAttributes(UpdateAttrs(CB->getAttributes())); } } MadeChanges |= ReplacedPointerArg; } SmallPtrSet InsertedValues; for (BasicBlock &BB : F) { if (!Solver.isBlockExecutable(&BB)) { LLVM_DEBUG(dbgs() << " BasicBlock Dead:" << BB); ++NumDeadBlocks; MadeChanges = true; if (&BB != &F.front()) BlocksToErase.push_back(&BB); continue; } MadeChanges |= Solver.simplifyInstsInBlock( BB, InsertedValues, NumInstRemoved, NumInstReplaced); } DomTreeUpdater DTU = IsFuncSpecEnabled && Specializer.isClonedFunction(&F) ? DomTreeUpdater(DomTreeUpdater::UpdateStrategy::Lazy) : Solver.getDTU(F); // Change dead blocks to unreachable. We do it after replacing constants // in all executable blocks, because changeToUnreachable may remove PHI // nodes in executable blocks we found values for. The function's entry // block is not part of BlocksToErase, so we have to handle it separately. for (BasicBlock *BB : BlocksToErase) { NumInstRemoved += changeToUnreachable(BB->getFirstNonPHI(), /*PreserveLCSSA=*/false, &DTU); } if (!Solver.isBlockExecutable(&F.front())) NumInstRemoved += changeToUnreachable(F.front().getFirstNonPHI(), /*PreserveLCSSA=*/false, &DTU); BasicBlock *NewUnreachableBB = nullptr; for (BasicBlock &BB : F) MadeChanges |= Solver.removeNonFeasibleEdges(&BB, DTU, NewUnreachableBB); for (BasicBlock *DeadBB : BlocksToErase) if (!DeadBB->hasAddressTaken()) DTU.deleteBB(DeadBB); for (BasicBlock &BB : F) { for (Instruction &Inst : llvm::make_early_inc_range(BB)) { if (Solver.getPredicateInfoFor(&Inst)) { if (auto *II = dyn_cast(&Inst)) { if (II->getIntrinsicID() == Intrinsic::ssa_copy) { Value *Op = II->getOperand(0); Inst.replaceAllUsesWith(Op); Inst.eraseFromParent(); } } } } } } // If we inferred constant or undef return values for a function, we replaced // all call uses with the inferred value. This means we don't need to bother // actually returning anything from the function. Replace all return // instructions with return undef. // // Do this in two stages: first identify the functions we should process, then // actually zap their returns. This is important because we can only do this // if the address of the function isn't taken. In cases where a return is the // last use of a function, the order of processing functions would affect // whether other functions are optimizable. SmallVector ReturnsToZap; for (const auto &I : Solver.getTrackedRetVals()) { Function *F = I.first; const ValueLatticeElement &ReturnValue = I.second; // If there is a known constant range for the return value, add !range // metadata to the function's call sites. if (ReturnValue.isConstantRange() && !ReturnValue.getConstantRange().isSingleElement()) { // Do not add range metadata if the return value may include undef. if (ReturnValue.isConstantRangeIncludingUndef()) continue; auto &CR = ReturnValue.getConstantRange(); for (User *User : F->users()) { auto *CB = dyn_cast(User); if (!CB || CB->getCalledFunction() != F) continue; // Limit to cases where the return value is guaranteed to be neither // poison nor undef. Poison will be outside any range and currently // values outside of the specified range cause immediate undefined // behavior. if (!isGuaranteedNotToBeUndefOrPoison(CB, nullptr, CB)) continue; // Do not touch existing metadata for now. // TODO: We should be able to take the intersection of the existing // metadata and the inferred range. if (CB->getMetadata(LLVMContext::MD_range)) continue; LLVMContext &Context = CB->getParent()->getContext(); Metadata *RangeMD[] = { ConstantAsMetadata::get(ConstantInt::get(Context, CR.getLower())), ConstantAsMetadata::get(ConstantInt::get(Context, CR.getUpper()))}; CB->setMetadata(LLVMContext::MD_range, MDNode::get(Context, RangeMD)); } continue; } if (F->getReturnType()->isVoidTy()) continue; if (SCCPSolver::isConstant(ReturnValue) || ReturnValue.isUnknownOrUndef()) findReturnsToZap(*F, ReturnsToZap, Solver); } for (auto *F : Solver.getMRVFunctionsTracked()) { assert(F->getReturnType()->isStructTy() && "The return type should be a struct"); StructType *STy = cast(F->getReturnType()); if (Solver.isStructLatticeConstant(F, STy)) findReturnsToZap(*F, ReturnsToZap, Solver); } // Zap all returns which we've identified as zap to change. SmallSetVector FuncZappedReturn; for (ReturnInst *RI : ReturnsToZap) { Function *F = RI->getParent()->getParent(); RI->setOperand(0, UndefValue::get(F->getReturnType())); // Record all functions that are zapped. FuncZappedReturn.insert(F); } // Remove the returned attribute for zapped functions and the // corresponding call sites. for (Function *F : FuncZappedReturn) { for (Argument &A : F->args()) F->removeParamAttr(A.getArgNo(), Attribute::Returned); for (Use &U : F->uses()) { CallBase *CB = dyn_cast(U.getUser()); if (!CB) { assert(isa(U.getUser()) || (isa(U.getUser()) && all_of(U.getUser()->users(), [](const User *UserUser) { return cast(UserUser)->isAssumeLikeIntrinsic(); }))); continue; } for (Use &Arg : CB->args()) CB->removeParamAttr(CB->getArgOperandNo(&Arg), Attribute::Returned); } } // If we inferred constant or undef values for globals variables, we can // delete the global and any stores that remain to it. for (const auto &I : make_early_inc_range(Solver.getTrackedGlobals())) { GlobalVariable *GV = I.first; if (SCCPSolver::isOverdefined(I.second)) continue; LLVM_DEBUG(dbgs() << "Found that GV '" << GV->getName() << "' is constant!\n"); while (!GV->use_empty()) { StoreInst *SI = cast(GV->user_back()); SI->eraseFromParent(); MadeChanges = true; } M.getGlobalList().erase(GV); ++NumGlobalConst; } return MadeChanges; } PreservedAnalyses IPSCCPPass::run(Module &M, ModuleAnalysisManager &AM) { const DataLayout &DL = M.getDataLayout(); auto &FAM = AM.getResult(M).getManager(); auto GetTLI = [&FAM](Function &F) -> const TargetLibraryInfo & { return FAM.getResult(F); }; auto GetTTI = [&FAM](Function &F) -> TargetTransformInfo & { return FAM.getResult(F); }; auto GetAC = [&FAM](Function &F) -> AssumptionCache & { return FAM.getResult(F); }; auto getAnalysis = [&FAM, this](Function &F) -> AnalysisResultsForFn { DominatorTree &DT = FAM.getResult(F); return { std::make_unique(F, DT, FAM.getResult(F)), &DT, FAM.getCachedResult(F), isFuncSpecEnabled() ? &FAM.getResult(F) : nullptr }; }; if (!runIPSCCP(M, DL, &FAM, GetTLI, GetTTI, GetAC, getAnalysis, isFuncSpecEnabled())) return PreservedAnalyses::all(); PreservedAnalyses PA; PA.preserve(); PA.preserve(); PA.preserve(); return PA; } namespace { //===--------------------------------------------------------------------===// // /// IPSCCP Class - This class implements interprocedural Sparse Conditional /// Constant Propagation. /// class IPSCCPLegacyPass : public ModulePass { public: static char ID; IPSCCPLegacyPass() : ModulePass(ID) { initializeIPSCCPLegacyPassPass(*PassRegistry::getPassRegistry()); } bool runOnModule(Module &M) override { if (skipModule(M)) return false; const DataLayout &DL = M.getDataLayout(); auto GetTLI = [this](Function &F) -> const TargetLibraryInfo & { return this->getAnalysis().getTLI(F); }; auto GetTTI = [this](Function &F) -> TargetTransformInfo & { return this->getAnalysis().getTTI(F); }; auto GetAC = [this](Function &F) -> AssumptionCache & { return this->getAnalysis().getAssumptionCache(F); }; auto getAnalysis = [this](Function &F) -> AnalysisResultsForFn { DominatorTree &DT = this->getAnalysis(F).getDomTree(); return { std::make_unique( F, DT, this->getAnalysis().getAssumptionCache( F)), nullptr, // We cannot preserve the LI, DT or PDT with the legacy pass nullptr, // manager, so set them to nullptr. nullptr}; }; return runIPSCCP(M, DL, nullptr, GetTLI, GetTTI, GetAC, getAnalysis, false); } void getAnalysisUsage(AnalysisUsage &AU) const override { AU.addRequired(); AU.addRequired(); AU.addRequired(); AU.addRequired(); } }; } // end anonymous namespace char IPSCCPLegacyPass::ID = 0; INITIALIZE_PASS_BEGIN(IPSCCPLegacyPass, "ipsccp", "Interprocedural Sparse Conditional Constant Propagation", false, false) INITIALIZE_PASS_DEPENDENCY(AssumptionCacheTracker) INITIALIZE_PASS_DEPENDENCY(DominatorTreeWrapperPass) INITIALIZE_PASS_DEPENDENCY(TargetLibraryInfoWrapperPass) INITIALIZE_PASS_END(IPSCCPLegacyPass, "ipsccp", "Interprocedural Sparse Conditional Constant Propagation", false, false) // createIPSCCPPass - This is the public interface to this file. ModulePass *llvm::createIPSCCPPass() { return new IPSCCPLegacyPass(); }