diff mbox series

c++: Fix mangling of otherwise unattached class-scope lambdas [PR116568]

Message ID 66d9c823.a70a0220.7bd4f.d873@mx.google.com
State New
Headers show
Series c++: Fix mangling of otherwise unattached class-scope lambdas [PR116568] | expand

Commit Message

Nathaniel Shead Sept. 5, 2024, 3:02 p.m. UTC
Bootstrapped and regtested (so far just dg.exp) on x86_64-pc-linux-gnu,
OK for trunk if full regtest passes?  Or would it be better to try to
implement all the rules mentioned in the linked pull request for one
commit; I admit I haven't looked very closely yet at how else we
diverge?

-- >8 --

This is a step closer to implementing the suggested changes for
https://github.com/itanium-cxx-abi/cxx-abi/pull/85.

The main purpose of the patch is to solve testcase PR c++/116568, caused
by lambda expressions within the templates not correctly having the
extra mangling scope attached.

While I was working on this I found some other cases where the mangling
of lambdas was incorrect and causing issues, notably the testcase
lambda-ctx3.C which currently emits the same mangling for the base class
and member lambdas, causing mysterious assembler errors.  Fixing this
ended up also improving the situation for PR c++/107741 as well, though
it doesn't seem easily possible to fix the A::x case at this time so
I've left that as an XFAIL.

	PR c++/107741
	PR c++/116568

gcc/cp/ChangeLog:

	* cp-tree.h (LAMBDA_EXPR_EXTRA_SCOPE): Adjust comment.
	* parser.cc (cp_parser_class_head): Start (and do not finish)
	lambda scope for all valid types.
	(cp_parser_class_specifier): Finish lambda scope after parsing
	members instead.
	(cp_parser_member_declaration): Adjust comment to mention
	missing lambda scoping for static member initializers.
	* pt.cc (instantiate_class_template): Add lambda scoping.
	(instantiate_template): Likewise.

gcc/testsuite/ChangeLog:

	* g++.dg/abi/lambda-ctx2.C: New test.
	* g++.dg/abi/lambda-ctx3.C: New test.
	* g++.dg/modules/lambda-8.h: New test.
	* g++.dg/modules/lambda-8_a.H: New test.
	* g++.dg/modules/lambda-8_b.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/cp-tree.h                          |  3 +-
 gcc/cp/parser.cc                          | 31 +++++++++++++--------
 gcc/cp/pt.cc                              | 12 +++++++-
 gcc/testsuite/g++.dg/abi/lambda-ctx2.C    | 34 +++++++++++++++++++++++
 gcc/testsuite/g++.dg/abi/lambda-ctx3.C    | 21 ++++++++++++++
 gcc/testsuite/g++.dg/modules/lambda-8.h   |  7 +++++
 gcc/testsuite/g++.dg/modules/lambda-8_a.H |  5 ++++
 gcc/testsuite/g++.dg/modules/lambda-8_b.C |  5 ++++
 8 files changed, 104 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2.C
 create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx3.C
 create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8.h
 create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8_a.H
 create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8_b.C

Comments

Jason Merrill Oct. 11, 2024, 2:37 p.m. UTC | #1
On 9/5/24 11:02 AM, Nathaniel Shead wrote:
> Bootstrapped and regtested (so far just dg.exp) on x86_64-pc-linux-gnu,
> OK for trunk if full regtest passes?  Or would it be better to try to
> implement all the rules mentioned in the linked pull request for one
> commit; I admit I haven't looked very closely yet at how else we
> diverge?

I see a few things in that pull request:

1. Mangling of multiple levels of template arguments, r14-4544.
2. Mangling of lambdas in general class scope, this patch.
3. Mangling explicit lambda template parameters, r13-3527.
4. Mangling lambdas in expressions: looks like we use 'tl' instead of 'L'.
5. A note to check that a lambda in the signature of a namespace-scope 
function doesn't match a textually identical declaration in another TU; 
we make the function internal, but I don't see a testcase.

Fixing #4 as a quick followup would be good.

> This is a step closer to implementing the suggested changes for
> https://github.com/itanium-cxx-abi/cxx-abi/pull/85.
> 
> The main purpose of the patch is to solve testcase PR c++/116568, caused
> by lambda expressions within the templates not correctly having the
> extra mangling scope attached.
> 
> While I was working on this I found some other cases where the mangling
> of lambdas was incorrect and causing issues, notably the testcase
> lambda-ctx3.C which currently emits the same mangling for the base class
> and member lambdas, causing mysterious assembler errors.  Fixing this
> ended up also improving the situation for PR c++/107741 as well, though
> it doesn't seem easily possible to fix the A::x case at this time so
> I've left that as an XFAIL.

It looks pretty straightforward to split grokfield into start and finish 
functions?  But that certainly doesn't need to be part of this patch, 
which is OK as is.

> 	PR c++/107741
> 	PR c++/116568
> 
> gcc/cp/ChangeLog:
> 
> 	* cp-tree.h (LAMBDA_EXPR_EXTRA_SCOPE): Adjust comment.
> 	* parser.cc (cp_parser_class_head): Start (and do not finish)
> 	lambda scope for all valid types.
> 	(cp_parser_class_specifier): Finish lambda scope after parsing
> 	members instead.
> 	(cp_parser_member_declaration): Adjust comment to mention
> 	missing lambda scoping for static member initializers.
> 	* pt.cc (instantiate_class_template): Add lambda scoping.
> 	(instantiate_template): Likewise.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/abi/lambda-ctx2.C: New test.
> 	* g++.dg/abi/lambda-ctx3.C: New test.
> 	* g++.dg/modules/lambda-8.h: New test.
> 	* g++.dg/modules/lambda-8_a.H: New test.
> 	* g++.dg/modules/lambda-8_b.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>   gcc/cp/cp-tree.h                          |  3 +-
>   gcc/cp/parser.cc                          | 31 +++++++++++++--------
>   gcc/cp/pt.cc                              | 12 +++++++-
>   gcc/testsuite/g++.dg/abi/lambda-ctx2.C    | 34 +++++++++++++++++++++++
>   gcc/testsuite/g++.dg/abi/lambda-ctx3.C    | 21 ++++++++++++++
>   gcc/testsuite/g++.dg/modules/lambda-8.h   |  7 +++++
>   gcc/testsuite/g++.dg/modules/lambda-8_a.H |  5 ++++
>   gcc/testsuite/g++.dg/modules/lambda-8_b.C |  5 ++++
>   8 files changed, 104 insertions(+), 14 deletions(-)
>   create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx2.C
>   create mode 100644 gcc/testsuite/g++.dg/abi/lambda-ctx3.C
>   create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8.h
>   create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8_a.H
>   create mode 100644 gcc/testsuite/g++.dg/modules/lambda-8_b.C
> 
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 2eeb5e3e8b1..af1e254745b 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -1513,7 +1513,8 @@ enum cp_lambda_default_capture_mode_type {
>     (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->locus)
>   
>   /* The mangling scope for the lambda: FUNCTION_DECL, PARM_DECL, VAR_DECL,
> -   FIELD_DECL or NULL_TREE.  If this is NULL_TREE, we have no linkage.  */
> +   FIELD_DECL, TYPE_DECL, or NULL_TREE.  If this is NULL_TREE, we have no
> +   linkage.  */
>   #define LAMBDA_EXPR_EXTRA_SCOPE(NODE) \
>     (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->extra_scope)
>   
> diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
> index 5654bc00e4d..6e5228757a5 100644
> --- a/gcc/cp/parser.cc
> +++ b/gcc/cp/parser.cc
> @@ -27051,6 +27051,8 @@ cp_parser_class_specifier (cp_parser* parser)
>     if (!braces.require_open (parser))
>       {
>         pop_deferring_access_checks ();
> +      if (type != error_mark_node)
> +	finish_lambda_scope ();
>         return error_mark_node;
>       }
>   
> @@ -27115,7 +27117,10 @@ cp_parser_class_specifier (cp_parser* parser)
>     if (cp_parser_allow_gnu_extensions_p (parser))
>       attributes = cp_parser_gnu_attributes_opt (parser);
>     if (type != error_mark_node)
> -    type = finish_struct (type, attributes);
> +    {
> +      type = finish_struct (type, attributes);
> +      finish_lambda_scope ();
> +    }
>     if (nested_name_specifier_p)
>       pop_inner_scope (old_scope, scope);
>   
> @@ -27955,6 +27960,12 @@ cp_parser_class_head (cp_parser* parser,
>     if (flag_concepts)
>       type = associate_classtype_constraints (type);
>   
> +  /* Lambdas in bases and members must have the same mangling scope for ABI.
> +     We open this scope now, and will close it in cp_parser_class_specifier
> +     after parsing the member list.  */
> +  if (type && type != error_mark_node)
> +    start_lambda_scope (TYPE_NAME (type));
> +
>     /* We will have entered the scope containing the class; the names of
>        base classes should be looked up in that context.  For example:
>   
> @@ -27969,16 +27980,10 @@ cp_parser_class_head (cp_parser* parser,
>     if (cp_lexer_next_token_is (parser->lexer, CPP_COLON))
>       {
>         if (type)
> -	{
> -	  pushclass (type);
> -	  start_lambda_scope (TYPE_NAME (type));
> -	}
> +	pushclass (type);
>         bases = cp_parser_base_clause (parser);
>         if (type)
> -	{
> -	  finish_lambda_scope ();
> -	  popclass ();
> -	}
> +	popclass ();
>       }
>     else
>       bases = NULL_TREE;
> @@ -28644,9 +28649,11 @@ cp_parser_member_declaration (cp_parser* parser)
>   		 pure-specifier.  It is not correct to parse the
>   		 initializer before registering the member declaration
>   		 since the member declaration should be in scope while
> -		 its initializer is processed.  However, the rest of the
> -		 front end does not yet provide an interface that allows
> -		 us to handle this correctly.  */
> +		 its initializer is processed.  And similarly, the ABI of
> +		 lambdas declared in the initializer should be scoped to
> +		 the member.  However, the rest of the front end does not
> +		 yet provide an interface that allows us to handle this
> +		 correctly.  */
>   	      if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))
>   		{
>   		  /* In [class.mem]:
> diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
> index 9195a5274e1..ed3aaaf4473 100644
> --- a/gcc/cp/pt.cc
> +++ b/gcc/cp/pt.cc
> @@ -12482,6 +12482,10 @@ instantiate_class_template (tree type)
>     gcc_assert (!DECL_CLASS_SCOPE_P (TYPE_MAIN_DECL (pattern))
>   	      || COMPLETE_OR_OPEN_TYPE_P (TYPE_CONTEXT (type)));
>   
> +  /* When instantiating nested lambdas, ensure that they get the mangling
> +     scope of the new class type.  */
> +  start_lambda_scope (TYPE_NAME (type));
> +
>     base_list = NULL_TREE;
>     /* Defer access checking while we substitute into the types named in
>        the base-clause.  */
> @@ -12841,6 +12845,8 @@ instantiate_class_template (tree type)
>     finish_struct_1 (type);
>     TYPE_BEING_DEFINED (type) = 0;
>   
> +  finish_lambda_scope ();
> +
>     /* Remember if instantiating this class ran into errors, so we can avoid
>        instantiating member functions in limit_bad_template_recursion.  We set
>        this flag even if the problem was in another instantiation triggered by
> @@ -22210,6 +22216,7 @@ instantiate_template (tree tmpl, tree orig_args, tsubst_flags_t complain)
>   	ctx = tsubst_entering_scope (DECL_CONTEXT (gen_tmpl), targ_ptr,
>   				     complain, gen_tmpl);
>         push_nested_class (ctx);
> +      start_lambda_scope (TYPE_NAME (ctx));
>       }
>   
>     tree pattern = DECL_TEMPLATE_RESULT (gen_tmpl);
> @@ -22239,7 +22246,10 @@ instantiate_template (tree tmpl, tree orig_args, tsubst_flags_t complain)
>     if (fndecl == NULL_TREE)
>       fndecl = tsubst_decl (pattern, targ_ptr, complain, /*use_spec_table=*/false);
>     if (DECL_CLASS_SCOPE_P (gen_tmpl))
> -    pop_nested_class ();
> +    {
> +      finish_lambda_scope ();
> +      pop_nested_class ();
> +    }
>     pop_from_top_level ();
>   
>     if (fndecl == error_mark_node)
> diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2.C b/gcc/testsuite/g++.dg/abi/lambda-ctx2.C
> new file mode 100644
> index 00000000000..26896105a6c
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2.C
> @@ -0,0 +1,34 @@
> +// PR c++/107741
> +// { dg-do compile { target c++17 } }
> +
> +struct A {
> +  // We currently parse static member initializers for non-templates before we
> +  // see their decls, and so don't get the chance to attach it as scope.
> +  static constexpr auto x = []{ return 1; };
> +};
> +
> +template <typename>
> +struct B {
> +  static constexpr auto x = []{ return 2; };
> +};
> +
> +template <typename>
> +struct C {
> +  static int x;
> +};
> +
> +void side_effect();
> +
> +template <typename T>
> +int C<T>::x = (side_effect(), []{ return 3; }());
> +
> +template int C<int>::x;
> +
> +void f() {
> +  A::x();
> +  B<int>::x();
> +}
> +
> +// { dg-final { scan-assembler {_ZNK1A1xMUlvE_clEv:} { xfail *-*-* } } }
> +// { dg-final { scan-assembler {_ZNK1BIiE1xMUlvE_clEv:} } }
> +// { dg-final { scan-assembler {_ZNK1CIiE1xMUlvE_clEv:} } }
> diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx3.C b/gcc/testsuite/g++.dg/abi/lambda-ctx3.C
> new file mode 100644
> index 00000000000..f92f2500531
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/abi/lambda-ctx3.C
> @@ -0,0 +1,21 @@
> +// { dg-do compile { target c++20 } }
> +// { dg-additional-options "-fkeep-inline-functions" }
> +// See also https://github.com/itanium-cxx-abi/cxx-abi/pull/85
> +
> +struct A {
> +  decltype([]{ return 1; }) f;
> +};
> +
> +struct B : decltype([]{ return 2; }) {
> +  decltype([]{ return 3; }) f;
> +};
> +
> +struct C : decltype([]{ return 4; }) {
> +  decltype([]{ return 5; }) f;
> +};
> +
> +// { dg-final { scan-assembler {_ZNK1AUlvE_clEv:} } }
> +// { dg-final { scan-assembler {_ZNK1BUlvE_clEv:} } }
> +// { dg-final { scan-assembler {_ZNK1BUlvE0_clEv:} } }
> +// { dg-final { scan-assembler {_ZNK1CUlvE_clEv:} } }
> +// { dg-final { scan-assembler {_ZNK1CUlvE0_clEv:} } }
> diff --git a/gcc/testsuite/g++.dg/modules/lambda-8.h b/gcc/testsuite/g++.dg/modules/lambda-8.h
> new file mode 100644
> index 00000000000..0c66f053b20
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/lambda-8.h
> @@ -0,0 +1,7 @@
> +template <typename> struct S {
> +  template <typename> static constexpr auto x = []{};
> +  template <typename> using t = decltype([]{});
> +};
> +
> +inline auto x = S<int>::x<int>;
> +using t = S<int>::t<int>;
> diff --git a/gcc/testsuite/g++.dg/modules/lambda-8_a.H b/gcc/testsuite/g++.dg/modules/lambda-8_a.H
> new file mode 100644
> index 00000000000..d20958ee140
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/lambda-8_a.H
> @@ -0,0 +1,5 @@
> +// PR c++/116568
> +// { dg-additional-options "-fmodules-ts -std=c++20" }
> +// { dg-module-cmi {} }
> +
> +#include "lambda-8.h"
> diff --git a/gcc/testsuite/g++.dg/modules/lambda-8_b.C b/gcc/testsuite/g++.dg/modules/lambda-8_b.C
> new file mode 100644
> index 00000000000..05ea4afd8c1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/modules/lambda-8_b.C
> @@ -0,0 +1,5 @@
> +// PR c++/116568
> +// { dg-additional-options "-fmodules-ts -std=c++20" }
> +
> +#include "lambda-8.h"
> +import "lambda-8_a.H";
Nathaniel Shead Oct. 15, 2024, 12:37 a.m. UTC | #2
On Fri, Oct 11, 2024 at 10:37:11AM -0400, Jason Merrill wrote:
> On 9/5/24 11:02 AM, Nathaniel Shead wrote:
> > Bootstrapped and regtested (so far just dg.exp) on x86_64-pc-linux-gnu,
> > OK for trunk if full regtest passes?  Or would it be better to try to
> > implement all the rules mentioned in the linked pull request for one
> > commit; I admit I haven't looked very closely yet at how else we
> > diverge?
> 
> I see a few things in that pull request:
> 
> 1. Mangling of multiple levels of template arguments, r14-4544.
> 2. Mangling of lambdas in general class scope, this patch.
> 3. Mangling explicit lambda template parameters, r13-3527.
> 4. Mangling lambdas in expressions: looks like we use 'tl' instead of 'L'.
> 5. A note to check that a lambda in the signature of a namespace-scope
> function doesn't match a textually identical declaration in another TU; we
> make the function internal, but I don't see a testcase.
> 
> Fixing #4 as a quick followup would be good.
> 

Thanks for investigating, I'll take a look when I get a chance.

> > This is a step closer to implementing the suggested changes for
> > https://github.com/itanium-cxx-abi/cxx-abi/pull/85.
> > 
> > The main purpose of the patch is to solve testcase PR c++/116568, caused
> > by lambda expressions within the templates not correctly having the
> > extra mangling scope attached.
> > 
> > While I was working on this I found some other cases where the mangling
> > of lambdas was incorrect and causing issues, notably the testcase
> > lambda-ctx3.C which currently emits the same mangling for the base class
> > and member lambdas, causing mysterious assembler errors.  Fixing this
> > ended up also improving the situation for PR c++/107741 as well, though
> > it doesn't seem easily possible to fix the A::x case at this time so
> > I've left that as an XFAIL.
> 
> It looks pretty straightforward to split grokfield into start and finish
> functions?  But that certainly doesn't need to be part of this patch, which
> is OK as is.
> 

Thanks; I haven't actually pushed this yet because I've found it
actually can cause breakages; consider e.g. 

  struct S {
    template <int I>
    using T = decltype([]{ return I; });
  };

  S::T<0> a;
  S::T<1> b;

  int main() {
    a();
    b();
  }

Currently this mangles as _ZNK1SUlvE0_clEv and _ZNK1SUlvE1_clEv, but
with my patch they now both get mangled as _ZNK1SUlvE_clEv: because
they're separate instantiations they each restart the numbering, it
appears.

I think the fix should be that template aliases should also get added as
a mangling context so that they depend on the name of the member and any
template params.  This looks like it'll require a fair bit of adjustment
to the mangling logic however.  Does that seem reasonable to you?

For comparison, Clang mangles as _ZNK1S3$_0clEv and _ZNK1S3$_1clEv
currently and gives the lambdas internal linkage, which I think is
incorrect.

Nathaniel
diff mbox series

Patch

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index 2eeb5e3e8b1..af1e254745b 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -1513,7 +1513,8 @@  enum cp_lambda_default_capture_mode_type {
   (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->locus)
 
 /* The mangling scope for the lambda: FUNCTION_DECL, PARM_DECL, VAR_DECL,
-   FIELD_DECL or NULL_TREE.  If this is NULL_TREE, we have no linkage.  */
+   FIELD_DECL, TYPE_DECL, or NULL_TREE.  If this is NULL_TREE, we have no
+   linkage.  */
 #define LAMBDA_EXPR_EXTRA_SCOPE(NODE) \
   (((struct tree_lambda_expr *)LAMBDA_EXPR_CHECK (NODE))->extra_scope)
 
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index 5654bc00e4d..6e5228757a5 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -27051,6 +27051,8 @@  cp_parser_class_specifier (cp_parser* parser)
   if (!braces.require_open (parser))
     {
       pop_deferring_access_checks ();
+      if (type != error_mark_node)
+	finish_lambda_scope ();
       return error_mark_node;
     }
 
@@ -27115,7 +27117,10 @@  cp_parser_class_specifier (cp_parser* parser)
   if (cp_parser_allow_gnu_extensions_p (parser))
     attributes = cp_parser_gnu_attributes_opt (parser);
   if (type != error_mark_node)
-    type = finish_struct (type, attributes);
+    {
+      type = finish_struct (type, attributes);
+      finish_lambda_scope ();
+    }
   if (nested_name_specifier_p)
     pop_inner_scope (old_scope, scope);
 
@@ -27955,6 +27960,12 @@  cp_parser_class_head (cp_parser* parser,
   if (flag_concepts)
     type = associate_classtype_constraints (type);
 
+  /* Lambdas in bases and members must have the same mangling scope for ABI.
+     We open this scope now, and will close it in cp_parser_class_specifier
+     after parsing the member list.  */
+  if (type && type != error_mark_node)
+    start_lambda_scope (TYPE_NAME (type));
+
   /* We will have entered the scope containing the class; the names of
      base classes should be looked up in that context.  For example:
 
@@ -27969,16 +27980,10 @@  cp_parser_class_head (cp_parser* parser,
   if (cp_lexer_next_token_is (parser->lexer, CPP_COLON))
     {
       if (type)
-	{
-	  pushclass (type);
-	  start_lambda_scope (TYPE_NAME (type));
-	}
+	pushclass (type);
       bases = cp_parser_base_clause (parser);
       if (type)
-	{
-	  finish_lambda_scope ();
-	  popclass ();
-	}
+	popclass ();
     }
   else
     bases = NULL_TREE;
@@ -28644,9 +28649,11 @@  cp_parser_member_declaration (cp_parser* parser)
 		 pure-specifier.  It is not correct to parse the
 		 initializer before registering the member declaration
 		 since the member declaration should be in scope while
-		 its initializer is processed.  However, the rest of the
-		 front end does not yet provide an interface that allows
-		 us to handle this correctly.  */
+		 its initializer is processed.  And similarly, the ABI of
+		 lambdas declared in the initializer should be scoped to
+		 the member.  However, the rest of the front end does not
+		 yet provide an interface that allows us to handle this
+		 correctly.  */
 	      if (cp_lexer_next_token_is (parser->lexer, CPP_EQ))
 		{
 		  /* In [class.mem]:
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 9195a5274e1..ed3aaaf4473 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -12482,6 +12482,10 @@  instantiate_class_template (tree type)
   gcc_assert (!DECL_CLASS_SCOPE_P (TYPE_MAIN_DECL (pattern))
 	      || COMPLETE_OR_OPEN_TYPE_P (TYPE_CONTEXT (type)));
 
+  /* When instantiating nested lambdas, ensure that they get the mangling
+     scope of the new class type.  */
+  start_lambda_scope (TYPE_NAME (type));
+
   base_list = NULL_TREE;
   /* Defer access checking while we substitute into the types named in
      the base-clause.  */
@@ -12841,6 +12845,8 @@  instantiate_class_template (tree type)
   finish_struct_1 (type);
   TYPE_BEING_DEFINED (type) = 0;
 
+  finish_lambda_scope ();
+
   /* Remember if instantiating this class ran into errors, so we can avoid
      instantiating member functions in limit_bad_template_recursion.  We set
      this flag even if the problem was in another instantiation triggered by
@@ -22210,6 +22216,7 @@  instantiate_template (tree tmpl, tree orig_args, tsubst_flags_t complain)
 	ctx = tsubst_entering_scope (DECL_CONTEXT (gen_tmpl), targ_ptr,
 				     complain, gen_tmpl);
       push_nested_class (ctx);
+      start_lambda_scope (TYPE_NAME (ctx));
     }
 
   tree pattern = DECL_TEMPLATE_RESULT (gen_tmpl);
@@ -22239,7 +22246,10 @@  instantiate_template (tree tmpl, tree orig_args, tsubst_flags_t complain)
   if (fndecl == NULL_TREE)
     fndecl = tsubst_decl (pattern, targ_ptr, complain, /*use_spec_table=*/false);
   if (DECL_CLASS_SCOPE_P (gen_tmpl))
-    pop_nested_class ();
+    {
+      finish_lambda_scope ();
+      pop_nested_class ();
+    }
   pop_from_top_level ();
 
   if (fndecl == error_mark_node)
diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx2.C b/gcc/testsuite/g++.dg/abi/lambda-ctx2.C
new file mode 100644
index 00000000000..26896105a6c
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/lambda-ctx2.C
@@ -0,0 +1,34 @@ 
+// PR c++/107741
+// { dg-do compile { target c++17 } }
+
+struct A {
+  // We currently parse static member initializers for non-templates before we
+  // see their decls, and so don't get the chance to attach it as scope.
+  static constexpr auto x = []{ return 1; };
+};
+
+template <typename>
+struct B {
+  static constexpr auto x = []{ return 2; };
+};
+
+template <typename>
+struct C {
+  static int x;
+};
+
+void side_effect();
+
+template <typename T>
+int C<T>::x = (side_effect(), []{ return 3; }());
+
+template int C<int>::x;
+
+void f() {
+  A::x();
+  B<int>::x();
+}
+
+// { dg-final { scan-assembler {_ZNK1A1xMUlvE_clEv:} { xfail *-*-* } } }
+// { dg-final { scan-assembler {_ZNK1BIiE1xMUlvE_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1CIiE1xMUlvE_clEv:} } }
diff --git a/gcc/testsuite/g++.dg/abi/lambda-ctx3.C b/gcc/testsuite/g++.dg/abi/lambda-ctx3.C
new file mode 100644
index 00000000000..f92f2500531
--- /dev/null
+++ b/gcc/testsuite/g++.dg/abi/lambda-ctx3.C
@@ -0,0 +1,21 @@ 
+// { dg-do compile { target c++20 } }
+// { dg-additional-options "-fkeep-inline-functions" }
+// See also https://github.com/itanium-cxx-abi/cxx-abi/pull/85
+
+struct A {
+  decltype([]{ return 1; }) f;
+};
+
+struct B : decltype([]{ return 2; }) {
+  decltype([]{ return 3; }) f;
+};
+
+struct C : decltype([]{ return 4; }) {
+  decltype([]{ return 5; }) f;
+};
+
+// { dg-final { scan-assembler {_ZNK1AUlvE_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1BUlvE_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1BUlvE0_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1CUlvE_clEv:} } }
+// { dg-final { scan-assembler {_ZNK1CUlvE0_clEv:} } }
diff --git a/gcc/testsuite/g++.dg/modules/lambda-8.h b/gcc/testsuite/g++.dg/modules/lambda-8.h
new file mode 100644
index 00000000000..0c66f053b20
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-8.h
@@ -0,0 +1,7 @@ 
+template <typename> struct S {
+  template <typename> static constexpr auto x = []{};
+  template <typename> using t = decltype([]{});
+};
+
+inline auto x = S<int>::x<int>;
+using t = S<int>::t<int>;
diff --git a/gcc/testsuite/g++.dg/modules/lambda-8_a.H b/gcc/testsuite/g++.dg/modules/lambda-8_a.H
new file mode 100644
index 00000000000..d20958ee140
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-8_a.H
@@ -0,0 +1,5 @@ 
+// PR c++/116568
+// { dg-additional-options "-fmodules-ts -std=c++20" }
+// { dg-module-cmi {} }
+
+#include "lambda-8.h"
diff --git a/gcc/testsuite/g++.dg/modules/lambda-8_b.C b/gcc/testsuite/g++.dg/modules/lambda-8_b.C
new file mode 100644
index 00000000000..05ea4afd8c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/modules/lambda-8_b.C
@@ -0,0 +1,5 @@ 
+// PR c++/116568
+// { dg-additional-options "-fmodules-ts -std=c++20" }
+
+#include "lambda-8.h"
+import "lambda-8_a.H";