We currently ICE upon the following valid (under -Wno-vla) code === cut here === void f(int c) { constexpr int r = 4; [&](auto) { int t[r * c]; }(0); } === cut here ===
The problem is that when parsing the lambda body, and more specifically the multiplication, we mark the lambda as LAMBDA_EXPR_CAPTURE_OPTIMIZED even though the replacement of r by 4 is "undone" by the call to build_min_non_dep in build_x_binary_op. This makes prune_lambda_captures remove the proxy declaration while it should not, and we trip on an assert at instantiation time. This patch fixes the ICE by making sure that lambdas are only marked as LAMBDA_EXPR_CAPTURE_OPTIMIZED when they're instantiated (I tried other strategies like not undoing constant folding in build_min_non_dep, but it is pretty intrusive and breaks lots of things). The test I added also shows that we don't always optimize out captures to constants for lambdas that are not within a template (see ok_2 for example, or ok_3 that unlike ok_2 "regresses" a bit with my patch) - I'm curious if we consider it a problem or not? If so, I can try to fix this in a follow-up patch. Successfully tested on x86_64-pc-linux-gnu. PR c++/114292 gcc/cp/ChangeLog: * cp-tree.h (mark_const_var_capture_optimized): Declare. * expr.cc (mark_use): Call mark_const_var_capture_optimized. * lambda.cc (mark_const_var_capture_optimized): New. Only set LAMBDA_EXPR_CAPTURE_OPTIMIZED at lambda instantiation time. gcc/testsuite/ChangeLog: * g++.dg/cpp1y/lambda-ice4.C: New test. --- gcc/cp/cp-tree.h | 1 + gcc/cp/expr.cc | 10 ++---- gcc/cp/lambda.cc | 13 +++++++ gcc/testsuite/g++.dg/cpp1y/lambda-ice4.C | 44 ++++++++++++++++++++++++ 4 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp1y/lambda-ice4.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index c5e0fc5c440..ce050032fdb 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -8058,6 +8058,7 @@ extern bool is_constant_capture_proxy (tree); extern void register_capture_members (tree); extern tree lambda_expr_this_capture (tree, int); extern void maybe_generic_this_capture (tree, tree); +extern void mark_const_var_capture_optimized (void); extern tree maybe_resolve_dummy (tree, bool); extern tree current_nonlambda_function (void); extern tree nonlambda_method_basetype (void); diff --git a/gcc/cp/expr.cc b/gcc/cp/expr.cc index de4991e616c..d6a2454c46e 100644 --- a/gcc/cp/expr.cc +++ b/gcc/cp/expr.cc @@ -120,10 +120,7 @@ mark_use (tree expr, bool rvalue_p, bool read_p, { tree val = RECUR (cap); if (!is_capture_proxy (val)) - { - tree l = current_lambda_expr (); - LAMBDA_EXPR_CAPTURE_OPTIMIZED (l) = true; - } + mark_const_var_capture_optimized (); return val; } } @@ -171,10 +168,7 @@ mark_use (tree expr, bool rvalue_p, bool read_p, { tree val = RECUR (cap); if (!is_capture_proxy (val)) - { - tree l = current_lambda_expr (); - LAMBDA_EXPR_CAPTURE_OPTIMIZED (l) = true; - } + mark_const_var_capture_optimized (); return val; } } diff --git a/gcc/cp/lambda.cc b/gcc/cp/lambda.cc index d8a15d97d5d..4fd3b39c99b 100644 --- a/gcc/cp/lambda.cc +++ b/gcc/cp/lambda.cc @@ -945,6 +945,19 @@ resolvable_dummy_lambda (tree object) return NULL_TREE; } +/* Called when optimizing out a capture to a const variable. */ + +void +mark_const_var_capture_optimized () +{ + /* The actual optimizing out only occurs when instantiating the lambda. */ + if (processing_template_decl) + return; + + tree l = current_lambda_expr (); + LAMBDA_EXPR_CAPTURE_OPTIMIZED (l) = true; +} + /* We don't want to capture 'this' until we know we need it, i.e. after overload resolution has chosen a non-static member function. At that point we call this function to turn a dummy object into a use of the diff --git a/gcc/testsuite/g++.dg/cpp1y/lambda-ice4.C b/gcc/testsuite/g++.dg/cpp1y/lambda-ice4.C new file mode 100644 index 00000000000..fe8df52b827 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp1y/lambda-ice4.C @@ -0,0 +1,44 @@ +// PR c++/114292 +// { dg-do "compile" { target c++14 } } +// { dg-additional-options "-Wno-vla" } + +#define ASSERT_CAPTURE_NUMBER(Lambda, NumCaptures) \ + { \ + auto oneCapture = [&](auto) { int t[c]; }; \ + const auto sizeOneCapture = sizeof (oneCapture); \ + const auto expected = NumCaptures ? NumCaptures * sizeOneCapture : 1; \ + static_assert (sizeof (Lambda) == expected, ""); \ + } + +void foo (int c) +{ + constexpr int r = 4; + + // This used to ICE. + [&](auto) { int t[c * r]; }(0); + + // All those worked already, but were not covered by any test - do it here. + auto ok_1 = [&](auto) { int t[c]; }; + ok_1 (0); + ASSERT_CAPTURE_NUMBER (ok_1, 1); + + auto ok_2 = [&](auto) { int n = r * c; int t[n]; }; + ok_2 (0); + ASSERT_CAPTURE_NUMBER (ok_2, 2); + + auto ok_3 = [&](auto) { int t[r]; }; + ok_3 (0); + ASSERT_CAPTURE_NUMBER (ok_3, 1); // Was 0 before the patch. + + auto ok_4 = [&](auto) { int t[c * 4]; }; + ok_4 (0); + ASSERT_CAPTURE_NUMBER (ok_4, 1); + + auto ok_5 = [&](auto) { int t[1]; }; + ok_5 (0); + ASSERT_CAPTURE_NUMBER (ok_5, 0); + + auto ok_6 = [&](auto) { int t[c * r]; }; + ok_6 (0); + ASSERT_CAPTURE_NUMBER (ok_6, 2); +} -- 2.44.0