Skip to content

Commit add16a8

Browse files
committed
[Builtins] Implement __builtin_is_constant_evaluated for use in C++2a
Summary: This patch implements `__builtin_is_constant_evaluated` as specifier by [P0595R2](https://wg21.link/p0595r2). It is built on the back of Bill Wendling's work for `__builtin_constant_p()`. More tests to come, but early feedback is appreciated. I plan to implement warnings for common mis-usages like those belowe in a following patch: ``` void foo(int x) { if constexpr (std::is_constant_evaluated())) { // condition is always `true`. Should use plain `if` instead. foo_constexpr(x); } else { foo_runtime(x); } } ``` Reviewers: rsmith, MaskRay, bruno, void Reviewed By: rsmith Subscribers: dexonsmith, zoecarver, fdeazeve, kristina, cfe-commits Differential Revision: https://reviews.llvm.org/D55500 llvm-svn: 359067
1 parent 42a2237 commit add16a8

File tree

7 files changed

+271
-2
lines changed

7 files changed

+271
-2
lines changed

clang/include/clang/Basic/Builtins.def

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -500,6 +500,7 @@ BUILTIN(__builtin_vsprintf, "ic*cC*a", "nFP:1:")
500500
BUILTIN(__builtin_vsnprintf, "ic*zcC*a", "nFP:2:")
501501
BUILTIN(__builtin_thread_pointer, "v*", "nc")
502502
BUILTIN(__builtin_launder, "v*v*", "nt")
503+
LANGBUILTIN(__builtin_is_constant_evaluated, "b", "n", CXX_LANG)
503504

504505
// GCC exception builtins
505506
BUILTIN(__builtin_eh_return, "vzv*", "r") // FIXME: Takes intptr_t, not size_t!

clang/lib/AST/ExprConstant.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8279,6 +8279,9 @@ bool IntExprEvaluator::VisitBuiltinCallExpr(const CallExpr *E,
82798279
return Success(false, E);
82808280
}
82818281

8282+
case Builtin::BI__builtin_is_constant_evaluated:
8283+
return Success(Info.InConstantContext, E);
8284+
82828285
case Builtin::BI__builtin_ctz:
82838286
case Builtin::BI__builtin_ctzl:
82848287
case Builtin::BI__builtin_ctzll:
@@ -11139,6 +11142,7 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage,
1113911142
EvalInfo::EvaluationMode EM = EvalInfo::EM_ConstantExpression;
1114011143
EvalInfo Info(Ctx, Result, EM);
1114111144
Info.InConstantContext = true;
11145+
1114211146
if (!::Evaluate(Result.Val, Info, this))
1114311147
return false;
1114411148

clang/lib/Basic/Builtins.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,12 @@ bool Builtin::Context::builtinIsSupported(const Builtin::Info &BuiltinInfo,
7575
bool OclCUnsupported = !LangOpts.OpenCL &&
7676
(BuiltinInfo.Langs & ALL_OCLC_LANGUAGES);
7777
bool OpenMPUnsupported = !LangOpts.OpenMP && BuiltinInfo.Langs == OMP_LANG;
78+
bool CPlusPlusUnsupported =
79+
!LangOpts.CPlusPlus && BuiltinInfo.Langs == CXX_LANG;
7880
return !BuiltinsUnsupported && !MathBuiltinsUnsupported && !OclCUnsupported &&
7981
!OclC1Unsupported && !OclC2Unsupported && !OpenMPUnsupported &&
80-
!GnuModeUnsupported && !MSModeUnsupported && !ObjCUnsupported;
82+
!GnuModeUnsupported && !MSModeUnsupported && !ObjCUnsupported &&
83+
!CPlusPlusUnsupported;
8184
}
8285

8386
/// initializeBuiltins - Mark the identifiers for all the builtins with their

clang/lib/CodeGen/CGDecl.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1783,7 +1783,8 @@ void CodeGenFunction::EmitAutoVarInit(const AutoVarEmission &emission) {
17831783
}
17841784

17851785
llvm::Constant *constant = nullptr;
1786-
if (emission.IsConstantAggregate || D.isConstexpr()) {
1786+
if (emission.IsConstantAggregate || D.isConstexpr() ||
1787+
D.isUsableInConstantExpressions(getContext())) {
17871788
assert(!capturedByInit && "constant init contains a capturing block?");
17881789
constant = ConstantEmitter(*this).tryEmitAbstractForInitializer(D);
17891790
if (constant && trivialAutoVarInit !=
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
// RUN: %clang_cc1 -triple x86_64-unknown-unknown -emit-llvm %s -std=c++2a -o %t.ll
2+
// RUN: FileCheck -check-prefix=CHECK-FN-CG -input-file=%t.ll %s
3+
// RUN: FileCheck -check-prefix=CHECK-STATIC -input-file=%t.ll %s
4+
// RUN: FileCheck -check-prefix=CHECK-DYN -input-file=%t.ll %s
5+
// RUN: FileCheck -check-prefix=CHECK-ARR -input-file=%t.ll %s
6+
// RUN: FileCheck -check-prefix=CHECK-FOLD -input-file=%t.ll %s
7+
8+
using size_t = decltype(sizeof(int));
9+
10+
#define CONSTINIT __attribute__((require_constant_initialization))
11+
12+
extern "C" [[noreturn]] void BOOM();
13+
extern "C" void OK();
14+
extern "C" size_t RANDU();
15+
16+
namespace std {
17+
inline constexpr bool is_constant_evaluated() noexcept {
18+
return __builtin_is_constant_evaluated();
19+
}
20+
} // namespace std
21+
22+
// CHECK-FN-CG-LABEL: define zeroext i1 @_Z3foov()
23+
// CHECK-FN-CG: ret i1 false
24+
bool foo() {
25+
return __builtin_is_constant_evaluated();
26+
}
27+
28+
// CHECK-FN-CG-LABEL: define linkonce_odr i32 @_Z1fv()
29+
constexpr int f() {
30+
// CHECK-FN-CG: store i32 13, i32* %n, align 4
31+
// CHECK-FN-CG: store i32 17, i32* %m, align 4
32+
// CHECK-FN-CG: %1 = load i32, i32* %m, align 4
33+
// CHECK-FN-CG: %add = add nsw i32 %1, 13
34+
// CHECK-FN-CG: ret i32 %add
35+
const int n = __builtin_is_constant_evaluated() && std::is_constant_evaluated() ? 13 : 17; // n == 13
36+
int m = __builtin_is_constant_evaluated() ? 13 : 17; // m might be 13 or 17 (see below)
37+
char arr[n] = {}; // char[13]
38+
return m + int(sizeof(arr));
39+
}
40+
41+
// CHECK-STATIC-DAG: @p = global i32 26,
42+
CONSTINIT int p = f(); // f().m == 13; initialized to 26
43+
// CHECK-STATIC-DAG: @p2 = global i32 26,
44+
int p2 = f(); // same result without CONSTINIT
45+
46+
// CHECK-DYN-LABEL: define internal void @__cxx_global_var_init()
47+
// CHECK-DYN: %0 = load i32, i32* @p, align 4
48+
// CHECK-DYN-NEXT: %call = call i32 @_Z1fv()
49+
// CHECK-DYN-NEXT: %add = add nsw i32 %0, %call
50+
// CHECK-DYN-NEXT: store i32 %add, i32* @q, align 4
51+
// CHECK-DYN-NEXT: ret void
52+
int q = p + f(); // m == 17 for this call; initialized to 56
53+
54+
int y;
55+
56+
// CHECK-STATIC-DAG: @b = global i32 2,
57+
CONSTINIT int b = __builtin_is_constant_evaluated() ? 2 : y; // static initialization to 2
58+
59+
// CHECK-DYN-LABEL: define internal void @__cxx_global_var_init.1()
60+
// CHECK-DYN: %0 = load i32, i32* @y, align 4
61+
// CHECK-DYN: %1 = load i32, i32* @y, align 4
62+
// CHECK-DYN-NEXT: %add = add
63+
// CHECK-DYN-NEXT: store i32 %add, i32* @c,
64+
int c = y + (__builtin_is_constant_evaluated() ? 2 : y); // dynamic initialization to y+y
65+
66+
// CHECK-DYN-LABEL: define internal void @__cxx_global_var_init.2()
67+
// CHECK-DYN: store i32 1, i32* @_ZL1a, align 4
68+
// CHECK-DYN-NEXT: ret void
69+
const int a = __builtin_is_constant_evaluated() ? y : 1; // dynamic initialization to 1
70+
const int *a_sink = &a;
71+
72+
// CHECK-ARR-LABEL: define void @_Z13test_arr_exprv
73+
void test_arr_expr() {
74+
// CHECK-ARR: %x1 = alloca [101 x i8],
75+
char x1[std::is_constant_evaluated() && __builtin_is_constant_evaluated() ? 101 : 1];
76+
77+
// CHECK-ARR: %x2 = alloca [42 x i8],
78+
char x2[std::is_constant_evaluated() && __builtin_is_constant_evaluated() ? 42 : RANDU()];
79+
80+
// CHECK-ARR: call i8* @llvm.stacksave()
81+
// CHECK-ARR: %vla = alloca i8, i64 13,
82+
char x3[std::is_constant_evaluated() || __builtin_is_constant_evaluated() ? RANDU() : 13];
83+
}
84+
85+
// CHECK-ARR-LABEL: define void @_Z17test_new_arr_exprv
86+
void test_new_arr_expr() {
87+
// CHECK-ARR: call i8* @_Znam(i64 17)
88+
new char[std::is_constant_evaluated() || __builtin_is_constant_evaluated() ? 1 : 17];
89+
}
90+
91+
// CHECK-FOLD-LABEL: @_Z31test_constant_initialized_locali(
92+
bool test_constant_initialized_local(int k) {
93+
// CHECK-FOLD: store i8 1, i8* %n,
94+
// CHECK-FOLD: store volatile i8* %n, i8** %p,
95+
const bool n = __builtin_is_constant_evaluated() && std::is_constant_evaluated();
96+
const bool *volatile p = &n;
97+
return *p;
98+
}
99+
100+
// CHECK-FOLD-LABEL: define void @_Z21test_ir_constant_foldv()
101+
void test_ir_constant_fold() {
102+
// CHECK-FOLD-NEXT: entry:
103+
// CHECK-FOLD-NEXT: call void @OK()
104+
// CHECK-FOLD-NEXT: call void @OK()
105+
// CHECK-FOLD-NEXT: ret void
106+
if (std::is_constant_evaluated()) {
107+
BOOM();
108+
} else {
109+
OK();
110+
}
111+
std::is_constant_evaluated() ? BOOM() : OK();
112+
}
113+
114+
// CHECK-STATIC-DAG: @ir = constant i32* @i_constant,
115+
int i_constant;
116+
int i_not_constant;
117+
int &ir = __builtin_is_constant_evaluated() ? i_constant : i_not_constant;
118+
119+
// CHECK-FOLD-LABEL: @_Z35test_ref_initialization_local_scopev()
120+
void test_ref_initialization_local_scope() {
121+
const int i_constant = 42;
122+
const int i_non_constant = 101;
123+
// CHECK-FOLD: store i32* %i_non_constant, i32** %r,
124+
const int &r = __builtin_is_constant_evaluated() ? i_constant : i_non_constant;
125+
}
126+
127+
// CHECK-FOLD-LABEL: @_Z22test_ref_to_static_varv()
128+
void test_ref_to_static_var() {
129+
static int i_constant = 42;
130+
static int i_non_constant = 101;
131+
// CHECK-FOLD: store i32* @_ZZ22test_ref_to_static_varvE10i_constant, i32** %r,
132+
int &r = __builtin_is_constant_evaluated() ? i_constant : i_non_constant;
133+
}

clang/test/Sema/builtins.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -314,3 +314,9 @@ void test23() {
314314
memcpy(buf, src, 11); // expected-warning{{'memcpy' will always overflow; destination buffer has size 10, but size argument is 11}}
315315
my_memcpy(buf, src, 11); // expected-warning{{'memcpy' will always overflow; destination buffer has size 10, but size argument is 11}}
316316
}
317+
318+
// Test that __builtin_is_constant_evaluated() is not allowed in C
319+
int test_cxx_builtin() {
320+
// expected-error@+1 {{use of unknown builtin '__builtin_is_constant_evaluated'}}
321+
return __builtin_is_constant_evaluated();
322+
}
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
// RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu
2+
3+
using size_t = decltype(sizeof(int));
4+
5+
namespace std {
6+
inline constexpr bool is_constant_evaluated() noexcept {
7+
return __builtin_is_constant_evaluated();
8+
}
9+
} // namespace std
10+
11+
extern int dummy; // expected-note 1+ {{declared here}}
12+
13+
static_assert(__builtin_is_constant_evaluated());
14+
static_assert(noexcept(__builtin_is_constant_evaluated()));
15+
16+
constexpr bool b = __builtin_is_constant_evaluated();
17+
static_assert(b);
18+
19+
const int n = __builtin_is_constant_evaluated() ? 4 : dummy;
20+
static_assert(n == 4);
21+
constexpr int cn = __builtin_is_constant_evaluated() ? 11 : dummy;
22+
static_assert(cn == 11);
23+
// expected-error@+1 {{'bn' must be initialized by a constant expression}}
24+
constexpr int bn = __builtin_is_constant_evaluated() ? dummy : 42; // expected-note {{non-const variable 'dummy' is not allowed}}
25+
26+
const int n2 = __builtin_is_constant_evaluated() ? dummy : 42; // expected-note {{declared here}}
27+
static_assert(n2 == 42); // expected-error {{static_assert expression is not an integral constant}}
28+
// expected-note@-1 {{initializer of 'n2' is not a constant expression}}
29+
30+
template <bool V, bool Default = std::is_constant_evaluated()>
31+
struct Templ { static_assert(V); static_assert(Default); };
32+
Templ<__builtin_is_constant_evaluated()> x; // type X<true>
33+
34+
template <class T>
35+
void test_if_constexpr() {
36+
if constexpr (__builtin_is_constant_evaluated()) {
37+
static_assert(__is_same(T, int));
38+
} else {
39+
using Test = typename T::DOES_NOT_EXIST;
40+
}
41+
}
42+
template void test_if_constexpr<int>();
43+
44+
void test_array_decl() {
45+
char x[__builtin_is_constant_evaluated() + std::is_constant_evaluated()];
46+
static_assert(sizeof(x) == 2, "");
47+
}
48+
49+
void test_case_stmt(int x) {
50+
switch (x) {
51+
case 0: // OK
52+
case __builtin_is_constant_evaluated(): // expected-note {{previous case}}
53+
case std::is_constant_evaluated() + __builtin_is_constant_evaluated(): // expected-note {{previous case}}
54+
case 1: // expected-error {{duplicate case value '1'}}
55+
case 2: // expected-error {{duplicate case value '2'}}
56+
break;
57+
}
58+
}
59+
60+
constexpr size_t good_array_size() {
61+
return std::is_constant_evaluated() ? 42 : static_cast<size_t>(-1);
62+
}
63+
64+
constexpr size_t bad_array_size() {
65+
return std::is_constant_evaluated() ? static_cast<size_t>(-1) : 13;
66+
}
67+
68+
template <class T>
69+
constexpr T require_constexpr(T v) {
70+
if (!std::is_constant_evaluated())
71+
throw "BOOM";
72+
return v;
73+
}
74+
75+
void test_new_expr() {
76+
constexpr size_t TooLarge = -1;
77+
auto *x = new int[std::is_constant_evaluated() ? 1 : TooLarge]; // expected-error {{array is too large}}
78+
auto *x2 = new int[std::is_constant_evaluated() ? TooLarge : 1]; // OK
79+
auto *y = new int[1][std::is_constant_evaluated() ? TooLarge : 1]{}; // expected-error {{array is too large}}
80+
auto *y2 = new int[1][require_constexpr(42)];
81+
}
82+
83+
void test_alignas_operand() {
84+
alignas(std::is_constant_evaluated() ? 8 : 2) char dummy;
85+
static_assert(__alignof(dummy) == 8);
86+
}
87+
88+
void test_static_assert_operand() {
89+
static_assert(std::is_constant_evaluated(), "");
90+
}
91+
92+
void test_enumerator() {
93+
enum MyEnum {
94+
ZERO = 0,
95+
ONE = std::is_constant_evaluated()
96+
};
97+
static_assert(ONE == 1, "");
98+
}
99+
100+
struct TestBitfieldWidth {
101+
unsigned Bits : std::is_constant_evaluated();
102+
};
103+
104+
void test_operand_of_noexcept_fn() noexcept(std::is_constant_evaluated());
105+
static_assert(noexcept(test_operand_of_noexcept_fn()), "");
106+
107+
108+
namespace test_ref_initialization {
109+
int x;
110+
int y;
111+
int &r = __builtin_is_constant_evaluated() ? x : y;
112+
static_assert(&r == &x);
113+
114+
} // namespace test_ref_initialization
115+
116+
#if defined(__cpp_conditional_explicit)
117+
struct TestConditionalExplicit {
118+
explicit(__builtin_is_constant_evaluated()) TestConditionalExplicit(int) {}
119+
};
120+
TestConditionalExplicit e = 42;
121+
#endif

0 commit comments

Comments
 (0)