1 change: 1 addition & 0 deletions clang/include/clang/Basic/LangOptions.def
Original file line number Diff line number Diff line change
Expand Up @@ -286,6 +286,7 @@ LANGOPT(NewAlignOverride , 32, 0, "maximum alignment guaranteed by '::operator
BENIGN_LANGOPT(ModulesCodegen , 1, 0, "Modules code generation")
BENIGN_LANGOPT(ModulesDebugInfo , 1, 0, "Modules debug info")
BENIGN_LANGOPT(ElideConstructors , 1, 1, "C++ copy constructor elision")
LANGOPT(StaticInitLists , 1, 0, "P2752 static storage for braced initializer lists")
BENIGN_LANGOPT(DumpRecordLayouts , 1, 0, "dumping the layout of IRgen'd records")
BENIGN_LANGOPT(DumpRecordLayoutsSimple , 1, 0, "dumping the layout of IRgen'd records in a simple form")
BENIGN_LANGOPT(DumpRecordLayoutsCanonical , 1, 0, "dumping the AST layout of records using canonical field types")
Expand Down
4 changes: 4 additions & 0 deletions clang/include/clang/Driver/Options.td
Original file line number Diff line number Diff line change
Expand Up @@ -1537,6 +1537,10 @@ defm elide_constructors : BoolFOption<"elide-constructors",
LangOpts<"ElideConstructors">, DefaultTrue,
NegFlag<SetFalse, [CC1Option], "Disable C++ copy constructor elision">,
PosFlag<SetTrue>>;
defm static_init_lists : BoolFOption<"static-init-lists",
LangOpts<"StaticInitLists">, DefaultFalse,
NegFlag<SetFalse, [CC1Option], "Never store braced initializers in static storage">,
PosFlag<SetTrue, [CC1Option]>>;
def fno_elide_type : Flag<["-"], "fno-elide-type">, Group<f_Group>,
Flags<[CC1Option]>,
HelpText<"Do not elide types when printing diagnostics">,
Expand Down
2 changes: 2 additions & 0 deletions clang/lib/Driver/ToolChains/Clang.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -6464,6 +6464,8 @@ void Clang::ConstructJob(Compilation &C, const JobAction &JA,
options::OPT_fno_access_control);
Args.addOptOutFlag(CmdArgs, options::OPT_felide_constructors,
options::OPT_fno_elide_constructors);
Args.addOptInFlag(CmdArgs, options::OPT_fstatic_init_lists,
options::OPT_fno_static_init_lists);

ToolChain::RTTIMode RTTIMode = TC.getRTTIMode();

Expand Down
50 changes: 44 additions & 6 deletions clang/lib/Sema/SemaInit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
//
//===----------------------------------------------------------------------===//

#include "clang/AST/ASTConsumer.h"
#include "clang/AST/ASTContext.h"
#include "clang/AST/DeclObjC.h"
#include "clang/AST/ExprCXX.h"
Expand All @@ -19,6 +20,7 @@
#include "clang/Basic/CharInfo.h"
#include "clang/Basic/SourceManager.h"
#include "clang/Basic/TargetInfo.h"
#include "clang/Lex/Preprocessor.h"
#include "clang/Sema/Designator.h"
#include "clang/Sema/Initialization.h"
#include "clang/Sema/Lookup.h"
Expand Down Expand Up @@ -7706,7 +7708,11 @@ void Sema::checkInitializerLifetime(const InitializedEntity &Entity,
}

case LK_MemInitializer: {
if (isa<MaterializeTemporaryExpr>(L)) {
auto *DRE = dyn_cast<DeclRefExpr>(L);
auto *VD = DRE ? dyn_cast<VarDecl>(DRE->getDecl()) : nullptr;
bool IsBackingArray = isa<MaterializeTemporaryExpr>(L) ||
(VD && VD->getName().startswith("__il"));
if (IsBackingArray) {
// Under C++ DR1696, if a mem-initializer (or a default member
// initializer used by the absence of one) would lifetime-extend a
// temporary, the program is ill-formed.
Expand Down Expand Up @@ -8804,18 +8810,50 @@ ExprResult InitializationSequence::Perform(Sema &S,
diag::warn_cxx98_compat_initializer_list_init)
<< CurInit.get()->getSourceRange();

DeclContext *DC = S.CurContext->getEnclosingNamespaceContext();

auto BuildStaticBackingArrayVarDecl = [&](SourceLocation Loc, QualType Type, StringRef Name) {
IdentifierInfo *II = &S.PP.getIdentifierTable().get(Name);
TypeSourceInfo *TInfo = S.Context.getTrivialTypeSourceInfo(Type, Loc);
VarDecl *VD = VarDecl::Create(S.Context, DC, Loc, Loc, II, Type, TInfo, SC_Static);
VD->setImplicit();
return VD;
};

// Materialize the temporary into memory.
MaterializeTemporaryExpr *MTE = S.CreateMaterializeTemporaryExpr(
CurInit.get()->getType(), CurInit.get(),
/*BoundToLvalueReference=*/false);
Expr *MTE = [&](QualType AT, Expr *Init) -> Expr* {
if (!S.getLangOpts().StaticInitLists) {
// eschew the optimization entirely
} else if (S.CurContext->isDependentContext()) {
// don't do anything until substitution time
} else if (!AT.isDestructedType()) {
// The backing array's name is static, so it needn't be unique
// except within this translation unit.
static int Count = 0;
VarDecl *VD = BuildStaticBackingArrayVarDecl(
Init->getExprLoc(), AT,
std::string("__il") + std::to_string(Count++)
);
S.AddInitializerToDecl(VD, Init, /*DirectInit=*/false);
S.FinalizeDeclaration(VD);
if (VD->hasConstantInitialization()) {
VD->setConstexpr(true); // so the initializer_list will be constexpr-iterable if need be
DC->addHiddenDecl(VD);
S.Consumer.HandleTopLevelDecl(DeclGroupRef(VD));
return S.BuildDeclRefExpr(VD, AT, VK_LValue, Init->getExprLoc());
} else {
VD->setInvalidDecl(); // so it won't be emitted
}
}
return S.CreateMaterializeTemporaryExpr(AT, Init, /*BoundToLvalueReference=*/false);
}(CurInit.get()->getType(), CurInit.get());

// Wrap it in a construction of a std::initializer_list<T>.
CurInit = new (S.Context) CXXStdInitializerListExpr(Step->Type, MTE);

// Bind the result, in case the library has given initializer_list a
// non-trivial destructor.
if (shouldBindAsTemporary(Entity))
CurInit = S.MaybeBindToTemporary(CurInit.get());
assert(!shouldBindAsTemporary(Entity));
break;
}

Expand Down
93 changes: 93 additions & 0 deletions clang/test/SemaCXX/static-init-list-regressions.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// RUN: %clang_cc1 -std=c++17 -Wunused-const-variable -fstatic-init-lists -fsyntax-only -verify %s
// RUN: %clang_cc1 -std=c++17 -Wunused-const-variable -fno-static-init-lists -fsyntax-only -verify %s
// expected-no-diagnostics

namespace std {
typedef decltype(sizeof(int)) size_t;

// libc++'s implementation
template <class _E>
class initializer_list {
const _E* __begin_;
size_t __size_;

initializer_list(const _E* __b, size_t __s)
: __begin_(__b),
__size_(__s)
{}

public:
typedef _E value_type;
typedef const _E& reference;
typedef const _E& const_reference;
typedef size_t size_type;

typedef const _E* iterator;
typedef const _E* const_iterator;

constexpr initializer_list() : __begin_(nullptr), __size_(0) {}

constexpr size_t size() const {return __size_;}
constexpr const _E* begin() const {return __begin_;}
constexpr const _E* end() const {return __begin_ + __size_;}
};
}

void simple(int x) {
(void)std::initializer_list<int>{ 1, 2, 3 };
(void)std::initializer_list<int>{ 1, 2, 3 };
(void)std::initializer_list<int>{ 1, x, 3 };
}
template<class T>
void templated(int x) {
(void)std::initializer_list<int>{ 1, 2, 3 };
(void)std::initializer_list<T>{ 1, 2, 3 };
(void)std::initializer_list<int>{ T(), T(), T() };
(void)std::initializer_list<T>{ T(), T(), T() };
}
template void templated<int>(int);
template void templated<short>(int);

struct S1 {
std::initializer_list<int> il_ = {1, 2, 3};
S1();
};
S1::S1() { }

struct S2 {
std::initializer_list<int> il_;
S2();
};
S2::S2() : il_{1, 2, 3} { }

struct A {
int i_;
constexpr A(int i) : i_(i) { }
};
void constexpr_construction_test() {
(void)std::initializer_list<A>{ 1, 2, 3 };
}

constexpr int use_il_in_constexpr(std::initializer_list<int> il) {
int sum = 0;
for (int i : il) {
sum += i;
}
return sum;
}
static_assert(use_il_in_constexpr({1,2,3}) == 6);

struct E {
E(std::initializer_list<int>);
};

auto sfinae_context() -> decltype(E{1,2,3}) {
return E{1,2,3};
}

template<class T>
auto sfinae_context_templated() -> decltype(T(1), E{1,2,3}) {
return E{1,2,3};
}

template E sfinae_context_templated<int>();