Path: blob/main/contrib/llvm-project/llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp
35268 views
//===------------ BPFCheckAndAdjustIR.cpp - Check and Adjust IR -----------===//1//2// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.3// See https://llvm.org/LICENSE.txt for license information.4// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception5//6//===----------------------------------------------------------------------===//7//8// Check IR and adjust IR for verifier friendly codes.9// The following are done for IR checking:10// - no relocation globals in PHI node.11// The following are done for IR adjustment:12// - remove __builtin_bpf_passthrough builtins. Target independent IR13// optimizations are done and those builtins can be removed.14// - remove llvm.bpf.getelementptr.and.load builtins.15// - remove llvm.bpf.getelementptr.and.store builtins.16// - for loads and stores with base addresses from non-zero address space17// cast base address to zero address space (support for BPF address spaces).18//19//===----------------------------------------------------------------------===//2021#include "BPF.h"22#include "BPFCORE.h"23#include "BPFTargetMachine.h"24#include "llvm/Analysis/LoopInfo.h"25#include "llvm/IR/DebugInfoMetadata.h"26#include "llvm/IR/GlobalVariable.h"27#include "llvm/IR/IRBuilder.h"28#include "llvm/IR/Instruction.h"29#include "llvm/IR/Instructions.h"30#include "llvm/IR/IntrinsicsBPF.h"31#include "llvm/IR/Module.h"32#include "llvm/IR/Type.h"33#include "llvm/IR/User.h"34#include "llvm/IR/Value.h"35#include "llvm/Pass.h"36#include "llvm/Transforms/Utils/BasicBlockUtils.h"3738#define DEBUG_TYPE "bpf-check-and-opt-ir"3940using namespace llvm;4142namespace {4344class BPFCheckAndAdjustIR final : public ModulePass {45bool runOnModule(Module &F) override;4647public:48static char ID;49BPFCheckAndAdjustIR() : ModulePass(ID) {}50virtual void getAnalysisUsage(AnalysisUsage &AU) const override;5152private:53void checkIR(Module &M);54bool adjustIR(Module &M);55bool removePassThroughBuiltin(Module &M);56bool removeCompareBuiltin(Module &M);57bool sinkMinMax(Module &M);58bool removeGEPBuiltins(Module &M);59bool insertASpaceCasts(Module &M);60};61} // End anonymous namespace6263char BPFCheckAndAdjustIR::ID = 0;64INITIALIZE_PASS(BPFCheckAndAdjustIR, DEBUG_TYPE, "BPF Check And Adjust IR",65false, false)6667ModulePass *llvm::createBPFCheckAndAdjustIR() {68return new BPFCheckAndAdjustIR();69}7071void BPFCheckAndAdjustIR::checkIR(Module &M) {72// Ensure relocation global won't appear in PHI node73// This may happen if the compiler generated the following code:74// B1:75// g1 = @llvm.skb_buff:0:1...76// ...77// goto B_COMMON78// B2:79// g2 = @llvm.skb_buff:0:2...80// ...81// goto B_COMMON82// B_COMMON:83// g = PHI(g1, g2)84// x = load g85// ...86// If anything likes the above "g = PHI(g1, g2)", issue a fatal error.87for (Function &F : M)88for (auto &BB : F)89for (auto &I : BB) {90PHINode *PN = dyn_cast<PHINode>(&I);91if (!PN || PN->use_empty())92continue;93for (int i = 0, e = PN->getNumIncomingValues(); i < e; ++i) {94auto *GV = dyn_cast<GlobalVariable>(PN->getIncomingValue(i));95if (!GV)96continue;97if (GV->hasAttribute(BPFCoreSharedInfo::AmaAttr) ||98GV->hasAttribute(BPFCoreSharedInfo::TypeIdAttr))99report_fatal_error("relocation global in PHI node");100}101}102}103104bool BPFCheckAndAdjustIR::removePassThroughBuiltin(Module &M) {105// Remove __builtin_bpf_passthrough()'s which are used to prevent106// certain IR optimizations. Now major IR optimizations are done,107// remove them.108bool Changed = false;109CallInst *ToBeDeleted = nullptr;110for (Function &F : M)111for (auto &BB : F)112for (auto &I : BB) {113if (ToBeDeleted) {114ToBeDeleted->eraseFromParent();115ToBeDeleted = nullptr;116}117118auto *Call = dyn_cast<CallInst>(&I);119if (!Call)120continue;121auto *GV = dyn_cast<GlobalValue>(Call->getCalledOperand());122if (!GV)123continue;124if (!GV->getName().starts_with("llvm.bpf.passthrough"))125continue;126Changed = true;127Value *Arg = Call->getArgOperand(1);128Call->replaceAllUsesWith(Arg);129ToBeDeleted = Call;130}131return Changed;132}133134bool BPFCheckAndAdjustIR::removeCompareBuiltin(Module &M) {135// Remove __builtin_bpf_compare()'s which are used to prevent136// certain IR optimizations. Now major IR optimizations are done,137// remove them.138bool Changed = false;139CallInst *ToBeDeleted = nullptr;140for (Function &F : M)141for (auto &BB : F)142for (auto &I : BB) {143if (ToBeDeleted) {144ToBeDeleted->eraseFromParent();145ToBeDeleted = nullptr;146}147148auto *Call = dyn_cast<CallInst>(&I);149if (!Call)150continue;151auto *GV = dyn_cast<GlobalValue>(Call->getCalledOperand());152if (!GV)153continue;154if (!GV->getName().starts_with("llvm.bpf.compare"))155continue;156157Changed = true;158Value *Arg0 = Call->getArgOperand(0);159Value *Arg1 = Call->getArgOperand(1);160Value *Arg2 = Call->getArgOperand(2);161162auto OpVal = cast<ConstantInt>(Arg0)->getValue().getZExtValue();163CmpInst::Predicate Opcode = (CmpInst::Predicate)OpVal;164165auto *ICmp = new ICmpInst(Opcode, Arg1, Arg2);166ICmp->insertBefore(Call);167168Call->replaceAllUsesWith(ICmp);169ToBeDeleted = Call;170}171return Changed;172}173174struct MinMaxSinkInfo {175ICmpInst *ICmp;176Value *Other;177ICmpInst::Predicate Predicate;178CallInst *MinMax;179ZExtInst *ZExt;180SExtInst *SExt;181182MinMaxSinkInfo(ICmpInst *ICmp, Value *Other, ICmpInst::Predicate Predicate)183: ICmp(ICmp), Other(Other), Predicate(Predicate), MinMax(nullptr),184ZExt(nullptr), SExt(nullptr) {}185};186187static bool sinkMinMaxInBB(BasicBlock &BB,188const std::function<bool(Instruction *)> &Filter) {189// Check if V is:190// (fn %a %b) or (ext (fn %a %b))191// Where:192// ext := sext | zext193// fn := smin | umin | smax | umax194auto IsMinMaxCall = [=](Value *V, MinMaxSinkInfo &Info) {195if (auto *ZExt = dyn_cast<ZExtInst>(V)) {196V = ZExt->getOperand(0);197Info.ZExt = ZExt;198} else if (auto *SExt = dyn_cast<SExtInst>(V)) {199V = SExt->getOperand(0);200Info.SExt = SExt;201}202203auto *Call = dyn_cast<CallInst>(V);204if (!Call)205return false;206207auto *Called = dyn_cast<Function>(Call->getCalledOperand());208if (!Called)209return false;210211switch (Called->getIntrinsicID()) {212case Intrinsic::smin:213case Intrinsic::umin:214case Intrinsic::smax:215case Intrinsic::umax:216break;217default:218return false;219}220221if (!Filter(Call))222return false;223224Info.MinMax = Call;225226return true;227};228229auto ZeroOrSignExtend = [](IRBuilder<> &Builder, Value *V,230MinMaxSinkInfo &Info) {231if (Info.SExt) {232if (Info.SExt->getType() == V->getType())233return V;234return Builder.CreateSExt(V, Info.SExt->getType());235}236if (Info.ZExt) {237if (Info.ZExt->getType() == V->getType())238return V;239return Builder.CreateZExt(V, Info.ZExt->getType());240}241return V;242};243244bool Changed = false;245SmallVector<MinMaxSinkInfo, 2> SinkList;246247// Check BB for instructions like:248// insn := (icmp %a (fn ...)) | (icmp (fn ...) %a)249//250// Where:251// fn := min | max | (sext (min ...)) | (sext (max ...))252//253// Put such instructions to SinkList.254for (Instruction &I : BB) {255ICmpInst *ICmp = dyn_cast<ICmpInst>(&I);256if (!ICmp)257continue;258if (!ICmp->isRelational())259continue;260MinMaxSinkInfo First(ICmp, ICmp->getOperand(1),261ICmpInst::getSwappedPredicate(ICmp->getPredicate()));262MinMaxSinkInfo Second(ICmp, ICmp->getOperand(0), ICmp->getPredicate());263bool FirstMinMax = IsMinMaxCall(ICmp->getOperand(0), First);264bool SecondMinMax = IsMinMaxCall(ICmp->getOperand(1), Second);265if (!(FirstMinMax ^ SecondMinMax))266continue;267SinkList.push_back(FirstMinMax ? First : Second);268}269270// Iterate SinkList and replace each (icmp ...) with corresponding271// `x < a && x < b` or similar expression.272for (auto &Info : SinkList) {273ICmpInst *ICmp = Info.ICmp;274CallInst *MinMax = Info.MinMax;275Intrinsic::ID IID = MinMax->getCalledFunction()->getIntrinsicID();276ICmpInst::Predicate P = Info.Predicate;277if (ICmpInst::isSigned(P) && IID != Intrinsic::smin &&278IID != Intrinsic::smax)279continue;280281IRBuilder<> Builder(ICmp);282Value *X = Info.Other;283Value *A = ZeroOrSignExtend(Builder, MinMax->getArgOperand(0), Info);284Value *B = ZeroOrSignExtend(Builder, MinMax->getArgOperand(1), Info);285bool IsMin = IID == Intrinsic::smin || IID == Intrinsic::umin;286bool IsMax = IID == Intrinsic::smax || IID == Intrinsic::umax;287bool IsLess = ICmpInst::isLE(P) || ICmpInst::isLT(P);288bool IsGreater = ICmpInst::isGE(P) || ICmpInst::isGT(P);289assert(IsMin ^ IsMax);290assert(IsLess ^ IsGreater);291292Value *Replacement;293Value *LHS = Builder.CreateICmp(P, X, A);294Value *RHS = Builder.CreateICmp(P, X, B);295if ((IsLess && IsMin) || (IsGreater && IsMax))296// x < min(a, b) -> x < a && x < b297// x > max(a, b) -> x > a && x > b298Replacement = Builder.CreateLogicalAnd(LHS, RHS);299else300// x > min(a, b) -> x > a || x > b301// x < max(a, b) -> x < a || x < b302Replacement = Builder.CreateLogicalOr(LHS, RHS);303304ICmp->replaceAllUsesWith(Replacement);305306Instruction *ToRemove[] = {ICmp, Info.ZExt, Info.SExt, MinMax};307for (Instruction *I : ToRemove)308if (I && I->use_empty())309I->eraseFromParent();310311Changed = true;312}313314return Changed;315}316317// Do the following transformation:318//319// x < min(a, b) -> x < a && x < b320// x > min(a, b) -> x > a || x > b321// x < max(a, b) -> x < a || x < b322// x > max(a, b) -> x > a && x > b323//324// Such patterns are introduced by LICM.cpp:hoistMinMax()325// transformation and might lead to BPF verification failures for326// older kernels.327//328// To minimize "collateral" changes only do it for icmp + min/max329// calls when icmp is inside a loop and min/max is outside of that330// loop.331//332// Verification failure happens when:333// - RHS operand of some `icmp LHS, RHS` is replaced by some RHS1;334// - verifier can recognize RHS as a constant scalar in some context;335// - verifier can't recognize RHS1 as a constant scalar in the same336// context;337//338// The "constant scalar" is not a compile time constant, but a register339// that holds a scalar value known to verifier at some point in time340// during abstract interpretation.341//342// See also:343// https://lore.kernel.org/bpf/[email protected]/344bool BPFCheckAndAdjustIR::sinkMinMax(Module &M) {345bool Changed = false;346347for (Function &F : M) {348if (F.isDeclaration())349continue;350351LoopInfo &LI = getAnalysis<LoopInfoWrapperPass>(F).getLoopInfo();352for (Loop *L : LI)353for (BasicBlock *BB : L->blocks()) {354// Filter out instructions coming from the same loop355Loop *BBLoop = LI.getLoopFor(BB);356auto OtherLoopFilter = [&](Instruction *I) {357return LI.getLoopFor(I->getParent()) != BBLoop;358};359Changed |= sinkMinMaxInBB(*BB, OtherLoopFilter);360}361}362363return Changed;364}365366void BPFCheckAndAdjustIR::getAnalysisUsage(AnalysisUsage &AU) const {367AU.addRequired<LoopInfoWrapperPass>();368}369370static void unrollGEPLoad(CallInst *Call) {371auto [GEP, Load] = BPFPreserveStaticOffsetPass::reconstructLoad(Call);372GEP->insertBefore(Call);373Load->insertBefore(Call);374Call->replaceAllUsesWith(Load);375Call->eraseFromParent();376}377378static void unrollGEPStore(CallInst *Call) {379auto [GEP, Store] = BPFPreserveStaticOffsetPass::reconstructStore(Call);380GEP->insertBefore(Call);381Store->insertBefore(Call);382Call->eraseFromParent();383}384385static bool removeGEPBuiltinsInFunc(Function &F) {386SmallVector<CallInst *> GEPLoads;387SmallVector<CallInst *> GEPStores;388for (auto &BB : F)389for (auto &Insn : BB)390if (auto *Call = dyn_cast<CallInst>(&Insn))391if (auto *Called = Call->getCalledFunction())392switch (Called->getIntrinsicID()) {393case Intrinsic::bpf_getelementptr_and_load:394GEPLoads.push_back(Call);395break;396case Intrinsic::bpf_getelementptr_and_store:397GEPStores.push_back(Call);398break;399}400401if (GEPLoads.empty() && GEPStores.empty())402return false;403404for_each(GEPLoads, unrollGEPLoad);405for_each(GEPStores, unrollGEPStore);406407return true;408}409410// Rewrites the following builtins:411// - llvm.bpf.getelementptr.and.load412// - llvm.bpf.getelementptr.and.store413// As (load (getelementptr ...)) or (store (getelementptr ...)).414bool BPFCheckAndAdjustIR::removeGEPBuiltins(Module &M) {415bool Changed = false;416for (auto &F : M)417Changed = removeGEPBuiltinsInFunc(F) || Changed;418return Changed;419}420421// Wrap ToWrap with cast to address space zero:422// - if ToWrap is a getelementptr,423// wrap it's base pointer instead and return a copy;424// - if ToWrap is Instruction, insert address space cast425// immediately after ToWrap;426// - if ToWrap is not an Instruction (function parameter427// or a global value), insert address space cast at the428// beginning of the Function F;429// - use Cache to avoid inserting too many casts;430static Value *aspaceWrapValue(DenseMap<Value *, Value *> &Cache, Function *F,431Value *ToWrap) {432auto It = Cache.find(ToWrap);433if (It != Cache.end())434return It->getSecond();435436if (auto *GEP = dyn_cast<GetElementPtrInst>(ToWrap)) {437Value *Ptr = GEP->getPointerOperand();438Value *WrappedPtr = aspaceWrapValue(Cache, F, Ptr);439auto *GEPTy = cast<PointerType>(GEP->getType());440auto *NewGEP = GEP->clone();441NewGEP->insertAfter(GEP);442NewGEP->mutateType(GEPTy->getPointerTo(0));443NewGEP->setOperand(GEP->getPointerOperandIndex(), WrappedPtr);444NewGEP->setName(GEP->getName());445Cache[ToWrap] = NewGEP;446return NewGEP;447}448449IRBuilder IB(F->getContext());450if (Instruction *InsnPtr = dyn_cast<Instruction>(ToWrap))451IB.SetInsertPoint(*InsnPtr->getInsertionPointAfterDef());452else453IB.SetInsertPoint(F->getEntryBlock().getFirstInsertionPt());454auto *PtrTy = cast<PointerType>(ToWrap->getType());455auto *ASZeroPtrTy = PtrTy->getPointerTo(0);456auto *ACast = IB.CreateAddrSpaceCast(ToWrap, ASZeroPtrTy, ToWrap->getName());457Cache[ToWrap] = ACast;458return ACast;459}460461// Wrap a pointer operand OpNum of instruction I462// with cast to address space zero463static void aspaceWrapOperand(DenseMap<Value *, Value *> &Cache, Instruction *I,464unsigned OpNum) {465Value *OldOp = I->getOperand(OpNum);466if (OldOp->getType()->getPointerAddressSpace() == 0)467return;468469Value *NewOp = aspaceWrapValue(Cache, I->getFunction(), OldOp);470I->setOperand(OpNum, NewOp);471// Check if there are any remaining users of old GEP,472// delete those w/o users473for (;;) {474auto *OldGEP = dyn_cast<GetElementPtrInst>(OldOp);475if (!OldGEP)476break;477if (!OldGEP->use_empty())478break;479OldOp = OldGEP->getPointerOperand();480OldGEP->eraseFromParent();481}482}483484// Support for BPF address spaces:485// - for each function in the module M, update pointer operand of486// each memory access instruction (load/store/cmpxchg/atomicrmw)487// by casting it from non-zero address space to zero address space, e.g:488//489// (load (ptr addrspace (N) %p) ...)490// -> (load (addrspacecast ptr addrspace (N) %p to ptr))491//492// - assign section with name .addr_space.N for globals defined in493// non-zero address space N494bool BPFCheckAndAdjustIR::insertASpaceCasts(Module &M) {495bool Changed = false;496for (Function &F : M) {497DenseMap<Value *, Value *> CastsCache;498for (BasicBlock &BB : F) {499for (Instruction &I : BB) {500unsigned PtrOpNum;501502if (auto *LD = dyn_cast<LoadInst>(&I))503PtrOpNum = LD->getPointerOperandIndex();504else if (auto *ST = dyn_cast<StoreInst>(&I))505PtrOpNum = ST->getPointerOperandIndex();506else if (auto *CmpXchg = dyn_cast<AtomicCmpXchgInst>(&I))507PtrOpNum = CmpXchg->getPointerOperandIndex();508else if (auto *RMW = dyn_cast<AtomicRMWInst>(&I))509PtrOpNum = RMW->getPointerOperandIndex();510else511continue;512513aspaceWrapOperand(CastsCache, &I, PtrOpNum);514}515}516Changed |= !CastsCache.empty();517}518// Merge all globals within same address space into single519// .addr_space.<addr space no> section520for (GlobalVariable &G : M.globals()) {521if (G.getAddressSpace() == 0 || G.hasSection())522continue;523SmallString<16> SecName;524raw_svector_ostream OS(SecName);525OS << ".addr_space." << G.getAddressSpace();526G.setSection(SecName);527// Prevent having separate section for constants528G.setConstant(false);529}530return Changed;531}532533bool BPFCheckAndAdjustIR::adjustIR(Module &M) {534bool Changed = removePassThroughBuiltin(M);535Changed = removeCompareBuiltin(M) || Changed;536Changed = sinkMinMax(M) || Changed;537Changed = removeGEPBuiltins(M) || Changed;538Changed = insertASpaceCasts(M) || Changed;539return Changed;540}541542bool BPFCheckAndAdjustIR::runOnModule(Module &M) {543checkIR(M);544return adjustIR(M);545}546547548