Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[C2y] Implement WG14 N3369 and N3469 (_Countof) #133125

Merged
merged 11 commits into from
Mar 27, 2025
17 changes: 17 additions & 0 deletions clang/docs/LanguageExtensions.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-------

Expand Down Expand Up @@ -1653,6 +1669,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
Expand Down
7 changes: 7 additions & 0 deletions clang/docs/ReleaseNotes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ 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::size`` already provide the same
functionality but with more granularity). The feature can be tested via
``__has_feature(c_countof)`` or ``__has_extension(c_countof)``.

C23 Feature Support
^^^^^^^^^^^^^^^^^^^
Expand Down
2 changes: 1 addition & 1 deletion clang/include/clang/AST/Stmt.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
};
Expand Down
13 changes: 13 additions & 0 deletions clang/include/clang/AST/Type.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions clang/include/clang/Basic/DiagnosticParseKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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">;
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Original file line number Diff line number Diff line change
Expand Up @@ -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<
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Basic/Features.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 2 additions & 0 deletions clang/include/clang/Basic/TokenKinds.def
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
31 changes: 31 additions & 0 deletions clang/lib/AST/ByteCode/Compiler.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down
50 changes: 50 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -14926,6 +14926,42 @@ 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);
}

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;
}
}

llvm_unreachable("unknown expr/type trait");
Expand Down Expand Up @@ -17428,6 +17464,20 @@ static ICEDiag CheckICE(const Expr* E, const ASTContext &Ctx) {
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: {
Expand Down
1 change: 1 addition & 0 deletions clang/lib/AST/ItaniumMangle.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
47 changes: 31 additions & 16 deletions clang/lib/CodeGen/CGExprScalar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3477,27 +3477,42 @@ 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()) {
// 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;

// 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 =
Expand Down
21 changes: 17 additions & 4 deletions clang/lib/Parse/ParseExpr.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
});
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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;
}
Expand Down
Loading
Loading