]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++: instantiate less for constant folding
authorJason Merrill <jason@redhat.com>
Thu, 14 Oct 2021 02:04:53 +0000 (22:04 -0400)
committerJason Merrill <jason@redhat.com>
Fri, 15 Oct 2021 01:40:11 +0000 (21:40 -0400)
I've been experimenting with a change to make all inline functions
implicitly constexpr; this revealed that we are instantiating too
aggressively for speculative constant evaluation, leading to ordering
difficulties with e.g. is_a_helper<cgraph_node*>::test.  This patch tries to
avoid such instantiation until we actually need the function definition to
determine whether a call is constant, by limiting the initial instantiation
of all used functions to manifestly-constant-evaluated expressions, and
checking whether the function arguments are constant before instantiating
the function.

This change resulted in a change in the diagnostics for a few library tests
due to instantiating the function with the static_assert later (during
constant evaluation) than we did before (during instantiation of the
intermediate function).

gcc/cp/ChangeLog:

* constexpr.c (cxx_bind_parameters_in_call): Replace
new_call parameter with fun.
(cxx_eval_call_expression): Call it before instantiation.
(cxx_eval_outermost_constant_expr): Only instantiate fns
when manifestly_const_eval.
* typeck2.c (check_narrowing): This context is manifestly
constant-evaluated.

libstdc++-v3/ChangeLog:

* testsuite/20_util/integer_comparisons/greater_equal_neg.cc:
* testsuite/20_util/integer_comparisons/greater_neg.cc:
* testsuite/20_util/integer_comparisons/less_equal_neg.cc:
Adjust expected message.
* testsuite/lib/prune.exp: Prune 'in constexpr expansion'.

gcc/testsuite/ChangeLog:

* g++.dg/ext/vla22.C: Don't expect a narrowing error.
* g++.dg/cpp0x/constexpr-inst1.C: New test.

gcc/cp/constexpr.c
gcc/cp/typeck2.c
gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C [new file with mode: 0644]
gcc/testsuite/g++.dg/ext/vla22.C
libstdc++-v3/testsuite/20_util/integer_comparisons/greater_equal_neg.cc
libstdc++-v3/testsuite/20_util/integer_comparisons/greater_neg.cc
libstdc++-v3/testsuite/20_util/integer_comparisons/less_equal_neg.cc
libstdc++-v3/testsuite/lib/prune.exp

index 08f8514d08deebdd08d0ea654c07dd16513baa9e..c5f01b9547033570434e35da532aaabf4b83a221 100644 (file)
@@ -1609,24 +1609,24 @@ addr_of_non_const_var (tree *tp, int *walk_subtrees, void *data)
    all arguments and bind their values to correspondings
    parameters, making up the NEW_CALL context.  */
 
-static void
-cxx_bind_parameters_in_call (const constexpr_ctx *ctx, tree t,
-                             constexpr_call *new_call,
+static tree
+cxx_bind_parameters_in_call (const constexpr_ctx *ctx, tree t, tree fun,
                             bool *non_constant_p, bool *overflow_p,
                             bool *non_constant_args)
 {
   const int nargs = call_expr_nargs (t);
-  tree fun = new_call->fundef->decl;
-  tree parms = new_call->fundef->parms;
+  tree parms = DECL_ARGUMENTS (fun);
   int i;
   /* We don't record ellipsis args below.  */
   int nparms = list_length (parms);
   int nbinds = nargs < nparms ? nargs : nparms;
-  tree binds = new_call->bindings = make_tree_vec (nbinds);
+  tree binds = make_tree_vec (nbinds);
   for (i = 0; i < nargs; ++i)
     {
       tree x, arg;
       tree type = parms ? TREE_TYPE (parms) : void_type_node;
+      if (parms && DECL_BY_REFERENCE (parms))
+       type = TREE_TYPE (type);
       x = get_nth_callarg (t, i);
       /* For member function, the first argument is a pointer to the implied
          object.  For a constructor, it might still be a dummy object, in
@@ -1647,7 +1647,7 @@ cxx_bind_parameters_in_call (const constexpr_ctx *ctx, tree t,
                                          non_constant_p, overflow_p);
       /* Don't VERIFY_CONSTANT here.  */
       if (*non_constant_p && ctx->quiet)
-       return;
+       break;
       /* Just discard ellipsis args after checking their constantitude.  */
       if (!parms)
        continue;
@@ -1698,6 +1698,8 @@ cxx_bind_parameters_in_call (const constexpr_ctx *ctx, tree t,
        }
       parms = TREE_CHAIN (parms);
     }
+
+  return binds;
 }
 
 /* Variables and functions to manage constexpr call expansion context.
@@ -2564,6 +2566,26 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
        }
     }
 
+  bool non_constant_args = false;
+  new_call.bindings
+    = cxx_bind_parameters_in_call (ctx, t, fun, non_constant_p,
+                                  overflow_p, &non_constant_args);
+
+  /* We build up the bindings list before we know whether we already have this
+     call cached.  If we don't end up saving these bindings, ggc_free them when
+     this function exits.  */
+  class free_bindings
+  {
+    tree *bindings;
+  public:
+    free_bindings (tree &b): bindings (&b) { }
+    ~free_bindings () { if (bindings) ggc_free (*bindings); }
+    void preserve () { bindings = NULL; }
+  } fb (new_call.bindings);
+
+  if (*non_constant_p)
+    return t;
+
   /* We can't defer instantiating the function any longer.  */
   if (!DECL_INITIAL (fun)
       && DECL_TEMPLOID_INSTANTIATION (fun)
@@ -2615,25 +2637,6 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
         }
     }
 
-  bool non_constant_args = false;
-  cxx_bind_parameters_in_call (ctx, t, &new_call,
-                              non_constant_p, overflow_p, &non_constant_args);
-
-  /* We build up the bindings list before we know whether we already have this
-     call cached.  If we don't end up saving these bindings, ggc_free them when
-     this function exits.  */
-  class free_bindings
-  {
-    tree *bindings;
-  public:
-    free_bindings (tree &b): bindings (&b) { }
-    ~free_bindings () { if (bindings) ggc_free (*bindings); }
-    void preserve () { bindings = NULL; }
-  } fb (new_call.bindings);
-
-  if (*non_constant_p)
-    return t;
-
   depth_ok = push_cx_call_context (t);
 
   /* Remember the object we are constructing or destructing.  */
@@ -7394,7 +7397,8 @@ cxx_eval_outermost_constant_expr (tree t, bool allow_non_constant,
   auto_vec<tree, 16> cleanups;
   global_ctx.cleanups = &cleanups;
 
-  instantiate_constexpr_fns (r);
+  if (manifestly_const_eval)
+    instantiate_constexpr_fns (r);
   r = cxx_eval_constant_expression (&ctx, r,
                                    false, &non_constant_p, &overflow_p);
 
index abfd7dabd607833491a46bfa1a27b52bae5ccfff..c01f2f8ced449acf3fd5ebd1f884635d8895823e 100644 (file)
@@ -892,7 +892,7 @@ check_narrowing (tree type, tree init, tsubst_flags_t complain,
 
   /* Even non-dependent expressions can still have template
      codes like CAST_EXPR, so use *_non_dependent_expr to cope.  */
-  init = fold_non_dependent_expr (init, complain);
+  init = fold_non_dependent_expr (init, complain, /*manifest*/true);
   if (init == error_mark_node)
     return ok;
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-inst1.C
new file mode 100644 (file)
index 0000000..3ce513d
--- /dev/null
@@ -0,0 +1,17 @@
+// Test that we don't uselessly instantiate f<A> immediately while parsing g.
+// Doing so is permitted by the standard, but has no benefit and breaks code
+// unnecessarily.
+
+// { dg-do compile { target c++11 } }
+
+// -O activates the call to maybe_constant_value in cp_fold.
+// { dg-additional-options -O }
+
+template <class T>
+constexpr int f (const T* p) { return p->i; }
+
+constexpr int g(const struct A* p) { return f(p); }
+
+struct A { int i; };
+
+// Instantiating f<A> at EOF works fine.
index 967adb9ab67b5a7733051debe217ef0e97b4a31a..2308ee748df0b2f4031793eec5728a952c41779d 100644 (file)
@@ -6,4 +6,4 @@ void
 f ()
 {
   const int tbl[(long) "h"] = { 12 }; // { dg-error "size of array .tbl. is not an integral constant-expression" }
-}                                    // { dg-warning "narrowing conversion" "" { target c++11 } .-1 }
+}
index 626332629484e0ae2de824aa35e9ac7ebf45cf20..8fc6179c07db809d2ce7789e6b92108f9331d842 100644 (file)
 
 #include <utility>
 
-bool a = std::cmp_greater_equal('1', 49); // { dg-error "here" }
-bool b = std::cmp_greater_equal(50, '2'); // { dg-error "here" }
-bool c = std::cmp_greater_equal(2, L'2'); // { dg-error "here" }
-bool d = std::cmp_greater_equal(L'2', 2); // { dg-error "here" }
-bool e = std::cmp_greater_equal(true, 1); // { dg-error "here" }
-bool f = std::cmp_greater_equal(0, false); // { dg-error "here" }
-bool g = std::cmp_greater_equal(97, u8'a'); // { dg-error "here" }
-bool h = std::cmp_greater_equal(u8'a', 97); // { dg-error "here" }
-bool i = std::cmp_greater_equal(97, u'a'); // { dg-error "here" }
-bool j = std::cmp_greater_equal(u'a', 97); // { dg-error "here" }
-bool k = std::cmp_greater_equal(97, U'a'); // { dg-error "here" }
-bool l = std::cmp_greater_equal(U'a', 97); // { dg-error "here" }
+bool a = std::cmp_greater_equal('1', 49); // { dg-error "constexpr" }
+bool b = std::cmp_greater_equal(50, '2'); // { dg-error "constexpr" }
+bool c = std::cmp_greater_equal(2, L'2'); // { dg-error "constexpr" }
+bool d = std::cmp_greater_equal(L'2', 2); // { dg-error "constexpr" }
+bool e = std::cmp_greater_equal(true, 1); // { dg-error "constexpr" }
+bool f = std::cmp_greater_equal(0, false); // { dg-error "constexpr" }
+bool g = std::cmp_greater_equal(97, u8'a'); // { dg-error "constexpr" }
+bool h = std::cmp_greater_equal(u8'a', 97); // { dg-error "constexpr" }
+bool i = std::cmp_greater_equal(97, u'a'); // { dg-error "constexpr" }
+bool j = std::cmp_greater_equal(u'a', 97); // { dg-error "constexpr" }
+bool k = std::cmp_greater_equal(97, U'a'); // { dg-error "constexpr" }
+bool l = std::cmp_greater_equal(U'a', 97); // { dg-error "constexpr" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
index 48cb64d56763379c15a9300ce03dbb9434366e91..d5524b86c261d6efe8d30706b139dd86aeaa384d 100644 (file)
 
 #include <utility>
 
-bool a = std::cmp_greater('1', 49); // { dg-error "here" }
-bool b = std::cmp_greater(50, '2'); // { dg-error "here" }
-bool c = std::cmp_greater(2, L'2'); // { dg-error "here" }
-bool d = std::cmp_greater(L'2', 2); // { dg-error "here" }
-bool e = std::cmp_greater(true, 1); // { dg-error "here" }
-bool f = std::cmp_greater(0, false); // { dg-error "here" }
-bool g = std::cmp_greater(97, u8'a'); // { dg-error "here" }
-bool h = std::cmp_greater(u8'a', 97); // { dg-error "here" }
-bool i = std::cmp_greater(97, u'a'); // { dg-error "here" }
-bool j = std::cmp_greater(u'a', 97); // { dg-error "here" }
-bool k = std::cmp_greater(97, U'a'); // { dg-error "here" }
-bool l = std::cmp_greater(U'a', 97); // { dg-error "here" }
+bool a = std::cmp_greater('1', 49); // { dg-error "constexpr" }
+bool b = std::cmp_greater(50, '2'); // { dg-error "constexpr" }
+bool c = std::cmp_greater(2, L'2'); // { dg-error "constexpr" }
+bool d = std::cmp_greater(L'2', 2); // { dg-error "constexpr" }
+bool e = std::cmp_greater(true, 1); // { dg-error "constexpr" }
+bool f = std::cmp_greater(0, false); // { dg-error "constexpr" }
+bool g = std::cmp_greater(97, u8'a'); // { dg-error "constexpr" }
+bool h = std::cmp_greater(u8'a', 97); // { dg-error "constexpr" }
+bool i = std::cmp_greater(97, u'a'); // { dg-error "constexpr" }
+bool j = std::cmp_greater(u'a', 97); // { dg-error "constexpr" }
+bool k = std::cmp_greater(97, U'a'); // { dg-error "constexpr" }
+bool l = std::cmp_greater(U'a', 97); // { dg-error "constexpr" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
index a16b36a83c93c2c586b349edf8a77decb3cd8943..61a987561da805b16719848e106b28b5461bcce2 100644 (file)
 
 #include <utility>
 
-bool a = std::cmp_less_equal('1', 49); // { dg-error "here" }
-bool b = std::cmp_less_equal(50, '2'); // { dg-error "here" }
-bool c = std::cmp_less_equal(2, L'2'); // { dg-error "here" }
-bool d = std::cmp_less_equal(L'2', 2); // { dg-error "here" }
-bool e = std::cmp_less_equal(true, 1); // { dg-error "here" }
-bool f = std::cmp_less_equal(0, false); // { dg-error "here" }
-bool g = std::cmp_less_equal(97, u8'a'); // { dg-error "here" }
-bool h = std::cmp_less_equal(u8'a', 97); // { dg-error "here" }
-bool i = std::cmp_less_equal(97, u'a'); // { dg-error "here" }
-bool j = std::cmp_less_equal(u'a', 97); // { dg-error "here" }
-bool k = std::cmp_less_equal(97, U'a'); // { dg-error "here" }
-bool l = std::cmp_less_equal(U'a', 97); // { dg-error "here" }
+bool a = std::cmp_less_equal('1', 49); // { dg-error "constexpr" }
+bool b = std::cmp_less_equal(50, '2'); // { dg-error "constexpr" }
+bool c = std::cmp_less_equal(2, L'2'); // { dg-error "constexpr" }
+bool d = std::cmp_less_equal(L'2', 2); // { dg-error "constexpr" }
+bool e = std::cmp_less_equal(true, 1); // { dg-error "constexpr" }
+bool f = std::cmp_less_equal(0, false); // { dg-error "constexpr" }
+bool g = std::cmp_less_equal(97, u8'a'); // { dg-error "constexpr" }
+bool h = std::cmp_less_equal(u8'a', 97); // { dg-error "constexpr" }
+bool i = std::cmp_less_equal(97, u'a'); // { dg-error "constexpr" }
+bool j = std::cmp_less_equal(u'a', 97); // { dg-error "constexpr" }
+bool k = std::cmp_less_equal(97, U'a'); // { dg-error "constexpr" }
+bool l = std::cmp_less_equal(U'a', 97); // { dg-error "constexpr" }
 
 // { dg-error "static assertion failed" "" { target *-*-* } 0 }
index fd4a0ac7f97bf2d90d6a3c4e947bee6577f1914e..334f8218d1673bae05177072f1eb2cf4e0e0d878 100644 (file)
@@ -46,6 +46,7 @@ proc libstdc++-dg-prune { system text } {
     regsub -all "(^|\n)\[^\n\]*(: )?At (top level|global scope):\[^\n\]*" $text "" text
     regsub -all "(^|\n)\[^\n\]*:   (recursively )?required \[^\n\]*" $text "" text
     regsub -all "(^|\n)\[^\n\]*:   . skipping \[0-9\]* instantiation contexts \[^\n\]*" $text "" text
+    regsub -all "(^|\n)\[^\n\]*:   in .constexpr. expansion \[^\n\]*" $text "" text
     regsub -all "(^|\n)    inlined from \[^\n\]*" $text "" text
     # Why doesn't GCC need these to strip header context?
     regsub -all "(^|\n)In file included from \[^\n\]*" $text "" text