[llvm] bbd61cb - Fix llvm bdce debug salvage (#220900)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Sep 17 06:10:42 PDT 2026
Author: MrEven132
Date: 2026-09-17T14:10:36+01:00
New Revision: bbd61cb2e932bb7734cbf034dc50a6f5835e7d77
URL: https://github.com/llvm/llvm-project/commit/bbd61cb2e932bb7734cbf034dc50a6f5835e7d77
DIFF: https://github.com/llvm/llvm-project/commit/bbd61cb2e932bb7734cbf034dc50a6f5835e7d77.diff
LOG: Fix llvm bdce debug salvage (#220900)
BDCE simplifies constant-mask `and`, `or`, and `xor` instructions when
the changed bits are not demanded by program uses. It can similarly
replace a `sext` with a `zext` when none of the sign bits are demanded.
Both transformations used ordinary RAUW, which also redirected direct
debug uses to values with different meanings.
Replace only non-metadata uses when applying these optimizations. This
leaves direct debug users for BDCE's existing reverse-order salvage
pass, which reconstructs supported operations in a `DIExpression` and
makes unsupported locations unavailable.
Add regression tests covering salvage of `and`, `or`, and `xor`, the
unavailable fallback for an unrepresentable wide constant, and
preservation of signed-extension semantics when program uses receive a
`zext`.
Fixes #218890
Fixes #46640
Added:
llvm/test/Transforms/BDCE/dbg-binops-multiuse.ll
llvm/test/Transforms/BDCE/dbg-sext-to-zext.ll
Modified:
llvm/lib/Transforms/Scalar/BDCE.cpp
Removed:
################################################################################
diff --git a/llvm/lib/Transforms/Scalar/BDCE.cpp b/llvm/lib/Transforms/Scalar/BDCE.cpp
index c907b9cd86c43..6697d6b083d5b 100644
--- a/llvm/lib/Transforms/Scalar/BDCE.cpp
+++ b/llvm/lib/Transforms/Scalar/BDCE.cpp
@@ -124,7 +124,9 @@ static bool bitTrackingDCE(Function &F, DemandedBits &DB) {
Demanded.countl_zero() >= (DestBitSize - SrcBitSize)) {
clearAssumptionsOfUsers(SE, DB);
IRBuilder<> Builder(SE);
- I.replaceAllUsesWith(
+ // Leave debug-info users pointing at the old instruction so they can
+ // be salvaged below.
+ I.replaceNonMetadataUsesWith(
Builder.CreateZExt(SE->getOperand(0), DstTy, SE->getName()));
Worklist.push_back(SE);
Changed = true;
@@ -156,7 +158,9 @@ static bool bitTrackingDCE(Function &F, DemandedBits &DB) {
// Avoid incorrect replacement of self-referential values.
if (CanBeSimplified && BO != BO->getOperand(0)) {
clearAssumptionsOfUsers(BO, DB);
- BO->replaceAllUsesWith(BO->getOperand(0));
+ // Leave debug-info users pointing at the old instruction so they
+ // can be salvaged below.
+ BO->replaceNonMetadataUsesWith(BO->getOperand(0));
Worklist.push_back(BO);
++NumSimplified;
Changed = true;
diff --git a/llvm/test/Transforms/BDCE/dbg-binops-multiuse.ll b/llvm/test/Transforms/BDCE/dbg-binops-multiuse.ll
new file mode 100644
index 0000000000000..bed5599c0399d
--- /dev/null
+++ b/llvm/test/Transforms/BDCE/dbg-binops-multiuse.ll
@@ -0,0 +1,69 @@
+; RUN: opt -S -passes=bdce < %s | FileCheck %s
+
+; Check that BDCE salvages debug uses when simplifying multi-use binary
+; operators. If the operation cannot be represented in a DIExpression, the
+; debug value must be killed instead of being replaced with the first operand.
+
+define void @test(i64 %a, i128 %wide) !dbg !5 {
+; CHECK-LABEL: define void @test(
+; CHECK-SAME: i64 [[A:%.*]], i128 [[WIDE:%.*]])
+; CHECK-NEXT: entry:
+; CHECK-NEXT: #dbg_value(i64 [[A]], [[OR_VAR:![0-9]+]], !DIExpression(DW_OP_constu, 3, DW_OP_or, DW_OP_stack_value)
+; CHECK-NEXT: [[OR_USE:%.*]] = and i64 [[A]], 8
+; CHECK-NEXT: #dbg_value(i64 [[A]], [[XOR_VAR:![0-9]+]], !DIExpression(DW_OP_constu, 3, DW_OP_xor, DW_OP_stack_value)
+; CHECK-NEXT: [[XOR_USE:%.*]] = and i64 [[A]], 8
+; CHECK-NEXT: #dbg_value(i64 [[A]], [[AND_VAR:![0-9]+]], !DIExpression(DW_OP_constu, 24, DW_OP_and, DW_OP_stack_value)
+; CHECK-NEXT: [[AND_USE:%.*]] = and i64 [[A]], 8
+; CHECK-NEXT: #dbg_value(i128 poison, [[WIDE_VAR:![0-9]+]], !DIExpression()
+; CHECK-NEXT: [[WIDE_USE:%.*]] = and i128 [[WIDE]], 8
+; CHECK-NEXT: call void @use(i64 [[OR_USE]])
+; CHECK-NEXT: call void @use(i64 [[XOR_USE]])
+; CHECK-NEXT: call void @use(i64 [[AND_USE]])
+; CHECK-NEXT: call void @use_wide(i128 [[WIDE_USE]])
+; CHECK-NEXT: ret void
+entry:
+ %or = or i64 %a, 3
+ #dbg_value(i64 %or, !9, !DIExpression(), !14)
+ %or.use = and i64 %or, 8
+
+ %xor = xor i64 %a, 3
+ #dbg_value(i64 %xor, !10, !DIExpression(), !14)
+ %xor.use = and i64 %xor, 8
+
+ %and = and i64 %a, 24
+ #dbg_value(i64 %and, !11, !DIExpression(), !14)
+ %and.use = and i64 %and, 8
+
+ %wide.or = or i128 %wide, 3
+ #dbg_value(i128 %wide.or, !12, !DIExpression(), !14)
+ %wide.use = and i128 %wide.or, 8
+
+ call void @use(i64 %or.use)
+ call void @use(i64 %xor.use)
+ call void @use(i64 %and.use)
+ call void @use_wide(i128 %wide.use)
+ ret void
+}
+
+declare void @use(i64)
+declare void @use_wide(i128)
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 2, !"Dwarf Version", i32 5}
+!5 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8)
+!6 = !DISubroutineType(types: !7)
+!7 = !{null, !13, !15}
+!8 = !{!9, !10, !11, !12}
+!9 = !DILocalVariable(name: "or", scope: !5, file: !1, line: 2, type: !13)
+!10 = !DILocalVariable(name: "xor", scope: !5, file: !1, line: 3, type: !13)
+!11 = !DILocalVariable(name: "and", scope: !5, file: !1, line: 4, type: !13)
+!12 = !DILocalVariable(name: "wide", scope: !5, file: !1, line: 5, type: !15)
+!13 = !DIBasicType(name: "uint64_t", size: 64, encoding: DW_ATE_unsigned)
+!14 = !DILocation(line: 2, column: 1, scope: !5)
+!15 = !DIBasicType(name: "uint128_t", size: 128, encoding: DW_ATE_unsigned)
diff --git a/llvm/test/Transforms/BDCE/dbg-sext-to-zext.ll b/llvm/test/Transforms/BDCE/dbg-sext-to-zext.ll
new file mode 100644
index 0000000000000..911f42b102487
--- /dev/null
+++ b/llvm/test/Transforms/BDCE/dbg-sext-to-zext.ll
@@ -0,0 +1,39 @@
+; RUN: opt -S -passes=bdce < %s | FileCheck %s
+
+; Check that BDCE preserves the signed-extension semantics of direct debug uses
+; when replacing a sext with a zext because the sign bits are not demanded by
+; program uses.
+
+define i32 @test(i32 %a) !dbg !5 {
+; CHECK-LABEL: define i32 @test(
+; CHECK-SAME: i32 [[A:%.*]])
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[ZEXT:%.*]] = zext i32 [[A]] to i64
+; CHECK-NEXT: #dbg_value(i32 [[A]], [[VAR:![0-9]+]], !DIExpression(DW_OP_LLVM_convert, 32, DW_ATE_signed, DW_OP_LLVM_convert, 64, DW_ATE_signed, DW_OP_stack_value)
+; CHECK-NEXT: [[OR:%.*]] = or i64 [[ZEXT]], 5
+; CHECK-NEXT: [[TRUNC:%.*]] = trunc i64 [[OR]] to i32
+; CHECK-NEXT: ret i32 [[TRUNC]]
+entry:
+ %sext = sext i32 %a to i64
+ #dbg_value(i64 %sext, !9, !DIExpression(), !10)
+ %or = or i64 %sext, 5
+ %trunc = trunc i64 %or to i32
+ ret i32 %trunc
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!3, !4}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: true, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "test.c", directory: "/")
+!2 = !{}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 2, !"Dwarf Version", i32 5}
+!5 = distinct !DISubprogram(name: "test", scope: !1, file: !1, line: 1, type: !6, scopeLine: 1, spFlags: DISPFlagDefinition | DISPFlagOptimized, unit: !0, retainedNodes: !8)
+!6 = !DISubroutineType(types: !7)
+!7 = !{!11, !11}
+!8 = !{!9}
+!9 = !DILocalVariable(name: "extended", scope: !5, file: !1, line: 2, type: !12)
+!10 = !DILocation(line: 2, column: 1, scope: !5)
+!11 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
+!12 = !DIBasicType(name: "long", size: 64, encoding: DW_ATE_signed)
More information about the llvm-commits
mailing list