Skip to content

Commit d781ac1

Browse files
authored
[C23] Add __builtin_c23_va_start (#131166)
This builtin is supported by GCC and is a way to improve diagnostic behavior for va_start in C23 mode. C23 no longer requires a second argument to the va_start macro in support of variadic functions with no leading parameters. However, we still want to diagnose passing more than two arguments, or diagnose when passing something other than the last parameter in the variadic function. This also updates the freestanding <stdarg.h> header to use the new builtin, same as how GCC works. Fixes #124031
1 parent 5cc2ae0 commit d781ac1

File tree

13 files changed

+131
-37
lines changed

13 files changed

+131
-37
lines changed

clang/docs/LanguageExtensions.rst

+11
Original file line numberDiff line numberDiff line change
@@ -4283,6 +4283,17 @@ the ellipsis. This function initializes the given ``__builtin_va_list`` object.
42834283
It is undefined behavior to call this function on an already initialized
42844284
``__builtin_va_list`` object.
42854285
4286+
* ``void __builtin_c23_va_start(__builtin_va_list list, ...)``
4287+
4288+
A builtin function for the target-specific ``va_start`` function-like macro,
4289+
available only in C23 and later. The builtin accepts zero or one argument for
4290+
the ellipsis (``...``). If such an argument is provided, it should be the name
4291+
of the parameter preceeding the ellipsis, which is used for compatibility with
4292+
C versions before C23. It is an error to provide two or more variadic arguments.
4293+
This function initializes the given ``__builtin_va_list`` object. It is
4294+
undefined behavior to call this function on an already initialized
4295+
``__builtin_va_list`` object.
4296+
42864297
* ``void __builtin_va_end(__builtin_va_list list)``
42874298
42884299
A builtin function for the target-specific ``va_end`` function-like macro. This

clang/docs/ReleaseNotes.rst

+4
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,10 @@ C2y Feature Support
123123

124124
C23 Feature Support
125125
^^^^^^^^^^^^^^^^^^^
126+
- Added ``__builtin_c23_va_start()`` for compatibility with GCC and to enable
127+
better diagnostic behavior for the ``va_start()`` macro in C23 and later.
128+
This also updates the definition of ``va_start()`` in ``<stdarg.h>`` to use
129+
the new builtin. Fixes #GH124031.
126130

127131
Non-comprehensive list of changes in this release
128132
-------------------------------------------------

clang/include/clang/Basic/Builtins.h

+1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ enum LanguageID : uint16_t {
4444
OCL_DSE = 0x400, // builtin requires OpenCL device side enqueue.
4545
ALL_OCL_LANGUAGES = 0x800, // builtin for OCL languages.
4646
HLSL_LANG = 0x1000, // builtin requires HLSL.
47+
C23_LANG = 0x2000, // builtin requires C23 or later.
4748
ALL_LANGUAGES = C_LANG | CXX_LANG | OBJC_LANG, // builtin for all languages.
4849
ALL_GNU_LANGUAGES = ALL_LANGUAGES | GNU_LANG, // builtin requires GNU mode.
4950
ALL_MS_LANGUAGES = ALL_LANGUAGES | MS_LANG // builtin requires MS mode.

clang/include/clang/Basic/Builtins.td

+6
Original file line numberDiff line numberDiff line change
@@ -833,6 +833,12 @@ def BuiltinVaStart : Builtin {
833833
let Prototype = "void(__builtin_va_list_ref, ...)";
834834
}
835835

836+
def BuiltinC23VaStart : LangBuiltin<"C23_LANG"> {
837+
let Spellings = ["__builtin_c23_va_start"];
838+
let Attributes = [NoThrow, CustomTypeChecking];
839+
let Prototype = "void(__builtin_va_list_ref, ...)";
840+
}
841+
836842
def BuiltinStdargStart : Builtin {
837843
let Spellings = ["__builtin_stdarg_start"];
838844
let Attributes = [NoThrow, CustomTypeChecking];

clang/include/clang/Basic/DiagnosticSemaKinds.td

+3
Original file line numberDiff line numberDiff line change
@@ -10619,6 +10619,9 @@ def warn_second_arg_of_va_start_not_last_non_variadic_param : Warning<
1061910619
def warn_c17_compat_ellipsis_only_parameter : Warning<
1062010620
"'...' as the only parameter of a function is incompatible with C standards "
1062110621
"before C23">, DefaultIgnore, InGroup<CPre23Compat>;
10622+
def warn_c17_compat_va_start_one_arg : Warning<
10623+
"passing only one argument to 'va_start' is incompatible with C standards "
10624+
"before C23">, DefaultIgnore, InGroup<CPre23Compat>;
1062210625
def warn_va_start_type_is_undefined : Warning<
1062310626
"passing %select{an object that undergoes default argument promotion|"
1062410627
"an object of reference type|a parameter declared with the 'register' "

clang/include/clang/Sema/Sema.h

+3-3
Original file line numberDiff line numberDiff line change
@@ -2499,9 +2499,9 @@ class Sema final : public SemaBase {
24992499

25002500
void checkFortifiedBuiltinMemoryFunction(FunctionDecl *FD, CallExpr *TheCall);
25012501

2502-
/// Check the arguments to '__builtin_va_start' or '__builtin_ms_va_start'
2503-
/// for validity. Emit an error and return true on failure; return false
2504-
/// on success.
2502+
/// Check the arguments to '__builtin_va_start', '__builtin_ms_va_start',
2503+
/// or '__builtin_c23_va_start' for validity. Emit an error and return true
2504+
/// on failure; return false on success.
25052505
bool BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall);
25062506
bool BuiltinVAStartARMMicrosoft(CallExpr *Call);
25072507

clang/lib/Basic/Builtins.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ static bool builtinIsSupported(const llvm::StringTable &Strings,
191191
/* consteval Unsupported */
192192
if (!LangOpts.CPlusPlus20 && strchr(AttributesStr.data(), 'G') != nullptr)
193193
return false;
194+
/* C23 unsupported */
195+
if (!LangOpts.C23 && BuiltinInfo.Langs == C23_LANG)
196+
return false;
194197
return true;
195198
}
196199

clang/lib/CodeGen/CGBuiltin.cpp

+1
Original file line numberDiff line numberDiff line change
@@ -3551,6 +3551,7 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
35513551
case Builtin::BI__builtin_stdarg_start:
35523552
case Builtin::BI__builtin_va_start:
35533553
case Builtin::BI__va_start:
3554+
case Builtin::BI__builtin_c23_va_start:
35543555
case Builtin::BI__builtin_va_end:
35553556
EmitVAStartEnd(BuiltinID == Builtin::BI__va_start
35563557
? EmitScalarExpr(E->getArg(0))

clang/lib/Headers/__stdarg_va_arg.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
#ifndef va_arg
1111

1212
#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 202311L
13-
/* C23 does not require the second parameter for va_start. */
14-
#define va_start(ap, ...) __builtin_va_start(ap, 0)
13+
/* C23 uses a special builtin. */
14+
#define va_start(...) __builtin_c23_va_start(__VA_ARGS__)
1515
#else
1616
/* Versions before C23 do require the second parameter. */
1717
#define va_start(ap, param) __builtin_va_start(ap, param)

clang/lib/Sema/SemaChecking.cpp

+37-13
Original file line numberDiff line numberDiff line change
@@ -2161,6 +2161,7 @@ Sema::CheckBuiltinFunctionCall(FunctionDecl *FDecl, unsigned BuiltinID,
21612161
case Builtin::BI__builtin_ms_va_start:
21622162
case Builtin::BI__builtin_stdarg_start:
21632163
case Builtin::BI__builtin_va_start:
2164+
case Builtin::BI__builtin_c23_va_start:
21642165
if (BuiltinVAStart(BuiltinID, TheCall))
21652166
return ExprError();
21662167
break;
@@ -4844,15 +4845,27 @@ static bool checkVAStartIsInVariadicFunction(Sema &S, Expr *Fn,
48444845

48454846
bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
48464847
Expr *Fn = TheCall->getCallee();
4847-
48484848
if (checkVAStartABI(*this, BuiltinID, Fn))
48494849
return true;
48504850

4851-
// In C23 mode, va_start only needs one argument. However, the builtin still
4852-
// requires two arguments (which matches the behavior of the GCC builtin),
4853-
// <stdarg.h> passes `0` as the second argument in C23 mode.
4854-
if (checkArgCount(TheCall, 2))
4855-
return true;
4851+
if (BuiltinID == Builtin::BI__builtin_c23_va_start) {
4852+
// This builtin requires one argument (the va_list), allows two arguments,
4853+
// but diagnoses more than two arguments. e.g.,
4854+
// __builtin_c23_va_start(); // error
4855+
// __builtin_c23_va_start(list); // ok
4856+
// __builtin_c23_va_start(list, param); // ok
4857+
// __builtin_c23_va_start(list, anything, anything); // error
4858+
// This differs from the GCC behavior in that they accept the last case
4859+
// with a warning, but it doesn't seem like a useful behavior to allow.
4860+
if (checkArgCountRange(TheCall, 1, 2))
4861+
return true;
4862+
} else {
4863+
// In C23 mode, va_start only needs one argument. However, the builtin still
4864+
// requires two arguments (which matches the behavior of the GCC builtin),
4865+
// <stdarg.h> passes `0` as the second argument in C23 mode.
4866+
if (checkArgCount(TheCall, 2))
4867+
return true;
4868+
}
48564869

48574870
// Type-check the first argument normally.
48584871
if (checkBuiltinArgument(*this, TheCall, 0))
@@ -4863,23 +4876,34 @@ bool Sema::BuiltinVAStart(unsigned BuiltinID, CallExpr *TheCall) {
48634876
if (checkVAStartIsInVariadicFunction(*this, Fn, &LastParam))
48644877
return true;
48654878

4866-
// Verify that the second argument to the builtin is the last argument of the
4867-
// current function or method. In C23 mode, if the second argument is an
4868-
// integer constant expression with value 0, then we don't bother with this
4869-
// check.
4870-
bool SecondArgIsLastNamedArgument = false;
4879+
// Verify that the second argument to the builtin is the last non-variadic
4880+
// argument of the current function or method. In C23 mode, if the call is
4881+
// not to __builtin_c23_va_start, and the second argument is an integer
4882+
// constant expression with value 0, then we don't bother with this check.
4883+
// For __builtin_c23_va_start, we only perform the check for the second
4884+
// argument being the last argument to the current function if there is a
4885+
// second argument present.
4886+
if (BuiltinID == Builtin::BI__builtin_c23_va_start &&
4887+
TheCall->getNumArgs() < 2) {
4888+
Diag(TheCall->getExprLoc(), diag::warn_c17_compat_va_start_one_arg);
4889+
return false;
4890+
}
4891+
48714892
const Expr *Arg = TheCall->getArg(1)->IgnoreParenCasts();
48724893
if (std::optional<llvm::APSInt> Val =
48734894
TheCall->getArg(1)->getIntegerConstantExpr(Context);
4874-
Val && LangOpts.C23 && *Val == 0)
4895+
Val && LangOpts.C23 && *Val == 0 &&
4896+
BuiltinID != Builtin::BI__builtin_c23_va_start) {
4897+
Diag(TheCall->getExprLoc(), diag::warn_c17_compat_va_start_one_arg);
48754898
return false;
4899+
}
48764900

48774901
// These are valid if SecondArgIsLastNamedArgument is false after the next
48784902
// block.
48794903
QualType Type;
48804904
SourceLocation ParamLoc;
48814905
bool IsCRegister = false;
4882-
4906+
bool SecondArgIsLastNamedArgument = false;
48834907
if (const DeclRefExpr *DR = dyn_cast<DeclRefExpr>(Arg)) {
48844908
if (const ParmVarDecl *PV = dyn_cast<ParmVarDecl>(DR->getDecl())) {
48854909
SecondArgIsLastNamedArgument = PV == LastParam;

clang/test/C/C23/n2975.c

+5-18
Original file line numberDiff line numberDiff line change
@@ -6,28 +6,21 @@
66

77
#include <stdarg.h>
88

9-
#define DERP this is an error
10-
119
void func(...) { // expected-warning {{'...' as the only parameter of a function is incompatible with C standards before C23}}
1210
// Show that va_start doesn't require the second argument in C23 mode.
1311
va_list list;
14-
va_start(list); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
15-
va_end(list);
16-
17-
// Show that va_start doesn't expand or evaluate the second argument.
18-
va_start(list, DERP);
12+
va_start(list); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
1913
va_end(list);
2014

21-
// FIXME: it would be kinder to diagnose this instead of silently accepting it.
22-
va_start(list, 1, 2);
15+
va_start(list, 1, 2); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
2316
va_end(list);
2417

2518
// We didn't change the behavior of __builtin_va_start (and neither did GCC).
2619
__builtin_va_start(list); // expected-error {{too few arguments to function call, expected 2, have 1}}
2720

2821
// Verify that the return type of a call to va_start is 'void'.
29-
_Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), ""); // expected-warning {{passing no argument for the '...' parameter of a variadic macro is incompatible with C standards before C23}} expected-note@* {{macro 'va_start' defined here}}
30-
_Static_assert(__builtin_types_compatible_p(__typeof__(__builtin_va_start(list, 0)), void), "");
22+
_Static_assert(__builtin_types_compatible_p(__typeof__(va_start(list)), void), ""); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
23+
_Static_assert(__builtin_types_compatible_p(__typeof__(__builtin_va_start(list, 0)), void), ""); // expected-warning {{passing only one argument to 'va_start' is incompatible with C standards before C23}}
3124
}
3225

3326
// Show that function pointer types also don't need an argument before the
@@ -37,13 +30,7 @@ typedef void (*fp)(...); // expected-warning {{'...' as the only parameter of a
3730
// Passing something other than the argument before the ... is still not valid.
3831
void diag(int a, int b, ...) {
3932
va_list list;
40-
// FIXME: the call to va_start should also diagnose the same way as the call
41-
// to __builtin_va_start. However, because va_start is not allowed to expand
42-
// or evaluate the second argument, we can't pass it along to
43-
// __builtin_va_start to get that diagnostic. So in C17 and earlier, we will
44-
// diagnose this use through the macro, but in C23 and later we've lost the
45-
// diagnostic entirely. GCC has the same issue currently.
46-
va_start(list, a);
33+
va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
4734
// However, the builtin itself is under no such constraints regarding
4835
// expanding or evaluating the second argument, so it can still diagnose.
4936
__builtin_va_start(list, a); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}

clang/test/CodeGen/varargs.c

+12-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -triple i386-unknown-unknown -emit-llvm -o - %s | FileCheck %s
1+
// RUN: %clang_cc1 -triple i386-unknown-unknown -std=c2y -emit-llvm -o - %s | FileCheck %s
22

33
// PR6433 - Don't crash on va_arg(typedef).
44
typedef double gdouble;
@@ -21,3 +21,14 @@ void vla(int n, ...)
2121
void *p;
2222
p = __builtin_va_arg(ap, typeof (int (*)[++n])); // CHECK: add nsw i32 {{.*}}, 1
2323
}
24+
25+
// Ensure that __builtin_va_start(list, 0) and __builtin_c23_va_start(list)
26+
// have the same codegen.
27+
void noargs(...) {
28+
__builtin_va_list list;
29+
// CHECK: %list = alloca ptr
30+
__builtin_va_start(list, 0);
31+
// CHECK-NEXT: call void @llvm.va_start.p0(ptr %list)
32+
__builtin_c23_va_start(list);
33+
// CHECK-NEXT: call void @llvm.va_start.p0(ptr %list)
34+
}

clang/test/Sema/c23-varargs.c

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple i386-pc-unknown
2+
// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -verify=expected,both %s -triple x86_64-apple-darwin9
3+
// RUN: %clang_cc1 -std=c23 -fsyntax-only -ffreestanding -fms-compatibility -verify=expected,both %s -triple x86_64-pc-win32
4+
// RUN: %clang_cc1 -std=c17 -fsyntax-only -ffreestanding -verify=both,pre-c23 %s
5+
6+
void foo(int x, int y, ...) {
7+
__builtin_va_list list;
8+
__builtin_c23_va_start(); // pre-c23-error {{use of unknown builtin '__builtin_c23_va_start'}} \
9+
expected-error{{too few arguments to function call, expected 1, have 0}}
10+
// Note, the unknown builtin diagnostic is only issued once per function,
11+
// which is why the rest of the lines do not get the same diagonstic.
12+
__builtin_c23_va_start(list); // ok
13+
__builtin_c23_va_start(list, 0); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
14+
__builtin_c23_va_start(list, x); // expected-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
15+
__builtin_c23_va_start(list, y); // ok
16+
__builtin_c23_va_start(list, 0, 1); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
17+
__builtin_c23_va_start(list, y, y); // expected-error {{too many arguments to function call, expected at most 2, have 3}}
18+
}
19+
20+
// Test the same thing as above, only with the macro from stdarg.h. This will
21+
// not have the unknown builtin diagnostics, but will have different
22+
// diagnostics between C23 and earlier modes.
23+
#include <stdarg.h>
24+
void bar(int x, int y, ...) {
25+
// FIXME: the "use of undeclared identifier 'va_start'" diagnostics is an odd
26+
// follow-on diagnostic that should be silenced.
27+
va_list list;
28+
va_start(); // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
29+
pre-c23-error {{use of undeclared identifier 'va_start'}} \
30+
expected-error{{too few arguments to function call, expected 1, have 0}}
31+
va_start(list); // pre-c23-error {{too few arguments provided to function-like macro invocation}} \
32+
pre-c23-error {{use of undeclared identifier 'va_start'}}
33+
va_start(list, 0); // both-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
34+
va_start(list, x); // both-warning {{second argument to 'va_start' is not the last non-variadic parameter}}
35+
va_start(list, y); // ok
36+
va_start(list, 0, 1); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
37+
pre-c23-error {{use of undeclared identifier 'va_start'}} \
38+
expected-error {{too many arguments to function call, expected at most 2, have 3}}
39+
va_start(list, y, y); // pre-c23-error {{too many arguments provided to function-like macro invocation}} \
40+
pre-c23-error {{use of undeclared identifier 'va_start'}} \
41+
expected-error {{too many arguments to function call, expected at most 2, have 3}}
42+
// pre-c23-note@__stdarg_va_arg.h:* 4 {{macro 'va_start' defined here}}
43+
}

0 commit comments

Comments
 (0)