Skip to content

Commit 00c43ae

Browse files
authored
[C2y] Implement WG14 N3369 and N3469 (_Countof) (#133125)
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. This is being exposed as an extension in earlier C language modes, but not in C++. C++ already has `std::extent` and `std::size` to cover these needs, so the operator doesn't seem to get the user enough benefit to warrant carrying this as an extension. Fixes #102836
1 parent 08bb0b8 commit 00c43ae

19 files changed

+534
-30
lines changed

clang/docs/LanguageExtensions.rst

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,22 @@ C11 ``_Thread_local``
15941594
Use ``__has_feature(c_thread_local)`` or ``__has_extension(c_thread_local)``
15951595
to determine if support for ``_Thread_local`` variables is enabled.
15961596

1597+
C2y
1598+
---
1599+
1600+
The features listed below are part of the C2y standard. As a result, all these
1601+
features are enabled with the ``-std=c2y`` or ``-std=gnu2y`` option when
1602+
compiling C code.
1603+
1604+
C2y ``_Countof``
1605+
^^^^^^^^^^^^^^^^
1606+
1607+
Use ``__has_feature(c_countof)`` (in C2y or later mode) or
1608+
``__has_extension(c_countof)`` (in C23 or earlier mode) to determine if support
1609+
for the ``_Countof`` operator is enabled. This feature is not available in C++
1610+
mode.
1611+
1612+
15971613
Modules
15981614
-------
15991615

@@ -1653,6 +1669,7 @@ Array & element qualification (N2607) C
16531669
Attributes (N2335) C23 C89
16541670
``#embed`` (N3017) C23 C89, C++
16551671
Octal literals prefixed with ``0o`` or ``0O`` C2y C89, C++
1672+
``_Countof`` (N3369, N3469) C2y C89
16561673
============================================= ================================ ============= =============
16571674

16581675
Builtin type aliases

clang/docs/ReleaseNotes.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,13 @@ C2y Feature Support
141141
paper also introduced octal and hexadecimal delimited escape sequences (e.g.,
142142
``"\x{12}\o{12}"``) which are also supported as an extension in older C
143143
language modes.
144+
- Implemented `WG14 N3369 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3369.pdf>`_
145+
which introduces the ``_Lengthof`` operator, and `WG14 N3469 <https://www.open-std.org/jtc1/sc22/wg14/www/docs/n3469.htm>`_
146+
which renamed ``_Lengthof`` to ``_Countof``. This feature is implemented as
147+
a conforming extension in earlier C language modes, but not in C++ language
148+
modes (``std::extent`` and ``std::size`` already provide the same
149+
functionality but with more granularity). The feature can be tested via
150+
``__has_feature(c_countof)`` or ``__has_extension(c_countof)``.
144151

145152
C23 Feature Support
146153
^^^^^^^^^^^^^^^^^^^

clang/include/clang/AST/Stmt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -531,7 +531,7 @@ class alignas(void *) Stmt {
531531
unsigned : NumExprBits;
532532

533533
LLVM_PREFERRED_TYPE(UnaryExprOrTypeTrait)
534-
unsigned Kind : 3;
534+
unsigned Kind : 4;
535535
LLVM_PREFERRED_TYPE(bool)
536536
unsigned IsType : 1; // true if operand is a type, false if an expression.
537537
};

clang/include/clang/AST/Type.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3812,6 +3812,19 @@ class IncompleteArrayType : public ArrayType {
38123812
/// ++x;
38133813
/// int Z[x];
38143814
/// }
3815+
///
3816+
/// FIXME: Even constant array types might be represented by a
3817+
/// VariableArrayType, as in:
3818+
///
3819+
/// void func(int n) {
3820+
/// int array[7][n];
3821+
/// }
3822+
///
3823+
/// Even though 'array' is a constant-size array of seven elements of type
3824+
/// variable-length array of size 'n', it will be represented as a
3825+
/// VariableArrayType whose 'SizeExpr' is an IntegerLiteral whose value is 7.
3826+
/// Instead, this should be a ConstantArrayType whose element is a
3827+
/// VariableArrayType, which models the type better.
38153828
class VariableArrayType : public ArrayType {
38163829
friend class ASTContext; // ASTContext creates these.
38173830

clang/include/clang/Basic/DiagnosticParseKinds.td

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,12 +171,17 @@ def ext_c99_feature : Extension<
171171
"'%0' is a C99 extension">, InGroup<C99>;
172172
def ext_c11_feature : Extension<
173173
"'%0' is a C11 extension">, InGroup<C11>;
174+
def ext_c2y_feature : Extension<
175+
"'%0' is a C2y extension">, InGroup<C2y>;
174176
def warn_c11_compat_keyword : Warning<
175177
"'%0' is incompatible with C standards before C11">,
176178
InGroup<CPre11Compat>, DefaultIgnore;
177179
def warn_c23_compat_keyword : Warning<
178180
"'%0' is incompatible with C standards before C23">,
179181
InGroup<CPre23Compat>, DefaultIgnore;
182+
def warn_c2y_compat_keyword : Warning<
183+
"'%0' is incompatible with C standards before C2y">,
184+
InGroup<CPre2yCompat>, DefaultIgnore;
180185

181186
def err_c11_noreturn_misplaced : Error<
182187
"'_Noreturn' keyword must precede function declarator">;

clang/include/clang/Basic/DiagnosticSemaKinds.td

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7022,6 +7022,8 @@ def err_sizeof_alignof_typeof_bitfield : Error<
70227022
"bit-field">;
70237023
def err_alignof_member_of_incomplete_type : Error<
70247024
"invalid application of 'alignof' to a field of a class still being defined">;
7025+
def err_countof_arg_not_array_type : Error<
7026+
"'_Countof' requires an argument of array type; %0 invalid">;
70257027
def err_vecstep_non_scalar_vector_type : Error<
70267028
"'vec_step' requires built-in scalar or vector type, %0 invalid">;
70277029
def err_offsetof_incomplete_type : Error<

clang/include/clang/Basic/Features.def

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -166,6 +166,8 @@ FEATURE(c_static_assert, LangOpts.C11)
166166
FEATURE(c_thread_local, LangOpts.C11 &&PP.getTargetInfo().isTLSSupported())
167167
// C23 features
168168
FEATURE(c_fixed_enum, LangOpts.C23)
169+
// C2y features
170+
FEATURE(c_countof, LangOpts.C2y)
169171
// C++11 features
170172
FEATURE(cxx_access_control_sfinae, LangOpts.CPlusPlus11)
171173
FEATURE(cxx_alias_templates, LangOpts.CPlusPlus11)
@@ -274,6 +276,8 @@ EXTENSION(c_thread_local, PP.getTargetInfo().isTLSSupported())
274276
// C23 features supported by other languages as extensions
275277
EXTENSION(c_attributes, true)
276278
EXTENSION(c_fixed_enum, true)
279+
// C2y features supported by other languages as extensions
280+
EXTENSION(c_countof, !LangOpts.C2y && !LangOpts.CPlusPlus)
277281
// C++11 features supported by other languages as extensions.
278282
EXTENSION(cxx_atomic, LangOpts.CPlusPlus)
279283
EXTENSION(cxx_default_function_template_args, LangOpts.CPlusPlus)

clang/include/clang/Basic/TokenKinds.def

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -349,6 +349,8 @@ KEYWORD(__func__ , KEYALL)
349349
KEYWORD(__objc_yes , KEYALL)
350350
KEYWORD(__objc_no , KEYALL)
351351

352+
// C2y
353+
UNARY_EXPR_OR_TYPE_TRAIT(_Countof, CountOf, KEYNOCXX)
352354

353355
// C++ 2.11p1: Keywords.
354356
KEYWORD(asm , KEYCXX|KEYGNU)

clang/lib/AST/ByteCode/Compiler.cpp

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2086,6 +2086,37 @@ bool Compiler<Emitter>::VisitUnaryExprOrTypeTraitExpr(
20862086
return this->emitConst(Size.getQuantity(), E);
20872087
}
20882088

2089+
if (Kind == UETT_CountOf) {
2090+
QualType Ty = E->getTypeOfArgument();
2091+
assert(Ty->isArrayType());
2092+
2093+
// We don't need to worry about array element qualifiers, so getting the
2094+
// unsafe array type is fine.
2095+
if (const auto *CAT =
2096+
dyn_cast<ConstantArrayType>(Ty->getAsArrayTypeUnsafe())) {
2097+
if (DiscardResult)
2098+
return true;
2099+
return this->emitConst(CAT->getSize(), E);
2100+
}
2101+
2102+
assert(!Ty->isConstantSizeType());
2103+
2104+
// If it's a variable-length array type, we need to check whether it is a
2105+
// multidimensional array. If so, we need to check the size expression of
2106+
// the VLA to see if it's a constant size. If so, we can return that value.
2107+
const auto *VAT = ASTCtx.getAsVariableArrayType(Ty);
2108+
assert(VAT);
2109+
if (VAT->getElementType()->isArrayType()) {
2110+
std::optional<APSInt> Res =
2111+
VAT->getSizeExpr()->getIntegerConstantExpr(ASTCtx);
2112+
if (Res) {
2113+
if (DiscardResult)
2114+
return true;
2115+
return this->emitConst(*Res, E);
2116+
}
2117+
}
2118+
}
2119+
20892120
if (Kind == UETT_AlignOf || Kind == UETT_PreferredAlignOf) {
20902121
CharUnits Size;
20912122

clang/lib/AST/ExprConstant.cpp

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14926,6 +14926,42 @@ bool IntExprEvaluator::VisitUnaryExprOrTypeTraitExpr(
1492614926

1492714927
return false;
1492814928
}
14929+
case UETT_CountOf: {
14930+
QualType Ty = E->getTypeOfArgument();
14931+
assert(Ty->isArrayType());
14932+
14933+
// We don't need to worry about array element qualifiers, so getting the
14934+
// unsafe array type is fine.
14935+
if (const auto *CAT =
14936+
dyn_cast<ConstantArrayType>(Ty->getAsArrayTypeUnsafe())) {
14937+
return Success(CAT->getSize(), E);
14938+
}
14939+
14940+
assert(!Ty->isConstantSizeType());
14941+
14942+
// If it's a variable-length array type, we need to check whether it is a
14943+
// multidimensional array. If so, we need to check the size expression of
14944+
// the VLA to see if it's a constant size. If so, we can return that value.
14945+
const auto *VAT = Info.Ctx.getAsVariableArrayType(Ty);
14946+
assert(VAT);
14947+
if (VAT->getElementType()->isArrayType()) {
14948+
std::optional<APSInt> Res =
14949+
VAT->getSizeExpr()->getIntegerConstantExpr(Info.Ctx);
14950+
if (Res) {
14951+
// The resulting value always has type size_t, so we need to make the
14952+
// returned APInt have the correct sign and bit-width.
14953+
APInt Val{
14954+
static_cast<unsigned>(Info.Ctx.getTypeSize(Info.Ctx.getSizeType())),
14955+
Res->getZExtValue()};
14956+
return Success(Val, E);
14957+
}
14958+
}
14959+
14960+
// Definitely a variable-length type, which is not an ICE.
14961+
// FIXME: Better diagnostic.
14962+
Info.FFDiag(E->getBeginLoc());
14963+
return false;
14964+
}
1492914965
}
1493014966

1493114967
llvm_unreachable("unknown expr/type trait");
@@ -17428,6 +17464,20 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
1742817464
if ((Exp->getKind() == UETT_SizeOf) &&
1742917465
Exp->getTypeOfArgument()->isVariableArrayType())
1743017466
return ICEDiag(IK_NotICE, E->getBeginLoc());
17467+
if (Exp->getKind() == UETT_CountOf) {
17468+
QualType ArgTy = Exp->getTypeOfArgument();
17469+
if (ArgTy->isVariableArrayType()) {
17470+
// We need to look whether the array is multidimensional. If it is,
17471+
// then we want to check the size expression manually to see whether
17472+
// it is an ICE or not.
17473+
const auto *VAT = Ctx.getAsVariableArrayType(ArgTy);
17474+
if (VAT->getElementType()->isArrayType())
17475+
return CheckICE(VAT->getSizeExpr(), Ctx);
17476+
17477+
// Otherwise, this is a regular VLA, which is definitely not an ICE.
17478+
return ICEDiag(IK_NotICE, E->getBeginLoc());
17479+
}
17480+
}
1743117481
return NoDiag();
1743217482
}
1743317483
case Expr::BinaryOperatorClass: {

clang/lib/AST/ItaniumMangle.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5367,6 +5367,7 @@ void CXXNameMangler::mangleExpression(const Expr *E, unsigned Arity,
53675367
MangleAlignofSizeofArg();
53685368
break;
53695369

5370+
case UETT_CountOf:
53705371
case UETT_VectorElements:
53715372
case UETT_OpenMPRequiredSimdAlign:
53725373
case UETT_VecStep:

clang/lib/CodeGen/CGExprScalar.cpp

Lines changed: 31 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3477,27 +3477,42 @@ ScalarExprEmitter::VisitUnaryExprOrTypeTraitExpr(
34773477
const UnaryExprOrTypeTraitExpr *E) {
34783478
QualType TypeToSize = E->getTypeOfArgument();
34793479
if (auto Kind = E->getKind();
3480-
Kind == UETT_SizeOf || Kind == UETT_DataSizeOf) {
3480+
Kind == UETT_SizeOf || Kind == UETT_DataSizeOf || Kind == UETT_CountOf) {
34813481
if (const VariableArrayType *VAT =
34823482
CGF.getContext().getAsVariableArrayType(TypeToSize)) {
3483-
if (E->isArgumentType()) {
3484-
// sizeof(type) - make sure to emit the VLA size.
3485-
CGF.EmitVariablyModifiedType(TypeToSize);
3486-
} else {
3487-
// C99 6.5.3.4p2: If the argument is an expression of type
3488-
// VLA, it is evaluated.
3489-
CGF.EmitIgnoredExpr(E->getArgumentExpr());
3483+
// For _Countof, we only want to evaluate if the extent is actually
3484+
// variable as opposed to a multi-dimensional array whose extent is
3485+
// constant but whose element type is variable.
3486+
bool EvaluateExtent = true;
3487+
if (Kind == UETT_CountOf && VAT->getElementType()->isArrayType()) {
3488+
EvaluateExtent =
3489+
!VAT->getSizeExpr()->isIntegerConstantExpr(CGF.getContext());
34903490
}
3491+
if (EvaluateExtent) {
3492+
if (E->isArgumentType()) {
3493+
// sizeof(type) - make sure to emit the VLA size.
3494+
CGF.EmitVariablyModifiedType(TypeToSize);
3495+
} else {
3496+
// C99 6.5.3.4p2: If the argument is an expression of type
3497+
// VLA, it is evaluated.
3498+
CGF.EmitIgnoredExpr(E->getArgumentExpr());
3499+
}
34913500

3492-
auto VlaSize = CGF.getVLASize(VAT);
3493-
llvm::Value *size = VlaSize.NumElts;
3494-
3495-
// Scale the number of non-VLA elements by the non-VLA element size.
3496-
CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type);
3497-
if (!eltSize.isOne())
3498-
size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size);
3501+
auto VlaSize = CGF.getVLASize(VAT);
3502+
llvm::Value *size = VlaSize.NumElts;
3503+
3504+
// For sizeof and __datasizeof, we need to scale the number of elements
3505+
// by the size of the array element type. For _Countof, we just want to
3506+
// return the size directly.
3507+
if (Kind != UETT_CountOf) {
3508+
// Scale the number of non-VLA elements by the non-VLA element size.
3509+
CharUnits eltSize = CGF.getContext().getTypeSizeInChars(VlaSize.Type);
3510+
if (!eltSize.isOne())
3511+
size = CGF.Builder.CreateNUWMul(CGF.CGM.getSize(eltSize), size);
3512+
}
34993513

3500-
return size;
3514+
return size;
3515+
}
35013516
}
35023517
} else if (E->getKind() == UETT_OpenMPRequiredSimdAlign) {
35033518
auto Alignment =

clang/lib/Parse/ParseExpr.cpp

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,8 @@ ExprResult Parser::ParseBuiltinPtrauthTypeDiscriminator() {
904904
/// [GNU] '__alignof' '(' type-name ')'
905905
/// [C11] '_Alignof' '(' type-name ')'
906906
/// [C++11] 'alignof' '(' type-id ')'
907+
/// [C2y] '_Countof' unary-expression
908+
/// [C2y] '_Countof' '(' type-name ')'
907909
/// [GNU] '&&' identifier
908910
/// [C++11] 'noexcept' '(' expression ')' [C++11 5.3.7]
909911
/// [C++] new-expression
@@ -1544,6 +1546,7 @@ ExprResult Parser::ParseCastExpression(CastParseKind ParseKind,
15441546
// unary-expression: '__builtin_omp_required_simd_align' '(' type-name ')'
15451547
case tok::kw___builtin_omp_required_simd_align:
15461548
case tok::kw___builtin_vectorelements:
1549+
case tok::kw__Countof:
15471550
if (NotPrimaryExpression)
15481551
*NotPrimaryExpression = true;
15491552
AllowSuffix = false;
@@ -2463,7 +2466,7 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
24632466
tok::kw___datasizeof, tok::kw___alignof, tok::kw_alignof,
24642467
tok::kw__Alignof, tok::kw_vec_step,
24652468
tok::kw___builtin_omp_required_simd_align,
2466-
tok::kw___builtin_vectorelements) &&
2469+
tok::kw___builtin_vectorelements, tok::kw__Countof) &&
24672470
"Not a typeof/sizeof/alignof/vec_step expression!");
24682471

24692472
ExprResult Operand;
@@ -2510,9 +2513,9 @@ Parser::ParseExprAfterUnaryExprOrTypeTrait(const Token &OpTok,
25102513
// is not going to help when the nesting is too deep. In this corner case
25112514
// we continue to parse with sufficient stack space to avoid crashing.
25122515
if (OpTok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
2513-
tok::kw_alignof, tok::kw__Alignof) &&
2516+
tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof) &&
25142517
Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
2515-
tok::kw_alignof, tok::kw__Alignof))
2518+
tok::kw_alignof, tok::kw__Alignof, tok::kw__Countof))
25162519
Actions.runWithSufficientStackSpace(Tok.getLocation(), [&] {
25172520
Operand = ParseCastExpression(UnaryExprOnly);
25182521
});
@@ -2594,12 +2597,14 @@ ExprResult Parser::ParseSYCLUniqueStableNameExpression() {
25942597
/// [GNU] '__alignof' '(' type-name ')'
25952598
/// [C11] '_Alignof' '(' type-name ')'
25962599
/// [C++11] 'alignof' '(' type-id ')'
2600+
/// [C2y] '_Countof' unary-expression
2601+
/// [C2y] '_Countof' '(' type-name ')'
25972602
/// \endverbatim
25982603
ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
25992604
assert(Tok.isOneOf(tok::kw_sizeof, tok::kw___datasizeof, tok::kw___alignof,
26002605
tok::kw_alignof, tok::kw__Alignof, tok::kw_vec_step,
26012606
tok::kw___builtin_omp_required_simd_align,
2602-
tok::kw___builtin_vectorelements) &&
2607+
tok::kw___builtin_vectorelements, tok::kw__Countof) &&
26032608
"Not a sizeof/alignof/vec_step expression!");
26042609
Token OpTok = Tok;
26052610
ConsumeToken();
@@ -2656,6 +2661,8 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
26562661
Diag(OpTok, diag::warn_cxx98_compat_alignof);
26572662
else if (getLangOpts().C23 && OpTok.is(tok::kw_alignof))
26582663
Diag(OpTok, diag::warn_c23_compat_keyword) << OpTok.getName();
2664+
else if (getLangOpts().C2y && OpTok.is(tok::kw__Countof))
2665+
Diag(OpTok, diag::warn_c2y_compat_keyword) << OpTok.getName();
26592666

26602667
EnterExpressionEvaluationContext Unevaluated(
26612668
Actions, Sema::ExpressionEvaluationContext::Unevaluated,
@@ -2690,6 +2697,12 @@ ExprResult Parser::ParseUnaryExprOrTypeTraitExpression() {
26902697
case tok::kw___builtin_vectorelements:
26912698
ExprKind = UETT_VectorElements;
26922699
break;
2700+
case tok::kw__Countof:
2701+
ExprKind = UETT_CountOf;
2702+
assert(!getLangOpts().CPlusPlus && "_Countof in C++ mode?");
2703+
if (!getLangOpts().C2y)
2704+
Diag(OpTok, diag::ext_c2y_feature) << OpTok.getName();
2705+
break;
26932706
default:
26942707
break;
26952708
}

0 commit comments

Comments
 (0)