Skip to content

Commit 2aacb56

Browse files
4asteddyz87
andauthored
BPF address space insn (#84410)
This commit aims to support BPF arena kernel side [feature](https://lore.kernel.org/bpf/[email protected]/): - arena is a memory region accessible from both BPF program and userspace; - base pointers for this memory region differ between kernel and user spaces; - `dst_reg = addr_space_cast(src_reg, dst_addr_space, src_addr_space)` translates src_reg, a pointer in src_addr_space to dst_reg, equivalent pointer in dst_addr_space, {src,dst}_addr_space are immediate constants; - number 0 is assigned to kernel address space; - number 1 is assigned to user address space. On the LLVM side, the goal is to make load and store operations on arena pointers "transparent" for BPF programs: - assume that pointers with non-zero address space are pointers to arena memory; - assume that arena is identified by address space number; - assume that address space zero corresponds to kernel address space; - assume that every BPF-side load or store from arena is done via pointer in user address space, thus convert base pointers using `addr_space_cast(src_reg, 0, 1)`; Only load, store, cmpxchg and atomicrmw IR instructions are handled by this transformation. For example, the following C code: ```c #define __as __attribute__((address_space(1))) void copy(int __as *from, int __as *to) { *to = *from; } ``` Compiled to the following IR: ```llvm define void @copy(ptr addrspace(1) %from, ptr addrspace(1) %to) { entry: %0 = load i32, ptr addrspace(1) %from, align 4 store i32 %0, ptr addrspace(1) %to, align 4 ret void } ``` Is transformed to: ```llvm %to2 = addrspacecast ptr addrspace(1) %to to ptr ;; ! %from1 = addrspacecast ptr addrspace(1) %from to ptr ;; ! %0 = load i32, ptr %from1, align 4, !tbaa !3 store i32 %0, ptr %to2, align 4, !tbaa !3 ret void ``` And compiled as: ```asm r2 = addr_space_cast(r2, 0, 1) r1 = addr_space_cast(r1, 0, 1) r1 = *(u32 *)(r1 + 0) *(u32 *)(r2 + 0) = r1 exit ``` Co-authored-by: Eduard Zingerman <[email protected]>
1 parent f467cc9 commit 2aacb56

21 files changed

+615
-0
lines changed

clang/lib/Basic/Targets/BPF.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,9 @@ void BPFTargetInfo::getTargetDefines(const LangOptions &Opts,
3535
Builder.defineMacro("__BPF_CPU_VERSION__", "0");
3636
return;
3737
}
38+
39+
Builder.defineMacro("__BPF_FEATURE_ARENA_CAST");
40+
3841
if (CPU.empty() || CPU == "generic" || CPU == "v1") {
3942
Builder.defineMacro("__BPF_CPU_VERSION__", "1");
4043
return;

clang/test/Preprocessor/bpf-predefined-macros.c

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@ int r;
6161
#ifdef __BPF_FEATURE_ST
6262
int s;
6363
#endif
64+
#ifdef __BPF_FEATURE_ARENA_CAST
65+
int t;
66+
#endif
6467

6568
// CHECK: int b;
6669
// CHECK: int c;
@@ -90,6 +93,11 @@ int s;
9093
// CPU_V4: int r;
9194
// CPU_V4: int s;
9295

96+
// CPU_V1: int t;
97+
// CPU_V2: int t;
98+
// CPU_V3: int t;
99+
// CPU_V4: int t;
100+
93101
// CPU_GENERIC: int g;
94102

95103
// CPU_PROBE: int f;

llvm/lib/Target/BPF/AsmParser/BPFAsmParser.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,7 @@ struct BPFOperand : public MCParsedAsmOperand {
271271
.Case("xchg32_32", true)
272272
.Case("cmpxchg_64", true)
273273
.Case("cmpxchg32_32", true)
274+
.Case("addr_space_cast", true)
274275
.Default(false);
275276
}
276277
};

llvm/lib/Target/BPF/BPF.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,14 @@ class BPFIRPeepholePass : public PassInfoMixin<BPFIRPeepholePass> {
6666
static bool isRequired() { return true; }
6767
};
6868

69+
class BPFASpaceCastSimplifyPass
70+
: public PassInfoMixin<BPFASpaceCastSimplifyPass> {
71+
public:
72+
PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM);
73+
74+
static bool isRequired() { return true; }
75+
};
76+
6977
class BPFAdjustOptPass : public PassInfoMixin<BPFAdjustOptPass> {
7078
public:
7179
PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM);
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
//===-- BPFASpaceCastSimplifyPass.cpp - BPF addrspacecast simplications --===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
#include "BPF.h"
10+
#include <optional>
11+
12+
#define DEBUG_TYPE "bpf-aspace-simplify"
13+
14+
using namespace llvm;
15+
16+
namespace {
17+
18+
struct CastGEPCast {
19+
AddrSpaceCastInst *OuterCast;
20+
21+
// Match chain of instructions:
22+
// %inner = addrspacecast N->M
23+
// %gep = getelementptr %inner, ...
24+
// %outer = addrspacecast M->N %gep
25+
// Where I is %outer.
26+
static std::optional<CastGEPCast> match(Value *I) {
27+
auto *OuterCast = dyn_cast<AddrSpaceCastInst>(I);
28+
if (!OuterCast)
29+
return std::nullopt;
30+
auto *GEP = dyn_cast<GetElementPtrInst>(OuterCast->getPointerOperand());
31+
if (!GEP)
32+
return std::nullopt;
33+
auto *InnerCast = dyn_cast<AddrSpaceCastInst>(GEP->getPointerOperand());
34+
if (!InnerCast)
35+
return std::nullopt;
36+
if (InnerCast->getSrcAddressSpace() != OuterCast->getDestAddressSpace())
37+
return std::nullopt;
38+
if (InnerCast->getDestAddressSpace() != OuterCast->getSrcAddressSpace())
39+
return std::nullopt;
40+
return CastGEPCast{OuterCast};
41+
}
42+
43+
static PointerType *changeAddressSpace(PointerType *Ty, unsigned AS) {
44+
return Ty->get(Ty->getContext(), AS);
45+
}
46+
47+
// Assuming match(this->OuterCast) is true, convert:
48+
// (addrspacecast M->N (getelementptr (addrspacecast N->M ptr) ...))
49+
// To:
50+
// (getelementptr ptr ...)
51+
GetElementPtrInst *rewrite() {
52+
auto *GEP = cast<GetElementPtrInst>(OuterCast->getPointerOperand());
53+
auto *InnerCast = cast<AddrSpaceCastInst>(GEP->getPointerOperand());
54+
unsigned AS = OuterCast->getDestAddressSpace();
55+
auto *NewGEP = cast<GetElementPtrInst>(GEP->clone());
56+
NewGEP->setName(GEP->getName());
57+
NewGEP->insertAfter(OuterCast);
58+
NewGEP->setOperand(0, InnerCast->getPointerOperand());
59+
auto *GEPTy = cast<PointerType>(GEP->getType());
60+
NewGEP->mutateType(changeAddressSpace(GEPTy, AS));
61+
OuterCast->replaceAllUsesWith(NewGEP);
62+
OuterCast->eraseFromParent();
63+
if (GEP->use_empty())
64+
GEP->eraseFromParent();
65+
if (InnerCast->use_empty())
66+
InnerCast->eraseFromParent();
67+
return NewGEP;
68+
}
69+
};
70+
71+
} // anonymous namespace
72+
73+
PreservedAnalyses BPFASpaceCastSimplifyPass::run(Function &F,
74+
FunctionAnalysisManager &AM) {
75+
SmallVector<CastGEPCast, 16> WorkList;
76+
bool Changed = false;
77+
for (BasicBlock &BB : F) {
78+
for (Instruction &I : BB)
79+
if (auto It = CastGEPCast::match(&I))
80+
WorkList.push_back(It.value());
81+
Changed |= !WorkList.empty();
82+
83+
while (!WorkList.empty()) {
84+
CastGEPCast InsnChain = WorkList.pop_back_val();
85+
GetElementPtrInst *NewGEP = InsnChain.rewrite();
86+
for (User *U : NewGEP->users())
87+
if (auto It = CastGEPCast::match(U))
88+
WorkList.push_back(It.value());
89+
}
90+
}
91+
return Changed ? PreservedAnalyses::none() : PreservedAnalyses::all();
92+
}

llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
// optimizations are done and those builtins can be removed.
1515
// - remove llvm.bpf.getelementptr.and.load builtins.
1616
// - remove llvm.bpf.getelementptr.and.store builtins.
17+
// - for loads and stores with base addresses from non-zero address space
18+
// cast base address to zero address space (support for BPF arenas).
1719
//
1820
//===----------------------------------------------------------------------===//
1921

@@ -55,6 +57,7 @@ class BPFCheckAndAdjustIR final : public ModulePass {
5557
bool removeCompareBuiltin(Module &M);
5658
bool sinkMinMax(Module &M);
5759
bool removeGEPBuiltins(Module &M);
60+
bool insertASpaceCasts(Module &M);
5861
};
5962
} // End anonymous namespace
6063

@@ -416,11 +419,124 @@ bool BPFCheckAndAdjustIR::removeGEPBuiltins(Module &M) {
416419
return Changed;
417420
}
418421

422+
// Wrap ToWrap with cast to address space zero:
423+
// - if ToWrap is a getelementptr,
424+
// wrap it's base pointer instead and return a copy;
425+
// - if ToWrap is Instruction, insert address space cast
426+
// immediately after ToWrap;
427+
// - if ToWrap is not an Instruction (function parameter
428+
// or a global value), insert address space cast at the
429+
// beginning of the Function F;
430+
// - use Cache to avoid inserting too many casts;
431+
static Value *aspaceWrapValue(DenseMap<Value *, Value *> &Cache, Function *F,
432+
Value *ToWrap) {
433+
auto It = Cache.find(ToWrap);
434+
if (It != Cache.end())
435+
return It->getSecond();
436+
437+
if (auto *GEP = dyn_cast<GetElementPtrInst>(ToWrap)) {
438+
Value *Ptr = GEP->getPointerOperand();
439+
Value *WrappedPtr = aspaceWrapValue(Cache, F, Ptr);
440+
auto *GEPTy = cast<PointerType>(GEP->getType());
441+
auto *NewGEP = GEP->clone();
442+
NewGEP->insertAfter(GEP);
443+
NewGEP->mutateType(GEPTy->getPointerTo(0));
444+
NewGEP->setOperand(GEP->getPointerOperandIndex(), WrappedPtr);
445+
NewGEP->setName(GEP->getName());
446+
Cache[ToWrap] = NewGEP;
447+
return NewGEP;
448+
}
449+
450+
IRBuilder IB(F->getContext());
451+
if (Instruction *InsnPtr = dyn_cast<Instruction>(ToWrap))
452+
IB.SetInsertPoint(*InsnPtr->getInsertionPointAfterDef());
453+
else
454+
IB.SetInsertPoint(F->getEntryBlock().getFirstInsertionPt());
455+
auto *PtrTy = cast<PointerType>(ToWrap->getType());
456+
auto *ASZeroPtrTy = PtrTy->getPointerTo(0);
457+
auto *ACast = IB.CreateAddrSpaceCast(ToWrap, ASZeroPtrTy, ToWrap->getName());
458+
Cache[ToWrap] = ACast;
459+
return ACast;
460+
}
461+
462+
// Wrap a pointer operand OpNum of instruction I
463+
// with cast to address space zero
464+
static void aspaceWrapOperand(DenseMap<Value *, Value *> &Cache, Instruction *I,
465+
unsigned OpNum) {
466+
Value *OldOp = I->getOperand(OpNum);
467+
if (OldOp->getType()->getPointerAddressSpace() == 0)
468+
return;
469+
470+
Value *NewOp = aspaceWrapValue(Cache, I->getFunction(), OldOp);
471+
I->setOperand(OpNum, NewOp);
472+
// Check if there are any remaining users of old GEP,
473+
// delete those w/o users
474+
for (;;) {
475+
auto *OldGEP = dyn_cast<GetElementPtrInst>(OldOp);
476+
if (!OldGEP)
477+
break;
478+
if (!OldGEP->use_empty())
479+
break;
480+
OldOp = OldGEP->getPointerOperand();
481+
OldGEP->eraseFromParent();
482+
}
483+
}
484+
485+
// Support for BPF arenas:
486+
// - for each function in the module M, update pointer operand of
487+
// each memory access instruction (load/store/cmpxchg/atomicrmw)
488+
// by casting it from non-zero address space to zero address space, e.g:
489+
//
490+
// (load (ptr addrspace (N) %p) ...)
491+
// -> (load (addrspacecast ptr addrspace (N) %p to ptr))
492+
//
493+
// - assign section with name .arena.N for globals defined in
494+
// non-zero address space N
495+
bool BPFCheckAndAdjustIR::insertASpaceCasts(Module &M) {
496+
bool Changed = false;
497+
for (Function &F : M) {
498+
DenseMap<Value *, Value *> CastsCache;
499+
for (BasicBlock &BB : F) {
500+
for (Instruction &I : BB) {
501+
unsigned PtrOpNum;
502+
503+
if (auto *LD = dyn_cast<LoadInst>(&I))
504+
PtrOpNum = LD->getPointerOperandIndex();
505+
else if (auto *ST = dyn_cast<StoreInst>(&I))
506+
PtrOpNum = ST->getPointerOperandIndex();
507+
else if (auto *CmpXchg = dyn_cast<AtomicCmpXchgInst>(&I))
508+
PtrOpNum = CmpXchg->getPointerOperandIndex();
509+
else if (auto *RMW = dyn_cast<AtomicRMWInst>(&I))
510+
PtrOpNum = RMW->getPointerOperandIndex();
511+
else
512+
continue;
513+
514+
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
515+
}
516+
}
517+
Changed |= !CastsCache.empty();
518+
}
519+
// Merge all globals within same address space into single
520+
// .arena.<addr space no> section
521+
for (GlobalVariable &G : M.globals()) {
522+
if (G.getAddressSpace() == 0 || G.hasSection())
523+
continue;
524+
SmallString<16> SecName;
525+
raw_svector_ostream OS(SecName);
526+
OS << ".arena." << G.getAddressSpace();
527+
G.setSection(SecName);
528+
// Prevent having separate section for constants
529+
G.setConstant(false);
530+
}
531+
return Changed;
532+
}
533+
419534
bool BPFCheckAndAdjustIR::adjustIR(Module &M) {
420535
bool Changed = removePassThroughBuiltin(M);
421536
Changed = removeCompareBuiltin(M) || Changed;
422537
Changed = sinkMinMax(M) || Changed;
423538
Changed = removeGEPBuiltins(M) || Changed;
539+
Changed = insertASpaceCasts(M) || Changed;
424540
return Changed;
425541
}
426542

llvm/lib/Target/BPF/BPFInstrInfo.td

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -420,6 +420,35 @@ let Predicates = [BPFHasMovsx] in {
420420
}
421421
}
422422

423+
def ADDR_SPACE_CAST
424+
: ALU_RR<BPF_ALU64, BPF_MOV, 64,
425+
(outs GPR:$dst),
426+
(ins GPR:$src, i64imm:$dst_as, i64imm:$src_as),
427+
"$dst = addr_space_cast($src, $dst_as, $src_as)",
428+
[]> {
429+
bits<64> dst_as;
430+
bits<64> src_as;
431+
432+
let Inst{47-32} = 1;
433+
let Inst{31-16} = dst_as{15-0};
434+
let Inst{15-0} = src_as{15-0};
435+
}
436+
437+
def SrcAddrSpace : SDNodeXForm<addrspacecast, [{
438+
return CurDAG->getTargetConstant(
439+
cast<AddrSpaceCastSDNode>(N)->getSrcAddressSpace(),
440+
SDLoc(N), MVT::i64);
441+
}]>;
442+
443+
def DstAddrSpace : SDNodeXForm<addrspacecast, [{
444+
return CurDAG->getTargetConstant(
445+
cast<AddrSpaceCastSDNode>(N)->getDestAddressSpace(),
446+
SDLoc(N), MVT::i64);
447+
}]>;
448+
449+
def : Pat<(addrspacecast:$this GPR:$src),
450+
(ADDR_SPACE_CAST $src, (DstAddrSpace $this), (SrcAddrSpace $this))>;
451+
423452
def FI_ri
424453
: TYPE_LD_ST<BPF_IMM.Value, BPF_DW.Value,
425454
(outs GPR:$dst),

llvm/lib/Target/BPF/BPFTargetMachine.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,10 @@ void BPFTargetMachine::registerPassBuilderCallbacks(
121121
FPM.addPass(BPFPreserveStaticOffsetPass(false));
122122
return true;
123123
}
124+
if (PassName == "bpf-aspace-simplify") {
125+
FPM.addPass(BPFASpaceCastSimplifyPass());
126+
return true;
127+
}
124128
return false;
125129
});
126130
PB.registerPipelineStartEPCallback(
@@ -135,6 +139,7 @@ void BPFTargetMachine::registerPassBuilderCallbacks(
135139
PB.registerPeepholeEPCallback([=](FunctionPassManager &FPM,
136140
OptimizationLevel Level) {
137141
FPM.addPass(SimplifyCFGPass(SimplifyCFGOptions().hoistCommonInsts(true)));
142+
FPM.addPass(BPFASpaceCastSimplifyPass());
138143
});
139144
PB.registerScalarOptimizerLateEPCallback(
140145
[=](FunctionPassManager &FPM, OptimizationLevel Level) {

llvm/lib/Target/BPF/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ add_llvm_target(BPFCodeGen
2424
BPFAbstractMemberAccess.cpp
2525
BPFAdjustOpt.cpp
2626
BPFAsmPrinter.cpp
27+
BPFASpaceCastSimplifyPass.cpp
2728
BPFCheckAndAdjustIR.cpp
2829
BPFFrameLowering.cpp
2930
BPFInstrInfo.cpp

0 commit comments

Comments
 (0)