Jakub Jelinek <ja...@redhat.com> writes: > Hi! > > My r16-1398 PR120434 ranger during expansion change broke profiled lto > bootstrap on x86_64-linux, the following testcase is reduced from that. > > The problem is during expand_gimple_cond, if we are unlucky that neither > of edge_true and edge_false point to the next basic block, the code > effectively attempts to split the false_edge and make the new bb BB_RTL > with some extra instructions which just arranges to jump. > It does it by creating a new bb, redirecting the false_edge and then > creating a new edge from the new bb to the dest. > Note, we don't have GIMPLE cfg hooks installed anymore and even if we > would, the 3 calls aren't the same as one split_edge with transformation > of the new bb into BB_RTL and adding it some BB_HEAD/BB_END. If > false_edge->dest is BB_RTL or doesn't have PHI nodes (which before my > patch was always the case because), then this works fine, but with > PHI nodes on false_edge->dest redirect_edge_succ will remove the false_edge > from dest->preds (unordered remove which moves into its place the last edge > in the vector) and the new make_edge will then add the new edge as last > in the vector. So, unless false_edge is the last edge in the dest->preds > vector this effectively swaps the last edge in the vector with > false_edge/its new replacement. > gimple_split_edge solves this by temporarily clearing phi_nodes on dest > (not needed when we don't have GIMPLE hooks), then making the new edge > first and redirecting the old edge (plus restoring phi_nodes on dest). > That way the redirection replaces the old edge with the new one and > PHI arguments don't need adjustment. At the cost of temporarily needing > one more edge in the vector and so if unlucky reallocation. > Doing it like that is one of the options (i.e. just move the > make_single_succ_edge call). This patch instead keeps doing what it did > and just swaps two edges again if needed to restore the PHI behavior > - remember edge_false->dest_idx first if there are PHI nodes in > edge_false->dest and afterwards if new edge's dest_idx is different from > the remembered one, swap the new edge with EDGE_PRED (dest, old_dest_idx). > That way PHI arguments are maintained properly as well. Without this > we sometimes just swap PHI arguments. > > Bootstrapped/regtested on x86_64-linux and i686-linux, ok for trunk? > > 2025-06-12 Jakub Jelinek <ja...@redhat.com> > > PR middle-end/120629 > * cfgexpand.cc (expand_gimple_cond): If dest bb isn't BB_RTL, > has any PHI nodes and false_edge->dest_idx before redirection is > different from make_single_succ_edge result's dest_idx, swap the > latter with the former last pred edge and their dest_idx members. > > * g++.dg/opt/pr120629.C: New test.
This isn't really my area, but Jakub helped me with some of the details off-list. And given the description of the problem, the fix seems natural enough, even if we later decide to do things differently. So LGTM. Very minor, but: > > --- gcc/cfgexpand.cc.jj 2025-06-11 19:28:52.462056696 +0200 > +++ gcc/cfgexpand.cc 2025-06-12 00:05:27.524152553 +0200 > @@ -3013,6 +3013,9 @@ expand_gimple_cond (basic_block bb, gcon > > new_bb = create_basic_block (NEXT_INSN (last), get_last_insn (), bb); > dest = false_edge->dest; > + unsigned int dest_idx = 0; > + if ((dest->flags & BB_RTL) == 0 && phi_nodes (dest)) > + dest_idx = false_edge->dest_idx; There didn't seem to be any particular need to keep this conditional. OK from my POV either way though. Thanks, Richard > redirect_edge_succ (false_edge, new_bb); > false_edge->flags |= EDGE_FALLTHRU; > new_bb->count = false_edge->count (); > @@ -3021,7 +3024,19 @@ expand_gimple_cond (basic_block bb, gcon > if (loop->latch == bb > && loop->header == dest) > loop->latch = new_bb; > - make_single_succ_edge (new_bb, dest, 0); > + edge e = make_single_succ_edge (new_bb, dest, 0); > + if ((dest->flags & BB_RTL) == 0 > + && phi_nodes (dest) > + && e->dest_idx != dest_idx) > + { > + /* If there are any PHI nodes on dest, swap the new succ edge > + with the one moved into false_edge's former position, so that > + PHI arguments don't need adjustment. */ > + edge e2 = EDGE_PRED (dest, dest_idx); > + std::swap (e->dest_idx, e2->dest_idx); > + std::swap (EDGE_PRED (dest, e->dest_idx), > + EDGE_PRED (dest, e2->dest_idx)); > + } > if (BARRIER_P (BB_END (new_bb))) > BB_END (new_bb) = PREV_INSN (BB_END (new_bb)); > update_bb_for_insn (new_bb); > --- gcc/testsuite/g++.dg/opt/pr120629.C.jj 2025-06-12 00:13:02.928211946 > +0200 > +++ gcc/testsuite/g++.dg/opt/pr120629.C 2025-06-12 00:14:26.008117524 > +0200 > @@ -0,0 +1,53 @@ > +// PR middle-end/120629 > +// { dg-do run } > +// { dg-options "-O2 -fprofile-generate -fno-exceptions -fno-rtti" } > +// { dg-require-profiling "-fprofile-generate" } > + > +__attribute__((noipa, noreturn, cold)) void > +foo (const char *, int, const char *) > +{ > + __builtin_abort (); > +} > + > +struct S > +{ > + __attribute__((noipa)) void bar (void *); > + static const int a = 8; > + unsigned int b[a + 1]; > +}; > + > +__attribute__((noipa)) unsigned long > +baz (void *) > +{ > + static int x = 8; > + return --x; > +} > + > +__attribute__((noipa)) void > +S::bar (void *x) > +{ > + unsigned int c; > + int k = 0; > + > + do > + { > + ((void) (!(k <= a) ? foo ("foo", 42, __FUNCTION__), 0 : 0)); > + c = b[k++] = baz (x); > + } > + while (c); > + while (k <= a) > + b[k++] = 0; > +} > + > +int > +main () > +{ > + struct T { S a; unsigned int b; } s = {}; > + s.b = 0x1234; > + s.a.bar (0); > + for (int i = 0; i < 9; ++i) > + if (s.a.b[i] != (i == 8 ? 0 : 7 - i)) > + __builtin_abort (); > + if (s.b != 0x1234) > + __builtin_abort (); > +} > > Jakub