From 65db7256b937148d79df25dbdc1d328beb16dcc5 Mon Sep 17 00:00:00 2001 From: Faisal Vali Date: Thu, 16 Feb 2017 04:12:21 +0000 Subject: [PATCH] [cxx1z-constexpr-lambda] Implement captures - thus completing implementation of constexpr lambdas. Enable evaluation of captures within constexpr lambdas by using a strategy similar to that used in CodeGen: - when starting evaluation of a lambda's call operator, create a map from VarDecl's to a closure's FieldDecls - every time a VarDecl (or '*this) that represents a capture is encountered while evaluating the expression via the expression evaluator (specifically the LValueEvaluator) in ExprConstant.cpp - it is replaced by the corresponding FieldDecl LValue (an Lvalue-to-Rvalue conversion on this LValue representation then determines the right rvalue when needed). Thanks to Richard Smith and Hubert Tong for their review and feedback! https://reviews.llvm.org/D29748 git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@295279 91177308-0d34-0410-b5e6-96231b3b80d8 --- lib/AST/ExprConstant.cpp | 108 ++++++++++++++++++-- test/SemaCXX/cxx1z-constexpr-lambdas.cpp | 119 ++++++++++++++++++++--- 2 files changed, 209 insertions(+), 18 deletions(-) diff --git a/lib/AST/ExprConstant.cpp b/lib/AST/ExprConstant.cpp index dff126db0c..337eaa40b9 100644 --- a/lib/AST/ExprConstant.cpp +++ b/lib/AST/ExprConstant.cpp @@ -425,6 +425,17 @@ namespace { /// Index - The call index of this call. unsigned Index; + // FIXME: Adding this to every 'CallStackFrame' may have a nontrivial impact + // on the overall stack usage of deeply-recursing constexpr evaluataions. + // (We should cache this map rather than recomputing it repeatedly.) + // But let's try this and see how it goes; we can look into caching the map + // as a later change. + + /// LambdaCaptureFields - Mapping from captured variables/this to + /// corresponding data members in the closure class. + llvm::DenseMap LambdaCaptureFields; + FieldDecl *LambdaThisCaptureField; + CallStackFrame(EvalInfo &Info, SourceLocation CallLoc, const FunctionDecl *Callee, const LValue *This, APValue *Arguments); @@ -2279,6 +2290,10 @@ static bool HandleLValueComplexElement(EvalInfo &Info, const Expr *E, return true; } +static bool handleLValueToRValueConversion(EvalInfo &Info, const Expr *Conv, + QualType Type, const LValue &LVal, + APValue &RVal); + /// Try to evaluate the initializer for a variable declaration. /// /// \param Info Information about the ongoing evaluation. @@ -2290,6 +2305,7 @@ static bool HandleLValueComplexElement(EvalInfo &Info, const Expr *E, static bool evaluateVarDeclInit(EvalInfo &Info, const Expr *E, const VarDecl *VD, CallStackFrame *Frame, APValue *&Result) { + // If this is a parameter to an active constexpr function call, perform // argument substitution. if (const ParmVarDecl *PVD = dyn_cast(VD)) { @@ -4180,6 +4196,10 @@ static bool HandleFunctionCall(SourceLocation CallLoc, return false; This->moveInto(Result); return true; + } else if (MD && isLambdaCallOperator(MD)) { + // We're in a lambda; determine the lambda capture field maps. + MD->getParent()->getCaptureFields(Frame.LambdaCaptureFields, + Frame.LambdaThisCaptureField); } StmtResult Ret = {Result, ResultSlot}; @@ -5041,6 +5061,33 @@ bool LValueExprEvaluator::VisitDeclRefExpr(const DeclRefExpr *E) { bool LValueExprEvaluator::VisitVarDecl(const Expr *E, const VarDecl *VD) { + + // If we are within a lambda's call operator, check whether the 'VD' referred + // to within 'E' actually represents a lambda-capture that maps to a + // data-member/field within the closure object, and if so, evaluate to the + // field or what the field refers to. + if (Info.CurrentCall && isLambdaCallOperator(Info.CurrentCall->Callee)) { + if (auto *FD = Info.CurrentCall->LambdaCaptureFields.lookup(VD)) { + if (Info.checkingPotentialConstantExpression()) + return false; + // Start with 'Result' referring to the complete closure object... + Result = *Info.CurrentCall->This; + // ... then update it to refer to the field of the closure object + // that represents the capture. + if (!HandleLValueMember(Info, E, Result, FD)) + return false; + // And if the field is of reference type, update 'Result' to refer to what + // the field refers to. + if (FD->getType()->isReferenceType()) { + APValue RVal; + if (!handleLValueToRValueConversion(Info, E, FD->getType(), Result, + RVal)) + return false; + Result.setFrom(Info.Ctx, RVal); + } + return true; + } + } CallStackFrame *Frame = nullptr; if (VD->hasLocalStorage() && Info.CurrentCall->Index > 1) { // Only if a local variable was declared in the function currently being @@ -5440,6 +5487,27 @@ public: return false; } Result = *Info.CurrentCall->This; + // If we are inside a lambda's call operator, the 'this' expression refers + // to the enclosing '*this' object (either by value or reference) which is + // either copied into the closure object's field that represents the '*this' + // or refers to '*this'. + if (isLambdaCallOperator(Info.CurrentCall->Callee)) { + // Update 'Result' to refer to the data member/field of the closure object + // that represents the '*this' capture. + if (!HandleLValueMember(Info, E, Result, + Info.CurrentCall->LambdaThisCaptureField)) + return false; + // If we captured '*this' by reference, replace the field with its referent. + if (Info.CurrentCall->LambdaThisCaptureField->getType() + ->isPointerType()) { + APValue RVal; + if (!handleLValueToRValueConversion(Info, E, E->getType(), Result, + RVal)) + return false; + + Result.setFrom(Info.Ctx, RVal); + } + } return true; } @@ -6269,14 +6337,40 @@ bool RecordExprEvaluator::VisitLambdaExpr(const LambdaExpr *E) { if (ClosureClass->isInvalidDecl()) return false; if (Info.checkingPotentialConstantExpression()) return true; - if (E->capture_size()) { - Info.FFDiag(E, diag::note_unimplemented_constexpr_lambda_feature_ast) - << "can not evaluate lambda expressions with captures"; - return false; + + const size_t NumFields = + std::distance(ClosureClass->field_begin(), ClosureClass->field_end()); + + assert(NumFields == + std::distance(E->capture_init_begin(), E->capture_init_end()) && + "The number of lambda capture initializers should equal the number of " + "fields within the closure type"); + + Result = APValue(APValue::UninitStruct(), /*NumBases*/0, NumFields); + // Iterate through all the lambda's closure object's fields and initialize + // them. + auto *CaptureInitIt = E->capture_init_begin(); + const LambdaCapture *CaptureIt = ClosureClass->captures_begin(); + bool Success = true; + for (const auto *Field : ClosureClass->fields()) { + assert(CaptureInitIt != E->capture_init_end()); + // Get the initializer for this field + Expr *const CurFieldInit = *CaptureInitIt++; + + // If there is no initializer, either this is a VLA or an error has + // occurred. + if (!CurFieldInit) + return Error(E); + + APValue &FieldVal = Result.getStructField(Field->getFieldIndex()); + if (!EvaluateInPlace(FieldVal, Info, This, CurFieldInit)) { + if (!Info.keepEvaluatingAfterFailure()) + return false; + Success = false; + } + ++CaptureIt; } - // FIXME: Implement captures. - Result = APValue(APValue::UninitStruct(), /*NumBases*/0, /*NumFields*/0); - return true; + return Success; } static bool EvaluateRecord(const Expr *E, const LValue &This, diff --git a/test/SemaCXX/cxx1z-constexpr-lambdas.cpp b/test/SemaCXX/cxx1z-constexpr-lambdas.cpp index 16d5730d3d..4a98a1b06c 100644 --- a/test/SemaCXX/cxx1z-constexpr-lambdas.cpp +++ b/test/SemaCXX/cxx1z-constexpr-lambdas.cpp @@ -1,6 +1,6 @@ -// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks %s -// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks -fdelayed-template-parsing %s -// RUN: %clang_cc1 -std=c++14 -verify -fsyntax-only -fblocks %s -DCPP14_AND_EARLIER +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks %s -fcxx-exceptions +// RUN: %clang_cc1 -std=c++1z -verify -fsyntax-only -fblocks -fdelayed-template-parsing %s -fcxx-exceptions +// RUN: %clang_cc1 -std=c++14 -verify -fsyntax-only -fblocks %s -DCPP14_AND_EARLIER -fcxx-exceptions namespace test_lambda_is_literal { @@ -157,18 +157,115 @@ constexpr auto M = // expected-error{{must be initialized by}} } // end ns1_simple_lambda -namespace ns1_unimplemented { -namespace ns1_captures { +namespace test_captures_1 { +namespace ns1 { constexpr auto f(int i) { - double d = 3.14; - auto L = [=](auto a) { //expected-note{{coming soon}} - int Isz = i + d; - return sizeof(i) + sizeof(a) + sizeof(d); + struct S { int x; } s = { i * 2 }; + auto L = [=](auto a) { + return i + s.x + a; + }; + return L; +} +constexpr auto M = f(3); + +static_assert(M(10) == 19); + +} // end test_captures_1::ns1 + +namespace ns2 { + +constexpr auto foo(int n) { + auto L = [i = n] (auto N) mutable { + if (!N(i)) throw "error"; + return [&i] { + return ++i; + }; }; + auto M = L([n](int p) { return p == n; }); + M(); M(); + L([n](int p) { return p == n + 2; }); + return L; } -constexpr auto M = f(3); //expected-error{{constant expression}} expected-note{{in call to}} -} // end ns1_captures + +constexpr auto L = foo(3); + +} // end test_captures_1::ns2 +namespace ns3 { + +constexpr auto foo(int n) { + auto L = [i = n] (auto N) mutable { + if (!N(i)) throw "error"; + return [&i] { + return [i]() mutable { + return ++i; + }; + }; + }; + auto M = L([n](int p) { return p == n; }); + M()(); M()(); + L([n](int p) { return p == n; }); + + return L; +} + +constexpr auto L = foo(3); +} // end test_captures_1::ns3 + +namespace ns2_capture_this_byval { +struct S { + int s; + constexpr S(int s) : s{s} { } + constexpr auto f(S o) { + return [*this,o] (auto a) { return s + o.s + a.s; }; + } +}; + +constexpr auto L = S{5}.f(S{10}); +static_assert(L(S{100}) == 115); +} // end test_captures_1::ns2_capture_this_byval + +namespace ns2_capture_this_byref { + +struct S { + int s; + constexpr S(int s) : s{s} { } + constexpr auto f() const { + return [this] { return s; }; + } +}; + +constexpr S SObj{5}; +constexpr auto L = SObj.f(); +constexpr int I = L(); +static_assert(I == 5); + +} // end ns2_capture_this_byref + +} // end test_captures_1 + +namespace test_capture_array { +namespace ns1 { +constexpr auto f(int I) { + int arr[] = { I, I *2, I * 3 }; + auto L1 = [&] (auto a) { return arr[a]; }; + int r = L1(2); + struct X { int x, y; }; + return [=](auto a) { return X{arr[a],r}; }; +} +constexpr auto L = f(3); +static_assert(L(0).x == 3); +static_assert(L(0).y == 9); +static_assert(L(1).x == 6); +static_assert(L(1).y == 9); +} // end ns1 + +} // end test_capture_array +namespace ns1_test_lvalue_type { + void f() { + volatile int n; + constexpr bool B = [&]{ return &n; }() == &n; // should be accepted + } } // end ns1_unimplemented } // end ns test_lambda_is_cce -- 2.40.0