Skip to content

Commit

Permalink
[clang][Interp] Allow initializing static class members
Browse files Browse the repository at this point in the history
We need to handle this when registering global variables.
  • Loading branch information
tbaederr committed Apr 11, 2024
1 parent 2ea7ec9 commit 64c3997
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 66 deletions.
34 changes: 21 additions & 13 deletions clang/lib/AST/Interp/ByteCodeExprGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2778,26 +2778,34 @@ bool ByteCodeExprGen<Emitter>::visitVarDecl(const VarDecl *VD) {
std::optional<PrimType> VarT = classify(VD->getType());

if (Context::shouldBeGloballyIndexed(VD)) {
// We've already seen and initialized this global.
if (P.getGlobal(VD))
return true;

std::optional<unsigned> GlobalIndex = P.createGlobal(VD, Init);

if (!GlobalIndex)
return false;

if (Init) {
auto initGlobal = [&](unsigned GlobalIndex) -> bool {
assert(Init);
DeclScope<Emitter> LocalScope(this, VD);

if (VarT) {
if (!this->visit(Init))
return false;
return this->emitInitGlobal(*VarT, *GlobalIndex, VD);
return this->emitInitGlobal(*VarT, GlobalIndex, VD);
}
return this->visitGlobalInitializer(Init, *GlobalIndex);
return this->visitGlobalInitializer(Init, GlobalIndex);
};

// We've already seen and initialized this global.
if (std::optional<unsigned> GlobalIndex = P.getGlobal(VD)) {
if (P.getPtrGlobal(*GlobalIndex).isInitialized())
return true;

// The previous attempt at initialization might've been unsuccessful,
// so let's try this one.
return Init && initGlobal(*GlobalIndex);
}
return true;

std::optional<unsigned> GlobalIndex = P.createGlobal(VD, Init);

if (!GlobalIndex)
return false;

return !Init || initGlobal(*GlobalIndex);
} else {
VariableScope<Emitter> LocalScope(this);
if (VarT) {
Expand Down
99 changes: 62 additions & 37 deletions clang/lib/AST/Interp/Interp.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,22 +56,65 @@ static bool Jf(InterpState &S, CodePtr &PC, int32_t Offset) {
return true;
}

static void diagnoseMissingInitializer(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
const SourceInfo &E = S.Current->getSource(OpPC);
S.FFDiag(E, diag::note_constexpr_var_init_unknown, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at) << VD->getSourceRange();
}

static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
const ValueDecl *VD);
static bool diagnoseUnknownDecl(InterpState &S, CodePtr OpPC,
const ValueDecl *D) {
const SourceInfo &E = S.Current->getSource(OpPC);

if (isa<ParmVarDecl>(D)) {
if (S.getLangOpts().CPlusPlus11) {
S.FFDiag(E, diag::note_constexpr_function_param_value_unknown) << D;
S.Note(D->getLocation(), diag::note_declared_at) << D->getSourceRange();
} else {
S.FFDiag(E);
}
} else if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (!VD->getType().isConstQualified()) {
diagnoseNonConstVariable(S, OpPC, VD);
return false;
}

// const, but no initializer.
if (!VD->getAnyInitializer()) {
diagnoseMissingInitializer(S, OpPC, VD);
return false;
}
}
return false;
}

static void diagnoseNonConstVariable(InterpState &S, CodePtr OpPC,
const ValueDecl *VD) {
if (!S.getLangOpts().CPlusPlus)
return;

const SourceInfo &Loc = S.Current->getSource(OpPC);
if (const auto *VarD = dyn_cast<VarDecl>(VD);
VarD && VarD->getType().isConstQualified() &&
!VarD->getAnyInitializer()) {
diagnoseMissingInitializer(S, OpPC, VD);
return;
}

if (VD->getType()->isIntegralOrEnumerationType())
if (VD->getType()->isIntegralOrEnumerationType()) {
S.FFDiag(Loc, diag::note_constexpr_ltor_non_const_int, 1) << VD;
else
S.FFDiag(Loc,
S.getLangOpts().CPlusPlus11
? diag::note_constexpr_ltor_non_constexpr
: diag::note_constexpr_ltor_non_integral,
1)
<< VD << VD->getType();
S.Note(VD->getLocation(), diag::note_declared_at);
return;
}

S.FFDiag(Loc,
S.getLangOpts().CPlusPlus11 ? diag::note_constexpr_ltor_non_constexpr
: diag::note_constexpr_ltor_non_integral,
1)
<< VD << VD->getType();
S.Note(VD->getLocation(), diag::note_declared_at);
}

Expand Down Expand Up @@ -202,6 +245,9 @@ bool CheckExtern(InterpState &S, CodePtr OpPC, const Pointer &Ptr) {
if (!Ptr.isExtern())
return true;

if (Ptr.isInitialized())
return true;

if (!S.checkingPotentialConstantExpression() && S.getLangOpts().CPlusPlus) {
const auto *VD = Ptr.getDeclDesc()->asValueDecl();
diagnoseNonConstVariable(S, OpPC, VD);
Expand Down Expand Up @@ -369,9 +415,15 @@ bool CheckInitialized(InterpState &S, CodePtr OpPC, const Pointer &Ptr,
if (const auto *VD = Ptr.getDeclDesc()->asVarDecl();
VD && VD->hasGlobalStorage()) {
const SourceInfo &Loc = S.Current->getSource(OpPC);
S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at);
if (VD->getAnyInitializer()) {
S.FFDiag(Loc, diag::note_constexpr_var_init_non_constant, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at);
} else {
diagnoseMissingInitializer(S, OpPC, VD);
}
return false;
}

if (!S.checkingPotentialConstantExpression()) {
S.FFDiag(S.Current->getSource(OpPC), diag::note_constexpr_access_uninit)
<< AK << /*uninitialized=*/true << S.Current->getRange(OpPC);
Expand Down Expand Up @@ -598,33 +650,6 @@ bool CheckFloatResult(InterpState &S, CodePtr OpPC, const Floating &Result,
return true;
}

static bool diagnoseUnknownDecl(InterpState &S, CodePtr OpPC,
const ValueDecl *D) {
const SourceInfo &E = S.Current->getSource(OpPC);

if (isa<ParmVarDecl>(D)) {
if (S.getLangOpts().CPlusPlus11) {
S.FFDiag(E, diag::note_constexpr_function_param_value_unknown) << D;
S.Note(D->getLocation(), diag::note_declared_at) << D->getSourceRange();
} else {
S.FFDiag(E);
}
} else if (const auto *VD = dyn_cast<VarDecl>(D)) {
if (!VD->getType().isConstQualified()) {
diagnoseNonConstVariable(S, OpPC, VD);
return false;
}

// const, but no initializer.
if (!VD->getAnyInitializer()) {
S.FFDiag(E, diag::note_constexpr_var_init_unknown, 1) << VD;
S.Note(VD->getLocation(), diag::note_declared_at) << VD->getSourceRange();
return false;
}
}
return false;
}

/// We aleady know the given DeclRefExpr is invalid for some reason,
/// now figure out why and print appropriate diagnostics.
bool CheckDeclRef(InterpState &S, CodePtr OpPC, const DeclRefExpr *DR) {
Expand Down
2 changes: 1 addition & 1 deletion clang/lib/AST/Interp/Program.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -177,7 +177,7 @@ std::optional<unsigned> Program::createGlobal(const ValueDecl *VD,
bool IsStatic, IsExtern;
if (const auto *Var = dyn_cast<VarDecl>(VD)) {
IsStatic = Context::shouldBeGloballyIndexed(VD);
IsExtern = !Var->getAnyInitializer();
IsExtern = Var->hasExternalStorage();
} else if (isa<UnnamedGlobalConstantDecl, MSGuidDecl>(VD)) {
IsStatic = true;
IsExtern = false;
Expand Down
17 changes: 6 additions & 11 deletions clang/test/AST/Interp/cxx23.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,18 @@

/// FIXME: The new interpreter is missing all the 'control flows through...' diagnostics.

constexpr int f(int n) { // ref20-error {{constexpr function never produces a constant expression}} \
// expected20-error {{constexpr function never produces a constant expression}}
constexpr int f(int n) { // ref20-error {{constexpr function never produces a constant expression}}
static const int m = n; // ref20-note {{control flows through the definition of a static variable}} \
// ref20-warning {{is a C++23 extension}} \
// expected20-warning {{is a C++23 extension}} \
// expected20-note {{declared here}} \
// expected20-warning {{is a C++23 extension}}

return m; // expected20-note {{initializer of 'm' is not a constant expression}}
return m;
}
constexpr int g(int n) { // ref20-error {{constexpr function never produces a constant expression}} \
// expected20-error {{constexpr function never produces a constant expression}}
constexpr int g(int n) { // ref20-error {{constexpr function never produces a constant expression}}
thread_local const int m = n; // ref20-note {{control flows through the definition of a thread_local variable}} \
// ref20-warning {{is a C++23 extension}} \
// expected20-warning {{is a C++23 extension}} \
// expected20-note {{declared here}}
return m; // expected20-note {{initializer of 'm' is not a constant expression}}

// expected20-warning {{is a C++23 extension}}
return m;
}

constexpr int c_thread_local(int n) { // ref20-error {{constexpr function never produces a constant expression}} \
Expand Down
32 changes: 28 additions & 4 deletions clang/test/AST/Interp/records.cpp
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -verify=expected,both %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++14 -verify=expected,both %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -verify=expected,both %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -triple i686 -verify=expected,both %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++20 -verify=expected,both %s
// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -triple i686 -verify=expected,both %s
// RUN: %clang_cc1 -verify=ref,both %s
// RUN: %clang_cc1 -verify=ref,both -std=c++14 %s
// RUN: %clang_cc1 -verify=ref,both -std=c++17 %s
// RUN: %clang_cc1 -verify=ref,both -std=c++17 -triple i686 %s
// RUN: %clang_cc1 -verify=ref,both -std=c++20 %s
// RUN: %clang_cc1 -verify=ref,both -triple i686 %s

/// Used to crash.
struct Empty {};
Expand Down Expand Up @@ -1285,3 +1285,27 @@ namespace {
}
}
#endif

namespace pr18633 {
struct A1 {
static const int sz;
static const int sz2;
};
const int A1::sz2 = 11;
template<typename T>
void func () {
int arr[A1::sz];
// both-warning@-1 {{variable length arrays in C++ are a Clang extension}}
// both-note@-2 {{initializer of 'sz' is unknown}}
// both-note@-9 {{declared here}}
}
template<typename T>
void func2 () {
int arr[A1::sz2];
}
const int A1::sz = 12;
void func2() {
func<int>();
func2<int>();
}
}

0 comments on commit 64c3997

Please sign in to comment.