From: Patrick Palka Date: Wed, 6 May 2026 00:30:50 +0000 (-0400) Subject: c++/reflection: rewrite and memoize consteval_only_p [PR125179] X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=c8498d405a5abb276caa06f6a8952aa26637f1db;p=thirdparty%2Fgcc.git c++/reflection: rewrite and memoize consteval_only_p [PR125179] 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 Reviewed-by: Jason Merrill --- diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc index 3b9f56ea548..816950d43ed 100644 --- a/gcc/cp/reflect.cc +++ b/gcc/cp/reflect.cc @@ -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 *>(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::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 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 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 (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 (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