diff --git a/clang/include/clang/AST/Decl.h b/clang/include/clang/AST/Decl.h index 50a712836e3dd..04f5d2220dac9 100644 --- a/clang/include/clang/AST/Decl.h +++ b/clang/include/clang/AST/Decl.h @@ -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. @@ -1339,6 +1343,13 @@ class VarDecl : public DeclaratorDecl, public Redeclarable { return const_cast(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 diff --git a/clang/include/clang/AST/ExternalASTSource.h b/clang/include/clang/AST/ExternalASTSource.h index 069a38cdc323f..c201cc9933076 100644 --- a/clang/include/clang/AST/ExternalASTSource.h +++ b/clang/include/clang/AST/ExternalASTSource.h @@ -51,6 +51,7 @@ class RecordDecl; class Selector; class Stmt; class TagDecl; +class VarDecl; /// Abstract interface for external sources of AST nodes. /// diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index 8ba7fa2479d04..973c2c84978c7 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -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 diff --git a/clang/lib/AST/Decl.cpp b/clang/lib/AST/Decl.cpp index cfbb3393183df..9e9916f5d7910 100644 --- a/clang/lib/AST/Decl.cpp +++ b/clang/lib/AST/Decl.cpp @@ -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; diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index d89b9ef6548b9..6e5be0eee74f6 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -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; diff --git a/clang/lib/Serialization/ASTReaderDecl.cpp b/clang/lib/Serialization/ASTReaderDecl.cpp index 27dbc2f4c9218..0ad3c2f7d4d14 100644 --- a/clang/lib/Serialization/ASTReaderDecl.cpp +++ b/clang/lib/Serialization/ASTReaderDecl.cpp @@ -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()) diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index dd1d35c15a60a..ccaf317b706f1 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -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(); diff --git a/clang/lib/Serialization/ASTWriterDecl.cpp b/clang/lib/Serialization/ASTWriterDecl.cpp index 41391cd0821d6..1ef19d206aada 100644 --- a/clang/lib/Serialization/ASTWriterDecl.cpp +++ b/clang/lib/Serialization/ASTWriterDecl.cpp @@ -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(D) && !D->isEscapingByref()) + !D->hasInitWithSideEffects() && !D->isEscapingByref() && + !HasDeducedType && D->getStorageDuration() != SD_Static && + !D->getDescribedVarTemplate() && !D->getMemberSpecializationInfo() && + !D->isObjCForDecl() && !isa(D) && + !D->isEscapingByref()) AbbrevToUse = Writer.getDeclVarAbbrev(); Code = serialization::DECL_VAR; @@ -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 diff --git a/clang/test/Modules/var-init-side-effects-modulemap.cpp b/clang/test/Modules/var-init-side-effects-modulemap.cpp new file mode 100644 index 0000000000000..750a6f1731405 --- /dev/null +++ b/clang/test/Modules/var-init-side-effects-modulemap.cpp @@ -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 + +void bar() { + (void) foo::var; +} + +//--- Baz.h +#pragma once +#include + +void baz() { + (void) foo::var; +} + +#include + +//--- module.modulemap +module Foo { + header "Foo.h" +} +module Bar { + header "Bar.h" +} +module Baz { + header "Baz.h" +} + diff --git a/clang/test/Modules/var-init-side-effects-templated.cpp b/clang/test/Modules/var-init-side-effects-templated.cpp new file mode 100644 index 0000000000000..542ca270429b6 --- /dev/null +++ b/clang/test/Modules/var-init-side-effects-templated.cpp @@ -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 +struct Wrapper { + double value; +}; + +export constexpr Wrapper Compute() { + return Wrapper{1.0}; +} + +export template +Wrapper ComputeInFloat() { + const Wrapper a = Compute(); + return a; +} diff --git a/clang/test/Modules/var-init-side-effects.cpp b/clang/test/Modules/var-init-side-effects.cpp new file mode 100644 index 0000000000000..438a9eb204275 --- /dev/null +++ b/clang/test/Modules/var-init-side-effects.cpp @@ -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; +} +