]> git.ipfire.org Git - thirdparty/gcc.git/commitdiff
c++/reflection: rewrite and memoize consteval_only_p [PR125179]
authorPatrick Palka <ppalka@redhat.com>
Wed, 6 May 2026 00:30:50 +0000 (20:30 -0400)
committerPatrick Palka <ppalka@redhat.com>
Wed, 6 May 2026 00:30:50 +0000 (20:30 -0400)
The TU from this PR exhibits a 40x compile-time slowdown with -freflection
vs without, all due to consteval_only_p which happens to be quite slow on
large intertwined classes due to its recursive walking of TYPE_FIELDS.

This patch firstly rewrites the predicate to use direct recursion instead
of cp_walk_tree so that it's easier to reason about and more closely
mirrors its standard definition at [basic.types.general]/12.

This patch also makes the recursive part of the predicate tri-state, where
the third 'unknown' state corresponds to seeing an incomplete class type
whose consteval-only-ness we don't yet know.

Finally this patch caches the result of the predicate for class types
when the result is known.  When the result is unknown (due to an
incomplete constituent type) then we don't cache so that a subsequent
call to the predicate can try again.

With this patch compile time for said TU is now 1.1x with -freflection
instead of 40x.

PR c++/125179

gcc/cp/ChangeLog:

* reflect.cc: (consteval_only_type_r): Remove this cp_walk_tree
callback and replace with ...
(consteval_only_walker): ... this recursive memoized
implementation.
(consteval_only_p): Define in terms of consteval_only_walker.

Reviewed-by: Jakub Jelinek <jakub@redhat.com>
Reviewed-by: Jason Merrill <jason@redhat.com>
gcc/cp/reflect.cc

index 3b9f56ea5484366eeceaeb66e50de5dcdc92d531..816950d43edee0f41cc55588dfcd56011e5c009f 100644 (file)
@@ -8561,47 +8561,26 @@ splice (tree refl)
   return refl;
 }
 
-/* A walker for consteval_only_p.  It cannot be a lambda, because we
-   have to call this recursively, sigh.  */
+/* A cache of the known boolean result of consteval_only_p_walker::walk
+   for class types.  */
 
-static tree
-consteval_only_type_r (tree *tp, int *walk_subtrees, void *data)
-{
-  tree t = *tp;
-  /* Types can contain themselves recursively, hence this.  */
-  auto visited = static_cast<hash_set<tree> *>(data);
-
-  if (!TYPE_P (t))
-    return NULL_TREE;
+static GTY((cache)) type_tree_cache_map *consteval_only_class_cache;
 
-  if (REFLECTION_TYPE_P (t))
-    return t;
-
-  if (typedef_variant_p (t))
-    /* Tell cp_walk_subtrees to look through typedefs.  */
-    *walk_subtrees = 2;
-
-  if (RECORD_OR_UNION_TYPE_P (t))
-    {
-      /* Don't walk template arguments; A<info>::type isn't a consteval-only
-        type.  */
-      *walk_subtrees = 0;
-      /* So we have to walk the fields manually.  */
-      for (tree member = TYPE_FIELDS (t);
-          member; member = DECL_CHAIN (member))
-       if (TREE_CODE (member) == FIELD_DECL)
-         if (tree r = cp_walk_tree (&TREE_TYPE (member),
-                                    consteval_only_type_r, visited, visited))
-           return r;
-    }
+struct consteval_only_p_walker
+{
+  /* The set of class types we've seen.  */
+  hash_set<tree> class_seen;
+  /* The number of class types we're recursively inside.  */
+  int class_depth = 0;
+  /* True if we've optimistically assumed an already-seen
+     consteval-unknown class type is not consteval.  */
+  bool optimistic_p = false;
 
-  return NULL_TREE;
-}
+  tristate walk (tree);
+};
 
-/* True if T is a consteval-only type as per [basic.types.general]:
-   "A type is consteval-only if it is either std::meta::info or a type
-   compounded from a consteval-only type", or something that has
-   a consteval-only type.  */
+/* True if T is a consteval-only type as per [basic.types.general]/12,
+   or is a declaration with such a type, or a TREE_VEC thereof.  */
 
 bool
 consteval_only_p (tree t)
@@ -8612,7 +8591,7 @@ consteval_only_p (tree t)
   if (!TYPE_P (t))
     t = TREE_TYPE (t);
 
-  if (!t)
+  if (!t || t == error_mark_node)
     return false;
 
   if (TREE_CODE (t) == TREE_VEC)
@@ -8634,9 +8613,83 @@ consteval_only_p (tree t)
      which could be consteval-only, depending on T.  */
   t = complete_type (t);
 
-  /* Classes with std::meta::info members are also consteval-only.  */
-  hash_set<tree> visited;
-  return !!cp_walk_tree (&t, consteval_only_type_r, &visited, &visited);
+  consteval_only_p_walker walker;
+  return walker.walk (t).is_true ();
+}
+
+/* Recursive workhorse of consteval_only_p.  Returns true if T is definitely
+   consteval-only, false if it's definitely not, and unknown if we saw an
+   incomplete type and therefore don't know.  */
+
+tristate
+consteval_only_p_walker::walk (tree t)
+{
+  t = TYPE_MAIN_VARIANT (t);
+
+  if (REFLECTION_TYPE_P (t))
+    return true;
+  else if (INDIRECT_TYPE_P (t))
+    return walk (TREE_TYPE (t));
+  else if (TREE_CODE (t) == ARRAY_TYPE)
+    return walk (TREE_TYPE (t));
+  else if (FUNC_OR_METHOD_TYPE_P (t))
+    {
+      tristate r = walk (TREE_TYPE (t));
+      for (tree parm = TYPE_ARG_TYPES (t);
+          parm != NULL_TREE && parm != void_list_node;
+          parm = TREE_CHAIN (parm))
+       {
+         if (r.is_true ())
+           break;
+         r = r || walk (TREE_VALUE (parm));
+       }
+      return r;
+    }
+  else if (RECORD_OR_UNION_TYPE_P (t))
+    {
+      if (tree *slot = hash_map_safe_get (consteval_only_class_cache, t))
+       return *slot == boolean_true_node;
+
+      if (!COMPLETE_TYPE_P (t) && LAMBDA_TYPE_P (t))
+       /* Defer until we've definitely gone through prune_lambda_captures.  */
+       return tristate::unknown ();
+
+      if (class_seen.add (t))
+       {
+         /* Optimistically assume this already seen consteval-unknown class is
+            not consteval-only, for sake of mutually recursive classes.  */
+         optimistic_p = true;
+         return false;
+       }
+      ++class_depth;
+
+      tristate r = COMPLETE_TYPE_P (t) ? false : tristate::unknown ();
+      for (tree member = TYPE_FIELDS (t); member; member = DECL_CHAIN (member))
+       if (TREE_CODE (member) == FIELD_DECL)
+         {
+           r = r || walk (TREE_TYPE (member));
+           if (r.is_true ())
+             break;
+         }
+
+      if (r.is_true ())
+       hash_map_safe_put<hm_ggc> (consteval_only_class_cache,
+                                  t, boolean_true_node);
+      else if (r.is_false ()
+              /* The optimistic assumption above is at odds with caching
+                 'false' results for a nested class type.  */
+              && (class_depth == 1 || !optimistic_p))
+       hash_map_safe_put<hm_ggc> (consteval_only_class_cache,
+                                  t, boolean_false_node);
+
+      --class_depth;
+      return r;
+    }
+  else if (TYPE_PTRMEM_P (t))
+    return (walk (TYPE_PTRMEM_CLASS_TYPE (t))
+           || walk (TYPE_PTRMEM_POINTED_TO_TYPE (t)));
+  else
+    return false;
 }
 
 /* A walker for check_out_of_consteval_use_r.  It cannot be a lambda, because