[clang] [C2y] Implement WG14 N3369 and N3469 (_Countof) (PR #133125)
Aaron Ballman via cfe-commits
cfe-commits at lists.llvm.org
Thu Mar 27 09:14:03 PDT 2025
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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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 <aaron at 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);
+}
More information about the cfe-commits
mailing list