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);
```