https://github.com/FlyMan2004 updated https://github.com/llvm/llvm-project/pull/230936
>From 9387a0b6c58d810680162e0f1817ab4cb3948788 Mon Sep 17 00:00:00 2001 From: FlyMan2004 <[email protected]> Date: Sat, 10 Oct 2026 14:26:51 +0800 Subject: [PATCH] [clang][CodeGen] Fix named breaks in optimized switch cases EmitCaseStmt can redirect a case containing only a break directly to its break destination. It previously used the top of BreakContinueStack, which belongs to the inner switch even when a named break targets an enclosing loop. At optimization levels above O0, this caused execution to continue the loop instead of leaving it. Resolve the destination with GetDestForLoopControlStmt before checking whether cleanups are required. This also redirects fallthrough to the named target and retains loop-scope cleanups when necessary. Cover for, while and do-while in C and C++, plus C++ range-for, ordinary switch breaks, fallthrough and cleanup paths. Disable LLVM passes in the optimized checks to isolate frontend code generation. Document the corrected behavior in the release notes. Fixes llvm/llvm-project#230932 Fixes: e4a1b5f36e71 Assisted-by: Codex --- clang/docs/ReleaseNotes.md | 5 + clang/lib/CodeGen/CGStmt.cpp | 5 +- clang/test/CodeGen/labeled-break-continue.c | 110 +++++++++++++ .../CodeGenCXX/labeled-break-continue.cpp | 149 ++++++++++++++++++ 4 files changed, 267 insertions(+), 2 deletions(-) diff --git a/clang/docs/ReleaseNotes.md b/clang/docs/ReleaseNotes.md index 77a1e73bc8af6..60b08e064b7cc 100644 --- a/clang/docs/ReleaseNotes.md +++ b/clang/docs/ReleaseNotes.md @@ -945,6 +945,11 @@ features cannot lower the translation-unit ABI level; #### Miscellaneous Bug Fixes +- Fixed a miscompilation of C2y named `break` statements targeting enclosing loops + from `switch` cases with optimizations enabled. These breaks now exit the named + loop and run required cleanups instead of leaving only the `switch`. + (#GH230932) + #### Miscellaneous Clang Crashes Fixed - Fixed a crash when Microsoft extensions were enabled and an unterminated diff --git a/clang/lib/CodeGen/CGStmt.cpp b/clang/lib/CodeGen/CGStmt.cpp index 05b3693138b0a..38988a0295b13 100644 --- a/clang/lib/CodeGen/CGStmt.cpp +++ b/clang/lib/CodeGen/CGStmt.cpp @@ -1870,7 +1870,8 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S, if (!CGM.getCodeGenOpts().hasProfileClangInstr() && CGM.getCodeGenOpts().OptimizationLevel > 0 && isa<BreakStmt>(S.getSubStmt())) { - JumpDest Block = BreakContinueStack.back().BreakBlock; + const auto &Break = cast<BreakStmt>(*S.getSubStmt()); + JumpDest Block = GetDestForLoopControlStmt(Break)->BreakBlock; // Only do this optimization if there are no cleanups that need emitting. if (isObviouslyBranchWithoutCleanups(Block)) { @@ -1879,7 +1880,7 @@ void CodeGenFunction::EmitCaseStmt(const CaseStmt &S, SwitchInsn->addCase(CaseVal, Block.getBlock()); // If there was a fallthrough into this case, make sure to redirect it to - // the end of the switch as well. + // the break destination as well. if (Builder.GetInsertBlock()) { Builder.CreateBr(Block.getBlock()); Builder.ClearInsertionPoint(); diff --git a/clang/test/CodeGen/labeled-break-continue.c b/clang/test/CodeGen/labeled-break-continue.c index f307a1bd79ab8..93a9763efcf89 100644 --- a/clang/test/CodeGen/labeled-break-continue.c +++ b/clang/test/CodeGen/labeled-break-continue.c @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -std=c2y -triple x86_64-unknown-linux -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -std=c2y -triple x86_64-unknown-linux -O1 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefix=OPT bool g1(); bool g2(); @@ -144,6 +145,12 @@ void f4() { // CHECK: br label %while.cond // CHECK: while.end: // CHECK: ret void +// OPT-LABEL: define {{.*}} void @f5() +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 1, label %while.end +// OPT-NEXT: i32 2, label %sw.epilog +// OPT-NEXT: i32 3, label %sw.bb +// OPT-NEXT: ] void f5() { l1: while (g1()) { l2: switch (g2()) { @@ -272,6 +279,10 @@ void f6() { // CHECK: br label %while.cond // CHECK: while.end: // CHECK: ret void +// OPT-LABEL: define {{.*}} void @f7() +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 1, label %while.end +// OPT-NEXT: ] void f7() { loop: while (g1()) { switch (g2()) { @@ -279,3 +290,102 @@ void f7() { } } } + +// CHECK-LABEL: define {{.*}} i32 @f8() +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %for.end +// OPT-LABEL: define {{.*}} i32 @f8() +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %sw.bb +// OPT-NEXT: ] +// OPT: sw.bb: +// OPT-NEXT: store i32 {{[0-9]+}}, ptr %cleanup.dest.slot +// OPT-NEXT: br label %cleanup +// OPT: cleanup: +// OPT-NEXT: call void @llvm.lifetime.end.p0(ptr %i) +// OPT-NEXT: br label %for.end +int f8() { + int count = 0; + outer: for (int i = 0; i < 2; ++i) { + ++count; + switch (i) { + case 0: break outer; + } + } + return count; +} + +// Check both the switch edge and fallthrough into a named break. +// OPT-LABEL: define {{.*}} void @f9( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 1, label %sw.bb +// OPT-NEXT: i32 0, label %while.end +// OPT-NEXT: i32 2, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.bb: +// OPT: call {{.*}} i1 @g2() +// OPT-NEXT: br label %while.end +void f9(int x) { + loop: while (g1()) { + switch (x) { + case 1: g2(); + case 0: break loop; + case 2: break; + } + } +} + +// Without loop-scope cleanups, the named break can go directly to the loop +// exit. An ordinary break must still go to the switch exit. +// CHECK-LABEL: define {{.*}} void @f10( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %for.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @f10( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %for.end +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.epilog: +// OPT-NEXT: br label %for.cond +void f10(int x) { + loop: for (; g1();) { + switch (x) { + case 0: break loop; + case 1: break; + } + } +} + +// CHECK-LABEL: define {{.*}} void @f11( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %do.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @f11( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %do.end +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.epilog: +// OPT-NEXT: br label %do.cond +void f11(int x) { + loop: do { + switch (x) { + case 0: break loop; + case 1: break; + } + } while (g1()); +} diff --git a/clang/test/CodeGenCXX/labeled-break-continue.cpp b/clang/test/CodeGenCXX/labeled-break-continue.cpp index 50c222a99c25c..7442ccf7e4c02 100644 --- a/clang/test/CodeGenCXX/labeled-break-continue.cpp +++ b/clang/test/CodeGenCXX/labeled-break-continue.cpp @@ -1,4 +1,5 @@ // RUN: %clang_cc1 -fnamed-loops -triple x86_64-unknown-linux -std=c++20 -emit-llvm -o - %s | FileCheck %s +// RUN: %clang_cc1 -fnamed-loops -triple x86_64-unknown-linux -std=c++20 -O1 -disable-llvm-passes -emit-llvm -o - %s | FileCheck %s --check-prefix=OPT static int a[10]{}; struct NonTrivialDestructor { @@ -219,3 +220,151 @@ template void f3<true>(); // CHECK: while.end: // CHECK: ret void template void f3<false>(); + +// A named break out of the loop must run its cleanup, even though breaking out +// of the inner switch would not require one. +// OPT-LABEL: define {{.*}} void @_Z2f4i( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %sw.bb +// OPT-NEXT: ] +// OPT: sw.bb: +// OPT-NEXT: store i32 [[EXIT:[0-9]+]], ptr %cleanup.dest.slot +// OPT-NEXT: br label %cleanup +// OPT: cleanup: +// OPT-NEXT: call void @_ZN20NonTrivialDestructorD1Ev( +// OPT: switch i32 %cleanup.dest, label %unreachable [ +// OPT: i32 [[EXIT]], label %while.end +void f4(int x) { + loop: while (g(0)) { + NonTrivialDestructor n; + switch (x) { + case 0: break loop; + } + } +} + +// Without loop-scope cleanups, the named break can go directly to the loop +// exit. An ordinary break must still go to the switch exit. +// CHECK-LABEL: define {{.*}} void @_Z2f5i( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %for.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @_Z2f5i( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %for.end +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.epilog: +// OPT-NEXT: br label %for.cond +void f5(int x) { + loop: for (; h();) { + switch (x) { + case 0: break loop; + case 1: break; + } + } +} + +// CHECK-LABEL: define {{.*}} void @_Z2f6i( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %while.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @_Z2f6i( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %while.end +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.epilog: +// OPT-NEXT: br label %while.cond +void f6(int x) { + loop: while (h()) { + switch (x) { + case 0: break loop; + case 1: break; + } + } +} + +// CHECK-LABEL: define {{.*}} void @_Z2f7i( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %do.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @_Z2f7i( +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %do.end +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.epilog: +// OPT-NEXT: br label %do.cond +void f7(int x) { + loop: do { + switch (x) { + case 0: break loop; + case 1: break; + } + } while (h()); +} + +// A named break must end the loop variable and iterator lifetimes before +// leaving the range-for. An ordinary break ends only the loop variable lifetime. +// CHECK-LABEL: define {{.*}} void @_Z2f8i( +// CHECK: switch i32 {{.*}}, label %sw.epilog [ +// CHECK-NEXT: i32 0, label %sw.bb +// CHECK-NEXT: i32 1, label %sw.bb1 +// CHECK-NEXT: ] +// CHECK: sw.bb: +// CHECK-NEXT: br label %for.end +// CHECK: sw.bb1: +// CHECK-NEXT: br label %sw.epilog +// OPT-LABEL: define {{.*}} void @_Z2f8i( +// OPT: for.cond.cleanup: +// OPT-NEXT: store i32 [[RANGE_EXIT:[1-9][0-9]*]], ptr %cleanup.dest.slot +// OPT-NEXT: br label %cleanup1 +// OPT: switch i32 {{.*}}, label %sw.epilog [ +// OPT-NEXT: i32 0, label %sw.bb +// OPT-NEXT: i32 1, label %sw.epilog +// OPT-NEXT: ] +// OPT: sw.bb: +// OPT-NEXT: store i32 [[RANGE_EXIT]], ptr %cleanup.dest.slot +// OPT-NEXT: br label %cleanup +// OPT: sw.epilog: +// OPT-NEXT: store i32 0, ptr %cleanup.dest.slot +// OPT-NEXT: br label %cleanup +// OPT: cleanup: +// OPT-NEXT: call void @llvm.lifetime.end.p0(ptr %i) +// OPT-NEXT: %cleanup.dest = load i32, ptr %cleanup.dest.slot +// OPT-NEXT: switch i32 %cleanup.dest, label %cleanup1 [ +// OPT-NEXT: i32 0, label %cleanup.cont +// OPT-NEXT: ] +// OPT: cleanup.cont: +// OPT-NEXT: br label %for.inc +// OPT: cleanup1: +// OPT-NEXT: call void @llvm.lifetime.end.p0(ptr %__end1) +// OPT-NEXT: call void @llvm.lifetime.end.p0(ptr %__begin1) +// OPT-NEXT: call void @llvm.lifetime.end.p0(ptr %__range1) +// OPT-NEXT: br label %for.end +// OPT: for.end: +// OPT-NEXT: ret void +void f8(int x) { + loop: for (int i : a) { + switch (x) { + case 0: break loop; + case 1: break; + } + } +} _______________________________________________ cfe-commits mailing list [email protected] https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits
