diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -3968,6 +3968,19 @@ /// because TrailingObjects cannot handle repeated types. struct ExceptionType { QualType Type; }; + /// The AArch64 SME ACLE (Arm C/C++ Language Extensions) define a number + /// of function type attributes that can be set on function types, including + /// function pointers. + enum AArch64SMETypeAttributes : unsigned { + SME_NormalFunction = 0, + SME_PStateSMEnabledMask = 1 << 0, + SME_PStateSMCompatibleMask = 1 << 1, + SME_PStateZASharedMask = 1 << 2, + SME_PStateZAPreservedMask = 1 << 3, + SME_AttributeMask = 0b111'111 // We only support maximum 6 bits because of the + // bitmask in FunctionTypeExtraBitfields. + }; + /// A simple holder for various uncommon bits which do not fit in /// FunctionTypeBitfields. Aligned to alignof(void *) to maintain the /// alignment of subsequent objects in TrailingObjects. @@ -3975,7 +3988,13 @@ /// The number of types in the exception specification. /// A whole unsigned is not needed here and according to /// [implimits] 8 bits would be enough here. - uint16_t NumExceptionType = 0; + unsigned NumExceptionType : 10; + + /// Any AArch64 SME ACLE type attributes that need to be propagated + /// on declarations and function pointers. + unsigned AArch64SMEAttributes : 6; + FunctionTypeExtraBitfields() + : NumExceptionType(0), AArch64SMEAttributes(SME_NormalFunction) {} }; protected: @@ -4154,18 +4173,22 @@ /// the various bits of extra information about a function prototype. struct ExtProtoInfo { FunctionType::ExtInfo ExtInfo; - bool Variadic : 1; - bool HasTrailingReturn : 1; + unsigned Variadic : 1; + unsigned HasTrailingReturn : 1; + unsigned AArch64SMEAttributes : 6; Qualifiers TypeQuals; RefQualifierKind RefQualifier = RQ_None; ExceptionSpecInfo ExceptionSpec; const ExtParameterInfo *ExtParameterInfos = nullptr; SourceLocation EllipsisLoc; - ExtProtoInfo() : Variadic(false), HasTrailingReturn(false) {} + ExtProtoInfo() + : Variadic(false), HasTrailingReturn(false), + AArch64SMEAttributes(SME_NormalFunction) {} ExtProtoInfo(CallingConv CC) - : ExtInfo(CC), Variadic(false), HasTrailingReturn(false) {} + : ExtInfo(CC), Variadic(false), HasTrailingReturn(false), + AArch64SMEAttributes(SME_NormalFunction) {} ExtProtoInfo withExceptionSpec(const ExceptionSpecInfo &ESI) { ExtProtoInfo Result(*this); @@ -4174,7 +4197,15 @@ } bool requiresFunctionProtoTypeExtraBitfields() const { - return ExceptionSpec.Type == EST_Dynamic; + return ExceptionSpec.Type == EST_Dynamic || + AArch64SMEAttributes != SME_NormalFunction; + } + + void setArmSMEAttribute(AArch64SMETypeAttributes Kind, bool Enable = true) { + if (Enable) + AArch64SMEAttributes |= Kind; + else + AArch64SMEAttributes &= ~Kind; } }; @@ -4301,6 +4332,7 @@ EPI.TypeQuals = getMethodQuals(); EPI.RefQualifier = getRefQualifier(); EPI.ExtParameterInfos = getExtParameterInfosOrNull(); + EPI.AArch64SMEAttributes = getAArch64SMEAttributes(); return EPI; } @@ -4482,6 +4514,15 @@ return getTrailingObjects(); } + /// Return a bitmask describing the SME attributes on the function type, see + /// AArch64SMETypeAttributes for their values. + unsigned getAArch64SMEAttributes() const { + if (!hasExtraBitfields()) + return SME_NormalFunction; + return getTrailingObjects() + ->AArch64SMEAttributes; + } + ExtParameterInfo getExtParameterInfo(unsigned I) const { assert(I < getNumParams() && "parameter index out of range"); if (hasExtParameterInfos()) diff --git a/clang/include/clang/AST/TypeProperties.td b/clang/include/clang/AST/TypeProperties.td --- a/clang/include/clang/AST/TypeProperties.td +++ b/clang/include/clang/AST/TypeProperties.td @@ -323,6 +323,9 @@ ? node->getExtParameterInfos() : llvm::ArrayRef() }]; } + def : Property<"AArch64SMEAttributes", UInt32> { + let Read = [{ node->getAArch64SMEAttributes() }]; + } def : Creator<[{ auto extInfo = FunctionType::ExtInfo(noReturn, hasRegParm, regParm, @@ -338,6 +341,7 @@ epi.ExceptionSpec = exceptionSpecifier; epi.ExtParameterInfos = extParameterInfo.empty() ? nullptr : extParameterInfo.data(); + epi.AArch64SMEAttributes = AArch64SMEAttributes; return ctx.getFunctionType(returnType, parameters, epi); }]>; } diff --git a/clang/include/clang/Basic/Attr.td b/clang/include/clang/Basic/Attr.td --- a/clang/include/clang/Basic/Attr.td +++ b/clang/include/clang/Basic/Attr.td @@ -2439,6 +2439,39 @@ let Documentation = [ArmSmeStreamingDocs]; } +def ArmStreamingCompatible : TypeAttr, TargetSpecificAttr { + let Spellings = [RegularKeyword<"__arm_streaming_compatible">]; + let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>; + let Documentation = [ArmSmeStreamingCompatibleDocs]; +} + +def ArmSharedZA : TypeAttr, TargetSpecificAttr { + let Spellings = [RegularKeyword<"__arm_shared_za">]; + let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>; + let Documentation = [ArmSmeSharedZADocs]; +} + +def ArmPreservesZA : TypeAttr, TargetSpecificAttr { + let Spellings = [RegularKeyword<"__arm_preserves_za">]; + let Subjects = SubjectList<[HasFunctionProto], ErrorDiag>; + let Documentation = [ArmSmePreservesZADocs]; +} + +def ArmLocallyStreaming : InheritableAttr, TargetSpecificAttr { + let Spellings = [RegularKeyword<"__arm_locally_streaming">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [ArmSmeLocallyStreamingDocs]; +} + +def ArmNewZA : InheritableAttr, TargetSpecificAttr { + let Spellings = [RegularKeyword<"__arm_new_za">]; + let Subjects = SubjectList<[Function], ErrorDiag>; + let Documentation = [ArmSmeNewZADocs]; +} +def : MutualExclusions<[ArmNewZA, ArmSharedZA]>; +def : MutualExclusions<[ArmNewZA, ArmPreservesZA]>; + + def Pure : InheritableAttr { let Spellings = [GCC<"pure">]; let Documentation = [Undocumented]; diff --git a/clang/include/clang/Basic/AttrDocs.td b/clang/include/clang/Basic/AttrDocs.td --- a/clang/include/clang/Basic/AttrDocs.td +++ b/clang/include/clang/Basic/AttrDocs.td @@ -6581,40 +6581,147 @@ }]; } -def ArmSmeStreamingDocs : Documentation { - let Category = DocCatType; +def DocCatArmSmeAttributes : DocumentationCategory<"AArch64 SME Attributes"> { let Content = [{ -.. Note:: This attribute has not been implemented yet, but once it is - implemented, it will behave as described below. +Clang supports a number of AArch64-specific attributes to manage state +added by the Scalable Matrix Extension (SME). This state includes the +runtime mode that the processor is in (e.g. non-streaming or streaming) +as well as the state of the ``ZA`` Matrix Storage. -The ``__arm_streaming`` keyword is only available on AArch64 targets. -It applies to function types and specifies that the function has a -"streaming interface". This means that: +The attributes come in the form of type- and declaration attributes: -* the function requires the Scalable Matrix Extension (SME) +* The SME declaration attributes can appear anywhere that a standard + ``[[...]]`` declaration attribute can appear. -* the function must be entered in streaming mode (that is, with PSTATE.SM - set to 1) +* The SME type attributes apply only to prototyped functions and can appear + anywhere that a standard ``[[...]]`` type attribute can appear. The SME + type attributes do not apply to functions having a K&R-style + unprototyped function type. -* the function must return in streaming mode - -* the function does not have a K&R-style unprototyped function type. +See `Arm C Language Extensions `_ +for more details about the features related to the SME extension. See `Procedure Call Standard for the Arm® 64-bit Architecture (AArch64) `_ for more details about -streaming-interface functions. +streaming-interface functions and shared/private-ZA interface functions. + }]; +} + +def ArmSmeStreamingDocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_streaming`` keyword applies to prototyped function types and specifies +that the function has a "streaming interface". This means that: + +* the function requires that the processor implements the Scalable Matrix + Extension (SME). + +* the function must be entered in streaming mode (that is, with PSTATE.SM + set to 1) + +* the function must return in streaming mode Clang manages PSTATE.SM automatically; it is not the source code's -responsibility to do this. For example, if a normal non-streaming +responsibility to do this. For example, if a non-streaming function calls an ``__arm_streaming`` function, Clang generates code that switches into streaming mode before calling the function and switches back to non-streaming mode on return. + }]; +} + +def ArmSmeStreamingCompatibleDocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_streaming_compatible`` keyword applies to prototyped function types and +specifies that the function has a “streaming compatible interface”. This +means that: -``__arm_streaming`` can appear anywhere that a standard ``[[...]]`` type -attribute can appear. +* the function may be entered in either non-streaming mode (PSTATE.SM=0) or + in streaming mode (PSTATE.SM=1). -See `Arm C Language Extensions `_ -for more details about this extension, and for other related SME features. +* the function must return in the same mode as it was entered. + +* the code executed in the function is compatible with either mode. + +Clang manages PSTATE.SM automatically; it is not the source code's +responsibility to do this. Clang will ensure that the generated code in +streaming-compatible functions is valid in either mode (PSTATE.SM=0 or +PSTATE.SM=1). For example, if an ``__arm_streaming_compatible`` function calls a +non-streaming function, Clang generates code to temporarily switch out of streaming +mode before calling the function and switch back to streaming-mode on return if +``PSTATE.SM`` is ``1`` on entry of the caller. If ``PSTATE.SM`` is ``0`` on +entry to the ``__arm_streaming_compatible`` function, the call will be executed +without changing modes. + }]; +} + +def ArmSmeSharedZADocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_shared_za`` keyword applies to prototyped function types and specifies +that the function shares SME's matrix storage (ZA) with its caller. This +means that: + +* the function requires that the processor implements the Scalable Matrix + Extension (SME). + +* the function enters with ZA in an active state. + +* the function returns with ZA in an active state. + }]; +} + +def ArmSmePreservesZADocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_preserves_za`` keyword applies to prototyped function types and +specifies that the function does not modify ZA state. + }]; +} + + +def ArmSmeLocallyStreamingDocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_locally_streaming`` keyword applies to function declarations +and specifies that all the statements in the function are executed in +streaming mode. This means that: + +* the function requires that the target processor implements the Scalable Matrix + Extension (SME). + +* the program automatically puts the machine into streaming mode before + executing the statements and automatically restores the previous mode + afterwards. + +Clang manages PSTATE.SM automatically; it is not the source code's +responsibility to do this. For example, Clang will emit code to enable +streaming mode at the start of the function, and disable streaming mode +at the end of the function. + }]; +} + +def ArmSmeNewZADocs : Documentation { + let Category = DocCatArmSmeAttributes; + let Content = [{ +The ``__arm_new_za`` keyword applies to function declarations and specifies +that the function will be set up with a fresh ZA context. + +This means that: + +* the function requires that the target processor implements the Scalable Matrix + Extension (SME). + +* the function will commit any lazily saved ZA data. + +* the function will create a new ZA context and enable PSTATE.ZA. + +* the function will disable PSTATE.ZA (by setting it to 0) before returning. + +For ``__arm_new_za`` functions Clang will set up the ZA context automatically +on entry to the function, and disable it before returning. For example, if ZA is +in a dormant state Clang will generate the code to commit a lazy-save and set up +a new ZA state before executing user code. }]; } diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -2035,6 +2035,10 @@ "than the function it overrides}1,2">; def note_overridden_virtual_function : Note< "overridden virtual function is here">; +def err_conflicting_overriding_attributes : Error< + "virtual function %0 has different attributes " + "%diff{($) than the function it overrides (which has $)|" + "than the function it overrides}1,2">; def err_conflicting_overriding_cc_attributes : Error< "virtual function %0 has different calling convention attributes " "%diff{($) than the function it overrides (which has calling convention $)|" @@ -3621,6 +3625,8 @@ "the vecreturn attribute can only be used on a class or structure with one member, which must be a vector">; def err_attribute_vecreturn_only_pod_record : Error< "the vecreturn attribute can only be used on a POD (plain old data) class or structure (i.e. no virtual functions)">; +def err_sme_attr_mismatch : Error< + "function declared %0 was previously declared %1, which has different SME function attributes">; def err_cconv_change : Error< "function declared '%0' here was previously declared " "%select{'%2'|without calling convention}1">; diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -7086,6 +7086,16 @@ NestedNameSpecInfo &IdInfo, bool EnteringContext); + /// The kind of conversion to check for. Either all attributes must match exactly, + /// or the converted type may add/drop '__arm_preserves_za'. + enum class AArch64SMECallConversionKind { + MatchExactly, + MayAddPreservesZA, + MayDropPreservesZA, + }; + bool IsInvalidSMECallConversion(QualType FromType, QualType ToType, + AArch64SMECallConversionKind C); + /// The parser has parsed a nested-name-specifier /// 'template[opt] template-name < template-args >::'. /// diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -3391,12 +3391,19 @@ argSlot[i] = params[i]; } + // Propagate the SME ACLE attributes. + if (epi.AArch64SMEAttributes != SME_NormalFunction) { + auto &ExtraBits = *getTrailingObjects(); + assert(epi.AArch64SMEAttributes <= SME_AttributeMask && + "Not enough bits to encode SME attributes"); + ExtraBits.AArch64SMEAttributes = epi.AArch64SMEAttributes; + } + // Fill in the exception type array if present. if (getExceptionSpecType() == EST_Dynamic) { auto &ExtraBits = *getTrailingObjects(); size_t NumExceptions = epi.ExceptionSpec.Exceptions.size(); - assert(NumExceptions <= UINT16_MAX && - "Not enough bits to encode exceptions"); + assert(NumExceptions <= 1023 && "Not enough bits to encode exceptions"); ExtraBits.NumExceptionType = NumExceptions; assert(hasExtraBitfields() && "missing trailing extra bitfields!"); @@ -3553,8 +3560,11 @@ // This is followed by an optional "consumed argument" section of the // same length as the first type sequence: // bool* - // Finally, we have the ext info and trailing return type flag: - // int bool + // This is followed by the ext info: + // int + // Finally we have a trailing return type flag (bool) + // combined with AArch64 SME Attributes, to save space: + // int // // There is no ambiguity between the consumed arguments and an empty EH // spec because of the leading 'bool' which unambiguously indicates @@ -3587,8 +3597,9 @@ for (unsigned i = 0; i != NumParams; ++i) ID.AddInteger(epi.ExtParameterInfos[i].getOpaqueValue()); } + epi.ExtInfo.Profile(ID); - ID.AddBoolean(epi.HasTrailingReturn); + ID.AddInteger((epi.AArch64SMEAttributes << 1) | epi.HasTrailingReturn); } void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -938,6 +938,15 @@ FunctionType::ExtInfo Info = T->getExtInfo(); + if ((T->getAArch64SMEAttributes() & FunctionType::SME_PStateSMCompatibleMask)) + OS << " __arm_streaming_compatible"; + if ((T->getAArch64SMEAttributes() & FunctionType::SME_PStateSMEnabledMask)) + OS << " __arm_streaming"; + if ((T->getAArch64SMEAttributes() & FunctionType::SME_PStateZASharedMask)) + OS << " __arm_shared_za"; + if ((T->getAArch64SMEAttributes() & FunctionType::SME_PStateZAPreservedMask)) + OS << " __arm_preserves_za"; + printFunctionAfter(Info, OS); if (!T->getMethodQuals().empty()) @@ -1772,6 +1781,18 @@ OS << "__arm_streaming"; return; } + if (T->getAttrKind() == attr::ArmStreamingCompatible) { + OS << "__arm_streaming_compatible"; + return; + } + if (T->getAttrKind() == attr::ArmSharedZA) { + OS << "__arm_shared_za"; + return; + } + if (T->getAttrKind() == attr::ArmPreservesZA) { + OS << "__arm_preserves_za"; + return; + } OS << " __attribute__(("; switch (T->getAttrKind()) { @@ -1814,6 +1835,9 @@ case attr::AnnotateType: case attr::WebAssemblyFuncref: case attr::ArmStreaming: + case attr::ArmStreamingCompatible: + case attr::ArmSharedZA: + case attr::ArmPreservesZA: llvm_unreachable("This attribute should have been handled already"); case attr::NSReturnsRetained: diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1761,6 +1761,15 @@ if (!isUnresolvedExceptionSpec(FPT->getExceptionSpecType()) && FPT->isNothrow()) FuncAttrs.addAttribute(llvm::Attribute::NoUnwind); + + if (FPT->getAArch64SMEAttributes() & FunctionType::SME_PStateSMEnabledMask) + FuncAttrs.addAttribute("aarch64_pstate_sm_enabled"); + if (FPT->getAArch64SMEAttributes() & FunctionType::SME_PStateSMCompatibleMask) + FuncAttrs.addAttribute("aarch64_pstate_sm_compatible"); + if (FPT->getAArch64SMEAttributes() & FunctionType::SME_PStateZASharedMask) + FuncAttrs.addAttribute("aarch64_pstate_za_shared"); + if (FPT->getAArch64SMEAttributes() & FunctionType::SME_PStateZAPreservedMask) + FuncAttrs.addAttribute("aarch64_pstate_za_preserved"); } static void AddAttributesFromAssumes(llvm::AttrBuilder &FuncAttrs, @@ -2406,6 +2415,12 @@ if (TargetDecl->hasAttr() && getLangOpts().OffloadUniformBlock) FuncAttrs.addAttribute("uniform-work-group-size", "true"); + + if (TargetDecl->hasAttr()) + FuncAttrs.addAttribute("aarch64_pstate_sm_body"); + + if (TargetDecl->hasAttr()) + FuncAttrs.addAttribute("aarch64_pstate_za_new"); } // Attach "no-builtins" attributes to: diff --git a/clang/lib/CodeGen/CodeGenModule.cpp b/clang/lib/CodeGen/CodeGenModule.cpp --- a/clang/lib/CodeGen/CodeGenModule.cpp +++ b/clang/lib/CodeGen/CodeGenModule.cpp @@ -2293,6 +2293,14 @@ return; } + // Handle SME attributes that apply to function definitions, + // rather than to function prototypes. + if (D->hasAttr()) + B.addAttribute("aarch64_pstate_sm_body"); + + if (D->hasAttr()) + B.addAttribute("aarch64_pstate_za_new"); + // Track whether we need to add the optnone LLVM attribute, // starting with the default for this optimization level. bool ShouldAddOptNone = diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -3770,6 +3770,16 @@ } } + // It is not permitted to redeclare an SME function with different SME + // attributes. + if (IsInvalidSMECallConversion(Old->getType(), New->getType(), + AArch64SMECallConversionKind::MatchExactly)) { + Diag(New->getLocation(), diag::err_sme_attr_mismatch) + << New->getType() << Old->getType(); + Diag(OldLocation, diag::note_previous_declaration); + return true; + } + // If a function is first declared with a calling convention, but is later // declared or defined without one, all following decls assume the calling // convention of the first. diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -5354,9 +5354,6 @@ case ParsedAttr::AT_AArch64SVEPcs: CC = CC_AArch64SVEPCS; break; - case ParsedAttr::AT_ArmStreaming: - CC = CC_C; // FIXME: placeholder until real SME support is added. - break; case ParsedAttr::AT_AMDGPUKernelCall: CC = CC_AMDGPUKernelCall; break; @@ -8731,6 +8728,28 @@ return false; } + +static void handleArmNewZaAttr(Sema &S, Decl *D, const ParsedAttr &AL) { + if (auto *FPT = dyn_cast(D->getFunctionType())) { + if (FPT->getAArch64SMEAttributes() & + FunctionType::SME_PStateZASharedMask) { + S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible) + << AL << "'__arm_shared_za'" << true; + AL.setInvalid(); + } + if (FPT->getAArch64SMEAttributes() & + FunctionType::SME_PStateZAPreservedMask) { + S.Diag(AL.getLoc(), diag::err_attributes_are_not_compatible) + << AL << "'__arm_preserves_za'" << true; + AL.setInvalid(); + } + if (AL.isInvalid()) + return; + } + + handleSimpleAttribute(S, D, AL); +} + /// ProcessDeclAttribute - Apply the specific attribute to the specified decl if /// the attribute applies to decls. If the attribute is a type attribute, just /// silently ignore it if a GNU attribute. @@ -9486,6 +9505,14 @@ handleArmBuiltinAliasAttr(S, D, AL); break; + case ParsedAttr::AT_ArmLocallyStreaming: + handleSimpleAttribute(S, D, AL); + break; + + case ParsedAttr::AT_ArmNewZA: + handleArmNewZaAttr(S, D, AL); + break; + case ParsedAttr::AT_AcquireHandle: handleAcquireHandleAttr(S, D, AL); break; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -18049,6 +18049,16 @@ } } + // SME attributes must match when overriding a function declaration. + if (IsInvalidSMECallConversion( + Old->getType(), New->getType(), + AArch64SMECallConversionKind::MayAddPreservesZA)) { + Diag(New->getLocation(), diag::err_conflicting_overriding_attributes) + << New << New->getType() << Old->getType(); + Diag(Old->getLocation(), diag::note_overridden_virtual_function); + return true; + } + // Virtual overrides must have the same code_seg. const auto *OldCSA = Old->getAttr(); const auto *NewCSA = New->getAttr(); diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -9679,6 +9679,40 @@ ColonLoc, result, VK, OK); } +// Check that the SME attributes for PSTATE.ZA and PSTATE.SM are compatible. +bool Sema::IsInvalidSMECallConversion(QualType FromType, QualType ToType, + AArch64SMECallConversionKind C) { + unsigned FromAttributes = 0, ToAttributes = 0; + if (const auto *FromFn = + dyn_cast(Context.getCanonicalType(FromType))) + FromAttributes = + FromFn->getAArch64SMEAttributes() & FunctionType::SME_AttributeMask; + if (const auto *ToFn = + dyn_cast(Context.getCanonicalType(ToType))) + ToAttributes = + ToFn->getAArch64SMEAttributes() & FunctionType::SME_AttributeMask; + + if (FromAttributes == ToAttributes) + return false; + + // If the '__arm_preserves_za' is the only difference between the types, + // check whether we're allowed to add or remove it. + if ((FromAttributes ^ ToAttributes) == + FunctionType::SME_PStateZAPreservedMask) { + switch (C) { + case AArch64SMECallConversionKind::MatchExactly: + return true; + case AArch64SMECallConversionKind::MayAddPreservesZA: + return !(ToAttributes & FunctionType::SME_PStateZAPreservedMask); + case AArch64SMECallConversionKind::MayDropPreservesZA: + return !(FromAttributes & FunctionType::SME_PStateZAPreservedMask); + } + } + + // There has been a mismatch of attributes + return true; +} + // Check if we have a conversion between incompatible cmse function pointer // types, that is, a conversion between a function pointer with the // cmse_nonsecure_call attribute and one without. @@ -9845,6 +9879,10 @@ return Sema::IncompatibleFunctionPointer; if (IsInvalidCmseNSCallConversion(S, ltrans, rtrans)) return Sema::IncompatibleFunctionPointer; + if (S.IsInvalidSMECallConversion( + rtrans, ltrans, + Sema::AArch64SMECallConversionKind::MayDropPreservesZA)) + return Sema::IncompatibleFunctionPointer; return ConvTy; } diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1687,6 +1687,26 @@ Changed = true; } + // Drop the 'arm_preserves_za' if not present in the target type (we can do + // that because it is merely a hint). + if (const auto *FromFPT = dyn_cast(FromFn)) { + FunctionProtoType::ExtProtoInfo ExtInfo = FromFPT->getExtProtoInfo(); + if (ExtInfo.AArch64SMEAttributes & + FunctionType::SME_PStateZAPreservedMask) { + unsigned ToFlags = 0; + if (const auto *ToFPT = dyn_cast(ToFn)) + ToFlags = ToFPT->getExtProtoInfo().AArch64SMEAttributes; + if (!(ToFlags & FunctionType::SME_PStateZAPreservedMask)) { + ExtInfo.setArmSMEAttribute(FunctionType::SME_PStateZAPreservedMask, + false); + QualType QT = Context.getFunctionType( + FromFPT->getReturnType(), FromFPT->getParamTypes(), ExtInfo); + FromFn = QT->getAs(); + Changed = true; + } + } + } + // Drop 'noexcept' if not present in target type. if (const auto *FromFPT = dyn_cast(FromFn)) { const auto *ToFPT = cast(ToFn); diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -128,7 +128,6 @@ case ParsedAttr::AT_VectorCall: \ case ParsedAttr::AT_AArch64VectorPcs: \ case ParsedAttr::AT_AArch64SVEPcs: \ - case ParsedAttr::AT_ArmStreaming: \ case ParsedAttr::AT_AMDGPUKernelCall: \ case ParsedAttr::AT_MSABI: \ case ParsedAttr::AT_SysVABI: \ @@ -143,6 +142,10 @@ case ParsedAttr::AT_NoReturn: \ case ParsedAttr::AT_Regparm: \ case ParsedAttr::AT_CmseNSCall: \ + case ParsedAttr::AT_ArmStreaming: \ + case ParsedAttr::AT_ArmStreamingCompatible: \ + case ParsedAttr::AT_ArmSharedZA: \ + case ParsedAttr::AT_ArmPreservesZA: \ case ParsedAttr::AT_AnyX86NoCallerSavedRegisters: \ case ParsedAttr::AT_AnyX86NoCfCheck: \ CALLING_CONV_ATTRS_CASELIST @@ -7772,6 +7775,26 @@ llvm_unreachable("unexpected attribute kind!"); } +static bool checkMutualExclusion(TypeProcessingState &state, + const FunctionProtoType::ExtProtoInfo &EPI, + ParsedAttr &Attr, + AttributeCommonInfo::Kind OtherKind) { + auto OtherAttr = std::find_if( + state.getCurrentAttributes().begin(), state.getCurrentAttributes().end(), + [OtherKind](const ParsedAttr &A) { return A.getKind() == OtherKind; }); + if (OtherAttr == state.getCurrentAttributes().end() || OtherAttr->isInvalid()) + return false; + + Sema &S = state.getSema(); + S.Diag(Attr.getLoc(), diag::err_attributes_are_not_compatible) + << *OtherAttr << Attr + << (OtherAttr->isRegularKeywordAttribute() || + Attr.isRegularKeywordAttribute()); + S.Diag(OtherAttr->getLoc(), diag::note_conflicting_attribute); + Attr.setInvalid(); + return true; +} + /// Process an individual function attribute. Returns true to /// indicate that the attribute was handled, false if it wasn't. static bool handleFunctionTypeAttr(TypeProcessingState &state, ParsedAttr &attr, @@ -7901,6 +7924,55 @@ return true; } + if (attr.getKind() == ParsedAttr::AT_ArmStreaming || + attr.getKind() == ParsedAttr::AT_ArmStreamingCompatible || + attr.getKind() == ParsedAttr::AT_ArmSharedZA || + attr.getKind() == ParsedAttr::AT_ArmPreservesZA){ + if (S.CheckAttrTarget(attr) || S.CheckAttrNoArgs(attr)) + return true; + + if (!unwrapped.isFunctionType()) + return false; + + const auto *FnTy = unwrapped.get()->getAs(); + if (!FnTy) { + // SME ACLE attributes are not supported on K&R-style unprototyped C + // functions. + S.Diag(attr.getLoc(), diag::warn_attribute_wrong_decl_type) << + attr << attr.isRegularKeywordAttribute() << ExpectedFunctionWithProtoType; + attr.setInvalid(); + return false; + } + + FunctionProtoType::ExtProtoInfo EPI = FnTy->getExtProtoInfo(); + switch (attr.getKind()) { + case ParsedAttr::AT_ArmStreaming: + if (checkMutualExclusion(state, EPI, attr, + ParsedAttr::AT_ArmStreamingCompatible)) + return true; + EPI.setArmSMEAttribute(FunctionType::SME_PStateSMEnabledMask); + break; + case ParsedAttr::AT_ArmStreamingCompatible: + if (checkMutualExclusion(state, EPI, attr, ParsedAttr::AT_ArmStreaming)) + return true; + EPI.setArmSMEAttribute(FunctionType::SME_PStateSMCompatibleMask); + break; + case ParsedAttr::AT_ArmSharedZA: + EPI.setArmSMEAttribute(FunctionType::SME_PStateZASharedMask); + break; + case ParsedAttr::AT_ArmPreservesZA: + EPI.setArmSMEAttribute(FunctionType::SME_PStateZAPreservedMask); + break; + default: + llvm_unreachable("Unsupported attribute"); + } + + QualType newtype = S.Context.getFunctionType(FnTy->getReturnType(), + FnTy->getParamTypes(), EPI); + type = unwrapped.wrap(S, newtype->getAs()); + return true; + } + if (attr.getKind() == ParsedAttr::AT_NoThrow) { // Delay if this is not a function type. if (!unwrapped.isFunctionType()) diff --git a/clang/test/AST/ast-dump-sme-attributes.cpp b/clang/test/AST/ast-dump-sme-attributes.cpp new file mode 100644 --- /dev/null +++ b/clang/test/AST/ast-dump-sme-attributes.cpp @@ -0,0 +1,66 @@ +// Test without serialization: +// RUN: %clang_cc1 -triple aarch64 -target-feature +sme -std=c++2a -ast-dump -ast-dump-filter Foo %s | FileCheck -strict-whitespace %s + +// Test with serialization: +// RUN: %clang_cc1 -std=c++20 -triple aarch64 -target-feature +sme -emit-pch -o %t %s +// RUN: %clang_cc1 -x c++ -std=c++20 -triple aarch64 -target-feature +sme -include-pch %t -ast-dump-all -ast-dump-filter Foo /dev/null \ +// RUN: | sed -e "s/ //" -e "s/ imported//" \ +// RUN: | FileCheck --strict-whitespace %s + +struct Foo { +// CHECK: |-CXXRecordDecl {{.*}} implicit struct Foo +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_streaming 'void () __arm_streaming' +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_streaming_compatible 'void () __arm_streaming_compatible' +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_locally_streaming 'void ()' +// CHECK-NEXT: | `-ArmLocallyStreamingAttr +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_shared_za 'void () __arm_shared_za' +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_new_za 'void ()' +// CHECK-NEXT: | `-ArmNewZAAttr +// CHECK-NEXT: |-CXXMethodDecl {{.*}} f_preserves_za 'void () __arm_preserves_za' + void f_streaming() __arm_streaming; + void f_streaming_compatible() __arm_streaming_compatible; + __arm_locally_streaming void f_locally_streaming(); + void f_shared_za() __arm_shared_za; + __arm_new_za void f_new_za(); + void f_preserves_za() __arm_preserves_za; + + +// CHECK: |-CXXMethodDecl {{.*}} test_lambda 'int (int)' implicit-inline +// CHECK: `-CompoundStmt +// CHECK-NEXT: |-DeclStmt +// CHECK-NEXT: | `-VarDecl +// CHECK-NEXT: | `-LambdaExpr +// CHECK-NEXT: | |-CXXRecordDecl +// CHECK: | | |-CXXMethodDecl {{.*}} used constexpr operator() 'int (int) __arm_streaming const' inline +// CHECK: | | |-CXXConversionDecl {{.*}} implicit constexpr operator int (*)(int) __arm_streaming 'int (*() const noexcept)(int) __arm_streaming' inline +// CHECK: | | |-CXXMethodDecl {{.*}} implicit __invoke 'int (int) __arm_streaming' static inline +// CHECK: `-ReturnStmt +// CHECK: `-CXXOperatorCallExpr +// CHECK-NEXT: |-ImplicitCastExpr {{.*}} 'int (*)(int) __arm_streaming const' +// CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int (int) __arm_streaming const' lvalue CXXMethod {{.*}} 'operator()' 'int (int) __arm_streaming const' + int test_lambda(int x) { + auto F = [](int x) __arm_streaming { return x; }; + return F(x); + } + +// CHECK: |-TypedefDecl {{.*}} referenced s_ptrty 'void (*)(int, int) __arm_streaming' +// CHECK-NEXT: | `-PointerType {{.*}} 'void (*)(int, int) __arm_streaming' +// CHECK-NEXT: | `-ParenType {{.*}} 'void (int, int) __arm_streaming' sugar +// CHECK-NEXT: | `-FunctionProtoType {{.*}} 'void (int, int) __arm_streaming' cdecl + typedef void (*s_ptrty) (int, int) __arm_streaming; + +// CHECK: `-CXXMethodDecl {{.*}} test_streaming_ptrty 'void (s_ptrty, int, int)' implicit-inline +// CHECK-NEXT: |-ParmVarDecl {{.*}} used f 's_ptrty':'void (*)(int, int) __arm_streaming' +// CHECK-NEXT: |-ParmVarDecl {{.*}} used x 'int' +// CHECK-NEXT: |-ParmVarDecl {{.*}} used y 'int' +// CHECK: `-CompoundStmt +// CHECK-NEXT: `-ReturnStmt +// CHECK-NEXT: `-CallExpr +// CHECK-NEXT: |-ImplicitCastExpr {{.*}} 's_ptrty':'void (*)(int, int) __arm_streaming' +// CHECK-NEXT: | `-DeclRefExpr {{.*}} 's_ptrty':'void (*)(int, int) __arm_streaming' lvalue ParmVar {{.*}} 'f' 's_ptrty':'void (*)(int, int) __arm_streaming' +// CHECK-NEXT: |-ImplicitCastExpr {{.*}} 'int' +// CHECK-NEXT: | `-DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'x' 'int' +// CHECK-NEXT: `-ImplicitCastExpr {{.*}} 'int' +// CHECK-NEXT: `-DeclRefExpr {{.*}} 'int' lvalue ParmVar {{.*}} 'y' 'int' + void test_streaming_ptrty(s_ptrty f, int x, int y) { return f(x, y); }; +}; diff --git a/clang/test/CodeGen/aarch64-sme-intrinsics/aarch64-sme-attrs.cpp b/clang/test/CodeGen/aarch64-sme-intrinsics/aarch64-sme-attrs.cpp new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/aarch64-sme-intrinsics/aarch64-sme-attrs.cpp @@ -0,0 +1,303 @@ +// RUN: %clang_cc1 -triple aarch64-none-linux-gnu -target-feature +sme \ +// RUN: -S -disable-O0-optnone -Werror -emit-llvm -o - %s \ +// RUN: | opt -S -passes=mem2reg \ +// RUN: | opt -S -passes=inline \ +// RUN: | FileCheck %s + +extern "C" { + +extern int normal_callee(); + +// == FUNCTION DECLARATIONS == + +int streaming_decl(void) __arm_streaming; +int streaming_compatible_decl(void) __arm_streaming_compatible; +int shared_za_decl(void) __arm_shared_za; +int preserves_za_decl(void) __arm_preserves_za; +int private_za_decl(void); + +// == FUNCTION DEFINITIONS == + +// CHECK-LABEL: @streaming_caller() +// CHECK-SAME: #[[SM_ENABLED:[0-9]+]] +// CHECK: call i32 @normal_callee() +// + int streaming_caller() __arm_streaming { + return normal_callee(); +} + +// CHECK: declare i32 @normal_callee() #[[NORMAL_DECL:[0-9]+]] + + +// CHECK-LABEL: @streaming_callee() +// CHECK-SAME: #[[SM_ENABLED]] +// CHECK: call i32 @streaming_decl() #[[SM_ENABLED_CALL:[0-9]+]] +// + int streaming_callee() __arm_streaming { + return streaming_decl(); +} + +// CHECK: declare i32 @streaming_decl() #[[SM_ENABLED_DECL:[0-9]+]] + +// CHECK-LABEL: @streaming_compatible_caller() +// CHECK-SAME: #[[SM_COMPATIBLE:[0-9]+]] +// CHECK: call i32 @normal_callee() +// + int streaming_compatible_caller() __arm_streaming_compatible { + return normal_callee(); +} + +// CHECK-LABEL: @streaming_compatible_callee() +// CHECK-SAME: #[[SM_COMPATIBLE]] +// CHECK: call i32 @streaming_compatible_decl() #[[SM_COMPATIBLE_CALL:[0-9]+]] +// + int streaming_compatible_callee() __arm_streaming_compatible { + return streaming_compatible_decl(); +} + +// CHECK: declare i32 @streaming_compatible_decl() #[[SM_COMPATIBLE_DECL:[0-9]+]] + +// CHECK-LABEL: @locally_streaming_caller() +// CHECK-SAME: #[[SM_BODY:[0-9]+]] +// CHECK: call i32 @normal_callee() +// +__arm_locally_streaming int locally_streaming_caller() { + return normal_callee(); +} + +// CHECK-LABEL: @locally_streaming_callee() +// CHECK-SAME: #[[SM_BODY]] +// CHECK: call i32 @locally_streaming_caller() #[[SM_BODY_CALL:[0-9]+]] +// +__arm_locally_streaming int locally_streaming_callee() { + return locally_streaming_caller(); +} + + +// CHECK-LABEL: @shared_za_caller() +// CHECK-SAME: #[[ZA_SHARED:[0-9]+]] +// CHECK: call i32 @normal_callee() +// + int shared_za_caller() __arm_shared_za { + return normal_callee(); +} + +// CHECK-LABEL: @shared_za_callee() +// CHECK-SAME: #[[ZA_SHARED]] +// CHECK: call i32 @shared_za_decl() #[[ZA_SHARED_CALL:[0-9]+]] +// + int shared_za_callee() __arm_shared_za { + return shared_za_decl(); +} + +// CHECK: declare i32 @shared_za_decl() #[[ZA_SHARED_DECL:[0-9]+]] + + +// CHECK-LABEL: @preserves_za_caller() +// CHECK-SAME: #[[ZA_PRESERVED:[0-9]+]] +// CHECK: call i32 @normal_callee() +// + int preserves_za_caller() __arm_preserves_za { + return normal_callee(); +} + +// CHECK-LABEL: @preserves_za_callee() +// CHECK-SAME: #[[ZA_PRESERVED]] +// CHECK: call i32 @preserves_za_decl() #[[ZA_PRESERVED_CALL:[0-9]+]] +// + int preserves_za_callee() __arm_preserves_za { + return preserves_za_decl(); +} + +// CHECK: declare i32 @preserves_za_decl() #[[ZA_PRESERVED_DECL:[0-9]+]] + + +// CHECK-LABEL: @new_za_caller() +// CHECK-SAME: #[[ZA_NEW:[0-9]+]] +// CHECK: call i32 @normal_callee() +// +__arm_new_za int new_za_caller() { + return normal_callee(); +} + +// CHECK-LABEL: @new_za_callee() +// CHECK-SAME: #[[ZA_NEW]] +// CHECK: call i32 @private_za_decl() +// +__arm_new_za int new_za_callee() { + return private_za_decl(); +} + +// CHECK: declare i32 @private_za_decl() + + +// Ensure that the attributes are correctly propagated to function types +// and also to callsites. +typedef void (*s_ptrty) (int, int) __arm_streaming; +typedef void (*sc_ptrty) (int, int) __arm_streaming_compatible; +typedef void (*sz_ptrty) (int, int) __arm_shared_za; +typedef void (*pz_ptrty) (int, int) __arm_preserves_za; + +// CHECK-LABEL: @test_streaming_ptrty( +// CHECK-SAME: #[[NORMAL_DEF:[0-9]+]] +// CHECK: call void [[F:%.*]](i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[SM_ENABLED_CALL]] +// +void test_streaming_ptrty(s_ptrty f, int x, int y) { return f(x, y); } +// CHECK-LABEL: @test_streaming_compatible_ptrty( +// CHECK-SAME: #[[NORMAL_DEF]] +// CHECK: call void [[F:%.*]](i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[SM_COMPATIBLE_CALL]] +// +void test_streaming_compatible_ptrty(sc_ptrty f, int x, int y) { return f(x, y); } +// CHECK-LABEL: @test_shared_za( +// CHECK-SAME: #[[ZA_SHARED]] +// CHECK: call void [[F:%.*]](i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ZA_SHARED_CALL]] +// +void test_shared_za(sz_ptrty f, int x, int y) __arm_shared_za { return f(x, y); } +// CHECK-LABEL: @test_preserved_za( +// CHECK-SAME: #[[ZA_SHARED]] +// CHECK: call void [[F:%.*]](i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[ZA_PRESERVED_CALL]] +// +void test_preserved_za(pz_ptrty f, int x, int y) __arm_shared_za { return f(x, y); } + +// CHECK-LABEL: @test_indirect_streaming_ptrty( +// CHECK-SAME: #[[NORMAL_DEF:[0-9]+]] +// CHECK: call void [[F:%.*]](i32 noundef [[X:%.*]], i32 noundef [[Y:%.*]]) #[[SM_ENABLED_CALL]] +// +typedef s_ptrty **indirect_s_ptrty; +void test_indirect_streaming_ptrty(indirect_s_ptrty fptr, int x, int y) { return (**fptr)(x, y); } +} // extern "C" + +// +// Test that having the attribute in different places (on declaration and on type) +// both results in the attribute being applied to the type. +// + +// CHECK-LABEL: @_Z24test_same_type_streamingv( +// CHECK: call void @_Z10streaming1v() #[[SM_ENABLED_CALL]] +// CHECK: call void @_Z10streaming2v() #[[SM_ENABLED_CALL]] +// CHECK: call void @_Z20same_type_streaming1v() #[[SM_ENABLED_CALL]] +// CHECK: call void @_Z20same_type_streaming2v() #[[SM_ENABLED_CALL]] +// CHECK: ret void +// CHECK: } +// CHECK: declare void @_Z10streaming1v() #[[SM_ENABLED_DECL]] +// CHECK: declare void @_Z10streaming2v() #[[SM_ENABLED_DECL]] +// CHECK: declare void @_Z20same_type_streaming1v() #[[SM_ENABLED_DECL]] +// CHECK: declare void @_Z20same_type_streaming2v() #[[SM_ENABLED_DECL]] +void streaming1(void) __arm_streaming; +void streaming2() __arm_streaming; +decltype(streaming1) same_type_streaming1; +decltype(streaming2) same_type_streaming2; +void test_same_type_streaming() { + streaming1(); + streaming2(); + same_type_streaming1(); + same_type_streaming2(); +} + +// +// Test overloading; the attribute is not required for overloaded types and +// does not apply if not specified. +// + +// CHECK-LABEL: @_Z12overloadedfni( +// CHECK-SAME: #[[SM_ENABLED]] +int overloadedfn(int x) __arm_streaming { return x; } +// CHECK-LABEL: @_Z12overloadedfnf( +// CHECK-SAME: #[[NORMAL_DEF]] +// +float overloadedfn(float x) { return x; } +// CHECK-LABEL: @_Z13test_overloadi( +// CHECK-SAME: #[[NORMAL_DEF]] +// +int test_overload(int x) { return overloadedfn(x); } +// CHECK-LABEL: @_Z13test_overloadf( +// CHECK-SAME: #[[NORMAL_DEF]] +// +float test_overload(float x) { return overloadedfn(x); } + +// CHECK-LABEL: @_Z11test_lambdai( +// CHECK-SAME: #[[NORMAL_DEF]] +// CHECK: call noundef i32 @"_ZZ11test_lambdaiENK3$_0clEi"({{.*}}) #[[SM_ENABLED_CALL]] +// +// CHECK: @"_ZZ11test_lambdaiENK3$_0clEi"( +// CHECK-SAME: #[[SM_ENABLED]] +int test_lambda(int x) { + auto F = [](int x) __arm_streaming { return x; }; + return F(x); +} + +// CHECK-LABEL: @_Z27test_template_instantiationv( +// CHECK-SAME: #[[NORMAL_DEF]] +// CHECK: call noundef i32 @_Z15template_functyIiET_S0_(i32 noundef 12) #[[SM_ENABLED_CALL]] +// +// CHECK: @_Z15template_functyIiET_S0_( +// CHECK-SAME: #[[SM_ENABLED]] +template +Ty template_functy(Ty x) __arm_streaming { return x; } +int test_template_instantiation() { return template_functy(12); } + +// +// Test that arm_locally_streaming is inherited by future redeclarations, +// even when they don't specify the attribute. +// + +// CHECK: define {{.*}} @_Z25locally_streaming_inheritv( +// CHECK-SAME: #[[SM_BODY]] +__arm_locally_streaming void locally_streaming_inherit(); +void locally_streaming_inherit() { + streaming_decl(); +} + +// Test that the attributes are propagated properly to calls +// when using a variadic template as indirection. +__attribute__((always_inline)) +int call() { return 0; } + +template +__attribute__((always_inline)) +int call(T f, Other... other) { + return f() + call(other...); +} + +// CHECK: {{.*}} @_Z22test_variadic_templatev( +// CHECK: call {{.*}} i32 @normal_callee() #[[NOUNWIND_CALL:[0-9]+]] +// CHECK-NEXT: call {{.*}} i32 @streaming_decl() #[[NOUNWIND_SM_ENABLED_CALL:[0-9]+]] +// CHECK-NEXT: call {{.*}} i32 @streaming_compatible_decl() #[[NOUNWIND_SM_COMPATIBLE_CALL:[0-9]+]] +// CHECK-NEXT: call {{.*}} i32 @shared_za_decl() #[[NOUNWIND_ZA_SHARED_CALL:[0-9]+]] +// CHECK-NEXT: call {{.*}} i32 @preserves_za_decl() #[[NOUNWIND_ZA_PRESERVED_CALL:[0-9]+]] +// CHECK-NEXT: add nsw +// CHECK-NEXT: add nsw +// CHECK-NEXT: add nsw +// CHECK-NEXT: add nsw +// CHECK-NEXT: ret +int test_variadic_template() { + return call(normal_callee, + streaming_decl, + streaming_compatible_decl, + shared_za_decl, + preserves_za_decl); +} + +// CHECK: attributes #[[SM_ENABLED]] = { mustprogress noinline nounwind "aarch64_pstate_sm_enabled" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[NORMAL_DECL]] = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[SM_ENABLED_DECL]] = { "aarch64_pstate_sm_enabled" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[SM_COMPATIBLE]] = { mustprogress noinline nounwind "aarch64_pstate_sm_compatible" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[SM_COMPATIBLE_DECL]] = { "aarch64_pstate_sm_compatible" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[SM_BODY]] = { mustprogress noinline nounwind "aarch64_pstate_sm_body" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[ZA_SHARED]] = { mustprogress noinline nounwind "aarch64_pstate_za_shared" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[ZA_SHARED_DECL]] = { "aarch64_pstate_za_shared" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[ZA_PRESERVED]] = { mustprogress noinline nounwind "aarch64_pstate_za_preserved" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[ZA_PRESERVED_DECL]] = { "aarch64_pstate_za_preserved" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[ZA_NEW]] = { mustprogress noinline nounwind "aarch64_pstate_za_new" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[NORMAL_DEF]] = { mustprogress noinline nounwind "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-features"="+bf16,+sme" } +// CHECK: attributes #[[SM_ENABLED_CALL]] = { "aarch64_pstate_sm_enabled" } +// CHECK: attributes #[[SM_COMPATIBLE_CALL]] = { "aarch64_pstate_sm_compatible" } +// CHECK: attributes #[[SM_BODY_CALL]] = { "aarch64_pstate_sm_body" } +// CHECK: attributes #[[ZA_SHARED_CALL]] = { "aarch64_pstate_za_shared" } +// CHECK: attributes #[[ZA_PRESERVED_CALL]] = { "aarch64_pstate_za_preserved" } +// CHECK: attributes #[[NOUNWIND_CALL]] = { nounwind } +// CHECK: attributes #[[NOUNWIND_SM_ENABLED_CALL]] = { nounwind "aarch64_pstate_sm_enabled" } +// CHECK: attributes #[[NOUNWIND_SM_COMPATIBLE_CALL]] = { nounwind "aarch64_pstate_sm_compatible" } +// CHECK: attributes #[[NOUNWIND_ZA_SHARED_CALL]] = { nounwind "aarch64_pstate_za_shared" } +// CHECK: attributes #[[NOUNWIND_ZA_PRESERVED_CALL]] = { nounwind "aarch64_pstate_za_preserved" } + diff --git a/clang/test/Modules/aarch64-sme-keywords.cppm b/clang/test/Modules/aarch64-sme-keywords.cppm new file mode 100644 --- /dev/null +++ b/clang/test/Modules/aarch64-sme-keywords.cppm @@ -0,0 +1,65 @@ +// REQUIRES: aarch64-registered-target +// +// RUN: rm -rf %t +// RUN: split-file %s %t +// +// RUN: %clang_cc1 -std=c++20 -triple aarch64 -target-feature +sme %t/A.cppm -emit-module-interface -o %t/A.pcm +// RUN: %clang_cc1 -std=c++20 -triple aarch64 -target-feature +sme -fprebuilt-module-path=%t -I%t %t/Use.cpp -emit-llvm +// RUN: cat %t/Use.ll | FileCheck %s + +//--- A.cppm +module; +export module A; + +export void f_streaming(void) __arm_streaming { } +export void f_streaming_compatible(void) __arm_streaming_compatible { } +export void f_shared_za(void) __arm_shared_za { } +export void f_preserves_za(void) __arm_preserves_za { } + +//--- Use.cpp +// expected-no-diagnostics +import A; + +// CHECK: define dso_local void @_Z18f_shared_za_callerv() #[[SHARED_ZA_DEF:[0-9]+]] { +// CHECK: entry: +// CHECK: call void @_ZW1A11f_shared_zav() #[[SHARED_ZA_USE:[0-9]+]] +// CHECK: call void @_ZW1A14f_preserves_zav() #[[PRESERVES_ZA_USE:[0-9]+]] +// CHECK: ret void +// CHECK: } +// +// CHECK:declare void @_ZW1A11f_shared_zav() #[[SHARED_ZA_DECL:[0-9]+]] +// +// CHECK:declare void @_ZW1A14f_preserves_zav() #[[PRESERVES_ZA_DECL:[0-9]+]] +// +// CHECK:; Function Attrs: mustprogress noinline nounwind optnone +// CHECK:define dso_local void @_Z21f_nonstreaming_callerv() #[[NORMAL_DEF:[0-9]+]] { +// CHECK:entry: +// CHECK: call void @_ZW1A11f_streamingv() #[[STREAMING_USE:[0-9]+]] +// CHECK: call void @_ZW1A22f_streaming_compatiblev() #[[STREAMING_COMPATIBLE_USE:[0-9]+]] +// CHECK: ret void +// CHECK:} +// +// CHECK:declare void @_ZW1A11f_streamingv() #[[STREAMING_DECL:[0-9]+]] +// +// CHECK:declare void @_ZW1A22f_streaming_compatiblev() #[[STREAMING_COMPATIBLE_DECL:[0-9]+]] +// +// CHECK-DAG: attributes #[[SHARED_ZA_DEF]] = {{{.*}} "aarch64_pstate_za_shared" {{.*}}} +// CHECK-DAG: attributes #[[SHARED_ZA_DECL]] = {{{.*}} "aarch64_pstate_za_shared" {{.*}}} +// CHECK-DAG: attributes #[[PRESERVES_ZA_DECL]] = {{{.*}} "aarch64_pstate_za_preserved" {{.*}}} +// CHECK-DAG: attributes #[[NORMAL_DEF]] = {{{.*}}} +// CHECK-DAG: attributes #[[STREAMING_DECL]] = {{{.*}} "aarch64_pstate_sm_enabled" {{.*}}} +// CHECK-DAG: attributes #[[STREAMING_COMPATIBLE_DECL]] = {{{.*}} "aarch64_pstate_sm_compatible" {{.*}}} +// CHECK-DAG: attributes #[[SHARED_ZA_USE]] = { "aarch64_pstate_za_shared" } +// CHECK-DAG: attributes #[[PRESERVES_ZA_USE]] = { "aarch64_pstate_za_preserved" } +// CHECK-DAG: attributes #[[STREAMING_USE]] = { "aarch64_pstate_sm_enabled" } +// CHECK-DAG: attributes #[[STREAMING_COMPATIBLE_USE]] = { "aarch64_pstate_sm_compatible" } + +void f_shared_za_caller(void) __arm_shared_za { + f_shared_za(); + f_preserves_za(); +} + +void f_nonstreaming_caller(void) { + f_streaming(); + f_streaming_compatible(); +} diff --git a/clang/test/Sema/aarch64-sme-func-attrs.c b/clang/test/Sema/aarch64-sme-func-attrs.c new file mode 100644 --- /dev/null +++ b/clang/test/Sema/aarch64-sme-func-attrs.c @@ -0,0 +1,308 @@ +// RUN: %clang_cc1 -triple aarch64-none-linux-gnu -target-feature +sme -fsyntax-only -verify %s +// RUN: %clang_cc1 -triple aarch64-none-linux-gnu -target-feature +sme -fsyntax-only -verify=expected-cpp -x c++ %s + +// Valid attributes + +void sme_arm_streaming(void) __arm_streaming; +void sme_arm_streaming_compatible(void) __arm_streaming_compatible; + +__arm_new_za void sme_arm_new_za(void) {} +void sme_arm_shared_za(void) __arm_shared_za; +void sme_arm_preserves_za(void) __arm_preserves_za; + +__arm_new_za void sme_arm_streaming_new_za(void) __arm_streaming {} +void sme_arm_streaming_shared_za(void) __arm_streaming __arm_shared_za; +void sme_arm_streaming_preserves_za(void) __arm_streaming __arm_preserves_za; + +__arm_new_za void sme_arm_sc_new_za(void) __arm_streaming_compatible {} +void sme_arm_sc_shared_za(void) __arm_streaming_compatible __arm_shared_za; +void sme_arm_sc_preserves_za(void) __arm_streaming_compatible __arm_preserves_za; + +void sme_arm_shared_preserves_za(void) __arm_shared_za __arm_preserves_za; + +__arm_locally_streaming void sme_arm_locally_streaming(void) { } +__arm_locally_streaming void sme_arm_streaming_and_locally_streaming(void) __arm_streaming { } +__arm_locally_streaming void sme_arm_streaming_and_streaming_compatible(void) __arm_streaming_compatible { } + +__arm_locally_streaming __arm_new_za void sme_arm_ls_new_za(void) { } +__arm_locally_streaming void sme_arm_ls_shared_za(void) __arm_shared_za { } +__arm_locally_streaming void sme_arm_ls_preserves_za(void) __arm_preserves_za { } + +// Valid attributes on function pointers + +void streaming_ptr(void) __arm_streaming; +typedef void (*fptrty1) (void) __arm_streaming; +fptrty1 call_streaming_func() { return streaming_ptr; } + +void streaming_compatible_ptr(void) __arm_streaming_compatible; +typedef void (*fptrty2) (void) __arm_streaming_compatible; +fptrty2 call_sc_func() { return streaming_compatible_ptr; } + +void shared_za_ptr(void) __arm_shared_za; +typedef void (*fptrty3) (void) __arm_shared_za; +fptrty3 call_shared_za_func() { return shared_za_ptr; } + +void preserves_za_ptr(void) __arm_preserves_za; +typedef void (*fptrty4) (void) __arm_preserves_za; +fptrty4 call_preserve_za_func() { return preserves_za_ptr; } + +void shared_preserves_za_ptr(void) __arm_shared_za __arm_preserves_za; +typedef void (*fptrty5) (void) __arm_shared_za __arm_preserves_za; +fptrty5 call_shared_preserve_za_func() { return shared_preserves_za_ptr; } + +typedef void (*fptrty6) (void); +fptrty6 cast_nza_func_to_normal() { return sme_arm_new_za; } +fptrty6 cast_ls_func_to_normal() { return sme_arm_locally_streaming; } + +// Invalid attributes + +// expected-cpp-error@+4 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-cpp-note@+3 {{conflicting attribute is here}} +// expected-error@+2 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void streaming_mode(void) __arm_streaming __arm_streaming_compatible; + +// expected-cpp-error@+4 {{'__arm_streaming' and '__arm_streaming_compatible' are not compatible}} +// expected-cpp-note@+3 {{conflicting attribute is here}} +// expected-error@+2 {{'__arm_streaming' and '__arm_streaming_compatible' are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void streaming_compatible(void) __arm_streaming_compatible __arm_streaming; + +// expected-cpp-error@+2 {{'__arm_new_za' and '__arm_shared_za' are not compatible}} +// expected-error@+1 {{'__arm_new_za' and '__arm_shared_za' are not compatible}} +__arm_new_za void new_shared_za(void) __arm_shared_za {} + +// expected-cpp-error@+2 {{'__arm_new_za' and '__arm_preserves_za' are not compatible}} +// expected-error@+1 {{'__arm_new_za' and '__arm_preserves_za' are not compatible}} +__arm_new_za void new_preserves_za(void) __arm_preserves_za {} + +// Invalid attributes on function pointers + +// expected-cpp-error@+4 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-cpp-note@+3 {{conflicting attribute is here}} +// expected-error@+2 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +void streaming_ptr_invalid(void) __arm_streaming __arm_streaming_compatible; +// expected-cpp-error@+4 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-cpp-note@+3 {{conflicting attribute is here}} +// expected-error@+2 {{'__arm_streaming_compatible' and '__arm_streaming' are not compatible}} +// expected-note@+1 {{conflicting attribute is here}} +typedef void (*fptrty7) (void) __arm_streaming __arm_streaming_compatible; +fptrty7 invalid_streaming_func() { return streaming_ptr_invalid; } + +// expected-warning@+2 {{'__arm_streaming' only applies to non-K&R-style functions}} +// expected-error@+1 {{'__arm_streaming' only applies to function types; type here is 'void ()'}} +void function_no_prototype() __arm_streaming; + +// +// Check for incorrect conversions of function pointers with the attributes +// + +typedef void (*n_ptrty) (void); +typedef void (*s_ptrty) (void) __arm_streaming; +s_ptrty return_valid_streaming_fptr(s_ptrty f) { return f; } + +// expected-cpp-error@+2 {{cannot initialize return object of type 's_ptrty' (aka 'void (*)() __arm_streaming') with an lvalue of type 'n_ptrty' (aka 'void (*)()')}} +// expected-error@+1 {{incompatible function pointer types returning 'n_ptrty' (aka 'void (*)(void)') from a function with result type 's_ptrty' (aka 'void (*)(void) __arm_streaming')}} +s_ptrty return_invalid_fptr_streaming_normal(n_ptrty f) { return f; } +// expected-cpp-error@+2 {{cannot initialize return object of type 'n_ptrty' (aka 'void (*)()') with an lvalue of type 's_ptrty' (aka 'void (*)() __arm_streaming')}} +// expected-error@+1 {{incompatible function pointer types returning 's_ptrty' (aka 'void (*)(void) __arm_streaming') from a function with result type 'n_ptrty' (aka 'void (*)(void)')}} +n_ptrty return_invalid_fptr_normal_streaming(s_ptrty f) { return f; } + +// Test an instance where the result type is not a prototyped function, such that we still get a diagnostic. +typedef void (*nonproto_n_ptrty) (); +// expected-cpp-error@+2 {{cannot initialize return object of type 'nonproto_n_ptrty' (aka 'void (*)()') with an lvalue of type 's_ptrty' (aka 'void (*)() __arm_streaming')}} +// expected-error@+1 {{incompatible function pointer types returning 's_ptrty' (aka 'void (*)(void) __arm_streaming') from a function with result type 'nonproto_n_ptrty' (aka 'void (*)()')}} +nonproto_n_ptrty return_invalid_fptr_streaming_nonprotonormal(s_ptrty f) { return f; } + +typedef void (*sc_ptrty) (void) __arm_streaming_compatible; +sc_ptrty return_valid_streaming_compatible_fptr(sc_ptrty f) { return f; } + +// expected-cpp-error@+2 {{cannot initialize return object of type 'sc_ptrty' (aka 'void (*)() __arm_streaming_compatible') with an lvalue of type 'n_ptrty' (aka 'void (*)()')}} +// expected-error@+1 {{incompatible function pointer types returning 'n_ptrty' (aka 'void (*)(void)') from a function with result type 'sc_ptrty' (aka 'void (*)(void) __arm_streaming_compatible')}} +sc_ptrty return_invalid_fptr_streaming_compatible_normal(n_ptrty f) { return f; } +// expected-cpp-error@+2 {{cannot initialize return object of type 'n_ptrty' (aka 'void (*)()') with an lvalue of type 'sc_ptrty' (aka 'void (*)() __arm_streaming_compatible')}} +// expected-error@+1 {{incompatible function pointer types returning 'sc_ptrty' (aka 'void (*)(void) __arm_streaming_compatible') from a function with result type 'n_ptrty' (aka 'void (*)(void)')}} +n_ptrty return_invalid_fptr_normal_streaming_compatible(sc_ptrty f) { return f; } + +typedef void (*sz_ptrty) (void) __arm_shared_za; +sz_ptrty return_valid_shared_za_fptr(sz_ptrty f) { return f; } + + +// expected-cpp-error@+2 {{cannot initialize return object of type 'sz_ptrty' (aka 'void (*)() __arm_shared_za') with an lvalue of type 'n_ptrty' (aka 'void (*)()')}} +// expected-error@+1 {{incompatible function pointer types returning 'n_ptrty' (aka 'void (*)(void)') from a function with result type 'sz_ptrty' (aka 'void (*)(void) __arm_shared_za')}} +sz_ptrty return_invalid_fptr_shared_za_normal(n_ptrty f) { return f; } +// expected-cpp-error@+2 {{cannot initialize return object of type 'n_ptrty' (aka 'void (*)()') with an lvalue of type 'sz_ptrty' (aka 'void (*)() __arm_shared_za')}} +// expected-error@+1 {{incompatible function pointer types returning 'sz_ptrty' (aka 'void (*)(void) __arm_shared_za') from a function with result type 'n_ptrty' (aka 'void (*)(void)')}} +n_ptrty return_invalid_fptr_normal_shared_za(sz_ptrty f) { return f; } + +typedef void (*pz_ptrty) (void) __arm_preserves_za; +pz_ptrty return_valid_preserves_za_fptr(pz_ptrty f) { return f; } + +// expected-cpp-error@+2 {{cannot initialize return object of type 'pz_ptrty' (aka 'void (*)() __arm_preserves_za') with an lvalue of type 'n_ptrty' (aka 'void (*)()')}} +// expected-error@+1 {{incompatible function pointer types returning 'n_ptrty' (aka 'void (*)(void)') from a function with result type 'pz_ptrty' (aka 'void (*)(void) __arm_preserves_za')}} +pz_ptrty return_invalid_fptr_preserves_za_normal(n_ptrty f) { return f; } +// No diagnostics, the preserves_za hint should be dropped silently. +n_ptrty return_invalid_fptr_normal_preserves_za(pz_ptrty f) { return f; } + +// Test template instantiations +#ifdef __cplusplus +template T templated(T x) __arm_streaming { return x; } +template <> int templated(int x) __arm_streaming { return x + 1; } +template <> float templated(float x) __arm_streaming { return x + 2; } +// expected-cpp-error@+2 {{explicit instantiation of 'templated' does not refer to a function template, variable template, member function, member class, or static data member}} +// expected-cpp-note@-4 {{candidate template ignored: could not match 'short (short) __arm_streaming' against 'short (short)'}} +template short templated(short); +#endif + +// Conflicting attributes on redeclarations + +// expected-error@+5 {{function declared 'void (void) __arm_streaming_compatible' was previously declared 'void (void) __arm_streaming', which has different SME function attributes}} +// expected-note@+3 {{previous declaration is here}} +// expected-cpp-error@+3 {{function declared 'void () __arm_streaming_compatible' was previously declared 'void () __arm_streaming', which has different SME function attributes}} +// expected-cpp-note@+1 {{previous declaration is here}} +void redecl(void) __arm_streaming; +void redecl(void) __arm_streaming_compatible { } + +// expected-error@+5 {{function declared 'void (void) __arm_shared_za' was previously declared 'void (void) __arm_shared_za __arm_preserves_za', which has different SME function attributes}} +// expected-note@+3 {{previous declaration is here}} +// expected-cpp-error@+3 {{function declared 'void () __arm_shared_za' was previously declared 'void () __arm_shared_za __arm_preserves_za', which has different SME function attributes}} +// expected-cpp-note@+1 {{previous declaration is here}} +void redecl_preserve_za(void) __arm_shared_za __arm_preserves_za;; +void redecl_preserve_za(void) __arm_shared_za {} + +// expected-error@+5 {{function declared 'void (void) __arm_shared_za __arm_preserves_za' was previously declared 'void (void) __arm_shared_za', which has different SME function attributes}} +// expected-note@+3 {{previous declaration is here}} +// expected-cpp-error@+3 {{function declared 'void () __arm_shared_za __arm_preserves_za' was previously declared 'void () __arm_shared_za', which has different SME function attributes}} +// expected-cpp-note@+1 {{previous declaration is here}} +void redecl_nopreserve_za(void) __arm_shared_za; +void redecl_nopreserve_za(void) __arm_shared_za __arm_preserves_za {} + +#ifdef __cplusplus +struct S { + virtual void shared_za_memberfn(void) __arm_shared_za; +}; + +struct S2 : public S { +// expected-cpp-error@+2 {{virtual function 'shared_za_memberfn' has different attributes ('void ()') than the function it overrides (which has 'void () __arm_shared_za')}} +// expected-cpp-note@-5 {{overridden virtual function is here}} + __arm_new_za void shared_za_memberfn(void) override {} +}; + +// The '__arm_preserves_za' property cannot be dropped when overriding a virtual +// function. It is however fine for the overriding function to be '__arm_preserves_za' +// even though the function that it overrides is not. + +struct S_PreservesZA { + virtual void memberfn(void) __arm_preserves_za; +}; + +struct S_Drop_PreservesZA : S_PreservesZA { +// expected-cpp-error@+2 {{virtual function 'memberfn' has different attributes ('void ()') than the function it overrides (which has 'void () __arm_preserves_za')}} +// expected-cpp-note@-5 {{overridden virtual function is here}} + void memberfn(void) override {} +}; + +struct S_NoPreservesZA { + virtual void memberfn(void); +}; +struct S_AddPreservesZA : S_NoPreservesZA { +// This is fine, the overridden function just adds more guarantees. + void memberfn(void) __arm_preserves_za override {} +}; + + +// Check that the attribute propagates through template instantiations. +template +struct S3 { + static constexpr int value = 0; +}; + +template <> +struct S3 { + static constexpr int value = 1; +}; + +template <> +struct S3 { + static constexpr int value = 2; +}; + +template <> +struct S3 { + static constexpr int value = 4; +}; + +template <> +struct S3 { + static constexpr int value = 8; +}; + +template <> +struct S3 { + static constexpr int value = 16; +}; + +void normal_func(void) {} +void streaming_func(void) __arm_streaming {} +void streaming_compatible_func(void) __arm_streaming_compatible {} +void shared_za_func(void) __arm_shared_za {} +void preserves_za_func(void) __arm_preserves_za {} + +static_assert(S3::value == 1, "why are we picking the wrong specialization?"); +static_assert(S3::value == 2, "why are we picking the wrong specialization?"); +static_assert(S3::value == 4, "why are we picking the wrong specialization?"); +static_assert(S3::value == 8, "why are we picking the wrong specialization?"); +static_assert(S3::value == 16, "why are we picking the wrong specialization?"); + +// Also test the attribute is propagated with variadic templates +constexpr int eval_variadic_template() { return 0; } +template +constexpr int eval_variadic_template(T f, Other... other) { + return S3::value + eval_variadic_template(other...); +} +static_assert(eval_variadic_template(normal_func, streaming_func, + streaming_compatible_func, + shared_za_func, preserves_za_func) == 31, + "attributes not propagated properly in variadic template"); + +// Test that the attribute is propagated with template specialization. +template int test_templated_f(T); +template<> constexpr int test_templated_f(void(*)(void)) { return 1; } +template<> constexpr int test_templated_f(void(*)(void)__arm_streaming) { return 2; } +template<> constexpr int test_templated_f(void(*)(void)__arm_streaming_compatible) { return 4; } +template<> constexpr int test_templated_f(void(*)(void)__arm_shared_za) { return 8; } +template<> constexpr int test_templated_f(void(*)(void)__arm_preserves_za) { return 16; } + +static_assert(test_templated_f(&normal_func) == 1, "Instantiated to wrong function"); +static_assert(test_templated_f(&streaming_func) == 2, "Instantiated to wrong function"); +static_assert(test_templated_f(&streaming_compatible_func) == 4, "Instantiated to wrong function"); +static_assert(test_templated_f(&shared_za_func) == 8, "Instantiated to wrong function"); +static_assert(test_templated_f(&preserves_za_func) == 16, "Instantiated to wrong function"); + +// expected-cpp-error@+2 {{'__arm_streaming' only applies to function types; type here is 'int'}} +// expected-error@+1 {{'__arm_streaming' only applies to function types; type here is 'int'}} +int invalid_type_for_attribute __arm_streaming; + +// Test overloads +constexpr int overload(void f(void)) { return 1; } +constexpr int overload(void f(void) __arm_streaming) { return 2; } +constexpr int overload(void f(void) __arm_streaming_compatible) { return 4; } +constexpr int overload(void f(void) __arm_shared_za) { return 8; } +constexpr int overload(void f(void) __arm_preserves_za) { return 16; } +static_assert(overload(&normal_func) == 1, "Overloaded to wrong function"); +static_assert(overload(&streaming_func) == 2, "Overloaded to wrong function"); +static_assert(overload(&streaming_compatible_func) == 4, "Overloaded to wrong function"); +static_assert(overload(&shared_za_func) == 8, "Overloaded to wrong function"); +static_assert(overload(&preserves_za_func) == 16, "Overloaded to wrong function"); + +// Test implicit instantiation +template struct X { + static void foo(T) __arm_streaming { } +}; +constexpr int overload_int(void f(int)) { return 1; } +constexpr int overload_int(void f(int) __arm_streaming) { return 2; } +constexpr X *ptr = 0; +static_assert(overload_int(ptr->foo) == 2, "Overloaded to the wrong function after implicit instantiation"); + +#endif // ifdef __cplusplus