c++: avoid initializer_list<string> [PR105838]
authorJason Merrill <jason@redhat.com>
Tue, 6 Dec 2022 14:51:51 +0000 (09:51 -0500)
committerJason Merrill <jason@redhat.com>
Thu, 8 Dec 2022 18:40:21 +0000 (13:40 -0500)
When constructing a vector<string> from { "strings" }, first is built an
initializer_list<string>, which is then copied into the strings in the
vector.  But this is inefficient: better would be treat the { "strings" }
as a range and construct the strings in the vector directly from the
string-literals.  We can do this transformation for standard library
classes because we know the design patterns they follow.

PR c++/105838

gcc/cp/ChangeLog:

* call.cc (list_ctor_element_type): New.
(braced_init_element_type): New.
(has_non_trivial_temporaries): New.
(maybe_init_list_as_array): New.
(maybe_init_list_as_range): New.
(build_user_type_conversion_1): Use maybe_init_list_as_range.
* parser.cc (cp_parser_braced_list): Call
recompute_constructor_flags.
* cp-tree.h (find_temps_r): Declare.

gcc/testsuite/ChangeLog:

* g++.dg/tree-ssa/initlist-opt1.C: New test.

gcc/cp/call.cc
gcc/cp/cp-tree.h
gcc/cp/parser.cc
gcc/testsuite/g++.dg/tree-ssa/initlist-opt1.C [new file with mode: 0644]

index 459e86b..33b5e7f 100644 (file)
@@ -4154,6 +4154,134 @@ add_list_candidates (tree fns, tree first_arg,
                  access_path, flags, candidates, complain);
 }
 
+/* Given C(std::initializer_list<A>), return A.  */
+
+static tree
+list_ctor_element_type (tree fn)
+{
+  gcc_checking_assert (is_list_ctor (fn));
+
+  tree parm = FUNCTION_FIRST_USER_PARMTYPE (fn);
+  parm = non_reference (TREE_VALUE (parm));
+  return TREE_VEC_ELT (CLASSTYPE_TI_ARGS (parm), 0);
+}
+
+/* If EXPR is a braced-init-list where the elements all decay to the same type,
+   return that type.  */
+
+static tree
+braced_init_element_type (tree expr)
+{
+  if (TREE_CODE (expr) == CONSTRUCTOR
+      && TREE_CODE (TREE_TYPE (expr)) == ARRAY_TYPE)
+    return TREE_TYPE (TREE_TYPE (expr));
+  if (!BRACE_ENCLOSED_INITIALIZER_P (expr))
+    return NULL_TREE;
+
+  tree elttype = NULL_TREE;
+  for (constructor_elt &e: CONSTRUCTOR_ELTS (expr))
+    {
+      tree type = TREE_TYPE (e.value);
+      type = type_decays_to (type);
+      if (!elttype)
+       elttype = type;
+      else if (!same_type_p (type, elttype))
+       return NULL_TREE;
+    }
+  return elttype;
+}
+
+/* True iff EXPR contains any temporaries with non-trivial destruction.
+
+   ??? Also ignore classes with non-trivial but no-op destruction other than
+   std::allocator?  */
+
+static bool
+has_non_trivial_temporaries (tree expr)
+{
+  auto_vec<tree*> temps;
+  cp_walk_tree_without_duplicates (&expr, find_temps_r, &temps);
+  for (tree *p : temps)
+    {
+      tree t = TREE_TYPE (*p);
+      if (!TYPE_HAS_TRIVIAL_DESTRUCTOR (t)
+         && !is_std_allocator (t))
+       return true;
+    }
+  return false;
+}
+
+/* We're initializing an array of ELTTYPE from INIT.  If it seems useful,
+   return INIT as an array (of its own type) so the caller can initialize the
+   target array in a loop.  */
+
+static tree
+maybe_init_list_as_array (tree elttype, tree init)
+{
+  /* Only do this if the array can go in rodata but not once converted.  */
+  if (!CLASS_TYPE_P (elttype))
+    return NULL_TREE;
+  tree init_elttype = braced_init_element_type (init);
+  if (!init_elttype || !SCALAR_TYPE_P (init_elttype) || !TREE_CONSTANT (init))
+    return NULL_TREE;
+
+  tree first = CONSTRUCTOR_ELT (init, 0)->value;
+  if (TREE_CODE (init_elttype) == INTEGER_TYPE && null_ptr_cst_p (first))
+    /* Avoid confusion from treating 0 as a null pointer constant.  */
+    first = build1 (UNARY_PLUS_EXPR, init_elttype, first);
+  first = (perform_implicit_conversion_flags
+          (elttype, first, tf_none, LOOKUP_IMPLICIT|LOOKUP_NO_NARROWING));
+  if (first == error_mark_node)
+    /* Let the normal code give the error.  */
+    return NULL_TREE;
+
+  /* Don't do this if the conversion would be constant.  */
+  first = maybe_constant_init (first);
+  if (TREE_CONSTANT (first))
+    return NULL_TREE;
+
+  /* We can't do this if the conversion creates temporaries that need
+     to live until the whole array is initialized.  */
+  if (has_non_trivial_temporaries (first))
+    return NULL_TREE;
+
+  init_elttype = cp_build_qualified_type (init_elttype, TYPE_QUAL_CONST);
+  tree arr = build_array_of_n_type (init_elttype, CONSTRUCTOR_NELTS (init));
+  return finish_compound_literal (arr, init, tf_none);
+}
+
+/* If we were going to call e.g. vector(initializer_list<string>) starting
+   with a list of string-literals (which is inefficient, see PR105838),
+   instead build an array of const char* and pass it to the range constructor.
+   But only do this for standard library types, where we can assume the
+   transformation makes sense.
+
+   Really the container classes should have initializer_list<U> constructors to
+   get the same effect more simply; this is working around that lack.  */
+
+static tree
+maybe_init_list_as_range (tree fn, tree expr)
+{
+  if (BRACE_ENCLOSED_INITIALIZER_P (expr)
+      && is_list_ctor (fn)
+      && decl_in_std_namespace_p (fn))
+    {
+      tree to = list_ctor_element_type (fn);
+      if (tree init = maybe_init_list_as_array (to, expr))
+       {
+         tree begin = decay_conversion (TARGET_EXPR_SLOT (init), tf_none);
+         tree nelts = array_type_nelts_top (TREE_TYPE (init));
+         tree end = cp_build_binary_op (input_location, PLUS_EXPR, begin,
+                                        nelts, tf_none);
+         begin = cp_build_compound_expr (init, begin, tf_none);
+         return build_constructor_va (init_list_type_node, 2,
+                                      NULL_TREE, begin, NULL_TREE, end);
+       }
+    }
+
+  return NULL_TREE;
+}
+
 /* Returns the best overload candidate to perform the requested
    conversion.  This function is used for three the overloading situations
    described in [over.match.copy], [over.match.conv], and [over.match.ref].
@@ -4425,6 +4553,16 @@ build_user_type_conversion_1 (tree totype, tree expr, int flags,
       return cand;
     }
 
+  /* Maybe pass { } as iterators instead of an initializer_list.  */
+  if (tree iters = maybe_init_list_as_range (cand->fn, expr))
+    if (z_candidate *cand2
+       = build_user_type_conversion_1 (totype, iters, flags, tf_none))
+      if (cand2->viable == 1)
+       {
+         cand = cand2;
+         expr = iters;
+       }
+
   tree convtype;
   if (!DECL_CONSTRUCTOR_P (cand->fn))
     convtype = non_reference (TREE_TYPE (TREE_TYPE (cand->fn)));
index 581ac2b..0d6c234 100644 (file)
@@ -7087,6 +7087,7 @@ extern void set_global_friend                     (tree);
 extern bool is_global_friend                   (tree);
 
 /* in init.cc */
+extern tree find_temps_r                       (tree *, int *, void *);
 extern tree expand_member_init                 (tree);
 extern void emit_mem_initializers              (tree);
 extern tree build_aggr_init                    (tree, tree, int,
index e8a5090..4798aae 100644 (file)
@@ -25445,6 +25445,7 @@ cp_parser_braced_list (cp_parser* parser, bool* non_constant_p)
   location_t finish_loc = cp_lexer_peek_token (parser->lexer)->location;
   braces.require_close (parser);
   TREE_TYPE (initializer) = init_list_type_node;
+  recompute_constructor_flags (initializer);
 
   cp_expr result (initializer);
   /* Build a location of the form:
diff --git a/gcc/testsuite/g++.dg/tree-ssa/initlist-opt1.C b/gcc/testsuite/g++.dg/tree-ssa/initlist-opt1.C
new file mode 100644 (file)
index 0000000..053317b
--- /dev/null
@@ -0,0 +1,25 @@
+// PR c++/105838
+// { dg-additional-options -fdump-tree-gimple }
+// { dg-do compile { target c++11 } }
+
+// Test that we do range-initialization from const char *.
+// { dg-final { scan-tree-dump {_M_range_initialize<const char\* const\*>} "gimple" } }
+
+#include <string>
+#include <vector>
+
+void g (const void *);
+
+void f (const char *p)
+{
+  std::vector<std::string> lst = {
+  "aahing", "aaliis", "aarrgh", "abacas", "abacus", "abakas", "abamps", "abands", "abased", "abaser", "abases", "abasia",
+  "abated", "abater", "abates", "abatis", "abator", "abattu", "abayas", "abbacy", "abbess", "abbeys", "abbots", "abcees",
+  "abdabs", "abduce", "abduct", "abears", "abeigh", "abeles", "abelia", "abends", "abhors", "abided", "abider", "abides",
+  "abject", "abjure", "ablate", "ablaut", "ablaze", "ablest", "ablets", "abling", "ablins", "abloom", "ablush", "abmhos",
+  "aboard", "aboded", "abodes", "abohms", "abolla", "abomas", "aboral", "abords", "aborne", "aborts", "abound", "abouts",
+  "aboves", "abrade", "abraid", "abrash", "abrays", "abrazo", "abrege", "abrins", "abroad", "abrupt", "abseil", "absent",
+  };
+
+  g(&lst);
+}