Skip to content

Commit 8a03acc

Browse files
committed
[Clang][AArch64] Generalise streaming mode checks for builtins.
PR #76975 added 'IsStreamingOrSVE2p1' to emit a diagnostic when a builtin marked with 'IsStreamingOrSVE2p1' is used in a non-streaming function that is not compiled with `+sve2p1`. The problem is a bit more complex than only this case. For example, we've marked lots of builtins with 'IsStreamingCompatible', meaning it can be used in either streaming, streaming-compatible or non-streaming functions. But the code in SemaChecking, doesn't check the appropriate target guards. This issue becomes relevant when SVE builtins are only available in streaming mode, e.g. when compiling for SME without SVE. If we were to add the appropriate target guards, we'd have to add many more combinations, e.g.: IsStreamingSMEOrSVE IsStreamingSME2OrSVE2 IsStreamingSMEOrSVE2p1 IsStreamingSME2OrSVE2p1 etc. To avoid having to add more combinations (and avoid having to add more in the future for new extensions), we use a single 'IsSVEOrStreamingSVE' flag for all builtins that are available in streaming mode for the appropriate SME flags, or in non-streaming mode for the appropriate SVE flags, or both. The code in SemaChecking will then verify for which mode (or both) the builtin would be defined, given the target features of the function/compilation unit. For example: 'svclamp' is enabled under FEAT_SVE2p1 and FEAT_SME2 * When we compile for SVE2p1 and SME (but not SME2), the builtin is undefined behaviour when called from a streaming function. * When we compile for SME2 and SVE2 (but not SVE2p1), the builtin is undefined behaviour when called from a non-streaming function. * When we compile for _both_ SVE2p1 and SME2, the builtin can be used in either mode (non-streaming, streaming or streaming-compatible)
1 parent 50d837e commit 8a03acc

File tree

7 files changed

+868
-808
lines changed

7 files changed

+868
-808
lines changed

clang/include/clang/Basic/arm_sve.td

Lines changed: 751 additions & 751 deletions
Large diffs are not rendered by default.

clang/include/clang/Basic/arm_sve_sme_incl.td

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ def IsStreamingCompatible : FlagType<0x4000000000>;
225225
def IsReadZA : FlagType<0x8000000000>;
226226
def IsWriteZA : FlagType<0x10000000000>;
227227
def IsReductionQV : FlagType<0x20000000000>;
228-
def IsStreamingOrSVE2p1 : FlagType<0x40000000000>; // Use for intrinsics that are common between sme/sme2 and sve2p1.
228+
def IsSVEOrStreamingSVE : FlagType<0x40000000000>; // Use for intrinsics that are common between SVE and SME.
229229
def IsInZA : FlagType<0x80000000000>;
230230
def IsOutZA : FlagType<0x100000000000>;
231231
def IsInOutZA : FlagType<0x200000000000>;

clang/include/clang/Sema/SemaARM.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,12 @@ class SemaARM : public SemaBase {
2626
SemaARM(Sema &S);
2727

2828
enum ArmStreamingType {
29-
ArmNonStreaming,
30-
ArmStreaming,
31-
ArmStreamingCompatible,
32-
ArmStreamingOrSVE2p1
29+
ArmNonStreaming, /// Intrinsic is only available in normal mode
30+
ArmStreaming, /// Intrinsic is only available in Streaming-SVE mode.
31+
ArmStreamingCompatible, /// Intrinsic is available both in normal and
32+
/// Streaming-SVE mode.
33+
ArmStreamingOrHasSVE /// Intrinsic is available in normal mode with +sve, or
34+
/// in Streaming-SVE mode with +sme.
3335
};
3436

3537
bool CheckARMBuiltinExclusiveCall(unsigned BuiltinID, CallExpr *TheCall,

clang/lib/Sema/SemaARM.cpp

Lines changed: 56 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -561,16 +561,61 @@ SemaARM::ArmStreamingType getArmStreamingFnType(const FunctionDecl *FD) {
561561

562562
static void checkArmStreamingBuiltin(Sema &S, CallExpr *TheCall,
563563
const FunctionDecl *FD,
564-
SemaARM::ArmStreamingType BuiltinType) {
564+
SemaARM::ArmStreamingType BuiltinType,
565+
unsigned BuiltinID) {
565566
SemaARM::ArmStreamingType FnType = getArmStreamingFnType(FD);
566-
if (BuiltinType == SemaARM::ArmStreamingOrSVE2p1) {
567-
// Check intrinsics that are available in [sve2p1 or sme/sme2].
568-
llvm::StringMap<bool> CallerFeatureMap;
569-
S.Context.getFunctionFeatureMap(CallerFeatureMap, FD);
570-
if (Builtin::evaluateRequiredTargetFeatures("sve2p1", CallerFeatureMap))
567+
568+
// Check if the intrinsic is available in the right mode, i.e.
569+
// * When compiling for SME only, the caller must be in streaming mode.
570+
// * When compiling for SVE only, the caller must be in non-streaming mode.
571+
// * When compiling for both SVE and SME, the caller can be in either mode.
572+
if (BuiltinType == SemaARM::ArmStreamingOrHasSVE) {
573+
static const FunctionDecl *CachedFD = nullptr;
574+
bool SatisfiesSVE = false, SatisfiesSME = false;
575+
576+
if (FD != CachedFD) {
577+
// We know the builtin requires either some combination of SVE flags, or
578+
// some combination of SME flags, but we need to figure out which part
579+
// of the required features is satisfied by the target features.
580+
//
581+
// For a builtin with target guard 'sve2p1|sme2', if we compile with
582+
// '+sve2p1,+sme', then we know that it satisfies the 'sve2p1' part if we
583+
// evaluate the features for '+sve2p1,+sme,+nosme'.
584+
//
585+
// Similarly, if we compile with '+sve2,+sme2', then we know it satisfies
586+
// the 'sme2' part if we evaluate the features for '+sve2,+sme2,+nosve'.
587+
llvm::StringMap<bool> CallerFeatureMap;
588+
auto DisableFeatures = [&CallerFeatureMap](StringRef S) {
589+
for (StringRef K : CallerFeatureMap.keys())
590+
if (K.starts_with(S))
591+
CallerFeatureMap[K] = false;
592+
};
593+
594+
StringRef BuiltinTargetGuards(
595+
S.Context.BuiltinInfo.getRequiredFeatures(BuiltinID));
596+
597+
S.Context.getFunctionFeatureMap(CallerFeatureMap, FD);
598+
DisableFeatures("sme");
599+
SatisfiesSVE = Builtin::evaluateRequiredTargetFeatures(
600+
BuiltinTargetGuards, CallerFeatureMap);
601+
602+
S.Context.getFunctionFeatureMap(CallerFeatureMap, FD);
603+
DisableFeatures("sve");
604+
SatisfiesSME = Builtin::evaluateRequiredTargetFeatures(
605+
BuiltinTargetGuards, CallerFeatureMap);
606+
607+
CachedFD = FD;
608+
}
609+
610+
if (SatisfiesSVE && SatisfiesSME)
571611
BuiltinType = SemaARM::ArmStreamingCompatible;
572-
else
612+
else if (SatisfiesSVE)
613+
BuiltinType = SemaARM::ArmNonStreaming;
614+
else if (SatisfiesSME)
573615
BuiltinType = SemaARM::ArmStreaming;
616+
else
617+
// This should be diagnosed by CodeGen
618+
return;
574619
}
575620

576621
if (FnType == SemaARM::ArmStreaming &&
@@ -622,7 +667,7 @@ bool SemaARM::CheckSMEBuiltinFunctionCall(unsigned BuiltinID,
622667
}
623668

624669
if (BuiltinType)
625-
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType);
670+
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType, BuiltinID);
626671

627672
if ((getSMEState(BuiltinID) & ArmZAMask) && !hasArmZAState(FD))
628673
Diag(TheCall->getBeginLoc(),
@@ -660,7 +705,7 @@ bool SemaARM::CheckSVEBuiltinFunctionCall(unsigned BuiltinID,
660705
#undef GET_SVE_STREAMING_ATTRS
661706
}
662707
if (BuiltinType)
663-
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType);
708+
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType, BuiltinID);
664709
}
665710
// Range check SVE intrinsics that take immediate values.
666711
SmallVector<std::tuple<int, int, int>, 3> ImmChecks;
@@ -688,7 +733,8 @@ bool SemaARM::CheckNeonBuiltinFunctionCall(const TargetInfo &TI,
688733
#define TARGET_BUILTIN(id, ...) case NEON::BI##id:
689734
#define BUILTIN(id, ...) case NEON::BI##id:
690735
#include "clang/Basic/arm_neon.inc"
691-
checkArmStreamingBuiltin(SemaRef, TheCall, FD, ArmNonStreaming);
736+
checkArmStreamingBuiltin(SemaRef, TheCall, FD, ArmNonStreaming,
737+
BuiltinID);
692738
break;
693739
#undef TARGET_BUILTIN
694740
#undef BUILTIN

clang/test/Sema/aarch64-sme2-sve2p1-diagnostics.c

Lines changed: 0 additions & 39 deletions
This file was deleted.
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py
2+
// RUN: %clang_cc1 -triple aarch64-none-linux-gnu -target-feature +sve -fsyntax-only -verify %s
3+
4+
// REQUIRES: aarch64-registered-target
5+
6+
#include <arm_sve.h>
7+
8+
__attribute__((target("+sve2p1")))
9+
svfloat32_t good1(svfloat32_t a, svfloat32_t b, svfloat32_t c) {
10+
return svclamp(a, b, c);
11+
}
12+
13+
__attribute__((target("+sme2")))
14+
svfloat32_t good2(svfloat32_t a, svfloat32_t b, svfloat32_t c) __arm_streaming {
15+
return svclamp(a, b, c);
16+
}
17+
18+
__attribute__((target("+sve2p1,+sme2")))
19+
svfloat32_t good3(svfloat32_t a, svfloat32_t b, svfloat32_t c) __arm_streaming_compatible {
20+
return svclamp(a, b, c);
21+
}
22+
23+
// Without '+sme2', the builtin is only valid in non-streaming mode.
24+
__attribute__((target("+sve2p1,+sme")))
25+
svfloat32_t bad1(svfloat32_t a, svfloat32_t b, svfloat32_t c) __arm_streaming {
26+
return svclamp(a, b, c); // expected-warning{{builtin call has undefined behaviour when called from a streaming function}}
27+
}
28+
29+
// Without '+sve2p1', the builtin is only valid in streaming mode.
30+
__attribute__((target("+sve2,+sme2")))
31+
svfloat32_t bad2(svfloat32_t a, svfloat32_t b, svfloat32_t c) {
32+
return svclamp(a, b, c); // expected-warning{{builtin call has undefined behaviour when called from a non-streaming function}}
33+
}
34+
35+
// Without '+sme2', the builtin is only valid in non-streaming mode.
36+
__attribute__((target("+sve2p1,+sme")))
37+
svfloat32_t bad3(svfloat32_t a, svfloat32_t b, svfloat32_t c) __arm_streaming_compatible {
38+
return svclamp(a, b, c); // expected-warning{{builtin call has undefined behaviour when called from a streaming compatible function}}
39+
}
40+
41+
// Without '+sve2p1', the builtin is only valid in streaming mode.
42+
__attribute__((target("+sve2,+sme2")))
43+
svfloat32_t bad4(svfloat32_t a, svfloat32_t b, svfloat32_t c) __arm_streaming_compatible {
44+
return svclamp(a, b, c); // expected-warning{{builtin call has undefined behaviour when called from a streaming compatible function}}
45+
}
46+
47+
// We don't want a warning about undefined behaviour if none of the feature requirements of the builtin are satisfied.
48+
// (this results in a target-guard error emitted by Clang CodeGen)
49+
svfloat32_t bad5(svfloat32_t a, svfloat32_t b, svfloat32_t c) {
50+
return svclamp(a, b, c);
51+
}

clang/utils/TableGen/SveEmitter.cpp

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1778,14 +1778,14 @@ void SVEEmitter::createStreamingAttrs(raw_ostream &OS, ACLEKind Kind) {
17781778
llvm::StringMap<std::set<std::string>> StreamingMap;
17791779

17801780
uint64_t IsStreamingFlag = getEnumValueForFlag("IsStreaming");
1781-
uint64_t IsStreamingOrSVE2p1Flag = getEnumValueForFlag("IsStreamingOrSVE2p1");
1781+
uint64_t IsSVEOrStreamingSVEFlag = getEnumValueForFlag("IsSVEOrStreamingSVE");
17821782
uint64_t IsStreamingCompatibleFlag =
17831783
getEnumValueForFlag("IsStreamingCompatible");
17841784
for (auto &Def : Defs) {
17851785
if (Def->isFlagSet(IsStreamingFlag))
17861786
StreamingMap["ArmStreaming"].insert(Def->getMangledName());
1787-
else if (Def->isFlagSet(IsStreamingOrSVE2p1Flag))
1788-
StreamingMap["ArmStreamingOrSVE2p1"].insert(Def->getMangledName());
1787+
else if (Def->isFlagSet(IsSVEOrStreamingSVEFlag))
1788+
StreamingMap["ArmStreamingOrHasSVE"].insert(Def->getMangledName());
17891789
else if (Def->isFlagSet(IsStreamingCompatibleFlag))
17901790
StreamingMap["ArmStreamingCompatible"].insert(Def->getMangledName());
17911791
else

0 commit comments

Comments
 (0)