Index: clang/lib/AST/Interp/ByteCodeExprGen.h =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.h +++ clang/lib/AST/Interp/ByteCodeExprGen.h @@ -232,6 +232,7 @@ bool dereferenceVar(const Expr *LV, PrimType T, const VarDecl *PD, DerefKind AK, llvm::function_ref Direct, llvm::function_ref Indirect); + bool allocateVariable(const ValueDecl *VD, const Expr *Init); /// Emits an APSInt constant. bool emitConst(const APSInt &Value, const Expr *E); @@ -258,9 +259,12 @@ } /// Returns whether we should create a global variable for the - /// given VarDecl. - bool shouldBeGloballyIndexed(const VarDecl *VD) const { - return VD->hasGlobalStorage() || VD->isConstexpr(); + /// given ValueDecl. + bool shouldBeGloballyIndexed(const ValueDecl *VD) const { + if (const auto *V = dyn_cast(VD)) + return V->hasGlobalStorage() || V->isConstexpr(); + + return false; } llvm::RoundingMode getRoundingMode(const Expr *E) const { Index: clang/lib/AST/Interp/ByteCodeExprGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeExprGen.cpp +++ clang/lib/AST/Interp/ByteCodeExprGen.cpp @@ -28,9 +28,11 @@ /// Scope used to handle temporaries in toplevel variable declarations. template class DeclScope final : public LocalScope { public: - DeclScope(ByteCodeExprGen *Ctx, const VarDecl *VD) + DeclScope(ByteCodeExprGen *Ctx, const ValueDecl *VD) : LocalScope(Ctx), Scope(Ctx->P, VD) {} + virtual ~DeclScope() override { this->emitDestruction(); } + void addExtended(const Scope::Local &Local) override { return this->addLocal(Local); } @@ -766,11 +768,11 @@ if (std::optional T = classify(LV->getType())) { if (!LV->refersToBitField()) { // Only primitive, non bit-field types can be dereferenced directly. - if (auto *DE = dyn_cast(LV)) { + if (const auto *DE = dyn_cast(LV)) { if (!DE->getDecl()->getType()->isReferenceType()) { - if (auto *PD = dyn_cast(DE->getDecl())) + if (const auto *PD = dyn_cast(DE->getDecl())) return dereferenceParam(LV, *T, PD, AK, Direct, Indirect); - if (auto *VD = dyn_cast(DE->getDecl())) + if (const auto *VD = dyn_cast(DE->getDecl())) return dereferenceVar(LV, *T, VD, AK, Direct, Indirect); } } @@ -1340,8 +1342,8 @@ } template -bool ByteCodeExprGen::visitVarDecl(const VarDecl *VD) { - const Expr *Init = VD->getInit(); +bool ByteCodeExprGen::allocateVariable(const ValueDecl *VD, + const Expr *Init) { std::optional VarT = classify(VD->getType()); if (shouldBeGloballyIndexed(VD)) { @@ -1364,6 +1366,11 @@ } else { DeclScope LocalScope(this, VD); + if (auto *BD = dyn_cast(VD); + BD && BD->getDecomposedDecl()->getType()->isReferenceType()) { + VarT = PT_Ptr; + } + if (VarT) { unsigned Offset = this->allocateLocalPrimitive( VD, *VarT, VD->getType().isConstQualified()); @@ -1373,6 +1380,14 @@ if (!this->visit(Init)) return false; + // Initializers are supposed to be rvalues. However, sometimes + // they aren't. This is the case for C++ decomposition decl + // bindings. + if (Init && Init->isLValue() && *VarT != PT_Ptr) { + if (!this->emitLoadPop(*VarT, VD)) + return false; + } + return this->emitSetLocal(*VarT, Offset, VD); } } else { @@ -1387,6 +1402,29 @@ return false; } +template +bool ByteCodeExprGen::visitVarDecl(const VarDecl *VD) { + // For decomposition decls, we just emit multiple variables. + if (const auto *DD = dyn_cast(VD)) { + // Expression to decompose. + const Expr *Init = DD->getInit(); + + if (!this->allocateVariable(DD, Init)) + return false; + + for (const auto *BD : DD->bindings()) { + assert(!BD->getHoldingVar()); // FIXME + if (!this->allocateVariable(BD, BD->getBinding())) + return false; + } + + return true; + } + + // Regular variables. + return this->allocateVariable(VD, VD->getInit()); +} + template bool ByteCodeExprGen::VisitBuiltinCallExpr(const CallExpr *E) { const Function *Func = getFunction(E->getDirectCallee()); @@ -1634,7 +1672,14 @@ // pointing to a reference, we need to get its value directly (i.e. the // pointer to the actual value) instead of a pointer to the pointer to the // value. - bool IsReference = Decl->getType()->isReferenceType(); + bool IsReference; + + // Special case DecomposeDecls here. We need to check the decomposed decl + // for reference-ness, not the declaration the DeclRefExpr referes to. + if (const auto *BD = dyn_cast(Decl)) + IsReference = BD->getDecomposedDecl()->getType()->isReferenceType(); + else + IsReference = Decl->getType()->isReferenceType(); if (auto It = Locals.find(Decl); It != Locals.end()) { const unsigned Offset = It->second.Offset; Index: clang/lib/AST/Interp/ByteCodeStmtGen.cpp =================================================================== --- clang/lib/AST/Interp/ByteCodeStmtGen.cpp +++ clang/lib/AST/Interp/ByteCodeStmtGen.cpp @@ -211,17 +211,11 @@ template bool ByteCodeStmtGen::visitDeclStmt(const DeclStmt *DS) { for (auto *D : DS->decls()) { - // Variable declarator. - if (auto *VD = dyn_cast(D)) { - if (!this->visitVarDecl(VD)) - return false; - continue; - } - - // Decomposition declarator. - if (auto *DD = dyn_cast(D)) { - return this->bail(DD); - } + const auto *VD = dyn_cast(D); + if (!VD) + return false; + if (!this->visitVarDecl(VD)) + return false; } return true; Index: clang/lib/AST/Interp/Program.h =================================================================== --- clang/lib/AST/Interp/Program.h +++ clang/lib/AST/Interp/Program.h @@ -131,7 +131,9 @@ /// Context to manage declaration lifetimes. class DeclScope { public: - DeclScope(Program &P, const VarDecl *VD) : P(P) { P.startDeclaration(VD); } + DeclScope(Program &P, const ValueDecl *VD) : P(P) { + P.startDeclaration(VD); + } ~DeclScope() { P.endDeclaration(); } private: @@ -222,7 +224,7 @@ unsigned CurrentDeclaration = NoDeclaration; /// Starts evaluating a declaration. - void startDeclaration(const VarDecl *Decl) { + void startDeclaration(const ValueDecl *Decl) { LastDeclaration += 1; CurrentDeclaration = LastDeclaration; } Index: clang/test/AST/Interp/cxx17.cpp =================================================================== --- /dev/null +++ clang/test/AST/Interp/cxx17.cpp @@ -0,0 +1,79 @@ +// RUN: %clang_cc1 -fexperimental-new-constant-interpreter -std=c++17 -verify %s +// RUN: %clang_cc1 -std=c++17 -verify=ref %s + +// ref-no-diagnostics + +struct F { int a; int b;}; +constexpr F getF() { + return {12, 3}; +} +constexpr int f() { + auto [a1, b1] = getF(); + auto [a2, b2] = getF(); + + return a1 + a2 + b1 + b2; +} +static_assert(f() == 30); + + +constexpr int structRefs() { + const auto &[a, b] = getF(); + + return a + b; +} +// FIXME: This should work, but the MaterializeTemporaryExpr handling is not ready for it. +static_assert(structRefs() == 15); // expected-error {{not an integral constant expression}} + +constexpr int structRefs2() { + F f = getF(); + const auto &[a, b] = f; + + return a + b; +} +static_assert(structRefs2() == 15); + + +template +struct Tuple { + T first; + T second; + constexpr Tuple(T a, T b) : first(a), second(b) {} +}; +template +constexpr T addTuple(const Tuple &Tu) { + auto [a, b] = Tu; + return a + b; +} + +constexpr Tuple T1 = Tuple(1,2); +static_assert(addTuple(T1) == 3); + +constexpr Tuple T2 = Tuple(11,2); +static_assert(addTuple(T2) == 13); + + + + +constexpr int a() { + int a[2] = {5, 3}; + auto [x, y] = a; + return x + y; +} +static_assert(a() == 8); + + +constexpr int b() { + int a[2] = {5, 3}; + auto &[x, y] = a; + x += 1; + y += 2; + return a[0] + a[1]; +} +static_assert(b() == 11); + + +constexpr int vol() { + struct { + volatile int a + } F = {12}; +}