Skip to content

[SimplifyCFG] Simplify identical predecessors - #173022

Merged
Camsyn merged 23 commits into
llvm:mainfrom
Camsyn:merge-dup-pred
Mar 12, 2026
Merged

[SimplifyCFG] Simplify identical predecessors#173022
Camsyn merged 23 commits into
llvm:mainfrom
Camsyn:merge-dup-pred

Conversation

@Camsyn

@Camsyn Camsyn commented Dec 19, 2025

Copy link
Copy Markdown
Contributor

When >1 predecessors of BB are identical, try to merge them into ONE.


Here is a simplified example (sink and bb*s share the same predecessor entry, hindering the existing uncond br folding to optimize such a case):

- entry:
-   switch to %br1, %br2, %br3, %sink
- bb1:
-   br label %sink
- bb2:
-   br label %sink
- bb3:
-   br label %sink
- sink:
-   %ret = phi i8 [ 0, %bb1 ], [ 0, %bb2 ], [ 0, %bb3 ], [ -1, %entry ]
+ entry:
+   switch to %br1, %sink
+ bb1:
+   br label %sink
+ sink:
+   %ret = phi i8 [ 0, %bb1 ], [ -1, %entry ]

Actually, simplifyDuplicateSwitchArms did similar things in a very limited scope (only for switch arms); this patch generalizes its logic to handle any BB with >1 identical predecessors.


This PR lands the discussion, i.e., "merge identical predecessor bottom to up", and implements the suggestion of #114262 (comment).

@Camsyn Camsyn self-assigned this Dec 19, 2025
@github-actions

github-actions Bot commented Dec 19, 2025

Copy link
Copy Markdown

✅ With the latest revision this PR passed the C/C++ code formatter.

@github-actions

github-actions Bot commented Dec 19, 2025

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 131887 tests passed
  • 2982 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented Dec 19, 2025

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 191759 tests passed
  • 4899 tests skipped

✅ The build succeeded and all tests passed.

@nikic

nikic commented Dec 19, 2025

Copy link
Copy Markdown
Contributor

This probably subsumes simplifyDuplicateSwitchArms?

@Camsyn

Camsyn commented Dec 20, 2025

Copy link
Copy Markdown
Contributor Author

This probably subsumes simplifyDuplicateSwitchArms?

Yes, it is indeed a generalization of simplifyDuplicateSwitchArms. I will replace simplifyDuplicateSwitchArms with this optimization after validating its functionality.

Comment thread llvm/test/Transforms/LoopDeletion/simplify-then-delete.ll Outdated
@Camsyn Camsyn changed the title [SimplifyCFG] Simplify identical predessors [SimplifyCFG] Simplify identical predecessors Dec 20, 2025
Comment thread llvm/test/CodeGen/AMDGPU/multi-divergent-exit-region.ll
Comment on lines +8978 to +8980
// Merge identical predecessors of this block
if (simplifyDuplicatePredecessors(BB, DTU))
return true;

@Camsyn Camsyn Dec 23, 2025

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we treat identical predecessor merging as part of sink-common-insts, and perform it after sinkCommonCodeFromPredecessors?

This avoids interfering with earlier optimizations that rely on special basic block structures (e.g., unconditioned branch folding, speculativelyExecuteBB and jump-threading).

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, we probably do not want to do this transform too early. Not sure whether having the same phase ordering as sink-common-insts is right, but it's probably a reasonable starting point...

return false;

// The BB must have at least one predecessor.
if (!BB->hasNPredecessorsOrMore(1))

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (!BB->hasNPredecessorsOrMore(1))
if (pred_empty(BB))

return false;

// TODO: relax this condition to merge equal blocks with >1 instructions?
if (BB->size() != 1)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is calculated in linear time:

/// Calculate the size of the list in linear time.

@Camsyn Camsyn Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is directly inherited from the original code.
Here, we can replace it with a O(1) judgment &BB->front() != &BB->back().

@dtcxzyw

dtcxzyw commented Mar 4, 2026

Copy link
Copy Markdown
Member

Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/lib/Transforms/Utils/SimplifyCFG.cpp Outdated
Comment thread llvm/test/Transforms/SimplifyCFG/dup-preds.ll Outdated
@Camsyn

Camsyn commented Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

Seems the comptime issue has been resolved.

dtcxzyw/llvm-opt-benchmark#3538


// Avoid blocks that are "address-taken" (blockaddress) or have unusual
// uses.
if (BB->hasAddressTaken() || BB->isLandingPad())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like these conditions don't have test coverage?

Also, are you sure you want to exclude landingpads specifically, rather than EH pads in general?

@Camsyn Camsyn Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes. We should use isEHPad().


I have added a new test to cover BB->hasAddressTaken() .


BB->isEHPad() is hard to cover as there are no TWO identical EHPad BBs with only ONE unconditioned branch.
I added this guard just for future usage.

return false;

// TODO: relax this condition to merge equal blocks with >1 instructions?
if (/* I.e., O(n) calc: size() != 1 */ &BB->front() != &BB->back())

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Move this comment out of the condition.

Comment on lines +8063 to +8064
Succ->phis(), [BB, &PhiPredIVs = *EBW->PhiPredIVs](PHINode &Phi) {
return PhiPredIVs[&Phi][BB];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Succ->phis(), [BB, &PhiPredIVs = *EBW->PhiPredIVs](PHINode &Phi) {
return PhiPredIVs[&Phi][BB];
Succ->phis(), [&](PHINode &Phi) {
return EBW->PhiPredIVs[&Phi][BB];

The explicit captures here look more confusing than helpful.

BasicBlock *KeepBB = (*It)->BB;
BasicBlock *DeadBB = EBW.BB;

// Avoid merging if either is the other's predecessor in weird ways.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is this really what this condition is guarding against? I'd have thought this would occur if the same block occurs multiple times in the candidate list, rather than some kind of relationship between the blocks.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am sorry I forgot the context of this comment due to some rebases, but it is now just a useless comment.

SmallSetVector<BasicBlock *, 16> FilteredArms(
llvm::from_range,
make_filter_range(successors(SI), EqualBBWrapper::canBeMerged));
return mergeIdenticalBBs(FilteredArms.getArrayRef(), DTU);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please explain why we still need the special switch handling next to the predecessor based logic?

@Camsyn Camsyn Mar 5, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They perform at different stages.

I indeed tried completely replacing "switch arm merging" with "identical pred merging" to consider unification; however, it triggered a regression due to the delay of "switch arm merging" (implemented by "identical pred merging").

The regression chain is too long to clarify here: dtcxzyw/llvm-opt-benchmark#3204 (comment)

Finally, I decided to keep the original logic of "switch arm merging" and append "identical pred merging" as new functionality.

@Camsyn

Camsyn commented Mar 10, 2026

Copy link
Copy Markdown
Contributor Author

Gentle ping😀.

@nikic nikic left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Camsyn
Camsyn merged commit fddc2c0 into llvm:main Mar 12, 2026
10 checks passed
@llvm-ci

llvm-ci commented Mar 12, 2026

Copy link
Copy Markdown

LLVM Buildbot has detected a new failure on builder sanitizer-x86_64-linux-fast running on sanitizer-buildbot3 while building llvm at step 2 "annotate".

Full details are available at: https://lab.llvm.org/buildbot/#/builders/169/builds/20839

Here is the relevant piece of the build log for the reference
Step 2 (annotate) failure: 'python ../sanitizer_buildbot/sanitizers/zorg/buildbot/builders/sanitizers/buildbot_selector.py' (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using lld-link: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using lld-link: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 97233 tests, 64 workers --
Testing: 
FAIL: LLVM :: ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll (1 of 97233)
******************** TEST 'LLVM :: ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 3
/home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lli -jit-kind=orc-lazy -orc-lazy-debug=funcs-to-stdout -extra-module /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll    /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/Inputs/noop-main.ll | /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/FileCheck /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# executed command: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lli -jit-kind=orc-lazy -orc-lazy-debug=funcs-to-stdout -extra-module /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/Inputs/noop-main.ll
# .---command stderr------------
# | JIT session error: In graph /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll.submodule.0x15e966b30c7cdabd.ll-jitted-objectbuffer, section .text: relocation target 0x7dc39ec1f02c (__orc_lcl.str3.3:0x7dc39ec1f030 + 0xfffffffffffffffc) is out of range of Delta32 fixup at address 0x7dc39ec1f030 (printf_wrapper, 0x79c39dc95000 + 0x3)
# | JIT session error: Failed to materialize symbols: { (main.impl, { printf_wrapper }) }
# `-----------------------------
# error: command failed with exit status: 1
# executed command: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/FileCheck /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# .---command stderr------------
# | /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll:16:10: error: CHECK: expected string not found in input
# | ; CHECK: Goodbye from destructor
# |          ^
# | <stdin>:13:26: note: scanning from here
# | Goodbye from __cxa_atexit
# |                          ^
# | <stdin>:14:3: note: possible intended match here
# | [ printf_wrapper ]
# |   ^
# | 
# | Input file: <stdin>
# | Check file: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |             .
# |             .
# |             .
# |             8: [ main ] 
# |             9: [ __lljit_run_atexits atexit ] 
# |            10: [ atexit_handler ] 
Step 10 (stage2/asan_ubsan check) failure: stage2/asan_ubsan check (failure)
...
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using lld-link: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using lld-link: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lld-link
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using ld64.lld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/ld64.lld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/llvm/config.py:569: note: using wasm-ld: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/wasm-ld
llvm-lit: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/utils/lit/lit/main.py:74: note: The test suite configuration requested an individual test timeout of 0 seconds but a timeout of 900 seconds was requested on the command line. Forcing timeout to be 900 seconds.
-- Testing: 97233 tests, 64 workers --
Testing: 
FAIL: LLVM :: ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll (1 of 97233)
******************** TEST 'LLVM :: ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll' FAILED ********************
Exit Code: 1

Command Output (stdout):
--
# RUN: at line 3
/home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lli -jit-kind=orc-lazy -orc-lazy-debug=funcs-to-stdout -extra-module /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll    /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/Inputs/noop-main.ll | /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/FileCheck /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# executed command: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/lli -jit-kind=orc-lazy -orc-lazy-debug=funcs-to-stdout -extra-module /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/Inputs/noop-main.ll
# .---command stderr------------
# | JIT session error: In graph /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll.submodule.0x15e966b30c7cdabd.ll-jitted-objectbuffer, section .text: relocation target 0x7dc39ec1f02c (__orc_lcl.str3.3:0x7dc39ec1f030 + 0xfffffffffffffffc) is out of range of Delta32 fixup at address 0x7dc39ec1f030 (printf_wrapper, 0x79c39dc95000 + 0x3)
# | JIT session error: Failed to materialize symbols: { (main.impl, { printf_wrapper }) }
# `-----------------------------
# error: command failed with exit status: 1
# executed command: /home/b/sanitizer-x86_64-linux-fast/build/llvm_build_asan_ubsan/bin/FileCheck /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# .---command stderr------------
# | /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll:16:10: error: CHECK: expected string not found in input
# | ; CHECK: Goodbye from destructor
# |          ^
# | <stdin>:13:26: note: scanning from here
# | Goodbye from __cxa_atexit
# |                          ^
# | <stdin>:14:3: note: possible intended match here
# | [ printf_wrapper ]
# |   ^
# | 
# | Input file: <stdin>
# | Check file: /home/b/sanitizer-x86_64-linux-fast/build/llvm-project/llvm/test/ExecutionEngine/OrcLazy/global-ctors-and-dtors.ll
# | 
# | -dump-input=help explains the following input dump.
# | 
# | Input was:
# | <<<<<<
# |             .
# |             .
# |             .
# |             8: [ main ] 
# |             9: [ __lljit_run_atexits atexit ] 
# |            10: [ atexit_handler ] 

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants