[PATCH v2 bpf-next 11/18] bpf: Follow PHIs in bpf_scan_ir

From: Anton Protopopov

Date: Mon Nov 09 2026 - 12:01:46 EST


An error often reaches the return through a PHI in the block of the
return which joins it with the results of other paths, some of them
dirty. Take the incoming values of such a PHI on the edges from the
blocks which only clean paths reach. Do not follow a PHI in an earlier
block: a later branch may split its values between clean and dirty
paths.

Take also the errors which LazyValueInfo knows exactly: an incoming
value on its edge and a returned value at the return.

Signed-off-by: Anton Protopopov <a.s.protopopov@xxxxxxxxx>
---
scripts/llvm-plugins/bpf_scan_ir.cpp | 84 ++++++++++++++++++++++++++--
1 file changed, 79 insertions(+), 5 deletions(-)

diff --git a/scripts/llvm-plugins/bpf_scan_ir.cpp b/scripts/llvm-plugins/bpf_scan_ir.cpp
index 0fc60b2130b1..81de8f941568 100644
--- a/scripts/llvm-plugins/bpf_scan_ir.cpp
+++ b/scripts/llvm-plugins/bpf_scan_ir.cpp
@@ -8,11 +8,13 @@
#include "llvm/ADT/SmallPtrSet.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/Analysis/CaptureTracking.h"
+#include "llvm/Analysis/LazyValueInfo.h"
#include "llvm/Analysis/ValueTracking.h"
#include "llvm/Config/llvm-config.h"
#include "llvm/IR/Analysis.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
+#include "llvm/IR/ConstantRange.h"
#include "llvm/IR/Constants.h"
#include "llvm/IR/Dominators.h"
#include "llvm/IR/GlobalVariable.h"
@@ -34,9 +36,40 @@ namespace {

static constexpr int MaxErrno = 4095;

+// is Range in [-4095, 0) ?
+static bool isErrnoRange(const ConstantRange &Range)
+{
+ unsigned Width = Range.getBitWidth();
+
+ if (Width != 32 && Width != 64)
+ return false;
+
+ auto L = APInt(Width, 0) - APInt(Width, MaxErrno);
+ auto R = APInt(Width, 0);
+ return !Range.isEmptySet() && ConstantRange(L, R).contains(Range);
+}
+
/* The errors which clean paths of a function return */
using ReturnValues = SmallVector<int64_t, 8>;

+/* Return true and set N if RANGE holds a single errno */
+static bool singleErrno(const ConstantRange &Range, int64_t &N)
+{
+ const APInt *C = Range.getSingleElement();
+
+ if (!C || !isErrnoRange(Range))
+ return false;
+ N = C->getSExtValue();
+ return true;
+}
+
+/* Return true and set N if the value at use U is a single errno */
+static bool singleErrnoAtUse(const Use &U, LazyValueInfo &LVI, int64_t &N)
+{
+ return U.get()->getType()->isIntegerTy() &&
+ singleErrno(LVI.getConstantRangeAtUse(U, false), N);
+}
+
/* Return true and set N if V is an errno constant */
static bool errnoConstant(const Value *V, int64_t &N)
{
@@ -49,6 +82,42 @@ static bool errnoConstant(const Value *V, int64_t &N)
return N >= -MaxErrno && N < 0;
}

+/*
+ * Add the errors which V, the value returned in RET, is known to be on the
+ * edges to RET from CLEANBBS to VALUES. V may be a PHI in RET. A PHI in an
+ * earlier block is not followed: a later branch may split its values between
+ * clean and dirty paths.
+ */
+static void collectErrnos(Value *V, BasicBlock *Ret, LazyValueInfo &LVI,
+ const SmallPtrSetImpl<BasicBlock *> &CleanBBs, ReturnValues &Values)
+{
+ int64_t N;
+
+ auto *Phi = dyn_cast<PHINode>(V);
+
+ if (!Phi || Phi->getParent() != Ret)
+ return;
+ for (unsigned i = 0; i < Phi->getNumIncomingValues(); i++) {
+ Value *Incoming = Phi->getIncomingValue(i);
+ BasicBlock *Pred = Phi->getIncomingBlock(i);
+
+ if (!CleanBBs.contains(Pred))
+ continue;
+ if (errnoConstant(Incoming, N)) {
+ Values.push_back(N);
+ continue;
+ }
+ /*
+ * LazyValueInfo knows the ranges of integers only. On an edge it
+ * also uses the branch condition, as in "if (err == -EINVAL) goto
+ * out", where err is the error.
+ */
+ if (Incoming->getType()->isIntegerTy() &&
+ singleErrno(LVI.getConstantRangeOnEdge(Incoming, Pred, Ret), N))
+ Values.push_back(N);
+ }
+}
+
class CleanInstructions {
DominatorTree &DT;
DenseMap<const Instruction *, bool> PrivateWrites;
@@ -111,7 +180,8 @@ static bool cleanBB(BasicBlock *BB, CleanInstructions &Clean)
}

/* Add the errors which clean paths of F are known to return to VALUES */
-static void cleanReturnValues(Function &F, CleanInstructions &Clean, ReturnValues &Values)
+static void cleanReturnValues(Function &F, LazyValueInfo &LVI, CleanInstructions &Clean,
+ ReturnValues &Values)
{
SmallPtrSet<BasicBlock *, 32> CleanBBs;
SmallVector<BasicBlock *, 32> Worklist;
@@ -154,8 +224,7 @@ static void cleanReturnValues(Function &F, CleanInstructions &Clean, ReturnValue

/*
* The returns of the clean blocks which only clean paths reach or which
- * one of them branches to. Dirty paths may also reach the latter, but
- * the returned constant is the same on all paths.
+ * one of them branches to
*/
for (BasicBlock &BB : F) {
auto *Return = dyn_cast<ReturnInst>(BB.getTerminator());
@@ -168,8 +237,11 @@ static void cleanReturnValues(Function &F, CleanInstructions &Clean, ReturnValue
Entered |= CleanBBs.contains(Pred);
if (!Entered)
continue;
- if (errnoConstant(Return->getReturnValue(), N))
+ if (singleErrnoAtUse(Return->getOperandUse(0), LVI, N)) {
Values.push_back(N);
+ continue;
+ }
+ collectErrnos(Return->getReturnValue(), &BB, LVI, CleanBBs, Values);
}
llvm::sort(Values);
Values.erase(std::unique(Values.begin(), Values.end()), Values.end());
@@ -237,10 +309,11 @@ public:
Flags |= FMODRET_FN_WEAK;
if (Ret->isIntegerTy(64))
Flags |= FMODRET_FN_RET64;
+ auto &LVI = FAM.getResult<LazyValueAnalysis>(F);
auto &DT = FAM.getResult<DominatorTreeAnalysis>(F);
CleanInstructions Clean(DT);

- cleanReturnValues(F, Clean, Values);
+ cleanReturnValues(F, LVI, Clean, Values);
/* A weak definition always has a record, see fmodret_metadata.h */
if (!Values.empty() || (Flags & FMODRET_FN_WEAK))
Descriptions.push_back({&F, Flags, std::move(Values)});
@@ -253,6 +326,7 @@ public:
PreservedAnalyses PA;
PA.preserve<FunctionAnalysisManagerModuleProxy>();
PA.preserveSet<CFGAnalyses>();
+ PA.preserve<LazyValueAnalysis>();
return PA;
}

--
2.43.0