https://github.com/yonghong-song updated https://github.com/llvm/llvm-project/pull/232498
>From dbc6f337ff16be345d8a695de1d1639a2126d2db Mon Sep 17 00:00:00 2001 From: Yonghong Song <[email protected]> Date: Fri, 23 Oct 2026 19:36:35 -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[10]; } __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 +6 w1 = w1 r2 = 10 ll CO-RE <enumval_value> enum cgroup_subsys_id::CGROUP_SUBSYS_COUNT = 10 w0 = 1 if r2 > r1 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 <<= 3 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 Supported enums --------------- 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. If the attribute is written directly on such an enum, an error is issued by default, consistent with other misuses of the attribute: enum { A1 } __attribute__((preserve_access_index)); error: 'preserve_access_index' attribute has no effect on an anonymous enum without a typedef name [-Wignored-attributes] It can be downgraded to a warning with -Wno-error=ignored-attributes. There is no diagnostic if such an enum gets the attribute implicitly from an enclosing struct or union, or from #pragma clang attribute, so existing annotated records and headers with many anonymous enums like vmlinux.h are not affected. Implementation -------------- This is implemented in Sema. If -g is specified, when a reference to an enumerator of such an enum is built, it is replaced by a PseudoObjectExpr whose syntactic form is the original DeclRefExpr and whose semantic form is __builtin_preserve_enum_value(*(enum_type *)enumerator, BPF_ENUMVAL_VALUE) The builtin call is built as if it were written in the source, so it is handled the same as an explicit call. The AST is printed as before, and codegen lowers it to the same llvm.bpf.preserve.enum.value intrinsic as an explicit builtin call. No backend or libbpf change is needed. Like an explicit builtin call, a relocated enumerator has type unsigned long rather than its C type, normally int, so a 64-bit enum value is not truncated even if the local value fits in int. So the surrounding arithmetic is unsigned 64-bit, e.g., with an int v, v < X is an unsigned 64-bit compare, as in is_valid() above, and -Wsign-compare warns: warning: comparison of integers of different signs: 'int' and 'unsigned long' [-Wsign-compare] In the argument of an explicit __builtin_preserve_enum_value() or libbpf bpf_core_enum_value(), i.e., *(enum_type *)enumerator, the enumerator refers to the enumerator itself rather than its value. When the builtin call is checked, the original DeclRefExpr is put back as the operand of the cast, so such builtin calls work as before with the same AST. Any other cast, e.g., (enum_type *)enumerator outside of the builtin, is relocated as other uses. Not a constant expression ------------------------- Since the relocated value is only known at load time, such a reference is not a constant expression, as an explicit builtin call never was. It is relocated wherever its value is used at runtime, and no codegen constant folding is possible, including through a local const variable initialized with it. With -g, an enumerator of an enum with the attribute is accepted and rejected in the same contexts as the equivalent explicit builtin call, with the same diagnostics. For example, both are rejected where a compile-time constant is required, e.g., in case labels, bitfield widths, static assertions, array sizes at file scope or in a struct, and initializers of global or static variables: int via_case_label(int value) { switch (value) { case memory_cgrp_id: return 1; } return 0; } error: expression is not an integer constant expression The new test clang/test/Sema/bpf-preserve-enum-value-parity.c checks that both forms get the same diagnostics, in C17 and C23. Differences from __builtin_preserve_enum_value() ------------------------------------------------ The differences between an enumerator A of an enum with the attribute, e.g., enum E { A = 4 } __attribute__((preserve_access_index)), and an explicit __builtin_preserve_enum_value(*(enum E *)A, 1) call are: - Without -g, an explicit builtin call is an error, while the attribute has no effect and enumerators are still constants, as with the attribute on a struct or union. - In the definition of the enum itself, e.g., B = A + 1, the enumerator A is not relocated and is still a constant, while an explicit builtin call is rejected: error: expression is not an integer constant expression - In an unevaluated operand, e.g., sizeof, an enumerator is not relocated and keeps its C type, normally int, while an explicit builtin call has type unsigned long: sizeof(A) 4 sizeof(__builtin_preserve_enum_value(*(enum E *)A, 1)) 8 Feature macro ------------- A new macro __BPF_FEATURE_ENUM_PRESERVE_ACCESS_INDEX is defined, so vmlinux.h can apply the attribute to enums only if it is supported: #ifndef BPF_NO_PRESERVE_ACCESS_INDEX #ifdef __BPF_FEATURE_ENUM_PRESERVE_ACCESS_INDEX #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 | 7 + .../clang/Basic/DiagnosticSemaKinds.td | 3 + clang/include/clang/Sema/Sema.h | 5 +- clang/include/clang/Sema/SemaBPF.h | 8 + clang/lib/Basic/Targets/BPF.cpp | 1 + clang/lib/Frontend/CompilerInstance.cpp | 3 +- clang/lib/Sema/Sema.cpp | 7 +- clang/lib/Sema/SemaBPF.cpp | 141 ++++++++++- clang/lib/Sema/SemaExpr.cpp | 3 + ...-attr-preserve-access-index-enum-nodebug.c | 20 ++ .../bpf-attr-preserve-access-index-enum.c | 227 ++++++++++++++++++ ...a-attribute-supported-attributes-list.test | 2 +- .../test/Preprocessor/bpf-predefined-macros.c | 9 + .../Sema/bpf-attr-preserve-access-index.c | 87 ++++++- .../Sema/bpf-preserve-enum-value-parity.c | 97 ++++++++ 16 files changed, 607 insertions(+), 15 deletions(-) create mode 100644 clang/test/CodeGen/bpf-attr-preserve-access-index-enum-nodebug.c create mode 100644 clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c create mode 100644 clang/test/Sema/bpf-preserve-enum-value-parity.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 687e022c7bd61f..723650b262e77b 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -3073,6 +3073,13 @@ 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 a named or typedef'd enum +declaration. A reference to its enumerator is then treated like +`__builtin_preserve_enum_value(*(enum_type *)enumerator, 1)`, so it is not +a constant expression and, if -g is specified, it is relocated. The macro +`__BPF_FEATURE_ENUM_PRESERVE_ACCESS_INDEX` is defined if the attribute is +supported on enum declarations. }]; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index dcb5732c3c19e7..335d5dc5399b5b 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -13556,6 +13556,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>, DefaultError; 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/Sema.h b/clang/include/clang/Sema/Sema.h index cd2227f2aa5c69..a3773fd4fe7899 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -911,7 +911,8 @@ class Sema final : public SemaBase { public: Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, TranslationUnitKind TUKind = TU_Complete, - CodeCompleteConsumer *CompletionConsumer = nullptr); + CodeCompleteConsumer *CompletionConsumer = nullptr, + bool HasReducedDebugInfo = false); ~Sema(); /// Perform initialization that occurs after the parser has been @@ -926,6 +927,7 @@ class Sema final : public SemaBase { LLVM_DECLARE_VIRTUAL_ANCHOR_FUNCTION(); const LangOptions &getLangOpts() const { return LangOpts; } + bool hasReducedDebugInfo() const { return HasReducedDebugInfo; } OpenCLOptions &getOpenCLOptions() { return OpenCLFeatures; } FPOptions &getCurFPFeatures() { return CurFPFeatures; } @@ -1328,6 +1330,7 @@ class Sema final : public SemaBase { FPOptions CurFPFeatures; const LangOptions &LangOpts; + const bool HasReducedDebugInfo; Preprocessor &PP; ASTContext &Context; ASTConsumer &Consumer; diff --git a/clang/include/clang/Sema/SemaBPF.h b/clang/include/clang/Sema/SemaBPF.h index d7577aea701227..ccc0df302eb7b1 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,13 @@ class SemaBPF : public SemaBase { void handlePreserveAIRecord(RecordDecl *RD); void handlePreserveAccessIndexAttr(Decl *D, const ParsedAttr &AL); + void DiagnoseAnonymousPreserveAIEnums(); + Expr *BuildPreserveAIEnumeratorRef(DeclRefExpr *E); + static bool isPreserveAIEnumeratorRef(const Expr *E); + +private: + llvm::MapVector<EnumDecl *, const BPFPreserveAccessIndexAttr *> + AnonPreserveAIEnums; }; } // namespace clang diff --git a/clang/lib/Basic/Targets/BPF.cpp b/clang/lib/Basic/Targets/BPF.cpp index 2c37d56186ce52..ea46d94be9b073 100644 --- a/clang/lib/Basic/Targets/BPF.cpp +++ b/clang/lib/Basic/Targets/BPF.cpp @@ -48,6 +48,7 @@ void BPFTargetInfo::getTargetDefines(const LangOptions &Opts, Builder.defineMacro("__BPF_FEATURE_ATOMIC_MEM_ORDERING"); Builder.defineMacro("__BPF_FEATURE_STACK_ARGUMENT"); Builder.defineMacro("__BPF_FEATURE_UNWIND"); + Builder.defineMacro("__BPF_FEATURE_ENUM_PRESERVE_ACCESS_INDEX"); if (CPU.empty()) CPU = "v3"; diff --git a/clang/lib/Frontend/CompilerInstance.cpp b/clang/lib/Frontend/CompilerInstance.cpp index f122208d6b4c8b..416c12290b76c2 100644 --- a/clang/lib/Frontend/CompilerInstance.cpp +++ b/clang/lib/Frontend/CompilerInstance.cpp @@ -771,7 +771,8 @@ CompilerInstance::createCodeCompletionConsumer(Preprocessor &PP, void CompilerInstance::createSema(TranslationUnitKind TUKind, CodeCompleteConsumer *CompletionConsumer) { TheSema.reset(new Sema(getPreprocessor(), getASTContext(), getASTConsumer(), - TUKind, CompletionConsumer)); + TUKind, CompletionConsumer, + getCodeGenOpts().hasReducedDebugInfo())); // Set up API notes. TheSema->APINotes.setSwiftVersion(getAPINotesOpts().SwiftVersion); diff --git a/clang/lib/Sema/Sema.cpp b/clang/lib/Sema/Sema.cpp index b121f17b96a288..62ff36fe55dd32 100644 --- a/clang/lib/Sema/Sema.cpp +++ b/clang/lib/Sema/Sema.cpp @@ -276,9 +276,11 @@ const unsigned Sema::MaxAlignmentExponent; const uint64_t Sema::MaximumAlignment; Sema::Sema(Preprocessor &pp, ASTContext &ctxt, ASTConsumer &consumer, - TranslationUnitKind TUKind, CodeCompleteConsumer *CodeCompleter) + TranslationUnitKind TUKind, CodeCompleteConsumer *CodeCompleter, + bool HasReducedDebugInfoArg) : SemaBase(*this), CollectStats(false), TUKind(TUKind), - CurFPFeatures(pp.getLangOpts()), LangOpts(pp.getLangOpts()), PP(pp), + CurFPFeatures(pp.getLangOpts()), LangOpts(pp.getLangOpts()), + HasReducedDebugInfo(HasReducedDebugInfoArg), PP(pp), Context(ctxt), Consumer(consumer), Diags(PP.getDiagnostics()), SourceMgr(PP.getSourceManager()), APINotes(SourceMgr, LangOpts), AnalysisWarnings(*this), ThreadSafetyDeclCache(nullptr), @@ -1335,6 +1337,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..935b033729177f 100644 --- a/clang/lib/Sema/SemaBPF.cpp +++ b/clang/lib/Sema/SemaBPF.cpp @@ -12,9 +12,11 @@ #include "clang/Sema/SemaBPF.h" #include "clang/AST/Decl.h" +#include "clang/AST/Expr.h" #include "clang/AST/Type.h" #include "clang/Basic/DiagnosticSema.h" #include "clang/Basic/TargetBuiltins.h" +#include "clang/Sema/Lookup.h" #include "clang/Sema/ParsedAttr.h" #include "clang/Sema/Sema.h" #include "llvm/ADT/APSInt.h" @@ -102,6 +104,32 @@ static bool isValidPreserveEnumValueArg(Expr *Arg) { return llvm::is_contained(ED->enumerators(), Enumerator); } +// The kind of a C-style cast of the enumerator E to a pointer, as computed by +// Sema::PrepareScalarCast(). +static CastKind getEnumeratorToPointerCastKind(ASTContext &Context, + const Expr *E) { + return E->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull) + ? CK_NullToPointer + : CK_IntegralToPointer; +} + +// In __builtin_preserve_enum_value(*(<enum_type> *)<enum_value>, flag), the +// enumerator refers to the enumerator itself rather than its value. If it is +// wrapped by BuildPreserveAIEnumeratorRef(), use the original DeclRefExpr, so +// that the argument is the same as without the preserve_access_index attribute. +static void unwrapPreserveEnumValueArg(ASTContext &Context, Expr *Arg) { + auto *UO = dyn_cast<UnaryOperator>(Arg->IgnoreParens()); + if (!UO || UO->getOpcode() != UO_Deref) + return; + auto *CE = dyn_cast<CStyleCastExpr>(UO->getSubExpr()); + if (!CE || !SemaBPF::isPreserveAIEnumeratorRef(CE->getSubExpr())) + return; + auto *DRE = cast<DeclRefExpr>( + cast<PseudoObjectExpr>(CE->getSubExpr())->getSyntacticForm()); + CE->setSubExpr(DRE); + CE->setCastKind(getEnumeratorToPointerCastKind(Context, DRE)); +} + bool SemaBPF::CheckBPFBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { assert((BuiltinID == BPF::BI__builtin_preserve_field_info || @@ -145,6 +173,7 @@ bool SemaBPF::CheckBPFBuiltinFunctionCall(unsigned BuiltinID, kind = diag::err_preserve_type_info_invalid; } } else if (BuiltinID == BPF::BI__builtin_preserve_enum_value) { + unwrapPreserveEnumValueArg(Context, Arg); if (!isValidPreserveEnumValueArg(Arg)) { InvalidArg = true; kind = diag::err_preserve_enum_value_invalid; @@ -179,10 +208,114 @@ 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); + 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(); +} + +static QualType getPreserveAIEnumType(ASTContext &Context, + const DeclRefExpr *E) { + const auto *Enumerator = dyn_cast<EnumConstantDecl>(E->getDecl()); + if (!Enumerator) + return QualType(); + + // In C, an enumerator usually has type int rather than the enum type, so + // find its enum through the declaration context. Within the definition of + // the enum, e.g., A in B = A + 1, the enum is not complete yet and A must + // stay a constant, so it is not relocated. + const auto *ED = dyn_cast<EnumDecl>(Enumerator->getDeclContext()); + if (!ED || !ED->isCompleteDefinition() || + !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 Context.getCanonicalTagType(ED); + if (const TypedefNameDecl *TD = ED->getTypedefNameForAnonDecl()) + return Context.getTypeDeclType(TD); + return QualType(); +} + +Expr *SemaBPF::BuildPreserveAIEnumeratorRef(DeclRefExpr *E) { + if (SemaRef.isUnevaluatedContext()) + return E; + if (!SemaRef.hasReducedDebugInfo()) + return E; + + ASTContext &Context = getASTContext(); + QualType EnumTy = getPreserveAIEnumType(Context, E); + if (EnumTy.isNull()) + return E; + + // Build *(enum_type *)E in the form required by + // __builtin_preserve_enum_value(). + SourceLocation Loc = E->getExprLoc(); + auto *Ref = DeclRefExpr::Create(Context, E->getQualifierLoc(), + SourceLocation(), E->getDecl(), + /*RefersToEnclosingVariableOrCapture=*/false, + E->getNameInfo(), E->getType(), VK_PRValue); + QualType PtrTy = Context.getPointerType(EnumTy); + Expr *Cast = CStyleCastExpr::Create( + Context, PtrTy, VK_PRValue, getEnumeratorToPointerCastKind(Context, Ref), + Ref, + /*BasePath=*/nullptr, FPOptionsOverride(), + Context.getTrivialTypeSourceInfo(PtrTy, Loc), Loc, Loc); + Expr *Deref = UnaryOperator::Create( + Context, Cast, UO_Deref, EnumTy, VK_LValue, OK_Ordinary, Loc, + /*CanOverflow=*/false, FPOptionsOverride()); + + // Flag 1 is BPF_ENUMVAL_VALUE. + Expr *Flag = + IntegerLiteral::Create(Context, llvm::APInt(32, 1), Context.IntTy, Loc); + Expr *Args[] = {Deref, Flag}; + + // Build the call the same way as the parser builds an explicit + // __builtin_preserve_enum_value() call, so that the two are compatible, i.e., + // they have the same AST and are handled the same, e.g., with the same + // diagnostics where a constant expression is required. + // Sema::BuildBuiltinCallExpr() is not used since it refers to the builtin as + // an ordinary function, for which the constant evaluator evaluates the + // arguments and may give additional diagnostics. + LookupResult R(SemaRef, + &Context.Idents.get(Context.BuiltinInfo.getName( + BPF::BI__builtin_preserve_enum_value)), + Loc, Sema::LookupOrdinaryName); + SemaRef.LookupName(R, SemaRef.TUScope, /*AllowBuiltinCreation=*/true); + auto *BuiltinDecl = R.getAsSingle<FunctionDecl>(); + assert(BuiltinDecl && "failed to find builtin declaration"); + ExprResult Callee = SemaRef.BuildDeclarationNameExpr( + CXXScopeSpec(), R.getLookupNameInfo(), BuiltinDecl); + assert(Callee.isUsable() && "Builtin reference cannot fail"); + ExprResult Call = + SemaRef.BuildCallExpr(/*Scope=*/nullptr, Callee.get(), Loc, Args, Loc); + assert(Call.isUsable() && "Call to builtin cannot fail"); + // The relocated value is not a constant, so the enumerator cannot be used + // where a compile-time constant is required. Like an explicit builtin call, + // it has type unsigned long rather than the type of the enumerator. + return PseudoObjectExpr::Create(Context, E, {Call.get()}, /*resultIndex=*/0); +} + +// Return whether E is an enumerator reference built by +// BuildPreserveAIEnumeratorRef(). +bool SemaBPF::isPreserveAIEnumeratorRef(const Expr *E) { + const auto *POE = dyn_cast<PseudoObjectExpr>(E); + if (!POE) + return false; + const auto *DRE = dyn_cast<DeclRefExpr>(POE->getSyntacticForm()); + return DRE && isa<EnumConstantDecl>(DRE->getDecl()); } } // namespace clang diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 43b0c86132172b..96635a0e9f2cfe 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -55,6 +55,7 @@ #include "clang/Sema/ScopeInfo.h" #include "clang/Sema/SemaAMDGPU.h" #include "clang/Sema/SemaARM.h" +#include "clang/Sema/SemaBPF.h" #include "clang/Sema/SemaCUDA.h" #include "clang/Sema/SemaFixItUtils.h" #include "clang/Sema/SemaHLSL.h" @@ -3686,6 +3687,8 @@ ExprResult Sema::BuildDeclarationNameExpr( // diagnostics). if (VD->isInvalidDecl() && E) return CreateRecoveryExpr(E->getBeginLoc(), E->getEndLoc(), {E}); + if (isa<EnumConstantDecl>(VD) && Context.getTargetInfo().getTriple().isBPF()) + return BPF().BuildPreserveAIEnumeratorRef(E); return E; } diff --git a/clang/test/CodeGen/bpf-attr-preserve-access-index-enum-nodebug.c b/clang/test/CodeGen/bpf-attr-preserve-access-index-enum-nodebug.c new file mode 100644 index 00000000000000..bb9aa4fcfcee93 --- /dev/null +++ b/clang/test/CodeGen/bpf-attr-preserve-access-index-enum-nodebug.c @@ -0,0 +1,20 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang_cc1 -triple bpf -emit-llvm -disable-llvm-passes %s -o - | FileCheck %s + +// Without -g, the enumerators of an enum with the preserve_access_index +// attribute are not relocated and keep their compile-time value. +enum e1 { E1_A = 1, E1_C = 10 } __attribute__((preserve_access_index)); + +int test(int x) { + if (x == E1_C) + return E1_A; + return 0; +} + +// CHECK: define dso_local i32 @test +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: icmp eq i32 %{{[0-9a-z]+}}, 10 +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: store i32 1, ptr %retval +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: ret i32 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..a6c463fe198b10 --- /dev/null +++ b/clang/test/CodeGen/bpf-attr-preserve-access-index-enum.c @@ -0,0 +1,227 @@ +// REQUIRES: bpf-registered-target +// RUN: %clang_cc1 -triple bpf -emit-llvm -debug-info-kind=limited -disable-llvm-passes \ +// RUN: -Wno-error=ignored-attributes -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__; +enum e5 { E5_Z = 0 } __reloc__; + +// 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" + +// __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: icmp eq i64 %{{[0-9a-z]+}}, [[V]] +// 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. A +// relocated enumerator has type unsigned long, so x < E1_C is an unsigned +// compare. +int test3(int x) { + if (E1_C == 10) + x++; + while (x < E1_C) + x += E1_C > 5 ? 2 : 3; + switch (E1_A) { + case 1: + 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 i64 %{{[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 ult i64 +// 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 ugt i64 %{{[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: switch i64 [[V]], label %{{.*}} [ +// CHECK-NEXT: i64 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(int x) { + return x + 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 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. +int test8(int x) { + const int k = E1_C; + const int k2 = k + 1; + const struct key kk = { E1_A, 2 }; + if (k2 == 11) + x++; + return x + k + kk.a; +} + +// 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: 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 + +// A conditional operator with relocated enumerators uses control flow, not a +// select, since the backend cannot relocate a select of two relocations. +int test10(int x) { + return x ? E1_A : E1_C; +} + +// CHECK: define dso_local i32 @test10 +// CHECK: br i1 %{{[0-9a-z]+}}, label %cond.true, label %cond.false +// CHECK: cond.true: +// CHECK-NEXT: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: cond.false: +// CHECK-NEXT: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E1]] +// CHECK: cond.end: +// CHECK-NEXT: phi i64 +// CHECK-NOT: select +// CHECK: ret i32 + +// A cast of an enumerator to a pointer to its enum is relocated as other +// uses, without a -Wint-to-pointer-cast warning since it has type unsigned +// long. +enum e1 *test11(void) { + return (enum e1 *)E1_C; +} + +// CHECK: define dso_local ptr @test11 +// 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: inttoptr i64 [[V]] to ptr + +// Only the enumerator in the argument of __builtin_preserve_enum_value() is +// not relocated, including an enumerator with value 0 and a typedef'd enum. +unsigned long test12(void) { + return __builtin_preserve_enum_value(*(enum e5 *)E5_Z, 1) + + __builtin_preserve_enum_value(*(e2 *)E2_A, 0) + + (unsigned long)(void *)E5_Z; +} + +// CHECK: define dso_local i64 @test12 +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 1), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E5:[0-9]+]] +// CHECK: call i64 @llvm.bpf.preserve.enum.value(i32 {{[0-9]+}}, ptr @{{[0-9]+}}, i64 0), !dbg !{{[0-9]+}}, !llvm.preserve.access.index ![[E2]] +// 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 ![[E5]] +// CHECK: inttoptr i64 [[V]] to ptr +// CHECK-NOT: @llvm.bpf.preserve.enum.value +// CHECK: ret i64 + +// 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, +// CHECK-DAG: ![[E5]] = !DICompositeType(tag: DW_TAG_enumeration_type, name: "e5" 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/Preprocessor/bpf-predefined-macros.c b/clang/test/Preprocessor/bpf-predefined-macros.c index f89e04ae6327af..977f1d3cffd44c 100644 --- a/clang/test/Preprocessor/bpf-predefined-macros.c +++ b/clang/test/Preprocessor/bpf-predefined-macros.c @@ -92,6 +92,9 @@ int ab; #if __BPF_CPU_VERSION__ < 5 && defined(__BPF_FEATURE_HMUL) #error HMUL requires CPU v5 or later #endif +#ifdef __BPF_FEATURE_ENUM_PRESERVE_ACCESS_INDEX +int ac; +#endif // CHECK: int b; // CHECK: int c; @@ -169,6 +172,12 @@ int ab; // CPU_V5: int ab; +// CPU_V1: int ac; +// CPU_V2: int ac; +// CPU_V3: int ac; +// CPU_V4: int ac; +// CPU_V5: int ac; + // CPU_GENERIC: int g; // CPU_PROBE: int f; diff --git a/clang/test/Sema/bpf-attr-preserve-access-index.c b/clang/test/Sema/bpf-attr-preserve-access-index.c index 34b8c917febf54..3fe8783898ec0c 100644 --- a/clang/test/Sema/bpf-attr-preserve-access-index.c +++ b/clang/test/Sema/bpf-attr-preserve-access-index.c @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -x c -triple bpf-pc-linux-gnu -dwarf-version=4 -fsyntax-only -verify %s +// RUN: %clang_cc1 -x c -triple bpf-pc-linux-gnu -debug-info-kind=limited -dwarf-version=4 -fsyntax-only -verify %s #define __reloc__ __attribute__((preserve_access_index)) #define __err_reloc__ __attribute__((preserve_access_index(0))) @@ -36,13 +36,90 @@ 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-error {{'preserve_access_index' attribute has no effect on an anonymous enum without a typedef name}} +enum { A2 } __reloc__ v2; // expected-error {{'preserve_access_index' attribute has no effect on an anonymous enum}} +typedef enum { A3 } __reloc__ *t3p; // expected-error {{'preserve_access_index' attribute has no effect on an anonymous enum}} +struct s4 { enum { A4 } __reloc__ f; }; // expected-error {{'preserve_access_index' attribute has no effect on an anonymous enum}} +enum __reloc__ { A9 } __reloc__; // expected-error {{'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-error {{'preserve_access_index' attribute has no effect on an anonymous enum}} +#pragma clang attribute pop +int invalid3(void) { + enum { A5 } __reloc__; // expected-error {{'preserve_access_index' attribute has no effect on an anonymous enum}} + return A1 + A2 + A3 + A4 + A5; +} + +// No diagnostic 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; } + +// An enumerator of an enum with the attribute is relocated, so it cannot be +// used where a compile-time constant is required. +int g1 = E1_B; // expected-error {{initializer element is not a compile-time constant}} +int g2[E1_B]; // expected-error {{variable length array declaration not allowed at file scope}} +struct s8 { + int a[E1_B]; // expected-error {{fields must have a constant size}} + int b : E1_B; // expected-error {{expression is not an integer constant expression}} +}; +_Static_assert(E2_A == 0, ""); // expected-error {{static assertion expression is not an integral constant expression}} +enum e7 { E7_A = A6 }; // expected-error {{expression is not an integer constant expression}} +int constant_contexts(int v) { + static int s = E1_B; // expected-error {{initializer element is not a compile-time constant}} + switch (v) { + case E1_B: // expected-error {{expression is not an integer constant expression}} + return 1; + case 2 ... E2_A + 3: // expected-error {{expression is not an integer constant expression}} + return 2; + } + return s; +} + +// An enumerator referenced in the definition of its enum, in an unevaluated +// operand, or of an anonymous enum without a typedef name, is not relocated. +// __builtin_preserve_enum_value() works as before. +enum e9 { E9_A = 1, E9_B = E9_A + 1 } __reloc__; +enum __reloc__ e10 { E10_A = 1, E10_B = E10_A + 1 }; +_Static_assert(sizeof(E1_B) == sizeof(int), ""); +int g4 = A1 + A7 + A8; +unsigned long valid7(int v) { + switch (v) { + case sizeof(E1_B): + return 1; + case A7 + 10: + return 2; + } + return E1_B + __builtin_preserve_enum_value(*(enum e1 *)E1_A, 1); +} + +// Only the argument of __builtin_preserve_enum_value() is checked as before. +// A relocated enumerator cast to a pointer is not warned on, as it was not +// before when it was a constant. +unsigned long casts(int v) { + unsigned long r = __builtin_preserve_enum_value(*(int *)E1_A, 1); // expected-error {{__builtin_preserve_enum_value argument 1 invalid}} + r += __builtin_preserve_enum_value(*(enum e1 *)(E1_A), 1); // expected-error {{__builtin_preserve_enum_value argument 1 invalid}} + r += (unsigned long)(char *)E1_B + (unsigned long)(void *)(E1_B); + return r + (unsigned long)(char *)v; // expected-warning {{cast to 'char *' from smaller integer type 'int'}} +} diff --git a/clang/test/Sema/bpf-preserve-enum-value-parity.c b/clang/test/Sema/bpf-preserve-enum-value-parity.c new file mode 100644 index 00000000000000..8260d8effa9546 --- /dev/null +++ b/clang/test/Sema/bpf-preserve-enum-value-parity.c @@ -0,0 +1,97 @@ +// RUN: %clang_cc1 -triple bpf -debug-info-kind=limited -fsyntax-only -verify %s +// RUN: %clang_cc1 -triple bpf -debug-info-kind=limited -fsyntax-only -verify -DEXPLICIT %s +// RUN: %clang_cc1 -triple bpf -debug-info-kind=limited -fsyntax-only -verify -std=c23 %s +// RUN: %clang_cc1 -triple bpf -debug-info-kind=limited -fsyntax-only -verify -std=c23 -DEXPLICIT %s + +// An enumerator of an enum with the preserve_access_index attribute is +// accepted and rejected in the same contexts as the equivalent +// __builtin_preserve_enum_value() call. +#ifdef EXPLICIT +enum E { A = 4 }; +#define X __builtin_preserve_enum_value(*(enum E *)A, 1) +#else +enum E { A = 4 } __attribute__((preserve_access_index)); +#define X A +#endif + +typedef unsigned long size_t; +void *memcpy(void *, const void *, size_t); + +// Rejected where a compile-time constant is required. +int g1 = X; // expected-error {{initializer element is not a compile-time constant}} +int g2[2] = {X, 1}; // expected-error {{initializer element is not a compile-time constant}} +int *g3 = (int[]){X}; // expected-error {{initializer element is not a compile-time constant}} +int g4[X]; // expected-error {{variable length array declaration not allowed at file scope}} +int g5[8] = {[X] = 1}; // expected-error {{expression is not an integer constant expression}} +_Alignas(X) int g6; // expected-error {{'aligned' attribute requires integer constant}} +int g7 __attribute__((aligned(X))); // expected-error {{'aligned' attribute requires integer constant}} +typedef int v4 __attribute__((vector_size(X * 4))); // expected-error {{vector_size attribute requires an integer constant}} +struct s1 { + int a[X]; // expected-error {{fields must have a constant size: 'variable length array in structure' extension will never be supported}} + int b : X; // expected-error {{expression is not an integer constant expression}} +}; +enum F { F1 = X }; // expected-error {{expression is not an integer constant expression}} +_Static_assert(X == 4, ""); // expected-error {{static assertion expression is not an integral constant expression}} + +void h(int); +int rejected(int v, void *p, const void *q) { + static int s = X; // expected-error {{initializer element is not a compile-time constant}} + int a[8] = {[X] = 1}; // expected-error {{expression is not an integer constant expression}} + switch (v) { + case X: // expected-error {{expression is not an integer constant expression}} + return 1; + case 5 ... X + 2: // expected-error {{expression is not an integer constant expression}} + return 2; + } +#pragma unroll X // expected-error {{expression is not an integer constant expression}} + for (int i = 0; i < 8; i++) + h(i); +#pragma clang loop unroll_count(X) // expected-error {{expression is not an integer constant expression}} + for (int i = 0; i < 8; i++) + h(i); + v += __builtin_choose_expr(X, 1, 2); // expected-error {{'__builtin_choose_expr' requires a constant expression}} + v += __builtin_object_size(p, X - 4); // expected-error {{argument to '__builtin_object_size' must be a constant integer}} + __builtin_prefetch(p, X - 4); // expected-error {{argument to '__builtin_prefetch' must be a constant integer}} + p = __builtin_assume_aligned(p, X); // expected-error {{argument to '__builtin_assume_aligned' must be a constant integer}} + p = __builtin_frame_address(X - 4); // expected-error {{argument to '__builtin_frame_address' must be a constant integer}} + p = __builtin_alloca_with_align(16, X * 16); // expected-error {{argument to '__builtin_alloca_with_align' must be a constant integer}} + __builtin_memcpy_inline(p, q, X); // expected-error {{argument to '__builtin_memcpy_inline' must be a constant integer}} + __builtin_memset_inline(p, 0, X); // expected-error {{argument to '__builtin_memset_inline' must be a constant integer}} + return v + a[0] + s; +} + +#if __STDC_VERSION__ >= 202311L +struct item { int v; }; +constexpr struct item cx1 = {X}; // expected-error {{constexpr variable 'cx1' must be initialized by a constant expression}} +constexpr int cx2 = X; // expected-error {{constexpr variable 'cx2' must be initialized by a constant expression}} +int constexpr_locals(void) { + constexpr struct item cx3 = {X}; // expected-error {{constexpr variable 'cx3' must be initialized by a constant expression}} + return cx3.v; +} +#endif + +typedef int v2 __attribute__((vector_size(8))); +v2 shuffle(v2 x) { + return __builtin_shufflevector(x, x, X - 4, 1); // expected-error {{index for __builtin_shufflevector must be a constant integer}} +} + +// Accepted where the value is used at runtime, or not used at all. +struct key { int a; int b; }; +void use(void *); +unsigned long accepted(int v, int *p, void *d, const void *s) { + const int k = X; + struct key kk = {X, 1}; + int arr[2] = {X, 1}; + use(&kk); + use(arr); + switch (X) { + case 4: + v++; + } + v += p[X] + (v == X) + (v && X ? X : 0) + (v << X) + k; + v += __builtin_bswap32(X) + __builtin_expect(v, X); + v += __builtin_constant_p(X) + sizeof(X) + _Generic(X, int: 1, default: 2); + memcpy(d, s, X); + __builtin___memcpy_chk(d, s, X, 64); + return v + __builtin_align_up((unsigned long)v, X); +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
