https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127629

--- Comment #4 from Ivan Lazaric <ivan.lazaric.gcc at gmail dot com> ---
An especially egregious example:
```cpp
template<typename T, unsigned N>
struct array {
  T elems[N];
  constexpr unsigned size() const { return N; }
  constexpr T& operator[](unsigned n) { return elems[n]; }
  constexpr const T& operator[](unsigned n) const { return elems[n]; }
};

constexpr unsigned N = 300;

constexpr auto storage = [] {
  array<array<unsigned, N>, N> out{};
  out[0][0] = 1;
  for (unsigned i = 1; i < N; ++i) out[i][0] = out[0][i] = 1;
  for (unsigned i = 1; i < N; ++i)
    for (unsigned j = 1; j < N; ++j) out[i][j] = out[i - 1][j] + out[i][j - 1];
  if not consteval { throw; }
  return out;
}();

static_assert(storage[2][2] == 6);
```

Compiled with "-O3 -fsyntax-only", it takes ~8.2s for me.
If the lambda is changed to accept a single defaulted param
(`[] (int x = 1) { ... }`), it takes ~1.7s to compile.


Explored codebase a bit, relevant difference between -O0 and others
is `flag_no_inline`, it's 1 for -O0, and 0 for others.
One place where it crops up:
```cpp
// gcc/cp/cp-gimplify.cc:cp_fold()
        /* Invoke maybe_constant_value for functions declared
           constexpr and not called with AGGR_INIT_EXPRs.
           TODO:
           Do constexpr expansion of expressions where the call itself is not
           constant, but the call followed by an INDIRECT_REF is.  */
        if (callee && DECL_DECLARED_CONSTEXPR_P (callee)
            && !(flags & ff_only_non_odr)
            && (!flag_no_inline
                || lookup_attribute ("always_inline",
                                     DECL_ATTRIBUTES (callee))))
          {
            mce_value manifestly_const_eval = mce_unknown;
            if (flags & ff_mce_false)
              /* Allow folding __builtin_is_constant_evaluated to false during
                 constexpr evaluation of this call.  */
              manifestly_const_eval = mce_false;
            r = maybe_constant_value (x, /*decl=*/NULL_TREE,
                                      manifestly_const_eval);
          }
```

There it kicks off a constexpr evaluation of the lambda,
but one where `is_constant_evaluated()` is false,
and on a rather strange form of the lambda.
Printing the expression, it is `0-><lambda()>()`.
Whereas when the lambda is evaluated properly,
the expression looks more like
`<lambda closure object><lambda()>().<lambda()>()`.


Played around a bit with the code, got something
to pass tests and show perf improvement on various cases:
```
diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 61d7a104c6e..423a780ce35 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -43,6 +43,23 @@ along with GCC; see the file COPYING3.  If not see
 #include "toplev.h"
 #include "contracts.h"

+static bool ivl_bad(tree t) {
+  if (t == NULL_TREE || t == error_mark_node) return false;
+  if (TREE_CODE(t) == TARGET_EXPR)
+    return ivl_bad(TARGET_EXPR_INITIAL(t));
+  if (TREE_CODE(t) == CALL_EXPR) {
+    auto c = get_callee_fndecl(t);
+    if (c != NULL_TREE &&
+       c != error_mark_node &&
+       LAMBDA_FUNCTION_P(c) &&
+       call_expr_nargs(t) >= 1 &&
+       TREE_CODE(CALL_EXPR_ARG(t, 0)) == INTEGER_CST) {
+      return true;
+    }
+  }
+  return false;
+}
+
 static bool verify_constant (tree, bool, bool *, bool *);
 #define VERIFY_CONSTANT(X)                                             \
 do {                                                                   \
@@ -11605,6 +11622,7 @@ cxx_eval_outermost_constant_expr (tree t, bool
allow_non_constant,
                                  bool constexpr_dtor = false,
                                  tree object = NULL_TREE)
 {
+  if (ivl_bad(t)) return t;
   auto_timevar time (TV_CONSTEXPR);

   bool non_constant_p = false;
@@ -12112,6 +12130,8 @@ tree
 maybe_constant_value (tree t, tree decl /* = NULL_TREE */,
                      mce_value manifestly_const_eval /* = mce_unknown */)
 {
+  if (ivl_bad(t)) return t;
+
   tree orig_t = t;
   tree r;

@@ -12357,6 +12377,8 @@ static tree
 maybe_constant_init_1 (tree t, tree decl, bool allow_non_constant,
                       mce_value manifestly_const_eval)
 {
+  if (ivl_bad(t)) return t;
+
   if (!t)
     return t;
   if (TREE_CODE (t) == EXPR_STMT)
```

With the patch I am not saying it is good to merge,
nor that it is a good direction for resolving this,
but merely as a source of inspiration for someone
more knowledgeable than me.
I also have no idea if it causes performance regressions
in the compiled binaries.
The idea behind the patch was:
Expression `0->lambda` looks like an intermediate state,
as if the proper lambda expression has not been completed yet,
so just reject to evaluate such forms.
(such form == lambda function applied to an INTEGER_CST)


I am also noting a comment preceding the evaluation
of the `0->lambda` form with "not constant evaluated" semantics:
```cpp
// gcc/cp/cp-gimplify.cc:cp_fold_function()
  /* By now all manifestly-constant-evaluated expressions will have
     been constant-evaluated already if possible, so we can safely
     pass ff_mce_false.  */
  cp_fold_data data (ff_genericize | ff_mce_false);
```

Reply via email to