https://github.com/AaronBallman updated https://github.com/llvm/llvm-project/pull/133125
>From 75ef42d644da9136fb07014ade18b6be137426a1 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 26 Mar 2025 12:54:29 -0400 Subject: [PATCH 01/11] [C2y] Implement WG14 N3369 and N3469 (_Countof) C2y adds the _Countof operator which returns the number of elements in an array. As with sizeof, _Countof either accepts a parenthesized type name or an expression. Its operand must be (of) an array type. When passed a constant-size array operand, the operator is a constant expression which is valid for use as an integer constant expression. Fixes #102836 --- clang/docs/LanguageExtensions.rst | 1 + clang/docs/ReleaseNotes.rst | 6 ++ .../clang/Basic/DiagnosticParseKinds.td | 5 + .../clang/Basic/DiagnosticSemaKinds.td | 2 + clang/include/clang/Basic/TokenKinds.def | 2 + clang/lib/AST/ExprConstant.cpp | 19 +++- clang/lib/AST/ItaniumMangle.cpp | 1 + clang/lib/CodeGen/CGExprScalar.cpp | 15 ++- clang/lib/Parse/ParseExpr.cpp | 21 ++++- clang/lib/Sema/SemaExpr.cpp | 39 ++++++-- clang/test/C/C2y/n3369.c | 61 ++++++++++++ clang/test/C/C2y/n3369_1.c | 25 +++++ clang/test/C/C2y/n3369_2.c | 92 +++++++++++++++++++ clang/test/C/C2y/n3469.c | 14 +++ clang/www/c_status.html | 4 +- 15 files changed, 288 insertions(+), 19 deletions(-) create mode 100644 clang/test/C/C2y/n3369.c create mode 100644 clang/test/C/C2y/n3369_1.c create mode 100644 clang/test/C/C2y/n3369_2.c create mode 100644 clang/test/C/C2y/n3469.c diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index d4771775c9739..8b5707ce2acac 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1653,6 +1653,7 @@ Array & element qualification (N2607) C Attributes (N2335) C23 C89 ``#embed`` (N3017) C23 C89, C++ Octal literals prefixed with ``0o`` or ``0O`` C2y C89, C++ +``_Countof`` (N3369, N3469) C2y C89 ============================================= ================================ ============= ============= Builtin type aliases diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index 04ec2cfef679c..b82e79c092c4e 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -141,6 +141,12 @@ C2y Feature Support paper also introduced octal and hexadecimal delimited escape sequences (e.g., ``"\x{12}\o{12}"``) which are also supported as an extension in older C language modes. +- Implemented `WG14 N3369 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf>`_ + which introduces the ``_Lengthof`` operator, and `WG14 N3469 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3469.htm>`_ + which renamed ``_Lengthof`` to ``_Countof``. This feature is implemented as + a conforming extension in earlier C language modes, but not in C++ language + modes (``std::extent`` and ``std::rank`` already provide the same + functionality but with more granularity). C23 Feature Support ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/DiagnosticParseKinds.td b/clang/include/clang/Basic/DiagnosticParseKinds.td index 4dc956f7ae6f7..86c361b4dbcf7 100644 --- a/clang/include/clang/Basic/DiagnosticParseKinds.td +++ b/clang/include/clang/Basic/DiagnosticParseKinds.td @@ -171,12 +171,17 @@ def ext_c99_feature : Extension< "'%0' is a C99 extension">, InGroup<C99>; def ext_c11_feature : Extension< "'%0' is a C11 extension">, InGroup<C11>; +def ext_c2y_feature : Extension< + "'%0' is a C2y extension">, InGroup<C2y>; def warn_c11_compat_keyword : Warning< "'%0' is incompatible with C standards before C11">, InGroup<CPre11Compat>, DefaultIgnore; def warn_c23_compat_keyword : Warning< "'%0' is incompatible with C standards before C23">, InGroup<CPre23Compat>, DefaultIgnore; +def warn_c2y_compat_keyword : Warning< + "'%0' is incompatible with C standards before C2y">, + InGroup<CPre2yCompat>, DefaultIgnore; def err_c11_noreturn_misplaced : Error< "'_Noreturn' keyword must precede function declarator">; diff --git a/clang/include/clang/Basic/DiagnosticSemaKinds.td b/clang/include/clang/Basic/DiagnosticSemaKinds.td index c77cde297dc32..1e900437d41ce 100644 --- a/clang/include/clang/Basic/DiagnosticSemaKinds.td +++ b/clang/include/clang/Basic/DiagnosticSemaKinds.td @@ -7022,6 +7022,8 @@ def err_sizeof_alignof_typeof_bitfield : Error< "bit-field">; def err_alignof_member_of_incomplete_type : Error< "invalid application of 'alignof' to a field of a class still being defined">; +def err_countof_arg_not_array_type : Error< + "'_Countof' requires an argument of array type; %0 invalid">; def err_vecstep_non_scalar_vector_type : Error< "'vec_step' requires built-in scalar or vector type, %0 invalid">; def err_offsetof_incomplete_type : Error< diff --git a/clang/include/clang/Basic/TokenKinds.def b/clang/include/clang/Basic/TokenKinds.def index 1bf9f43f80986..880928ae0447d 100644 --- a/clang/include/clang/Basic/TokenKinds.def +++ b/clang/include/clang/Basic/TokenKinds.def @@ -349,6 +349,8 @@ KEYWORD(__func__ , KEYALL) KEYWORD(__objc_yes , KEYALL) KEYWORD(__objc_no , KEYALL) +// C2y +UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX) // C++ 2.11p1: Keywords. KEYWORD(asm , KEYCXX|KEYGNU) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 95da7b067b459..92b1f41bf2fab 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -14926,6 +14926,23 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr( return false; } + case UETT_CountOf: { + QualType Ty = E->getTypeOfArgument(); + assert(Ty->isArrayType()); + + // We don't need to worry about array element qualifiers, so getting the + // unsafe array type is fine. + if (const auto *CAT = + dyn_cast<ConstantArrayType>(Ty->getAsArrayTypeUnsafe())) { + return Success(CAT->getSize(), E); + } + + // If it wasn't a constant array, it's not a valid constant expression. + assert(!Ty->isConstantSizeType()); + // FIXME: Better diagnostic. + Info.FFDiag(E->getBeginLoc()); + return false; + } } llvm_unreachable("unknown expr/type trait"); @@ -17425,7 +17442,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { } case Expr::UnaryExprOrTypeTraitExprClass: { const UnaryExprOrTypeTraitExpr *Exp = cast<UnaryExprOrTypeTraitExpr>(E); - if ((Exp->getKind() == UETT_SizeOf) && + if ((Exp->getKind() == UETT_SizeOf || Exp->getKind() == UETT_CountOf) && Exp->getTypeOfArgument()->isVariableArrayType()) return ICEDiag(IK_NotICE, E->getBeginLoc()); return NoDiag(); diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 917402544d4f6..981cdb3c806b1 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -5367,6 +5367,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity, MangleAlignofSizeofArg(); break; + case UETT_CountOf: case UETT_VectorElements: case UETT_OpenMPRequiredSimdAlign: case UETT_VecStep: diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index eccdcdb497f84..e858de7a4f6d2 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -3477,7 +3477,7 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( const UnaryExprOrTypeTraitExpr *E) { QualType TypeToSize = E->getTypeOfArgument(); if (auto Kind = E->getKind(); - Kind == UETT_SizeOf || Kind == UETT_DataSizeOf) { + Kind == UETT_SizeOf || Kind == UETT_DataSizeOf || Kind == UETT_CountOf) { if (const VariableArrayType *VAT = CGF.getContext().getAsVariableArrayType(TypeToSize)) { if (E->isArgumentType()) { @@ -3492,10 +3492,15 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( auto VlaSize = CGF.getVLASize(VAT); llvm::Value *size = VlaSize.NumElts; - // Scale the number of non-VLA elements by the non-VLA element size. - CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type); - if (!eltSize.isOne()) - size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size); + // For sizeof and __datasizeof, we need to scale the number of elements + // by the size of the array element type. For _Countof, we just want to + // return the size directly. + if (Kind != UETT_CountOf) { + // Scale the number of non-VLA elements by the non-VLA element size. + CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type); + if (!eltSize.isOne()) + size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size); + } return size; } diff --git a/clang/lib/Parse/ParseExpr.cpp b/clang/lib/Parse/ParseExpr.cpp index 0c28972d6ed8f..0a22f7372a9f9 100644 --- a/clang/lib/Parse/ParseExpr.cpp +++ b/clang/lib/Parse/ParseExpr.cpp @@ -904,6 +904,8 @@ ExprResult Parser::ParseBuiltinPtrauthTypeDiscriminator() { /// [GNU] '__alignof' '(' type-name ')' /// [C11] '_Alignof' '(' type-name ')' /// [C++11] 'alignof' '(' type-id ')' +/// [C2y] '_Countof' unary-expression +/// [C2y] '_Countof' '(' type-name ')' /// [GNU] '&&' identifier /// [C++11] 'noexcept' '(' expression ')' [C++11 5.3.7] /// [C++] new-expression @@ -1544,6 +1546,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind, // unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')' case tok::kw___builtin_omp_required_simd_align: case tok::kw___builtin_vectorelements: + case tok::kw__Countof: if (NotPrimaryExpression) *NotPrimaryExpression = true; AllowSuffix = false; @@ -2463,7 +2466,7 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok, tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step, tok::kw___builtin_omp_required_simd_align, - tok::kw___builtin_vectorelements) && + tok::kw___builtin_vectorelements, tok::kw__Countof) && "Not a typeof/sizeof/alignof/vec_step expression!"); ExprResult Operand; @@ -2510,9 +2513,9 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok, // is not going to help when the nesting is too deep. In this corner case // we continue to parse with sufficient stack space to avoid crashing. if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, - tok::kw_alignof, tok::kw__Alignof) && + tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof) && Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, - tok::kw_alignof, tok::kw__Alignof)) + tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof)) Actions.runWithSufficientStackSpace(Tok.getLocation(), [&] { Operand = ParseCastExpression(UnaryExprOnly); }); @@ -2594,12 +2597,14 @@ ExprResult Parser::ParseSYCLUniqueStableNameExpression() { /// [GNU] '__alignof' '(' type-name ')' /// [C11] '_Alignof' '(' type-name ')' /// [C++11] 'alignof' '(' type-id ')' +/// [C2y] '_Countof' unary-expression +/// [C2y] '_Countof' '(' type-name ')' /// \endverbatim ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() { assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step, tok::kw___builtin_omp_required_simd_align, - tok::kw___builtin_vectorelements) && + tok::kw___builtin_vectorelements, tok::kw__Countof) && "Not a sizeof/alignof/vec_step expression!"); Token OpTok = Tok; ConsumeToken(); @@ -2656,6 +2661,8 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() { Diag(OpTok, diag::warn_cxx98_compat_alignof); else if (getLangOpts().C23 && OpTok.is(tok::kw_alignof)) Diag(OpTok, diag::warn_c23_compat_keyword) << OpTok.getName(); + else if (getLangOpts().C2y && OpTok.is(tok::kw__Countof)) + Diag(OpTok, diag::warn_c2y_compat_keyword) << OpTok.getName(); EnterExpressionEvaluationContext Unevaluated( Actions, Sema::ExpressionEvaluationContext::Unevaluated, @@ -2690,6 +2697,12 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() { case tok::kw___builtin_vectorelements: ExprKind = UETT_VectorElements; break; + case tok::kw__Countof: + ExprKind = UETT_CountOf; + assert(!getLangOpts().CPlusPlus && "_Countof in C++ mode?"); + if (!getLangOpts().C2y) + Diag(OpTok, diag::ext_c2y_feature) << OpTok.getName(); + break; default: break; } diff --git a/clang/lib/Sema/SemaExpr.cpp b/clang/lib/Sema/SemaExpr.cpp index 3af6d6c23438f..f7554d90ee4c5 100644 --- a/clang/lib/Sema/SemaExpr.cpp +++ b/clang/lib/Sema/SemaExpr.cpp @@ -4266,7 +4266,7 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E, bool IsUnevaluatedOperand = (ExprKind == UETT_SizeOf || ExprKind == UETT_DataSizeOf || ExprKind == UETT_AlignOf || ExprKind == UETT_PreferredAlignOf || - ExprKind == UETT_VecStep); + ExprKind == UETT_VecStep || ExprKind == UETT_CountOf); if (IsUnevaluatedOperand) { ExprResult Result = CheckUnevaluatedOperand(E); if (Result.isInvalid()) @@ -4338,6 +4338,21 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(Expr *E, E->getSourceRange(), ExprKind)) return true; + if (ExprKind == UETT_CountOf) { + // The type has to be an array type. We already checked for incomplete + // types above. + QualType ExprType = E->IgnoreParens()->getType(); + if (!ExprType->isArrayType()) { + Diag(E->getExprLoc(), diag::err_countof_arg_not_array_type) << ExprType; + return true; + } + // FIXME: warn on _Countof on an array parameter. Not warning on it + // currently because there are papers in WG14 about array types which do + // not decay that could impact this behavior, so we want to see if anything + // changes here before coming up with a warning group for _Countof-related + // diagnostics. + } + if (ExprKind == UETT_SizeOf) { if (const auto *DeclRef = dyn_cast<DeclRefExpr>(E->IgnoreParens())) { if (const auto *PVD = dyn_cast<ParmVarDecl>(DeclRef->getFoundDecl())) { @@ -4608,6 +4623,15 @@ bool Sema::CheckUnaryExprOrTypeTraitOperand(QualType ExprType, return true; } + if (ExprKind == UETT_CountOf) { + // The type has to be an array type. We already checked for incomplete + // types above. + if (!ExprType->isArrayType()) { + Diag(OpLoc, diag::err_countof_arg_not_array_type) << ExprType; + return true; + } + } + // WebAssembly tables are always illegal operands to unary expressions and // type traits. if (Context.getTargetInfo().getTriple().isWasm() && @@ -4666,7 +4690,8 @@ ExprResult Sema::CreateUnaryExprOrTypeTraitExpr(TypeSourceInfo *TInfo, // properly deal with VLAs in nested calls of sizeof and typeof. if (currentEvaluationContext().isUnevaluated() && currentEvaluationContext().InConditionallyConstantEvaluateContext && - ExprKind == UETT_SizeOf && TInfo->getType()->isVariablyModifiedType()) + (ExprKind == UETT_SizeOf || ExprKind == UETT_CountOf) && + TInfo->getType()->isVariablyModifiedType()) TInfo = TransformToPotentiallyEvaluated(TInfo); // C99 6.5.3.4p4: the type (an unsigned integer type) is size_t. @@ -4697,16 +4722,16 @@ Sema::CreateUnaryExprOrTypeTraitExpr(Expr *E, SourceLocation OpLoc, } else if (E->refersToBitField()) { // C99 6.5.3.4p1. Diag(E->getExprLoc(), diag::err_sizeof_alignof_typeof_bitfield) << 0; isInvalid = true; - } else if (ExprKind == UETT_VectorElements) { - isInvalid = CheckUnaryExprOrTypeTraitOperand(E, UETT_VectorElements); - } else { - isInvalid = CheckUnaryExprOrTypeTraitOperand(E, UETT_SizeOf); + } else if (ExprKind == UETT_VectorElements || ExprKind == UETT_SizeOf || + ExprKind == UETT_CountOf) { // FIXME: __datasizeof? + isInvalid = CheckUnaryExprOrTypeTraitOperand(E, ExprKind); } if (isInvalid) return ExprError(); - if (ExprKind == UETT_SizeOf && E->getType()->isVariableArrayType()) { + if ((ExprKind == UETT_SizeOf || ExprKind == UETT_CountOf) && + E->getType()->isVariableArrayType()) { PE = TransformToPotentiallyEvaluated(E); if (PE.isInvalid()) return ExprError(); E = PE.get(); diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c new file mode 100644 index 0000000000000..c199feb7f9d54 --- /dev/null +++ b/clang/test/C/C2y/n3369.c @@ -0,0 +1,61 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wall -Wno-comment -verify %s + +/* WG14 N3369: Clang 21 + * _Lengthof operator + * + * Adds an operator to get the length of an array. Note that WG14 N3469 renamed + * this operator to _Countof. + */ + +int global_array[12]; + +void test_parsing_failures() { + (void)_Countof; // expected-error {{expected expression}} + (void)_Countof(; // expected-error {{expected expression}} + (void)_Countof(); // expected-error {{expected expression}} + (void)_Countof int; // expected-error {{expected expression}} +} + +void test_semantic_failures() { + (void)_Countof(1); // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}} + int non_array; + (void)_Countof non_array; // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}} + (void)_Countof(int); // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}} +} + +void test_constant_expression_behavior(int n) { + static_assert(_Countof(global_array) == 12); + static_assert(_Countof global_array == 12); + static_assert(_Countof(int[12]) == 12); + + // Use of a VLA makes it not a constant expression, same as with sizeof. + int array[n]; + static_assert(_Countof(array)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(sizeof(array)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(int[n]));// expected-error {{static assertion expression is not an integral constant expression}} + static_assert(sizeof(int[n])); // expected-error {{static assertion expression is not an integral constant expression}} + + // Constant folding works the same way as sizeof, too. + const int m = 12; + int other_array[m]; + static_assert(sizeof(other_array)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(other_array)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(sizeof(int[m])); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(int[m])); // expected-error {{static assertion expression is not an integral constant expression}} + + // Note that this applies to each array dimension. + int another_array[n][7]; + static_assert(_Countof(another_array)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(*another_array) == 7); +} + +void test_with_function_param(int array[12], int (*array_ptr)[12]) { + (void)_Countof(array); // expected-error {{'_Countof' requires an argument of array type; 'int *' invalid}} + static_assert(_Countof(*array_ptr) == 12); +} + +void test_multidimensional_arrays() { + int array[12][7]; + static_assert(_Countof(array) == 12); + static_assert(_Countof(*array) == 7); +} diff --git a/clang/test/C/C2y/n3369_1.c b/clang/test/C/C2y/n3369_1.c new file mode 100644 index 0000000000000..b4e75151a5404 --- /dev/null +++ b/clang/test/C/C2y/n3369_1.c @@ -0,0 +1,25 @@ +/* RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wpre-c2y-compat -verify=compat %s + RUN: %clang_cc1 -fsyntax-only -std=c23 -pedantic -verify %s + RUN: %clang_cc1 -fsyntax-only -std=c89 -pedantic -verify=expected,static-assert %s + RUN: %clang_cc1 -fsyntax-only -pedantic -verify=cpp,static-assert -x c++ %s + */ + +/* This tests the extension behavior for _Countof in language modes before C2y. + * It also tests the behavior of the precompat warning. And it tests the + * behavior in C++ mode where the extension is not supported. + */ +int array[12]; +int x = _Countof(array); /* expected-warning {{'_Countof' is a C2y extension}} + compat-warning {{'_Countof' is incompatible with C standards before C2y}} + cpp-error {{use of undeclared identifier '_Countof'}} + */ +int y = _Countof(int[12]); /* expected-warning {{'_Countof' is a C2y extension}} + compat-warning {{'_Countof' is incompatible with C standards before C2y}} + cpp-error {{expected '(' for function-style cast or type construction}} + */ + +_Static_assert(_Countof(int[12]) == 12, ""); /* expected-warning {{'_Countof' is a C2y extension}} + compat-warning {{'_Countof' is incompatible with C standards before C2y}} + cpp-error {{expected '(' for function-style cast or type construction}} + static-assert-warning {{'_Static_assert' is a C11 extension}} + */ diff --git a/clang/test/C/C2y/n3369_2.c b/clang/test/C/C2y/n3369_2.c new file mode 100644 index 0000000000000..3f324040c500e --- /dev/null +++ b/clang/test/C/C2y/n3369_2.c @@ -0,0 +1,92 @@ +// NOTE: Assertions have been autogenerated by utils/update_cc_test_checks.py UTC_ARGS: --version 5 +// RUN: %clang_cc1 -std=c2y -emit-llvm -o - %s | FileCheck %s + +// This tests the codegen behavior for _Countof. +// CHECK-LABEL: define dso_local i32 @test1( +// CHECK-SAME: ) #[[ATTR0:[0-9]+]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[ARRAY:%.*]] = alloca [12 x i32], align 16 +// CHECK-NEXT: ret i32 12 +// +int test1() { + int array[12]; + return _Countof(array); +} + +// CHECK-LABEL: define dso_local i32 @test2( +// CHECK-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[SAVED_STACK:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[__VLA_EXPR0:%.*]] = alloca i64, align 8 +// CHECK-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64 +// CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.stacksave.p0() +// CHECK-NEXT: store ptr [[TMP2]], ptr [[SAVED_STACK]], align 8 +// CHECK-NEXT: [[VLA:%.*]] = alloca i32, i64 [[TMP1]], align 16 +// CHECK-NEXT: store i64 [[TMP1]], ptr [[__VLA_EXPR0]], align 8 +// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP1]] to i32 +// CHECK-NEXT: [[TMP3:%.*]] = load ptr, ptr [[SAVED_STACK]], align 8 +// CHECK-NEXT: call void @llvm.stackrestore.p0(ptr [[TMP3]]) +// CHECK-NEXT: ret i32 [[CONV]] +// +int test2(int n) { + int array[n]; + return _Countof(array); +} + +// CHECK-LABEL: define dso_local i32 @test3( +// CHECK-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64 +// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP1]] to i32 +// CHECK-NEXT: ret i32 [[CONV]] +// +int test3(int n) { + return _Countof(int[n]); +} + +// CHECK-LABEL: define dso_local i32 @test4( +// CHECK-SAME: ) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: ret i32 100 +// +int test4() { + return _Countof(float[100]); +} + +// CHECK-LABEL: define dso_local i32 @test5( +// CHECK-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[SAVED_STACK:%.*]] = alloca ptr, align 8 +// CHECK-NEXT: [[__VLA_EXPR0:%.*]] = alloca i64, align 8 +// CHECK-NEXT: [[X:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[Y:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64 +// CHECK-NEXT: [[TMP2:%.*]] = call ptr @llvm.stacksave.p0() +// CHECK-NEXT: store ptr [[TMP2]], ptr [[SAVED_STACK]], align 8 +// CHECK-NEXT: [[VLA:%.*]] = alloca [7 x i32], i64 [[TMP1]], align 16 +// CHECK-NEXT: store i64 [[TMP1]], ptr [[__VLA_EXPR0]], align 8 +// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP1]] to i32 +// CHECK-NEXT: store i32 [[CONV]], ptr [[X]], align 4 +// CHECK-NEXT: store i32 7, ptr [[Y]], align 4 +// CHECK-NEXT: [[TMP3:%.*]] = load i32, ptr [[X]], align 4 +// CHECK-NEXT: [[TMP4:%.*]] = load i32, ptr [[Y]], align 4 +// CHECK-NEXT: [[ADD:%.*]] = add nsw i32 [[TMP3]], [[TMP4]] +// CHECK-NEXT: [[TMP5:%.*]] = load ptr, ptr [[SAVED_STACK]], align 8 +// CHECK-NEXT: call void @llvm.stackrestore.p0(ptr [[TMP5]]) +// CHECK-NEXT: ret i32 [[ADD]] +// +int test5(int n) { + int array[n][7]; + int x = _Countof(array); + int y = _Countof(*array); + return x + y; +} diff --git a/clang/test/C/C2y/n3469.c b/clang/test/C/C2y/n3469.c new file mode 100644 index 0000000000000..3d9ac8e6411e9 --- /dev/null +++ b/clang/test/C/C2y/n3469.c @@ -0,0 +1,14 @@ +// RUN: %clang_cc1 -fsyntax-only -std=c2y -verify %s + +/* WG14 N3469: Clang 21 + * The Big Array Size Survey + * + * This renames _Lengthof to _Countof. + */ + +void test() { + (void)_Countof(int[12]); // Ok + (void)_Lengthof(int[12]); // expected-error {{use of undeclared identifier '_Lengthof'}} \ + expected-error {{expected expression}} +} + diff --git a/clang/www/c_status.html b/clang/www/c_status.html index c9e2eda4304f3..77bb0ca165c72 100644 --- a/clang/www/c_status.html +++ b/clang/www/c_status.html @@ -243,11 +243,11 @@ <h2 id="c2y">C2y implementation status</h2> </tr> <tr> <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf">N3369</a></td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 21</td> </tr> <tr> <!-- Graz Feb 2025 --> <td><a href="https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3469.htm">N3469</a></td> - <td class="none" align="center">No</td> + <td class="unreleased" align="center">Clang 21</td> </tr> <tr> <td>Named loops, v3</td> >From 12db58572c15ad4c2a9deeb4896dec03a8c21a60 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 26 Mar 2025 13:51:57 -0400 Subject: [PATCH 02/11] Fix formatting; NFC --- clang/lib/AST/ExprConstant.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 92b1f41bf2fab..31589ec553e56 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -17442,7 +17442,7 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { } case Expr::UnaryExprOrTypeTraitExprClass: { const UnaryExprOrTypeTraitExpr *Exp = cast<UnaryExprOrTypeTraitExpr>(E); - if ((Exp->getKind() == UETT_SizeOf || Exp->getKind() == UETT_CountOf) && + if ((Exp->getKind() == UETT_SizeOf || Exp->getKind() == UETT_CountOf) && Exp->getTypeOfArgument()->isVariableArrayType()) return ICEDiag(IK_NotICE, E->getBeginLoc()); return NoDiag(); >From 301b038845c9f3f02f750210a8467e78106cb314 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 26 Mar 2025 13:53:50 -0400 Subject: [PATCH 03/11] Fix bit-field overflow caught by tests --- clang/include/clang/AST/Stmt.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/include/clang/AST/Stmt.h b/clang/include/clang/AST/Stmt.h index fa4abdb489203..1ab951a005fd9 100644 --- a/clang/include/clang/AST/Stmt.h +++ b/clang/include/clang/AST/Stmt.h @@ -531,7 +531,7 @@ class alignas(void *) Stmt { unsigned : NumExprBits; LLVM_PREFERRED_TYPE(UnaryExprOrTypeTrait) - unsigned Kind : 3; + unsigned Kind : 4; LLVM_PREFERRED_TYPE(bool) unsigned IsType : 1; // true if operand is a type, false if an expression. }; >From 824f20c8c4c2c63fa30f172922d5af87ff088714 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Wed, 26 Mar 2025 15:55:39 -0400 Subject: [PATCH 04/11] Fix handling of VLAs --- clang/include/clang/AST/Type.h | 13 ++++++++ clang/lib/AST/ExprConstant.cpp | 37 ++++++++++++++++++++-- clang/lib/CodeGen/CGExprScalar.cpp | 50 ++++++++++++++++++------------ clang/test/C/C2y/n3369.c | 9 +++++- clang/test/C/C2y/n3369_2.c | 24 ++++++++++++++ 5 files changed, 110 insertions(+), 23 deletions(-) diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 65756203f2073..a809102c069a8 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -3812,6 +3812,19 @@ class IncompleteArrayType : public ArrayType { /// ++x; /// int Z[x]; /// } +/// +/// FIXME: Even constant array types might be represented by a +/// VariableArrayType, as in: +/// +/// void func(int n) { +/// int array[7][n]; +/// } +/// +/// Even though 'array' is a constant-size array of seven elements of type +/// variable-length array of size 'n', it will be represented as a +/// VariableArrayType whose 'SizeExpr' is an IntegerLiteral whose value is 7. +/// Instead, this should be a ConstantArrayType whose element is a +/// VariableArrayType, which models the type better. class VariableArrayType : public ArrayType { friend class ASTContext; // ASTContext creates these. diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 31589ec553e56..80ece3c4ed7e1 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -14937,8 +14937,27 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr( return Success(CAT->getSize(), E); } - // If it wasn't a constant array, it's not a valid constant expression. assert(!Ty->isConstantSizeType()); + + // If it's a variable-length array type, we need to check whether it is a + // multidimensional array. If so, we need to check the size expression of + // the VLA to see if it's a constant size. If so, we can return that value. + const auto *VAT = Info.Ctx.getAsVariableArrayType(Ty); + assert(VAT); + if (VAT->getElementType()->isArrayType()) { + std::optional<APSInt> Res = + VAT->getSizeExpr()->getIntegerConstantExpr(Info.Ctx); + if (Res) { + // The resulting value always has type size_t, so we need to make the + // returned APInt have the correct sign and bit-width. + APInt Val{ + static_cast<unsigned>(Info.Ctx.getTypeSize(Info.Ctx.getSizeType())), + Res->getZExtValue()}; + return Success(Val, E); + } + } + + // Definitely a variable-length type, which is not an ICE. // FIXME: Better diagnostic. Info.FFDiag(E->getBeginLoc()); return false; @@ -17442,9 +17461,23 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) { } case Expr::UnaryExprOrTypeTraitExprClass: { const UnaryExprOrTypeTraitExpr *Exp = cast<UnaryExprOrTypeTraitExpr>(E); - if ((Exp->getKind() == UETT_SizeOf || Exp->getKind() == UETT_CountOf) && + if ((Exp->getKind() == UETT_SizeOf) && Exp->getTypeOfArgument()->isVariableArrayType()) return ICEDiag(IK_NotICE, E->getBeginLoc()); + if (Exp->getKind() == UETT_CountOf) { + QualType ArgTy = Exp->getTypeOfArgument(); + if (ArgTy->isVariableArrayType()) { + // We need to look whether the array is multidimensional. If it is, + // then we want to check the size expression manually to see whether + // it is an ICE or not. + const auto *VAT = Ctx.getAsVariableArrayType(ArgTy); + if (VAT->getElementType()->isArrayType()) + return CheckICE(VAT->getSizeExpr(), Ctx); + + // Otherwise, this is a regular VLA, which is definitely not an ICE. + return ICEDiag(IK_NotICE, E->getBeginLoc()); + } + } return NoDiag(); } case Expr::BinaryOperatorClass: { diff --git a/clang/lib/CodeGen/CGExprScalar.cpp b/clang/lib/CodeGen/CGExprScalar.cpp index e858de7a4f6d2..140a12d384502 100644 --- a/clang/lib/CodeGen/CGExprScalar.cpp +++ b/clang/lib/CodeGen/CGExprScalar.cpp @@ -3480,29 +3480,39 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr( Kind == UETT_SizeOf || Kind == UETT_DataSizeOf || Kind == UETT_CountOf) { if (const VariableArrayType *VAT = CGF.getContext().getAsVariableArrayType(TypeToSize)) { - if (E->isArgumentType()) { - // sizeof(type) - make sure to emit the VLA size. - CGF.EmitVariablyModifiedType(TypeToSize); - } else { - // C99 6.5.3.4p2: If the argument is an expression of type - // VLA, it is evaluated. - CGF.EmitIgnoredExpr(E->getArgumentExpr()); + // For _Countof, we only want to evaluate if the extent is actually + // variable as opposed to a multi-dimensional array whose extent is + // constant but whose element type is variable. + bool EvaluateExtent = true; + if (Kind == UETT_CountOf && VAT->getElementType()->isArrayType()) { + EvaluateExtent = + !VAT->getSizeExpr()->isIntegerConstantExpr(CGF.getContext()); } + if (EvaluateExtent) { + if (E->isArgumentType()) { + // sizeof(type) - make sure to emit the VLA size. + CGF.EmitVariablyModifiedType(TypeToSize); + } else { + // C99 6.5.3.4p2: If the argument is an expression of type + // VLA, it is evaluated. + CGF.EmitIgnoredExpr(E->getArgumentExpr()); + } - auto VlaSize = CGF.getVLASize(VAT); - llvm::Value *size = VlaSize.NumElts; - - // For sizeof and __datasizeof, we need to scale the number of elements - // by the size of the array element type. For _Countof, we just want to - // return the size directly. - if (Kind != UETT_CountOf) { - // Scale the number of non-VLA elements by the non-VLA element size. - CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type); - if (!eltSize.isOne()) - size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size); - } + auto VlaSize = CGF.getVLASize(VAT); + llvm::Value *size = VlaSize.NumElts; + + // For sizeof and __datasizeof, we need to scale the number of elements + // by the size of the array element type. For _Countof, we just want to + // return the size directly. + if (Kind != UETT_CountOf) { + // Scale the number of non-VLA elements by the non-VLA element size. + CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type); + if (!eltSize.isOne()) + size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size); + } - return size; + return size; + } } } else if (E->getKind() == UETT_OpenMPRequiredSimdAlign) { auto Alignment = diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index c199feb7f9d54..80115cae47859 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -47,11 +47,18 @@ void test_constant_expression_behavior(int n) { int another_array[n][7]; static_assert(_Countof(another_array)); // expected-error {{static assertion expression is not an integral constant expression}} static_assert(_Countof(*another_array) == 7); + + // Only the first dimension is needed for constant evaluation; other + // dimensions can be ignored. + int yet_another_array[7][n]; + static_assert(_Countof(yet_another_array) == 7); + static_assert(_Countof(*yet_another_array)); // expected-error {{static assertion expression is not an integral constant expression}} } -void test_with_function_param(int array[12], int (*array_ptr)[12]) { +void test_with_function_param(int array[12], int (*array_ptr)[12], int static_array[static 12]) { (void)_Countof(array); // expected-error {{'_Countof' requires an argument of array type; 'int *' invalid}} static_assert(_Countof(*array_ptr) == 12); + (void)_Countof(static_array); // expected-error {{'_Countof' requires an argument of array type; 'int *' invalid}} } void test_multidimensional_arrays() { diff --git a/clang/test/C/C2y/n3369_2.c b/clang/test/C/C2y/n3369_2.c index 3f324040c500e..867a755eb27f6 100644 --- a/clang/test/C/C2y/n3369_2.c +++ b/clang/test/C/C2y/n3369_2.c @@ -90,3 +90,27 @@ int test5(int n) { int y = _Countof(*array); return x + y; } + +// CHECK-LABEL: define dso_local void @test6( +// CHECK-SAME: i32 noundef [[N:%.*]]) #[[ATTR0]] { +// CHECK-NEXT: [[ENTRY:.*:]] +// CHECK-NEXT: [[N_ADDR:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[X:%.*]] = alloca i32, align 4 +// CHECK-NEXT: [[Y:%.*]] = alloca i32, align 4 +// CHECK-NEXT: store i32 [[N]], ptr [[N_ADDR]], align 4 +// CHECK-NEXT: store i32 7, ptr [[X]], align 4 +// CHECK-NEXT: [[TMP0:%.*]] = load i32, ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[INC:%.*]] = add nsw i32 [[TMP0]], 1 +// CHECK-NEXT: store i32 [[INC]], ptr [[N_ADDR]], align 4 +// CHECK-NEXT: [[TMP1:%.*]] = zext i32 [[TMP0]] to i64 +// CHECK-NEXT: [[CONV:%.*]] = trunc i64 [[TMP1]] to i32 +// CHECK-NEXT: store i32 [[CONV]], ptr [[Y]], align 4 +// CHECK-NEXT: ret void +// +void test6(int n) { + // n should not be evaluated in this case because the operator does not need + // to evaluate it to know the result is 7. + int x = _Countof(int[7][n++]); + // n should be evaluated in this case, however. + int y = _Countof(int[n++][7]); +} >From d9aa111881108af6aad5b2c5c42ba4590f7205b9 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 07:48:02 -0400 Subject: [PATCH 05/11] Add additional test cases --- clang/test/C/C2y/n3369.c | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index 80115cae47859..185ead3f9608c 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -65,4 +65,38 @@ void test_multidimensional_arrays() { int array[12][7]; static_assert(_Countof(array) == 12); static_assert(_Countof(*array) == 7); + + int mdarray[12][7][100][3]; + static_assert(_Countof(mdarray) == 12); + static_assert(_Countof(*mdarray) == 7); + static_assert(_Countof(**mdarray) == 100); + static_assert(_Countof(***mdarray) == 3); +} + +void test_unspecified_array_length() { + static_assert(_Countof(int[])); // expected-error {{invalid application of '_Countof' to an incomplete type 'int[]'}} + + extern int x[][6][3]; + static_assert(_Countof(x)); // expected-error {{invalid application of '_Countof' to an incomplete type 'int[][6][3]'}} + static_assert(_Countof(*x) == 6); + static_assert(_Countof(**x) == 3); +} + +// Test that the return type of _Countof is what you'd expect (size_t). +void test_return_type() { + static_assert(_Generic(typeof(_Countof global_array), typeof(sizeof(0)) : 1, default : 0)); +} + +// Test that _Countof is able to look through typedefs. +void test_typedefs() { + typedef int foo[12]; + foo f; + static_assert(_Countof(foo) == 12); + static_assert(_Countof(f) == 12); + + // Ensure multidimensional arrays also work. + foo x[100]; + static_assert(_Generic(typeof(x), int[100][12] : 1, default : 0)); + static_assert(_Countof(x) == 100); + static_assert(_Countof(*x) == 12); } >From dd70849a6bee88262f6149608584ff1768f73cf4 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 07:49:43 -0400 Subject: [PATCH 06/11] Reword release note slightly --- clang/docs/ReleaseNotes.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index b82e79c092c4e..d84a30254b1f8 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -145,7 +145,7 @@ C2y Feature Support which introduces the ``_Lengthof`` operator, and `WG14 N3469 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3469.htm>`_ which renamed ``_Lengthof`` to ``_Countof``. This feature is implemented as a conforming extension in earlier C language modes, but not in C++ language - modes (``std::extent`` and ``std::rank`` already provide the same + modes (``std::extent`` and ``std::size`` already provide the same functionality but with more granularity). C23 Feature Support >From c71f221b5e76df251985c439f06d5ae66f0c206f Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 07:56:58 -0400 Subject: [PATCH 07/11] Implement support in the new constexpr interpreter as well --- clang/lib/AST/ByteCode/Compiler.cpp | 31 +++++++++++++++++++++++++++++ clang/test/C/C2y/n3369.c | 1 + 2 files changed, 32 insertions(+) diff --git a/clang/lib/AST/ByteCode/Compiler.cpp b/clang/lib/AST/ByteCode/Compiler.cpp index f2e98218f373b..021acbd798646 100644 --- a/clang/lib/AST/ByteCode/Compiler.cpp +++ b/clang/lib/AST/ByteCode/Compiler.cpp @@ -2086,6 +2086,37 @@ bool Compiler<Emitter>::VisitUnaryExprOrTypeTraitExpr( return this->emitConst(Size.getQuantity(), E); } + if (Kind == UETT_CountOf) { + QualType Ty = E->getTypeOfArgument(); + assert(Ty->isArrayType()); + + // We don't need to worry about array element qualifiers, so getting the + // unsafe array type is fine. + if (const auto *CAT = + dyn_cast<ConstantArrayType>(Ty->getAsArrayTypeUnsafe())) { + if (DiscardResult) + return true; + return this->emitConst(CAT->getSize(), E); + } + + assert(!Ty->isConstantSizeType()); + + // If it's a variable-length array type, we need to check whether it is a + // multidimensional array. If so, we need to check the size expression of + // the VLA to see if it's a constant size. If so, we can return that value. + const auto *VAT = ASTCtx.getAsVariableArrayType(Ty); + assert(VAT); + if (VAT->getElementType()->isArrayType()) { + std::optional<APSInt> Res = + VAT->getSizeExpr()->getIntegerConstantExpr(ASTCtx); + if (Res) { + if (DiscardResult) + return true; + return this->emitConst(*Res, E); + } + } + } + if (Kind == UETT_AlignOf || Kind == UETT_PreferredAlignOf) { CharUnits Size; diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index 185ead3f9608c..77b218de965ef 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wall -Wno-comment -verify %s +// RUN: %clang_cc1 -fsyntax-only -std=c2y -pedantic -Wall -Wno-comment -fexperimental-new-constant-interpreter -verify %s /* WG14 N3369: Clang 21 * _Lengthof operator >From 9ae08926a1d9b2aa047ec31306fe0e33074598fe Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 08:18:40 -0400 Subject: [PATCH 08/11] Add another test from review feedback --- clang/test/C/C2y/n3369.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index 77b218de965ef..fac750abf9049 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -54,6 +54,11 @@ void test_constant_expression_behavior(int n) { int yet_another_array[7][n]; static_assert(_Countof(yet_another_array) == 7); static_assert(_Countof(*yet_another_array)); // expected-error {{static assertion expression is not an integral constant expression}} + + int one_more_time[n][n][7]; + static_assert(_Countof(one_more_time)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(*one_more_time)); // expected-error {{static assertion expression is not an integral constant expression}} + static_assert(_Countof(**one_more_time) == 7); } void test_with_function_param(int array[12], int (*array_ptr)[12], int static_array[static 12]) { >From 4a7ca5ef651b34e7b9bd76cc8de9a096cb78de47 Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 08:49:36 -0400 Subject: [PATCH 09/11] Add feature testing support --- clang/docs/LanguageExtensions.rst | 16 ++++++++++++++++ clang/docs/ReleaseNotes.rst | 3 ++- clang/include/clang/Basic/Features.def | 4 ++++ clang/test/C/C2y/n3369.c | 9 +++++++++ clang/test/C/C2y/n3369_1.c | 18 ++++++++++++++++++ 5 files changed, 49 insertions(+), 1 deletion(-) diff --git a/clang/docs/LanguageExtensions.rst b/clang/docs/LanguageExtensions.rst index 8b5707ce2acac..3b8a9cac6587a 100644 --- a/clang/docs/LanguageExtensions.rst +++ b/clang/docs/LanguageExtensions.rst @@ -1594,6 +1594,22 @@ C11 ``_Thread_local`` Use ``__has_feature(c_thread_local)`` or ``__has_extension(c_thread_local)`` to determine if support for ``_Thread_local`` variables is enabled. +C2y +--- + +The features listed below are part of the C2y standard. As a result, all these +features are enabled with the ``-std=c2y`` or ``-std=gnu2y`` option when +compiling C code. + +C2y ``_Countof`` +^^^^^^^^^^^^^^^^ + +Use ``__has_feature(c_countof)`` (in C2y or later mode) or +``__has_extension(c_countof)`` (in C23 or earlier mode) to determine if support +for the ``_Countof`` operator is enabled. This feature is not available in C++ +mode. + + Modules ------- diff --git a/clang/docs/ReleaseNotes.rst b/clang/docs/ReleaseNotes.rst index d84a30254b1f8..35e52b19c3e6f 100644 --- a/clang/docs/ReleaseNotes.rst +++ b/clang/docs/ReleaseNotes.rst @@ -146,7 +146,8 @@ C2y Feature Support which renamed ``_Lengthof`` to ``_Countof``. This feature is implemented as a conforming extension in earlier C language modes, but not in C++ language modes (``std::extent`` and ``std::size`` already provide the same - functionality but with more granularity). + functionality but with more granularity). The feature can be tested via + ``__has_feature(c_countof)`` or ``__has_extension(c_countof)``. C23 Feature Support ^^^^^^^^^^^^^^^^^^^ diff --git a/clang/include/clang/Basic/Features.def b/clang/include/clang/Basic/Features.def index 05ce214935fad..b4409efaa9c04 100644 --- a/clang/include/clang/Basic/Features.def +++ b/clang/include/clang/Basic/Features.def @@ -166,6 +166,8 @@ FEATURE(c_static_assert, LangOpts.C11) FEATURE(c_thread_local, LangOpts.C11 &&PP.getTargetInfo().isTLSSupported()) // C23 features FEATURE(c_fixed_enum, LangOpts.C23) +// C2y features +FEATURE(c_countof, LangOpts.C2y) // C++11 features FEATURE(cxx_access_control_sfinae, LangOpts.CPlusPlus11) FEATURE(cxx_alias_templates, LangOpts.CPlusPlus11) @@ -274,6 +276,8 @@ EXTENSION(c_thread_local, PP.getTargetInfo().isTLSSupported()) // C23 features supported by other languages as extensions EXTENSION(c_attributes, true) EXTENSION(c_fixed_enum, true) +// C2y features supported by other languages as extensions +EXTENSION(c_countof, !LangOpts.C2y && !LangOpts.CPlusPlus) // C++11 features supported by other languages as extensions. EXTENSION(cxx_atomic, LangOpts.CPlusPlus) EXTENSION(cxx_default_function_template_args, LangOpts.CPlusPlus) diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index fac750abf9049..c43880d4ea31a 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -8,6 +8,15 @@ * this operator to _Countof. */ +#if !__has_feature(c_countof) +#error "Expected to have _Countof support" +#endif + +#if !__has_extension(c_countof) +// __has_extension returns true if __has_feature returns true. +#error "Expected to have _Countof support" +#endif + int global_array[12]; void test_parsing_failures() { diff --git a/clang/test/C/C2y/n3369_1.c b/clang/test/C/C2y/n3369_1.c index b4e75151a5404..d33551f4bc081 100644 --- a/clang/test/C/C2y/n3369_1.c +++ b/clang/test/C/C2y/n3369_1.c @@ -8,6 +8,24 @@ * It also tests the behavior of the precompat warning. And it tests the * behavior in C++ mode where the extension is not supported. */ + +#if __STDC_VERSION__ < 202400L && !defined(__cplusplus) +#if __has_feature(c_countof) +#error "Did not expect _Countof to be a feature in older language modes" +#endif + +#if !__has_extension(c_countof) +#error "Expected _Countof to be an extension in older language modes" +#endif +#endif /* __STDC_VERSION__ < 202400L && !defined(__cplusplus) */ + +#ifdef __cplusplus +/* C++ should not have this as a feature or as an extension. */ +#if __has_feature(c_countof) || __has_extension(c_countof) +#error "did not expect there to be _Countof support" +#endif +#endif /* __cplusplus */ + int array[12]; int x = _Countof(array); /* expected-warning {{'_Countof' is a C2y extension}} compat-warning {{'_Countof' is incompatible with C standards before C2y}} >From bd341ca4af5509dbb09ddc3a84504c2c8e19ca0f Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 12:03:00 -0400 Subject: [PATCH 10/11] Add additional test coverage --- clang/test/C/C2y/n3369.c | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index c43880d4ea31a..69bc82209bea9 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -31,6 +31,11 @@ void test_semantic_failures() { int non_array; (void)_Countof non_array; // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}} (void)_Countof(int); // expected-error {{'_Countof' requires an argument of array type; 'int' invalid}} + (void)_Countof(test_semantic_failures); // expected-error {{invalid application of '_Countof' to a function type}} + (void)_Countof(struct S); // expected-error {{invalid application of '_Countof' to an incomplete type 'struct S'}} \ + expected-note {{forward declaration of 'struct S'}} + struct T { int x; }; + (void)_Countof(struct T); // expected-error {{'_Countof' requires an argument of array type; 'struct T' invalid}} } void test_constant_expression_behavior(int n) { @@ -115,3 +120,22 @@ void test_typedefs() { static_assert(_Countof(x) == 100); static_assert(_Countof(*x) == 12); } + +void test_zero_size_arrays() { + int array[0]; // expected-warning {{zero size arrays are an extension}} + static_assert(_Countof(array) == 0); + static_assert(_Countof(int[0]) == 0); // expected-warning {{zero size arrays are an extension}} +} + +void test_struct_members() { + struct S { + int array[10]; + } s; + static_assert(_Countof(s.array) == 10); + + struct T { + int count; + int fam[]; + } t; + static_assert(_Countof(t.fam)); // expected-error {{invalid application of '_Countof' to an incomplete type 'int[]'}} +} >From b50ac64688338b7c21df9434c3cd5b075c3832ca Mon Sep 17 00:00:00 2001 From: Aaron Ballman <aa...@aaronballman.com> Date: Thu, 27 Mar 2025 12:13:28 -0400 Subject: [PATCH 11/11] Add test coverage for compound literals --- clang/test/C/C2y/n3369.c | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/clang/test/C/C2y/n3369.c b/clang/test/C/C2y/n3369.c index 69bc82209bea9..389828b52b6a2 100644 --- a/clang/test/C/C2y/n3369.c +++ b/clang/test/C/C2y/n3369.c @@ -139,3 +139,8 @@ void test_struct_members() { } t; static_assert(_Countof(t.fam)); // expected-error {{invalid application of '_Countof' to an incomplete type 'int[]'}} } + +void test_compound_literals() { + static_assert(_Countof((int[2]){}) == 2); + static_assert(_Countof((int[]){1, 2, 3, 4}) == 4); +} _______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits