[gcc r10-9240] c++: decl_constant_value and unsharing [PR96197]

Patrick Palka ppalka@gcc.gnu.org
Sat Jan 9 03:52:22 GMT 2021


https://gcc.gnu.org/g:afe708223f0bfffe688674659f7a71c5130f01d1

commit r10-9240-gafe708223f0bfffe688674659f7a71c5130f01d1
Author: Patrick Palka <ppalka@redhat.com>
Date:   Thu Jul 30 22:21:41 2020 -0400

    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.
    
    (cherry picked from commit 8c00059ce058ea2aec2933319e270f5443b8b909)

Diff:
---
 gcc/cp/constexpr.c                            |  4 ++--
 gcc/cp/cp-tree.h                              |  3 ++-
 gcc/cp/init.c                                 | 34 ++++++++++++++++++---------
 gcc/testsuite/g++.dg/cpp1y/constexpr-array8.C | 18 ++++++++++++++
 4 files changed, 45 insertions(+), 14 deletions(-)

diff --git a/gcc/cp/constexpr.c b/gcc/cp/constexpr.c
index b7d81f9e4b3..c5051c020c5 100644
--- a/gcc/cp/constexpr.c
+++ b/gcc/cp/constexpr.c
@@ -5678,9 +5678,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);
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 3a6632b9b97..82716d33c49 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -6780,7 +6780,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);
diff --git a/gcc/cp/init.c b/gcc/cp/init.c
index e91d0037f9b..4ad6c3db99f 100644
--- a/gcc/cp/init.c
+++ b/gcc/cp/init.c
@@ -2273,10 +2273,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)
@@ -2344,9 +2346,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
@@ -2358,26 +2360,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)
+decl_constant_value (tree decl, bool unshare_p)
 {
   return constant_value_1 (decl, /*strict_p=*/processing_template_decl,
-			   /*return_aggregate_cst_ok_p=*/true);
+			   /*return_aggregate_cst_ok_p=*/true,
+			   /*unshare_p=*/unshare_p);
+}
+
+tree
+decl_constant_value (tree decl)
+{
+  return decl_constant_value (decl, /*unshare_p=*/true);
 }
 

 /* Common subroutines of build_new and build_vec_delete.  */
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-array8.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-array8.C
new file mode 100644
index 00000000000..339abb69019
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-array8.C
@@ -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();


More information about the Gcc-cvs mailing list