This commit adds a new BPF specific structure attribte
`__attribute__((preserve_static_offset))` and a pass to deal with it.
This attribute may be attached to a struct or union declaration, where
it notifies the compiler that this structure is a "context" structure.
The following limitations apply to context structures:
- runtime environment might patch access to the fields of this type by
updating the field offset;
BPF verifier limits access patterns allowed for certain data
types. E.g. `struct __sk_buff` and `struct bpf_sock_ops`. For these
types only `LD/ST <reg> <static-offset>` memory loads and stores are
allowed.
This is so because offsets of the fields of these structures do not
match real offsets in the running kernel. During BPF program
load/verification loads and stores to the fields of these types are
rewritten so that offsets match real offsets. For this rewrite to
happen static offsets have to be encoded in the instructions.
See `kernel/bpf/verifier.c:convert_ctx_access` function in the Linux
kernel source tree for details.
- runtime environment might disallow access to the field of the type
through modified pointers.
During BPF program verification a tag `PTR_TO_CTX` is tracked for
register values. In case if register with such tag is modified BPF
programs are not allowed to read or write memory using register. See
kernel/bpf/verifier.c:check_mem_access function in the Linux kernel
source tree for details.
Access to the structure fields is translated to IR as a sequence:
- `(load (getelementptr %ptr %offset))` or
- `(store (getelementptr %ptr %offset))`
During instruction selection phase such sequences are translated as a
single load instruction with embedded offset, e.g. `LDW %ptr, %offset`,
which matches access pattern necessary for the restricted
set of types described above (when `%offset` is static).
Multiple optimizer passes might separate these instructions, this
includes:
- SimplifyCFGPass (sinking)
- InstCombine (sinking)
- GVN (hoisting)
The `preserve_static_offset` attribute marks structures for which the
following transformations happen:
- at the early IR processing stage:
- `(load (getelementptr ...))` replaced by call to intrinsic
`llvm.bpf.getelementptr.and.load`;
- `(store (getelementptr ...))` replaced by call to intrinsic
`llvm.bpf.getelementptr.and.store`;
- at the late IR processing stage this modification is undone.
Such handling prevents various optimizer passes from generating
sequences of instructions that would be rejected by BPF verifier.
The __attribute__((preserve_static_offset)) has a priority over
__attribute__((preserve_access_index)). When preserve_access_index
attribute is present preserve access index transformations are not
applied.
This addresses the issue reported by the following thread:
https://lore.kernel.org/bpf/CAA-VZPmxh8o8EBcJ=m-DH4ytcxDFmo0JKsm1p1gf40kS0CE3NQ@mail.gmail.com/T/#m4b9ce2ce73b34f34172328f975235fc6f19841b6
This is a second attempt to commit this change, previous reverted
commit is: cb13e9286b.
The following items had been fixed:
- test case bpf-preserve-static-offset-bitfield.c now uses
`-triple bpfel` to avoid different codegen for little/big endian
targets.
- BPFPreserveStaticOffset.cpp:removePAICalls() modified to avoid
use after free for `WorkList` elements `V`.
Differential Revision: https://reviews.llvm.org/D133361
681 lines
25 KiB
C++
681 lines
25 KiB
C++
//===------ BPFPreserveStaticOffset.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
|
|
//
|
|
//===----------------------------------------------------------------------===//
|
|
//
|
|
// TLDR: replaces llvm.preserve.static.offset + GEP + load / store
|
|
// with llvm.bpf.getelementptr.and.load / store
|
|
//
|
|
// This file implements BPFPreserveStaticOffsetPass transformation.
|
|
// This transformation address two BPF verifier specific issues:
|
|
//
|
|
// (a) Access to the fields of some structural types is allowed only
|
|
// using load and store instructions with static immediate offsets.
|
|
//
|
|
// Examples of such types are `struct __sk_buff` and `struct
|
|
// bpf_sock_ops`. This is so because offsets of the fields of
|
|
// these structures do not match real offsets in the running
|
|
// kernel. During BPF program load LDX and STX instructions
|
|
// referring to the fields of these types are rewritten so that
|
|
// offsets match real offsets. For this rewrite to happen field
|
|
// offsets have to be encoded as immediate operands of the
|
|
// instructions.
|
|
//
|
|
// See kernel/bpf/verifier.c:convert_ctx_access function in the
|
|
// Linux kernel source tree for details.
|
|
//
|
|
// (b) Pointers to context parameters of BPF programs must not be
|
|
// modified before access.
|
|
//
|
|
// During BPF program verification a tag PTR_TO_CTX is tracked for
|
|
// register values. In case if register with such tag is modified
|
|
// BPF program is not allowed to read or write memory using this
|
|
// register. See kernel/bpf/verifier.c:check_mem_access function
|
|
// in the Linux kernel source tree for details.
|
|
//
|
|
// The following sequence of the IR instructions:
|
|
//
|
|
// %x = getelementptr %ptr, %constant_offset
|
|
// %y = load %x
|
|
//
|
|
// Is translated as a single machine instruction:
|
|
//
|
|
// LDW %ptr, %constant_offset
|
|
//
|
|
// In order for cases (a) and (b) to work the sequence %x-%y above has
|
|
// to be preserved by the IR passes.
|
|
//
|
|
// However, several optimization passes might sink `load` instruction
|
|
// or hoist `getelementptr` instruction so that the instructions are
|
|
// no longer in sequence. Examples of such passes are:
|
|
// SimplifyCFGPass, InstCombinePass, GVNPass.
|
|
// After such modification the verifier would reject the BPF program.
|
|
//
|
|
// To avoid this issue the patterns like (load/store (getelementptr ...))
|
|
// are replaced by calls to BPF specific intrinsic functions:
|
|
// - llvm.bpf.getelementptr.and.load
|
|
// - llvm.bpf.getelementptr.and.store
|
|
//
|
|
// These calls are lowered back to (load/store (getelementptr ...))
|
|
// by BPFCheckAndAdjustIR pass right before the translation from IR to
|
|
// machine instructions.
|
|
//
|
|
// The transformation is split into the following steps:
|
|
// - When IR is generated from AST the calls to intrinsic function
|
|
// llvm.preserve.static.offset are inserted.
|
|
// - BPFPreserveStaticOffsetPass is executed as early as possible
|
|
// with AllowPatial set to true, this handles marked GEP chains
|
|
// with constant offsets.
|
|
// - BPFPreserveStaticOffsetPass is executed at ScalarOptimizerLateEPCallback
|
|
// with AllowPatial set to false, this handles marked GEP chains
|
|
// with offsets that became constant after loop unrolling, e.g.
|
|
// to handle the following code:
|
|
//
|
|
// struct context { int x[4]; } __attribute__((preserve_static_offset));
|
|
//
|
|
// struct context *ctx = ...;
|
|
// #pragma clang loop unroll(full)
|
|
// for (int i = 0; i < 4; ++i)
|
|
// foo(ctx->x[i]);
|
|
//
|
|
// The early BPFPreserveStaticOffsetPass run is necessary to allow
|
|
// additional GVN / CSE opportunities after functions inlining.
|
|
// The relative order of optimization applied to function:
|
|
// - early stage (1)
|
|
// - ...
|
|
// - function inlining (2)
|
|
// - ...
|
|
// - loop unrolling
|
|
// - ...
|
|
// - ScalarOptimizerLateEPCallback (3)
|
|
//
|
|
// When function A is inlined into function B all optimizations for A
|
|
// are already done, while some passes remain for B. In case if
|
|
// BPFPreserveStaticOffsetPass is done at (3) but not done at (1)
|
|
// the code after (2) would contain a mix of
|
|
// (load (gep %p)) and (get.and.load %p) usages:
|
|
// - the (load (gep %p)) would come from the calling function;
|
|
// - the (get.and.load %p) would come from the callee function.
|
|
// Thus clobbering CSE / GVN passes done after inlining.
|
|
|
|
#include "BPF.h"
|
|
#include "BPFCORE.h"
|
|
#include "llvm/ADT/SmallPtrSet.h"
|
|
#include "llvm/ADT/SmallVector.h"
|
|
#include "llvm/IR/Argument.h"
|
|
#include "llvm/IR/Attributes.h"
|
|
#include "llvm/IR/BasicBlock.h"
|
|
#include "llvm/IR/Constants.h"
|
|
#include "llvm/IR/DebugInfoMetadata.h"
|
|
#include "llvm/IR/DiagnosticInfo.h"
|
|
#include "llvm/IR/IRBuilder.h"
|
|
#include "llvm/IR/InstIterator.h"
|
|
#include "llvm/IR/Instructions.h"
|
|
#include "llvm/IR/Intrinsics.h"
|
|
#include "llvm/IR/IntrinsicsBPF.h"
|
|
#include "llvm/Support/Debug.h"
|
|
#include "llvm/Support/ErrorHandling.h"
|
|
|
|
#define DEBUG_TYPE "bpf-preserve-static-offset"
|
|
|
|
using namespace llvm;
|
|
|
|
static const unsigned GepAndLoadFirstIdxArg = 6;
|
|
static const unsigned GepAndStoreFirstIdxArg = 7;
|
|
|
|
static bool isIntrinsicCall(Value *I, Intrinsic::ID Id) {
|
|
if (auto *Call = dyn_cast<CallInst>(I))
|
|
if (Function *Func = Call->getCalledFunction())
|
|
return Func->getIntrinsicID() == Id;
|
|
return false;
|
|
}
|
|
|
|
static bool isPreserveStaticOffsetCall(Value *I) {
|
|
return isIntrinsicCall(I, Intrinsic::preserve_static_offset);
|
|
}
|
|
|
|
static CallInst *isGEPAndLoad(Value *I) {
|
|
if (isIntrinsicCall(I, Intrinsic::bpf_getelementptr_and_load))
|
|
return cast<CallInst>(I);
|
|
return nullptr;
|
|
}
|
|
|
|
static CallInst *isGEPAndStore(Value *I) {
|
|
if (isIntrinsicCall(I, Intrinsic::bpf_getelementptr_and_store))
|
|
return cast<CallInst>(I);
|
|
return nullptr;
|
|
}
|
|
|
|
template <class T = Instruction>
|
|
static DILocation *mergeDILocations(SmallVector<T *> &Insns) {
|
|
DILocation *Merged = (*Insns.begin())->getDebugLoc();
|
|
for (T *I : Insns)
|
|
Merged = DILocation::getMergedLocation(Merged, I->getDebugLoc());
|
|
return Merged;
|
|
}
|
|
|
|
static CallInst *makeIntrinsicCall(Module *M,
|
|
Intrinsic::BPFIntrinsics Intrinsic,
|
|
ArrayRef<Type *> Types,
|
|
ArrayRef<Value *> Args) {
|
|
|
|
Function *Fn = Intrinsic::getDeclaration(M, Intrinsic, Types);
|
|
return CallInst::Create(Fn, Args);
|
|
}
|
|
|
|
static void setParamElementType(CallInst *Call, unsigned ArgNo, Type *Type) {
|
|
LLVMContext &C = Call->getContext();
|
|
Call->addParamAttr(ArgNo, Attribute::get(C, Attribute::ElementType, Type));
|
|
}
|
|
|
|
static void setParamReadNone(CallInst *Call, unsigned ArgNo) {
|
|
LLVMContext &C = Call->getContext();
|
|
Call->addParamAttr(ArgNo, Attribute::get(C, Attribute::ReadNone));
|
|
}
|
|
|
|
static void setParamReadOnly(CallInst *Call, unsigned ArgNo) {
|
|
LLVMContext &C = Call->getContext();
|
|
Call->addParamAttr(ArgNo, Attribute::get(C, Attribute::ReadOnly));
|
|
}
|
|
|
|
static void setParamWriteOnly(CallInst *Call, unsigned ArgNo) {
|
|
LLVMContext &C = Call->getContext();
|
|
Call->addParamAttr(ArgNo, Attribute::get(C, Attribute::WriteOnly));
|
|
}
|
|
|
|
namespace {
|
|
struct GEPChainInfo {
|
|
bool InBounds;
|
|
Type *SourceElementType;
|
|
SmallVector<Value *> Indices;
|
|
SmallVector<GetElementPtrInst *> Members;
|
|
|
|
GEPChainInfo() { reset(); }
|
|
|
|
void reset() {
|
|
InBounds = true;
|
|
SourceElementType = nullptr;
|
|
Indices.clear();
|
|
Members.clear();
|
|
}
|
|
};
|
|
} // Anonymous namespace
|
|
|
|
template <class T = std::disjunction<LoadInst, StoreInst>>
|
|
static void fillCommonArgs(LLVMContext &C, SmallVector<Value *> &Args,
|
|
GEPChainInfo &GEP, T *Insn) {
|
|
Type *Int8Ty = Type::getInt8Ty(C);
|
|
Type *Int1Ty = Type::getInt1Ty(C);
|
|
// Implementation of Align guarantees that ShiftValue < 64
|
|
unsigned AlignShiftValue = Log2_64(Insn->getAlign().value());
|
|
Args.push_back(GEP.Members[0]->getPointerOperand());
|
|
Args.push_back(ConstantInt::get(Int1Ty, Insn->isVolatile()));
|
|
Args.push_back(ConstantInt::get(Int8Ty, (unsigned)Insn->getOrdering()));
|
|
Args.push_back(ConstantInt::get(Int8Ty, (unsigned)Insn->getSyncScopeID()));
|
|
Args.push_back(ConstantInt::get(Int8Ty, AlignShiftValue));
|
|
Args.push_back(ConstantInt::get(Int1Ty, GEP.InBounds));
|
|
Args.append(GEP.Indices.begin(), GEP.Indices.end());
|
|
}
|
|
|
|
static Instruction *makeGEPAndLoad(Module *M, GEPChainInfo &GEP,
|
|
LoadInst *Load) {
|
|
SmallVector<Value *> Args;
|
|
fillCommonArgs(M->getContext(), Args, GEP, Load);
|
|
CallInst *Call = makeIntrinsicCall(M, Intrinsic::bpf_getelementptr_and_load,
|
|
{Load->getType()}, Args);
|
|
setParamElementType(Call, 0, GEP.SourceElementType);
|
|
Call->applyMergedLocation(mergeDILocations(GEP.Members), Load->getDebugLoc());
|
|
Call->setName((*GEP.Members.rbegin())->getName());
|
|
if (Load->isUnordered()) {
|
|
Call->setOnlyReadsMemory();
|
|
Call->setOnlyAccessesArgMemory();
|
|
setParamReadOnly(Call, 0);
|
|
}
|
|
for (unsigned I = GepAndLoadFirstIdxArg; I < Args.size(); ++I)
|
|
Call->addParamAttr(I, Attribute::ImmArg);
|
|
Call->setAAMetadata(Load->getAAMetadata());
|
|
return Call;
|
|
}
|
|
|
|
static Instruction *makeGEPAndStore(Module *M, GEPChainInfo &GEP,
|
|
StoreInst *Store) {
|
|
SmallVector<Value *> Args;
|
|
Args.push_back(Store->getValueOperand());
|
|
fillCommonArgs(M->getContext(), Args, GEP, Store);
|
|
CallInst *Call =
|
|
makeIntrinsicCall(M, Intrinsic::bpf_getelementptr_and_store,
|
|
{Store->getValueOperand()->getType()}, Args);
|
|
setParamElementType(Call, 1, GEP.SourceElementType);
|
|
if (Store->getValueOperand()->getType()->isPointerTy())
|
|
setParamReadNone(Call, 0);
|
|
Call->applyMergedLocation(mergeDILocations(GEP.Members),
|
|
Store->getDebugLoc());
|
|
if (Store->isUnordered()) {
|
|
Call->setOnlyWritesMemory();
|
|
Call->setOnlyAccessesArgMemory();
|
|
setParamWriteOnly(Call, 1);
|
|
}
|
|
for (unsigned I = GepAndStoreFirstIdxArg; I < Args.size(); ++I)
|
|
Call->addParamAttr(I, Attribute::ImmArg);
|
|
Call->setAAMetadata(Store->getAAMetadata());
|
|
return Call;
|
|
}
|
|
|
|
static unsigned getOperandAsUnsigned(CallInst *Call, unsigned ArgNo) {
|
|
if (auto *Int = dyn_cast<ConstantInt>(Call->getOperand(ArgNo)))
|
|
return Int->getValue().getZExtValue();
|
|
std::string Report;
|
|
raw_string_ostream ReportS(Report);
|
|
ReportS << "Expecting ConstantInt as argument #" << ArgNo << " of " << *Call
|
|
<< "\n";
|
|
report_fatal_error(StringRef(Report));
|
|
}
|
|
|
|
static GetElementPtrInst *reconstructGEP(CallInst *Call, int Delta) {
|
|
SmallVector<Value *> Indices;
|
|
Indices.append(Call->data_operands_begin() + 6 + Delta,
|
|
Call->data_operands_end());
|
|
Type *GEPPointeeType = Call->getParamElementType(Delta);
|
|
auto *GEP =
|
|
GetElementPtrInst::Create(GEPPointeeType, Call->getOperand(Delta),
|
|
ArrayRef<Value *>(Indices), Call->getName());
|
|
GEP->setIsInBounds(getOperandAsUnsigned(Call, 5 + Delta));
|
|
return GEP;
|
|
}
|
|
|
|
template <class T = std::disjunction<LoadInst, StoreInst>>
|
|
static void reconstructCommon(CallInst *Call, GetElementPtrInst *GEP, T *Insn,
|
|
int Delta) {
|
|
Insn->setVolatile(getOperandAsUnsigned(Call, 1 + Delta));
|
|
Insn->setOrdering((AtomicOrdering)getOperandAsUnsigned(Call, 2 + Delta));
|
|
Insn->setSyncScopeID(getOperandAsUnsigned(Call, 3 + Delta));
|
|
unsigned AlignShiftValue = getOperandAsUnsigned(Call, 4 + Delta);
|
|
Insn->setAlignment(Align(1ULL << AlignShiftValue));
|
|
GEP->setDebugLoc(Call->getDebugLoc());
|
|
Insn->setDebugLoc(Call->getDebugLoc());
|
|
Insn->setAAMetadata(Call->getAAMetadata());
|
|
}
|
|
|
|
std::pair<GetElementPtrInst *, LoadInst *>
|
|
BPFPreserveStaticOffsetPass::reconstructLoad(CallInst *Call) {
|
|
GetElementPtrInst *GEP = reconstructGEP(Call, 0);
|
|
Type *ReturnType = Call->getFunctionType()->getReturnType();
|
|
auto *Load = new LoadInst(ReturnType, GEP, "",
|
|
/* These would be set in reconstructCommon */
|
|
false, Align(1));
|
|
reconstructCommon(Call, GEP, Load, 0);
|
|
return std::pair{GEP, Load};
|
|
}
|
|
|
|
std::pair<GetElementPtrInst *, StoreInst *>
|
|
BPFPreserveStaticOffsetPass::reconstructStore(CallInst *Call) {
|
|
GetElementPtrInst *GEP = reconstructGEP(Call, 1);
|
|
auto *Store = new StoreInst(Call->getOperand(0), GEP,
|
|
/* These would be set in reconstructCommon */
|
|
false, Align(1));
|
|
reconstructCommon(Call, GEP, Store, 1);
|
|
return std::pair{GEP, Store};
|
|
}
|
|
|
|
static bool isZero(Value *V) {
|
|
auto *CI = dyn_cast<ConstantInt>(V);
|
|
return CI && CI->isZero();
|
|
}
|
|
|
|
// Given a chain of GEP instructions collect information necessary to
|
|
// merge this chain as a single GEP instruction of form:
|
|
// getelementptr %<type>, ptr %p, i32 0, <field_idx1>, <field_idx2>, ...
|
|
static bool foldGEPChainAsStructAccess(SmallVector<GetElementPtrInst *> &GEPs,
|
|
GEPChainInfo &Info) {
|
|
if (GEPs.empty())
|
|
return false;
|
|
|
|
if (!all_of(GEPs, [=](GetElementPtrInst *GEP) {
|
|
return GEP->hasAllConstantIndices();
|
|
}))
|
|
return false;
|
|
|
|
GetElementPtrInst *First = GEPs[0];
|
|
Info.InBounds = First->isInBounds();
|
|
Info.SourceElementType = First->getSourceElementType();
|
|
Type *ResultElementType = First->getResultElementType();
|
|
Info.Indices.append(First->idx_begin(), First->idx_end());
|
|
Info.Members.push_back(First);
|
|
|
|
for (auto *Iter = GEPs.begin() + 1; Iter != GEPs.end(); ++Iter) {
|
|
GetElementPtrInst *GEP = *Iter;
|
|
if (!isZero(*GEP->idx_begin())) {
|
|
Info.reset();
|
|
return false;
|
|
}
|
|
if (!GEP->getSourceElementType() ||
|
|
GEP->getSourceElementType() != ResultElementType) {
|
|
Info.reset();
|
|
return false;
|
|
}
|
|
Info.InBounds &= GEP->isInBounds();
|
|
Info.Indices.append(GEP->idx_begin() + 1, GEP->idx_end());
|
|
Info.Members.push_back(GEP);
|
|
ResultElementType = GEP->getResultElementType();
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Given a chain of GEP instructions collect information necessary to
|
|
// merge this chain as a single GEP instruction of form:
|
|
// getelementptr i8, ptr %p, i64 %offset
|
|
static bool foldGEPChainAsU8Access(SmallVector<GetElementPtrInst *> &GEPs,
|
|
GEPChainInfo &Info) {
|
|
if (GEPs.empty())
|
|
return false;
|
|
|
|
GetElementPtrInst *First = GEPs[0];
|
|
const DataLayout &DL = First->getModule()->getDataLayout();
|
|
LLVMContext &C = First->getContext();
|
|
Type *PtrTy = First->getType()->getScalarType();
|
|
APInt Offset(DL.getIndexTypeSizeInBits(PtrTy), 0);
|
|
for (GetElementPtrInst *GEP : GEPs) {
|
|
if (!GEP->accumulateConstantOffset(DL, Offset)) {
|
|
Info.reset();
|
|
return false;
|
|
}
|
|
Info.InBounds &= GEP->isInBounds();
|
|
Info.Members.push_back(GEP);
|
|
}
|
|
Info.SourceElementType = Type::getInt8Ty(C);
|
|
Info.Indices.push_back(ConstantInt::get(C, Offset));
|
|
|
|
return true;
|
|
}
|
|
|
|
static void reportNonStaticGEPChain(Instruction *Insn) {
|
|
auto Msg = DiagnosticInfoUnsupported(
|
|
*Insn->getFunction(),
|
|
Twine("Non-constant offset in access to a field of a type marked "
|
|
"with preserve_static_offset might be rejected by BPF verifier")
|
|
.concat(Insn->getDebugLoc()
|
|
? ""
|
|
: " (pass -g option to get exact location)"),
|
|
Insn->getDebugLoc(), DS_Warning);
|
|
Insn->getContext().diagnose(Msg);
|
|
}
|
|
|
|
static bool allZeroIndices(SmallVector<GetElementPtrInst *> &GEPs) {
|
|
return GEPs.empty() || all_of(GEPs, [=](GetElementPtrInst *GEP) {
|
|
return GEP->hasAllZeroIndices();
|
|
});
|
|
}
|
|
|
|
static bool tryToReplaceWithGEPBuiltin(Instruction *LoadOrStoreTemplate,
|
|
SmallVector<GetElementPtrInst *> &GEPs,
|
|
Instruction *InsnToReplace) {
|
|
GEPChainInfo GEPChain;
|
|
if (!foldGEPChainAsStructAccess(GEPs, GEPChain) &&
|
|
!foldGEPChainAsU8Access(GEPs, GEPChain)) {
|
|
return false;
|
|
}
|
|
Module *M = InsnToReplace->getModule();
|
|
if (auto *Load = dyn_cast<LoadInst>(LoadOrStoreTemplate)) {
|
|
Instruction *Replacement = makeGEPAndLoad(M, GEPChain, Load);
|
|
Replacement->insertBefore(InsnToReplace);
|
|
InsnToReplace->replaceAllUsesWith(Replacement);
|
|
}
|
|
if (auto *Store = dyn_cast<StoreInst>(LoadOrStoreTemplate)) {
|
|
Instruction *Replacement = makeGEPAndStore(M, GEPChain, Store);
|
|
Replacement->insertBefore(InsnToReplace);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
// Check if U->getPointerOperand() == I
|
|
static bool isPointerOperand(Value *I, User *U) {
|
|
if (auto *L = dyn_cast<LoadInst>(U))
|
|
return L->getPointerOperand() == I;
|
|
if (auto *S = dyn_cast<StoreInst>(U))
|
|
return S->getPointerOperand() == I;
|
|
if (auto *GEP = dyn_cast<GetElementPtrInst>(U))
|
|
return GEP->getPointerOperand() == I;
|
|
if (auto *Call = isGEPAndLoad(U))
|
|
return Call->getArgOperand(0) == I;
|
|
if (auto *Call = isGEPAndStore(U))
|
|
return Call->getArgOperand(1) == I;
|
|
return false;
|
|
}
|
|
|
|
static bool isInlineableCall(User *U) {
|
|
if (auto *Call = dyn_cast<CallInst>(U))
|
|
return Call->hasFnAttr(Attribute::InlineHint);
|
|
return false;
|
|
}
|
|
|
|
static void rewriteAccessChain(Instruction *Insn,
|
|
SmallVector<GetElementPtrInst *> &GEPs,
|
|
SmallVector<Instruction *> &Visited,
|
|
bool AllowPatial, bool &StillUsed);
|
|
|
|
static void rewriteUses(Instruction *Insn,
|
|
SmallVector<GetElementPtrInst *> &GEPs,
|
|
SmallVector<Instruction *> &Visited, bool AllowPatial,
|
|
bool &StillUsed) {
|
|
for (User *U : Insn->users()) {
|
|
auto *UI = dyn_cast<Instruction>(U);
|
|
if (UI && (isPointerOperand(Insn, UI) || isPreserveStaticOffsetCall(UI) ||
|
|
isInlineableCall(UI)))
|
|
rewriteAccessChain(UI, GEPs, Visited, AllowPatial, StillUsed);
|
|
else
|
|
LLVM_DEBUG({
|
|
llvm::dbgs() << "unsupported usage in BPFPreserveStaticOffsetPass:\n";
|
|
llvm::dbgs() << " Insn: " << *Insn << "\n";
|
|
llvm::dbgs() << " User: " << *U << "\n";
|
|
});
|
|
}
|
|
}
|
|
|
|
// A DFS traversal of GEP chain trees starting from Root.
|
|
//
|
|
// Recursion descends through GEP instructions and
|
|
// llvm.preserve.static.offset calls. Recursion stops at any other
|
|
// instruction. If load or store instruction is reached it is replaced
|
|
// by a call to `llvm.bpf.getelementptr.and.load` or
|
|
// `llvm.bpf.getelementptr.and.store` intrinsic.
|
|
// If `llvm.bpf.getelementptr.and.load/store` is reached the accumulated
|
|
// GEPs are merged into the intrinsic call.
|
|
// If nested calls to `llvm.preserve.static.offset` are encountered these
|
|
// calls are marked for deletion.
|
|
//
|
|
// Parameters description:
|
|
// - Insn - current position in the tree
|
|
// - GEPs - GEP instructions for the current branch
|
|
// - Visited - a list of visited instructions in DFS order,
|
|
// order is important for unused instruction deletion.
|
|
// - AllowPartial - when true GEP chains that can't be folded are
|
|
// not reported, otherwise diagnostic message is show for such chains.
|
|
// - StillUsed - set to true if one of the GEP chains could not be
|
|
// folded, makes sense when AllowPartial is false, means that root
|
|
// preserve.static.offset call is still in use and should remain
|
|
// until the next run of this pass.
|
|
static void rewriteAccessChain(Instruction *Insn,
|
|
SmallVector<GetElementPtrInst *> &GEPs,
|
|
SmallVector<Instruction *> &Visited,
|
|
bool AllowPatial, bool &StillUsed) {
|
|
auto MarkAndTraverseUses = [&]() {
|
|
Visited.push_back(Insn);
|
|
rewriteUses(Insn, GEPs, Visited, AllowPatial, StillUsed);
|
|
};
|
|
auto TryToReplace = [&](Instruction *LoadOrStore) {
|
|
// Do nothing for (preserve.static.offset (load/store ..)) or for
|
|
// GEPs with zero indices. Such constructs lead to zero offset and
|
|
// are simplified by other passes.
|
|
if (allZeroIndices(GEPs))
|
|
return;
|
|
if (tryToReplaceWithGEPBuiltin(LoadOrStore, GEPs, Insn)) {
|
|
Visited.push_back(Insn);
|
|
return;
|
|
}
|
|
if (!AllowPatial)
|
|
reportNonStaticGEPChain(Insn);
|
|
StillUsed = true;
|
|
};
|
|
if (isa<LoadInst>(Insn) || isa<StoreInst>(Insn)) {
|
|
TryToReplace(Insn);
|
|
} else if (isGEPAndLoad(Insn)) {
|
|
auto [GEP, Load] =
|
|
BPFPreserveStaticOffsetPass::reconstructLoad(cast<CallInst>(Insn));
|
|
GEPs.push_back(GEP);
|
|
TryToReplace(Load);
|
|
GEPs.pop_back();
|
|
delete Load;
|
|
delete GEP;
|
|
} else if (isGEPAndStore(Insn)) {
|
|
// This case can't be merged with the above because
|
|
// `delete Load` / `delete Store` wants a concrete type,
|
|
// destructor of Instruction is protected.
|
|
auto [GEP, Store] =
|
|
BPFPreserveStaticOffsetPass::reconstructStore(cast<CallInst>(Insn));
|
|
GEPs.push_back(GEP);
|
|
TryToReplace(Store);
|
|
GEPs.pop_back();
|
|
delete Store;
|
|
delete GEP;
|
|
} else if (auto *GEP = dyn_cast<GetElementPtrInst>(Insn)) {
|
|
GEPs.push_back(GEP);
|
|
MarkAndTraverseUses();
|
|
GEPs.pop_back();
|
|
} else if (isPreserveStaticOffsetCall(Insn)) {
|
|
MarkAndTraverseUses();
|
|
} else if (isInlineableCall(Insn)) {
|
|
// Preserve preserve.static.offset call for parameters of
|
|
// functions that might be inlined. These would be removed on a
|
|
// second pass after inlining.
|
|
// Might happen when a pointer to a preserve_static_offset
|
|
// structure is passed as parameter of a function that would be
|
|
// inlined inside a loop that would be unrolled.
|
|
if (AllowPatial)
|
|
StillUsed = true;
|
|
} else {
|
|
SmallString<128> Buf;
|
|
raw_svector_ostream BufStream(Buf);
|
|
BufStream << *Insn;
|
|
report_fatal_error(
|
|
Twine("Unexpected rewriteAccessChain Insn = ").concat(Buf));
|
|
}
|
|
}
|
|
|
|
static void removeMarkerCall(Instruction *Marker) {
|
|
Marker->replaceAllUsesWith(Marker->getOperand(0));
|
|
Marker->eraseFromParent();
|
|
}
|
|
|
|
static bool rewriteAccessChain(Instruction *Marker, bool AllowPatial,
|
|
SmallPtrSetImpl<Instruction *> &RemovedMarkers) {
|
|
SmallVector<GetElementPtrInst *> GEPs;
|
|
SmallVector<Instruction *> Visited;
|
|
bool StillUsed = false;
|
|
rewriteUses(Marker, GEPs, Visited, AllowPatial, StillUsed);
|
|
// Check if Visited instructions could be removed, iterate in
|
|
// reverse to unblock instructions higher in the chain.
|
|
for (auto V = Visited.rbegin(); V != Visited.rend(); ++V) {
|
|
if (isPreserveStaticOffsetCall(*V)) {
|
|
removeMarkerCall(*V);
|
|
RemovedMarkers.insert(*V);
|
|
} else if ((*V)->use_empty()) {
|
|
(*V)->eraseFromParent();
|
|
}
|
|
}
|
|
return StillUsed;
|
|
}
|
|
|
|
static std::vector<Instruction *>
|
|
collectPreserveStaticOffsetCalls(Function &F) {
|
|
std::vector<Instruction *> Calls;
|
|
for (Instruction &Insn : instructions(F))
|
|
if (isPreserveStaticOffsetCall(&Insn))
|
|
Calls.push_back(&Insn);
|
|
return Calls;
|
|
}
|
|
|
|
bool isPreserveArrayIndex(Value *V) {
|
|
return isIntrinsicCall(V, Intrinsic::preserve_array_access_index);
|
|
}
|
|
|
|
bool isPreserveStructIndex(Value *V) {
|
|
return isIntrinsicCall(V, Intrinsic::preserve_struct_access_index);
|
|
}
|
|
|
|
bool isPreserveUnionIndex(Value *V) {
|
|
return isIntrinsicCall(V, Intrinsic::preserve_union_access_index);
|
|
}
|
|
|
|
static void removePAICalls(Instruction *Marker) {
|
|
auto IsPointerOperand = [](Value *Op, User *U) {
|
|
if (auto *GEP = dyn_cast<GetElementPtrInst>(U))
|
|
return GEP->getPointerOperand() == Op;
|
|
if (isPreserveStaticOffsetCall(U) || isPreserveArrayIndex(U) ||
|
|
isPreserveStructIndex(U) || isPreserveUnionIndex(U))
|
|
return cast<CallInst>(U)->getArgOperand(0) == Op;
|
|
return false;
|
|
};
|
|
|
|
SmallVector<Value *, 32> WorkList;
|
|
WorkList.push_back(Marker);
|
|
do {
|
|
Value *V = WorkList.pop_back_val();
|
|
for (User *U : V->users())
|
|
if (IsPointerOperand(V, U))
|
|
WorkList.push_back(U);
|
|
auto *Call = dyn_cast<CallInst>(V);
|
|
if (!Call)
|
|
continue;
|
|
if (isPreserveArrayIndex(V))
|
|
BPFCoreSharedInfo::removeArrayAccessCall(Call);
|
|
else if (isPreserveStructIndex(V))
|
|
BPFCoreSharedInfo::removeStructAccessCall(Call);
|
|
else if (isPreserveUnionIndex(V))
|
|
BPFCoreSharedInfo::removeUnionAccessCall(Call);
|
|
} while (!WorkList.empty());
|
|
}
|
|
|
|
// Look for sequences:
|
|
// - llvm.preserve.static.offset -> getelementptr... -> load
|
|
// - llvm.preserve.static.offset -> getelementptr... -> store
|
|
// And replace those with calls to intrinsics:
|
|
// - llvm.bpf.getelementptr.and.load
|
|
// - llvm.bpf.getelementptr.and.store
|
|
static bool rewriteFunction(Function &F, bool AllowPartial) {
|
|
LLVM_DEBUG(dbgs() << "********** BPFPreserveStaticOffsetPass (AllowPartial="
|
|
<< AllowPartial << ") ************\n");
|
|
|
|
auto MarkerCalls = collectPreserveStaticOffsetCalls(F);
|
|
SmallPtrSet<Instruction *, 16> RemovedMarkers;
|
|
|
|
LLVM_DEBUG(dbgs() << "There are " << MarkerCalls.size()
|
|
<< " preserve.static.offset calls\n");
|
|
|
|
if (MarkerCalls.empty())
|
|
return false;
|
|
|
|
for (auto *Call : MarkerCalls)
|
|
removePAICalls(Call);
|
|
|
|
for (auto *Call : MarkerCalls) {
|
|
if (RemovedMarkers.contains(Call))
|
|
continue;
|
|
bool StillUsed = rewriteAccessChain(Call, AllowPartial, RemovedMarkers);
|
|
if (!StillUsed || !AllowPartial)
|
|
removeMarkerCall(Call);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
PreservedAnalyses
|
|
llvm::BPFPreserveStaticOffsetPass::run(Function &F,
|
|
FunctionAnalysisManager &AM) {
|
|
return rewriteFunction(F, AllowPartial) ? PreservedAnalyses::none()
|
|
: PreservedAnalyses::all();
|
|
}
|