llvm / llvm/llvm-project

[AMDGPU] Consider safely coalescing acyclic same-predicate triangle chains with live-out PHIs

Open
#218,331 2 comments 0 reactions 0 assignees View on GitHub
backend:AMDGPU llvm:transforms missed-optimization
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Summary

I would like to discuss a narrowly scoped CFG transformation for consecutive single-arm diamonds (triangles) guarded by exactly the same SSA predicate. This pattern occurs in AMDGPU workloads and can leave redundant divergent branches.

This is **not** a proposal to enable `JumpThreadingPass` for divergent targets or broadly relax SimplifyCFG's live-out restriction. The transformation should apply only after proving the complete CFG, SSA, and convergence safety conditions below.

## Motivating example

Simplified input:

```llvm
define i32 @same_predicate(ptr %a, ptr %b, i1 noundef %p) {
entry:
br i1 %p, label %load.a, label %join.a

load.a:
%va = load i32, ptr %a, align 4
br label %join.a

join.a:
%a.value = phi i32 [ %va, %load.a ], [ 0, %entry ]
br i1 %p, label %load.b, label %join.b

load.b:
%vb = load i32, ptr %b, align 4
br label %join.b

join.b:
%b.value = phi i32 [ %vb, %load.b ], [ 0, %join.a ]
%result = add i32 %a.value, %b.value
ret i32 %result
}
```

The second branch is redundant because it uses the same `%p`, but `%a.value` is live beyond `join.a`. This prevents the existing generic SimplifyCFG threading from redirecting the edge.

A possible result is:

```llvm
define i32 @same_predicate(ptr %a, ptr %b, i1 noundef %p) {
entry:
br i1 %p, label %load.a, label %join.b

load.a:
%va = load i32, ptr %a, align 4
br label %load.b

load.b:
%vb = load i32, ptr %b, align 4
br label %join.b

join.b:
%a.value = phi i32 [ %va, %load.b ], [ 0, %entry ]
%b.value = phi i32 [ %vb, %load.b ], [ 0, %entry ]
%result = add i32 %a.value, %b.value
ret i32 %result
}
```

For `%p == true`, both versions execute `load.a` followed by `load.b`. For `%p == false`, both loads are skipped and both PHIs select their fallback values.

## Conservative safety boundary

The transformation would be attempted only when all of the following are proven:

- Every branch uses the exact same `Value *` condition and the same active-successor polarity.
- Each body has one predecessor and one unconditional successor, each join has exactly the expected two predecessors, and there are no side entries or exits.
- Intermediate joins contain only PHIs, debug information, and the conditional branch, and no removed block has its address taken.
- The complete candidate is outside a cycle. Initially, the transform should be rejected whenever the final join may reach the first head.
- Initially, bodies contain only side-effect-free instructions and simple non-volatile loads. All `CallBase`, writes, volatile or atomic operations, fences, barriers, inline assembly, and convergence- or WQM/WWM-sensitive intrinsics are rejected.
- Every moved PHI incoming value dominates its new incoming edge.
- A moved PHI has no semantic use before the final join and is not used as an incoming value of another PHI at that join.
- Existing PHIs in the final join can have their bypass predecessor remapped without introducing a dominance violation.

These instruction restrictions are deliberately conservative and can be reconsidered separately after the basic transformation is established.

For example, this case must not be transformed by merely moving `%a.value` to `join.b`:

```llvm
join.a:
%a.value = phi i32 [ %va, %load.a ], [ 0, %entry ]
br i1 %p, label %load.b, label %join.b

load.b:
%use.before.final.join = add i32 %a.value, 1
br label %join.b
```

After the move, `%a.value` would no longer dominate its use in `load.b`. PHI operands require the same edge-sensitive treatment.

## Possible implementation direction

One possible implementation is a dedicated `SimplifyCFG` helper, called only when existing predecessor-known threading makes no change:

1. Collect a maximal eligible chain without mutating the IR.
2. Validate all structural, dominance, PHI-use, cycle, and instruction conditions.
3. Move the existing PHIs to the final join, remap incoming blocks, and replace redundant branches.
4. Apply dominator-tree updates together and let normal cleanup remove trivial blocks.

This should require neither `SSAUpdater`, instruction cloning, nor new PHIs. A single whole-chain commit also avoids repeatedly moving accumulated PHIs.

`FlattenCFG` could instead provide a target-scoped fallback if the pattern is shown to arise too late for SimplifyCFG. The logic should not be duplicated in both places.

## Safety and validation

Before enabling this in a default pipeline, the patch should include:

- Positive and negative IR tests covering both polarities, multiple PHIs, malformed CFGs, PHI edge uses, cycles, side effects, convergence, and WQM/WWM operations.
- `-verify-each`, dominator-tree, debug-info, and long-chain compile-time coverage.
- An AMDGPU end-to-end test showing that the redundant mask branch is removed without changing convergence-sensitive cases.
- Alive2 checks for target-independent semantics, with separate AMDGPU tests for convergence and EXEC-mask behavior.

Does this sufficiently narrow the legality boundary, and does `SimplifyCFG` seem like the appropriate owner for such a transformation?

Contributor guide

Open the contributing guide

Research direction

Start by reading SimplifyCFG's existing predecessor-known threading and compare it with the proposed dedicated helper; FlattenCFG is mentioned as an alternative owner. Done requires a validated whole-chain transformation plus positive and negative IR tests covering PHIs, cycles, side effects, convergence, and WQM/WWM operations, along with verifier, dominator-tree, debug-info, compile-time, and AMDGPU validation.

Written by the indexing model from the issue text.

Assessment

Domain
compilers
Issue type
Feature
Difficulty
5/5
Estimated time
Over a week
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.