llvm / llvm/llvm-project

SimplifyCFG hoists musttail call above branch, breaking 'musttail must precede ret' invariant

Open
#188,012 0 comments 0 reactions 0 assignees View on GitHub
crash-on-valid llvm:transforms
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

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.