diff --git a/clang/include/clang/Basic/CMakeLists.txt b/clang/include/clang/Basic/CMakeLists.txt --- a/clang/include/clang/Basic/CMakeLists.txt +++ b/clang/include/clang/Basic/CMakeLists.txt @@ -69,6 +69,9 @@ clang_tablegen(arm_sve_typeflags.inc -gen-arm-sve-typeflags SOURCE arm_sve.td TARGET ClangARMSveTypeFlags) +clang_tablegen(arm_sve_sema_rangechecks.inc -gen-arm-sve-sema-rangechecks + SOURCE arm_sve.td + TARGET ClangARMSveSemaRangeChecks) clang_tablegen(arm_cde_builtins.inc -gen-arm-cde-builtin-def SOURCE arm_cde.td TARGET ClangARMCdeBuiltinsDef) diff --git a/clang/include/clang/Basic/TargetBuiltins.h b/clang/include/clang/Basic/TargetBuiltins.h --- a/clang/include/clang/Basic/TargetBuiltins.h +++ b/clang/include/clang/Basic/TargetBuiltins.h @@ -190,6 +190,13 @@ #include "clang/Basic/arm_sve_typeflags.inc" #undef LLVM_GET_SVE_MERGETYPES }; + + enum ImmCheckType { +#define LLVM_GET_SVE_IMMCHECKTYPES +#include "clang/Basic/arm_sve_typeflags.inc" +#undef LLVM_GET_SVE_IMMCHECKTYPES + }; + SVETypeFlags(uint64_t F) : Flags(F) { EltTypeShift = llvm::countTrailingZeros(EltTypeMask); MemEltTypeShift = llvm::countTrailingZeros(MemEltTypeMask); diff --git a/clang/include/clang/Basic/arm_sve.td b/clang/include/clang/Basic/arm_sve.td --- a/clang/include/clang/Basic/arm_sve.td +++ b/clang/include/clang/Basic/arm_sve.td @@ -61,6 +61,10 @@ // d: default // c: const pointer type // P: predicate type +// +// i: constant uint64_t +// +// I: Predicate Pattern (sv_pattern) // l: int64_t @@ -147,9 +151,22 @@ def IsStructStore : FlagType<0x00040000>; def IsZExtReturn : FlagType<0x00080000>; // Return value is sign-extend by default +// These must be kept in sync with the flags in include/clang/Basic/TargetBuiltins.h +class ImmCheckType { + int Value = val; +} +def ImmCheck0_31 : ImmCheckType<0>; // 0..31 (used for e.g. predicate patterns) +def ImmCheck1_16 : ImmCheckType<1>; // 1..16 + +class ImmCheck { + int Arg = arg; + int EltSizeArg = eltSizeArg; + ImmCheckType Kind = kind; +} + // Every intrinsic subclasses Inst. class Inst ft, MemEltType met> { + list ft, list ch, MemEltType met> { string Name = n; string Prototype = p; string Types = t; @@ -158,13 +175,21 @@ string MergeSuffix = mt.Suffix; string LLVMIntrinsic = i; list Flags = ft; + list ImmChecks = ch; int MemEltType = met.Value; } +// SInst: Instruction with signed/unsigned suffix (e.g., "s8", "u8") +class SInst ft = [], list ch = []> + : Inst { +} + // MInst: Instructions which access memory class MInst f, - MemEltType met=MemEltTyDefault, string i=""> - : Inst {} + MemEltType met = MemEltTyDefault, string i = ""> + : Inst { +} //////////////////////////////////////////////////////////////////////////////// // Loads @@ -256,3 +281,8 @@ // Store one vector, with no truncation, non-temporal (scalar base, VL displacement) def SVSTNT1_VNUM : MInst<"svstnt1_vnum[_{d}]", "vPpld", "csilUcUsUiUlhfd", [IsStore], MemEltTyDefault, "aarch64_sve_stnt1">; + +//////////////////////////////////////////////////////////////////////////////// +// Saturating scalar arithmetic +def SVQDECH_S : SInst<"svqdech_pat[_{d}]", "ddIi", "s", MergeNone, "aarch64_sve_sqdech", [], [ImmCheck<2, ImmCheck1_16>]>; +def SVQDECH_U : SInst<"svqdech_pat[_{d}]", "ddIi", "Us", MergeNone, "aarch64_sve_uqdech", [], [ImmCheck<2, ImmCheck1_16>]>; 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 @@ -11974,6 +11974,7 @@ unsigned MaxWidth); bool CheckNeonBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall); bool CheckMVEBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall); + bool CheckSVEBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall); bool CheckCDEBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall); bool CheckARMCoprocessorImmediate(const Expr *CoprocArg, bool WantCDE); bool CheckARMBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall); diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp --- a/clang/lib/CodeGen/CGBuiltin.cpp +++ b/clang/lib/CodeGen/CGBuiltin.cpp @@ -7464,6 +7464,39 @@ return Op; } +// Return the llvm vector type corresponding to the specified element TypeFlags. +llvm::Type *CodeGenFunction::getSVEType(const SVETypeFlags &TypeFlags) { + switch (TypeFlags.getEltType()) { + default: + llvm_unreachable("Invalid SVETypeFlag!"); + + case SVETypeFlags::EltTyInt8: + return llvm::VectorType::get(Builder.getInt8Ty(), {16, true}); + case SVETypeFlags::EltTyInt16: + return llvm::VectorType::get(Builder.getInt16Ty(), {8, true}); + case SVETypeFlags::EltTyInt32: + return llvm::VectorType::get(Builder.getInt32Ty(), {4, true}); + case SVETypeFlags::EltTyInt64: + return llvm::VectorType::get(Builder.getInt64Ty(), {2, true}); + + case SVETypeFlags::EltTyFloat16: + return llvm::VectorType::get(Builder.getHalfTy(), {8, true}); + case SVETypeFlags::EltTyFloat32: + return llvm::VectorType::get(Builder.getFloatTy(), {4, true}); + case SVETypeFlags::EltTyFloat64: + return llvm::VectorType::get(Builder.getDoubleTy(), {2, true}); + + case SVETypeFlags::EltTyBool8: + return llvm::VectorType::get(Builder.getInt1Ty(), {16, true}); + case SVETypeFlags::EltTyBool16: + return llvm::VectorType::get(Builder.getInt1Ty(), {8, true}); + case SVETypeFlags::EltTyBool32: + return llvm::VectorType::get(Builder.getInt1Ty(), {4, true}); + case SVETypeFlags::EltTyBool64: + return llvm::VectorType::get(Builder.getInt1Ty(), {2, true}); + } +} + // Reinterpret the input predicate so that it can be used to correctly isolate // the elements of the specified datatype. Value *CodeGenFunction::EmitSVEPredicateCast(Value *Pred, @@ -7572,8 +7605,19 @@ for (unsigned i = 0, e = E->getNumArgs(); i != e; i++) { if ((ICEArguments & (1 << i)) == 0) Ops.push_back(EmitScalarExpr(E->getArg(i))); - else - llvm_unreachable("Not yet implemented"); + else { + // If this is required to be a constant, constant fold it so that we know + // that the generated intrinsic gets a ConstantInt. + llvm::APSInt Result; + if (!E->getArg(i)->isIntegerConstantExpr(Result, getContext())) + llvm_unreachable("Expected argument to be a constant"); + + // Immediates for SVE llvm intrinsics are always 32bit. We can safely + // truncate because the immediate has been range checked and no valid + // immediate requires more than a handful of bits. + Result = Result.extOrTrunc(32); + Ops.push_back(llvm::ConstantInt::get(getLLVMContext(), Result)); + } } auto *Builtin = findARMVectorIntrinsicInMap(AArch64SVEIntrinsicMap, BuiltinID, @@ -7585,6 +7629,13 @@ TypeFlags.isZExtReturn()); else if (TypeFlags.isStore()) return EmitSVEMaskedStore(E, Ops, Builtin->LLVMIntrinsic); + else if (Builtin->LLVMIntrinsic != 0) { + llvm::Type* OverloadedTy = getSVEType(TypeFlags); + + Function *F = CGM.getIntrinsic(Builtin->LLVMIntrinsic, OverloadedTy); + Value *Call = Builder.CreateCall(F, Ops); + return Call; + } /// Should not happen return nullptr; diff --git a/clang/lib/CodeGen/CodeGenFunction.h b/clang/lib/CodeGen/CodeGenFunction.h --- a/clang/lib/CodeGen/CodeGenFunction.h +++ b/clang/lib/CodeGen/CodeGenFunction.h @@ -77,6 +77,7 @@ class ObjCAtSynchronizedStmt; class ObjCAutoreleasePoolStmt; class ReturnsNonNullAttr; +class SVETypeFlags; namespace analyze_os_log { class OSLogBufferLayout; @@ -3903,6 +3904,7 @@ llvm::Type *Ty, bool usgn, const char *name); llvm::Value *vectorWrapScalar16(llvm::Value *Op); + llvm::Type *getSVEType(const SVETypeFlags &TypeFlags); llvm::Value *EmitSVEPredicateCast(llvm::Value *Pred, llvm::VectorType *VTy); llvm::Value *EmitSVEMaskedLoad(const CallExpr *, llvm::Type *ReturnTy, SmallVectorImpl &Ops, diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -1998,6 +1998,39 @@ llvm_unreachable("Invalid NeonTypeFlag!"); } +bool Sema::CheckSVEBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { + // Range check SVE intrinsics that take immediate values. + SmallVector, 3> ImmChecks; + + switch (BuiltinID) { + default: + return false; +#define GET_SVE_IMMEDIATE_CHECK +#include "clang/Basic/arm_sve_sema_rangechecks.inc" +#undef GET_SVE_IMMEDIATE_CHECK + } + + // Perform all the immediate checks for this builtin call. + bool HasError = false; + for (auto &I : ImmChecks) { + int ArgNum, CheckTy, ElementSizeInBits; + std::tie(ArgNum, CheckTy, ElementSizeInBits) = I; + + switch ((SVETypeFlags::ImmCheckType)CheckTy) { + case SVETypeFlags::ImmCheck0_31: + if (SemaBuiltinConstantArgRange(TheCall, ArgNum, 0, 31)) + HasError = true; + break; + case SVETypeFlags::ImmCheck1_16: + if (SemaBuiltinConstantArgRange(TheCall, ArgNum, 1, 16)) + HasError = true; + break; + } + } + + return HasError; +} + bool Sema::CheckNeonBuiltinFunctionCall(unsigned BuiltinID, CallExpr *TheCall) { llvm::APSInt Result; uint64_t mask = 0; @@ -2352,6 +2385,9 @@ if (CheckNeonBuiltinFunctionCall(BuiltinID, TheCall)) return true; + if (CheckSVEBuiltinFunctionCall(BuiltinID, TheCall)) + return true; + // For intrinsics which take an immediate value as part of the instruction, // range check them here. unsigned i = 0, l = 0, u = 0; diff --git a/clang/test/CodeGen/aarch64-sve-intrinsics/acle_sve_qdech.c b/clang/test/CodeGen/aarch64-sve-intrinsics/acle_sve_qdech.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/aarch64-sve-intrinsics/acle_sve_qdech.c @@ -0,0 +1,163 @@ +// RUN: %clang_cc1 -D__ARM_FEATURE_SVE -triple aarch64-none-linux-gnu -target-feature +sve -fallow-half-arguments-and-returns -S -O1 -Werror -Wall -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -D__ARM_FEATURE_SVE -DSVE_OVERLOADED_FORMS -triple aarch64-none-linux-gnu -target-feature +sve -fallow-half-arguments-and-returns -S -O1 -Werror -Wall -emit-llvm -o - %s | FileCheck %s + +#include + +#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) A1##A3 +#else +#define SVE_ACLE_FUNC(A1,A2,A3,A4) A1##A2##A3##A4 +#endif + +svint16_t test_svqdech_pat_s16(svint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_s16 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.sqdech.nxv8i16( %op, i32 0, i32 1) + // CHECK: ret %[[INTRINSIC]] + return svqdech_pat_s16(op, SV_POW2, 1); +} + +svint16_t test_svqdech_pat_s16_all(svint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_s16_all + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.sqdech.nxv8i16( %op, i32 31, i32 16) + // CHECK: ret %[[INTRINSIC]] + return svqdech_pat_s16(op, SV_ALL, 16); +} + +svuint16_t test_svqdech_pat_u16_pow2(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_pow2 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 0, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_POW2, 16); +} + +svuint16_t test_svqdech_pat_u16_vl1(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl1 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 1, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL1, 16); +} + +svuint16_t test_svqdech_pat_u16_vl2(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl2 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 2, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL2, 16); +} + +svuint16_t test_svqdech_pat_u16_vl3(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl3 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 3, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL3, 16); +} + +svuint16_t test_svqdech_pat_u16_vl4(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl4 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 4, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL4, 16); +} + +svuint16_t test_svqdech_pat_u16_vl5(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl5 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 5, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL5, 16); +} + +svuint16_t test_svqdech_pat_u16_vl6(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl6 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 6, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL6, 16); +} + +svuint16_t test_svqdech_pat_u16_vl7(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl7 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 7, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL7, 16); +} + +svuint16_t test_svqdech_pat_u16_vl8(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl8 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 8, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL8, 16); +} + +svuint16_t test_svqdech_pat_u16_vl16(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl16 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 9, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL16, 16); +} + +svuint16_t test_svqdech_pat_u16_vl32(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl32 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 10, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL32, 16); +} + +svuint16_t test_svqdech_pat_u16_vl64(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl64 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 11, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL64, 16); +} + +svuint16_t test_svqdech_pat_u16_vl128(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl128 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 12, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL128, 16); +} + +svuint16_t test_svqdech_pat_u16_vl256(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_vl256 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 13, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_VL256, 16); +} + +svuint16_t test_svqdech_pat_u16_mul4(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_mul4 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 29, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_MUL4, 16); +} + +svuint16_t test_svqdech_pat_u16_mul3(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_mul3 + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 30, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_MUL3, 16); +} + +svuint16_t test_svqdech_pat_u16_all(svuint16_t op) +{ + // CHECK-LABEL: test_svqdech_pat_u16_all + // CHECK: %[[INTRINSIC:.*]] = call @llvm.aarch64.sve.uqdech.nxv8i16( %op, i32 31, i32 16) + // CHECK: ret %[[INTRINSIC]] + return SVE_ACLE_FUNC(svqdech_pat,_u16,,)(op, SV_ALL, 16); +} diff --git a/clang/test/CodeGen/aarch64-sve-intrinsics/negative/acle_sve_qdech.c b/clang/test/CodeGen/aarch64-sve-intrinsics/negative/acle_sve_qdech.c new file mode 100644 --- /dev/null +++ b/clang/test/CodeGen/aarch64-sve-intrinsics/negative/acle_sve_qdech.c @@ -0,0 +1,35 @@ +// RUN: %clang_cc1 -D__ARM_FEATURE_SVE -triple aarch64-none-linux-gnu -target-feature +sve -fallow-half-arguments-and-returns -fsyntax-only -verify %s +// RUN: %clang_cc1 -D__ARM_FEATURE_SVE -DSVE_OVERLOADED_FORMS -triple aarch64-none-linux-gnu -target-feature +sve -fallow-half-arguments-and-returns -fsyntax-only -verify %s + +#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) A1##A3 +#else +#define SVE_ACLE_FUNC(A1,A2,A3,A4) A1##A2##A3##A4 +#endif + +#include + +svint16_t test_svqdech_pat_s16(svint16_t op) +{ + // expected-error@+1 {{argument value 0 is outside the valid range [1, 16]}} + return svqdech_pat_s16(op, SV_VL8, 0); +} + +svint16_t test_svqdech_pat_s16_2(svint16_t op) +{ + // expected-error@+1 {{argument value 17 is outside the valid range [1, 16]}} + return svqdech_pat_s16(op, SV_VL16, 17); +} + +svuint16_t test_svqdech_pat_u16(svuint16_t op) +{ + // expected-error@+1 {{argument value 0 is outside the valid range [1, 16]}} + return svqdech_pat_u16(op, SV_VL32, 0); +} + +svuint16_t test_svqdech_pat_u16_2(svuint16_t op) +{ + // expected-error@+1 {{argument value 17 is outside the valid range [1, 16]}} + return svqdech_pat_u16(op, SV_VL64, 17); +} diff --git a/clang/utils/TableGen/SveEmitter.cpp b/clang/utils/TableGen/SveEmitter.cpp --- a/clang/utils/TableGen/SveEmitter.cpp +++ b/clang/utils/TableGen/SveEmitter.cpp @@ -46,6 +46,22 @@ namespace { +class ImmCheck { + unsigned Arg; + unsigned Kind; + unsigned ElementSizeInBits; + +public: + ImmCheck(unsigned Arg, unsigned Kind, unsigned ElementSizeInBits = 0) + : Arg(Arg), Kind(Kind), ElementSizeInBits(ElementSizeInBits) {} + ImmCheck(const ImmCheck &Other) = default; + ~ImmCheck() = default; + + unsigned getArg() const { return Arg; } + unsigned getKind() const { return Kind; } + unsigned getElementSizeInBits() const { return ElementSizeInBits; } +}; + class SVEType { TypeSpec TS; bool Float, Signed, Immediate, Void, Constant, Pointer; @@ -146,11 +162,13 @@ uint64_t Flags; + SmallVector ImmChecks; + public: Intrinsic(StringRef Name, StringRef Proto, uint64_t MergeTy, StringRef MergeSuffix, uint64_t MemoryElementTy, StringRef LLVMName, - uint64_t Flags, TypeSpec BT, ClassKind Class, SVEEmitter &Emitter, - StringRef Guard); + uint64_t Flags, ArrayRef ImmChecks, TypeSpec BT, + ClassKind Class, SVEEmitter &Emitter, StringRef Guard); ~Intrinsic()=default; @@ -171,6 +189,8 @@ uint64_t getFlags() const { return Flags; } bool isFlagSet(uint64_t Flag) const { return Flags & Flag;} + ArrayRef getImmChecks() const { return ImmChecks; } + /// Return the type string for a BUILTIN() macro in Builtins.def. std::string getBuiltinTypeStr(); @@ -204,6 +224,7 @@ llvm::StringMap MemEltTypes; llvm::StringMap FlagTypes; llvm::StringMap MergeTypes; + llvm::StringMap ImmCheckTypes; public: SVEEmitter(RecordKeeper &R) : Records(R) { @@ -215,6 +236,16 @@ FlagTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); for (auto *RV : Records.getAllDerivedDefinitions("MergeType")) MergeTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); + for (auto *RV : Records.getAllDerivedDefinitions("ImmCheckType")) + ImmCheckTypes[RV->getNameInitAsString()] = RV->getValueAsInt("Value"); + } + + /// Returns the enum value for the immcheck type + unsigned getEnumValueForImmCheck(StringRef C) const { + auto It = ImmCheckTypes.find(C); + if (It != ImmCheckTypes.end()) + return It->getValue(); + llvm_unreachable("Unsupported imm check"); } // Returns the SVETypeFlags for a given value and mask. @@ -258,6 +289,9 @@ /// Emit all the information needed to map builtin -> LLVM IR intrinsic. void createCodeGenMap(raw_ostream &o); + /// Emit all the range checks for the immediates. + void createRangeChecks(raw_ostream &o); + /// Create the SVETypeFlags used in CGBuiltins void createTypeFlags(raw_ostream &o); @@ -428,6 +462,23 @@ Bitwidth = 16; ElementBitwidth = 1; break; + case 'i': + Predicate = false; + Float = false; + ElementBitwidth = Bitwidth = 64; + NumVectors = 0; + Signed = false; + Immediate = true; + break; + case 'I': + Predicate = false; + Float = false; + ElementBitwidth = Bitwidth = 32; + NumVectors = 0; + Signed = true; + Immediate = true; + PredicatePattern = true; + break; case 'l': Predicate = false; Signed = true; @@ -531,16 +582,25 @@ Intrinsic::Intrinsic(StringRef Name, StringRef Proto, uint64_t MergeTy, StringRef MergeSuffix, uint64_t MemoryElementTy, - StringRef LLVMName, uint64_t Flags, TypeSpec BT, - ClassKind Class, SVEEmitter &Emitter, StringRef Guard) + StringRef LLVMName, uint64_t Flags, + ArrayRef Checks, TypeSpec BT, ClassKind Class, + SVEEmitter &Emitter, StringRef Guard) : Name(Name.str()), LLVMName(LLVMName), Proto(Proto.str()), BaseTypeSpec(BT), Class(Class), Guard(Guard.str()), - MergeSuffix(MergeSuffix.str()), BaseType(BT, 'd'), Flags(Flags) { + MergeSuffix(MergeSuffix.str()), BaseType(BT, 'd'), Flags(Flags), + ImmChecks(Checks.begin(), Checks.end()) { // Types[0] is the return value. for (unsigned I = 0; I < Proto.size(); ++I) { SVEType T(BaseTypeSpec, Proto[I]); Types.push_back(T); + + // Add range checks for immediates + if (I > 0) { + if (T.isPredicatePattern()) + ImmChecks.emplace_back( + I - 1, Emitter.getEnumValueForImmCheck("ImmCheck0_31")); + } } // Set flags based on properties @@ -714,6 +774,7 @@ StringRef MergeSuffix = R->getValueAsString("MergeSuffix"); uint64_t MemEltType = R->getValueAsInt("MemEltType"); std::vector FlagsList = R->getValueAsListOfDefs("Flags"); + std::vector ImmCheckList = R->getValueAsListOfDefs("ImmChecks"); int64_t Flags = 0; for (auto FlagRec : FlagsList) @@ -737,15 +798,30 @@ // Create an Intrinsic for each type spec. for (auto TS : TypeSpecs) { - Out.push_back(std::make_unique(Name, Proto, Merge, MergeSuffix, - MemEltType, LLVMName, Flags, TS, - ClassS, *this, Guard)); + // Collate a list of range/option checks for the immediates. + SmallVector ImmChecks; + for (auto *R : ImmCheckList) { + unsigned Arg = R->getValueAsInt("Arg"); + unsigned EltSizeArg = R->getValueAsInt("EltSizeArg"); + unsigned Kind = R->getValueAsDef("Kind")->getValueAsInt("Value"); + + unsigned ElementSizeInBits = 0; + if (EltSizeArg >= 0) + ElementSizeInBits = + SVEType(TS, Proto[EltSizeArg + /* offset by return arg */ 1]) + .getElementSizeInBits(); + ImmChecks.push_back(ImmCheck(Arg, Kind, ElementSizeInBits)); + } + + Out.push_back(std::make_unique( + Name, Proto, Merge, MergeSuffix, MemEltType, LLVMName, Flags, ImmChecks, + TS, ClassS, *this, Guard)); // Also generate the short-form (e.g. svadd_m) for the given type-spec. if (Intrinsic::isOverloadedIntrinsic(Name)) - Out.push_back(std::make_unique(Name, Proto, Merge, MergeSuffix, - MemEltType, LLVMName, Flags, TS, - ClassG, *this, Guard)); + Out.push_back(std::make_unique( + Name, Proto, Merge, MergeSuffix, MemEltType, LLVMName, Flags, + ImmChecks, TS, ClassG, *this, Guard)); } } @@ -795,6 +871,27 @@ OS << "typedef __SVFloat64_t svfloat64_t;\n"; OS << "typedef __SVBool_t svbool_t;\n\n"; + OS << "typedef enum\n"; + OS << "{\n"; + OS << " SV_POW2 = 0,\n"; + OS << " SV_VL1 = 1,\n"; + OS << " SV_VL2 = 2,\n"; + OS << " SV_VL3 = 3,\n"; + OS << " SV_VL4 = 4,\n"; + OS << " SV_VL5 = 5,\n"; + OS << " SV_VL6 = 6,\n"; + OS << " SV_VL7 = 7,\n"; + OS << " SV_VL8 = 8,\n"; + OS << " SV_VL16 = 9,\n"; + OS << " SV_VL32 = 10,\n"; + OS << " SV_VL64 = 11,\n"; + OS << " SV_VL128 = 12,\n"; + OS << " SV_VL256 = 13,\n"; + OS << " SV_MUL4 = 29,\n"; + OS << " SV_MUL3 = 30,\n"; + OS << " SV_ALL = 31\n"; + OS << "} sv_pattern;\n\n"; + OS << "/* Function attributes */\n"; OS << "#define __aio static inline __attribute__((__always_inline__, " "__nodebug__, __overloadable__))\n\n"; @@ -897,6 +994,41 @@ OS << "#endif\n\n"; } +void SVEEmitter::createRangeChecks(raw_ostream &OS) { + std::vector RV = Records.getAllDerivedDefinitions("Inst"); + SmallVector, 128> Defs; + for (auto *R : RV) + createIntrinsic(R, Defs); + + // The mappings must be sorted based on BuiltinID. + llvm::sort(Defs, [](const std::unique_ptr &A, + const std::unique_ptr &B) { + return A->getMangledName() < B->getMangledName(); + }); + + + OS << "#ifdef GET_SVE_IMMEDIATE_CHECK\n"; + + // Ensure these are only emitted once. + std::set Emitted; + + for (auto &Def : Defs) { + if (Emitted.find(Def->getMangledName()) != Emitted.end() || + Def->getImmChecks().empty()) + continue; + + OS << "case SVE::BI__builtin_sve_" << Def->getMangledName() << ":\n"; + for (auto &Check : Def->getImmChecks()) + OS << "ImmChecks.push_back(std::make_tuple(" << Check.getArg() << ", " + << Check.getKind() << ", " << Check.getElementSizeInBits() << "));\n"; + OS << " break;\n"; + + Emitted.insert(Def->getMangledName()); + } + + OS << "#endif\n\n"; +} + /// Create the SVETypeFlags used in CGBuiltins void SVEEmitter::createTypeFlags(raw_ostream &OS) { OS << "#ifdef LLVM_GET_SVE_TYPEFLAGS\n"; @@ -918,6 +1050,11 @@ for (auto &KV : MergeTypes) OS << " " << KV.getKey() << " = " << KV.getValue() << ",\n"; OS << "#endif\n\n"; + + OS << "#ifdef LLVM_GET_SVE_IMMCHECKTYPES\n"; + for (auto &KV : ImmCheckTypes) + OS << " " << KV.getKey() << " = " << KV.getValue() << ",\n"; + OS << "#endif\n\n"; } namespace clang { @@ -932,6 +1069,11 @@ void EmitSveBuiltinCG(RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createCodeGenMap(OS); } + +void EmitSveRangeChecks(RecordKeeper &Records, raw_ostream &OS) { + SVEEmitter(Records).createRangeChecks(OS); +} + void EmitSveTypeFlags(RecordKeeper &Records, raw_ostream &OS) { SVEEmitter(Records).createTypeFlags(OS); } diff --git a/clang/utils/TableGen/TableGen.cpp b/clang/utils/TableGen/TableGen.cpp --- a/clang/utils/TableGen/TableGen.cpp +++ b/clang/utils/TableGen/TableGen.cpp @@ -74,6 +74,7 @@ GenArmSveBuiltins, GenArmSveBuiltinCG, GenArmSveTypeFlags, + GenArmSveRangeChecks, GenArmCdeHeader, GenArmCdeBuiltinDef, GenArmCdeBuiltinSema, @@ -197,6 +198,8 @@ "Generate arm_sve_builtin_cg_map.inc for clang"), clEnumValN(GenArmSveTypeFlags, "gen-arm-sve-typeflags", "Generate arm_sve_typeflags.inc for clang"), + clEnumValN(GenArmSveRangeChecks, "gen-arm-sve-sema-rangechecks", + "Generate arm_sve_sema_rangechecks.inc for clang"), clEnumValN(GenArmMveHeader, "gen-arm-mve-header", "Generate arm_mve.h for clang"), clEnumValN(GenArmMveBuiltinDef, "gen-arm-mve-builtin-def", @@ -390,6 +393,9 @@ case GenArmSveTypeFlags: EmitSveTypeFlags(Records, OS); break; + case GenArmSveRangeChecks: + EmitSveRangeChecks(Records, OS); + break; case GenArmCdeHeader: EmitCdeHeader(Records, OS); break; diff --git a/clang/utils/TableGen/TableGenBackends.h b/clang/utils/TableGen/TableGenBackends.h --- a/clang/utils/TableGen/TableGenBackends.h +++ b/clang/utils/TableGen/TableGenBackends.h @@ -95,6 +95,7 @@ void EmitSveBuiltins(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveBuiltinCG(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitSveTypeFlags(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); +void EmitSveRangeChecks(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitMveHeader(llvm::RecordKeeper &Records, llvm::raw_ostream &OS); void EmitMveBuiltinDef(llvm::RecordKeeper &Records, llvm::raw_ostream &OS);