SimplifyCFG hoists musttail call above branch, breaking 'musttail must precede ret' invariant
- Dominant language
- LLVM
- Stars
- 40.5k
- Forks
- 18.7k
- PR merge metrics
- PR metrics pending
Description
Disclaimer: This issue is filed by Claude Opus 4.6 based on an actual bug. LLM helped narrowing down the bug and creating this minimal reproducer.
## Description
`SimplifyCFG` with `hoist-common-insts` hoists a `musttail call` above a conditional branch, separating it from its `ret` instruction. This violates the LLVM IR invariant that a `musttail call` must be immediately followed by a `ret` (with an optional bitcast).
The issue is triggered when two branches each contain a `musttail call` to the same `internal noinline` callee with identical arguments. Earlier optimization passes (IPSCCP/DeadArgElim/InstCombine) replace unused callee arguments with `poison` at call sites, making the two `musttail call` instructions appear identical. SimplifyCFG then treats them as a common instruction and hoists the call into the predecessor block, but the stores between the branch and the `musttail call` differ between the two paths, so they cannot be hoisted. This results in a `musttail call` followed by a `br` instead of a `ret`.
## Reproducer
```llvm
; RUN: opt -O2 -S %s -o /dev/null
; RUN: opt -O1 -S %s -o /dev/null
define internal i64 @callee(ptr %0, ptr %1, i64 %2) #0 {
ret i64 0
}
define i64 @caller(ptr %env, ptr %tb, i64 %arg) {
entry:
%val = load i64, ptr %env, align 8
%cond = icmp eq i64 %val, 0
br i1 %cond, label %path_b, label %path_a
path_a:
%p_a = getelementptr i8, ptr %env, i64 8
store i64 100, ptr %p_a, align 8
%r_a = musttail call i64 @callee(ptr %env, ptr null, i64 0)
ret i64 %r_a
path_b:
%p_b = getelementptr i8, ptr %env, i64 8
store i64 200, ptr %p_b, align 8
%r_b = musttail call i64 @callee(ptr %env, ptr null, i64 0)
ret i64 %r_b
}
attributes #0 = { noinline }
```
## Error output
```
musttail call must precede a ret with an optional bitcast
%r_b = musttail call i64 @callee(ptr nonnull poison, ptr poison, i64 poison)
LLVM ERROR: Broken module found, compilation aborted!
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
```
## Root cause analysis
1. **CGSCC passes** (function-attrs, IPSCCP, instcombine) determine that `@callee`'s arguments 1 and 2 are unused, and replace them with `poison` at call sites. Both `musttail call` instructions become identical.
2. **SimplifyCFG** with `hoist-common-insts` sees two identical `musttail call` instructions at the end of `path_a` and `path_b`, and hoists the call into the `entry` block. However, the preceding `store` instructions differ (storing 100 vs 200), so they remain in the separate blocks.
3. The resulting IR has:
```llvm
entry:
...
%r_b = musttail call i64 @callee(...)
br i1 %cond, label %path_b, label %path_a ; ILLEGAL: musttail not followed by ret
path_a:
store i64 100, ...
br label %common.ret
path_b:
store i64 200, ...
br label %common.ret
common.ret:
ret i64 %r_b
```
SimplifyCFG should not hoist `musttail call` instructions since they must remain immediately before `ret`.
## Isolated pass reproducer
After running the CGSCC pipeline, the bug can be triggered with a single pass:
```
opt -p='simplifycfg' -S intermediate.ll -o /dev/null
```
where `intermediate.ll` contains the post-CGSCC IR with `poison` arguments already in place.
## Version
LLVM 21.1.8 (x86_64-pc-linux-gnu)
Contributor guide
Assessment
This issue has not been assessed yet.