Skip to content

Commit 94f290f

Browse files
[BPF] Handle certain mem intrinsic functions with addr-space arguments (#160025)
In linux kernel commit [1], we have a bpf selftest failure caused by llvm. In this particular case, the BPFCheckAndAdjustIR pass has a function insertASpaceCasts() which inserts proper addrspacecast insn at proper IR places. It does not handle __builtin_memset() and hance caused selftest failure. Add support in insertASpaceCasts() to handle __builtin_(memset,memcpy,memmove,memset_inline,memcpy_inline}() properly and this can fix the issue in [1] as well. [1] https://lore.kernel.org/all/[email protected]/
1 parent 9715cca commit 94f290f

File tree

3 files changed

+241
-6
lines changed

3 files changed

+241
-6
lines changed

llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp

Lines changed: 132 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "llvm/IR/IRBuilder.h"
2727
#include "llvm/IR/Instruction.h"
2828
#include "llvm/IR/Instructions.h"
29+
#include "llvm/IR/IntrinsicInst.h"
2930
#include "llvm/IR/IntrinsicsBPF.h"
3031
#include "llvm/IR/Module.h"
3132
#include "llvm/IR/Type.h"
@@ -478,9 +479,95 @@ static void aspaceWrapOperand(DenseMap<Value *, Value *> &Cache, Instruction *I,
478479
}
479480
}
480481

482+
static Value *wrapPtrIfASNotZero(DenseMap<Value *, Value *> &Cache,
483+
CallInst *CI, Value *P) {
484+
if (auto *PTy = dyn_cast<PointerType>(P->getType())) {
485+
if (PTy->getAddressSpace() == 0)
486+
return P;
487+
}
488+
return aspaceWrapValue(Cache, CI->getFunction(), P);
489+
}
490+
491+
static Instruction *aspaceMemSet(Intrinsic::ID ID,
492+
DenseMap<Value *, Value *> &Cache,
493+
CallInst *CI) {
494+
auto *MI = cast<MemIntrinsic>(CI);
495+
IRBuilder<> B(CI);
496+
497+
Value *OldDst = CI->getArgOperand(0);
498+
Value *NewDst = wrapPtrIfASNotZero(Cache, CI, OldDst);
499+
if (OldDst == NewDst)
500+
return nullptr;
501+
502+
// memset(new_dst, val, len, align, isvolatile, md)
503+
Value *Val = CI->getArgOperand(1);
504+
Value *Len = CI->getArgOperand(2);
505+
506+
auto *MS = cast<MemSetInst>(CI);
507+
MaybeAlign Align = MS->getDestAlign();
508+
bool IsVolatile = MS->isVolatile();
509+
510+
if (ID == Intrinsic::memset)
511+
return B.CreateMemSet(NewDst, Val, Len, Align, IsVolatile,
512+
MI->getAAMetadata());
513+
else
514+
return B.CreateMemSetInline(NewDst, Align, Val, Len, IsVolatile,
515+
MI->getAAMetadata());
516+
}
517+
518+
static Instruction *aspaceMemCpy(Intrinsic::ID ID,
519+
DenseMap<Value *, Value *> &Cache,
520+
CallInst *CI) {
521+
auto *MI = cast<MemIntrinsic>(CI);
522+
IRBuilder<> B(CI);
523+
524+
Value *OldDst = CI->getArgOperand(0);
525+
Value *OldSrc = CI->getArgOperand(1);
526+
Value *NewDst = wrapPtrIfASNotZero(Cache, CI, OldDst);
527+
Value *NewSrc = wrapPtrIfASNotZero(Cache, CI, OldSrc);
528+
if (OldDst == NewDst && OldSrc == NewSrc)
529+
return nullptr;
530+
531+
// memcpy(new_dst, dst_align, new_src, src_align, len, isvolatile, md)
532+
Value *Len = CI->getArgOperand(2);
533+
534+
auto *MT = cast<MemTransferInst>(CI);
535+
MaybeAlign DstAlign = MT->getDestAlign();
536+
MaybeAlign SrcAlign = MT->getSourceAlign();
537+
bool IsVolatile = MT->isVolatile();
538+
539+
return B.CreateMemTransferInst(ID, NewDst, DstAlign, NewSrc, SrcAlign, Len,
540+
IsVolatile, MI->getAAMetadata());
541+
}
542+
543+
static Instruction *aspaceMemMove(DenseMap<Value *, Value *> &Cache,
544+
CallInst *CI) {
545+
auto *MI = cast<MemIntrinsic>(CI);
546+
IRBuilder<> B(CI);
547+
548+
Value *OldDst = CI->getArgOperand(0);
549+
Value *OldSrc = CI->getArgOperand(1);
550+
Value *NewDst = wrapPtrIfASNotZero(Cache, CI, OldDst);
551+
Value *NewSrc = wrapPtrIfASNotZero(Cache, CI, OldSrc);
552+
if (OldDst == NewDst && OldSrc == NewSrc)
553+
return nullptr;
554+
555+
// memmove(new_dst, dst_align, new_src, src_align, len, isvolatile, md)
556+
Value *Len = CI->getArgOperand(2);
557+
558+
auto *MT = cast<MemTransferInst>(CI);
559+
MaybeAlign DstAlign = MT->getDestAlign();
560+
MaybeAlign SrcAlign = MT->getSourceAlign();
561+
bool IsVolatile = MT->isVolatile();
562+
563+
return B.CreateMemMove(NewDst, DstAlign, NewSrc, SrcAlign, Len, IsVolatile,
564+
MI->getAAMetadata());
565+
}
566+
481567
// Support for BPF address spaces:
482568
// - for each function in the module M, update pointer operand of
483569
// each memory access instruction (load/store/cmpxchg/atomicrmw)
570+
// or intrinsic call insns (memset/memcpy/memmove)
484571
// by casting it from non-zero address space to zero address space, e.g:
485572
//
486573
// (load (ptr addrspace (N) %p) ...)
@@ -493,21 +580,60 @@ bool BPFCheckAndAdjustIR::insertASpaceCasts(Module &M) {
493580
for (Function &F : M) {
494581
DenseMap<Value *, Value *> CastsCache;
495582
for (BasicBlock &BB : F) {
496-
for (Instruction &I : BB) {
583+
for (Instruction &I : llvm::make_early_inc_range(BB)) {
497584
unsigned PtrOpNum;
498585

499-
if (auto *LD = dyn_cast<LoadInst>(&I))
586+
if (auto *LD = dyn_cast<LoadInst>(&I)) {
500587
PtrOpNum = LD->getPointerOperandIndex();
501-
else if (auto *ST = dyn_cast<StoreInst>(&I))
588+
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
589+
continue;
590+
}
591+
if (auto *ST = dyn_cast<StoreInst>(&I)) {
502592
PtrOpNum = ST->getPointerOperandIndex();
503-
else if (auto *CmpXchg = dyn_cast<AtomicCmpXchgInst>(&I))
593+
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
594+
continue;
595+
}
596+
if (auto *CmpXchg = dyn_cast<AtomicCmpXchgInst>(&I)) {
504597
PtrOpNum = CmpXchg->getPointerOperandIndex();
505-
else if (auto *RMW = dyn_cast<AtomicRMWInst>(&I))
598+
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
599+
continue;
600+
}
601+
if (auto *RMW = dyn_cast<AtomicRMWInst>(&I)) {
506602
PtrOpNum = RMW->getPointerOperandIndex();
603+
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
604+
continue;
605+
}
606+
607+
auto *CI = dyn_cast<CallInst>(&I);
608+
if (!CI)
609+
continue;
610+
611+
Function *Callee = CI->getCalledFunction();
612+
if (!Callee || !Callee->isIntrinsic())
613+
continue;
614+
615+
// Check memset/memcpy/memmove
616+
Intrinsic::ID ID = Callee->getIntrinsicID();
617+
bool IsSet = ID == Intrinsic::memset || ID == Intrinsic::memset_inline;
618+
bool IsCpy = ID == Intrinsic::memcpy || ID == Intrinsic::memcpy_inline;
619+
bool IsMove = ID == Intrinsic::memmove;
620+
if (!IsSet && !IsCpy && !IsMove)
621+
continue;
622+
623+
Instruction *New;
624+
if (IsSet)
625+
New = aspaceMemSet(ID, CastsCache, CI);
626+
else if (IsCpy)
627+
New = aspaceMemCpy(ID, CastsCache, CI);
507628
else
629+
New = aspaceMemMove(CastsCache, CI);
630+
631+
if (!New)
508632
continue;
509633

510-
aspaceWrapOperand(CastsCache, &I, PtrOpNum);
634+
I.replaceAllUsesWith(New);
635+
New->takeName(&I);
636+
I.eraseFromParent();
511637
}
512638
}
513639
Changed |= !CastsCache.empty();
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
2+
; RUN: opt --bpf-check-and-opt-ir -S -mtriple=bpf-pc-linux < %s | FileCheck %s
3+
4+
@page1 = dso_local local_unnamed_addr addrspace(1) global [10 x ptr] zeroinitializer, align 8
5+
@page2 = dso_local local_unnamed_addr addrspace(1) global [10 x ptr] zeroinitializer, align 8
6+
7+
define dso_local void @test_memset() local_unnamed_addr {
8+
; CHECK-LABEL: define dso_local void @test_memset() local_unnamed_addr {
9+
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 16) to ptr), i8 0, i64 16, i1 false)
10+
; CHECK-NEXT: ret void
11+
;
12+
tail call void @llvm.memset.p1.i64(ptr addrspace(1) noundef nonnull align 8 dereferenceable(16) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 16), i8 0, i64 16, i1 false)
13+
ret void
14+
}
15+
16+
declare void @llvm.memset.p1.i64(ptr addrspace(1) writeonly captures(none), i8, i64, i1 immarg)
17+
18+
define dso_local void @test_memcpy() local_unnamed_addr {
19+
; CHECK-LABEL: define dso_local void @test_memcpy() local_unnamed_addr {
20+
; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8) to ptr), ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 8) to ptr), i64 16, i1 false)
21+
; CHECK-NEXT: ret void
22+
;
23+
tail call void @llvm.memcpy.p1.p1.i64(ptr addrspace(1) noundef nonnull align 8 dereferenceable(16) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8), ptr addrspace(1) noundef nonnull align 8 dereferenceable(16) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 8), i64 16, i1 false)
24+
ret void
25+
}
26+
27+
declare void @llvm.memcpy.p1.p1.i64(ptr addrspace(1) noalias writeonly captures(none), ptr addrspace(1) noalias readonly captures(none), i64, i1 immarg)
28+
29+
define dso_local void @test_memmove() local_unnamed_addr {
30+
; CHECK-LABEL: define dso_local void @test_memmove() local_unnamed_addr {
31+
; CHECK-NEXT: call void @llvm.memmove.p0.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 16) to ptr), ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8) to ptr), i64 16, i1 false)
32+
; CHECK-NEXT: ret void
33+
;
34+
tail call void @llvm.memmove.p1.p1.i64(ptr addrspace(1) noundef nonnull align 8 dereferenceable(16) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 16), ptr addrspace(1) noundef nonnull align 8 dereferenceable(16) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8), i64 16, i1 false)
35+
ret void
36+
}
37+
38+
declare void @llvm.memmove.p1.p1.i64(ptr addrspace(1) writeonly captures(none), ptr addrspace(1) readonly captures(none), i64, i1 immarg)
39+
40+
define dso_local void @test_memset_inline() local_unnamed_addr {
41+
; CHECK-LABEL: define dso_local void @test_memset_inline() local_unnamed_addr {
42+
; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 16) to ptr), i8 0, i64 16, i1 false)
43+
; CHECK-NEXT: ret void
44+
;
45+
tail call void @llvm.memset.inline.p1.i64(ptr addrspace(1) nonnull align 8 getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 16), i8 0, i64 16, i1 false)
46+
ret void
47+
}
48+
49+
declare void @llvm.memset.inline.p1.i64(ptr addrspace(1) writeonly captures(none), i8, i64, i1 immarg)
50+
51+
define dso_local void @test_memcpy_inline() local_unnamed_addr {
52+
; CHECK-LABEL: define dso_local void @test_memcpy_inline() local_unnamed_addr {
53+
; CHECK-NEXT: call void @llvm.memcpy.inline.p0.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8) to ptr), ptr align 8 addrspacecast (ptr addrspace(1) getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 8) to ptr), i64 16, i1 false)
54+
; CHECK-NEXT: ret void
55+
;
56+
tail call void @llvm.memcpy.inline.p1.p1.i64(ptr addrspace(1) nonnull align 8 getelementptr inbounds nuw (i8, ptr addrspace(1) @page2, i64 8), ptr addrspace(1) nonnull align 8 getelementptr inbounds nuw (i8, ptr addrspace(1) @page1, i64 8), i64 16, i1 false)
57+
ret void
58+
}
59+
60+
declare void @llvm.memcpy.inline.p1.p1.i64(ptr addrspace(1) noalias writeonly captures(none), ptr addrspace(1) noalias readonly captures(none), i64, i1 immarg)
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
; NOTE: Assertions have been autogenerated by utils/update_test_checks.py UTC_ARGS: --version 6
2+
; RUN: opt --bpf-check-and-opt-ir -S -mtriple=bpf-pc-linux < %s | FileCheck %s
3+
4+
@page1 = dso_local local_unnamed_addr addrspace(1) global [10 x ptr] zeroinitializer, align 8
5+
@page2 = dso_local local_unnamed_addr addrspace(1) global [10 x ptr] zeroinitializer, align 8
6+
7+
define dso_local void @test_memset() local_unnamed_addr {
8+
; CHECK-LABEL: define dso_local void @test_memset() local_unnamed_addr {
9+
; CHECK-NEXT: call void @llvm.memset.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) @page1 to ptr), i8 0, i64 16, i1 false)
10+
; CHECK-NEXT: ret void
11+
;
12+
tail call void @llvm.memset.p1.i64(ptr addrspace(1) noundef align 8 dereferenceable(16) @page1, i8 0, i64 16, i1 false)
13+
ret void
14+
}
15+
16+
declare void @llvm.memset.p1.i64(ptr addrspace(1) writeonly captures(none), i8, i64, i1 immarg)
17+
18+
define dso_local void @test_memcpy() local_unnamed_addr {
19+
; CHECK-LABEL: define dso_local void @test_memcpy() local_unnamed_addr {
20+
; CHECK-NEXT: call void @llvm.memcpy.p0.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) @page2 to ptr), ptr align 8 addrspacecast (ptr addrspace(1) @page1 to ptr), i64 16, i1 false)
21+
; CHECK-NEXT: ret void
22+
;
23+
tail call void @llvm.memcpy.p1.p1.i64(ptr addrspace(1) noundef align 8 dereferenceable(16) @page2, ptr addrspace(1) noundef align 8 dereferenceable(16) @page1, i64 16, i1 false)
24+
ret void
25+
}
26+
27+
declare void @llvm.memcpy.p1.p1.i64(ptr addrspace(1) noalias writeonly captures(none), ptr addrspace(1) noalias readonly captures(none), i64, i1 immarg)
28+
29+
define dso_local void @test_memset_inline() local_unnamed_addr {
30+
; CHECK-LABEL: define dso_local void @test_memset_inline() local_unnamed_addr {
31+
; CHECK-NEXT: call void @llvm.memset.inline.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) @page1 to ptr), i8 0, i64 16, i1 false)
32+
; CHECK-NEXT: ret void
33+
;
34+
tail call void @llvm.memset.inline.p1.i64(ptr addrspace(1) align 8 @page1, i8 0, i64 16, i1 false)
35+
ret void
36+
}
37+
38+
declare void @llvm.memset.inline.p1.i64(ptr addrspace(1) writeonly captures(none), i8, i64, i1 immarg)
39+
40+
define dso_local void @test_memcpy_inline() local_unnamed_addr {
41+
; CHECK-LABEL: define dso_local void @test_memcpy_inline() local_unnamed_addr {
42+
; CHECK-NEXT: call void @llvm.memcpy.inline.p0.p0.i64(ptr align 8 addrspacecast (ptr addrspace(1) @page2 to ptr), ptr align 8 addrspacecast (ptr addrspace(1) @page1 to ptr), i64 16, i1 false)
43+
; CHECK-NEXT: ret void
44+
;
45+
tail call void @llvm.memcpy.inline.p1.p1.i64(ptr addrspace(1) align 8 @page2, ptr addrspace(1) align 8 @page1, i64 16, i1 false)
46+
ret void
47+
}
48+
49+
declare void @llvm.memcpy.inline.p1.p1.i64(ptr addrspace(1) noalias writeonly captures(none), ptr addrspace(1) noalias readonly captures(none), i64, i1 immarg)

0 commit comments

Comments
 (0)