On 8/8/24 1:37 PM, Marek Polacek wrote:
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?

-- >8 --
The problem in this PR is that we ended up with

   {.rows=(&<PLACEHOLDER_EXPR struct Widget>)->n,
    .outer_stride=(&<PLACEHOLDER_EXPR struct MatrixLayout>)->rows}

that is, two PLACEHOLDER_EXPRs for different types on the same level
in one { }.  That should not happen; we may, for instance, neglect to
replace a PLACEHOLDER_EXPR due to CONSTRUCTOR_PLACEHOLDER_BOUNDARY on
the constructor.

The same problem happened in PR100252, which I fixed by introducing
replace_placeholders_for_class_temp_r.  That didn't work here, though,
because r_p_for_c_t_r only works for non-eliding TARGET_EXPRs: replacing
a PLACEHOLDER_EXPR with a temporary that is going to be elided will
result in a crash in gimplify_var_or_parm_decl when it encounters such
a loose decl.

But leaving the PLACEHOLDER_EXPRs in is also bad because then we end
up with this PR.

TARGET_EXPRs for function arguments are elided in gimplify_arg.  The
argument will get a real temporary only in get_formal_tmp_var.  One
idea was to use the temporary that is going to be elided anyway, and
then replace_decl it with the real object once we get it.  But that
didn't work out: one problem is that we elide the TARGET_EXPR for an
argument before we create the real temporary for the argument, and
when we get it, the context that this was a TARGET_EXPR for an argument
has been lost.  We're also in the middle end territory now, even though
this is a C++-specific problem.

How complex!

I figured that since the to-be-elided temporary is going to stay around
until gimplification, the front end is free to use it.  Once we're done
with things like store_init_value, which replaces PLACEHOLDER_EXPRs with
the decl it is initializing, we can turn those to-be-elided temporaries
into PLACEHOLDER_EXPRs again, so that cp_gimplify_init_expr can replace
them with the real object once available.  The context is not lost so we
do not need an extra flag for these makeshift temporaries.

Clever, that makes a lot of sense. But I wonder if we can avoid the problem more simply than working around it?

I see that the get_formal_tmp_var happens directly from gimplify_arg, so it strips the TARGET_EXPR to avoid a temporary...and then immediately turns around and creates a new temporary.

Would it work to stop stripping the TARGET_EXPR in gimplify_arg and therefore stop setting TARGET_EXPR_ELIDING_P in convert_for_arg_passing?

        PR c++/116015

gcc/cp/ChangeLog:

        * cp-gimplify.cc (replace_argument_temps_with_placeholders): New.
        (cp_genericize_r) <case CALL_EXPR>: Call it.
        * typeck2.cc (replace_placeholders_for_class_temp_r): Do replace
        placeholders in TARGET_EXPRs for function arguments.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp1y/nsdmi-aggr23.C: New test.
---
  gcc/cp/cp-gimplify.cc                     | 24 +++++++++++++++++++++
  gcc/cp/typeck2.cc                         | 23 ++++++++++++++++++++
  gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr23.C | 26 +++++++++++++++++++++++
  3 files changed, 73 insertions(+)
  create mode 100644 gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr23.C

diff --git a/gcc/cp/cp-gimplify.cc b/gcc/cp/cp-gimplify.cc
index 003e68f1ea7..7f203cd6804 100644
--- a/gcc/cp/cp-gimplify.cc
+++ b/gcc/cp/cp-gimplify.cc
@@ -1597,6 +1597,29 @@ predeclare_vla (tree expr)
      }
  }
+/* Replace the temporaries used for function arguments with PLACEHOLDER_EXPRs
+   so that they can be replaced again.  See the comment in
+   replace_placeholders_for_class_temp_r for more context.  CALL is either
+   the CALL_EXPR or AGGR_INIT_EXPR whose arguments we're going to adjust.  */
+
+static void
+replace_argument_temps_with_placeholders (tree call)
+{
+  for (int i = 0; i < call_expr_nargs (call); ++i)
+    {
+      tree arg = get_nth_callarg (call, i);
+      if (TREE_CODE (arg) == TARGET_EXPR && TARGET_EXPR_ELIDING_P (arg))
+       {
+         tree slot = TARGET_EXPR_SLOT (arg);
+         tree placeholder = build0 (PLACEHOLDER_EXPR, TREE_TYPE (slot));
+         tree &init = TARGET_EXPR_INITIAL (arg);
+         if (replace_decl (&init, slot, placeholder)
+             && TREE_CODE (init) == CONSTRUCTOR)
+           CONSTRUCTOR_PLACEHOLDER_BOUNDARY (init) = true;
+       }
+    }
+}
+
  /* Perform any pre-gimplification lowering of C++ front end trees to
     GENERIC.  */
@@ -2125,6 +2148,7 @@ cp_genericize_r (tree *stmt_p, int *walk_subtrees, void *data)
         version is inlinable, a direct call to this version can be made
         otherwise the call should go through the dispatcher.  */
        {
+       replace_argument_temps_with_placeholders (stmt);
        tree fn = cp_get_callee_fndecl_nofold (stmt);
        if (fn && DECL_FUNCTION_VERSIONED (fn)
            && (current_function_decl == NULL
diff --git a/gcc/cp/typeck2.cc b/gcc/cp/typeck2.cc
index 30a6fbe95c9..9b6109ac3ff 100644
--- a/gcc/cp/typeck2.cc
+++ b/gcc/cp/typeck2.cc
@@ -1425,6 +1425,29 @@ replace_placeholders_for_class_temp_r (tree *tp, int *, 
void *)
  {
    tree t = *tp;
+ /* If a TARGET_EXPR is an initializer of a function argument, it is
+     going to be elided in gimplify_arg.  So, we should not be using
+     its slot to replace the PLACEHOLDER_EXPR.  But we will only have
+     the real object once we've gimplified the argument, which is too
+     late: we should replace the PLACEHOLDER_EXPR now to avoid winding
+     up with two different PLACEHOLDER_EXPRs in a single {} (c++/116015).
+
+     We can get away with using the temporary...temporarily, because it's
+     going to survive at least until genericization, where we replace it
+     with a PLACEHOLDER_EXPR again, so that it can be replaced with a real
+     object once the argument has been gimplified.  */
+  if (TREE_CODE (t) == CALL_EXPR || TREE_CODE (t) == AGGR_INIT_EXPR)
+    for (int i = 0; i < call_expr_nargs (t); ++i)
+      {
+       tree arg = get_nth_callarg (t, i);
+       if (TREE_CODE (arg) == TARGET_EXPR && TARGET_EXPR_ELIDING_P (arg))
+         {
+           TARGET_EXPR_ELIDING_P (arg) = false;
+           replace_placeholders_for_class_temp_r (&arg, nullptr, nullptr);
+           TARGET_EXPR_ELIDING_P (arg) = true;
+         }
+      }
+
    /* We're looking for a TARGET_EXPR nested in the whole expression.  */
    if (TREE_CODE (t) == TARGET_EXPR
        /* That serves as temporary materialization, not an initializer.  */
diff --git a/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr23.C 
b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr23.C
new file mode 100644
index 00000000000..2f5b8ca97bf
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/nsdmi-aggr23.C
@@ -0,0 +1,26 @@
+// PR c++/116015
+// { dg-do compile { target c++14 } }
+// { dg-additional-options "-Wno-c++20-extensions" }
+
+struct MatrixLayout {
+    int rows         = 0;
+    int outer_stride = rows;
+};
+struct Matrix {
+    Matrix(MatrixLayout m) {}
+};
+struct Widget {
+    int n = 5;
+    Matrix A0{{}};
+    Matrix A1{{n}};
+    Matrix A1_{{.rows = n}};
+    Matrix A2{{n, n}};
+};
+
+int
+main ()
+{
+ Widget w{};
+ Widget w1;
+ Widget w2 = {};
+}

base-commit: c4d3dba253b49fb0e8e32109783f76453bc53653

Reply via email to