llvm / llvm/llvm-project

[DebugInfo][SimplifyCFG] Folding a branch with a speculatable call causes a crash and produces invalid IR

Open
#220,848 3 comments 0 reactions 0 assignees View on GitHub
confirmed crash-on-valid debuginfo llvm:transforms regression:15
Dominant language
LLVM
Stars
40.5k
Forks
18.7k
PR merge metrics
PR metrics pending

Description

## Description

This crash only occurs when debug information is generated with `-g`; compilation succeeds without `-g`.

`SimplifyCFG` crashes while folding a branch to a common destination when the branch contains a speculatable call. The transformation moves the call out of the branch, but the moved call loses its `!dbg` location.

The resulting IR contains an inlinable call in a function with debug info without a debug location. The verifier then rejects the module and `opt` aborts with an `LLVM ERROR`.

My current understanding of the behavior is:
- Because the call is speculatively moved onto a path where it would not have executed originally, `SimplifyCFG` deliberately drops its `!dbg` location to prevent the debugger from stopping at an incorrect source line.
- However, the LLVM verifier requires a direct call to carry a `!dbg` location when both the caller and callee are debug-info-bearing function definitions in the same module. Otherwise, the inliner cannot correctly construct the callee's debug scope.

## Crash Output

`opt` aborts when the verifier checks the result of `SimplifyCFG`. The complete output from `reproduce.sh` is:

```text
case.c:3:88: warning: comparison of nonnull parameter 'p' not equal to a null pointer is 'true' on first encounter [-Wtautological-pointer-compare]
3 | __attribute__((noinline, pure, nonnull(1))) int speculate_call(const void *p) { return p != 0; }
| ^ ~
case.c:3:32: note: declared 'nonnull' here
3 | __attribute__((noinline, pure, nonnull(1))) int speculate_call(const void *p) { return p != 0; }
| ^
1 warning generated.
inlinable function call in a function with debug info must have a !dbg location
%5 = call i32 @speculate_call(ptr %2) #2
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 and instructions to reproduce the bug.
Stack dump:
0. Program arguments: opt -passes=simplifycfg -bonus-inst-threshold=2 -disable-output src.ll
1. Running pass "verify" on module "src.ll"
#0 0x00005f76703bd059 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (/usr/bin/opt+0x4d92059)
#1 0x00005f76703ba184 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
#2 0x00007d2dd6e42520 (/lib/x86_64-linux-gnu/libc.so.6+0x42520)
#3 0x00007d2dd6e969bc __pthread_kill_implementation ./nptl/pthread_kill.c:44:76
#4 0x00007d2dd6e969bc __pthread_kill_internal ./nptl/pthread_kill.c:78:10
#5 0x00007d2dd6e969bc pthread_kill ./nptl/pthread_kill.c:89:10
#6 0x00007d2dd6e42476 gsignal ./signal/../sysdeps/posix/raise.c:27:6
#7 0x00007d2dd6e287f3 abort ./stdlib/abort.c:81:7
#8 0x00005f766bce24bb (/usr/bin/opt+0x6b74bb)
#9 0x00005f76702e40ae (/usr/bin/opt+0x4cb90ae)
#10 0x00005f767015c9e4 (/usr/bin/opt+0x4b319e4)
#11 0x00005f766bd91446 llvm::detail::PassModel>::runImpl(llvm::detail::PassConcept>&, llvm::Module&, llvm::AnalysisManager&) (/usr/bin/opt+0x766446)
#12 0x00005f767012b64f llvm::PassManager>::run(llvm::Module&, llvm::AnalysisManager&) (/usr/bin/opt+0x4b0064f)
#13 0x00005f766bd9d0ec llvm::runPassPipeline(llvm::StringRef, llvm::Module&, llvm::TargetMachine*, llvm::TargetLibraryInfoImpl*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::ToolOutputFile*, llvm::StringRef, llvm::ArrayRef, llvm::ArrayRef>, llvm::opt_tool::OutputKind, llvm::opt_tool::VerifierKind, bool, bool, bool, bool, bool, bool, bool) (/usr/bin/opt+0x7720ec)
#14 0x00005f766bd9068c optMain (/usr/bin/opt+0x76568c)
#15 0x00007d2dd6e29d90 __libc_start_call_main ./csu/../sysdeps/nptl/pthread_call_main.h:58:16
#16 0x00007d2dd6e29e40 call_init ./csu/../sysdeps/posix/libc-start.c:128:20
#17 0x00007d2dd6e29e40 __libc_start_main ./csu/../sysdeps/posix/libc-start.c:379:16
#18 0x00005f766bd85505 _start (/usr/bin/opt+0x75a505)
reproduce.sh: 第 14 行: 9 已中止 (核心已转储) opt -passes=simplifycfg -bonus-inst-threshold=2 -disable-output src.ll
```

## Reproducer

`case.c`:

```c
#include

volatile unsigned int sideeffect_count;

__attribute__((noinline, pure, nonnull(1))) int speculate_call(const void *p) { return p != 0; }
__attribute__((noinline)) void sideeffect(void) { ++sideeffect_count; }

__attribute__((noinline))
void one_pred_with_spec_call(uint8_t v0, uint8_t v1, const void *p) {
(void)v0;
if (p != 0) {
int x = speculate_call(p);
(void)x;
if (v1 == 0)
return;
}
sideeffect();
}

int main(void) {
int value = 7;
one_pred_with_spec_call(0, 0, &value);
one_pred_with_spec_call(0, 1, 0);
return 0;
}
```

Build commands:

```sh
clang -g -Xclang -disable-O0-optnone -S -emit-llvm case.c -o case.ll
opt -passes='forceattrs,function(mem2reg)' \
-force-attribute=speculate_call:speculatable \
-S case.ll -o src.ll
opt -passes=simplifycfg -bonus-inst-threshold=2 -disable-output src.ll
```

The complete `src.ll` and the IR produced by `SimplifyCFG` with verification disabled are available in this [Godbolt link](https://godbolt.org/z/q3z1G87bq).

The last command crashes. The `-disable-verify` option is only needed to save and inspect the invalid IR produced before verification:

```sh
opt -disable-verify -passes=simplifycfg -bonus-inst-threshold=2 -S src.ll -o tgt-invalid.ll
```

## Observed Behavior

After `SimplifyCFG` folds the branch and moves the speculatable call, the call has no `!dbg` location:

```llvm
entry:
%4 = icmp ne ptr %2, null, !dbg !42
%5 = call i32 @speculate_call(ptr %2) #2
#dbg_value(i32 %5, !44, !DIExpression(), !46)
%6 = zext i8 %1 to i32
%7 = icmp eq i32 %6, 0
%or.cond = and i1 %4, %7, !dbg !42
br i1 %or.cond, label %9, label %8, !dbg !42
```

The callee is marked `speculatable` by `forceattrs`, which enables the transformation:

```llvm
define dso_local i32 @speculate_call(ptr noundef nonnull %0) #0 !dbg !14
...
attributes #0 = { noinline nounwind speculatable willreturn memory(read) ... }
```

## Environment

```text
clang version 24.0.0git
llvm-project revision: d35d0e69980f11c2acbd3670c65fec3cf574224a
Target: x86_64-unknown-linux-gnu

LLVM version 24.0.0git
LLDB version 24.0.0git
Linux x86_64
```

Contributor guide

Open the contributing guide

Research direction

Reproduce the failure with case.c, src.ll, and opt -passes=simplifycfg -bonus-inst-threshold=2 -disable-output. Start by tracing SimplifyCFG's branch-folding path and its handling of debug locations when moving the speculatable call. Done means the reproducer no longer aborts verification and the transformed IR remains valid.

Written by the indexing model from the issue text.

Assessment

Tech stack
c, cpp
Domain
compilers
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
45/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.