https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105256
--- Comment #18 from Jakub Jelinek <jakub at gcc dot gnu.org> --- The rationale for introduction of CONSTRUCTOR_PLACEHOLDER_BOUNDARY was given in the https://gcc.gnu.org/legacy-ml/gcc-patches/2018-03/threads.html#00681 thread, not all PLACEHOLDER_EXPRs nested in an expression should be replaced by the same object, if there are e.g. nested TARGET_EXPRs with PLACEHOLDER_EXPRs in their initializers, those nested PLACEHOLDER_EXPRs should be replaced by the TARGET_EXPR's slot rather than the outermost expression. Before the r8-7334-g570f86f94eca76fbf, the PLACEHOLDER_EXPR was replaced from build_over_call, but that has removed. The expectation was that we'd always have some object (var being constructed, TARGET_EXPR slot, etc. and we'd replace_placeholders when gimplifying INIT_EXPR for it). But on: int bar (int &); struct S { struct T { struct U { int i = bar (i); } u; }; }; void foo (S::T *p) { *p = {}; }; cp_gimplify_init_expr is never called, gimplify_target_expr neither, because we ICE earlier than that. While we have there a TARGET_EXPR when we start gimplification: *NON_LVALUE_EXPR <p> = *(struct T &) &TARGET_EXPR <D.2400, {.u={.i=bar ((int &) &(&<PLACEHOLDER_EXPR struct U>)->i)}}>, seems something has elided it.