On Mon, Oct 27, 2025 at 04:07:30PM +0200, Jason Merrill wrote:
> On 10/22/25 6:11 AM, Nathaniel Shead wrote:
> > On Sun, Oct 19, 2025 at 09:35:28PM +0300, Jason Merrill wrote:
> > > On 10/19/25 2:14 AM, Nathaniel Shead wrote:
> > > > On Sat, Oct 18, 2025 at 06:41:15PM +0300, Jason Merrill wrote:
> > > > > On 10/18/25 4:13 PM, Nathaniel Shead wrote:
> > > > > > Bootstrapped and regtested on x86_64-pc-linux-gnu, OK for trunk/15?
> > > > > >
> > > > > > -- >8 --
> > > > > >
> > > > > > The ICE in the linked PR occurs because we first stream the lambda
> > > > > > type
> > > > > > before its keyed decl has been streamed, but the key decl's type
> > > > > > depends
> > > > > > on the lambda. And so when streaming the key decl to check for an
> > > > > > existing decl to merge with, merging the key decl itself crashes
> > > > > > because
> > > > > > its type has only been partially streamed.
> > > > > >
> > > > > > This patch fixes the issue by generalising the existing FIELD_DECL
> > > > > > handling to any class member using the outermost containing
> > > > > > TYPE_DECL as
> > > > > > its key type. This way we can guarantee that the key decl has been
> > > > > > streamed before the lambda type is otherwise needed.
> > > > >
> > > > > Why doesn't the existing FIELD_DECL handling already key the lambda
> > > > > to the
> > > > > class, since "do_nothing" is a data member?
> > > > >
> > > >
> > > > Because 'do_nothing' is a static member it's a VAR_DECL, not a
> > > > FIELD_DECL.
> > >
> > > Ah, indeed.
> > >
> > > > > Why is the outermost class needed? It looks like the A/B case is
> > > > > testing
> > > > > that, but why doesn't it work to start streaming the lambda and check
> > > > > with
> > > > > MoreInner for a merge candidate?
> > > > >
> > > >
> > > > A more minimal example of this case that also fails in GCC 15:
> > > >
> > > > struct X {
> > > > struct Inner {
> > > > union MoreInner {
> > > > decltype([]{}) y;
> > > > };
> > > > };
> > > > using B = decltype(Inner::MoreInner::y);
> > > > };
> > > >
> > > > Here we crash when streaming in the attached decls for 'MoreInner'.
> > > >
> > > > The type of 'y' is attached to 'MoreInner' directly, but we currently
> > > > stream members in reverse order of declaration, so we see 'B' before
> > > > streaming 'Inner' or 'MoreInner'. So similarly to the case in the PR,
> > > > we end up reading 'MoreInner' first while finding the key entity for the
> > > > lambda, see that's it a duplicate, read its attached entities, and then
> > > > fail the check here:
> > > >
> > > > if (DECL_LANG_SPECIFIC (inner)
> > > > && DECL_MODULE_KEYED_DECLS_P (inner))
> > > > {
> > > > /* Read and maybe install the attached entities. */
> > > > bool existed;
> > > > auto &set = keyed_table->get_or_insert (STRIP_TEMPLATE
> > > > (existing),
> > > > &existed);
> > > > unsigned num = u ();
> > > > if (is_new == existed)
> > > > set_overrun ();
> > > > if (is_new)
> > > > set.reserve (num);
> > > > for (unsigned ix = 0; !get_overrun () && ix != num; ix++)
> > > > {
> > > > tree attached = tree_node ();
> > > > dump (dumper::MERGE)
> > > > && dump ("Read %d[%u] %s attached decl %N", tag, ix,
> > > > is_new ? "new" : "matched", attached);
> > > > if (is_new)
> > > > set.quick_push (attached);
> > > > else if (set[ix] != attached)
> > > > set_overrun (); // <--- fail here
> > > > }
> > > > }
> > > >
> > > > because the partially-streamed, not-yet-deduplicated type of the lambda
> > > > isn't the same type as the existing lambda keyed to the existing
> > > > MoreInner type.
> > > >
> > > > By keying to the outermost class we avoid any potential issues with
> > > > out-of-order member dependencies. These specific cases could possibly
> > > > also be fixed by reversing the order that we stream members, but I'm not
> > > > sure if that would expose other possible issues (with e.g. lazily
> > > > declared members or instantiations or whatnot).
> > >
> > > I would imagine that in general, streaming members in declaration order
> > > would improve how often a reference points to something already loaded. Is
> > > there any rationale for reverse order?
> > >
> >
> > On closer inspection, looks like I misunderstood; the ordering of class
> > members is redone by 'sort_cluster' which effectively randomises
> > non-field deps unless they have ordering dependencies between them
> > (which was what I was looking at...).
> >
> > This implies that another possible solution for this particular bug
> > would be to add some additional ordering guarantee so that intra-cluster
> > decls always emit the key decl before the lambda. This still doesn't
> > work for FIELD_DECLs though (as they have no dep for sort_cluster to
> > order), and I wasn't able to find a neat way to enforce this ordering
> > for other decls without adding additional data to stream in
> > 'decl_container'.
> >
> > Still OK for trunk/15 or would you prefer me to try to find another
> > approach to guarantee this ordering?
>
> Still OK with a comment explaining the above.
>
Thanks, I'll push with the following comment:
@@ -21511,9 +21498,21 @@ maybe_key_decl (tree ctx, tree decl)
&& TREE_CODE (ctx) != CONCEPT_DECL)
return;
- /* For fields, key it to the containing type to handle deduplication
- correctly. */
- if (TREE_CODE (ctx) == FIELD_DECL)
+ /* For members, key it to the containing type to handle deduplication
+ correctly. For fields, this is necessary as FIELD_DECLs have no
+ dep and so would only be streamed after the lambda type, defeating
+ our ability to merge them.
+
+ Other class-scope key decls might depend on the type of the lambda
+ but be within the same cluster; we need to ensure that we never
+ first see the key decl while streaming the lambda type as merging
+ would then fail when comparing the partially-streamed lambda type
+ of the key decl with the existing (PR c++/122310).
+
+ Perhaps sort_cluster can be adjusted to handle this better, but
+ this is a simple workaround (and might down on the number of
+ entries in keyed_table as a bonus). */
+ while (DECL_CLASS_SCOPE_P (ctx))
ctx = TYPE_NAME (DECL_CONTEXT (ctx));
if (!keyed_table)
> > > In the meantime, this patch is OK with the tweaks below.
> > >
> > > > There's also the very
> > > > very slight benefit that we need to allocate less vecs in the
> > > > keyed_decls map if more decls reuse the same key here.
> > > >
> > > > > > @@ -21506,9 +21493,11 @@ maybe_key_decl (tree ctx, tree decl)
> > > > > > && TREE_CODE (ctx) != CONCEPT_DECL)
> > > > > > return;
> > > > > > - /* For fields, key it to the containing type to handle
> > > > > > deduplication
> > > > > > - correctly. */
> > > > > > - if (TREE_CODE (ctx) == FIELD_DECL)
> > > > > > + /* For members, key it to the containing type to handle
> > > > > > deduplication
> > > > > > + correctly: otherwise we may run into issues when loading the
> > > > > > lambda
> > > > > > + type before its keyed decl, if the member itself depends on
> > > > > > the
> > > > > > + lambda type (PR c++/122310). */
> > > > > > + while (RECORD_OR_UNION_TYPE_P (DECL_CONTEXT (ctx)))
> > >
> > > DECL_CLASS_SCOPE_P
> > >
> > > > > > ctx = TYPE_NAME (DECL_CONTEXT (ctx));
> > > > > > if (!keyed_table)
> > > > > > @@ -21523,6 +21512,30 @@ maybe_key_decl (tree ctx, tree decl)
> > > > > > vec.safe_push (decl);
> > > > > > }
> > > > > > +/* Find the scope that the lambda DECL is keyed to, if any. */
> > > > > > +
> > > > > > +static tree
> > > > > > +get_keyed_decl_scope (tree decl)
> > > > > > +{
> > > > > > + gcc_checking_assert (LAMBDA_TYPE_P (TREE_TYPE (decl)));
> > > > > > + tree scope = LAMBDA_TYPE_EXTRA_SCOPE (TREE_TYPE (decl));
> > > > > > + if (!scope)
> > > > > > + return NULL_TREE;
> > > > > > +
> > > > > > + gcc_checking_assert (TREE_CODE (scope) == VAR_DECL
> > > > > > + || TREE_CODE (scope) == FIELD_DECL
> > > > > > + || TREE_CODE (scope) == PARM_DECL
> > > > > > + || TREE_CODE (scope) == TYPE_DECL
> > > > > > + || TREE_CODE (scope) == CONCEPT_DECL);
> > > > > > +
> > > > > > + while (RECORD_OR_UNION_TYPE_P (DECL_CONTEXT (scope)))
> > >
> > > Likewise.
> > >
> > > Jason
> > >
> >
>