Skip to content

[BranchFolding] Merge debug locs on common hoisted code #140063

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
May 20, 2025

Conversation

OCHyams
Copy link
Contributor

@OCHyams OCHyams commented May 15, 2025

branch-folder hoists common instructions from TBB and FBB into their pred. Without this patch it achieves this by splicing the instructions from TBB and deleting the common ones in FBB. That moves the debug locations and debug instructions from TBB into the pred without modification, which is not ideal. The merged instructions should get merged debug locations for debugging and PGO purposes, which is handled in this patch. Debug instructions also need to be handled differently. That'll come in another patch.

This issue was found by @omern1. I'm not sure how prevalent this pattern is in the compiler (splicing to merge instructions) - imo it's worth looking into further to see if there's a holistic fix we can introduce (e.g., add a new api for this case), but for now I think fixing up this case by itself is still worth it.

@llvmbot
Copy link
Member

llvmbot commented May 15, 2025

@llvm/pr-subscribers-debuginfo

Author: Orlando Cazalet-Hyams (OCHyams)

Changes

branch-folder hoists common instructions from TBB and FBB into their pred. Without this patch it achieves this by splicing the instructions from TBB and deleting the common ones in FBB. That moves the debug locations and debug instructions from TBB into the pred without modification, which is not ideal. The merged instructions should get merged debug locations for debugging and PGO purposes, which is handled in this patch. Debug instructions also need to be handled differently. That'll come in another patch.

This issue was found by @omern1. I'm not sure how prevalent this pattern is in the compiler (splicing to merge instructions) - imo it's worth looking into further to see if there's a holistic fix we can introduce (e.g., add a new api for this case), but for now I think fixing up this case by itself is still worth it.


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

2 Files Affected:

  • (modified) llvm/lib/CodeGen/BranchFolding.cpp (+28-1)
  • (added) llvm/test/DebugInfo/X86/branch-folder-dbg.mir (+111)
diff --git a/llvm/lib/CodeGen/BranchFolding.cpp b/llvm/lib/CodeGen/BranchFolding.cpp
index 6f5afbd2a996a..7dc87dbabf400 100644
--- a/llvm/lib/CodeGen/BranchFolding.cpp
+++ b/llvm/lib/CodeGen/BranchFolding.cpp
@@ -2071,7 +2071,34 @@ bool BranchFolder::HoistCommonCodeInSuccs(MachineBasicBlock *MBB) {
   if (!HasDups)
     return false;
 
-  MBB->splice(Loc, TBB, TBB->begin(), TIB);
+  // Hoist the instructions from [T.begin, TIB) and then delete [F.begin, FIB).
+  // Merge the debug locations. FIXME: We should do something with the
+  // debug instructions too (from BOTH branches).
+  {
+    // TIB and FIB point to the end of the regions to hoist/merge in TBB and
+    // FBB.
+    MachineBasicBlock::iterator FE = FIB;
+    MachineBasicBlock::iterator FI = FBB->begin();
+    for (MachineBasicBlock::iterator TI :
+         make_early_inc_range(make_range(TBB->begin(), TIB))) {
+      // Move debug instructions and pseudo probes without modifying them.
+      // FIXME: This is the wrong thing to do for debug locations, which
+      // should at least be killed.
+      if (TI->isDebugOrPseudoInstr()) {
+        TI->moveBefore(&*Loc);
+        continue;
+      }
+
+      // Get the next non-meta instruction in FBB.
+      FI = skipDebugInstructionsForward(FI, FE, false);
+      assert(FI != FE && "Expected non-debug lockstep");
+
+      // Merge debug locs on hoisted instructions.
+      TI->setDebugLoc(
+          DILocation::getMergedLocation(TI->getDebugLoc(), FI->getDebugLoc()));
+      TI->moveBefore(&*Loc);
+    }
+  }
   FBB->erase(FBB->begin(), FIB);
 
   if (UpdateLiveIns)
diff --git a/llvm/test/DebugInfo/X86/branch-folder-dbg.mir b/llvm/test/DebugInfo/X86/branch-folder-dbg.mir
new file mode 100644
index 0000000000000..5c38fd2a43829
--- /dev/null
+++ b/llvm/test/DebugInfo/X86/branch-folder-dbg.mir
@@ -0,0 +1,111 @@
+# RUN: llc %s --start-before=branch-folder --stop-after=branch-folder -o - | FileCheck %s
+
+## Check that common instructions hoisted from `if.then` and `if.else` into
+## common pred `entry` get merged debug locations.
+
+## FIXME: The debug instructions handling here is wrong.
+
+# CHECK: bb.0
+# CHECK:      CALL64pcrel32 @f, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit-def $rax
+## --- Start splice from bb.2.if.else ---
+# CHECK-NEXT: DBG_VALUE 2, $noreg, ![[#]], !DIExpression(),  debug-location ![[#]]
+# CHECK-NEXT: $edi = MOV32r0 implicit-def dead $eflags,  debug-location !DILocation(line: 0, scope: ![[#]])
+## --- End splice --------------
+# CHECK-NEXT: TEST64rr killed renamable $rax, renamable $rax, implicit-def $eflags
+# CHECK-NEXT: JCC_1 %bb.2, 9, implicit killed $eflags
+# CHECK: bb.1
+
+--- |
+  target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
+  target triple = "x86_64-unknown-linux-gnu"
+
+  declare dso_local noundef i64 @f() local_unnamed_addr
+
+  define dso_local noundef i32 @g() local_unnamed_addr !dbg !7 {
+    %call = tail call noundef i64 @f()
+    %cmp1 = icmp sgt i64 0, %call
+    %conv2 = trunc i64 0 to i32
+    br i1 %cmp1, label %if.then, label %if.else
+
+  if.then:                                          ; preds = %0
+      #dbg_value(i64 0, !11, !DIExpression(), !13)
+    tail call void @_Z3fooii(i32 noundef %conv2, i32 noundef 0), !dbg !14
+      #dbg_value(i64 1, !11, !DIExpression(), !13)
+    br label %if.end, !dbg !15
+
+  if.else:                                          ; preds = %0
+      #dbg_value(i64 2, !11, !DIExpression(), !13)
+    tail call void @_Z3barii(i32 noundef %conv2, i32 noundef 1), !dbg !16
+      #dbg_value(i64 3, !11, !DIExpression(), !13)
+    br label %if.end, !dbg !17
+
+  if.end:                                           ; preds = %if.else, %if.then
+    ret i32 2
+  }
+
+  declare void @_Z3fooii(i32 noundef, i32 noundef) local_unnamed_addr
+
+  declare void @_Z3barii(i32 noundef, i32 noundef) local_unnamed_addr
+
+  !llvm.module.flags = !{!0, !1}
+  !llvm.ident = !{!2}
+  !llvm.dbg.cu = !{!3}
+  !llvm.debugify = !{!5, !6}
+
+  !0 = !{i32 7, !"Dwarf Version", i32 5}
+  !1 = !{i32 2, !"Debug Info Version", i32 3}
+  !2 = !{!"clang version 21.0.0"}
+  !3 = distinct !DICompileUnit(language: DW_LANG_C, file: !4, producer: "debugify", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+  !4 = !DIFile(filename: "test.nodbg.ll", directory: "/")
+  !5 = !{i32 15}
+  !6 = !{i32 7}
+  !7 = distinct !DISubprogram(name: "g", linkageName: "g", scope: null, file: !4, line: 1, type: !8, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !3, retainedNodes: !10)
+  !8 = !DISubroutineType(types: !9)
+  !9 = !{}
+  !10 = !{!11}
+  !11 = !DILocalVariable(name: "1", scope: !7, file: !4, line: 3, type: !12)
+  !12 = !DIBasicType(name: "ty64", size: 64, encoding: DW_ATE_unsigned)
+  !13 = !DILocation(line: 3, column: 1, scope: !7)
+  !14 = !DILocation(line: 9, column: 1, scope: !7)
+  !15 = !DILocation(line: 10, column: 1, scope: !7)
+  !16 = !DILocation(line: 11, column: 1, scope: !7)
+  !17 = !DILocation(line: 12, column: 1, scope: !7)
+...
+---
+name:            g
+body:             |
+  bb.0 (%ir-block.0):
+    successors: %bb.1(0x40000000), %bb.2(0x40000000)
+
+    frame-setup PUSH64r undef $rax, implicit-def $rsp, implicit $rsp
+    frame-setup CFI_INSTRUCTION def_cfa_offset 16
+    CALL64pcrel32 @f, csr_64, implicit $rsp, implicit $ssp, implicit-def $rsp, implicit-def $ssp, implicit-def $rax
+    TEST64rr killed renamable $rax, renamable $rax, implicit-def $eflags
+    JCC_1 %bb.2, 9, implicit killed $eflags
+    JMP_1 %bb.1
+
+  bb.1.if.then:
+    successors: %bb.3(0x80000000)
+
+    DBG_VALUE 0, $noreg, !11, !DIExpression(),  debug-location !13
+    $edi = MOV32r0 implicit-def dead $eflags,  debug-location !14
+    $esi = MOV32r0 implicit-def dead $eflags,  debug-location !14
+    CALL64pcrel32 target-flags(x86-plt) @_Z3fooii, csr_64, implicit $rsp, implicit $ssp, implicit killed $edi, implicit killed $esi, implicit-def $rsp, implicit-def $ssp,  debug-location !14
+    DBG_VALUE 1, $noreg, !11, !DIExpression(),  debug-location !13
+    JMP_1 %bb.3,  debug-location !15
+
+  bb.2.if.else:
+    successors: %bb.3(0x80000000)
+
+    DBG_VALUE 2, $noreg, !11, !DIExpression(),  debug-location !13
+    $edi = MOV32r0 implicit-def dead $eflags,  debug-location !16
+    $esi = MOV32ri 1,  debug-location !16
+    CALL64pcrel32 target-flags(x86-plt) @_Z3barii, csr_64, implicit $rsp, implicit $ssp, implicit killed $edi, implicit killed $esi, implicit-def $rsp, implicit-def $ssp,  debug-location !16
+    DBG_VALUE 3, $noreg, !11, !DIExpression(),  debug-location !13
+
+  bb.3.if.end:
+    $eax = MOV32ri 2
+    $rcx = frame-destroy POP64r implicit-def $rsp, implicit $rsp
+    frame-destroy CFI_INSTRUCTION def_cfa_offset 8
+    RET 0, $eax
+...

Copy link
Member

@jmorse jmorse left a comment

Choose a reason for hiding this comment

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

This LGTM but I think we should leave it a little time for others to chip in. I feel like asserting that TI and FI are identical would help, but that Should (TM) be guaranteed by the earlier loop in HoistCommonCodeInSuccs.

Covered by existings tests e.g.
test/CodeGen/ARM/aes-erratum-fix.ll
OCHyams added a commit to OCHyams/llvm-project that referenced this pull request May 15, 2025
branch-folder hoists common instructions from TBB and FBB into their
pred. Without this patch it achieves this by splicing the instructions from TBB
and deleting the common ones in FBB. That moves the debug locations and debug
instructions from TBB into the pred without modification, which is not
ideal. Debug locations are handled in llvm#140063.

This patch handles debug instructions - in the simplest way possible, which is
to just kill (undef) them. We kill and hoist the ones in FBB as well as TBB
because otherwise the fact there's an assignment on the code path is deleted
(which might lead to a prior location extending further than it should).

There's possibly something we could do to preserve some variable locations in
some cases, but this is the easiest not-incorrect thing to do.

Note I had to replace the constant DBG_VALUEs to use registers in the test- it
turns out setDebugValueUndef doesn't undef constant DBG_VALUEs... which feels
wrong to me, but isn't something I want to touch right now.
OCHyams added 2 commits May 19, 2025 10:44
… modifying some kill markers after its own isIdenticalTo checks
@OCHyams
Copy link
Contributor Author

OCHyams commented May 20, 2025

The pre-merge check failure clang/test/Analysis/ftime-trace-no-init.cpp seems to be spurious (or at least passes for me on windows and linux).

@OCHyams OCHyams merged commit 4060d38 into llvm:main May 20, 2025
9 of 11 checks passed
@OCHyams OCHyams deleted the branch-fold-dbg-loc branch May 20, 2025 10:29
@mikaelholmen
Copy link
Collaborator

Hi @OCHyams

I started seeing crashes with this patch for my downstream target. I managed to reduce and then reproduce it for ARM with
llc -mtriple=armv7-apple-ios bbi-107255_arm.mir -o - -run-pass=branch-folder

It crashes with

llc: ../lib/CodeGen/BranchFolding.cpp:2097: bool llvm::BranchFolder::HoistCommonCodeInSuccs(MachineBasicBlock *): Assertion `TI->isIdenticalTo(*FI, MachineInstr::CheckDefs) && "Expected non-debug lockstep"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0.	Program arguments: build-all/bin/llc -mtriple=armv7-apple-ios bbi-107255_arm.mir -o - -run-pass=branch-folder
1.	Running pass 'Function Pass Manager' on module 'bbi-107255_arm.mir'.
2.	Running pass 'Control Flow Optimizer' on function '@f_1418_2054'
 #0 0x00005629c40322a6 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (build-all/bin/llc+0x76d22a6)
 #1 0x00005629c402fd8e llvm::sys::RunSignalHandlers() (build-all/bin/llc+0x76cfd8e)
 #2 0x00005629c40329d9 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #3 0x00007fa8fc4c7d10 __restore_rt (/lib64/libpthread.so.0+0x12d10)
 #4 0x00007fa8f9e6752f raise (/lib64/libc.so.6+0x4e52f)
 #5 0x00007fa8f9e3ae65 abort (/lib64/libc.so.6+0x21e65)
 #6 0x00007fa8f9e3ad39 _nl_load_domain.cold.0 (/lib64/libc.so.6+0x21d39)
 #7 0x00007fa8f9e5fe86 (/lib64/libc.so.6+0x46e86)
 #8 0x00005629c2e5c974 llvm::BranchFolder::HoistCommonCodeInSuccs(llvm::MachineBasicBlock*) BranchFolding.cpp:0:0
 #9 0x00005629c2e53c25 llvm::BranchFolder::OptimizeFunction(llvm::MachineFunction&, llvm::TargetInstrInfo const*, llvm::TargetRegisterInfo const*, llvm::MachineLoopInfo*, bool) BranchFolding.cpp:0:0
#10 0x00005629c2e5d10b (anonymous namespace)::BranchFolderLegacy::runOnMachineFunction(llvm::MachineFunction&) BranchFolding.cpp:0:0
#11 0x00005629c3062c67 llvm::MachineFunctionPass::runOnFunction(llvm::Function&) (build-all/bin/llc+0x6702c67)
#12 0x00005629c35bb3d9 llvm::FPPassManager::runOnFunction(llvm::Function&) (build-all/bin/llc+0x6c5b3d9)
#13 0x00005629c35c39d2 llvm::FPPassManager::runOnModule(llvm::Module&) (build-all/bin/llc+0x6c639d2)
#14 0x00005629c35bbeb8 llvm::legacy::PassManagerImpl::run(llvm::Module&) (build-all/bin/llc+0x6c5beb8)
#15 0x00005629c0fcf6dd compileModule(char**, llvm::LLVMContext&) llc.cpp:0:0
#16 0x00005629c0fccdc0 main (build-all/bin/llc+0x466cdc0)
#17 0x00007fa8f9e537e5 __libc_start_main (/lib64/libc.so.6+0x3a7e5)
#18 0x00005629c0fcc22e _start (build-all/bin/llc+0x466c22e)
Abort (core dumped)

bbi-107255_arm.mir.gz

@mikaelholmen
Copy link
Collaborator

Hi @OCHyams

I started seeing crashes with this patch for my downstream target. I managed to reduce and then reproduce it for ARM with llc -mtriple=armv7-apple-ios bbi-107255_arm.mir -o - -run-pass=branch-folder

It crashes with

llc: ../lib/CodeGen/BranchFolding.cpp:2097: bool llvm::BranchFolder::HoistCommonCodeInSuccs(MachineBasicBlock *): Assertion `TI->isIdenticalTo(*FI, MachineInstr::CheckDefs) && "Expected non-debug lockstep"' failed.
PLEASE submit a bug report to https://github.com/llvm/llvm-project/issues/ and include the crash backtrace.
Stack dump:
0.	Program arguments: build-all/bin/llc -mtriple=armv7-apple-ios bbi-107255_arm.mir -o - -run-pass=branch-folder
1.	Running pass 'Function Pass Manager' on module 'bbi-107255_arm.mir'.
2.	Running pass 'Control Flow Optimizer' on function '@f_1418_2054'
 #0 0x00005629c40322a6 llvm::sys::PrintStackTrace(llvm::raw_ostream&, int) (build-all/bin/llc+0x76d22a6)
 #1 0x00005629c402fd8e llvm::sys::RunSignalHandlers() (build-all/bin/llc+0x76cfd8e)
 #2 0x00005629c40329d9 SignalHandler(int, siginfo_t*, void*) Signals.cpp:0:0
 #3 0x00007fa8fc4c7d10 __restore_rt (/lib64/libpthread.so.0+0x12d10)
 #4 0x00007fa8f9e6752f raise (/lib64/libc.so.6+0x4e52f)
 #5 0x00007fa8f9e3ae65 abort (/lib64/libc.so.6+0x21e65)
 #6 0x00007fa8f9e3ad39 _nl_load_domain.cold.0 (/lib64/libc.so.6+0x21d39)
 #7 0x00007fa8f9e5fe86 (/lib64/libc.so.6+0x46e86)
 #8 0x00005629c2e5c974 llvm::BranchFolder::HoistCommonCodeInSuccs(llvm::MachineBasicBlock*) BranchFolding.cpp:0:0
 #9 0x00005629c2e53c25 llvm::BranchFolder::OptimizeFunction(llvm::MachineFunction&, llvm::TargetInstrInfo const*, llvm::TargetRegisterInfo const*, llvm::MachineLoopInfo*, bool) BranchFolding.cpp:0:0
#10 0x00005629c2e5d10b (anonymous namespace)::BranchFolderLegacy::runOnMachineFunction(llvm::MachineFunction&) BranchFolding.cpp:0:0
#11 0x00005629c3062c67 llvm::MachineFunctionPass::runOnFunction(llvm::Function&) (build-all/bin/llc+0x6702c67)
#12 0x00005629c35bb3d9 llvm::FPPassManager::runOnFunction(llvm::Function&) (build-all/bin/llc+0x6c5b3d9)
#13 0x00005629c35c39d2 llvm::FPPassManager::runOnModule(llvm::Module&) (build-all/bin/llc+0x6c639d2)
#14 0x00005629c35bbeb8 llvm::legacy::PassManagerImpl::run(llvm::Module&) (build-all/bin/llc+0x6c5beb8)
#15 0x00005629c0fcf6dd compileModule(char**, llvm::LLVMContext&) llc.cpp:0:0
#16 0x00005629c0fccdc0 main (build-all/bin/llc+0x466cdc0)
#17 0x00007fa8f9e537e5 __libc_start_main (/lib64/libc.so.6+0x3a7e5)
#18 0x00005629c0fcc22e _start (build-all/bin/llc+0x466c22e)
Abort (core dumped)

bbi-107255_arm.mir.gz

I think the problem happens when we come to BranchFolder::HoistCommonCodeInSuccs and TBB and FBB are the same block and we still go ahead and hoist and then after

      ++FI;

FI suddenly points to an instruction in MBB.

@OCHyams
Copy link
Contributor Author

OCHyams commented May 22, 2025

Thanks for the reproducer and notes - on it

OCHyams added a commit to OCHyams/llvm-project that referenced this pull request May 22, 2025
Assertion failure introduced in llvm#140063, which didn't account for TBB and FBB
being the same block.
@OCHyams
Copy link
Contributor Author

OCHyams commented May 22, 2025

#141028

OCHyams added a commit that referenced this pull request May 22, 2025
)

Assertion failure introduced in #140063, which didn't account for TBB
and FBB being the same block.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants