From 6bff58bb35499c5047e06eaed35174d50deae409 Mon Sep 17 00:00:00 2001 From: Tom Honermann Date: Sun, 1 Oct 2023 18:52:45 -0700 Subject: [PATCH] [clang][DFP] Add __attribute__((mode(*D))) support for declaring decimal floating-point types. Current proposals for supporting decimal floating-point (DFP) types in C++, e.g., ISO/IEC TR 24733:2011, do not specify keywords to be used as type specifiers. Rather, DFP types are specified as standard library types that, in practice, wrap a builtin type that is not otherwise exposed with a simple name. Gcc enables builtin DFP types to be declared using the GNU mode attribute as follows: float __attribute__((mode(SD))) // _Decimal32 float __attribute__((mode(DD))) // _Decimal64 float __attribute__((mode(TD))) // _Decimal128 This change implements support for these additional machine modes. This change also extends several of the clang::Type AST node attributes to include DFP types in addition to other floating-point types. These extensions match extensions to terms of the same name made in C23. The directly affected predicates includes: Type::isRealFloatingType() Type::isFloatingType() Type::isRealType() Type::isArithmeticType() The following new predicate can be used to differentiate DFP types where needed. Type::isDecimalFloatingType() The existing DFP specifications, including C23, do not include support for complex DFP types. Gcc does not provide such support either. These changes follow that existing precedent and do not enable support for complex DFP types as well. However, vector types are allowed to have DFP types as their element type. --- clang/include/clang/AST/Type.h | 11 +++- clang/include/clang/Basic/TargetInfo.h | 16 ++++- clang/lib/AST/ASTContext.cpp | 6 ++ clang/lib/AST/Type.cpp | 12 +++- clang/lib/Basic/TargetInfo.cpp | 3 + clang/lib/Sema/SemaChecking.cpp | 9 ++- clang/lib/Sema/SemaDeclAttr.cpp | 14 ++++ clang/test/Driver/dfp-enablement-lang.c | 8 +++ clang/test/Sema/dfp-types.c | 87 +++++++++++++++++++++++-- clang/test/SemaCXX/dfp-types.cpp | 14 ++++ 10 files changed, 169 insertions(+), 11 deletions(-) create mode 100644 clang/test/SemaCXX/dfp-types.cpp diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 46dbadd8b878b..3fcec7b594abc 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -2176,9 +2176,14 @@ class alignas(8) Type : public ExtQualsTypeCommonBase { bool isUnscopedEnumerationType() const; /// Floating point categories. + bool isDecimalFloatingType() const; + // C23 6.2.5p13 (_Decimal32/64/128) bool isRealFloatingType() const; // C99 6.2.5p10 (float, double, long double) + // C23 6.2.5p14 (standard + decimal float) + // C23 H.2.4p5 (+interchange +extended FP) /// isComplexType() does *not* include complex integers (a GCC extension). /// isComplexIntegerType() can be used to test for complex integers. + /// C23 did not add complex decimal floating-point. bool isComplexType() const; // C99 6.2.5p11 (complex) bool isAnyComplexType() const; // C99 6.2.5p11 (complex) + Complex Int. bool isFloatingType() const; // C99 6.2.5p11 (real floating + complex) @@ -2746,8 +2751,12 @@ class BuiltinType : public Type { return getKind() >= Bool && getKind() <= UInt128; } + bool isDecimalFloatingPoint() const { + return getKind() >= DecimalFloat32 && getKind() <= DecimalFloat128; + } + bool isFloatingPoint() const { - return getKind() >= Half && getKind() <= Ibm128; + return getKind() >= Half && getKind() <= DecimalFloat128; } bool isSVEBool() const { return getKind() == Kind::SveBool; } diff --git a/clang/include/clang/Basic/TargetInfo.h b/clang/include/clang/Basic/TargetInfo.h index ddf166c971fec..f8734dcda10c7 100644 --- a/clang/include/clang/Basic/TargetInfo.h +++ b/clang/include/clang/Basic/TargetInfo.h @@ -75,9 +75,23 @@ enum class FloatModeKind { LongDouble = 1 << 3, Float128 = 1 << 4, Ibm128 = 1 << 5, - LLVM_MARK_AS_BITMASK_ENUM(Ibm128) + Decimal32 = 1 << 6, + Decimal64 = 1 << 7, + Decimal128 = 1 << 8, + LLVM_MARK_AS_BITMASK_ENUM(Decimal128) }; +inline bool isDecimalFloatModeKind(FloatModeKind FMK) { + switch (FMK) { + case FloatModeKind::Decimal32: + case FloatModeKind::Decimal64: + case FloatModeKind::Decimal128: + return true; + default: + return false; + } +} + /// Fields controlling how types are laid out in memory; these may need to /// be copied for targets like AMDGPU that base their ABIs on an auxiliary /// CPU target. diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 1a411232cab9d..0aef78b685460 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -12164,6 +12164,12 @@ QualType ASTContext::getRealTypeForBitwidth(unsigned DestWidth, return Float128Ty; case FloatModeKind::Ibm128: return Ibm128Ty; + case FloatModeKind::Decimal32: + return DecimalFloat32Ty; + case FloatModeKind::Decimal64: + return DecimalFloat64Ty; + case FloatModeKind::Decimal128: + return DecimalFloat128Ty; case FloatModeKind::NoFloat: return {}; } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 91de51add303e..26d813b160bc3 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2190,7 +2190,7 @@ bool Type::hasUnsignedIntegerRepresentation() const { bool Type::isFloatingType() const { if (const auto *BT = dyn_cast(CanonicalType)) return BT->getKind() >= BuiltinType::Half && - BT->getKind() <= BuiltinType::Ibm128; + BT->getKind() <= BuiltinType::DecimalFloat128; if (const auto *CT = dyn_cast(CanonicalType)) return CT->getElementType()->isFloatingType(); return false; @@ -2204,6 +2204,12 @@ bool Type::hasFloatingRepresentation() const { return isFloatingType(); } +bool Type::isDecimalFloatingType() const { + if (const auto *BT = dyn_cast(CanonicalType)) + return BT->isDecimalFloatingPoint(); + return false; +} + bool Type::isRealFloatingType() const { if (const auto *BT = dyn_cast(CanonicalType)) return BT->isFloatingPoint(); @@ -2213,7 +2219,7 @@ bool Type::isRealFloatingType() const { bool Type::isRealType() const { if (const auto *BT = dyn_cast(CanonicalType)) return BT->getKind() >= BuiltinType::Bool && - BT->getKind() <= BuiltinType::Ibm128; + BT->getKind() <= BuiltinType::DecimalFloat128; if (const auto *ET = dyn_cast(CanonicalType)) return ET->getDecl()->isComplete() && !ET->getDecl()->isScoped(); return isBitIntType(); @@ -2222,7 +2228,7 @@ bool Type::isRealType() const { bool Type::isArithmeticType() const { if (const auto *BT = dyn_cast(CanonicalType)) return BT->getKind() >= BuiltinType::Bool && - BT->getKind() <= BuiltinType::Ibm128; + BT->getKind() <= BuiltinType::DecimalFloat128; if (const auto *ET = dyn_cast(CanonicalType)) // GCC allows forward declaration of enum types (forbid by C99 6.7.2.3p2). // If a body isn't seen by the time we get here, return false. diff --git a/clang/lib/Basic/TargetInfo.cpp b/clang/lib/Basic/TargetInfo.cpp index 5cd7e259db90d..6b34c9e8d33eb 100644 --- a/clang/lib/Basic/TargetInfo.cpp +++ b/clang/lib/Basic/TargetInfo.cpp @@ -319,6 +319,9 @@ TargetInfo::IntType TargetInfo::getLeastIntTypeByWidth(unsigned BitWidth, FloatModeKind TargetInfo::getRealTypeByWidth(unsigned BitWidth, FloatModeKind ExplicitType) const { + if (isDecimalFloatModeKind(ExplicitType)) + return ExplicitType; + if (getHalfWidth() == BitWidth) return FloatModeKind::Half; if (getFloatWidth() == BitWidth) diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index fad70223362ed..97f175f770ca3 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8531,8 +8531,8 @@ bool Sema::SemaBuiltinComplex(CallExpr *TheCall) { << Real->getSourceRange() << Imag->getSourceRange(); } - // We don't allow _Complex _Float16 nor _Complex __fp16 as type specifiers; - // don't allow this builtin to form those types either. + // We don't allow _Complex _Float16, _Complex __fp16, or _Complex _DecimalXX + // as type specifiers; don't allow this builtin to form those types either. // FIXME: Should we allow these types? if (Real->getType()->isFloat16Type()) return Diag(TheCall->getBeginLoc(), diag::err_invalid_complex_spec) @@ -8540,6 +8540,11 @@ bool Sema::SemaBuiltinComplex(CallExpr *TheCall) { if (Real->getType()->isHalfType()) return Diag(TheCall->getBeginLoc(), diag::err_invalid_complex_spec) << "half"; + if (Real->getType()->isDecimalFloatingType()) { + const BuiltinType *BT = Real->getType()->getAs(); + return Diag(TheCall->getBeginLoc(), diag::err_invalid_complex_spec) + << BT->getName(Context.getPrintingPolicy()); + } TheCall->setType(Context.getComplexType(Real->getType())); return false; diff --git a/clang/lib/Sema/SemaDeclAttr.cpp b/clang/lib/Sema/SemaDeclAttr.cpp index cc98713241395..cf9c51329577e 100644 --- a/clang/lib/Sema/SemaDeclAttr.cpp +++ b/clang/lib/Sema/SemaDeclAttr.cpp @@ -4668,6 +4668,7 @@ static void parseModeAttrArg(Sema &S, StringRef Str, unsigned &DestWidth, IntegerMode = true; ComplexMode = false; ExplicitType = FloatModeKind::NoFloat; + FloatModeKind ExplicitDFPType = FloatModeKind::NoFloat; switch (Str.size()) { case 2: switch (Str[0]) { @@ -4678,9 +4679,11 @@ static void parseModeAttrArg(Sema &S, StringRef Str, unsigned &DestWidth, DestWidth = 16; break; case 'S': + ExplicitDFPType = FloatModeKind::Decimal32; DestWidth = 32; break; case 'D': + ExplicitDFPType = FloatModeKind::Decimal64; DestWidth = 64; break; case 'X': @@ -4692,6 +4695,7 @@ static void parseModeAttrArg(Sema &S, StringRef Str, unsigned &DestWidth, break; case 'T': ExplicitType = FloatModeKind::LongDouble; + ExplicitDFPType = FloatModeKind::Decimal128; DestWidth = 128; break; case 'I': @@ -4704,6 +4708,9 @@ static void parseModeAttrArg(Sema &S, StringRef Str, unsigned &DestWidth, } else if (Str[1] == 'C') { IntegerMode = false; ComplexMode = true; + } else if (Str[1] == 'D') { + IntegerMode = false; + ExplicitType = ExplicitDFPType; } else if (Str[1] != 'I') { DestWidth = 0; } @@ -4853,6 +4860,13 @@ void Sema::AddModeAttr(Decl *D, const AttributeCommonInfo &CI, return; } + if (NewElemTy->isDecimalFloatingType()) { + if (!getLangOpts().DecimalFloatingPoint) { + Diag(AttrLoc, diag::err_dfp_disabled); + return; + } + } + if (ComplexMode) { NewElemTy = Context.getComplexType(NewElemTy); } diff --git a/clang/test/Driver/dfp-enablement-lang.c b/clang/test/Driver/dfp-enablement-lang.c index fe566e6cef848..05406d653a69f 100644 --- a/clang/test/Driver/dfp-enablement-lang.c +++ b/clang/test/Driver/dfp-enablement-lang.c @@ -34,3 +34,11 @@ _Decimal64 d64; // cxx-error {{unknown type name '_Decimal64'}} \ // c-dfp-off-error {{decimal floating-point extensions are not enabled}} _Decimal128 d128; // cxx-error {{unknown type name '_Decimal128'}} \ // c-dfp-off-error {{decimal floating-point extensions are not enabled}} + +typedef float __attribute__((mode(SD))) D32; // dfp-off-error {{decimal floating-point extensions are not enabled}} +typedef float __attribute__((mode(DD))) D64; // dfp-off-error {{decimal floating-point extensions are not enabled}} +typedef float __attribute__((mode(TD))) D128; // dfp-off-error {{decimal floating-point extensions are not enabled}} + +float __attribute__((mode(SD))) famsd; // dfp-off-error {{decimal floating-point extensions are not enabled}} +float __attribute__((mode(DD))) famdd; // dfp-off-error {{decimal floating-point extensions are not enabled}} +float __attribute__((mode(TD))) famtd; // dfp-off-error {{decimal floating-point extensions are not enabled}} diff --git a/clang/test/Sema/dfp-types.c b/clang/test/Sema/dfp-types.c index 86bb5ce945ca1..330fb898f9414 100644 --- a/clang/test/Sema/dfp-types.c +++ b/clang/test/Sema/dfp-types.c @@ -1,9 +1,88 @@ -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -fexperimental-decimal-floating-point -fsyntax-only -verify=c %s -// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ -std=c++2c -fexperimental-decimal-floating-point -fsyntax-only -verify=cxx %s - -// c-no-diagnostics +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c23 -fexperimental-decimal-floating-point -fsyntax-only -verify=expected,c %s +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -x c++ -std=c++2c -fexperimental-decimal-floating-point -fsyntax-only -verify=expected,cxx %s // _Decimal32, _Decimal64, and _Decimal128 are never keywords in C++. _Decimal32 d32; // cxx-error {{unknown type name '_Decimal32'}} _Decimal64 d64; // cxx-error {{unknown type name '_Decimal64'}} _Decimal128 d28; // cxx-error {{unknown type name '_Decimal128'}} + +// DFP types are available via the GNU mode attribute in both C and C++. +typedef float __attribute__((mode(SD))) D32; +typedef float __attribute__((mode(DD))) D64; +typedef float __attribute__((mode(TD))) D128; + +// The GNU mode attribute requires a floating point base type for DFP types. +// These are ok. +long double __attribute((mode(SD))) ldamsd; +double __attribute((mode(DD))) damdd; +_Float16 __attribute((mode(SD))) f16amsd; +__bf16 __attribute((mode(SD))) bf16amsd; +__float128 __attribute((mode(TD))) f128amtd; +// These are not ok. +void __attribute((mode(SD))) vamsd; // expected-error {{type of machine mode does not match type of base type}} +int __attribute((mode(DD))) iamdd; // expected-error {{type of machine mode does not match type of base type}} +int* __attribute((mode(TD))) ipamtd; // expected-error {{mode attribute only supported for integer and floating-point types}} +float __attribute((mode(TD))) *fapmtd; // expected-error {{mode attribute only supported for integer and floating-point types}} + +// DFP types may be used as vector elements, but declaration form is restricted. +float __attribute__((mode(V4SD))) famv4sd; // expected-warning {{deprecated; use the 'vector_size' attribute instead}} +float __attribute__((mode(SD))) __attribute__((vector_size(16))) famsdv16; +D64 __attribute__((vector_size(16))) d64av16; + +// DFP types are not allowed as elements of complex types. +D32 _Complex d32c; // expected-error {{'_Complex type-name' is invalid}} +_Decimal32 _Complex kd32c; // c-error {{'_Complex _Decimal32' is invalid}} \ + cxx-error {{unknown type name '_Decimal32'}} + +_Static_assert(sizeof(D32) == 4); +_Static_assert(sizeof(D64) == 8); +_Static_assert(sizeof(D128) == 16); + +_Static_assert(_Alignof(D32) == 4); +_Static_assert(_Alignof(D64) == 8); +_Static_assert(_Alignof(D128) == 16); + +struct s { + D32 d32; + D64 d64; + D128 d128; + union { + D32 ud32; + D64 ud64; + D128 ud128; + }; +}; + +struct bitfield { + D32 d32 : 32; // expected-error {{bit-field 'd32' has non-integral type}} + D64 d64 : 64; // expected-error {{bit-field 'd64' has non-integral type}} + D128 d128 : 128; // expected-error {{bit-field 'd128' has non-integral type}} +}; + +D32 test_d32(D32 d32) { + return d32; +} + +D64 test_d64(D64 d64) { + return d64; +} + +D128 test_d128(D128 d128) { + return d128; +} + +void test_builtin_complex(D32 d32) { + __auto_type lv = __builtin_complex(d32, d32); // expected-error {{'_Complex _Decimal32' is invalid}} +} + +void test_generic(D32 d32, D64 d64, D128 d128) { + (void)_Generic(d32, D64 : 0, D128 : 0); // expected-error-re {{controlling expression type {{.*}} not compatible with any generic association type}} + (void)_Generic(d64, D32 : 0, D128 : 0); // expected-error-re {{controlling expression type {{.*}} not compatible with any generic association type}} + (void)_Generic(d128, D32 : 0, D64 : 0); // expected-error-re {{controlling expression type {{.*}} not compatible with any generic association type}} + _Static_assert(_Generic(d32, D64 : 0, D128 : 0, default : 1) == 1); + _Static_assert(_Generic(d64, D32 : 0, D128 : 0, default : 1) == 1); + _Static_assert(_Generic(d128, D32 : 0, D64 : 0, default : 1) == 1); + _Static_assert(_Generic(d32, D32 : 1, D64 : 0, D128 : 0) == 1); + _Static_assert(_Generic(d64, D32 : 0, D64 : 1, D128 : 0) == 1); + _Static_assert(_Generic(d128, D32 : 0, D64 : 0, D128 : 1) == 1); +} diff --git a/clang/test/SemaCXX/dfp-types.cpp b/clang/test/SemaCXX/dfp-types.cpp new file mode 100644 index 0000000000000..9a26aefab20f6 --- /dev/null +++ b/clang/test/SemaCXX/dfp-types.cpp @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -triple x86_64-unknown-linux-gnu -std=c++2c -fexperimental-decimal-floating-point -fsyntax-only -verify %s + +using D32 = float __attribute__((mode(SD))); +using D64 = float __attribute__((mode(DD))); +using D128 = float __attribute__((mode(TD))); + +// Dependent type specifiers for the GNU mode attribute base type are ok, but +// must be of a valid type when instantiated. +template +T __attribute((mode(SD))) dtamsd; // expected-error {{type of machine mode does not match type of base type}} +auto g1 = dtamsd; +auto g2 = dtamsd; +auto g3 = dtamsd; +auto g4 = dtamsd; // expected-note {{in instantiation of variable template specialization 'dtamsd' requested here}}