https://github.com/yonghong-song updated https://github.com/llvm/llvm-project/pull/232498
>From 581093c4d0a74b0cb01b0dd8ae1af69a5eb8aea2 Mon Sep 17 00:00:00 2001 From: Yonghong Song <[email protected]> Date: Thu, 15 Oct 2026 13:37:53 -0700 Subject: [PATCH] [BPF] Support preserve_access_index attribute for enum Currently, an enum value can only be relocated with the form __builtin_preserve_enum_value(*(enum E *)C, BPF_ENUMVAL_VALUE) or the libbpf macro bpf_core_enum_value(enum E, C). It is easy to forget to use it, and since most enum values are stable, a missing relocation is often not noticed until the program runs on a kernel with a different value. This patch allows the preserve_access_index attribute on enum declarations, in addition to struct and union declarations. If -g is specified, each use of an enumerator of such an enum whose value is computed at runtime becomes an ENUM_VALUE relocation, without any builtin. For example: enum cgroup_subsys_id { cpuset_cgrp_id = 0, cpu_cgrp_id = 1, memory_cgrp_id = 4, CGROUP_SUBSYS_COUNT = 10, } __attribute__((preserve_access_index)); struct css_set { void *subsys[CGROUP_SUBSYS_COUNT]; } __attribute__((preserve_access_index)); int is_valid(int id) { return id >= 0 && id < CGROUP_SUBSYS_COUNT; } void *get_mem_css(struct css_set *cset) { return cset->subsys[memory_cgrp_id]; } The generated code is: is_valid: w0 = 0 if w1 s< 0 goto +5 r2 = 10 ll CO-RE <enumval_value> enum cgroup_subsys_id::CGROUP_SUBSYS_COUNT = 10 w0 = 1 if w1 s< w2 goto +1 w0 = 0 exit get_mem_css: r2 = 0 CO-RE <byte_off> struct css_set::subsys (0:0) r1 += r2 r2 = 4 ll CO-RE <enumval_value> enum cgroup_subsys_id::memory_cgrp_id = 4 r2 <<= 32 r2 s>>= 29 r1 += r2 r0 = *(u64 *)(r1 + 0) exit Without the attribute on the enum, memory_cgrp_id is folded into the struct relocation, so the array index 4 is fixed: get_mem_css: r0 = *(u64 *)(r1 + 32) CO-RE <byte_off> struct css_set::subsys[4] (0:0:4) exit The attribute takes effect for - a named enum, e.g., enum cgroup_subsys_id above, - an anonymous enum with a typedef name, e.g., typedef enum { A = 1 } __attribute__((preserve_access_index)) T; - a named enum defined inside a struct or union with the attribute, which inherits the attribute as nested records do. An anonymous enum without a typedef name is not supported since libbpf matches relocations by type name. The attribute has no effect on it, and a -Wignored-attributes warning is issued if the attribute is written directly on the enum. An enumerator is relocated where its value is used at runtime, e.g., - in expressions, such as return values, function arguments, array indices and comparisons, - in conditions of if/switch/loops/?:/&&/|| statements and expressions, - in builtin calls which can be constant evaluated, - in initializers of local variables, including local aggregates, - through a local const variable or its members initialized with such an enumerator, e.g., k in const int k = memory_cgrp_id; if (k == 4) ... An enumerator is an integer constant expression, so clang codegen may fold an expression containing it before the enumerator itself is emitted. Such folding is disabled in the above cases. An enumerator keeps its compile-time value - where a compile-time constant is required, e.g., case labels, array sizes like subsys[CGROUP_SUBSYS_COUNT] above, bitfield widths, static assertions and inline asm "i" operands, - in initializers of global and static variables, including uses of a global or static const variable initialized with an enumerator, - in unevaluated operands, e.g., sizeof(memory_cgrp_id), - if -g is not specified. The enumerators are lowered to the same llvm.bpf.preserve.enum.value intrinsic as __builtin_preserve_enum_value(), so no backend or libbpf change is needed. __builtin_preserve_enum_value() works as before. Its intrinsic lowering is moved into a helper which is shared with the new case. Existing code is mostly not affected since bpftool-generated vmlinux.h only applies the attribute to records and never defines a named enum inside a record. To relocate enum values from vmlinux.h, bpftool can apply the attribute to enums as well. Since older clang rejects the attribute on enums, this needs to be guarded by the clang version: #ifndef BPF_NO_PRESERVE_ACCESS_INDEX #if defined(__clang__) && __clang_major__ >= 24 #pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = any(record, enum)) #else #pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = record) #endif #endif --- clang/include/clang/Basic/Attr.td | 2 +- clang/include/clang/Basic/AttrDocs.td | 11 + .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/include/clang/Sema/SemaBPF.h | 6 + clang/lib/CodeGen/CGBuiltin.cpp | 2 +- clang/lib/CodeGen/CGDecl.cpp | 3 +- clang/lib/CodeGen/CGExprAgg.cpp | 3 +- clang/lib/CodeGen/CGExprScalar.cpp | 12 +- clang/lib/CodeGen/CodeGenFunction.cpp | 54 +++++ clang/lib/CodeGen/CodeGenFunction.h | 14 ++ clang/lib/CodeGen/TargetBuiltins/ARM.cpp | 74 ++++--- clang/lib/Sema/Sema.cpp | 1 + clang/lib/Sema/SemaBPF.cpp | 23 ++- .../bpf-attr-preserve-access-index-enum.c | 192 ++++++++++++++++++ ...a-attribute-supported-attributes-list.test | 2 +- .../Sema/bpf-attr-preserve-access-index.c | 37 +++- 16 files changed, 397 insertions(+), 42 deletions(-) create mode 100644 clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index f8bd097733529a..b7eccfa414dc6a 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2576,7 +2576,7 @@ def AMDGPUAvailableVisible : StmtAttr, TargetSpecificAttr<TargetAMDGPU> { def BPFPreserveAccessIndex : InheritableAttr, TargetSpecificAttr<TargetBPF> { let Spellings = [Clang<"preserve_access_index">]; - let Subjects = SubjectList<[Record], ErrorDiag>; + let Subjects = SubjectList<[Record, Enum], ErrorDiag>; let Documentation = [BPFPreserveAccessIndexDocs]; let LangOpts = [COnly]; } diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index e8ce3c3dea7f3b..5643dc50320078 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3073,6 +3073,17 @@ attribute for the BPF target. This attribute may be attached to a struct or union declaration, where if -g is specified, it enables preserving struct or union member access debuginfo indices of this struct or union, similar to clang `__builtin_preserve_access_index()`. + +The attribute may also be attached to an enum declaration. If -g is +specified, each use of an enumerator of this enum whose value is computed +at runtime is replaced by an `ENUM_VALUE` CO-RE relocation, similar to +`__builtin_preserve_enum_value(*(enum_type *)enumerator, 1)`, so that the +bpf loader may adjust the value based on the target kernel. Such uses are +not constant folded. Uses that require a compile-time constant, e.g., +case labels, array sizes, bitfield widths, static assertions and global +or static variable initializers, keep the compile-time value. The enum +must be named or have a typedef name; otherwise, the attribute has no +effect and a warning is issued. }]; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 3e8ba5bf88a42b..1dcafdfb5f0eaa 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13545,6 +13545,9 @@ def err_preserve_enum_value_invalid : Error< "__builtin_preserve_enum_value argument %0 invalid">; def err_preserve_enum_value_not_const: Error< "__builtin_preserve_enum_value argument %0 not a constant">; +def warn_preserve_access_index_anon_enum : Warning< + "%0 attribute has no effect on an anonymous enum without a typedef name">, + InGroup<IgnoredAttributes>; def err_bit_cast_non_trivially_copyable : Error< "'__builtin_bit_cast' %select{source|destination}0 type must be trivially copyable">; diff --git a/clang/include/clang/Sema/SemaBPF.h b/clang/include/clang/Sema/SemaBPF.h index d7577aea701227..dd2644ba5619c0 100644 --- a/clang/include/clang/Sema/SemaBPF.h +++ b/clang/include/clang/Sema/SemaBPF.h @@ -15,6 +15,7 @@ #include "clang/AST/ASTFwd.h" #include "clang/Sema/SemaBase.h" +#include "llvm/ADT/MapVector.h" namespace clang { class ParsedAttr; @@ -27,6 +28,11 @@ class SemaBPF : public SemaBase { void handlePreserveAIRecord(RecordDecl *RD); void handlePreserveAccessIndexAttr(Decl *D, const ParsedAttr &AL); + void DiagnoseAnonymousPreserveAIEnums(); + +private: + llvm::MapVector<EnumDecl *, const BPFPreserveAccessIndexAttr *> + AnonPreserveAIEnums; }; } // namespace clang diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp index c93375ff3a18ef..dd54a83b03404a 100644 --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -2725,7 +2725,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID, // TODO: Extend this handling to all builtin calls that we can constant-fold. Expr::EvalResult Result; if (E->isPRValue() && E->EvaluateAsRValue(Result, CGM.getContext()) && - !Result.hasSideEffects()) { + !Result.hasSideEffects() && !containsBPFPreservedEnumerator(E)) { if (Result.Val.isInt()) return RValue::get(llvm::ConstantInt::get(getLLVMContext(), Result.Val.getInt())); diff --git a/clang/lib/CodeGen/CGDecl.cpp b/clang/lib/CodeGen/CGDecl.cpp index 8862f573014126..98c26b1ae80ac7 100644 --- a/clang/lib/CodeGen/CGDecl.cpp +++ b/clang/lib/CodeGen/CGDecl.cpp @@ -1538,7 +1538,8 @@ CodeGenFunction::EmitAutoVarAlloca(const VarDecl &D) { if (D.getInit() && (Ty->isArrayType() || Ty->isRecordType()) && (D.isConstexpr() || ((Ty.isPODType(getContext()) || - getContext().getBaseElementType(Ty)->isObjCObjectPointerType())))) { + getContext().getBaseElementType(Ty)->isObjCObjectPointerType()))) && + !containsBPFPreservedEnumerator(D.getInit())) { emission.ConstantAggregateInitializer = ConstantEmitter(*this).tryEmitAbstractForInitializer(D); if (emission.ConstantAggregateInitializer) { diff --git a/clang/lib/CodeGen/CGExprAgg.cpp b/clang/lib/CodeGen/CGExprAgg.cpp index 73c6301c5d585b..963229dee95066 100644 --- a/clang/lib/CodeGen/CGExprAgg.cpp +++ b/clang/lib/CodeGen/CGExprAgg.cpp @@ -581,7 +581,8 @@ void AggExprEmitter::EmitArrayInit(Address DestPtr, llvm::ArrayType *AType, // more efficient than per-element initialization, the size of the elements // with explicit initializers should be large enough. if (NumInitElements * elementSize.getQuantity() > 16 && - elementType.isTriviallyCopyableType(CGF.getContext())) { + elementType.isTriviallyCopyableType(CGF.getContext()) && + !CGF.containsBPFPreservedEnumerator(ExprToVisit)) { CodeGen::CodeGenModule &CGM = CGF.CGM; ConstantEmitter Emitter(CGF); QualType GVArrayQTy = CGM.getContext().getAddrSpaceQualType( diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index f1394fc5246459..2eca122b1cac80 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -608,8 +608,11 @@ class ScalarExprEmitter // l-values. Value *VisitDeclRefExpr(DeclRefExpr *E) { - if (CodeGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E)) - return CGF.emitScalarConstant(Constant, E); + if (Value *V = CGF.EmitBPFPreservedEnumerator(E)) + return V; + if (!CGF.containsBPFPreservedEnumerator(E)) + if (CodeGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E)) + return CGF.emitScalarConstant(Constant, E); return EmitLoadOfLValue(E); } @@ -2144,7 +2147,10 @@ Value *ScalarExprEmitter::VisitConvertVectorExpr(ConvertVectorExpr *E) { } Value *ScalarExprEmitter::VisitMemberExpr(MemberExpr *E) { - if (CodeGenFunction::ConstantEmission Constant = CGF.tryEmitAsConstant(E)) { + if (CGF.containsBPFPreservedEnumerator(E)) { + // Do not constant fold. + } else if (CodeGenFunction::ConstantEmission Constant = + CGF.tryEmitAsConstant(E)) { CGF.EmitIgnoredExpr(E->getBase()); return CGF.emitScalarConstant(Constant, E); } else { diff --git a/clang/lib/CodeGen/CodeGenFunction.cpp b/clang/lib/CodeGen/CodeGenFunction.cpp index e5bca359e6a855..dcef9e84055676 100644 --- a/clang/lib/CodeGen/CodeGenFunction.cpp +++ b/clang/lib/CodeGen/CodeGenFunction.cpp @@ -1780,6 +1780,57 @@ bool CodeGenFunction::mightAddDeclToScope(const Stmt *S) { return false; } +QualType CodeGenFunction::getBPFPreservedEnumType(const DeclRefExpr *E) { + const auto *Enumerator = dyn_cast<EnumConstantDecl>(E->getDecl()); + if (!Enumerator || !getTarget().getTriple().isBPF() || !getDebugInfo()) + return QualType(); + + // In C, the type of an enumerator may not be the enum type, so get the + // enum from the declaration context. + const auto *ED = dyn_cast<EnumDecl>(Enumerator->getDeclContext()); + if (!ED || !ED->hasAttr<BPFPreserveAccessIndexAttr>()) + return QualType(); + + // The relocation is matched by type name, so an anonymous enum is + // only supported through its typedef name. + if (ED->getDeclName()) + return getContext().getCanonicalTagType(ED); + if (const TypedefNameDecl *TD = ED->getTypedefNameForAnonDecl()) + return getContext().getTypeDeclType(TD); + return QualType(); +} + +bool CodeGenFunction::containsBPFPreservedEnumerator(const Expr *E) { + if (!getTarget().getTriple().isBPF() || !getDebugInfo()) + return false; + + llvm::SmallPtrSet<const VarDecl *, 4> Visited; + auto Contains = [this, &Visited](auto &&Self, const Stmt *S) -> bool { + if (!S) + return false; + // A ConstantExpr is always emitted as a constant, and the enumerators + // in an unevaluated operand do not contribute their values. + if (isa<ConstantExpr, UnaryExprOrTypeTraitExpr>(S)) + return false; + if (const auto *DRE = dyn_cast<DeclRefExpr>(S)) { + if (!getBPFPreservedEnumType(DRE).isNull()) + return true; + // A local const variable may be constant folded through its + // initializer. + const auto *VD = dyn_cast<VarDecl>(DRE->getDecl()); + if (VD && VD->hasLocalStorage() && VD->getType().isConstQualified() && + Visited.insert(VD).second) + return Self(Self, VD->getInit()); + return false; + } + for (const Stmt *SubStmt : S->children()) + if (Self(Self, SubStmt)) + return true; + return false; + }; + return Contains(Contains, E); +} + /// ConstantFoldsToSimpleInteger - If the specified expression does not fold /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the boolean result in Result. @@ -1813,6 +1864,9 @@ bool CodeGenFunction::ConstantFoldsToSimpleInteger(const Expr *Cond, if (!Cond->EvaluateAsInt(Result, getContext())) return false; // Not foldable, not integer or not fully evaluatable. + if (containsBPFPreservedEnumerator(Cond)) + return false; + llvm::APSInt Int = Result.Val.getInt(); if (!AllowLabels && CodeGenFunction::ContainsLabel(Cond)) return false; // Contains a label. diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h index 73090c03484b73..279103f9b35011 100644 --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -5041,6 +5041,10 @@ class CodeGenFunction : public CodeGenTypeCache { llvm::Value *EmitAArch64BuiltinExpr(unsigned BuiltinID, const CallExpr *E, llvm::Triple::ArchType Arch); llvm::Value *EmitBPFBuiltinExpr(unsigned BuiltinID, const CallExpr *E); + llvm::Value *EmitBPFPreserveEnumValue(const EnumConstantDecl *Enumerator, + QualType EnumTy, SourceLocation Loc, + uint64_t Flag); + llvm::Value *EmitBPFPreservedEnumerator(const DeclRefExpr *E); llvm::Value *BuildVector(ArrayRef<llvm::Value *> Ops); llvm::Value *EmitX86BuiltinExpr(unsigned BuiltinID, const CallExpr *E); @@ -5372,6 +5376,16 @@ class CodeGenFunction : public CodeGenTypeCache { /// current scope, by being a (possibly-labelled) DeclStmt. static bool mightAddDeclToScope(const Stmt *S); + /// For the BPF target, if E refers to an enumerator of an enum with the + /// preserve_access_index attribute, return the enum type to be used for + /// its CO-RE relocation. Otherwise, return a null type. + QualType getBPFPreservedEnumType(const DeclRefExpr *E); + + /// For the BPF target, return true if the expression contains an + /// enumerator to be relocated, directly or through the initializer of a + /// local const variable. Such an expression should not be constant folded. + bool containsBPFPreservedEnumerator(const Expr *E); + /// ConstantFoldsToSimpleInteger - If the specified expression does not fold /// to a constant, or if it does but contains a label, return false. If it /// constant folds return true and set the boolean result in Result. diff --git a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp index 4a7bdba6d917ee..0cb239c6da3bd0 100644 --- a/clang/lib/CodeGen/TargetBuiltins/ARM.cpp +++ b/clang/lib/CodeGen/TargetBuiltins/ARM.cpp @@ -7287,6 +7287,51 @@ Value *CodeGenFunction::EmitAArch64BuiltinExpr(unsigned BuiltinID, } } +// A sequence number, injected into IR builtin functions, to +// prevent CSE given the only difference of the function +// may just be the debuginfo metadata. +static uint32_t BPFBuiltinSeqNum; + +Value * +CodeGenFunction::EmitBPFPreserveEnumValue(const EnumConstantDecl *Enumerator, + QualType EnumTy, SourceLocation Loc, + uint64_t Flag) { + llvm::DIType *DbgInfo = + getDebugInfo()->getOrCreateStandaloneType(EnumTy, Loc); + + auto InitVal = Enumerator->getInitVal(); + std::string InitValStr; + if (InitVal.isNegative() || InitVal > uint64_t(INT64_MAX)) + InitValStr = std::to_string(InitVal.getSExtValue()); + else + InitValStr = std::to_string(InitVal.getZExtValue()); + std::string EnumStr = Enumerator->getNameAsString() + ":" + InitValStr; + Value *EnumStrVal = Builder.CreateGlobalString(EnumStr); + + Value *FlagValue = ConstantInt::get(Int64Ty, Flag); + Value *SeqNumVal = ConstantInt::get(Int32Ty, BPFBuiltinSeqNum++); + + llvm::Function *IntrinsicFn = Intrinsic::getOrInsertDeclaration( + &CGM.getModule(), Intrinsic::bpf_preserve_enum_value, {}); + CallInst *Fn = + Builder.CreateCall(IntrinsicFn, {SeqNumVal, EnumStrVal, FlagValue}); + Fn->setMetadata(LLVMContext::MD_preserve_access_index, DbgInfo); + return Fn; +} + +Value *CodeGenFunction::EmitBPFPreservedEnumerator(const DeclRefExpr *E) { + QualType EnumTy = getBPFPreservedEnumType(E); + if (EnumTy.isNull()) + return nullptr; + + // Flag 1 is ENUM_VALUE. + Value *V = EmitBPFPreserveEnumValue(cast<EnumConstantDecl>(E->getDecl()), + EnumTy, E->getExprLoc(), 1); + QualType Ty = E->getType(); + return Builder.CreateIntCast(V, ConvertType(Ty), + Ty->isSignedIntegerOrEnumerationType()); +} + Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned BuiltinID, const CallExpr *E) { assert((BuiltinID == BPF::BI__builtin_preserve_field_info || @@ -7295,11 +7340,6 @@ Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned BuiltinID, BuiltinID == BPF::BI__builtin_preserve_enum_value) && "unexpected BPF builtin"); - // A sequence number, injected into IR builtin functions, to - // prevent CSE given the only difference of the function - // may just be the debuginfo metadata. - static uint32_t BuiltinSeqNum; - switch (BuiltinID) { default: llvm_unreachable("Unexpected BPF builtin"); @@ -7343,7 +7383,7 @@ Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned BuiltinID, ConstantInt *Flag = cast<ConstantInt>(EmitScalarExpr(E->getArg(1))); Value *FlagValue = ConstantInt::get(Int64Ty, Flag->getSExtValue()); - Value *SeqNumVal = ConstantInt::get(Int32Ty, BuiltinSeqNum++); + Value *SeqNumVal = ConstantInt::get(Int32Ty, BPFBuiltinSeqNum++); llvm::Function *FnDecl; if (BuiltinID == BPF::BI__builtin_btf_type_id) @@ -7363,8 +7403,6 @@ Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned BuiltinID, } const Expr *Arg0 = E->getArg(0); - llvm::DIType *DbgInfo = getDebugInfo()->getOrCreateStandaloneType( - Arg0->getType(), Arg0->getExprLoc()); // Find enumerator const auto *UO = cast<UnaryOperator>(Arg0->IgnoreParens()); @@ -7372,25 +7410,9 @@ Value *CodeGenFunction::EmitBPFBuiltinExpr(unsigned BuiltinID, const auto *DR = cast<DeclRefExpr>(CE->getSubExpr()); const auto *Enumerator = cast<EnumConstantDecl>(DR->getDecl()); - auto InitVal = Enumerator->getInitVal(); - std::string InitValStr; - if (InitVal.isNegative() || InitVal > uint64_t(INT64_MAX)) - InitValStr = std::to_string(InitVal.getSExtValue()); - else - InitValStr = std::to_string(InitVal.getZExtValue()); - std::string EnumStr = Enumerator->getNameAsString() + ":" + InitValStr; - Value *EnumStrVal = Builder.CreateGlobalString(EnumStr); - ConstantInt *Flag = cast<ConstantInt>(EmitScalarExpr(E->getArg(1))); - Value *FlagValue = ConstantInt::get(Int64Ty, Flag->getSExtValue()); - Value *SeqNumVal = ConstantInt::get(Int32Ty, BuiltinSeqNum++); - - llvm::Function *IntrinsicFn = Intrinsic::getOrInsertDeclaration( - &CGM.getModule(), Intrinsic::bpf_preserve_enum_value, {}); - CallInst *Fn = - Builder.CreateCall(IntrinsicFn, {SeqNumVal, EnumStrVal, FlagValue}); - Fn->setMetadata(LLVMContext::MD_preserve_access_index, DbgInfo); - return Fn; + return EmitBPFPreserveEnumValue(Enumerator, Arg0->getType(), + Arg0->getExprLoc(), Flag->getSExtValue()); } } } diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index bae41a6e6f5cf4..935930ada44fdc 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -1335,6 +1335,7 @@ void Sema::ActOnEndOfTranslationUnit() { DiagnoseUnterminatedPragmaAlignPack(); DiagnoseUnterminatedPragmaAttribute(); + BPF().DiagnoseAnonymousPreserveAIEnums(); OpenMP().DiagnoseUnterminatedOpenMPDeclareTarget(); DiagnosePrecisionLossInComplexDivision(); DiagnoseUnusedAPINotesSelectors(); diff --git a/clang/lib/Sema/SemaBPF.cpp b/clang/lib/Sema/SemaBPF.cpp index a9761764bbdc67..8f7183ab73aa67 100644 --- a/clang/lib/Sema/SemaBPF.cpp +++ b/clang/lib/Sema/SemaBPF.cpp @@ -179,10 +179,25 @@ void SemaBPF::handlePreserveAIRecord(RecordDecl *RD) { } void SemaBPF::handlePreserveAccessIndexAttr(Decl *D, const ParsedAttr &AL) { - auto *Rec = cast<RecordDecl>(D); - handlePreserveAIRecord(Rec); - Rec->addAttr(::new (getASTContext()) - BPFPreserveAccessIndexAttr(getASTContext(), AL)); + if (auto *Rec = dyn_cast<RecordDecl>(D)) + handlePreserveAIRecord(Rec); + auto *A = + ::new (getASTContext()) BPFPreserveAccessIndexAttr(getASTContext(), AL); + // The relocation of an enumerator is matched by type name. Whether an + // anonymous enum gets a typedef name is checked at the end of the + // translation unit. Skip #pragma clang attribute which may cover many + // anonymous enums in a header. + if (auto *ED = dyn_cast<EnumDecl>(D); + ED && !ED->getDeclName() && !AL.isPragmaClangAttribute()) + AnonPreserveAIEnums.insert({ED, A}); + D->addAttr(A); +} + +void SemaBPF::DiagnoseAnonymousPreserveAIEnums() { + for (auto [ED, A] : AnonPreserveAIEnums) + if (!ED->getTypedefNameForAnonDecl()) + Diag(A->getLocation(), diag::warn_preserve_access_index_anon_enum) << A; + AnonPreserveAIEnums.clear(); } } // namespace clang diff --git a/clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c b/clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c new file mode 100644 index 00000000000000..4293dee04d1466 --- /dev/null +++ b/clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c @@ -0,0 +1,192 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang_cc1 -triple bpf -emit-llvm -debug-info-kind=limited -disable-llvm-passes -verify %s -o - | FileCheck %s + +#define __reloc__ __attribute__((preserve_access_index)) + +enum AA { VAL1 = -100, VAL2 = 0xffff8000 }; +typedef enum { VAL10 = 0xffffFFFF80000000 } __BB; +enum e1 { E1_A = 1, E1_C = 10 } __reloc__; +typedef enum { E2_A = 3 } __reloc__ e2; +enum { E3_A = 4 } __reloc__; // expected-warning {{attribute has no effect on an anonymous enum without a typedef name}} +struct s { + enum e4 { E4_A = 5 } a; +} __reloc__; +enum s64 { S64_BIG = 0x100000000, S64_NEG = -5 } __reloc__; +enum u64 { U64_BIG = 0xffffffff80000000 } __reloc__; + +// Contexts requiring a constant keep the compile-time value. +int g1 = E1_C; +int g2[E1_C]; +_Static_assert(E1_A == 1, ""); + +// CHECK: @g1 = global i32 10 +// CHECK: @[[VAL1:[0-9]+]] = private unnamed_addr constant [10 x i8] c"VAL1:-100\00" +// CHECK: @[[VAL2:[0-9]+]] = private unnamed_addr constant [16 x i8] c"VAL2:4294934528\00" +// CHECK: @[[VAL10:[0-9]+]] = private unnamed_addr constant [18 x i8] c"VAL10:-2147483648\00" +// CHECK: @[[E1_A:[0-9]+]] = private unnamed_addr constant [7 x i8] c"E1_A:1\00" +// CHECK: @[[E1_C:[0-9]+]] = private unnamed_addr constant [8 x i8] c"E1_C:10\00" +// CHECK: @[[E2_A:[0-9]+]] = private unnamed_addr constant [7 x i8] c"E2_A:3\00" +// CHECK: @[[E4_A:[0-9]+]] = private unnamed_addr constant [7 x i8] c"E4_A:5\00" +// CHECK: @[[S64_BIG:[0-9]+]] = private unnamed_addr constant [19 x i8] c"S64_BIG:4294967296\00" +// CHECK: @[[S64_NEG:[0-9]+]] = private unnamed_addr constant [11 x i8] c"S64_NEG:-5\00" +// CHECK: @[[U64_BIG:[0-9]+]] = private unnamed_addr constant [20 x i8] c"U64_BIG:-2147483648\00" +// CHECK: @g2 = global [10 x i32] zeroinitializer + +// __builtin_preserve_enum_value() is not affected by the attribute. +int test1(void) { + return __builtin_preserve_enum_value(*(enum AA *)VAL1, 0) + + __builtin_preserve_enum_value(*(enum AA *)VAL2, 1) + + __builtin_preserve_enum_value(*(__BB *)VAL10, 1) + + __builtin_preserve_enum_value(*(enum e1 *)E1_A, 0); +} + +// CHECK: define dso_local i32 @test1 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 0, ptr @[[VAL1]], i64 0), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[AA:[0-9]+]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 1, ptr @[[VAL2]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[AA]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 2, ptr @[[VAL10]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[BB:[0-9]+]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 3, ptr @[[E1_A]], i64 0), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1:[0-9]+]] +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: ret i32 + +// An enumerator of an attributed enum is an ENUM_VALUE relocation, with or +// without __builtin_preserve_access_index(). +int test2(int x) { + return (x == E1_C) + __builtin_preserve_access_index(E2_A) + (E4_A); +} + +// CHECK: define dso_local i32 @test2 +// CHECK: [[V:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[E1_C]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: [[T:%[0-9a-z]+]] = trunc i64 [[V]] to i32 +// CHECK: icmp eq i32 %{{[0-9a-z]+}}, [[T]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[E2_A]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E2:[0-9]+]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[E4_A]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E4:[0-9]+]] + +// Conditions and builtin calls using the enumerators are not folded. +int test3(int x) { + if (E1_C == 10) + x++; + while (x < E1_C) + x += E1_C > 5 ? 2 : 3; + switch (E1_A) { + case E1_A: // Case labels keep the compile-time value. + x++; + break; + default: + break; + } + return x + __builtin_bswap32(E1_C); +} + +// CHECK: define dso_local i32 @test3 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: icmp eq i32 %{{[0-9a-z]+}}, 10 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: icmp slt i32 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: icmp sgt i32 %{{[0-9a-z]+}}, 5 +// CHECK: select i1 +// CHECK: [[V:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: [[T:%[0-9a-z]+]] = trunc i64 [[V]] to i32 +// CHECK: switch i32 [[T]], label %{{.*}} [ +// CHECK-NEXT: i32 1, label +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: call i32 @llvm.bswap.i32 + +// Local aggregates initialized with the enumerators are not constant +// initialized. +struct key { int a; int b; }; +void use(void *); +void test4(void) { + struct key k = { E1_A, 2 }; + long arr[4] = { 1, 2, 3, E1_C }; + use(&k); + use(arr); +} + +// CHECK: define dso_local void @test4 +// CHECK-NOT: memcpy +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK-NOT: memcpy +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK-NOT: memcpy +// CHECK: ret void + +// Enumerators of an enum without the attribute, of an anonymous enum without +// typedef name, and in unevaluated operands keep their compile-time value. +int test5(void) { + static int s = E1_C; + return s + VAL1 + E3_A + sizeof(E1_C); +} + +// CHECK: define dso_local i32 @test5 +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: add nsw i64 %{{[0-9a-z]+}}, -100 +// CHECK-NEXT: add nsw i64 %{{[0-9a-z]+}}, 4 +// CHECK-NEXT: add i64 %{{[0-9a-z]+}}, 4 +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: ret i32 + +// Enumerators with 64-bit values are relocated without truncation. +long test6(void) { + return S64_BIG + S64_NEG; +} + +// CHECK: define dso_local i64 @test6 +// CHECK: [[V1:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[S64_BIG]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[S64:[0-9]+]] +// CHECK: [[V2:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[S64_NEG]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[S64]] +// CHECK: [[ADD:%[0-9a-z]+]] = add nsw i64 [[V1]], [[V2]] +// CHECK: ret i64 [[ADD]] + +unsigned long test7(void) { + return U64_BIG; +} + +// CHECK: define dso_local i64 @test7 +// CHECK: [[V:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @[[U64_BIG]], i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[U64:[0-9]+]] +// CHECK: ret i64 [[V]] + +// Local const variables initialized with the enumerators are not constant +// folded. Global and static local ones keep the compile-time value. +const int g3 = E1_C; +int test8(int x) { + const int k = E1_C; + const int k2 = k + 1; + const struct key kk = { E1_A, 2 }; + static const int s = E1_C; + if (k2 == 11) + x++; + return x + k + kk.a + s + g3; +} + +// CHECK: define dso_local i32 @test8 +// CHECK: [[V:%[0-9a-z]+]] = call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: [[T:%[0-9a-z]+]] = trunc i64 [[V]] to i32 +// CHECK: store i32 [[T]], ptr %k, +// CHECK: [[K:%[0-9a-z]+]] = load i32, ptr %k, +// CHECK: add nsw i32 [[K]], 1 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: [[K2:%[0-9a-z]+]] = load i32, ptr %k2, +// CHECK: icmp eq i32 [[K2]], 11 +// CHECK: load i32, ptr %k, +// CHECK: load i32, ptr %a{{[0-9]+}}, +// CHECK: add nsw i32 %{{[0-9a-z]+}}, 10 +// CHECK-NEXT: add nsw i32 %{{[0-9a-z]+}}, 10 +// CHECK: ret i32 + +// A self-referencing initializer is handled. +int test9(void) { + const int k = k + E1_C; + return k; +} + +// CHECK: define dso_local i32 @test9 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: ret i32 + +// CHECK-DAG: ![[AA]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "AA" +// CHECK-DAG: ![[BB]] = !DIDerivedType(tag: DW_TAG_typedef, name: "__BB" +// CHECK-DAG: ![[E1]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "e1" +// CHECK-DAG: ![[E2]] = !DIDerivedType(tag: DW_TAG_typedef, name: "e2" +// CHECK-DAG: ![[E4]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "e4" +// CHECK-DAG: ![[S64]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "s64"{{.*}}, size: 64, +// CHECK-DAG: ![[U64]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "u64"{{.*}}, size: 64, diff --git a/clang/test/Misc/pragma-attribute-supported-attributes-list.test b/clang/test/Misc/pragma-attribute-supported-attributes-list.test index 8f8c1aa3d3f9cd..a228186ea9fdb2 100644 --- a/clang/test/Misc/pragma-attribute-supported-attributes-list.test +++ b/clang/test/Misc/pragma-attribute-supported-attributes-list.test @@ -23,7 +23,7 @@ // CHECK-NEXT: Availability ((SubjectMatchRule_record, SubjectMatchRule_enum, SubjectMatchRule_enum_constant, SubjectMatchRule_field, SubjectMatchRule_function, SubjectMatchRule_namespace, SubjectMatchRule_objc_category, SubjectMatchRule_objc_implementation, SubjectMatchRule_objc_interface, SubjectMatchRule_objc_method, SubjectMatchRule_objc_property, SubjectMatchRule_objc_protocol, SubjectMatchRule_record, SubjectMatchRule_type_alias, SubjectMatchRule_variable)) // CHECK-NEXT: AvailableOnlyInDefaultEvalMethod (SubjectMatchRule_type_alias) // CHECK-NEXT: BPFFastCall (SubjectMatchRule_hasType_functionType) -// CHECK-NEXT: BPFPreserveAccessIndex (SubjectMatchRule_record) +// CHECK-NEXT: BPFPreserveAccessIndex (SubjectMatchRule_record, SubjectMatchRule_enum) // CHECK-NEXT: BPFPreserveStaticOffset (SubjectMatchRule_record) // CHECK-NEXT: BTFDeclTag (SubjectMatchRule_variable, SubjectMatchRule_function, SubjectMatchRule_record, SubjectMatchRule_field, SubjectMatchRule_type_alias) // CHECK-NEXT: BuiltinAlias (SubjectMatchRule_function) diff --git a/clang/test/Sema/bpf-attr-preserve-access-index.c b/clang/test/Sema/bpf-attr-preserve-access-index.c index 34b8c917febf54..71f81dd5289ae5 100644 --- a/clang/test/Sema/bpf-attr-preserve-access-index.c +++ b/clang/test/Sema/bpf-attr-preserve-access-index.c @@ -36,13 +36,42 @@ struct p { int a; }; -int a __reloc__; // expected-error {{'preserve_access_index' attribute only applies to structs, unions, and classes}} -struct s *p __reloc__; // expected-error {{'preserve_access_index' attribute only applies to structs, unions, and classes}} +int a __reloc__; // expected-error {{'preserve_access_index' attribute only applies to structs, unions, classes, and enums}} +struct s *p __reloc__; // expected-error {{'preserve_access_index' attribute only applies to structs, unions, classes, and enums}} -void invalid1(const int __reloc__ *arg) {} // expected-error {{'preserve_access_index' attribute only applies to structs, unions, and classes}} -void invalid2(void) { const int __reloc__ *arg; } // expected-error {{'preserve_access_index' attribute only applies to structs, unions, and classes}} +void invalid1(const int __reloc__ *arg) {} // expected-error {{'preserve_access_index' attribute only applies to structs, unions, classes, and enums}} +void invalid2(void) { const int __reloc__ *arg; } // expected-error {{'preserve_access_index' attribute only applies to structs, unions, classes, and enums}} int valid3(struct t4 *arg) { return arg->a + arg->b[3] + arg->c + arg->d; } int valid4(void *arg) { struct local_t { int a; int b; } __reloc__; return ((struct local_t *)arg)->b; } + +enum e1 { E1_A, E1_B } __reloc__; +enum __reloc__ e2 { E2_A }; +typedef enum { E3_A } __reloc__ e3; +enum e4 { E4_A __reloc__ }; // expected-error {{'preserve_access_index' attribute only applies to structs, unions, classes, and enums}} +int valid5(void) { return E1_B + E2_A + E3_A; } + +// An anonymous enum without typedef name cannot be relocated. +enum { A1 } __reloc__; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum without a typedef name}} +enum { A2 } __reloc__ v2; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} +typedef enum { A3 } __reloc__ *t3p; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} +struct s4 { enum { A4 } __reloc__ f; }; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} +enum __reloc__ { A9 } __reloc__; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = enum) +enum { A10 } __reloc__; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} +#pragma clang attribute pop +int invalid3(void) { + enum { A5 } __reloc__; // expected-warning {{'preserve_access_index' attribute has no effect on an anonymous enum}} + return A1 + A2 + A3 + A4 + A5; +} + +// No warning with a typedef name, an attribute implicitly added from the +// enclosing record, or #pragma clang attribute. +typedef enum __reloc__ { A6 } t6; +struct s7 { enum { A7 } f; } __reloc__; +#pragma clang attribute push (__attribute__((preserve_access_index)), apply_to = enum) +enum { A8 }; +#pragma clang attribute pop +int valid6(void) { return A6 + A7 + A8; } _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
