Skip to content

[codegen]Ensure __builtin_trap() has an unreachable - #197789

Merged
oontvoo merged 19 commits into
llvm:mainfrom
oontvoo:builtintrap
Jul 15, 2026
Merged

[codegen]Ensure __builtin_trap() has an unreachable#197789
oontvoo merged 19 commits into
llvm:mainfrom
oontvoo:builtintrap

Conversation

@oontvoo

@oontvoo oontvoo commented May 14, 2026

Copy link
Copy Markdown
Member
  void test2() {
    __builtin_trap();
}

Previously, this would generate this:

; Function Attrs: mustprogress noinline nounwind optnone uwtable
define dso_local void @test2()() #2 !dbg !14 {
  call void @llvm.trap(), !dbg !15
  ret void, !dbg !16
}

Now we'll have unreachable after the trap

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @test2() #0 {
  call void @llvm.trap()
  unreachable
}

@oontvoo
oontvoo requested a review from jyknight May 14, 2026 19:39
@llvmorg-github-actions llvmorg-github-actions Bot added clang Clang issues not falling into any other category clang:codegen IR generation bugs: mangling, exceptions, etc. labels May 14, 2026
@llvmorg-github-actions

llvmorg-github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

@llvm/pr-subscribers-backend-amdgpu
@llvm/pr-subscribers-clang-codegen

@llvm/pr-subscribers-clang

Author: Vy Nguyen (oontvoo)

Changes
  void test2() {
    __builtin_trap();
}

Previously, this would generate this:

; Function Attrs: mustprogress noinline nounwind optnone uwtable
define dso_local void @<!-- -->test2()() #<!-- -->2 !dbg !14 {
  call void @<!-- -->llvm.trap(), !dbg !15
  ret void, !dbg !16
}

Now we'll have unreachable after the trap

; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @<!-- -->test2() #<!-- -->0 {
  call void @<!-- -->llvm.trap()
  unreachable
}


Full diff: https://github.com/llvm/llvm-project/pull/197789.diff

1 Files Affected:

  • (modified) clang/lib/CodeGen/CGBuiltin.cpp (+6)
diff --git a/clang/lib/CodeGen/CGBuiltin.cpp b/clang/lib/CodeGen/CGBuiltin.cpp
index 20f99f489c55f..678985ebb55e1 100644
--- a/clang/lib/CodeGen/CGBuiltin.cpp
+++ b/clang/lib/CodeGen/CGBuiltin.cpp
@@ -4022,6 +4022,12 @@ RValue CodeGenFunction::EmitBuiltinExpr(const GlobalDecl GD, unsigned BuiltinID,
   }
   case Builtin::BI__builtin_trap:
     EmitTrapCall(Intrinsic::trap);
+    if (Builder.GetInsertBlock()) {
+      Builder.CreateUnreachable();
+      // Dummy block for the ret void - it'll be clened up
+      llvm::BasicBlock *DeadBB = createBasicBlock("dead.trap");
+      EmitBlock(DeadBB);
+    }
     return RValue::get(nullptr);
   case Builtin::BI__builtin_verbose_trap: {
     llvm::DILocation *TrapLocation = Builder.getCurrentDebugLocation();

@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

🐧 Linux x64 Test Results

  • 120661 tests passed
  • 4895 tests skipped

✅ The build succeeded and all tests passed.

@github-actions

github-actions Bot commented May 14, 2026

Copy link
Copy Markdown

🪟 Windows x64 Test Results

  • 57400 tests passed
  • 2749 tests skipped

✅ The build succeeded and all tests passed.

@efriedma-quic

Copy link
Copy Markdown
Contributor

I guess we can do this... but what's the benefit?

@InakiVA

InakiVA commented May 14, 2026

Copy link
Copy Markdown
Contributor

I guess we can do this... but what's the benefit?

We are currently relying on the optimization pipeline to deduce that whenever a trap function is called there will be an unreachable following it. We already know from the get-go that after a trap comes an unreachable, so it would be nice to know this information when optimizations are disabled for analysis sake.

@oontvoo

oontvoo commented May 15, 2026

Copy link
Copy Markdown
Member Author

I guess we can do this... but what's the benefit?

At -O0 (ie., debug build, with dead code elimination disabled), this could help keeping the binary small because all the stuff that physically follow the trap can be safely dropped (unreachable and ClearInsertionPoint() ensure that)

@efriedma-quic efriedma-quic 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.

That doesn't really seem like much of a benefit, but the cost is also pretty small, so fine, I guess.

Regression tests are still failing.

}

// CHECK-LABEL: define dso_local void @"?call_var_args@@YAXXZ"()
// FIXME all of these are after the trap so they should be gone.

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.

I don't think this is something we plan to fix... not sure the FIXME is useful.

Comment thread clang/test/CodeGen/attr-nomerge.cpp Outdated
// CHECK: call void @llvm.debugtrap() #[[ATTR0]]
// CHECK: call void @llvm.trap() #[[ATTR0]]
// CHECK: call void @_ZN1AD1Ev(ptr {{.*}}) #[[ATTR1]]
// CHECK: call void @llvm.trap() #[[ATTR8:[0-9]+]]

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.

This breaks what this is supposed to be testing.

Please split the call to __builtin_trap() into a separate function.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

done

@ojhunt ojhunt 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.

Rather than special casing these I feel like we should have at least EmitTrapCall() automatically plant the unreachable if the requested intrinsic is a no return/no throw (and attach the various no throw, etc flags if they do need to be set directly on the call).

That would also catch the verbose traps, etc.

Honestly if we're doing this in debug builds I'd almost just have EmitCall plant a trap + unreachable after noreturn functions.

Comment thread clang/lib/CodeGen/CGBuiltin.cpp Outdated
Comment thread clang/lib/CodeGen/CGBuiltin.cpp Outdated
@oontvoo

oontvoo commented Jun 3, 2026

Copy link
Copy Markdown
Member Author

Rather than special casing these I feel like we should have at least EmitTrapCall() automatically plant the unreachable if the requested intrinsic is a no return/no throw (and attach the various no throw, etc flags if they do need to be set directly on the call).

That would also catch the verbose traps, etc.

Honestly if we're doing this in debug builds I'd almost just have EmitCall plant a trap + unreachable after noreturn functions.

Done

@oontvoo
oontvoo requested a review from ojhunt June 3, 2026 16:26
@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

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

@oontvoo
oontvoo requested a review from efriedma-quic June 25, 2026 18:09
@llvmorg-github-actions llvmorg-github-actions Bot added the clang:openmp OpenMP related changes to Clang label Jun 25, 2026
@oontvoo

oontvoo commented Jun 25, 2026

Copy link
Copy Markdown
Member Author

@ojhunt I've made the requested change. Please have another look. Thank you.

ojhunt
ojhunt previously requested changes Jun 25, 2026

@ojhunt ojhunt 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.

Just a minor renaming - I'm really surprised that there is not a single "builtin_trap" test case anywhere.

I think it would be worth it to add one, with different targets - thoughts are:

  • -O0 vs -O1 (I think you get a debug? trap in some cases - it would be good to find such path and ensure that it does not get the unreachable, non-returning, etc attributes)
  • Something like wasm (maybe?), or some other platform where trap is implemented as exceptions or something other than a hardware trap

Comment thread clang/lib/CodeGen/CGBuiltin.cpp Outdated
Comment thread clang/lib/CodeGen/CGExpr.cpp Outdated
@oontvoo

oontvoo commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Just a minor renaming - I'm really surprised that there is not a single "builtin_trap" test case anywhere.

I think it would be worth it to add one, with different targets - thoughts are:

  • -O0 vs -O1 (I think you get a debug? trap in some cases - it would be good to find such path and ensure that it does not get the unreachable, non-returning, etc attributes)
  • Something like wasm (maybe?), or some other platform where trap is implemented as exceptions or something other than a hardware trap

done

@oontvoo
oontvoo requested a review from ojhunt June 26, 2026 00:35
@oontvoo

oontvoo commented Jul 1, 2026

Copy link
Copy Markdown
Member Author

@ojhunt HI, ping?

@oontvoo
oontvoo requested a review from alinas July 7, 2026 01:09
@oontvoo

oontvoo commented Jul 14, 2026

Copy link
Copy Markdown
Member Author

@ojhunt @efriedma-quic @jyknight Hi, friendly ping again?
Does anyone have any objection to this?

@efriedma-quic efriedma-quic 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

@oontvoo
oontvoo dismissed ojhunt’s stale review July 15, 2026 18:22

(already addressed the feedbacks)

@oontvoo
oontvoo enabled auto-merge (squash) July 15, 2026 18:23
@oontvoo
oontvoo merged commit 0f494fc into llvm:main Jul 15, 2026
11 checks passed
Logans-olo pushed a commit to Logans-olo/llvm-project that referenced this pull request Jul 15, 2026
```
  void test2() {
    __builtin_trap();
}
```

Previously, this would generate this:

```
; Function Attrs: mustprogress noinline nounwind optnone uwtable
define dso_local void @test2()() llvm#2 !dbg !14 {
  call void @llvm.trap(), !dbg !15
  ret void, !dbg !16
}
```


Now we'll have `unreachable` after the trap 

```
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @test2() #0 {
  call void @llvm.trap()
  unreachable
}

```
@rupprecht

Copy link
Copy Markdown
Contributor

This is causing test failures in some build modes (I think release build modes hide it) for the attr-cpuspecific tests. ubsan explains it:

$ clang -cc1 -emit-llvm -o /dev/null clang/test/CodeGen/attr-cpuspecific.c
clang/lib/CodeGen/CGStmt.cpp:662:12: runtime error: member call on null pointer of type 'llvm::Function'
    #0 0x5633c492a531 in clang::CodeGen::CodeGenFunction::EmitBlock(llvm::BasicBlock*, bool) clang/lib/CodeGen/CGStmt.cpp:662:12
    #1 0x5633c479a5a4 in clang::CodeGen::CodeGenFunction::EmitTrapCall(unsigned int) clang/lib/CodeGen/CGExpr.cpp:4645:5
    #2 0x5633c4be52ac in clang::CodeGen::CodeGenFunction::EmitX86MultiVersionResolver(llvm::Function*, llvm::ArrayRef<clang::CodeGen::CodeGenFunction::FMVResolverOption>) clang/lib/CodeGen/CodeGenFunction.cpp:3350:30
    #3 0x5633c4be4d01 in clang::CodeGen::CodeGenFunction::EmitMultiVersionResolver(llvm::Function*, llvm::ArrayRef<clang::CodeGen::CodeGenFunction::FMVResolverOption>) clang/lib/CodeGen/CodeGenFunction.cpp:3076:5
    #4 0x5633c4c14f4b in clang::CodeGen::CodeGenModule::emitCPUDispatchDefinition(clang::GlobalDecl) clang/lib/CodeGen/CodeGenModule.cpp:5295:7
    #5 0x5633c4c12482 in clang::CodeGen::CodeGenModule::EmitGlobal(clang::GlobalDecl) clang/lib/CodeGen/CodeGenModule.cpp:4608:12
    #6 0x5633c4c0b018 in clang::CodeGen::CodeGenModule::EmitTopLevelDecl(clang::Decl*) clang/lib/CodeGen/CodeGenModule.cpp:7929:5
    #7 0x5633c4dc7946 in (anonymous namespace)::CodeGeneratorImpl::HandleTopLevelDecl(clang::DeclGroupRef) clang/lib/CodeGen/ModuleBuilder.cpp:201:18
    #8 0x5633c470dd5b in clang::BackendConsumer::HandleTopLevelDecl(clang::DeclGroupRef) clang/lib/CodeGen/CodeGenAction.cpp:179:8
    #9 0x5633c5b3eb28 in clang::ParseAST(clang::Sema&, bool, bool) clang/lib/Parse/ParseAST.cpp:174:31
    #10 0x5633c5a273b3 in clang::ASTFrontendAction::ExecuteAction() clang/lib/Frontend/FrontendAction.cpp:1464:3
    #11 0x5633c4714128 in clang::CodeGenAction::ExecuteAction() clang/lib/CodeGen/CodeGenAction.cpp:1166:30
    #12 0x5633c5a269c9 in clang::FrontendAction::Execute() clang/lib/Frontend/FrontendAction.cpp:1344:3
    #13 0x5633c59a47ec in clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) clang/lib/Frontend/CompilerInstance.cpp:1033:33
    #14 0x5633c470d1b9 in clang::ExecuteCompilerInvocation(clang::CompilerInstance*) clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp:319:25
    #15 0x5633c46fb388 in cc1_main(llvm::ArrayRef<char const*>, char const*, void*) clang/tools/driver/cc1_main.cpp:296:13

w/o ubsan:

Stack dump:
0.      Program arguments: clang -cc1 -emit-llvm -o /dev/null clang/test/CodeGen/attr-cpuspecific.c
1.      clang/test/CodeGen/attr-cpuspecific.c:64:1 <Spelling=clang/test/CodeGen/attr-cpuspecific.c:8:17>: current parser token '__attribute__'
2.      clang/test/CodeGen/attr-cpuspecific.c:44:6: LLVM IR generation of declaration 'SingleVersion'

Similar for attr-cpuspecific.cpp and attr-cpuspecific-outoflinedefs.cpp

erichkeane added a commit that referenced this pull request Jul 15, 2026
HLSL added a bunch of diagnostics for entry functions that messed with
the matrix test, see here:
#184892

The solution is to just mark the test as a library triple.

Builtin-trap stopped translating in classic codegen, so the call after
it is now missing (and there is an unreachable after it),w hich actually
better matches our behavior see:
#197789

This patch fixes both tests.
llvm-upstreamsync Bot pushed a commit to qualcomm/cpullvm-toolchain that referenced this pull request Jul 15, 2026
HLSL added a bunch of diagnostics for entry functions that messed with
the matrix test, see here:
llvm/llvm-project#184892

The solution is to just mark the test as a library triple.

Builtin-trap stopped translating in classic codegen, so the call after
it is now missing (and there is an unreachable after it),w hich actually
better matches our behavior see:
llvm/llvm-project#197789

This patch fixes both tests.
llvm-sync Bot pushed a commit to arm/arm-toolchain that referenced this pull request Jul 15, 2026
HLSL added a bunch of diagnostics for entry functions that messed with
the matrix test, see here:
llvm/llvm-project#184892

The solution is to just mark the test as a library triple.

Builtin-trap stopped translating in classic codegen, so the call after
it is now missing (and there is an unreachable after it),w hich actually
better matches our behavior see:
llvm/llvm-project#197789

This patch fixes both tests.
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request Jul 15, 2026
```
  void test2() {
    __builtin_trap();
}
```

Previously, this would generate this:

```
; Function Attrs: mustprogress noinline nounwind optnone uwtable
define dso_local void @test2()() #2 !dbg !14 {
  call void @llvm.trap(), !dbg !15
  ret void, !dbg !16
}
```


Now we'll have `unreachable` after the trap 

```
; Function Attrs: noinline nounwind optnone uwtable
define dso_local void @test2() #0 {
  call void @llvm.trap()
  unreachable
}

```
pedroMVicente pushed a commit to pedroMVicente/llvm-project that referenced this pull request Jul 15, 2026
HLSL added a bunch of diagnostics for entry functions that messed with
the matrix test, see here:
llvm#184892

The solution is to just mark the test as a library triple.

Builtin-trap stopped translating in classic codegen, so the call after
it is now missing (and there is an unreachable after it),w hich actually
better matches our behavior see:
llvm#197789

This patch fixes both tests.
@erichkeane

Copy link
Copy Markdown
Contributor

This is causing test failures in some build modes (I think release build modes hide it) for the attr-cpuspecific tests. ubsan explains it:

$ clang -cc1 -emit-llvm -o /dev/null clang/test/CodeGen/attr-cpuspecific.c
clang/lib/CodeGen/CGStmt.cpp:662:12: runtime error: member call on null pointer of type 'llvm::Function'
    #0 0x5633c492a531 in clang::CodeGen::CodeGenFunction::EmitBlock(llvm::BasicBlock*, bool) clang/lib/CodeGen/CGStmt.cpp:662:12
    #1 0x5633c479a5a4 in clang::CodeGen::CodeGenFunction::EmitTrapCall(unsigned int) clang/lib/CodeGen/CGExpr.cpp:4645:5
    #2 0x5633c4be52ac in clang::CodeGen::CodeGenFunction::EmitX86MultiVersionResolver(llvm::Function*, llvm::ArrayRef<clang::CodeGen::CodeGenFunction::FMVResolverOption>) clang/lib/CodeGen/CodeGenFunction.cpp:3350:30
    #3 0x5633c4be4d01 in clang::CodeGen::CodeGenFunction::EmitMultiVersionResolver(llvm::Function*, llvm::ArrayRef<clang::CodeGen::CodeGenFunction::FMVResolverOption>) clang/lib/CodeGen/CodeGenFunction.cpp:3076:5
    #4 0x5633c4c14f4b in clang::CodeGen::CodeGenModule::emitCPUDispatchDefinition(clang::GlobalDecl) clang/lib/CodeGen/CodeGenModule.cpp:5295:7
    #5 0x5633c4c12482 in clang::CodeGen::CodeGenModule::EmitGlobal(clang::GlobalDecl) clang/lib/CodeGen/CodeGenModule.cpp:4608:12
    #6 0x5633c4c0b018 in clang::CodeGen::CodeGenModule::EmitTopLevelDecl(clang::Decl*) clang/lib/CodeGen/CodeGenModule.cpp:7929:5
    #7 0x5633c4dc7946 in (anonymous namespace)::CodeGeneratorImpl::HandleTopLevelDecl(clang::DeclGroupRef) clang/lib/CodeGen/ModuleBuilder.cpp:201:18
    #8 0x5633c470dd5b in clang::BackendConsumer::HandleTopLevelDecl(clang::DeclGroupRef) clang/lib/CodeGen/CodeGenAction.cpp:179:8
    #9 0x5633c5b3eb28 in clang::ParseAST(clang::Sema&, bool, bool) clang/lib/Parse/ParseAST.cpp:174:31
    #10 0x5633c5a273b3 in clang::ASTFrontendAction::ExecuteAction() clang/lib/Frontend/FrontendAction.cpp:1464:3
    #11 0x5633c4714128 in clang::CodeGenAction::ExecuteAction() clang/lib/CodeGen/CodeGenAction.cpp:1166:30
    #12 0x5633c5a269c9 in clang::FrontendAction::Execute() clang/lib/Frontend/FrontendAction.cpp:1344:3
    #13 0x5633c59a47ec in clang::CompilerInstance::ExecuteAction(clang::FrontendAction&) clang/lib/Frontend/CompilerInstance.cpp:1033:33
    #14 0x5633c470d1b9 in clang::ExecuteCompilerInvocation(clang::CompilerInstance*) clang/lib/FrontendTool/ExecuteCompilerInvocation.cpp:319:25
    #15 0x5633c46fb388 in cc1_main(llvm::ArrayRef<char const*>, char const*, void*) clang/tools/driver/cc1_main.cpp:296:13

w/o ubsan:

Stack dump:
0.      Program arguments: clang -cc1 -emit-llvm -o /dev/null clang/test/CodeGen/attr-cpuspecific.c
1.      clang/test/CodeGen/attr-cpuspecific.c:64:1 <Spelling=clang/test/CodeGen/attr-cpuspecific.c:8:17>: current parser token '__attribute__'
2.      clang/test/CodeGen/attr-cpuspecific.c:44:6: LLVM IR generation of declaration 'SingleVersion'

Similar for attr-cpuspecific.cpp and attr-cpuspecific-outoflinedefs.cpp

Hmm... There's no reason that the resolver call CAN'T set the CurFn I think, and probably should. I'll work up a quick patch to do so, hopefully it won't have horrific impact on the cpu-specific tests.

erichkeane added a commit to erichkeane/llvm-project that referenced this pull request Jul 15, 2026
Commit llvm#197789 manages to try to reference CurFn while generating a
trap, which some of the multiversion resolvers do.  This patch makes
sure we set it to the resolver so we are inserting stuff into the right
places.

This somewhat shockingly causes no tests to fail, but it SHOULD stop the
UBSan failures.
oontvoo pushed a commit that referenced this pull request Jul 16, 2026
Commit #197789 manages to try to reference CurFn while generating a
trap, which some of the multiversion resolvers do. This patch makes sure
we set it to the resolver so we are inserting stuff into the right
places.

This somewhat shockingly causes no tests to fail, but it SHOULD stop the
UBSan failures.
@oontvoo
oontvoo deleted the builtintrap branch July 16, 2026 14:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backend:AMDGPU clang:codegen IR generation bugs: mangling, exceptions, etc. clang:openmp OpenMP related changes to Clang clang Clang issues not falling into any other category

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants