Skip to content

[Clang][AArch64] Generalise streaming mode checks for builtins. #93802

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -3200,9 +3200,8 @@ def err_attribute_bad_sve_vector_size : Error<
def err_attribute_arm_feature_sve_bits_unsupported : Error<
"%0 is only supported when '-msve-vector-bits=<bits>' is specified with a "
"value of 128, 256, 512, 1024 or 2048">;
def warn_attribute_arm_sm_incompat_builtin : Warning<
"builtin call has undefined behaviour when called from a %0 function">,
InGroup<DiagGroup<"undefined-arm-streaming">>;
def err_attribute_arm_sm_incompat_builtin : Error<
"builtin can only be called from a %0 function">;
def warn_attribute_arm_za_builtin_no_za_state : Warning<
"builtin call is not valid when calling from a function without active ZA state">,
InGroup<DiagGroup<"undefined-arm-za">>;
Expand Down
1,550 changes: 781 additions & 769 deletions clang/include/clang/Basic/arm_sve.td

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion clang/include/clang/Basic/arm_sve_sme_incl.td
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,7 @@ def IsStreamingCompatible : FlagType<0x4000000000>;
def IsReadZA : FlagType<0x8000000000>;
def IsWriteZA : FlagType<0x10000000000>;
def IsReductionQV : FlagType<0x20000000000>;
def IsStreamingOrSVE2p1 : FlagType<0x40000000000>; // Use for intrinsics that are common between sme/sme2 and sve2p1.
def VerifyRuntimeMode : FlagType<0x40000000000>; // Use for intrinsics that are common between SVE and SME.
def IsInZA : FlagType<0x80000000000>;
def IsOutZA : FlagType<0x100000000000>;
def IsInOutZA : FlagType<0x200000000000>;
Expand Down
11 changes: 7 additions & 4 deletions clang/include/clang/Sema/SemaARM.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,13 @@ class SemaARM : public SemaBase {
SemaARM(Sema &S);

enum ArmStreamingType {
ArmNonStreaming,
ArmStreaming,
ArmStreamingCompatible,
ArmStreamingOrSVE2p1
ArmNonStreaming, /// Intrinsic is only available in normal mode
ArmStreaming, /// Intrinsic is only available in Streaming-SVE mode.
ArmStreamingCompatible, /// Intrinsic is available both in normal and
/// Streaming-SVE mode.
VerifyRuntimeMode /// Intrinsic is available in normal mode with
/// SVE flags, or in Streaming-SVE mode with SME
/// flags. Do Sema checks for the runtime mode.
};

bool CheckARMBuiltinExclusiveCall(unsigned BuiltinID, CallExpr *TheCall,
Expand Down
95 changes: 72 additions & 23 deletions clang/lib/Sema/SemaARM.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -560,31 +560,76 @@ SemaARM::ArmStreamingType getArmStreamingFnType(const FunctionDecl *FD) {
return SemaARM::ArmNonStreaming;
}

static void checkArmStreamingBuiltin(Sema &S, CallExpr *TheCall,
static bool checkArmStreamingBuiltin(Sema &S, CallExpr *TheCall,
const FunctionDecl *FD,
SemaARM::ArmStreamingType BuiltinType) {
SemaARM::ArmStreamingType BuiltinType,
unsigned BuiltinID) {
SemaARM::ArmStreamingType FnType = getArmStreamingFnType(FD);
if (BuiltinType == SemaARM::ArmStreamingOrSVE2p1) {
// Check intrinsics that are available in [sve2p1 or sme/sme2].
llvm::StringMap<bool> CallerFeatureMap;
S.Context.getFunctionFeatureMap(CallerFeatureMap, FD);
if (Builtin::evaluateRequiredTargetFeatures("sve2p1", CallerFeatureMap))
BuiltinType = SemaARM::ArmStreamingCompatible;
else

// Check if the intrinsic is available in the right mode, i.e.
// * When compiling for SME only, the caller must be in streaming mode.
// * When compiling for SVE only, the caller must be in non-streaming mode.
// * When compiling for both SVE and SME, the caller can be in either mode.
if (BuiltinType == SemaARM::VerifyRuntimeMode) {
auto DisableFeatures = [](llvm::StringMap<bool> &Map, StringRef S) {
for (StringRef K : Map.keys())
if (K.starts_with(S))
Map[K] = false;
};

llvm::StringMap<bool> CallerFeatureMapWithoutSVE;
S.Context.getFunctionFeatureMap(CallerFeatureMapWithoutSVE, FD);
DisableFeatures(CallerFeatureMapWithoutSVE, "sve");

// Avoid emitting diagnostics for a function that can never compile.
if (FnType == SemaARM::ArmStreaming && !CallerFeatureMapWithoutSVE["sme"])
return false;

llvm::StringMap<bool> CallerFeatureMapWithoutSME;
S.Context.getFunctionFeatureMap(CallerFeatureMapWithoutSME, FD);
DisableFeatures(CallerFeatureMapWithoutSME, "sme");

// We know the builtin requires either some combination of SVE flags, or
// some combination of SME flags, but we need to figure out which part
// of the required features is satisfied by the target features.
//
// For a builtin with target guard 'sve2p1|sme2', if we compile with
// '+sve2p1,+sme', then we know that it satisfies the 'sve2p1' part if we
// evaluate the features for '+sve2p1,+sme,+nosme'.
//
// Similarly, if we compile with '+sve2,+sme2', then we know it satisfies
// the 'sme2' part if we evaluate the features for '+sve2,+sme2,+nosve'.
StringRef BuiltinTargetGuards(
S.Context.BuiltinInfo.getRequiredFeatures(BuiltinID));
bool SatisfiesSVE = Builtin::evaluateRequiredTargetFeatures(
BuiltinTargetGuards, CallerFeatureMapWithoutSME);
bool SatisfiesSME = Builtin::evaluateRequiredTargetFeatures(
BuiltinTargetGuards, CallerFeatureMapWithoutSVE);

if ((SatisfiesSVE && SatisfiesSME) ||
(SatisfiesSVE && FnType == SemaARM::ArmStreamingCompatible))
return false;
else if (SatisfiesSVE)
BuiltinType = SemaARM::ArmNonStreaming;
else if (SatisfiesSME)
BuiltinType = SemaARM::ArmStreaming;
else
// This should be diagnosed by CodeGen
return false;
}

if (FnType == SemaARM::ArmStreaming &&
if (FnType != SemaARM::ArmNonStreaming &&
BuiltinType == SemaARM::ArmNonStreaming)
S.Diag(TheCall->getBeginLoc(), diag::warn_attribute_arm_sm_incompat_builtin)
<< TheCall->getSourceRange() << "streaming";
else if (FnType == SemaARM::ArmNonStreaming && BuiltinType == SemaARM::ArmStreaming)
S.Diag(TheCall->getBeginLoc(), diag::warn_attribute_arm_sm_incompat_builtin)
S.Diag(TheCall->getBeginLoc(), diag::err_attribute_arm_sm_incompat_builtin)
<< TheCall->getSourceRange() << "non-streaming";
else if (FnType == SemaARM::ArmStreamingCompatible &&
BuiltinType != SemaARM::ArmStreamingCompatible)
S.Diag(TheCall->getBeginLoc(), diag::warn_attribute_arm_sm_incompat_builtin)
<< TheCall->getSourceRange() << "streaming compatible";
else if (FnType != SemaARM::ArmStreaming &&
BuiltinType == SemaARM::ArmStreaming)
S.Diag(TheCall->getBeginLoc(), diag::err_attribute_arm_sm_incompat_builtin)
<< TheCall->getSourceRange() << "streaming";
else
return false;

return true;
}

static bool hasArmZAState(const FunctionDecl *FD) {
Expand Down Expand Up @@ -622,8 +667,9 @@ bool SemaARM::CheckSMEBuiltinFunctionCall(unsigned BuiltinID,
#undef GET_SME_STREAMING_ATTRS
}

if (BuiltinType)
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType);
if (BuiltinType &&
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType, BuiltinID))
return true;

if ((getSMEState(BuiltinID) & ArmZAMask) && !hasArmZAState(FD))
Diag(TheCall->getBeginLoc(),
Expand Down Expand Up @@ -660,8 +706,9 @@ bool SemaARM::CheckSVEBuiltinFunctionCall(unsigned BuiltinID,
#include "clang/Basic/arm_sve_streaming_attrs.inc"
#undef GET_SVE_STREAMING_ATTRS
}
if (BuiltinType)
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType);
if (BuiltinType &&
checkArmStreamingBuiltin(SemaRef, TheCall, FD, *BuiltinType, BuiltinID))
return true;
}
// Range check SVE intrinsics that take immediate values.
SmallVector<std::tuple<int, int, int>, 3> ImmChecks;
Expand Down Expand Up @@ -689,7 +736,9 @@ bool SemaARM::CheckNeonBuiltinFunctionCall(const TargetInfo &TI,
#define TARGET_BUILTIN(id, ...) case NEON::BI##id:
#define BUILTIN(id, ...) case NEON::BI##id:
#include "clang/Basic/arm_neon.inc"
checkArmStreamingBuiltin(SemaRef, TheCall, FD, ArmNonStreaming);
if (checkArmStreamingBuiltin(SemaRef, TheCall, FD, ArmNonStreaming,
BuiltinID))
return true;
break;
#undef TARGET_BUILTIN
#undef BUILTIN
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
// RUN: %clang_cc1 -triple aarch64 -target-feature +bf16 -target-feature +sme -target-feature +sve -target-feature +sme2 -O1 -Werror -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple aarch64 -target-feature +sve -target-feature +sve2 -target-feature +sve2p1 -O1 -Werror -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple aarch64 -target-feature +sve -target-feature +sve2 -target-feature +sve2p1 -S -disable-O0-optnone -Werror -Wall -o /dev/null %s
// RUN: %clang_cc1 -triple aarch64 -target-feature +bf16 -target-feature +sve -target-feature +sve2 -target-feature +sme -target-feature +sve2p1 -O1 -Werror -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple aarch64 -target-feature +bf16 -target-feature +sve -target-feature +sve2 -target-feature +sve2p1 -O1 -Werror -emit-llvm -o - %s | FileCheck %s
// RUN: %clang_cc1 -triple aarch64 -target-feature +bf16 -target-feature +sme -target-feature +sme2 -O1 -Werror -emit-llvm -o - -x c++ %s | FileCheck %s -check-prefix=CPP-CHECK
// RUN: %clang_cc1 -triple aarch64 -target-feature +bf16 -target-feature +sme -target-feature +sme2 -S -disable-O0-optnone -Werror -Wall -o /dev/null %s

Expand Down
12 changes: 9 additions & 3 deletions clang/test/CodeGen/aarch64-sve2p1-intrinsics/acle_sve2p1_qrshr.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@

#include <arm_sve.h>

#ifdef __ARM_FEATURE_SME
#define ATTR __arm_streaming
#else
#define ATTR
#endif

#ifdef SVE_OVERLOADED_FORMS
// A simple used,unused... macro, long enough to represent any SVE builtin.
#define SVE_ACLE_FUNC(A1,A2_UNUSED,A3,A4_UNUSED,A5) A1##A3##A5
Expand All @@ -34,7 +40,7 @@
// CPP-CHECK-NEXT: [[TMP2:%.*]] = tail call <vscale x 8 x i16> @llvm.aarch64.sve.sqrshrn.x2.nxv4i32(<vscale x 4 x i32> [[TMP0]], <vscale x 4 x i32> [[TMP1]], i32 16)
// CPP-CHECK-NEXT: ret <vscale x 8 x i16> [[TMP2]]
//
svint16_t test_svqrshrn_s16_s32_x2(svint32x2_t zn) __arm_streaming_compatible {
svint16_t test_svqrshrn_s16_s32_x2(svint32x2_t zn) ATTR {
return SVE_ACLE_FUNC(svqrshrn,_n,_s16,_s32_x2,)(zn, 16);
}

Expand All @@ -54,7 +60,7 @@ svint16_t test_svqrshrn_s16_s32_x2(svint32x2_t zn) __arm_streaming_compatible {
// CPP-CHECK-NEXT: [[TMP2:%.*]] = tail call <vscale x 8 x i16> @llvm.aarch64.sve.uqrshrn.x2.nxv4i32(<vscale x 4 x i32> [[TMP0]], <vscale x 4 x i32> [[TMP1]], i32 16)
// CPP-CHECK-NEXT: ret <vscale x 8 x i16> [[TMP2]]
//
svuint16_t test_svqrshrn_u16_u32_x2(svuint32x2_t zn) __arm_streaming_compatible {
svuint16_t test_svqrshrn_u16_u32_x2(svuint32x2_t zn) ATTR {
return SVE_ACLE_FUNC(svqrshrn,_n,_u16,_u32_x2,)(zn, 16);
}

Expand All @@ -74,6 +80,6 @@ svuint16_t test_svqrshrn_u16_u32_x2(svuint32x2_t zn) __arm_streaming_compatible
// CPP-CHECK-NEXT: [[TMP2:%.*]] = tail call <vscale x 8 x i16> @llvm.aarch64.sve.sqrshrun.x2.nxv4i32(<vscale x 4 x i32> [[TMP0]], <vscale x 4 x i32> [[TMP1]], i32 16)
// CPP-CHECK-NEXT: ret <vscale x 8 x i16> [[TMP2]]
//
svuint16_t test_svqrshrun_u16_s32_x2(svint32x2_t zn) __arm_streaming_compatible {
svuint16_t test_svqrshrun_u16_s32_x2(svint32x2_t zn) ATTR {
return SVE_ACLE_FUNC(svqrshrun,_n,_u16,_s32_x2,)(zn, 16);
}
32 changes: 12 additions & 20 deletions clang/test/Sema/aarch64-incompat-sm-builtin-calls.c
Original file line number Diff line number Diff line change
Expand Up @@ -9,100 +9,93 @@
#include "arm_sve.h"

int16x8_t incompat_neon_sm(int16x8_t splat) __arm_streaming {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return (int16x8_t)__builtin_neon_vqaddq_v((int8x16_t)splat, (int8x16_t)splat, 33);
}

__arm_locally_streaming int16x8_t incompat_neon_ls(int16x8_t splat) {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return (int16x8_t)__builtin_neon_vqaddq_v((int8x16_t)splat, (int8x16_t)splat, 33);
}

int16x8_t incompat_neon_smc(int16x8_t splat) __arm_streaming_compatible {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming compatible function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return (int16x8_t)__builtin_neon_vqaddq_v((int8x16_t)splat, (int8x16_t)splat, 33);
}

void incompat_sme_smc(svbool_t pg, void const *ptr) __arm_streaming_compatible __arm_inout("za") {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming compatible function}}
// expected-error@+1 {{builtin can only be called from a streaming function}}
return __builtin_sme_svld1_hor_za128(0, 0, pg, ptr);
}

svuint32_t incompat_sve_sm(svbool_t pg, svuint32_t a, int16_t b) __arm_streaming {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svld1_gather_u32base_index_u32(pg, a, b);
}

// expected-warning@+2 {{returning a VL-dependent argument from a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
// expected-warning@+1 {{passing a VL-dependent argument to a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
__arm_locally_streaming svuint32_t incompat_sve_ls(svbool_t pg, svuint32_t a, int64_t b) {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svld1_gather_u32base_index_u32(pg, a, b);
}

svuint32_t incompat_sve_smc(svbool_t pg, svuint32_t a, int64_t b) __arm_streaming_compatible {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming compatible function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svld1_gather_u32base_index_u32(pg, a, b);
}

svuint32_t incompat_sve2_sm(svbool_t pg, svuint32_t a, int64_t b) __arm_streaming {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svldnt1_gather_u32base_index_u32(pg, a, b);
}

// expected-warning@+2 {{returning a VL-dependent argument from a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
// expected-warning@+1 {{passing a VL-dependent argument to a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
__arm_locally_streaming svuint32_t incompat_sve2_ls(svbool_t pg, svuint32_t a, int64_t b) {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svldnt1_gather_u32base_index_u32(pg, a, b);
}

svuint32_t incompat_sve2_smc(svbool_t pg, svuint32_t a, int64_t b) __arm_streaming_compatible {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a streaming compatible function}}
// expected-error@+1 {{builtin can only be called from a non-streaming function}}
return __builtin_sve_svldnt1_gather_u32base_index_u32(pg, a, b);
}

void incompat_sme_sm(svbool_t pn, svbool_t pm, svfloat32_t zn, svfloat32_t zm) __arm_inout("za") {
// expected-warning@+1 {{builtin call has undefined behaviour when called from a non-streaming function}}
// expected-error@+1 {{builtin can only be called from a streaming function}}
svmops_za32_f32_m(0, pn, pm, zn, zm);
}

svfloat64_t streaming_caller_sve(svbool_t pg, svfloat64_t a, float64_t b) __arm_streaming {
// expected-no-warning
return svadd_n_f64_m(pg, a, b);
}

// expected-warning@+2 {{returning a VL-dependent argument from a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
// expected-warning@+1 {{passing a VL-dependent argument to a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
__arm_locally_streaming svfloat64_t locally_streaming_caller_sve(svbool_t pg, svfloat64_t a, float64_t b) {
// expected-no-warning
return svadd_n_f64_m(pg, a, b);
}

svfloat64_t streaming_compatible_caller_sve(svbool_t pg, svfloat64_t a, float64_t b) __arm_streaming_compatible {
// expected-no-warning
return svadd_n_f64_m(pg, a, b);
}

svint16_t streaming_caller_sve2(svint16_t op1, svint16_t op2) __arm_streaming {
// expected-no-warning
return svmul_lane_s16(op1, op2, 0);
}

// expected-warning@+2 {{returning a VL-dependent argument from a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
// expected-warning@+1 {{passing a VL-dependent argument to a locally streaming function is undefined behaviour when the streaming and non-streaming vector lengths are different at runtime}}
__arm_locally_streaming svint16_t locally_streaming_caller_sve2(svint16_t op1, svint16_t op2) {
// expected-no-warning
return svmul_lane_s16(op1, op2, 0);
}

svint16_t streaming_compatible_caller_sve2(svint16_t op1, svint16_t op2) __arm_streaming_compatible {
// expected-no-warning
return svmul_lane_s16(op1, op2, 0);
}

svbool_t streaming_caller_ptrue(void) __arm_streaming {
// expected-no-warning
return svand_z(svptrue_b16(), svptrue_pat_b16(SV_ALL), svptrue_pat_b16(SV_VL4));
}

Expand All @@ -113,7 +106,6 @@ svint8_t missing_za(svint8_t zd, svbool_t pg, uint32_t slice_base) __arm_streami

__arm_new("za")
svint8_t new_za(svint8_t zd, svbool_t pg, uint32_t slice_base) __arm_streaming {
// expected-no-warning
return svread_hor_za8_s8_m(zd, pg, 0, slice_base);
}

Expand All @@ -123,4 +115,4 @@ void missing_zt0(void) __arm_streaming {
}

__arm_new("zt0")
void new_zt0(void) __arm_streaming { svzero_zt(0); } // no warning
void new_zt0(void) __arm_streaming { svzero_zt(0); }
2 changes: 1 addition & 1 deletion clang/test/Sema/aarch64-sme-intrinsics/acle_sme_target.c
Original file line number Diff line number Diff line change
Expand Up @@ -22,5 +22,5 @@ void test_plus_sme(svbool_t pg, void *ptr) __arm_streaming __arm_inout("za") {

__attribute__((target("+sme")))
void undefined(svbool_t pg, void *ptr) __arm_inout("za") {
svst1_ver_vnum_za64(0, 0, pg, ptr, 0); // expected-warning {{builtin call has undefined behaviour when called from a non-streaming function}}
svst1_ver_vnum_za64(0, 0, pg, ptr, 0); // expected-error {{builtin can only be called from a streaming function}}
}
Loading
Loading