Skip to content

Commit

Permalink
[cxx1z-constexpr-lambda] Implement captures - thus completing impleme…
Browse files Browse the repository at this point in the history
…ntation 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

llvm-svn: 295279
  • Loading branch information
faisalv committed Feb 16, 2017
1 parent da5cc84 commit 051e3a2
Show file tree
Hide file tree
Showing 2 changed files with 209 additions and 18 deletions.
108 changes: 101 additions & 7 deletions clang/lib/AST/ExprConstant.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<const VarDecl *, FieldDecl *> LambdaCaptureFields;
FieldDecl *LambdaThisCaptureField;

CallStackFrame(EvalInfo &Info, SourceLocation CallLoc,
const FunctionDecl *Callee, const LValue *This,
APValue *Arguments);
Expand Down Expand Up @@ -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.
Expand All @@ -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<ParmVarDecl>(VD)) {
Expand Down Expand Up @@ -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};
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -5440,6 +5487,27 @@ class PointerExprEvaluator
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;
}

Expand Down Expand Up @@ -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,
Expand Down
119 changes: 108 additions & 11 deletions clang/test/SemaCXX/cxx1z-constexpr-lambdas.cpp
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 051e3a2

Please sign in to comment.