Files
clang-p2996/llvm/lib/Target/BPF/BPFAdjustOpt.cpp
Yonghong Song 2e94d8e67a [BPF] handle unsigned icmp ops in BPFAdjustOpt pass
When investigating an issue with bcc tool inject.py, I found
a verifier failure with latest clang. The portion of code
can be illustrated as below:
  struct pid_struct {
    u64 curr_call;
    u64 conds_met;
    u64 stack[2];
  };
  struct pid_struct *bpf_map_lookup_elem();
  int foo() {
    struct pid_struct *p = bpf_map_lookup_elem();
    if (!p) return 0;
    p->curr_call--;
    if (p->conds_met < 1 || p->conds_met >= 3)
        return 0;
    if (p->stack[p->conds_met - 1] == p->curr_call)
        p->conds_met--;
    ...
  }

The verifier failure looks like:
  ...
  8: (79) r1 = *(u64 *)(r0 +0)
   R0_w=map_value(id=0,off=0,ks=4,vs=32,imm=0) R10=fp0 fp-8=mmmm????
  9: (07) r1 += -1
  10: (7b) *(u64 *)(r0 +0) = r1
   R0_w=map_value(id=0,off=0,ks=4,vs=32,imm=0) R1_w=inv(id=0) R10=fp0 fp-8=mmmm????
  11: (79) r2 = *(u64 *)(r0 +8)
   R0_w=map_value(id=0,off=0,ks=4,vs=32,imm=0) R1_w=inv(id=0) R10=fp0 fp-8=mmmm????
  12: (bf) r3 = r2
  13: (07) r3 += -3
  14: (b7) r4 = -2
  15: (2d) if r4 > r3 goto pc+13
   R0=map_value(id=0,off=0,ks=4,vs=32,imm=0) R1=inv(id=0) R2=inv(id=2)
   R3=inv(id=0,umin_value=18446744073709551614,var_off=(0xffffffff00000000; 0xffffffff))
   R4=inv-2 R10=fp0 fp-8=mmmm????
  16: (07) r2 += -1
  17: (bf) r3 = r2
  18: (67) r3 <<= 3
  19: (bf) r4 = r0
  20: (0f) r4 += r3
  math between map_value pointer and register with unbounded min value is not allowed

Here the compiler optimized "p->conds_met < 1 || p->conds_met >= 3" to
  r2 = p->conds_met
  r3 = r2
  r3 += -3
  r4 = -2
  if (r3 < r4) return 0
  r2 += -1
  r3 = r2
  ...
In the above, r3 is initially equal to r2, but is modified used by the comparison.
But later on r2 is used again. This caused verification failure.

BPF backend has a pass, AdjustOpt, to prevent such transformation, but only
focused on signed integers since typical bpf helper returns signed integers.
To fix this case, let us handle unsigned integers as well.

Differential Revision: https://reviews.llvm.org/D121937
2022-03-17 16:24:39 -07:00

394 lines
12 KiB
C++

//===---------------- BPFAdjustOpt.cpp - Adjust Optimization --------------===//
//
// 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
//
//===----------------------------------------------------------------------===//
//
// Adjust optimization to make the code more kernel verifier friendly.
//
//===----------------------------------------------------------------------===//
#include "BPF.h"
#include "BPFCORE.h"
#include "BPFTargetMachine.h"
#include "llvm/IR/Instruction.h"
#include "llvm/IR/Instructions.h"
#include "llvm/IR/IntrinsicsBPF.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/PatternMatch.h"
#include "llvm/IR/Type.h"
#include "llvm/IR/User.h"
#include "llvm/IR/Value.h"
#include "llvm/Pass.h"
#include "llvm/Transforms/Utils/BasicBlockUtils.h"
#define DEBUG_TYPE "bpf-adjust-opt"
using namespace llvm;
using namespace llvm::PatternMatch;
static cl::opt<bool>
DisableBPFserializeICMP("bpf-disable-serialize-icmp", cl::Hidden,
cl::desc("BPF: Disable Serializing ICMP insns."),
cl::init(false));
static cl::opt<bool> DisableBPFavoidSpeculation(
"bpf-disable-avoid-speculation", cl::Hidden,
cl::desc("BPF: Disable Avoiding Speculative Code Motion."),
cl::init(false));
namespace {
class BPFAdjustOpt final : public ModulePass {
public:
static char ID;
BPFAdjustOpt() : ModulePass(ID) {}
bool runOnModule(Module &M) override;
};
class BPFAdjustOptImpl {
struct PassThroughInfo {
Instruction *Input;
Instruction *UsedInst;
uint32_t OpIdx;
PassThroughInfo(Instruction *I, Instruction *U, uint32_t Idx)
: Input(I), UsedInst(U), OpIdx(Idx) {}
};
public:
BPFAdjustOptImpl(Module *M) : M(M) {}
bool run();
private:
Module *M;
SmallVector<PassThroughInfo, 16> PassThroughs;
bool adjustICmpToBuiltin();
void adjustBasicBlock(BasicBlock &BB);
bool serializeICMPCrossBB(BasicBlock &BB);
void adjustInst(Instruction &I);
bool serializeICMPInBB(Instruction &I);
bool avoidSpeculation(Instruction &I);
bool insertPassThrough();
};
} // End anonymous namespace
char BPFAdjustOpt::ID = 0;
INITIALIZE_PASS(BPFAdjustOpt, "bpf-adjust-opt", "BPF Adjust Optimization",
false, false)
ModulePass *llvm::createBPFAdjustOpt() { return new BPFAdjustOpt(); }
bool BPFAdjustOpt::runOnModule(Module &M) { return BPFAdjustOptImpl(&M).run(); }
bool BPFAdjustOptImpl::run() {
bool Changed = adjustICmpToBuiltin();
for (Function &F : *M)
for (auto &BB : F) {
adjustBasicBlock(BB);
for (auto &I : BB)
adjustInst(I);
}
return insertPassThrough() || Changed;
}
// Commit acabad9ff6bf ("[InstCombine] try to canonicalize icmp with
// trunc op into mask and cmp") added a transformation to
// convert "(conv)a < power_2_const" to "a & <const>" in certain
// cases and bpf kernel verifier has to handle the resulted code
// conservatively and this may reject otherwise legitimate program.
// Here, we change related icmp code to a builtin which will
// be restored to original icmp code later to prevent that
// InstCombine transformatin.
bool BPFAdjustOptImpl::adjustICmpToBuiltin() {
bool Changed = false;
ICmpInst *ToBeDeleted = nullptr;
for (Function &F : *M)
for (auto &BB : F)
for (auto &I : BB) {
if (ToBeDeleted) {
ToBeDeleted->eraseFromParent();
ToBeDeleted = nullptr;
}
auto *Icmp = dyn_cast<ICmpInst>(&I);
if (!Icmp)
continue;
Value *Op0 = Icmp->getOperand(0);
if (!isa<TruncInst>(Op0))
continue;
auto ConstOp1 = dyn_cast<ConstantInt>(Icmp->getOperand(1));
if (!ConstOp1)
continue;
auto ConstOp1Val = ConstOp1->getValue().getZExtValue();
auto Op = Icmp->getPredicate();
if (Op == ICmpInst::ICMP_ULT || Op == ICmpInst::ICMP_UGE) {
if ((ConstOp1Val - 1) & ConstOp1Val)
continue;
} else if (Op == ICmpInst::ICMP_ULE || Op == ICmpInst::ICMP_UGT) {
if (ConstOp1Val & (ConstOp1Val + 1))
continue;
} else {
continue;
}
Constant *Opcode =
ConstantInt::get(Type::getInt32Ty(BB.getContext()), Op);
Function *Fn = Intrinsic::getDeclaration(
M, Intrinsic::bpf_compare, {Op0->getType(), ConstOp1->getType()});
auto *NewInst = CallInst::Create(Fn, {Opcode, Op0, ConstOp1});
BB.getInstList().insert(I.getIterator(), NewInst);
Icmp->replaceAllUsesWith(NewInst);
Changed = true;
ToBeDeleted = Icmp;
}
return Changed;
}
bool BPFAdjustOptImpl::insertPassThrough() {
for (auto &Info : PassThroughs) {
auto *CI = BPFCoreSharedInfo::insertPassThrough(
M, Info.UsedInst->getParent(), Info.Input, Info.UsedInst);
Info.UsedInst->setOperand(Info.OpIdx, CI);
}
return !PassThroughs.empty();
}
// To avoid combining conditionals in the same basic block by
// instrcombine optimization.
bool BPFAdjustOptImpl::serializeICMPInBB(Instruction &I) {
// For:
// comp1 = icmp <opcode> ...;
// comp2 = icmp <opcode> ...;
// ... or comp1 comp2 ...
// changed to:
// comp1 = icmp <opcode> ...;
// comp2 = icmp <opcode> ...;
// new_comp1 = __builtin_bpf_passthrough(seq_num, comp1)
// ... or new_comp1 comp2 ...
Value *Op0, *Op1;
// Use LogicalOr (accept `or i1` as well as `select i1 Op0, true, Op1`)
if (!match(&I, m_LogicalOr(m_Value(Op0), m_Value(Op1))))
return false;
auto *Icmp1 = dyn_cast<ICmpInst>(Op0);
if (!Icmp1)
return false;
auto *Icmp2 = dyn_cast<ICmpInst>(Op1);
if (!Icmp2)
return false;
Value *Icmp1Op0 = Icmp1->getOperand(0);
Value *Icmp2Op0 = Icmp2->getOperand(0);
if (Icmp1Op0 != Icmp2Op0)
return false;
// Now we got two icmp instructions which feed into
// an "or" instruction.
PassThroughInfo Info(Icmp1, &I, 0);
PassThroughs.push_back(Info);
return true;
}
// To avoid combining conditionals in the same basic block by
// instrcombine optimization.
bool BPFAdjustOptImpl::serializeICMPCrossBB(BasicBlock &BB) {
// For:
// B1:
// comp1 = icmp <opcode> ...;
// if (comp1) goto B2 else B3;
// B2:
// comp2 = icmp <opcode> ...;
// if (comp2) goto B4 else B5;
// B4:
// ...
// changed to:
// B1:
// comp1 = icmp <opcode> ...;
// comp1 = __builtin_bpf_passthrough(seq_num, comp1);
// if (comp1) goto B2 else B3;
// B2:
// comp2 = icmp <opcode> ...;
// if (comp2) goto B4 else B5;
// B4:
// ...
// Check basic predecessors, if two of them (say B1, B2) are using
// icmp instructions to generate conditions and one is the predesessor
// of another (e.g., B1 is the predecessor of B2). Add a passthrough
// barrier after icmp inst of block B1.
BasicBlock *B2 = BB.getSinglePredecessor();
if (!B2)
return false;
BasicBlock *B1 = B2->getSinglePredecessor();
if (!B1)
return false;
Instruction *TI = B2->getTerminator();
auto *BI = dyn_cast<BranchInst>(TI);
if (!BI || !BI->isConditional())
return false;
auto *Cond = dyn_cast<ICmpInst>(BI->getCondition());
if (!Cond || B2->getFirstNonPHI() != Cond)
return false;
Value *B2Op0 = Cond->getOperand(0);
auto Cond2Op = Cond->getPredicate();
TI = B1->getTerminator();
BI = dyn_cast<BranchInst>(TI);
if (!BI || !BI->isConditional())
return false;
Cond = dyn_cast<ICmpInst>(BI->getCondition());
if (!Cond)
return false;
Value *B1Op0 = Cond->getOperand(0);
auto Cond1Op = Cond->getPredicate();
if (B1Op0 != B2Op0)
return false;
if (Cond1Op == ICmpInst::ICMP_SGT || Cond1Op == ICmpInst::ICMP_SGE) {
if (Cond2Op != ICmpInst::ICMP_SLT && Cond2Op != ICmpInst::ICMP_SLE)
return false;
} else if (Cond1Op == ICmpInst::ICMP_SLT || Cond1Op == ICmpInst::ICMP_SLE) {
if (Cond2Op != ICmpInst::ICMP_SGT && Cond2Op != ICmpInst::ICMP_SGE)
return false;
} else if (Cond1Op == ICmpInst::ICMP_ULT || Cond1Op == ICmpInst::ICMP_ULE) {
if (Cond2Op != ICmpInst::ICMP_UGT && Cond2Op != ICmpInst::ICMP_UGE)
return false;
} else if (Cond1Op == ICmpInst::ICMP_UGT || Cond1Op == ICmpInst::ICMP_UGE) {
if (Cond2Op != ICmpInst::ICMP_ULT && Cond2Op != ICmpInst::ICMP_ULE)
return false;
} else {
return false;
}
PassThroughInfo Info(Cond, BI, 0);
PassThroughs.push_back(Info);
return true;
}
// To avoid speculative hoisting certain computations out of
// a basic block.
bool BPFAdjustOptImpl::avoidSpeculation(Instruction &I) {
if (auto *LdInst = dyn_cast<LoadInst>(&I)) {
if (auto *GV = dyn_cast<GlobalVariable>(LdInst->getOperand(0))) {
if (GV->hasAttribute(BPFCoreSharedInfo::AmaAttr) ||
GV->hasAttribute(BPFCoreSharedInfo::TypeIdAttr))
return false;
}
}
if (!isa<LoadInst>(&I) && !isa<CallInst>(&I))
return false;
// For:
// B1:
// var = ...
// ...
// /* icmp may not be in the same block as var = ... */
// comp1 = icmp <opcode> var, <const>;
// if (comp1) goto B2 else B3;
// B2:
// ... var ...
// change to:
// B1:
// var = ...
// ...
// /* icmp may not be in the same block as var = ... */
// comp1 = icmp <opcode> var, <const>;
// if (comp1) goto B2 else B3;
// B2:
// var = __builtin_bpf_passthrough(seq_num, var);
// ... var ...
bool isCandidate = false;
SmallVector<PassThroughInfo, 4> Candidates;
for (User *U : I.users()) {
Instruction *Inst = dyn_cast<Instruction>(U);
if (!Inst)
continue;
// May cover a little bit more than the
// above pattern.
if (auto *Icmp1 = dyn_cast<ICmpInst>(Inst)) {
Value *Icmp1Op1 = Icmp1->getOperand(1);
if (!isa<Constant>(Icmp1Op1))
return false;
isCandidate = true;
continue;
}
// Ignore the use in the same basic block as the definition.
if (Inst->getParent() == I.getParent())
continue;
// use in a different basic block, If there is a call or
// load/store insn before this instruction in this basic
// block. Most likely it cannot be hoisted out. Skip it.
for (auto &I2 : *Inst->getParent()) {
if (isa<CallInst>(&I2))
return false;
if (isa<LoadInst>(&I2) || isa<StoreInst>(&I2))
return false;
if (&I2 == Inst)
break;
}
// It should be used in a GEP or a simple arithmetic like
// ZEXT/SEXT which is used for GEP.
if (Inst->getOpcode() == Instruction::ZExt ||
Inst->getOpcode() == Instruction::SExt) {
PassThroughInfo Info(&I, Inst, 0);
Candidates.push_back(Info);
} else if (auto *GI = dyn_cast<GetElementPtrInst>(Inst)) {
// traverse GEP inst to find Use operand index
unsigned i, e;
for (i = 1, e = GI->getNumOperands(); i != e; ++i) {
Value *V = GI->getOperand(i);
if (V == &I)
break;
}
if (i == e)
continue;
PassThroughInfo Info(&I, GI, i);
Candidates.push_back(Info);
}
}
if (!isCandidate || Candidates.empty())
return false;
llvm::append_range(PassThroughs, Candidates);
return true;
}
void BPFAdjustOptImpl::adjustBasicBlock(BasicBlock &BB) {
if (!DisableBPFserializeICMP && serializeICMPCrossBB(BB))
return;
}
void BPFAdjustOptImpl::adjustInst(Instruction &I) {
if (!DisableBPFserializeICMP && serializeICMPInBB(I))
return;
if (!DisableBPFavoidSpeculation && avoidSpeculation(I))
return;
}
PreservedAnalyses BPFAdjustOptPass::run(Module &M, ModuleAnalysisManager &AM) {
return BPFAdjustOptImpl(&M).run() ? PreservedAnalyses::none()
: PreservedAnalyses::all();
}