https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128198

            Bug ID: 128198
           Summary: Wrong points-to set after unification in the PTA
                    solver leads to infinite loop at -O2
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: tree-optimization
          Assignee: unassigned at gcc dot gnu.org
          Reporter: mix at emboss dot co.nz
  Target Milestone: ---

Created attachment 65978
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=65978&action=edit
Minimized test case

Bug was found as a result of an unexplainable process hang. Minimization and
triage was performed using an LLM as it's very sensitive to code remote to the
point of failure. I have included the LLM analysis, and it looks plausible, but
I am not familiar with the code so take it with a grain of salt.

The attached test case fails for gcc on x86-64 at -O2, works correctly (prints
"4") at other optimization levels or if "-fno-tree-pta" is used.

Godbolt link: https://godbolt.org/z/Ehd4Md8qY

See line 34 in the assembler:
    .L6:
        jmp     .L6

--- LLM triage follows ---

GCC's own points-to solution violates its own constraints, and a later fold
acts on the wrong solution.
- **Root cause: points-to analysis** (`-ftree-pta`, the `alias` pass, Andersen
solver). While solving, the local array
  `fixed` is unified into an SSA node (`_25`) whose solution has already been
propagated (`solution == oldsolution ==
  {NULL, HEAP}`). The successor edges `fixed` brings along (the later loops'
loads `b = c->start`, `e = c->finish`)
  therefore never receive `HEAP`, and the loads end with the points-to set
`{NULL}`, although the constraint
  `b ⊇ *c` with `c ∋ fixed ∋ HEAP` requires `{NULL, HEAP}`.
- **Consumer: loop header copying** (`-ftree-ch`, pass `ch2`). Its CFG cleanup
folds the inner loop's exit test
  `b + 1 != e` to true through `ptrs_compare_unequal` (match.pd:8475): `b + 1`
points to nothing and is nonnull, `e`
  points only to NULL. DCE empties the loop, leaving `jmp .`.

## Evidence

1. The PTA constraints and solution for `main` (`-fdump-tree-alias-details`):

   ```
   _25 = *c_2                 // start = c->start (first loop)
   p_39 = &HEAP(26)           // malloc
   tmp.0+64 = p_39 ; tmp.0+64 = _25 ; s_28 = tmp.0+64 ; *c_2 = s_28
   c_2 = &fixed ; ...
   b_16 = *c_5 ; e_17 = *c_5 + 64 ; c_5 = &fixed ; ...
   Direct node id 19 "tmp.0+64" mapped to SCC leader node id 16 "_25"
   Direct node id 29 "s_28" mapped to SCC leader node id 16 "_25"
   Solving graph
   Unifying fixed to _25
   fixed = { NULL HEAP(26) } same as _25
   c_5 = { fixed }
   b_16 = { NULL }            <-- violates b_16 ⊇ *c_5
   e_17 = { NULL }
   ```

2. The solver state at that unification (gdb on cc1, `unify_nodes (to=_25,
from=fixed, update_changed=true)`):
   `to->solution = to->oldsolution = {NULL, HEAP}`; `from->solution = {NULL}`,
`from->oldsolution = NULL`;
   `succs[from] = {b_16, e_17, ...}`, `succs[to] = {}`. The delta of `_25` is
empty, so HEAP is never pushed along the
   edges inherited from `fixed`. (Reading of the dumped state; the GCC sources
were not at hand, so the exact line is
   for GCC's developers to confirm. Clearing `to->oldsolution` when `from`
brings new successors would likely fix it.)

3. The fold in ch2 (`-fdump-tree-ch2-details-folding`): `Merging blocks 14 and
15 ... Applying pattern match.pd:8475`;
   call path `gimple_merge_blocks → replace_uses_by → fold_stmt →
gimple_simplify_NE_EXPR → ptrs_compare_unequal`,
   with `PT(b+1) = {}` and `PT(e) = {NULL}`; `if (b' != e)` becomes `if (1 !=
0)`.

4. The result: `<bb 10>: goto <bb 10>;`, i.e. `.L6: jmp .L6`.

Reply via email to