diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index fe906fc8c6d69c..0a9474c3ee86d6 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -1273,12 +1273,15 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { EvaluatedStmt *getEvaluatedStmt() const; /// Attempt to evaluate the value of the initializer attached to this - /// declaration, and produce notes explaining why it cannot be evaluated or is - /// not a constant expression. Returns a pointer to the value if evaluation - /// succeeded, 0 otherwise. + /// declaration, and produce notes explaining why it cannot be evaluated. + /// Returns a pointer to the value if evaluation succeeded, 0 otherwise. APValue *evaluateValue() const; - APValue *evaluateValue(SmallVectorImpl &Notes) const; +private: + APValue *evaluateValueImpl(SmallVectorImpl &Notes, + bool IsConstantInitialization) const; + +public: /// Return the already-evaluated value of this variable's /// initializer, or NULL if the value is not yet known. Returns pointer /// to untyped APValue if the value could not be evaluated. diff --git a/clang/include/clang/AST/Expr.h b/clang/include/clang/AST/Expr.h index 4a8e4e483a862f..25957909739eca 100644 --- a/clang/include/clang/AST/Expr.h +++ b/clang/include/clang/AST/Expr.h @@ -699,7 +699,8 @@ class Expr : public ValueStmt { /// notes will be produced if the expression is not a constant expression. bool EvaluateAsInitializer(APValue &Result, const ASTContext &Ctx, const VarDecl *VD, - SmallVectorImpl &Notes) const; + SmallVectorImpl &Notes, + bool IsConstantInitializer) const; /// EvaluateWithSubstitution - Evaluate an expression as if from the context /// of a call to the given function with the given arguments, inside an diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index 5331ed8f0a9e1a..5084f867622f4b 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -2360,11 +2360,11 @@ EvaluatedStmt *VarDecl::getEvaluatedStmt() const { APValue *VarDecl::evaluateValue() const { SmallVector Notes; - return evaluateValue(Notes); + return evaluateValueImpl(Notes, hasConstantInitialization()); } -APValue *VarDecl::evaluateValue( - SmallVectorImpl &Notes) const { +APValue *VarDecl::evaluateValueImpl(SmallVectorImpl &Notes, + bool IsConstantInitialization) const { EvaluatedStmt *Eval = ensureEvaluatedStmt(); const auto *Init = cast(Eval->Value); @@ -2383,8 +2383,16 @@ APValue *VarDecl::evaluateValue( Eval->IsEvaluating = true; - bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, getASTContext(), - this, Notes); + ASTContext &Ctx = getASTContext(); + bool Result = Init->EvaluateAsInitializer(Eval->Evaluated, Ctx, this, Notes, + IsConstantInitialization); + + // In C++11, this isn't a constant initializer if we produced notes. In that + // case, we can't keep the result, because it may only be correct under the + // assumption that the initializer is a constant context. + if (IsConstantInitialization && Ctx.getLangOpts().CPlusPlus11 && + !Notes.empty()) + Result = false; // Ensure the computed APValue is cleaned up later if evaluation succeeded, // or that it's empty (so that there's nothing to clean up) if evaluation @@ -2392,7 +2400,7 @@ APValue *VarDecl::evaluateValue( if (!Result) Eval->Evaluated = APValue(); else if (Eval->Evaluated.needsCleanup()) - getASTContext().addDestruction(&Eval->Evaluated); + Ctx.addDestruction(&Eval->Evaluated); Eval->IsEvaluating = false; Eval->WasEvaluated = true; @@ -2447,7 +2455,14 @@ bool VarDecl::checkForConstantInitialization( assert(!Init->isValueDependent()); // Evaluate the initializer to check whether it's a constant expression. - Eval->HasConstantInitialization = evaluateValue(Notes) && Notes.empty(); + Eval->HasConstantInitialization = + evaluateValueImpl(Notes, true) && Notes.empty(); + + // If evaluation as a constant initializer failed, allow re-evaluation as a + // non-constant initializer if we later find we want the value. + if (!Eval->HasConstantInitialization) + Eval->WasEvaluated = false; + return Eval->HasConstantInitialization; } diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 34893302f2e796..f4664bb624148f 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -3269,12 +3269,9 @@ static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, // Check that we can fold the initializer. In C++, we will have already done // this in the cases where it matters for conformance. - SmallVector Notes; - if (!VD->evaluateValue(Notes)) { - Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, - Notes.size() + 1) << VD; + if (!VD->evaluateValue()) { + Info.FFDiag(E, diag::note_constexpr_var_init_non_constant, 1) << VD; NoteLValueLocation(Info, Base); - Info.addNotes(Notes); return false; } @@ -14687,7 +14684,8 @@ bool Expr::EvaluateAsConstantExpr(EvalResult &Result, ConstExprUsage Usage, bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, const VarDecl *VD, - SmallVectorImpl &Notes) const { + SmallVectorImpl &Notes, + bool IsConstantInitialization) const { assert(!isValueDependent() && "Expression evaluator can't be called on a dependent expression."); @@ -14700,11 +14698,12 @@ bool Expr::EvaluateAsInitializer(APValue &Value, const ASTContext &Ctx, Expr::EvalStatus EStatus; EStatus.Diag = &Notes; - EvalInfo Info(Ctx, EStatus, VD->isConstexpr() - ? EvalInfo::EM_ConstantExpression - : EvalInfo::EM_ConstantFold); + EvalInfo Info(Ctx, EStatus, + (IsConstantInitialization && Ctx.getLangOpts().CPlusPlus11) + ? EvalInfo::EM_ConstantExpression + : EvalInfo::EM_ConstantFold); Info.setEvaluatingDecl(VD, Value); - Info.InConstantContext = true; + Info.InConstantContext = IsConstantInitialization; SourceLocation DeclLoc = VD->getLocation(); QualType DeclTy = VD->getType(); diff --git a/clang/lib/CodeGen/CGExprConstant.cpp b/clang/lib/CodeGen/CGExprConstant.cpp index 57f1ad59a72a41..011d39cfa29474 100644 --- a/clang/lib/CodeGen/CGExprConstant.cpp +++ b/clang/lib/CodeGen/CGExprConstant.cpp @@ -1622,8 +1622,8 @@ llvm::Constant *ConstantEmitter::tryEmitPrivateForVarInit(const VarDecl &D) { if (CD->isTrivial() && CD->isDefaultConstructor()) return CGM.EmitNullConstant(D.getType()); } - InConstantContext = true; } + InConstantContext = D.hasConstantInitialization(); QualType destType = D.getType(); diff --git a/clang/test/CodeGenCXX/builtin-is-constant-evaluated.cpp b/clang/test/CodeGenCXX/builtin-is-constant-evaluated.cpp index cc4e375b50ad4f..78dfc695c15e46 100644 --- a/clang/test/CodeGenCXX/builtin-is-constant-evaluated.cpp +++ b/clang/test/CodeGenCXX/builtin-is-constant-evaluated.cpp @@ -63,9 +63,9 @@ CONSTINIT int b = __builtin_is_constant_evaluated() ? 2 : y; // static initializ // CHECK-DYN-NEXT: store i32 %add, i32* @c, int c = y + (__builtin_is_constant_evaluated() ? 2 : y); // dynamic initialization to y+y -// CHECK-DYN-LABEL: define internal void @__cxx_global_var_init.2() -// CHECK-DYN: store i32 1, i32* @_ZL1a, align 4 -// CHECK-DYN-NEXT: ret void +// This is dynamic initialization that we can convert to static initialization +// during lowering. When doing so, the dynamic initializer value is preserved. +// CHECK-STATIC-DAG: @_ZL1a = internal constant i32 1 const int a = __builtin_is_constant_evaluated() ? y : 1; // dynamic initialization to 1 const int *a_sink = &a; diff --git a/clang/test/PCH/builtin-is-constant-evaluated.cpp b/clang/test/PCH/builtin-is-constant-evaluated.cpp new file mode 100644 index 00000000000000..667d586c2578d8 --- /dev/null +++ b/clang/test/PCH/builtin-is-constant-evaluated.cpp @@ -0,0 +1,67 @@ +// RUN: %clang_cc1 -std=c++98 -Wno-constant-evaluated -triple x86_64-linux -include %s -verify %s -emit-llvm -o - | FileCheck %s +// RUN: %clang_cc1 -std=c++98 -Wno-constant-evaluated -triple x86_64-linux -emit-pch %s -o %t +// RUN: %clang_cc1 -std=c++98 -Wno-constant-evaluated -triple x86_64-linux -include-pch %t -verify %s -emit-llvm -o - | FileCheck %s + +// RUN: %clang_cc1 -std=c++11 -Wno-constant-evaluated -triple x86_64-linux -include %s -verify %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,CXX11 +// RUN: %clang_cc1 -std=c++11 -Wno-constant-evaluated -triple x86_64-linux -emit-pch %s -o %t-cxx11 +// RUN: %clang_cc1 -std=c++11 -Wno-constant-evaluated -triple x86_64-linux -include-pch %t-cxx11 -verify %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,CXX11 + +// RUN: %clang_cc1 -std=c++20 -Wno-constant-evaluated -triple x86_64-linux -include %s -verify %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,CXX11 +// RUN: %clang_cc1 -std=c++20 -Wno-constant-evaluated -triple x86_64-linux -emit-pch %s -o %t-cxx11 +// RUN: %clang_cc1 -std=c++20 -Wno-constant-evaluated -triple x86_64-linux -include-pch %t-cxx11 -verify %s -emit-llvm -o - | FileCheck %s --check-prefixes=CHECK,CXX11 + +// expected-no-diagnostics + +#ifndef HEADER_INCLUDED +#define HEADER_INCLUDED + +// CHECK-DAG: @a = global i8 1, +// CHECK-DAG: @b = constant i8 1, +// CXX11-DAG: @c = constant i8 1, +// CHECK-DAG: @d = global float 1.000000e+00 +// CHECK-DAG: @e = constant float 1.000000e+00 + +bool a = __builtin_is_constant_evaluated(); +extern const bool b = __builtin_is_constant_evaluated(); +#if __cplusplus >= 201103L +extern constexpr bool c = __builtin_is_constant_evaluated(); +#endif +float d = __builtin_is_constant_evaluated(); +extern const float e = __builtin_is_constant_evaluated(); + +void g(...); + +// CHECK-LABEL: define {{.*}} @_Z1fv( +// CHECK: store i8 0, i8* %[[A:.*]], +// CHECK: store i8 1, i8* %[[B:.*]], +// CXX11: store i8 1, i8* %[[C:.*]], +// CHECK: store float 0.000000e+00, float* %[[D:.*]], +// CHECK: store float 0.000000e+00, float* %[[E:.*]], +// CHECK: load i8, i8* %[[A]], +// CHECK: call {{.*}} @_Z1gz(i32 %{{[^,]+}}, i32 1 +// CXX11-SAME: , i32 1 +// CHECK-SAME: , double %{{[^,]+}}, double 0.000000e+00) +void f() { + bool a = __builtin_is_constant_evaluated(); + const bool b = __builtin_is_constant_evaluated(); +#if __cplusplus >= 201103L + constexpr bool c = __builtin_is_constant_evaluated(); +#endif + float d = __builtin_is_constant_evaluated(); + const float e = __builtin_is_constant_evaluated(); + g(a, b +#if __cplusplus >= 201103L + , c +#endif + , d, e); +} + +#else + +_Static_assert(b, ""); +#if __cplusplus >= 201103L +static_assert(c, ""); +#endif +_Static_assert(__builtin_constant_p(1) ? e == 1.0f : false, ""); + +#endif diff --git a/clang/test/SemaCXX/attr-require-constant-initialization.cpp b/clang/test/SemaCXX/attr-require-constant-initialization.cpp index 32fc7f3eec99ec..77a4d52538ddfe 100644 --- a/clang/test/SemaCXX/attr-require-constant-initialization.cpp +++ b/clang/test/SemaCXX/attr-require-constant-initialization.cpp @@ -152,7 +152,7 @@ void test_basic_start_static_2_2() { #else ATTR static PODType pod; // expected-error {{variable does not have a constant initializer}} // expected-note@-1 {{required by 'require_constant_initialization' attribute here}} -// expected-note@-2 {{subobject of type 'int' is not initialized}} +// expected-note-re@-2 {{{{non-constexpr constructor|subobject of type 'int' is not initialized}}}} #endif ATTR static PODType pot2 = {ReturnInt()}; // expected-error {{variable does not have a constant initializer}} // expected-note@-1 {{required by 'require_constant_initialization' attribute here}} @@ -191,7 +191,7 @@ struct TT2 { PODType TT2::pod_noinit; // expected-note 0+ {{declared here}} #if __cplusplus >= 201103L // expected-error@-2 {{variable does not have a constant initializer}} -// expected-note@-3 {{subobject of type 'int' is not initialized}} +// expected-note-re@-3 {{{{non-constexpr constructor|subobject of type 'int' is not initialized}}}} #endif PODType TT2::pod_copy_init(TT2::pod_noinit); // expected-error {{variable does not have a constant initializer}} #if __cplusplus >= 201103L diff --git a/clang/test/SemaCXX/builtin-is-constant-evaluated.cpp b/clang/test/SemaCXX/builtin-is-constant-evaluated.cpp index 4af2b494751c96..c54d7c1b7aa843 100644 --- a/clang/test/SemaCXX/builtin-is-constant-evaluated.cpp +++ b/clang/test/SemaCXX/builtin-is-constant-evaluated.cpp @@ -1,5 +1,7 @@ // RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -Wno-constant-evaluated -triple=x86_64-linux-gnu +#define fold(x) (__builtin_constant_p(x) ? (x) : (x)) + using size_t = decltype(sizeof(int)); namespace std { @@ -119,3 +121,25 @@ struct TestConditionalExplicit { }; TestConditionalExplicit e = 42; #endif + +namespace fold_initializer { + // Global 'f' has a constant initializer. + const float f = __builtin_is_constant_evaluated(); + static_assert(fold(f == 1.0f)); + + void g() { + // Local static 'sf' has a constant initializer. + static const float sf = __builtin_is_constant_evaluated(); + static_assert(fold(sf == 1.0f)); + + // Local non-static 'f' has a non-constant initializer. + const float f = __builtin_is_constant_evaluated(); + static_assert(fold(f == 0.0f)); + } + + struct A { + static const float f; + }; + const float A::f = __builtin_is_constant_evaluated(); + static_assert(fold(A::f == 1.0f)); +}