Bug 100252 - [11/12 Regression] Internal compiler error during template instantiation
Summary: [11/12 Regression] Internal compiler error during template instantiation
Status: ASSIGNED
Alias: None
Product: gcc
Classification: Unclassified
Component: c++ (show other bugs)
Version: 10.3.1
: P2 normal
Target Milestone: 11.5
Assignee: Marek Polacek
URL:
Keywords: ice-on-valid-code
Depends on: 105550
Blocks: NSDMI
  Show dependency treegraph
 
Reported: 2021-04-25 03:08 UTC by Jeremy R.
Modified: 2024-02-21 16:58 UTC (History)
3 users (show)

See Also:
Host:
Target:
Build:
Known to work:
Known to fail: 10.3.0, 11.0, 12.0
Last reconfirmed: 2021-04-26 00:00:00


Attachments

Note You need to log in before you can comment on or make changes to this bug.
Description Jeremy R. 2021-04-25 03:08:34 UTC
An internal compiler error occurs while compiling this template code: https://godbolt.org/z/rP9Wf97vP.

Error message:
<source>: In member function 'typename example::Ac<B>::gt<I>::k& tcc<A>::gr() [with int I = 1; A = {float, int}]':
<source>:97:62: internal compiler error: in replace_placeholders_r, at cp/tree.c:3332
   97 |         return typename example::Ac<A...>::template gt<I>{k}.K;
      |                                                     ~~~~~~~~~^
0x1cff079 internal_error(char const*, ...)
	???:0
0x6bac69 fancy_abort(char const*, int, char const*)
	???:0
0x13971f3 walk_tree_1(tree_node**, tree_node* (*)(tree_node**, int*, void*), void*, hash_set<tree_node*, false, default_hash_traits<tree_node*> >*, tree_node* (*)(tree_node**, int*, tree_node* (*)(tree_node**, int*, void*), void*, hash_set<tree_node*, false, default_hash_traits<tree_node*> >*))
	???:0
Comment 1 Jeremy R. 2021-04-25 15:31:13 UTC
A more minimal case: https://godbolt.org/z/jxP9e35bz
Comment 2 Jeremy R. 2021-04-26 00:47:44 UTC
Even more minimal case: https://godbolt.org/z/M3Tv9oqcn
Comment 3 Richard Biener 2021-04-26 07:28:48 UTC
template<int I>
struct E{
    int &K;
    decltype(E<I-1>::k) &k = E<I-1>{K}.k;
};

template<>
struct E<0>{
    int &K;
    int &k = K;
};

int main(){
    int r;
    E<1>{r}.k = 7;
}
Comment 4 Marek Polacek 2021-04-26 14:37:02 UTC
Started with r216750.
Comment 5 Patrick Palka 2021-04-26 21:11:17 UTC
Reduced:

struct A {
  int x;
  int y = x;
};

struct B {
  int x = 0;
  int y = A{x}.y;
};

B b = {};
Comment 6 Jakub Jelinek 2021-05-14 09:54:53 UTC
GCC 8 branch is being closed.
Comment 7 Richard Biener 2021-06-01 08:20:38 UTC
GCC 9.4 is being released, retargeting bugs to GCC 9.5.
Comment 8 Marek Polacek 2022-04-25 22:43:51 UTC
This is tricky, because we end up with

{.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}

that is, two PLACEHOLDER_EXPRs for different types on the same level in one { }, so our CONSTRUCTOR_PLACEHOLDER_BOUNDARY mechanism to avoid replacing unrelated PLACEHOLDER_EXPRs will not work.
Comment 9 Marek Polacek 2022-04-26 15:54:32 UTC
When we are cp_parser_late_parsing_nsdmi for "int y = A{x}.y;" we perform finish_compound_literal on type=A, compound_literal={((struct B *) this)->x}.  When digesting this initializer, we call get_nsdmi which creates a PLACEHOLDER_EXPR for A -- we don't have any object to refer to yet.  After digesting, we have

  {.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}

and since we've created a PLACEHOLDER_EXPR inside it, we marked the whole ctor CONSTRUCTOR_PLACEHOLDER_BOUNDARY.  f_c_l creates a TARGET_EXPR and returns

  TARGET_EXPR <D.2384, {.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>

(*)

Then we get to

  B b = {};

and call store_init_value, which digest the {}, which produces

  {.x=NON_LVALUE_EXPR <0>, .y=(TARGET_EXPR <D.2395, {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>).y}

The call to replace_placeholders in store_init_value will not do anything: we've marked the inner { } CONSTRUCTOR_PLACEHOLDER_BOUNDARY, and it's only a sub-expression, so replace_placeholders does nothing, so the <PLACEHOLDER_EXPR struct B> stays even though now it was the perfect time to replace it because we have an object for it: 'b'.

Later, in cp_gimplify_init_expr the *expr_p is

  D.2395 = {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}

where D.2395 is of type A, but we crash because we hit <PLACEHOLDER_EXPR struct B>, which has a different type.


Now here's an idea how we could fix this: at the (*) point in finish_compound_literal we could replace <PLACEHOLDER_EXPR struct A> with D.2384 because now we have an object!  Then clear CONSTRUCTOR_PLACEHOLDER_BOUNDARY, because we no longer have a PLACEHOLDER_EXPR in the {}.  Then store_init_value will be able to replace <PLACEHOLDER_EXPR struct B> with 'b', and we should be good to go.
Comment 10 Marek Polacek 2022-04-26 15:58:06 UTC
Proof of concept:

--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -3296,6 +3296,14 @@ finish_compound_literal (tree type, tree compound_literal,
       if (TREE_CODE (compound_literal) == CONSTRUCTOR)
 	TREE_HAS_CONSTRUCTOR (compound_literal) = false;
       compound_literal = get_target_expr_sfinae (compound_literal, complain);
+      if (parsing_nsdmi ())
+	{
+	  tree obj = TARGET_EXPR_SLOT (compound_literal);
+	  tree &ctor = TARGET_EXPR_INITIAL (compound_literal);
+	  replace_placeholders (compound_literal, obj);
+	  if (TREE_CODE (ctor) == CONSTRUCTOR)
+	    CONSTRUCTOR_PLACEHOLDER_BOUNDARY (ctor) = false;
+	}
     }
   else
     /* For e.g. int{42} just make sure it's a prvalue.  */
Comment 11 GCC Commits 2022-05-25 15:01:40 UTC
The trunk branch has been updated by Marek Polacek <mpolacek@gcc.gnu.org>:

https://gcc.gnu.org/g:1b661f3f5e712c951e774b3b91fffe4dac734cc7

commit r13-765-g1b661f3f5e712c951e774b3b91fffe4dac734cc7
Author: Marek Polacek <polacek@redhat.com>
Date:   Tue Apr 26 15:52:00 2022 -0400

    c++: ICE with temporary of class type in DMI [PR100252]
    
    Consider
    
      struct A {
        int x;
        int y = x;
      };
    
      struct B {
        int x = 0;
        int y = A{x}.y; // #1
      };
    
    where for #1 we end up with
    
      {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
    
    that is, two PLACEHOLDER_EXPRs for different types on the same level in
    a {}.  This crashes because our CONSTRUCTOR_PLACEHOLDER_BOUNDARY mechanism to
    avoid replacing unrelated PLACEHOLDER_EXPRs cannot deal with it.
    
    Here's why we wound up with those PLACEHOLDER_EXPRs: When we're performing
    cp_parser_late_parsing_nsdmi for "int y = A{x}.y;" we use finish_compound_literal
    on type=A, compound_literal={((struct B *) this)->x}.  When digesting this
    initializer, we call get_nsdmi which creates a PLACEHOLDER_EXPR for A -- we don't
    have any object to refer to yet.  After digesting, we have
    
      {.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
    
    and since we've created a PLACEHOLDER_EXPR inside it, we marked the whole ctor
    CONSTRUCTOR_PLACEHOLDER_BOUNDARY.  f_c_l creates a TARGET_EXPR and returns
    
      TARGET_EXPR <D.2384, {.x=((struct B *) this)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>
    
    Then we get to
    
      B b = {};
    
    and call store_init_value, which digests the {}, which produces
    
      {.x=NON_LVALUE_EXPR <0>, .y=(TARGET_EXPR <D.2395, {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}>).y}
    
    lookup_placeholder in constexpr won't find an object to replace the
    PLACEHOLDER_EXPR for B, because ctx->object will be D.2395 of type A, and we
    cannot search outward from D.2395 to find 'b'.
    
    The call to replace_placeholders in store_init_value will not do anything:
    we've marked the inner { } CONSTRUCTOR_PLACEHOLDER_BOUNDARY, and it's only
    a sub-expression, so replace_placeholders does nothing, so the <P_E struct B>
    stays even though now is the perfect time to replace it because we have an
    object for it: 'b'.
    
    Later, in cp_gimplify_init_expr the *expr_p is
    
      D.2395 = {.x=(&<PLACEHOLDER_EXPR struct B>)->x, .y=(&<PLACEHOLDER_EXPR struct A>)->x}
    
    where D.2395 is of type A, but we crash because we hit <P_E struct B>, which
    has a different type.
    
    My idea was to replace <P_E struct A> with D.2384 after creating the
    TARGET_EXPR because that means we have an object we can refer to.
    Then clear CONSTRUCTOR_PLACEHOLDER_BOUNDARY because we no longer have
    a PLACEHOLDER_EXPR in the {}.  Then store_init_value will be able to
    replace <P_E struct B> with 'b', and we should be good to go.  We must
    be careful not to break guaranteed copy elision, so this replacement
    happens in digest_nsdmi_init where we can see the whole initializer,
    and avoid replacing any placeholders in TARGET_EXPRs used in the context
    of initialization/copy elision.  This is achieved via the new function
    called potential_prvalue_result_of.
    
    While fixing this problem, I found PR105550, thus the FIXMEs in the
    tests.
    
            PR c++/100252
    
    gcc/cp/ChangeLog:
    
            * typeck2.cc (potential_prvalue_result_of): New.
            (replace_placeholders_for_class_temp_r): New.
            (digest_nsdmi_init): Call it.
    
    gcc/testsuite/ChangeLog:
    
            * g++.dg/cpp1y/nsdmi-aggr14.C: New test.
            * g++.dg/cpp1y/nsdmi-aggr15.C: New test.
            * g++.dg/cpp1y/nsdmi-aggr16.C: New test.
            * g++.dg/cpp1y/nsdmi-aggr17.C: New test.
            * g++.dg/cpp1y/nsdmi-aggr18.C: New test.
            * g++.dg/cpp1y/nsdmi-aggr19.C: New test.
Comment 12 Marek Polacek 2022-05-25 15:03:28 UTC
Fixed on trunk so far.
Comment 13 Richard Biener 2022-05-27 09:45:10 UTC
GCC 9 branch is being closed
Comment 14 Jakub Jelinek 2022-06-28 10:44:41 UTC
GCC 10.4 is being released, retargeting bugs to GCC 10.5.
Comment 15 Richard Biener 2023-07-07 10:39:38 UTC
GCC 10 branch is being closed.