[PATCH, constexpr, coroutines ] Generic lambda coroutines cannot be constexpr [PR96251].

Jason Merrill jason@redhat.com
Tue Feb 23 02:23:03 GMT 2021


On 2/22/21 3:59 PM, Iain Sandoe wrote:
> Hi,
> 
> PR96251 (and dups) is a rejects-valid bug against coroutines (but I’m
> not sure that the problem is really there).
> 
>   * coroutines cannot be constexpr.
> 
> * Part of the way that the coroutine implementation works is a
> consequence of the "lazy discovery of coroutine-ness”;  whenever
> we first encounter a coroutine keyword...
> 
> .. we mark the function as a coroutine, and then we can deal with
> diagnostics etc. that change under these circumstances.  This
> marking of the function is independent of whether keyword
> expressions are type-dependent or not.
> 
> * when we encounter a coroutine keyword in a constexpr context
> we error.
> 
> * the constexpr machinery also has been taught that coroutine
> expressions should cause constexpr-ness to be rejected when
> checking for "potentially constexpr".
> 
> So why is there a problem?
> 
> * lambdas are implicitly constexpr from C++17.
> 
> * the constexpr machinery tends to bail early *with a conservative
>    assumption that the expression is potentially constexpr* when it
>    finds a type-dependent expression - without evaluating sub-
>    expressions to see if they are valid (thus missing coroutine exprs.
>    in such positions).
> 
> The combination of these ^^ two things, means that for many generic
> lambdas with non-trivial bodies - we then enter instantiation with a
> constexpr type-dependent coroutine (which is a Thing that Should not
> Be).  As soon as we try to tsub any coroutine keyword expression
> encountered, we error out.
> 
> * I was not able to see any way in which the instantiation process
>    could be made to bail in this case and re-try for non-constexpr.

Many of the other places that set cp_function_chain->invalid_constexpr 
condition their errors on !is_instantiation_of_constexpr, which should 
also fix this testcase.

> * Nor able to see somewhere else where that decision could be made.
> 
> ^^ these two points reflect that I'm not familiar with the constexpr
>      machinery.
> 
> * Proposed solution.
> 
> Since coroutines cannot be constexpr (not even sure what that would
> mean in any practicable implementation).  The fix proposed is to
> reject constexpr-ness for coroutine functions as early as possible.
> 
> * a) We can prevent a generic lambda coroutine from being converted
>    to constexpr in finish_function ().  Likewise, via the tests below, 
> we can
>    avoid it for regular lambdas.
> 
>    b) We can also reject coroutine function decls early in both
>    is_valid_constexpr_fn () and potential_constant_expression_1 ().
> 
> So - is there some alternate solution that would be better?
> 
> ====
> 
> (in some ways it seems pointless to delay rejection of a coroutine
>   function to some later point).
> 
> OTOH, I suppose that one could have some weird code where coroutine
> coroutine keywords only appeared in a non-constexpr paths of the code
> - but our current lowering is not prepared for such a circumstance.
> 
> AFAIU the standard, there's no dispensation from the "cannot be
> constexpr" for such a code construction .. but ICBW.
> 
> In any event, the cases reported (and fixed by this patch) are not trying
> anything so fiendishly clever.
> 
> Tested on x86_64-darwin.
> 
> Suggestions?

> OK for master (after wider testing)?
> thanks
> Iain
> 
> = ----
> 
> coroutines cannot be constexpr, but generic lambdas (from C++17)
> are implicitly assumed to be, and the processing of type-
> dependent lambda bodies often terminates before any coroutine
> expressions are encountered.  For example, the PR notes cases
> where the coro expressions are hidden by type-dependent for or
> switch expressions.
> 
> The solution proposed is to deny coroutine generic lambdas.  We also
> tighten up the checks for is_valid_constexpr_fn() and do an early
> test for coroutine functions in checking for potential constant
> expressions.
> 
> gcc/cp/ChangeLog:
> 
>      PR c++/96251
>      * constexpr.c (is_valid_constexpr_fn): Test for a
>      coroutine before the chekc for lambda.
>      (potential_constant_expression_1): Disallow coroutine
>      function decls.
>      * decl.c (finish_function): Do not mark generic
>      coroutine lambda templates as constexpr.
> 
> gcc/testsuite/ChangeLog:
> 
>      PR c++/96251
>      * g++.dg/coroutines/pr96251.C: New test.
> ---
>   gcc/cp/constexpr.c                        |  7 ++++++-
>   gcc/cp/decl.c                             |  2 +-
>   gcc/testsuite/g++.dg/coroutines/pr96251.C | 22 ++++++++++++++++++++++
>   3 files changed, 29 insertions(+), 2 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/coroutines/pr96251.C
> 
> diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
> index 377fe322ee8..036bf04efa9 100644
> --- a/gcc/cp/constexpr.c
> +++ b/gcc/cp/constexpr.c
> @@ -210,7 +210,9 @@ is_valid_constexpr_fn (tree fun, bool complain)
>         }
>       }
> 
> -  if (LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun)) && cxx_dialect < cxx17)
> +  if (DECL_COROUTINE_P (fun))
> +    ret = false;
> +  else if (LAMBDA_TYPE_P (CP_DECL_CONTEXT (fun)) && cxx_dialect < cxx17)
>       {
>         ret = false;
>         if (complain)
> @@ -7827,6 +7829,9 @@ potential_constant_expression_1 (tree t, bool 
> want_rval, bool strict, bool now,
>     switch (TREE_CODE (t))
>       {
>       case FUNCTION_DECL:
> +      if (DECL_COROUTINE_P (t))
> +    return false;
> +    /* FALLTHROUGH.  */
>       case BASELINK:
>       case TEMPLATE_DECL:
>       case OVERLOAD:
> diff --git a/gcc/cp/decl.c b/gcc/cp/decl.c
> index 7fa8f52d667..3a089b5bee5 100644
> --- a/gcc/cp/decl.c
> +++ b/gcc/cp/decl.c
> @@ -17374,7 +17374,7 @@ finish_function (bool inline_p)
>     if (cxx_dialect >= cxx17
>         && LAMBDA_TYPE_P (CP_DECL_CONTEXT (fndecl)))
>       DECL_DECLARED_CONSTEXPR_P (fndecl)
> -      = ((processing_template_decl
> +      = (((processing_template_decl && !DECL_COROUTINE_P(fndecl))
>         || is_valid_constexpr_fn (fndecl, /*complain*/false))
>        && potential_constant_expression (DECL_SAVED_TREE (fndecl)));
> 
> diff --git a/gcc/testsuite/g++.dg/coroutines/pr96251.C 
> b/gcc/testsuite/g++.dg/coroutines/pr96251.C
> new file mode 100644
> index 00000000000..6824d783d5f
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/coroutines/pr96251.C
> @@ -0,0 +1,22 @@
> +#include <coroutine>
> +
> +struct coroutine {
> +  struct promise_type {
> +    auto get_return_object() { return coroutine(); }
> +    auto initial_suspend() { return std::suspend_always(); }
> +    auto yield_value(int) { return std::suspend_always(); }
> +    void return_void() {}
> +    auto final_suspend() noexcept { return std::suspend_always(); }
> +    void unhandled_exception() {}
> +  };
> +};
> +
> +int main() {
> +  auto f = [](auto max) -> coroutine {
> +    for (int i = 0; i < max; ++i) {
> +       co_yield i;
> +    }
> +  };
> +
> +  f(10);
> +}



More information about the Gcc-patches mailing list