Skip to content

[Modules] Record side effect info in EvaluatedStmt (#146468) #10957

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

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 12 additions & 1 deletion clang/include/clang/AST/Decl.h
Original file line number Diff line number Diff line change
Expand Up @@ -874,13 +874,17 @@ struct EvaluatedStmt {
bool HasICEInit : 1;
bool CheckedForICEInit : 1;

bool HasSideEffects : 1;
bool CheckedForSideEffects : 1;

LazyDeclStmtPtr Value;
APValue Evaluated;

EvaluatedStmt()
: WasEvaluated(false), IsEvaluating(false),
HasConstantInitialization(false), HasConstantDestruction(false),
HasICEInit(false), CheckedForICEInit(false) {}
HasICEInit(false), CheckedForICEInit(false), HasSideEffects(false),
CheckedForSideEffects(false) {}
};

/// Represents a variable declaration or definition.
Expand Down Expand Up @@ -1339,6 +1343,13 @@ class VarDecl : public DeclaratorDecl, public Redeclarable<VarDecl> {
return const_cast<VarDecl *>(this)->getInitializingDeclaration();
}

/// Checks whether this declaration has an initializer with side effects.
/// The result is cached. If the result hasn't been computed this can trigger
/// deserialization and constant evaluation. By running this during
/// serialization and serializing the result all clients can safely call this
/// without triggering further deserialization.
bool hasInitWithSideEffects() const;

/// Determine whether this variable's value might be usable in a
/// constant expression, according to the relevant language standard.
/// This only checks properties of the declaration, and does not check
Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/AST/ExternalASTSource.h
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ class RecordDecl;
class Selector;
class Stmt;
class TagDecl;
class VarDecl;

/// Abstract interface for external sources of AST nodes.
///
Expand Down
4 changes: 1 addition & 3 deletions clang/lib/AST/ASTContext.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13262,9 +13262,7 @@ bool ASTContext::DeclMustBeEmitted(const Decl *D) {
return true;

// Variables that have initialization with side-effects are required.
if (VD->getInit() && VD->getInit()->HasSideEffects(*this) &&
// We can get a value-dependent initializer during error recovery.
(VD->getInit()->isValueDependent() || !VD->evaluateValue()))
if (VD->hasInitWithSideEffects())
return true;

// Likewise, variables with tuple-like bindings are required if their
Expand Down
17 changes: 17 additions & 0 deletions clang/lib/AST/Decl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2440,6 +2440,23 @@ VarDecl *VarDecl::getInitializingDeclaration() {
return Def;
}

bool VarDecl::hasInitWithSideEffects() const {
if (!hasInit())
return false;

EvaluatedStmt *ES = ensureEvaluatedStmt();
if (!ES->CheckedForSideEffects) {
const Expr *E = getInit();
ES->HasSideEffects =
E->HasSideEffects(getASTContext()) &&
// We can get a value-dependent initializer during error recovery.
(E->isValueDependent() || getType()->isDependentType() ||
!evaluateValue());
ES->CheckedForSideEffects = true;
}
return ES->HasSideEffects;
}

bool VarDecl::isOutOfLine() const {
if (Decl::isOutOfLine())
return true;
Expand Down
6 changes: 6 additions & 0 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16268,6 +16268,12 @@ static bool EvaluateInPlace(APValue &Result, EvalInfo &Info, const LValue &This,
const Expr *E, bool AllowNonLiteralTypes) {
assert(!E->isValueDependent());

// Normally expressions passed to EvaluateInPlace have a type, but not when
// a VarDecl initializer is evaluated before the untyped ParenListExpr is
// replaced with a CXXConstructExpr. This can happen in LLDB.
if (E->getType().isNull())
return false;

if (!AllowNonLiteralTypes && !CheckLiteralType(Info, E, &This))
return false;

Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Serialization/ASTReaderDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1692,6 +1692,8 @@ void ASTDeclReader::ReadVarDeclInit(VarDecl *VD) {
Eval->HasConstantInitialization = (Val & 2) != 0;
Eval->HasConstantDestruction = (Val & 4) != 0;
Eval->WasEvaluated = (Val & 8) != 0;
Eval->HasSideEffects = (Val & 16) != 0;
Eval->CheckedForSideEffects = true;
if (Eval->WasEvaluated) {
Eval->Evaluated = Record.readAPValue();
if (Eval->Evaluated.needsCleanup())
Expand Down
4 changes: 4 additions & 0 deletions clang/lib/Serialization/ASTWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6834,6 +6834,10 @@ void ASTRecordWriter::AddVarDeclInit(const VarDecl *VD) {

uint64_t Val = 1;
if (EvaluatedStmt *ES = VD->getEvaluatedStmt()) {
// This may trigger evaluation, so run it first
if (VD->hasInitWithSideEffects())
Val |= 16;
assert(ES->CheckedForSideEffects);
Val |= (ES->HasConstantInitialization ? 2 : 0);
Val |= (ES->HasConstantDestruction ? 4 : 0);
APValue *Evaluated = VD->getEvaluatedValue();
Expand Down
13 changes: 7 additions & 6 deletions clang/lib/Serialization/ASTWriterDecl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1207,10 +1207,11 @@ void ASTDeclWriter::VisitVarDecl(VarDecl *D) {
!D->hasExtInfo() && D->getFirstDecl() == D->getMostRecentDecl() &&
D->getKind() == Decl::Var && !D->isInline() && !D->isConstexpr() &&
!D->isInitCapture() && !D->isPreviousDeclInSameBlockScope() &&
!D->isEscapingByref() && !HasDeducedType &&
D->getStorageDuration() != SD_Static && !D->getDescribedVarTemplate() &&
!D->getMemberSpecializationInfo() && !D->isObjCForDecl() &&
!isa<ImplicitParamDecl>(D) && !D->isEscapingByref())
!D->hasInitWithSideEffects() && !D->isEscapingByref() &&
!HasDeducedType && D->getStorageDuration() != SD_Static &&
!D->getDescribedVarTemplate() && !D->getMemberSpecializationInfo() &&
!D->isObjCForDecl() && !isa<ImplicitParamDecl>(D) &&
!D->isEscapingByref())
AbbrevToUse = Writer.getDeclVarAbbrev();

Code = serialization::DECL_VAR;
Expand Down Expand Up @@ -2529,12 +2530,12 @@ void ASTWriter::WriteDeclAbbrevs() {
// VarDecl
Abv->Add(BitCodeAbbrevOp(
BitCodeAbbrevOp::Fixed,
21)); // Packed Var Decl bits: Linkage, ModulesCodegen,
22)); // Packed Var Decl bits: Linkage, ModulesCodegen,
// SClass, TSCSpec, InitStyle,
// isARCPseudoStrong, IsThisDeclarationADemotedDefinition,
// isExceptionVariable, isNRVOVariable, isCXXForRangeDecl,
// isInline, isInlineSpecified, isConstexpr,
// isInitCapture, isPrevDeclInSameScope,
// isInitCapture, isPrevDeclInSameScope, hasInitWithSideEffects,
// EscapingByref, HasDeducedType, ImplicitParamKind, isObjCForDecl
Abv->Add(BitCodeAbbrevOp(0)); // VarKind (local enum)
// Type Source Info
Expand Down
51 changes: 51 additions & 0 deletions clang/test/Modules/var-init-side-effects-modulemap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t

// RUN: %clang_cc1 -fsyntax-only -fmodules -fmodules-cache-path=%t -fmodule-map-file=%t/module.modulemap %t/test.cppm -I%t
//

//--- test.cppm
#pragma clang module import Baz

//--- Foo.h
#pragma once
class foo {
char dummy = 1;

public:
static foo var;

};

inline foo foo::var;

//--- Bar.h
#pragma once
#include <Foo.h>

void bar() {
(void) foo::var;
}

//--- Baz.h
#pragma once
#include <Foo.h>

void baz() {
(void) foo::var;
}

#include <Bar.h>

//--- module.modulemap
module Foo {
header "Foo.h"
}
module Bar {
header "Bar.h"
}
module Baz {
header "Baz.h"
}

20 changes: 20 additions & 0 deletions clang/test/Modules/var-init-side-effects-templated.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Tests referencing variable with initializer containing side effect across module boundary

// RUN: %clang_cc1 -std=c++20 -emit-module-interface %s -o %t

export module Foo;

export template <class Float>
struct Wrapper {
double value;
};

export constexpr Wrapper<double> Compute() {
return Wrapper<double>{1.0};
}

export template <typename Float>
Wrapper<Float> ComputeInFloat() {
const Wrapper<Float> a = Compute();
return a;
}
37 changes: 37 additions & 0 deletions clang/test/Modules/var-init-side-effects.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
// Tests referencing variable with initializer containing side effect across module boundary
//
// RUN: rm -rf %t
// RUN: mkdir -p %t
// RUN: split-file %s %t

// RUN: %clang_cc1 -std=c++20 -emit-module-interface %t/Foo.cppm \
// RUN: -o %t/Foo.pcm

// RUN: %clang_cc1 -std=c++20 -emit-module-interface \
// RUN: -fmodule-file=Foo=%t/Foo.pcm \
// RUN: %t/Bar.cppm \
// RUN: -o %t/Bar.pcm

// RUN: %clang_cc1 -std=c++20 -emit-obj \
// RUN: -main-file-name Bar.cppm \
// RUN: -fmodule-file=Foo=%t/Foo.pcm \
// RUN: -x pcm %t/Bar.pcm \
// RUN: -o %t/Bar.o

//--- Foo.cppm
export module Foo;

export {
class S {};

inline S s = S{};
}

//--- Bar.cppm
export module Bar;
import Foo;

S bar() {
return s;
}