diff --git a/clang/lib/CodeGen/CGExpr.cpp b/clang/lib/CodeGen/CGExpr.cpp --- a/clang/lib/CodeGen/CGExpr.cpp +++ b/clang/lib/CodeGen/CGExpr.cpp @@ -3707,6 +3707,48 @@ return eltType; } +// Returns true if D is marked with __attribute__((btf_decl_tag("ctx"))) +static bool hasCtxBTFDeclTagAttr(const Decl *D) { + if (auto *Attr = D->getAttr()) + return Attr->getBTFDeclTag().equals("ctx"); + + return false; +} + +static bool pointeeHasCtxBTFDeclTagAttr(const Expr *E) { + if (auto *PtrType = dyn_cast(E->getType())) + if (auto *BaseDecl = PtrType->getPointeeType()->getAsRecordDecl()) + return hasCtxBTFDeclTagAttr(BaseDecl); + + return false; +} + +// Returns true if declaration is marked +// with __attribute__((preserve_access_index)) +// but NOT with __attribute__((btf_decl_tag("ctx"))) +static bool hasBPFPreserveAccessIndexAttr(const Decl *D) { + const Decl *TaggedDecl; + if (auto *Field = dyn_cast(D)) + TaggedDecl = Field->getParent(); + else + TaggedDecl = D; + return D->hasAttr() && + !hasCtxBTFDeclTagAttr(TaggedDecl); +} + +// Wraps Addr with a call to llvm.context.marker.bpf intrinsic. +// This is used for target specific rewrites, for details see +// llvm/lib/Target/BPF/BPFContextMarker.cpp +static Address wrapWithBPFContextMarker(CodeGenFunction &CGF, Address &Addr) { + if (!CGF.getTarget().getTriple().isBPF()) + return Addr; + + llvm::Function *Fn = + CGF.CGM.getIntrinsic(llvm::Intrinsic::context_marker_bpf); + llvm::CallInst *Call = CGF.Builder.CreateCall(Fn, {Addr.getPointer()}); + return Address(Call, Addr.getElementType(), Addr.getAlignment()); +} + /// Given an array base, check whether its member access belongs to a record /// with preserve_access_index attribute or not. static bool IsPreserveAIArrayBase(CodeGenFunction &CGF, const Expr *ArrayBase) { @@ -3722,7 +3764,7 @@ // p->b[5] is a MemberExpr example. const Expr *E = ArrayBase->IgnoreImpCasts(); if (const auto *ME = dyn_cast(E)) - return ME->getMemberDecl()->hasAttr(); + return hasBPFPreserveAccessIndexAttr(ME->getMemberDecl()); if (const auto *DRE = dyn_cast(E)) { const auto *VarDef = dyn_cast(DRE->getDecl()); @@ -3736,7 +3778,7 @@ const auto *PointeeT = PtrT->getPointeeType() ->getUnqualifiedDesugaredType(); if (const auto *RecT = dyn_cast(PointeeT)) - return RecT->getDecl()->hasAttr(); + return hasBPFPreserveAccessIndexAttr(RecT->getDecl()); return false; } @@ -3768,6 +3810,9 @@ CharUnits eltAlign = getArrayElementAlign(addr.getAlignment(), indices.back(), eltSize); + if (Base && pointeeHasCtxBTFDeclTagAttr(Base)) + addr = wrapWithBPFContextMarker(CGF, addr); + llvm::Value *eltPtr; auto LastIndex = dyn_cast(indices.back()); if (!LastIndex || @@ -4380,9 +4425,11 @@ Address Addr = base.getAddress(*this); unsigned Idx = RL.getLLVMFieldNo(field); const RecordDecl *rec = field->getParent(); + if (hasCtxBTFDeclTagAttr(rec)) + Addr = wrapWithBPFContextMarker(*this, Addr); if (!UseVolatile) { if (!IsInPreservedAIRegion && - (!getDebugInfo() || !rec->hasAttr())) { + (!getDebugInfo() || !hasBPFPreserveAccessIndexAttr(rec))) { if (Idx != 0) // For structs, we GEP to the field that the record layout suggests. Addr = Builder.CreateStructGEP(Addr, Idx, field->getName()); @@ -4453,6 +4500,8 @@ } Address addr = base.getAddress(*this); + if (hasCtxBTFDeclTagAttr(rec)) + addr = wrapWithBPFContextMarker(*this, addr); if (auto *ClassDef = dyn_cast(rec)) { if (CGM.getCodeGenOpts().StrictVTablePointers && ClassDef->isDynamicClass()) { @@ -4475,7 +4524,7 @@ addr = Builder.CreateLaunderInvariantGroup(addr); if (IsInPreservedAIRegion || - (getDebugInfo() && rec->hasAttr())) { + (getDebugInfo() && hasBPFPreserveAccessIndexAttr(rec))) { // Remember the original union field index llvm::DIType *DbgInfo = getDebugInfo()->getOrCreateStandaloneType(base.getType(), rec->getLocation()); @@ -4489,7 +4538,7 @@ addr = addr.withElementType(CGM.getTypes().ConvertTypeForMem(FieldType)); } else { if (!IsInPreservedAIRegion && - (!getDebugInfo() || !rec->hasAttr())) + (!getDebugInfo() || !hasBPFPreserveAccessIndexAttr(rec))) // For structs, we GEP to the field that the record layout suggests. addr = emitAddrOfFieldStorage(*this, addr, field); else diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7632,14 +7632,31 @@ return false; } +static bool hasCtxBTFDeclTagAttr(Decl *D) { + return hasBTFDeclTagAttr(D, "ctx"); +} + +// Add btf_decl_tag("ctx") for nested record declarations +static void propagateCtxBTFDeclTagAttr(Sema &S, RecordDecl *RD) { + for (auto *D : RD->decls()) { + auto *NestedRD = dyn_cast(D); + if (!NestedRD || hasCtxBTFDeclTagAttr(NestedRD) || + NestedRD->getDeclContext() != RD) + continue; + D->addAttr(BTFDeclTagAttr::CreateImplicit(S.Context, "ctx")); + propagateCtxBTFDeclTagAttr(S, NestedRD); + } +} + static void handleBTFDeclTagAttr(Sema &S, Decl *D, const ParsedAttr &AL) { StringRef Str; if (!S.checkStringLiteralArgumentAttr(AL, 0, Str)) return; if (hasBTFDeclTagAttr(D, Str)) return; - D->addAttr(::new (S.Context) BTFDeclTagAttr(S.Context, AL, Str)); + if (Str == "ctx" && isa(D)) + propagateCtxBTFDeclTagAttr(S, cast(D)); } BTFDeclTagAttr *Sema::mergeBTFDeclTagAttr(Decl *D, const BTFDeclTagAttr &AL) { diff --git a/clang/test/CodeGen/btf-decl-tag-ctx-arr.c b/clang/test/CodeGen/btf-decl-tag-ctx-arr.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/btf-decl-tag-ctx-arr.c @@ -0,0 +1,34 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 2 +// REQUIRES: bpf-registered-target +// RUN: %clang -cc1 -triple bpf -disable-llvm-passes -S -emit-llvm -o - %s \ +// RUN: | FileCheck %s + +// Check that call to context.marker.bpf() is generated when array +// member of a struct marked with btf_decl_tag("ctx") is accessed. + +#define __ctx __attribute__((btf_decl_tag("ctx"))) + +struct foo { + struct { + int a; + } b[7]; +} __ctx; + +// CHECK-LABEL: define dso_local i32 @arr_access +// CHECK-SAME: (ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.context.marker.bpf(ptr [[TMP0]]) +// CHECK-NEXT: [[B:%.*]] = getelementptr inbounds [[STRUCT_FOO:%.*]], ptr [[TMP1]], i32 0, i32 0 +// CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.context.marker.bpf(ptr [[B]]) +// CHECK-NEXT: [[ARRAYIDX:%.*]] = getelementptr inbounds [7 x %struct.anon], ptr [[TMP2]], i64 0, i64 2 +// CHECK-NEXT: [[TMP3:%.*]] = call ptr @llvm.context.marker.bpf(ptr [[ARRAYIDX]]) +// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds [[STRUCT_ANON:%.*]], ptr [[TMP3]], i32 0, i32 0 +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[A]], align 4 +// CHECK-NEXT: ret i32 [[TMP4]] +// +int arr_access(struct foo *p) { + return p->b[2].a; +} diff --git a/clang/test/CodeGen/btf-decl-tag-ctx-bitfield.c b/clang/test/CodeGen/btf-decl-tag-ctx-bitfield.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/btf-decl-tag-ctx-bitfield.c @@ -0,0 +1,30 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 2 +// REQUIRES: bpf-registered-target +// RUN: %clang -cc1 -triple bpf -disable-llvm-passes -S -emit-llvm -o - %s \ +// RUN: | FileCheck %s + +// Check that call to context.marker.bpf() is generated when bitfield +// from a struct marked with btf_decl_tag("ctx") is accessed. + +#define __ctx __attribute__((btf_decl_tag("ctx"))) + +struct foo { + unsigned a:1; +} __ctx; + +// CHECK-LABEL: define dso_local void @lvalue_bitfield +// CHECK-SAME: (ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.context.marker.bpf(ptr [[TMP0]]) +// CHECK-NEXT: [[BF_LOAD:%.*]] = load i8, ptr [[TMP1]], align 4 +// CHECK-NEXT: [[BF_CLEAR:%.*]] = and i8 [[BF_LOAD]], -2 +// CHECK-NEXT: [[BF_SET:%.*]] = or i8 [[BF_CLEAR]], 1 +// CHECK-NEXT: store i8 [[BF_SET]], ptr [[TMP1]], align 4 +// CHECK-NEXT: ret void +// +void lvalue_bitfield(struct foo *p) { + p->a = 1; +} diff --git a/clang/test/CodeGen/btf-decl-tag-ctx-lvalue.c b/clang/test/CodeGen/btf-decl-tag-ctx-lvalue.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/btf-decl-tag-ctx-lvalue.c @@ -0,0 +1,28 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 2 +// REQUIRES: bpf-registered-target +// RUN: %clang -cc1 -triple bpf -disable-llvm-passes -S -emit-llvm -o - %s \ +// RUN: | FileCheck %s + +// Check that call to context.marker.bpf() is generated when field of +// a struct marked with btf_decl_tag("ctx") is accessed. + +#define __ctx __attribute__((btf_decl_tag("ctx"))) + +struct foo { + int a; +} __ctx; + +// CHECK-LABEL: define dso_local void @lvalue +// CHECK-SAME: (ptr noundef [[P:%.*]]) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: entry: +// CHECK-NEXT: [[P_ADDR:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: store ptr [[P]], ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP0:%.*]] = load ptr, ptr [[P_ADDR]], align 8 +// CHECK-NEXT: [[TMP1:%.*]] = call ptr @llvm.context.marker.bpf(ptr [[TMP0]]) +// CHECK-NEXT: [[A:%.*]] = getelementptr inbounds [[STRUCT_FOO:%.*]], ptr [[TMP1]], i32 0, i32 0 +// CHECK-NEXT: store i32 42, ptr [[A]], align 4 +// CHECK-NEXT: ret void +// +void lvalue(struct foo *p) { + p->a = 42; +} diff --git a/clang/test/CodeGen/btf-decl-tag-ctx-non-bpf.c b/clang/test/CodeGen/btf-decl-tag-ctx-non-bpf.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/btf-decl-tag-ctx-non-bpf.c @@ -0,0 +1,17 @@ +// REQUIRES: x86-registered-target +// RUN: %clang -cc1 -triple x86_64 -disable-llvm-passes -S -emit-llvm -o - %s \ +// RUN: | FileCheck %s + +// Verify that btf_decl_tag("ctx") has no effect for non-BPF target. + +#define __ctx __attribute__((btf_decl_tag("ctx"))) + +struct foo { + int a; +} __ctx; + +// CHECK-NOT: @llvm.context.marker.bpf + +int bar(struct foo *p) { + return p->a; +} diff --git a/clang/test/CodeGen/btf-decl-tag-ctx-pai.c b/clang/test/CodeGen/btf-decl-tag-ctx-pai.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/btf-decl-tag-ctx-pai.c @@ -0,0 +1,22 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang -cc1 -triple bpf -disable-llvm-passes -S -emit-llvm -o - %s \ +// RUN: -O2 -debug-info-kind=limited \ +// RUN: | FileCheck %s + +// Verify that btf_decl_tag("ctx") has priority over preserve_access_index. + +#define __ctx __attribute__((btf_decl_tag("ctx"))) +#define __pai __attribute__((preserve_access_index)) + +struct foo { + int a; + struct { + int aa; + } b; +} __ctx __pai; + +// CHECK: @llvm.context.marker.bpf +// CHECK-NOT: @llvm.preserve.array.access.index +int bar(struct foo *p) { + return p->a + p->b.aa; +} diff --git a/clang/test/Sema/btf-decl-tag-ctx-ast.c b/clang/test/Sema/btf-decl-tag-ctx-ast.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/btf-decl-tag-ctx-ast.c @@ -0,0 +1,54 @@ +// RUN: %clang_cc1 -fsyntax-only -ast-dump %s | FileCheck %s +// +// SemaDeclAttr.cpp has special handling for the btf_decl_tag("ctx") +// attribute, check it here. + +// The '... has no meaning' attribute should be attached to 'bar' but +// should not be propagated to 'w' struct. +// +// CHECK: RecordDecl {{.*}} struct bar definition +// CHECK-NEXT: BTFDeclTagAttr {{.*}} "this attr has no meaning" +// CHECK-NEXT: FieldDecl {{.*}} u +// CHECK-NEXT: RecordDecl {{.*}} struct definition +// CHILD-NEXT: FieldDecl {{.*}} v +struct bar { + int u; + struct { + int v; + } w; +} __attribute__((btf_decl_tag("this attr has no meaning"))); + +// The 'ctx' attribute should be propagated to inline declarations +// ('b', 'bb', 'c' but not 'd'). +// +// CHECK: RecordDecl {{.*}} struct foo definition +// CHECK-NEXT: BTFDeclTagAttr {{.*}} "ctx" +// CHECK-NEXT: FieldDecl {{.*}} a +// CHECK-NEXT: RecordDecl {{.*}} struct definition +// CHECK-NEXT: BTFDeclTagAttr {{.*}} Implicit "ctx" +// CHECK-NEXT: FieldDecl {{.*}} aa +// CHECK-NEXT: RecordDecl {{.*}} struct definition +// CHECK-NEXT: BTFDeclTagAttr {{.*}} Implicit "ctx" +// CHECK-NEXT: FieldDecl {{.*}} aaa +// CHECK-NEXT: FieldDecl {{.*}} bb +// CHECK-NEXT: FieldDecl {{.*}} b +// CHECK-NEXT: RecordDecl {{.*}} union definition +// CHECK-NEXT: BTFDeclTagAttr {{.*}} Implicit "ctx" +// CHECK-NEXT: FieldDecl {{.*}} bb +// CHECK-NEXT: FieldDecl {{.*}} c +// CHECK-NEXT: FieldDecl {{.*}} d +// CHECK-NEXT: FieldDecl {{.*}} e +struct foo { + int a; + struct { + int aa; + struct { + int aaa; + } bb; + } b; + union { + int bb; + } c; + struct bar d; + int e; +} __attribute__((btf_decl_tag("ctx"))); diff --git a/llvm/include/llvm/IR/Intrinsics.td b/llvm/include/llvm/IR/Intrinsics.td --- a/llvm/include/llvm/IR/Intrinsics.td +++ b/llvm/include/llvm/IR/Intrinsics.td @@ -2436,6 +2436,11 @@ [IntrNoMem, ImmArg>, ImmArg>]>; +def int_context_marker_bpf : DefaultAttrsIntrinsic<[llvm_ptr_ty], + [llvm_ptr_ty], + [IntrNoMem, IntrSpeculatable, + NoCapture >, + ReadNone >]>; //===------------ Intrinsics to perform common vector shuffles ------------===// diff --git a/llvm/include/llvm/IR/IntrinsicsBPF.td b/llvm/include/llvm/IR/IntrinsicsBPF.td --- a/llvm/include/llvm/IR/IntrinsicsBPF.td +++ b/llvm/include/llvm/IR/IntrinsicsBPF.td @@ -37,4 +37,43 @@ def int_bpf_compare : ClangBuiltin<"__builtin_bpf_compare">, Intrinsic<[llvm_i1_ty], [llvm_i32_ty, llvm_anyint_ty, llvm_anyint_ty], [IntrNoMem]>; + def int_bpf_getelementptr_and_load : ClangBuiltin<"__builtin_bpf_getelementptr_and_load">, + Intrinsic<[llvm_any_ty], + [llvm_ptr_ty, // base ptr for getelementptr + llvm_i1_ty, // volatile + llvm_i8_ty, // atomic order + llvm_i8_ty, // synscope id + llvm_i8_ty, // alignment + llvm_i1_ty, // inbounds + llvm_vararg_ty], // indices for getelementptr insn + [IntrNoCallback, + IntrNoFree, + IntrWillReturn, + NoCapture >, + ImmArg >, // volatile + ImmArg >, // atomic order + ImmArg >, // synscope id + ImmArg >, // alignment + ImmArg >, // inbounds + ]>; + def int_bpf_getelementptr_and_store : ClangBuiltin<"__builtin_bpf_getelementptr_and_store">, + Intrinsic<[], + [llvm_any_ty, // value to store + llvm_ptr_ty, // base ptr for getelementptr + llvm_i1_ty, // volatile + llvm_i8_ty, // atomic order + llvm_i8_ty, // syncscope id + llvm_i8_ty, // alignment + llvm_i1_ty, // inbounds + llvm_vararg_ty], // indexes for getelementptr insn + [IntrNoCallback, + IntrNoFree, + IntrWillReturn, + NoCapture >, + ImmArg >, // volatile + ImmArg >, // atomic order + ImmArg >, // syncscope id + ImmArg >, // alignment + ImmArg >, // inbounds + ]>; } diff --git a/llvm/lib/Target/BPF/BPF.h b/llvm/lib/Target/BPF/BPF.h --- a/llvm/lib/Target/BPF/BPF.h +++ b/llvm/lib/Target/BPF/BPF.h @@ -10,6 +10,7 @@ #define LLVM_LIB_TARGET_BPF_BPF_H #include "MCTargetDesc/BPFMCTargetDesc.h" +#include "llvm/IR/Instructions.h" #include "llvm/IR/PassManager.h" #include "llvm/Pass.h" #include "llvm/Target/TargetMachine.h" @@ -64,6 +65,24 @@ public: PreservedAnalyses run(Module &M, ModuleAnalysisManager &AM); }; + +class BPFRewriteContextAccessPass + : public PassInfoMixin { + bool AllowPartial; + +public: + BPFRewriteContextAccessPass(bool AllowPartial) : AllowPartial(AllowPartial) {} + PreservedAnalyses run(Function &F, FunctionAnalysisManager &AM); + + static bool isRequired() { return true; } + + static std::pair + reconstructLoad(CallInst *Call); + + static std::pair + reconstructStore(CallInst *Call); +}; + } // namespace llvm #endif diff --git a/llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp b/llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp --- a/llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp +++ b/llvm/lib/Target/BPF/BPFCheckAndAdjustIR.cpp @@ -12,6 +12,8 @@ // The following are done for IR adjustment: // - remove __builtin_bpf_passthrough builtins. Target independent IR // optimizations are done and those builtins can be removed. +// - remove llvm.bpf.getelementptr.and.load builtins. +// - remove llvm.bpf.getelementptr.and.store builtins. // //===----------------------------------------------------------------------===// @@ -22,6 +24,7 @@ #include "llvm/IR/GlobalVariable.h" #include "llvm/IR/Instruction.h" #include "llvm/IR/Instructions.h" +#include "llvm/IR/IntrinsicsBPF.h" #include "llvm/IR/Module.h" #include "llvm/IR/Type.h" #include "llvm/IR/User.h" @@ -47,6 +50,7 @@ bool adjustIR(Module &M); bool removePassThroughBuiltin(Module &M); bool removeCompareBuiltin(Module &M); + bool removeGEPBuiltins(Module &M); }; } // End anonymous namespace @@ -161,9 +165,61 @@ return Changed; } +static void unrollGEPLoad(CallInst *Call) { + auto [GEP, Load] = BPFRewriteContextAccessPass::reconstructLoad(Call); + GEP->insertBefore(Call); + Load->insertBefore(Call); + Call->replaceAllUsesWith(Load); + Call->eraseFromParent(); +} + +static void unrollGEPStore(CallInst *Call) { + auto [GEP, Store] = BPFRewriteContextAccessPass::reconstructStore(Call); + GEP->insertBefore(Call); + Store->insertBefore(Call); + Call->eraseFromParent(); +} + +static bool removeGEPBuiltinsInFunc(Function &F) { + SmallVector GEPLoads; + SmallVector GEPStores; + for (auto &BB : F) + for (auto &Insn : BB) + if (auto *Call = dyn_cast(&Insn)) + if (auto *Called = Call->getCalledFunction()) + switch (Called->getIntrinsicID()) { + case Intrinsic::bpf_getelementptr_and_load: + GEPLoads.push_back(Call); + break; + case Intrinsic::bpf_getelementptr_and_store: + GEPStores.push_back(Call); + break; + } + + if (GEPLoads.empty() && GEPStores.empty()) + return false; + + for_each(GEPLoads, unrollGEPLoad); + for_each(GEPStores, unrollGEPStore); + + return true; +} + +// Rewrites the following builtins: +// - llvm.bpf.getelementptr.and.load +// - llvm.bpf.getelementptr.and.store +// As (load (getelementptr ...)) or (store (getelementptr ...)). +bool BPFCheckAndAdjustIR::removeGEPBuiltins(Module &M) { + bool Changed = false; + for (auto &F : M) + Changed = removeGEPBuiltinsInFunc(F) || Changed; + return Changed; +} + bool BPFCheckAndAdjustIR::adjustIR(Module &M) { bool Changed = removePassThroughBuiltin(M); Changed = removeCompareBuiltin(M) || Changed; + Changed = removeGEPBuiltins(M) || Changed; return Changed; } diff --git a/llvm/lib/Target/BPF/BPFContextMarker.cpp b/llvm/lib/Target/BPF/BPFContextMarker.cpp new file mode 100644 --- /dev/null +++ b/llvm/lib/Target/BPF/BPFContextMarker.cpp @@ -0,0 +1,605 @@ +//===------ BPFContextMarker.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.context.marker.bpf + GEP + load / store +// with llvm.bpf.getelementptr.and.load / store +// +// This file implements BPFRewriteContextAccessPass 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.context.marker.bpf are inserted. +// - BPFRewriteContextAccessPass is executed as early as possible +// with AllowPatial set to true, this handles marked GEP chains +// with constant offsets. +// - BPFRewriteContextAccessPass 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__((btf_decl_tag("ctx"))); +// +// struct context *ctx = ...; +// #pragma clang loop unroll(full) +// for (int i = 0; i < 4; ++i) +// foo(ctx->x[i]); +// +// The early BPFRewriteContextAccessPass 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 +// BPFRewriteContextAccessPass 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-context-marker" + +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(I)) + if (Function *Func = Call->getCalledFunction()) + return Func->getIntrinsicID() == Id; + return false; +} + +static bool isContextMarkerCall(Value *I) { + return isIntrinsicCall(I, Intrinsic::context_marker_bpf); +} + +static CallInst *isGEPAndLoad(Value *I) { + if (isIntrinsicCall(I, Intrinsic::bpf_getelementptr_and_load)) + return cast(I); + return nullptr; +} + +static CallInst *isGEPAndStore(Value *I) { + if (isIntrinsicCall(I, Intrinsic::bpf_getelementptr_and_store)) + return cast(I); + return nullptr; +} + +template +static DILocation *mergeDILocations(SmallVector &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 Types, + ArrayRef 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 Indices; + SmallVector Members; + + GEPChainInfo() { reset(); } + + void reset() { + InBounds = true; + SourceElementType = nullptr; + Indices.clear(); + Members.clear(); + } +}; +} // Anonymous namespace + +template > +static void fillCommonArgs(LLVMContext &C, SmallVector &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 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 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(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 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(Indices), Call->getName()); + GEP->setIsInBounds(getOperandAsUnsigned(Call, 5 + Delta)); + return GEP; +} + +template > +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 +BPFRewriteContextAccessPass::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 +BPFRewriteContextAccessPass::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(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 %, ptr %p, i32 0, , , ... +static bool foldGEPChainAsStructAccess(SmallVector &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 &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 bool isPointerOperand(Value *I, User *U) { + if (auto *L = dyn_cast(U)) + return L->getPointerOperand() == I; + if (auto *S = dyn_cast(U)) + return S->getPointerOperand() == I; + if (auto *GEP = dyn_cast(U)) + return GEP->getPointerOperand() == I; + if (auto *Call = isGEPAndLoad(I)) + return Call->getArgOperand(0) == U; + if (auto *Call = isGEPAndStore(I)) + return Call->getArgOperand(1) == U; + 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 btf_decl_tag(\"ctx\") 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 &GEPs) { + return GEPs.empty() || all_of(GEPs, [=](GetElementPtrInst *GEP) { + return GEP->hasAllZeroIndices(); + }); +} + +static bool tryToReplaceWithGEPBuiltin(Instruction *LoadOrStoreTemplate, + SmallVector &GEPs, + Instruction *InsnToReplace) { + GEPChainInfo GEPChain; + if (!foldGEPChainAsStructAccess(GEPs, GEPChain) && + !foldGEPChainAsU8Access(GEPs, GEPChain)) { + return false; + } + Module *M = InsnToReplace->getModule(); + if (auto *Load = dyn_cast(LoadOrStoreTemplate)) { + Instruction *Replacement = makeGEPAndLoad(M, GEPChain, Load); + Replacement->insertBefore(InsnToReplace); + InsnToReplace->replaceAllUsesWith(Replacement); + } + if (auto *Store = dyn_cast(LoadOrStoreTemplate)) { + Instruction *Replacement = makeGEPAndStore(M, GEPChain, Store); + Replacement->insertBefore(InsnToReplace); + } + return true; +} + +// A DFS traversal of GEP chain trees starting from Root. +// +// Recursion descends through GEP instructions and +// llvm.context.marker.bpf 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.context.marker.bpf` are encountered these +// calls are marked for deletion. +// +// Parameters description: +// - Root - a call to llvm.context.marker.bpf, the root of the tree +// - 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 marker is still +// in use and should remain until the next run of this pass. +static void rewriteAccessChain(Value *Root, Instruction *Insn, + SmallVector &GEPs, + SmallVector &Visited, + bool AllowPatial, bool &StillUsed) { + auto MarkAndTraverseUses = [&]() { + Visited.push_back(Insn); + for (auto *U : Insn->users()) + if (auto *UI = dyn_cast(U)) + if (isPointerOperand(Insn, UI)) + rewriteAccessChain(Root, UI, GEPs, Visited, AllowPatial, StillUsed); + }; + auto TryToReplace = [&](Instruction *LoadOrStore) { + // Do nothing for (marker (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(Insn) || isa(Insn)) { + TryToReplace(Insn); + } else if (isGEPAndLoad(Insn)) { + auto [GEP, Load] = + BPFRewriteContextAccessPass::reconstructLoad(cast(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] = + BPFRewriteContextAccessPass::reconstructStore(cast(Insn)); + GEPs.push_back(GEP); + TryToReplace(Store); + GEPs.pop_back(); + delete Store; + delete GEP; + } else if (auto *GEP = dyn_cast(Insn)) { + GEPs.push_back(GEP); + MarkAndTraverseUses(); + GEPs.pop_back(); + } else if (isContextMarkerCall(Insn)) { + MarkAndTraverseUses(); + } else if (auto *Call = dyn_cast(Insn); + Call && Call->hasFnAttr(Attribute::InlineHint)) { + // Preserve marker 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 context structure is passed as parameter + // of a function that would be inlined inside a loop that would be unrolled. + if (AllowPatial) + StillUsed = true; + } +} + +static void removeMarker(Instruction *Marker) { + Marker->replaceAllUsesWith(Marker->getOperand(0)); + Marker->eraseFromParent(); +} + +static bool rewriteAccessChain(Instruction *Marker, bool AllowPatial, + SmallPtrSetImpl &RemovedMarkers) { + SmallVector GEPs; + SmallVector Visited; + bool StillUsed = false; + for (User *U : Marker->users()) + if (auto *UI = dyn_cast(U)) + if (isPointerOperand(Marker, UI)) + rewriteAccessChain(Marker, UI, 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 (isContextMarkerCall(*V)) { + removeMarker(*V); + RemovedMarkers.insert(*V); + } else if ((*V)->use_empty()) { + (*V)->eraseFromParent(); + } + } + return StillUsed; +} + +static std::vector collectContextMarkers(Function &F) { + std::vector Markers; + for (Instruction &Insn : instructions(F)) + if (isContextMarkerCall(&Insn)) + Markers.push_back(&Insn); + return Markers; +} + +// Look for sequences: +// - llvm.context.marker.bpf -> getelementptr... -> load +// - llvm.context.marker.bpf -> getelementptr... -> store +// And replace those with calls to intrinsics: +// - llvm.bpf.getelementptr.and.load +// - llvm.bpf.getelementptr.and.store +static bool rewriteContextAccess(Function &F, bool AllowPartial) { + LLVM_DEBUG(dbgs() << "********** Rewrite Context Access (AllowPartial=" + << AllowPartial << ") ************\n"); + + auto ContextMarkers = collectContextMarkers(F); + SmallPtrSet RemovedMarkers; + + LLVM_DEBUG(dbgs() << "There are " << ContextMarkers.size() + << " context markers\n"); + + if (ContextMarkers.empty()) + return false; + + for (auto *Marker : ContextMarkers) { + if (RemovedMarkers.contains(Marker)) + continue; + bool StillUsed = rewriteAccessChain(Marker, AllowPartial, RemovedMarkers); + if (!StillUsed || !AllowPartial) + removeMarker(Marker); + } + + return true; +} + +PreservedAnalyses +llvm::BPFRewriteContextAccessPass::run(Function &F, + FunctionAnalysisManager &AM) { + return rewriteContextAccess(F, AllowPartial) ? PreservedAnalyses::none() + : PreservedAnalyses::all(); +} diff --git a/llvm/lib/Target/BPF/BPFTargetMachine.cpp b/llvm/lib/Target/BPF/BPFTargetMachine.cpp --- a/llvm/lib/Target/BPF/BPFTargetMachine.cpp +++ b/llvm/lib/Target/BPF/BPFTargetMachine.cpp @@ -106,6 +106,10 @@ FPM.addPass(BPFIRPeepholePass()); return true; } + if (PassName == "bpf-context-marker") { + FPM.addPass(BPFRewriteContextAccessPass(false)); + return true; + } return false; }); PB.registerPipelineStartEPCallback( @@ -114,12 +118,19 @@ FPM.addPass(BPFAbstractMemberAccessPass(this)); FPM.addPass(BPFPreserveDITypePass()); FPM.addPass(BPFIRPeepholePass()); + FPM.addPass(BPFRewriteContextAccessPass(true)); MPM.addPass(createModuleToFunctionPassAdaptor(std::move(FPM))); }); PB.registerPeepholeEPCallback([=](FunctionPassManager &FPM, OptimizationLevel Level) { FPM.addPass(SimplifyCFGPass(SimplifyCFGOptions().hoistCommonInsts(true))); }); + PB.registerScalarOptimizerLateEPCallback( + [=](FunctionPassManager &FPM, OptimizationLevel Level) { + // Run this after loop unrolling but before + // SimplifyCFGPass(... .sinkCommonInsts(true)) + FPM.addPass(BPFRewriteContextAccessPass(false)); + }); PB.registerPipelineEarlySimplificationEPCallback( [=](ModulePassManager &MPM, OptimizationLevel) { MPM.addPass(BPFAdjustOptPass()); diff --git a/llvm/lib/Target/BPF/CMakeLists.txt b/llvm/lib/Target/BPF/CMakeLists.txt --- a/llvm/lib/Target/BPF/CMakeLists.txt +++ b/llvm/lib/Target/BPF/CMakeLists.txt @@ -19,6 +19,7 @@ BPFAdjustOpt.cpp BPFAsmPrinter.cpp BPFCheckAndAdjustIR.cpp + BPFContextMarker.cpp BPFFrameLowering.cpp BPFInstrInfo.cpp BPFIRPeephole.cpp diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-align.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-align.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-align.ll @@ -0,0 +1,68 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a load instruction for a field with non-standard +; alignment by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; typedef int aligned_int __attribute__((aligned(128))); +; +; struct foo { +; int _; +; aligned_int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->a); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, [124 x i8], i32, [124 x i8] } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 2 + %1 = load i32, ptr %a, align 128, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[a1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 7, i1 true, i32 immarg 0, i32 immarg 2) +; ^^^^ +; alignment 2**7 +; CHECK-SAME: #[[v2:.*]], !tbaa +; CHECK-NEXT: call void @consume(i32 noundef %[[a1]]) + +; CHECK: attributes #[[v2]] = { memory(argmem: read) } + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 128} +!3 = !{!"foo", !4, i64 0, !4, i64 128} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} + diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-atomic.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-atomic.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-atomic.ll @@ -0,0 +1,67 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of atomic load instruction by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int _; +; int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; int r; +; __atomic_load(&p->a, &r, 2); +; consume(r); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %1 = load atomic i32, ptr %a acquire, align 4 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[a1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr elementtype(%struct.foo) %[[p:.*]], +; i1 false, i8 4, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) +; ^^^^ +; atomic order +; CHECK-NOT: #{{[0-9]+}} +; CHECK-NEXT: call void @consume(i32 noundef %[[a1]]) + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +declare void @consume(i32 noundef) #3 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-2.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-2.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-2.ll @@ -0,0 +1,83 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds GEP chains that end by +; getelementptr.and.load. +; +; Source (modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->b.bb); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; And modified to fold last getelementptr/load as a single +; getelementptr.and.load. + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %bb1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr readonly elementtype(%struct.bar) %b, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) + #4, !tbaa !2 + call void @consume(i32 noundef %bb1) + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[bb1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa +; CHECK-NEXT: call void @consume(i32 noundef %[[bb1]]) + +; CHECK: attributes #[[v2]] = { memory(argmem: read) } + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #3 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #3 = { nocallback nofree nounwind willreturn } +attributes #4 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-oob.ll @@ -0,0 +1,74 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker keeps track of 'inbounds' flags while +; folding chain of GEP instructions. +; +; Source (IR modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a[2]; +; }; +; +; struct bar { +; int a; +; struct foo b; +; } __ctx; +; +; extern void consume(int); +; +; void buz(struct bar *p) { +; consume(p->b.a[1]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; Modified to remove one of the 'inbounds' from one of the GEP instructions. + + +%struct.bar = type { i32, %struct.foo } +%struct.foo = type { [2 x i32] } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %a = getelementptr %struct.foo, ptr %b, i32 0, i32 0 + %arrayidx = getelementptr inbounds [2 x i32], ptr %a, i64 0, i64 1 + %1 = load i32, ptr %arrayidx, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[v1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.bar) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 false, +; ^^^^^^^^ +; not inbounds +; CHECK-SAME: i32 immarg 0, i32 immarg 1, i32 immarg 0, i64 immarg 1) +; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +; folded gep chain +; CHECK-NEXT: call void @consume(i32 noundef %[[v1]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"int", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-oob.ll @@ -0,0 +1,75 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; The GEP chain in this example has unexpected shape and thus is +; folded as i8 access. +; +; Source (IR modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char a[2]; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; extern void consume(char); +; +; void buz(struct bar *p) { +; consume((&p->b)[1].a[1]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; Modified to remove 'inbounds' from one of the GEP instructions. + + +%struct.bar = type { i8, %struct.foo } +%struct.foo = type { [2 x i8] } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %arrayidx = getelementptr inbounds %struct.foo, ptr %b, i64 1 +; ^^^^^ +; folded as i8 access because of this index + %a = getelementptr %struct.foo, ptr %arrayidx, i32 0, i32 0 + %arrayidx1 = getelementptr inbounds [2 x i8], ptr %a, i64 0, i64 1 + %1 = load i8, ptr %arrayidx1, align 1, !tbaa !2 + call void @consume(i8 noundef signext %1) + ret void +} + +; CHECK: %[[v1:.*]] = call i8 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i8 +; CHECK-SAME: (ptr readonly elementtype(i8) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 0, i1 false, i64 immarg 4) +; ^^^^^^^^ ^^^^^^^^^^^^ +; not inbounds ---' | +; offset from 'struct bar' start -------------' +; CHECK-NEXT: call void @consume(i8 noundef signext %[[v1]]) + +declare void @consume(i8 noundef signext) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"omnipotent char", !4, i64 0} +!4 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-type-mismatch.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-type-mismatch.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8-type-mismatch.ll @@ -0,0 +1,74 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; The GEP chain in this example has unexpected shape and thus is +; folded as i8 access. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char aa; +; char bb; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; extern void consume(char); +; +; void buz(struct bar *p) { +; consume(((struct foo *)(((char*)&p->b) + 1))->bb); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.bar = type { i8, %struct.foo } +%struct.foo = type { i8, i8 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %add.ptr = getelementptr inbounds i8, ptr %b, i64 1 +; ~~ +; these types do not match, thus GEP chain is folded as an offset +; ~~~~~~~~~~~ + %bb = getelementptr inbounds %struct.foo, ptr %add.ptr, i32 0, i32 1 + %1 = load i8, ptr %bb, align 1, !tbaa !2 + call void @consume(i8 noundef signext %1) + ret void +} + +; CHECK: %[[bb1:.*]] = call i8 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i8 +; CHECK-SAME: (ptr readonly elementtype(i8) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 0, i1 true, i64 immarg 3) +; ^^^^^^^^^^^^ +; offset from 'struct bar' start +; CHECK-NEXT: call void @consume(i8 noundef signext %[[bb1]]) + +declare void @consume(i8 noundef signext) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 1} +!3 = !{!"foo", !4, i64 0, !4, i64 1} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain-u8.ll @@ -0,0 +1,72 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; The GEP chain in this example has unexpected shape and thus is +; folded as i8 access. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char a[2]; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; extern void consume(char); +; +; void buz(struct bar *p) { +; consume((&p->b)[1].a[1]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.bar = type { i8, %struct.foo } +%struct.foo = type { [2 x i8] } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %arrayidx = getelementptr inbounds %struct.foo, ptr %b, i64 1 +; ^^^^^ +; folded as i8 access because of this index + %a = getelementptr inbounds %struct.foo, ptr %arrayidx, i32 0, i32 0 + %arrayidx1 = getelementptr inbounds [2 x i8], ptr %a, i64 0, i64 1 + %1 = load i8, ptr %arrayidx1, align 1, !tbaa !2 + call void @consume(i8 noundef signext %1) + ret void +} + +; CHECK: %[[v1:.*]] = call i8 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i8 +; CHECK-SAME: (ptr readonly elementtype(i8) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 0, i1 true, i64 immarg 4) +; ^^^^^^^^^^^^ +; offset from 'struct bar' start +; CHECK-NEXT: call void @consume(i8 noundef signext %[[v1]]) + +declare void @consume(i8 noundef signext) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"omnipotent char", !4, i64 0} +!4 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-chain.ll @@ -0,0 +1,69 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a[2]; +; }; +; +; struct bar { +; int a; +; struct foo b; +; } __ctx; +; +; extern void consume(int); +; +; void buz(struct bar *p) { +; consume(p->b.a[1]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.bar = type { i32, %struct.foo } +%struct.foo = type { [2 x i32] } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %a = getelementptr inbounds %struct.foo, ptr %b, i32 0, i32 0 + %arrayidx = getelementptr inbounds [2 x i32], ptr %a, i64 0, i64 1 + %1 = load i32, ptr %arrayidx, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[v1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.bar) %{{[^,]+}}, +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, +; CHECK-SAME: i32 immarg 0, i32 immarg 1, i32 immarg 0, i64 immarg 1) +; ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ +; folded gep chain +; CHECK-NEXT: call void @consume(i32 noundef %[[v1]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"int", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-inline.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-inline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-inline.ll @@ -0,0 +1,87 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check position of bpf-context-marker pass in the pipeline: +; - context.marker.bpf call is preserved if address is passed as +; a parameter to an inline-able function; +; - second bpf-context-marker pass (after inlining) should introduce +; getelementptr.and.load call using the preserved marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; extern void consume(int); +; +; static inline void bar(struct bar *p){ +; consume(p->bb); +; } +; +; void quux(struct foo *p) { +; bar(&p->b); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @quux(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + call void @bar(ptr noundef %b) + ret void +} + +; Function Attrs: inlinehint nounwind +define internal void @bar(ptr noundef %p) #1 { +entry: + %bb = getelementptr inbounds %struct.bar, ptr %p, i32 0, i32 1 + %0 = load i32, ptr %bb, align 4, !tbaa !2 + call void @consume(i32 noundef %0) + ret void +} + +; CHECK: define dso_local void @quux(ptr nocapture noundef readonly %[[p:.*]]) +; CHECK: %[[bb_i1:.*]] = tail call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i64 immarg 0, i32 immarg 1, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa +; CHECK-NEXT: tail call void @consume(i32 noundef %[[bb_i1]]) + +; CHECK: attributes #[[v2]] = { memory(argmem: read) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +declare void @consume(i32 noundef) #3 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { inlinehint nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #3 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"bar", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-non-const.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-non-const.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-non-const.ll @@ -0,0 +1,76 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s 2>&1 | FileCheck %s +; +; If load offset is not a constant bpf-context-marker should report a +; warning and remove context.marker.bpf call. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a[7]; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p, unsigned long i) { +; consume(p->a[i]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -debug-info-kind=line-tables-only -triple bpf \ +; -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { [7 x i32] } + +; CHECK: warning: some-file.c:10:11: in function bar void (ptr, i64): +; CHECK-SAME: Non-constant offset in access to a field of a type marked with +; CHECK-SAME: btf_decl_tag("ctx") might be rejected by BPF verifier + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p, i64 noundef %i) #0 !dbg !5 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p), !dbg !9 + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 0, !dbg !9 + %arrayidx = getelementptr inbounds [7 x i32], ptr %a, i64 0, i64 %i, !dbg !10 + %1 = load i32, ptr %arrayidx, align 4, !dbg !10, !tbaa !11 + call void @consume(i32 noundef %1), !dbg !15 + ret void, !dbg !16 +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]], i64 noundef %[[i:.*]]) +; CHECK: %[[a:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 0, !dbg +; CHECK-NEXT: %[[arrayidx:.*]] = getelementptr inbounds [7 x i32], ptr %[[a]], i64 0, i64 %[[i]], !dbg +; CHECK-NEXT: %[[v5:.*]] = load i32, ptr %[[arrayidx]], align 4, !dbg {{.*}}, !tbaa +; CHECK-NEXT: call void @consume(i32 noundef %[[v5]]), !dbg + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.dbg.cu = !{!0} +!llvm.module.flags = !{!2, !3} +!llvm.ident = !{!4} + +!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)", isOptimized: true, runtimeVersion: 0, emissionKind: LineTablesOnly, splitDebugInlining: false, nameTableKind: None) +!1 = !DIFile(filename: "some-file.c", directory: "/some/dir/") +!2 = !{i32 2, !"Debug Info Version", i32 3} +!3 = !{i32 1, !"wchar_size", i32 4} +!4 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!5 = distinct !DISubprogram(name: "bar", scope: !6, file: !6, line: 9, type: !7, scopeLine: 9, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0) +!6 = !DIFile(filename: "some-file.c", directory: "/some/dir/") +!7 = !DISubroutineType(types: !8) +!8 = !{} +!9 = !DILocation(line: 10, column: 14, scope: !5) +!10 = !DILocation(line: 10, column: 11, scope: !5) +!11 = !{!12, !12, i64 0} +!12 = !{!"int", !13, i64 0} +!13 = !{!"omnipotent char", !14, i64 0} +!14 = !{!"Simple C/C++ TBAA"} +!15 = !DILocation(line: 10, column: 3, scope: !5) +!16 = !DILocation(line: 11, column: 1, scope: !5) diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-simple.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-simple.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-simple.ll @@ -0,0 +1,72 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a simple load instruction by bpf-context-marker. +; Verify: +; - presence of gep.and.load intrinsic call +; - correct attributes for intrinsic call +; - presence of tbaa annotations +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int _; +; int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->a); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %1 = load i32, ptr %a, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[a1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) +; CHECK-SAME: #[[v1:.*]], !tbaa +; CHECK-NEXT: call void @consume(i32 noundef %[[a1]]) + +; CHECK: declare i32 +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, {{.*}}) #[[v2:.*]] + +; CHECK: attributes #[[v2]] = { nocallback nofree nounwind willreturn } +; CHECK: attributes #[[v1]] = { memory(argmem: read) } + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-align.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-align.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-align.ll @@ -0,0 +1,63 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that getelementptr.and.load unroll restores alignment spec. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; typedef int aligned_int __attribute__((aligned(128))); +; +; struct foo { +; int _; +; aligned_int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->a); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, [124 x i8], i32, [124 x i8] } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %a1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr readonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 7, i1 true, i32 immarg 0, i32 immarg 2) + #3, !tbaa !2 + call void @consume(i32 noundef %a1) + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[a11:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 2 +; CHECK: %[[v2:.*]] = load i32, ptr %[[a11]], align 128 +; CHECK: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 128} +!3 = !{!"foo", !4, i64 0, !4, i64 128} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-oob.ll @@ -0,0 +1,70 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that getelementptr.and.load unroll can skip 'inbounds' flag. +; +; Source (IR modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; extern void consume(int); +; +; void buz(struct foo *p) { +; consume(p->b.bb); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - +; +; Modified to set 'inbounds' flag to false. + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %bb1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr readonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 false, i32 immarg 0, i32 immarg 1, i32 immarg 1) + #3, !tbaa !2 + call void @consume(i32 noundef %bb1) + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[bb11:.*]] = getelementptr %struct.foo, ptr %[[p]], i32 0, i32 1, i32 1 +; CHECK: %[[v2:.*]] = load i32, ptr %[[bb11]], align 4 +; CHECK: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-u8.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-u8.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain-u8.ll @@ -0,0 +1,64 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check unroll of getelementptr.and.load when direct memory offset is +; used instead of field indexes. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char aa; +; char bb; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; extern void consume(char); +; +; void buz(struct bar *p) { +; consume(((struct foo *)(((char*)&p->b) + 1))->bb); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %bb1 = call i8 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i8 + (ptr readonly elementtype(i8) %p, + i1 false, i8 0, i8 1, i8 0, i1 true, i64 immarg 3) + #3, !tbaa !2 + call void @consume(i8 noundef signext %bb1) + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[bb11:.*]] = getelementptr inbounds i8, ptr %[[p]], i64 3 +; CHECK: %[[v2:.*]] = load i8, ptr %[[bb11]], align 1 +; CHECK: call void @consume(i8 noundef signext %[[v2]]) + +declare void @consume(i8 noundef signext) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i8 @llvm.bpf.getelementptr.and.load.i8(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 1} +!3 = !{!"foo", !4, i64 0, !4, i64 1} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-chain.ll @@ -0,0 +1,69 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s + +; Check unroll of getelementptr.and.load when several field indexes +; are specified in a chain. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; extern void consume(int); +; +; void buz(struct foo *p) { +; consume(p->b.bb); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %bb1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr readonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1, i32 immarg 1) + #3, !tbaa !2 + call void @consume(i32 noundef %bb1) + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[bb11:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1, i32 1 +; CHECK: %[[v2:.*]] = load i32, ptr %[[bb11]], align 4 +; CHECK: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-simple.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-simple.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-simple.ll @@ -0,0 +1,61 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check unroll of getelementptr.and.load. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; int b; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p){ +; consume(p->b); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %b1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr readonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) + #3, !tbaa !2 + call void @consume(i32 noundef %b1) + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) #[[v1:.*]] { +; CHECK: %[[b11:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1 +; CHECK-NEXT: %[[v2:.*]] = load i32, ptr %[[b11]], align 4 +; CHECK-NEXT: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: read) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-volatile.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-volatile.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-undo-volatile.ll @@ -0,0 +1,60 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that unroll of getelementptr.and.load restores volatile. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; volatile int b; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p){ +; consume(p->b); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %b1 = call i32 (ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.load.i32 + (ptr elementtype(%struct.foo) %p, + i1 true, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1), + !tbaa !2 + call void @consume(i32 noundef %b1) + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[b11:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1 +; CHECK: %[[v2:.*]] = load volatile i32, ptr %[[b11]], align 4 +; CHECK: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare i32 @llvm.bpf.getelementptr.and.load.i32(ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nounwind willreturn } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll-inline.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll-inline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll-inline.ll @@ -0,0 +1,108 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check position of bpf-context-marker pass in the pipeline: +; - context.marker.bpf call is preserved if address is passed as +; a parameter to an inline-able function; +; - second bpf-context-marker pass (after inlining) should introduce +; getelementptr.and.load call using the preserved marker after loops +; unrolling; +; - readonly and tbaa attributes should allow replacement of +; getelementptr.and.load calls by CSE transformation. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; int b[4]; +; } __ctx; +; +; extern void consume(int); +; +; static inline void bar(int * restrict p) { +; consume(p[1]); +; } +; +; void quux(struct foo *p){ +; unsigned long i = 0; +; #pragma clang loop unroll(full) +; while (i < 2) { +; bar(p->b); +; ++i; +; } +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, [4 x i32] } + +; Function Attrs: nounwind +define dso_local void @quux(ptr noundef %p) #0 { +entry: + br label %while.cond + +while.cond: ; preds = %while.body, %entry + %i.0 = phi i64 [ 0, %entry ], [ %inc, %while.body ] + %cmp = icmp ult i64 %i.0, 2 + br i1 %cmp, label %while.body, label %while.end + +while.body: ; preds = %while.cond + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %arraydecay = getelementptr inbounds [4 x i32], ptr %b, i64 0, i64 0 + call void @bar(ptr noundef %arraydecay) + %inc = add i64 %i.0, 1 + br label %while.cond, !llvm.loop !2 + +while.end: ; preds = %while.cond + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1 + +; Function Attrs: inlinehint nounwind +define internal void @bar(ptr noalias noundef %p) #2 { +entry: + %arrayidx = getelementptr inbounds i32, ptr %p, i64 1 + %0 = load i32, ptr %arrayidx, align 4, !tbaa !5 + call void @consume(i32 noundef %0) + ret void +} + +; CHECK: define dso_local void @quux(ptr nocapture noundef readonly %[[p:.*]]) +; CHECK: %[[v1:.*]] = tail call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i64 immarg 0, i32 immarg 1, i64 immarg 1) +; CHECK: tail call void @consume(i32 noundef %[[v1]]) +; CHECK: tail call void @consume(i32 noundef %[[v1]]) + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #3 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1 + +declare void @consume(i32 noundef) #4 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #2 = { inlinehint nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #4 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = distinct !{!2, !3, !4} +!3 = !{!"llvm.loop.mustprogress"} +!4 = !{!"llvm.loop.unroll.full"} +!5 = !{!6, !6, i64 0} +!6 = !{!"int", !7, i64 0} +!7 = !{!"omnipotent char", !8, i64 0} +!8 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-unroll.ll @@ -0,0 +1,98 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check position of bpf-context-marker pass in the pipeline: +; context.marker.bpf call should be preserved long enough to allow +; introduction of getelementptr.and.load after loops unrolling. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; int b[4]; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p){ +; unsigned long i = 0; +; #pragma clang loop unroll(full) +; while (i < 2) +; consume(p->b[i++]); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.foo = type { i32, [4 x i32] } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + br label %while.cond + +while.cond: ; preds = %while.body, %entry + %i.0 = phi i64 [ 0, %entry ], [ %inc, %while.body ] + %cmp = icmp ult i64 %i.0, 2 + br i1 %cmp, label %while.body, label %while.end + +while.body: ; preds = %while.cond + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %inc = add i64 %i.0, 1 + %arrayidx = getelementptr inbounds [4 x i32], ptr %b, i64 0, i64 %i.0 + %1 = load i32, ptr %arrayidx, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + br label %while.cond, !llvm.loop !6 + +while.end: ; preds = %while.cond + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef readonly %[[p:.*]]) +; +; CHECK: %[[v1:.*]] = tail call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i64 immarg 0, i32 immarg 1, i64 immarg 0) +; CHECK-SAME: #[[attrs:.*]], !tbaa +; CHECK-NEXT: tail call void @consume(i32 noundef %[[v1]]) +; CHECK-NEXT: %[[v2:.*]] = tail call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr readonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i64 immarg 0, i32 immarg 1, i64 immarg 1) +; CHECK-SAME: #[[attrs]], !tbaa +; CHECK-NEXT: tail call void @consume(i32 noundef %[[v2]]) + +; CHECK: attributes #[[attrs]] = { memory(argmem: read) } + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1 + +declare void @consume(i32 noundef) #2 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #3 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #2 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !3, i64 0} +!3 = !{!"int", !4, i64 0} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} +!6 = distinct !{!6, !7, !8} +!7 = !{!"llvm.loop.mustprogress"} +!8 = !{!"llvm.loop.unroll.full"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-volatile.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-volatile.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-volatile.ll @@ -0,0 +1,62 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a volatile load instruction by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int _; +; volatile int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->a); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %1 = load volatile i32, ptr %a, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[a1:.*]] = call i32 (ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.load.i32 +; CHECK-SAME: (ptr elementtype(%struct.foo) %{{[^,]+}}, +; CHECK-SAME: i1 true, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) +; ^^^^^^^^ +; volatile +; CHECK-NOT: #{{[0-9]+}} +; CHECK-NEXT: call void @consume(i32 noundef %[[a1]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/load-zero.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-zero.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/load-zero.ll @@ -0,0 +1,57 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that loads from zero offset are not modified by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p) { +; consume(p->a); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + + +%struct.foo = type { i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 0 + %1 = load i32, ptr %a, align 4, !tbaa !2 + call void @consume(i32 noundef %1) + ret void +} + +; CHECK: %[[a:.*]] = getelementptr inbounds %struct.foo, ptr %[[p:.*]], i32 0, i32 0 +; CHECK-NEXT: %[[v2:.*]] = load i32, ptr %[[a]], align 4, !tbaa +; CHECK-NEXT: call void @consume(i32 noundef %[[v2]]) + +declare void @consume(i32 noundef) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 0} +!3 = !{!"foo", !4, i64 0} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-align.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-align.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-align.ll @@ -0,0 +1,60 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a store instruction for a field with non-standard +; alignment by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; typedef int aligned_int __attribute__((aligned(128))); +; +; struct foo { +; int _; +; aligned_int a; +; } __ctx; +; +; void bar(struct foo *p) { +; p->a = 7; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, [124 x i8], i32, [124 x i8] } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 2 + store i32 7, ptr %a, align 128, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 7, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 7, i1 true, i32 immarg 0, i32 immarg 2) +; CHECK-SAME: #[[v2:.*]], !tbaa + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 128} +!3 = !{!"foo", !4, i64 0, !4, i64 128} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-atomic.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-atomic.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-atomic.ll @@ -0,0 +1,60 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of atomic store instruction by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int _; +; int a; +; } __ctx; +; +; void bar(struct foo *p) { +; int r; +; r = 7; +; __atomic_store(&p->a, &r, 3); +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + store atomic i32 7, ptr %a release, align 4 + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 7, +; CHECK-SAME: ptr elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 5, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) +; CHECK-NOT: #{{[0-9]+}} +; CHECK-NEXT: ret void + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1 + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #2 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #2 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-2.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-2.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-2.ll @@ -0,0 +1,78 @@ +; RUN: opt -passes=bpf-context-marker -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds GEP chains that end by +; getelementptr.and.store. +; +; Source (modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; void buz(struct foo *p) { +; p->b.bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; And modified to fold last getelementptr/store as a single +; getelementptr.and.store. + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr writeonly elementtype(%struct.bar) %b, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) + #3, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 42, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #2 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } +attributes #2 = { nocallback nofree nounwind willreturn } +attributes #3 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-oob.ll @@ -0,0 +1,68 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker keeps track of 'inbounds' flags while +; folding chain of GEP instructions. +; +; Source (IR modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; void buz(struct foo *p) { +; p->b.bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; Modified to remove one of the 'inbounds' from one of the GEP instructions. + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %bb = getelementptr %struct.bar, ptr %b, i32 0, i32 1 + store i32 42, ptr %bb, align 4, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 42, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 false, i32 immarg 0, i32 immarg 1, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8-oob.ll @@ -0,0 +1,68 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; The GEP chain in this example has type mismatch and thus is +; folded as i8 access. +; +; Source (modified by hand): +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char aa; +; char bb; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; void buz(struct bar *p) { +; ((struct foo *)(((char*)&p->b) + 1))->bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - +; +; Modified to remove one of the 'inbounds' from one of the getelementptr. + +%struct.bar = type { i8, %struct.foo } +%struct.foo = type { i8, i8 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %add.ptr = getelementptr i8, ptr %b, i64 1 + %bb = getelementptr inbounds %struct.foo, ptr %add.ptr, i32 0, i32 1 + store i8 42, ptr %bb, align 1, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i8, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i8 +; CHECK-SAME: (i8 42, +; CHECK-SAME: ptr writeonly elementtype(i8) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 0, i1 false, i64 immarg 3) +; CHECK-SAME: #[[v2:.*]], !tbaa ![[v3:.*]] + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 1} +!3 = !{!"foo", !4, i64 0, !4, i64 1} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain-u8.ll @@ -0,0 +1,69 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; The GEP chain in this example has type mismatch and thus is +; folded as i8 access. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char aa; +; char bb; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; void buz(struct bar *p) { +; ((struct foo *)(((char*)&p->b) + 1))->bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.bar = type { i8, %struct.foo } +%struct.foo = type { i8, i8 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.bar, ptr %0, i32 0, i32 1 + %add.ptr = getelementptr inbounds i8, ptr %b, i64 1 +; ~~ +; these types do not match, thus GEP chain is folded as an offset +; ~~~~~~~~~~~ + %bb = getelementptr inbounds %struct.foo, ptr %add.ptr, i32 0, i32 1 + store i8 42, ptr %bb, align 1, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i8, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i8 +; CHECK-SAME: (i8 42, +; CHECK-SAME: ptr writeonly elementtype(i8) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 0, i1 true, i64 immarg 3) +; CHECK-SAME: #[[v2:.*]], !tbaa ![[v3:.*]] + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 1} +!3 = !{!"foo", !4, i64 0, !4, i64 1} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-chain.ll @@ -0,0 +1,65 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that bpf-context-marker folds chain of GEP instructions. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; void buz(struct foo *p) { +; p->b.bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %bb = getelementptr inbounds %struct.bar, ptr %b, i32 0, i32 1 + store i32 42, ptr %bb, align 4, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 42, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-simple.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-simple.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-simple.ll @@ -0,0 +1,61 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a simple store instruction by bpf-context-marker. +; Verify: +; - presence of gep.and.store intrinsic call +; - correct attributes for intrinsic call +; - presence of tbaa annotations +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int _; +; int a; +; } __ctx; +; +; void bar(struct foo *p) { +; p->a = 7; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + store i32 7, ptr %a, align 4, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 7, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) +; CHECK-SAME: #[[v2:.*]], !tbaa + +; CHECK: attributes #[[v2]] = { memory(argmem: write) } + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-align.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-align.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-align.ll @@ -0,0 +1,58 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that getelementptr.and.store unroll restores alignment spec. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; typedef int aligned_int __attribute__((aligned(128))); +; +; struct foo { +; int _; +; aligned_int a; +; } __ctx; +; +; void bar(struct foo *p) { +; p->a = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, [124 x i8], i32, [124 x i8] } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr writeonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 7, i1 true, i32 immarg 0, i32 immarg 2) + #2, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[v2:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 2 +; CHECK: store i32 42, ptr %[[v2]], align 128 +; CHECK: ret void + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } +attributes #2 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 128} +!3 = !{!"foo", !4, i64 0, !4, i64 128} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-oob.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-oob.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-oob.ll @@ -0,0 +1,63 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that getelementptr.and.load unroll can skip 'inbounds' flag. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; void buz(struct foo *p) { +; p->b.bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr writeonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 false, i32 immarg 0, i32 immarg 1, i32 immarg 1) + #2, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[v2:.*]] = getelementptr %struct.foo, ptr %[[p]], i32 0, i32 1, i32 1 +; CHECK: store i32 42, ptr %[[v2]], align 4 +; CHECK: ret void + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } +attributes #2 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-u8.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-u8.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain-u8.ll @@ -0,0 +1,58 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check unroll of getelementptr.and.store when direct memory offset is +; used instead of field indexes. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; char aa; +; char bb; +; }; +; +; struct bar { +; char a; +; struct foo b; +; } __ctx; +; +; void buz(struct bar *p) { +; ((struct foo *)(((char*)&p->b) + 1))->bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + call void (i8, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i8 + (i8 42, + ptr writeonly elementtype(i8) %p, + i1 false, i8 0, i8 1, i8 0, i1 true, i64 immarg 3) + #2, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[v2:.*]] = getelementptr inbounds i8, ptr %[[p]], i64 3 +; CHECK: store i8 42, ptr %[[v2]], align 1 + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i8(i8, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } +attributes #2 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 1} +!3 = !{!"foo", !4, i64 0, !4, i64 1} +!4 = !{!"omnipotent char", !5, i64 0} +!5 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-chain.ll @@ -0,0 +1,64 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check unroll of getelementptr.and.store when several field indexes +; are specified in a chain. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct bar { +; int aa; +; int bb; +; }; +; +; struct foo { +; int a; +; struct bar b; +; } __ctx; +; +; void buz(struct foo *p) { +; p->b.bb = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, %struct.bar } +%struct.bar = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @buz(ptr noundef %p) #0 { +entry: + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr writeonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1, i32 immarg 1) + #2, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @buz(ptr noundef %[[p:.*]]) +; CHECK: %[[v2:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1, i32 1 +; CHECK: store i32 42, ptr %[[v2]], align 4 +; CHECK: ret void + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } +attributes #2 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 8} +!3 = !{!"foo", !4, i64 0, !7, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} +!7 = !{!"bar", !4, i64 0, !4, i64 4} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-simple.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-simple.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-simple.ll @@ -0,0 +1,57 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check unroll of getelementptr.and.store. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; int b; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p){ +; p->b = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr writeonly elementtype(%struct.foo) %p, + i1 false, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1) + #2, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: %[[v2:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1 +; CHECK: store i32 42, ptr %[[v2]], align 4 + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } +attributes #2 = { memory(argmem: write) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-volatile.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-volatile.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-undo-volatile.ll @@ -0,0 +1,57 @@ +; RUN: opt --bpf-check-and-opt-ir -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that unroll of getelementptr.and.store restores volatile. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; volatile int b; +; } __ctx; +; +; extern void consume(int); +; +; void bar(struct foo *p){ +; p->b = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=sroa,bpf-context-marker -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + call void (i32, ptr, i1, i8, i8, i8, i1, ...) + @llvm.bpf.getelementptr.and.store.i32 + (i32 42, + ptr elementtype(%struct.foo) %p, + i1 true, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1), + !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr noundef %[[p:.*]]) +; CHECK: entry: +; CHECK: %[[v2:.*]] = getelementptr inbounds %struct.foo, ptr %[[p]], i32 0, i32 1 +; CHECK: store volatile i32 42, ptr %[[v2]], align 4 + +; Function Attrs: nocallback nofree nounwind willreturn +declare void @llvm.bpf.getelementptr.and.store.i32(i32, ptr nocapture, i1 immarg, i8 immarg, i8 immarg, i8 immarg, i1 immarg, ...) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nounwind willreturn } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-unroll-inline.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-unroll-inline.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-unroll-inline.ll @@ -0,0 +1,104 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check position of bpf-context-marker pass in the pipeline: +; - context.marker.bpf call is preserved if address is passed as +; a parameter to an inline-able function; +; - second bpf-context-marker pass (after inlining) should introduce +; getelementptr.and.store call using the preserved marker after loops +; unrolling; +; - memory(argmem: readwrite) and tbaa attributes should allow +; removing one getelementptr.and.store call. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; int b[4]; +; } __ctx; +; +; static inline void bar(int * restrict p, unsigned long i) { +; p[0] = i; +; } +; +; void quux(struct foo *p){ +; unsigned long i = 0; +; #pragma clang loop unroll(full) +; while (i < 2) { +; bar(p->b, i); +; ++i; +; } +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, [4 x i32] } + +; Function Attrs: nounwind +define dso_local void @quux(ptr noundef %p) #0 { +entry: + br label %while.cond + +while.cond: ; preds = %while.body, %entry + %i.0 = phi i64 [ 0, %entry ], [ %inc, %while.body ] + %cmp = icmp ult i64 %i.0, 2 + br i1 %cmp, label %while.body, label %while.end + +while.body: ; preds = %while.cond + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + %arraydecay = getelementptr inbounds [4 x i32], ptr %b, i64 0, i64 0 + call void @bar(ptr noundef %arraydecay, i64 noundef %i.0) + %inc = add i64 %i.0, 1 + br label %while.cond, !llvm.loop !2 + +while.end: ; preds = %while.cond + ret void +} + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.start.p0(i64 immarg, ptr nocapture) #1 + +; Function Attrs: inlinehint nounwind +define internal void @bar(ptr noalias noundef %p, i64 noundef %i) #2 { +entry: + %conv = trunc i64 %i to i32 + %arrayidx = getelementptr inbounds i32, ptr %p, i64 0 + store i32 %conv, ptr %arrayidx, align 4, !tbaa !5 + ret void +} + +; CHECK: define dso_local void @quux(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK-NEXT: entry: +; CHECK-NEXT: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 1, +; CHECK-SAME: ptr writeonly elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 false, i8 0, i8 1, i8 2, i1 true, i64 immarg 0, i32 immarg 1) +; CHECK-NEXT: ret void + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #3 + +; Function Attrs: nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) +declare void @llvm.lifetime.end.p0(i64 immarg, ptr nocapture) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind willreturn memory(argmem: readwrite) } +attributes #2 = { inlinehint nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #3 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = distinct !{!2, !3, !4} +!3 = !{!"llvm.loop.mustprogress"} +!4 = !{!"llvm.loop.unroll.full"} +!5 = !{!6, !6, i64 0} +!6 = !{!"int", !7, i64 0} +!7 = !{!"omnipotent char", !8, i64 0} +!8 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-volatile.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-volatile.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-volatile.ll @@ -0,0 +1,56 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check handling of a volatile store instruction by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; volatile int b; +; } __ctx; +; +; void bar(struct foo *p) { +; p->b = 42; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32, i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %b = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 1 + store volatile i32 42, ptr %b, align 4, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef %[[p:.*]]) +; CHECK: tail call void (i32, ptr, i1, i8, i8, i8, i1, ...) +; CHECK-SAME: @llvm.bpf.getelementptr.and.store.i32 +; CHECK-SAME: (i32 42, +; CHECK-SAME: ptr elementtype(%struct.foo) %[[p]], +; CHECK-SAME: i1 true, i8 0, i8 1, i8 2, i1 true, i32 immarg 0, i32 immarg 1), +; CHECK-NOT: #{{[0-9]+}} +; CHECK-SAME: !tbaa + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 4} +!3 = !{!"foo", !4, i64 0, !4, i64 4} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"} diff --git a/llvm/test/CodeGen/BPF/decl-tag-ctx/store-zero.ll b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-zero.ll new file mode 100644 --- /dev/null +++ b/llvm/test/CodeGen/BPF/decl-tag-ctx/store-zero.ll @@ -0,0 +1,51 @@ +; RUN: opt -O2 -mtriple=bpf-pc-linux -S -o - %s | FileCheck %s +; +; Check that stores from zero offset are not modified by bpf-context-marker. +; +; Source: +; #define __ctx __attribute__((btf_decl_tag("ctx"))) +; +; struct foo { +; int a; +; } __ctx; +; +; void bar(struct foo *p) { +; p->a = 0; +; } +; +; Compilation flag: +; clang -cc1 -O2 -triple bpf -S -emit-llvm -disable-llvm-passes -o - \ +; | opt -passes=function(sroa) -S -o - + +%struct.foo = type { i32 } + +; Function Attrs: nounwind +define dso_local void @bar(ptr noundef %p) #0 { +entry: + %0 = call ptr @llvm.context.marker.bpf(ptr %p) + %a = getelementptr inbounds %struct.foo, ptr %0, i32 0, i32 0 + store i32 0, ptr %a, align 4, !tbaa !2 + ret void +} + +; CHECK: define dso_local void @bar(ptr nocapture noundef writeonly %[[p:.*]]) +; CHECK-NEXT: entry: +; CHECK-NEXT: store i32 0, ptr %[[p]], align 4, !tbaa +; CHECK-NEXT: ret void + +; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none) +declare ptr @llvm.context.marker.bpf(ptr nocapture readnone) #1 + +attributes #0 = { nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" } +attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) } + +!llvm.module.flags = !{!0} +!llvm.ident = !{!1} + +!0 = !{i32 1, !"wchar_size", i32 4} +!1 = !{!"clang version 17.0.0 (/home/eddy/work/llvm-project/clang 24793f24a0c419509b42ca4d86ec12f45f059b1f)"} +!2 = !{!3, !4, i64 0} +!3 = !{!"foo", !4, i64 0} +!4 = !{!"int", !5, i64 0} +!5 = !{!"omnipotent char", !6, i64 0} +!6 = !{!"Simple C/C++ TBAA"}