c++: decl_constant_value and unsharing [PR96197]

In the testcase from the PR we're seeing excessive memory use (> 5GB)
during constexpr evaluation, almost all of which is due to the call to
decl_constant_value in the VAR_DECL/CONST_DECL branch of
cxx_eval_constant_expression.  We reach here every time we evaluate an
ARRAY_REF of a constexpr VAR_DECL, and from there decl_constant_value
makes an unshared copy of the VAR_DECL's initializer.  But unsharing
here is unnecessary because callers of cxx_eval_constant_expression
already unshare its result when necessary.

To fix this excessive unsharing, this patch adds a new defaulted
parameter unshare_p to decl_really_constant_value and
decl_constant_value so that callers can control whether to unshare.

As a simplification, we can also move the call to unshare_expr in
constant_value_1 outside of the loop, since doing unshare_expr on a
DECL_P is a no-op.

Now that we no longer unshare the result of decl_constant_value and
decl_really_constant_value from cxx_eval_constant_expression, memory use
during constexpr evaluation for the testcase from the PR falls from ~5GB
to 15MB according to -ftime-report.

gcc/cp/ChangeLog:

	PR c++/96197
	* constexpr.c (cxx_eval_constant_expression) <case CONST_DECL>:
	Pass false to decl_constant_value and decl_really_constant_value
	so that they don't unshare their result.
	* cp-tree.h (decl_constant_value): New declaration with an added
	bool parameter.
	(decl_really_constant_value): Add bool parameter defaulting to
	true to existing declaration.
	* init.c (constant_value_1): Add bool parameter which controls
	whether to unshare the initializer before returning.  Call
	unshare_expr at most once.
	(scalar_constant_value): Pass true to constant_value_1's new
	bool parameter.
	(decl_really_constant_value): Add bool parameter and forward it
	to constant_value_1.
	(decl_constant_value): Likewise, but instead define a new
	overload with an added bool parameter.

gcc/testsuite/ChangeLog:

	PR c++/96197
	* g++.dg/cpp1y/constexpr-array8.C: New test.
This commit is contained in:
Patrick Palka 2020-07-30 22:21:41 -04:00
parent d48cca8f21
commit 8c00059ce0
4 changed files with 45 additions and 14 deletions

View file

@ -5695,9 +5695,9 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
TREE_CONSTANT (r) = true;
}
else if (ctx->strict)
r = decl_really_constant_value (t);
r = decl_really_constant_value (t, /*unshare_p=*/false);
else
r = decl_constant_value (t);
r = decl_constant_value (t, /*unshare_p=*/false);
if (TREE_CODE (r) == TARGET_EXPR
&& TREE_CODE (TARGET_EXPR_INITIAL (r)) == CONSTRUCTOR)
r = TARGET_EXPR_INITIAL (r);

View file

@ -6773,7 +6773,8 @@ extern tree build_vec_delete (location_t, tree, tree,
extern tree create_temporary_var (tree);
extern void initialize_vtbl_ptrs (tree);
extern tree scalar_constant_value (tree);
extern tree decl_really_constant_value (tree);
extern tree decl_constant_value (tree, bool);
extern tree decl_really_constant_value (tree, bool = true);
extern int diagnose_uninitialized_cst_or_ref_member (tree, bool, bool);
extern tree build_vtbl_address (tree);
extern bool maybe_reject_flexarray_init (tree, tree);

View file

@ -2272,10 +2272,12 @@ build_offset_ref (tree type, tree member, bool address_p,
recursively); otherwise, return DECL. If STRICT_P, the
initializer is only returned if DECL is a
constant-expression. If RETURN_AGGREGATE_CST_OK_P, it is ok to
return an aggregate constant. */
return an aggregate constant. If UNSHARE_P, return an unshared
copy of the initializer. */
static tree
constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p)
constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p,
bool unshare_p)
{
while (TREE_CODE (decl) == CONST_DECL
|| decl_constant_var_p (decl)
@ -2343,9 +2345,9 @@ constant_value_1 (tree decl, bool strict_p, bool return_aggregate_cst_ok_p)
&& !DECL_INITIALIZED_BY_CONSTANT_EXPRESSION_P (decl)
&& DECL_NONTRIVIALLY_INITIALIZED_P (decl))
break;
decl = unshare_expr (init);
decl = init;
}
return decl;
return unshare_p ? unshare_expr (decl) : decl;
}
/* If DECL is a CONST_DECL, or a constant VAR_DECL initialized by constant
@ -2357,26 +2359,36 @@ tree
scalar_constant_value (tree decl)
{
return constant_value_1 (decl, /*strict_p=*/true,
/*return_aggregate_cst_ok_p=*/false);
/*return_aggregate_cst_ok_p=*/false,
/*unshare_p=*/true);
}
/* Like scalar_constant_value, but can also return aggregate initializers. */
/* Like scalar_constant_value, but can also return aggregate initializers.
If UNSHARE_P, return an unshared copy of the initializer. */
tree
decl_really_constant_value (tree decl)
decl_really_constant_value (tree decl, bool unshare_p /*= true*/)
{
return constant_value_1 (decl, /*strict_p=*/true,
/*return_aggregate_cst_ok_p=*/true);
/*return_aggregate_cst_ok_p=*/true,
/*unshare_p=*/unshare_p);
}
/* A more relaxed version of scalar_constant_value, used by the
/* A more relaxed version of decl_really_constant_value, used by the
common C/C++ code. */
tree
decl_constant_value (tree decl, bool unshare_p)
{
return constant_value_1 (decl, /*strict_p=*/processing_template_decl,
/*return_aggregate_cst_ok_p=*/true,
/*unshare_p=*/unshare_p);
}
tree
decl_constant_value (tree decl)
{
return constant_value_1 (decl, /*strict_p=*/processing_template_decl,
/*return_aggregate_cst_ok_p=*/true);
return decl_constant_value (decl, /*unshare_p=*/true);
}
/* Common subroutines of build_new and build_vec_delete. */

View file

@ -0,0 +1,18 @@
// PR c++/96197
// { dg-do compile { target c++14 } }
struct S {
S* p = this;
};
constexpr S ary[5000] = {};
constexpr int foo() {
int count = 0;
for (int i = 0; i < 5000; i++)
if (ary[i].p != nullptr)
count++;
return count;
}
constexpr int bar = foo();