llvmorg-github-actions[bot] wrote:
<!--LLVM PR SUMMARY COMMENT--> @llvm/pr-subscribers-clang Author: LoveSy (yujincheng08) <details> <summary>Changes</summary> Support copy attribute like GCC. --- Patch is 42.98 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/229355.diff 17 Files Affected: - (modified) clang/docs/ReleaseNotes.md (+4) - (modified) clang/include/clang/Basic/Attr.td (+10) - (modified) clang/include/clang/Basic/AttrDocs.td (+41) - (modified) clang/include/clang/Basic/DiagnosticSemaKinds.td (+9) - (modified) clang/include/clang/Sema/Sema.h (+4) - (modified) clang/lib/Sema/SemaDeclAttr.cpp (+260) - (modified) clang/lib/Sema/SemaTemplateInstantiateDecl.cpp (+21) - (modified) clang/lib/Sema/SemaType.cpp (+48) - (added) clang/test/AST/attr-copy.c (+105) - (added) clang/test/CodeGen/attr-copy.c (+34) - (added) clang/test/CodeGenCXX/attr-copy-calling-conv.cpp (+8) - (added) clang/test/PCH/attr-copy.cpp (+31) - (added) clang/test/Sema/attr-copy-calling-conv.c (+24) - (added) clang/test/Sema/attr-copy.c (+97) - (added) clang/test/SemaCXX/attr-copy-calling-conv.cpp (+30) - (added) clang/test/SemaTemplate/attr-copy.cpp (+60) - (modified) clang/utils/TableGen/ClangAttrEmitter.cpp (+58-1) ``````````diff diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 7b878a0abbab3..1aed07bbcdfc1 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -304,6 +304,10 @@ features cannot lower the translation-unit ABI level; ### Attribute Changes in Clang +- Clang now supports `__attribute__((copy(expression)))` and + `[[gnu::copy(expression)]]` for copying GNU declaration attributes from + another function, variable, or type. + - Clang now properly propagates attributes on class and variable templates to their redeclarations, which will result in redeclarations not interfering with diagnostics. (#GH209812) - Clang now recognizes the `[[gnu::flag_enum]]` attribute and treats it equivalent to `[[clang::flag_enum]]` diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td index 4ef0bd2b5ca7c..a78c350d5d4e6 100644 --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -878,6 +878,16 @@ def Alias : Attr { let Documentation = [Undocumented]; } +def Copy : InheritableAttr { + let Spellings = [GCC<"copy">]; + let Args = [ExprArgument<"Source">]; + let Subjects = SubjectList<[Function, Var, Field, Tag, TypedefName]>; + let ParseArgumentsAsUnevaluated = 1; + let TemplateDependent = 1; + let MeaningfulToClassTemplateDefinition = 1; + let Documentation = [CopyDocs]; +} + def BuiltinAlias : Attr { let Spellings = [CXX11<"clang", "builtin_alias">, C23<"clang", "builtin_alias">, diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td index 0ab1429ff09d6..11569376cdccb 100644 --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -42,6 +42,47 @@ This page lists the attributes currently supported by Clang. }]; } +def CopyDocs : Documentation { + let Category = DocCatDecl; + let Content = [{ +The `copy(expression)` attribute applies the GNU declaration attributes of the +entity or type referenced by `expression` to another declaration. The expression +is unevaluated. For example: + +```c +void *allocate(unsigned long) __attribute__((malloc, alloc_size(1), nothrow)); +extern __typeof__(allocate) allocate_alias + __attribute__((alias("allocate"), copy(allocate))); +``` + +The argument can name a function or variable, take its address, or refer to a +type through an expression such as `(struct A *)0`. A function's attributes +cannot be copied to a variable, or vice versa. Copied attributes are checked +against the destination just as if they had been written there explicitly. + +Attributes controlling linkage, inlining, or visibility, including `alias`, +`ifunc`, `always_inline`, `noinline`, `gnu_inline`, `visibility`, `weak`, +`weakref`, `dllimport`, and `dllexport`, are not copied. +Neither `deprecated` nor `target_clones` is copied. The attribute does not copy +the source's type; `__typeof__`, as in the example, can be used when the types +must also match. + +Function type attributes, such as calling conventions, can also be copied. For +example, a captureless lambda can use `copy` to match a callback's calling +convention: + +```cpp +using Callback = int (__attribute__((ms_abi)) *)(int); +Callback callback = [](int x) __attribute__((copy((Callback)nullptr))) { + return x; +}; +``` + +The calling convention is applied to the lambda's call operator and its +conversion to a function pointer. + }]; +} + def SectionDocs : Documentation { let Category = DocCatVariable; let Content = [{ diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index 9208aba1445d7..2616cda0b6fda 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -4056,6 +4056,15 @@ def err_attribute_weakref_without_alias : Error< "weakref declaration of %0 must also have an alias attribute">; def err_alias_not_supported_on_darwin : Error < "aliases are not supported on darwin">; +def err_attribute_copy_invalid_argument : Error< + "'copy' attribute requires an expression referring to a function, variable, " + "or type">; +def warn_attribute_copy_kind : Warning< + "'copy' attribute ignored on a declaration of a different kind than its " + "argument">, InGroup<IgnoredAttributes>; +def warn_attribute_copy_self : Warning< + "'copy' attribute ignored on a declaration referring to itself">, + InGroup<IgnoredAttributes>; def warn_attribute_non_character_array : Warning< "%0%select{ attribute|}1 only applies to fields or variables of character " "array type; type is %2">, InGroup<IgnoredAttributes>; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 7fbcc4aae7d6c..e662471831630 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -5050,6 +5050,10 @@ class Sema final : public SemaBase { /// treat a reference type as valid. bool isValidPointerAttrType(QualType T, bool RefOkay = false); + void AddCopyAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E, + bool TypeAttrsOnly = false); + bool ProcessCopiedTypeAttribute(Decl *D, ParsedAttr &AL); + /// AddAssumeAlignedAttr - Adds an assume_aligned attribute to a particular /// declaration. void AddAssumeAlignedAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E, diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index eb4a8c2ab9ae0..8f882fd67ee66 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -7802,6 +7802,9 @@ ProcessDeclAttribute(Sema &S, Decl *D, const ParsedAttr &AL, case ParsedAttr::AT_Constructor: handleConstructorAttr(S, D, AL); break; + case ParsedAttr::AT_Copy: + S.AddCopyAttr(D, AL, AL.getArgAsExpr(0)); + break; case ParsedAttr::AT_Deprecated: handleDeprecatedAttr(S, D, AL); break; @@ -8552,6 +8555,263 @@ static void checkAMDGPUReqdWorkGroupSize(Sema &S, Decl *D) { } } +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + Expr *E) { + return E; +} + +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + unsigned Value) { + return IntegerLiteral::Create(Context, llvm::APInt(32, Value), + Context.UnsignedIntTy, Loc); +} + +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + ParamIdx Index) { + return createCopyAttrArgument(Context, Loc, Index.getSourceIndex()); +} + +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + StringRef Value) { + return StringLiteral::Create( + Context, Value, StringLiteralKind::Ordinary, false, + Context.getStringLiteralArrayType(Context.CharTy, Value.size()), Loc); +} + +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + const IdentifierInfo *II) { + return new (Context) IdentifierLoc(Loc, const_cast<IdentifierInfo *>(II)); +} + +static ArgsUnion createCopyAttrArgument(ASTContext &Context, SourceLocation Loc, + FunctionDecl *FD) { + return DeclRefExpr::Create(Context, NestedNameSpecifierLoc(), + SourceLocation(), FD, false, Loc, FD->getType(), + VK_LValue); +} + +#define WANT_DECL_COPY_LOGIC +#include "clang/Sema/AttrParsedAttrImpl.inc" +#undef WANT_DECL_COPY_LOGIC + +static bool isCopyableAttr(const Attr *A) { + // copy propagates GNU declaration attributes, not implicit attributes or + // attributes from other vendors. In particular, an implicit BuiltinAttr must + // not turn the destination into a different builtin function. + if (A->isImplicit() || (!A->isGNUAttribute() && !A->isGNUScope())) + return false; + + switch (A->getKind()) { + case attr::Alias: + case attr::AlwaysInline: + case attr::Copy: + case attr::Deprecated: + case attr::DLLExport: + case attr::DLLImport: + case attr::GNUInline: + case attr::IFunc: + case attr::NoInline: + case attr::SelectAny: + case attr::TargetClones: + case attr::Visibility: + case attr::Weak: + case attr::WeakRef: + return false; + default: + return ParsedAttrInfo::get(*A).IsKnownToGCC; + } +} + +void Sema::AddCopyAttr(Decl *D, const AttributeCommonInfo &CI, Expr *E, + bool TypeAttrsOnly) { + if (E->isTypeDependent() || E->isValueDependent()) { + auto *A = ::new (Context) CopyAttr(Context, CI, E); + A->setImplicit(TypeAttrsOnly); + D->addAttr(A); + return; + } + + Expr *SourceExpr = E->IgnoreParenCasts(); + if (const auto *UO = dyn_cast<UnaryOperator>(SourceExpr); + UO && (UO->getOpcode() == UO_AddrOf || UO->getOpcode() == UO_Deref)) + SourceExpr = UO->getSubExpr()->IgnoreParenCasts(); + + const Decl *Source = nullptr; + if (const auto *DRE = dyn_cast<DeclRefExpr>(SourceExpr)) + Source = DRE->getDecl(); + else if (const auto *ME = dyn_cast<MemberExpr>(SourceExpr)) + Source = ME->getMemberDecl(); + + // Attributes are processed before a new function or variable declaration is + // linked into its redeclaration chain. + const auto *SourceVD = dyn_cast_or_null<ValueDecl>(Source); + const auto *DestVD = dyn_cast<ValueDecl>(D); + bool IsRedeclaration = + SourceVD && DestVD && SourceVD->getDeclName() == DestVD->getDeclName() && + SourceVD->getDeclContext()->Equals(DestVD->getDeclContext()) && + Context.hasSameType(SourceVD->getType(), DestVD->getType()); + // Nested block scopes share a DeclContext, but can declare distinct local + // variables with the same name and type. + if (const auto *VD = dyn_cast_or_null<VarDecl>(Source)) + IsRedeclaration &= !VD->isLocalVarDecl(); + if (Source && (Source->getCanonicalDecl() == D->getCanonicalDecl() || + IsRedeclaration)) { + Diag(CI.getLoc(), diag::warn_attribute_copy_self); + return; + } + + if (Source && !isa<FunctionDecl, VarDecl, FieldDecl>(Source)) { + Diag(E->getExprLoc(), diag::err_attribute_copy_invalid_argument); + return; + } + + if (isa<StringLiteral>(SourceExpr->IgnoreParenCasts()) || + (!Source && E->getType()->isArithmeticType() && + E->isEvaluatable(Context)) || + E->getType() == Context.OverloadTy) { + Diag(E->getExprLoc(), diag::err_attribute_copy_invalid_argument); + return; + } + + QualType Ty = E->getType(); + if (const auto *FD = dyn_cast_or_null<FunctionDecl>(Source)) + Ty = FD->getType(); + if (Source && isa<FunctionDecl>(D) != isa<FunctionDecl>(Source) && + isa<FunctionDecl, VarDecl, FieldDecl>(D)) { + Diag(CI.getLoc(), diag::warn_attribute_copy_kind); + return; + } + + SmallVector<const Decl *, 4> Sources; + SmallVector<const Attr *, 8> SourceAttrs; + if (Source) + Sources.push_back(Source->getMostRecentDecl()); + + // Keep typedef attributes as well as attributes on the underlying tag. Do + // not canonicalize Ty before walking it: that would discard typedefs. + bool SawPointer = false; + bool HasCallingConventionAttr = false; + while (true) { + if (const auto *TT = dyn_cast<TypedefType>(Ty.getTypePtr())) + Sources.push_back(TT->getDecl()); + if (const auto *AT = dyn_cast<AttributedType>(Ty.getTypePtr())) { + if (const Attr *A = AT->getAttr()) { + SourceAttrs.push_back(A); + HasCallingConventionAttr |= AT->isCallingConv(); + } + } + QualType Next = Ty.getSingleStepDesugaredType(Context); + if (Next != Ty) { + Ty = Next; + } else if (!SawPointer && Ty->isPointerType()) { + Ty = Ty->getPointeeType(); + SawPointer = true; + } else { + break; + } + } + // Naming a pointer to a class template specialization need not instantiate + // it, but its attributes may depend on its template arguments. + if (Ty->getAsCXXRecordDecl()) + isCompleteType(CI.getLoc(), Ty); + if (const auto *TT = Ty->getAs<TagType>()) + Sources.push_back(TT->getDecl()->getMostRecentDecl()); + + AttributeFactory Factory; + ParsedAttributes Copied(Factory); + // Some function attributes are represented only in the function type's + // extended information, without an Attr or AttributedType node. + if (const auto *FT = Ty->getAs<FunctionType>()) { + auto Add = [&](StringRef Name, MutableArrayRef<ArgsUnion> Args = {}) { + Copied.addNew(&Context.Idents.get(Name), CI.getRange(), {}, Args.data(), + Args.size(), AttributeCommonInfo::Form::GNU()); + }; + FunctionType::ExtInfo EI = FT->getExtInfo(); + // Substituting a template type argument can discard the AttributedType + // sugar while retaining the calling convention in the canonical type. + if (!HasCallingConventionAttr) { + switch (EI.getCC()) { + case CC_X86StdCall: + case CC_X86FastCall: + case CC_X86ThisCall: + case CC_Win64: + case CC_X86_64SysV: + case CC_X86RegCall: + Add(FunctionType::getNameForCallConv(EI.getCC())); + break; + case CC_AAPCS: + case CC_AAPCS_VFP: { + ArgsUnion Arg = createCopyAttrArgument( + Context, CI.getLoc(), FunctionType::getNameForCallConv(EI.getCC())); + Add("pcs", Arg); + break; + } + default: + break; + } + } + if (EI.getNoReturn()) + Add("noreturn"); + if (EI.getNoCallerSavedRegs()) + Add("no_caller_saved_registers"); + if (EI.getNoCfCheck()) + Add("nocf_check"); + if (EI.getHasRegParm()) { + ArgsUnion Arg = + createCopyAttrArgument(Context, CI.getLoc(), EI.getRegParm()); + Add("regparm", Arg); + } + if (const auto *FPT = dyn_cast<FunctionProtoType>(FT)) + if (FPT->getExceptionSpecType() == EST_NoThrow) + Add("nothrow"); + } + for (const Decl *From : Sources) { + // A type expression can refer to the very type being declared. + if (From->getCanonicalDecl() == D->getCanonicalDecl()) { + Diag(CI.getLoc(), diag::warn_attribute_copy_self); + continue; + } + SourceAttrs.append(From->attr_begin(), From->attr_end()); + } + for (const Attr *A : SourceAttrs) { + if (!isCopyableAttr(A)) + continue; + + ArgsVector Args; + if (const auto *AA = dyn_cast<AlignedAttr>(A)) { + if (AA->isAlignmentExpr() && AA->getAlignmentExpr()) + Args.push_back(AA->getAlignmentExpr()); + } else if (!getAttributeCopyArgs(Context, A, CI.getLoc(), Args)) { + continue; + } + + Copied.addNew(&Context.Idents.get(A->getSpelling()), CI.getRange(), + AttributeScopeInfo(A->getScopeName(), CI.getLoc()), + Args.data(), Args.size(), A->getForm()); + } + ParsedAttributesView DeclAttrs; + bool CopiedType = false; + for (ParsedAttr &AL : Copied) { + if (ProcessCopiedTypeAttribute(D, AL)) + CopiedType = true; + else if (!TypeAttrsOnly) + DeclAttrs.addAtEnd(&AL); + } + // Template instantiation rebuilds types from their written TypeSourceInfo, + // which does not contain attributes introduced by copy. An implicit copy + // attribute replays only the type attributes: declaration attributes are + // already expanded and will be instantiated normally. + if (CopiedType && D->isTemplated()) { + auto *A = ::new (Context) CopyAttr(Context, CI, E); + A->setImplicit(true); + D->addAttr(A); + } + // The enclosing attribute list may still contain attributes needed by a + // group check (for example, alias after weakref and copy). + for (const ParsedAttr &AL : DeclAttrs) + ProcessDeclAttribute(*this, D, AL, ProcessDeclAttributeOptions()); +} + void Sema::ProcessDeclAttributeList( Scope *S, Decl *D, const ParsedAttributesView &AttrList, const ProcessDeclAttributeOptions &Options) { diff --git a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp index 5e72f05c863a0..77d7f33273369 100644 --- a/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/clang/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -88,6 +88,17 @@ bool TemplateDeclInstantiator::SubstQualifier(const TagDecl *OldDecl, // Include attribute instantiation code. #include "clang/Sema/AttrTemplateInstantiate.inc" +static void +instantiateDependentCopyAttr(Sema &S, + const MultiLevelTemplateArgumentList &TemplateArgs, + const CopyAttr *A, Decl *New) { + EnterExpressionEvaluationContext Unevaluated( + S, Sema::ExpressionEvaluationContext::Unevaluated); + ExprResult E = S.SubstExpr(A->getSource(), TemplateArgs); + if (!E.isInvalid()) + S.AddCopyAttr(New, *A, E.get(), A->isImplicit()); +} + static void instantiateDependentAlignedAttr( Sema &S, const MultiLevelTemplateArgumentList &TemplateArgs, const AlignedAttr *Aligned, Decl *New, bool IsPackExpansion) { @@ -847,6 +858,11 @@ void Sema::InstantiateAttrsForDecl( if (!isRelevantAttr(*this, New, TmplAttr)) continue; + if (const auto *A = dyn_cast<CopyAttr>(TmplAttr)) { + instantiateDependentCopyAttr(*this, TemplateArgs, A, New); + continue; + } + // FIXME: If any of the special case versions from InstantiateAttrs become // applicable to template declaration, we'll need to add them here. CXXThisScopeRAII ThisScope( @@ -889,6 +905,11 @@ void Sema::InstantiateAttrs(const MultiLevelTemplateArgumentList &TemplateArgs, if (!isRelevantAttr(*this, New, TmplAttr)) continue; + if (const auto *A = dyn_cast<CopyAttr>(TmplAttr)) { + instantiateDependentCopyAttr(*this, TemplateArgs, A, New); + continue; + } + // FIXME: This should be generalized to more than just the AlignedAttr. const AlignedAttr *Aligned = dyn_cast<AlignedAttr>(TmplAttr); if (Aligned && Aligned->isAlignmentDependent()) { diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index e1481b437df9e..a70505549508f 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -8468,6 +8468,54 @@ static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr, return true; } +bool Sema::ProcessCopiedTypeAttribute(Decl *D, ParsedAttr &AL) { + switch (AL.getKind()) { + case ParsedAttr::AT_NoThrow: + if (!getLangOpts().CPlusPlus) + return false; + [[fallthrough]]; + FUNCTION_TYPE_ATTRS_CASELIST: + break; + default: + return false; + } + + QualType T; + if (auto *VD = dyn_cast<ValueDecl>(D)) + T = VD->getType(); + else if (auto *TD = dyn_cast<TypedefNameDecl>(D)) + T = TD->getUnderlyingType(); + else + return false; + + if (checkCommonAttributeFeatures(D, AL)) + return true; + + // Reuse the function type attribute checks, including calling convention + // conflicts and restrictions on variadic functions. No declarator chunks + // need to be processed: the destination's type has already been built. + AttributeFactory Factory; + DeclSpec DS(Factory); + Declarator Declarator(DS, ParsedAttributesView::none(), + DeclaratorContext::TypeName); + TypeProcessingState State(*this, Declarator); + CUDAFunctionTarget CFT = CUDAFunctionTarget::Host; + if (getLangOpts().CUDA) + if (auto *FD = dyn_cast<FunctionDecl>(D)) + CFT = CUDA().IdentifyTarget(FD); + if (!handleFunctionTypeAttr(State, AL, T, CFT)) + diagnoseBadTypeAttribute(*this, AL, T); + else if (!AL.isInvalid()) { + if (auto *VD = dyn_cast<ValueDecl>(D)) + VD->setType(T); + else { + auto *TD = cast<TypedefNameDecl>(D); + TD->setModedTypeSourceInfo(TD->getTypeSourceInfo(), T); + } + } + return true; +} + bool Sema::hasExplicitCallingConv(QualType T) { const AttributedType *AT; diff --git a/clang/test/AST/attr-copy.c b/clang/test/AST/attr-copy.c new file mode 100644 index 0000000000000..725891fc3dc22 --- /dev/null +++ b/clang/test/AST/attr-copy.c @@ -0,0 +1,105 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -Wno-deprecated-declarations -verify -ast-dump %s | FileCheck %s + +void *source(int) __attribute__((malloc, alloc_size(1), nothrow, returns_nonnull, + deprecated, weak, visibility("hidden"))); + +// CHECK-LABEL: FunctionDecl {{.*}} copied 'void *(int)' +// CHECK: Restrict... [truncated] `````````` </details> https://github.com/llvm/llvm-project/pull/229355 _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
