[llvm] [DebugInfo][Reassociate] Preserve `fmul`/`fdiv` debug info by wrapping with `DW_OP_neg` when the constant is sign-flipped (PR #224898)
via llvm-commits
llvm-commits at lists.llvm.org
Mon Sep 21 03:42:02 PDT 2026
https://github.com/MrEven132 updated https://github.com/llvm/llvm-project/pull/224898
>From 712e15ba4a468d2207ea5c657e531ee2c273d423 Mon Sep 17 00:00:00 2001
From: MrEven132 <13554966229 at 163.com>
Date: Sun, 20 Sep 2026 17:03:52 +0800
Subject: [PATCH 1/3] [DebugInfo][Reassociate] Preserve `fmul`/`fdiv` debug
info by wrapping with `DW_OP_neg` when the constant is sign-flipped
When `reassociate` rewrites `fmul X, -K` into `fmul X, K` (or the analogous
`fdiv` case) via `canonicalizeNegFPConstantsForOp`, the SSA def is reused but
its computed value has flipped sign. Any `#dbg_value` describing that def
otherwise continues to claim the original sign-flipped value, so LLDB ends up
reporting the wrong value for the source variable (for example printing
`mul = 0.1234` for `-0.1234 * y`).
Wrap each surviving debug user's `DIExpression` with `DW_OP_neg` via the
existing `DIExpression::prependOpcodes` helper. The IR-level canonicalization
is unchanged; only the debug record's expression is updated so the source
variable continues to report its source-level value.
Fixes #220208.
---
llvm/lib/Transforms/Scalar/Reassociate.cpp | 19 ++++++
.../canonicalize-neg-const-debuginfo.ll | 62 +++++++++++++++++++
2 files changed, 81 insertions(+)
create mode 100644 llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
diff --git a/llvm/lib/Transforms/Scalar/Reassociate.cpp b/llvm/lib/Transforms/Scalar/Reassociate.cpp
index 5e5b0a8ae1d8f..02859bc28a803 100644
--- a/llvm/lib/Transforms/Scalar/Reassociate.cpp
+++ b/llvm/lib/Transforms/Scalar/Reassociate.cpp
@@ -32,11 +32,14 @@
#include "llvm/Analysis/ConstantFolding.h"
#include "llvm/Analysis/GlobalsModRef.h"
#include "llvm/Analysis/ValueTracking.h"
+#include "llvm/BinaryFormat/Dwarf.h"
#include "llvm/IR/Argument.h"
#include "llvm/IR/BasicBlock.h"
#include "llvm/IR/CFG.h"
#include "llvm/IR/Constant.h"
#include "llvm/IR/Constants.h"
+#include "llvm/IR/DebugInfo.h"
+#include "llvm/IR/DebugInfoMetadata.h"
#include "llvm/IR/Function.h"
#include "llvm/IR/IRBuilder.h"
#include "llvm/IR/InstrTypes.h"
@@ -2245,6 +2248,22 @@ Instruction *ReassociatePass::canonicalizeNegFPConstantsForOp(Instruction *I,
Negatible->setOperand(1, ConstantFP::get(Negatible->getType(), abs(*C)));
MadeChange = true;
}
+ // The def's value has changed sign; wrap each `#dbg_value`
+ // referencing it with `DW_OP_neg` so the variable keeps reporting
+ // its source-level value.
+ SmallVector<DbgVariableRecord *, 1> DPUsers;
+ findDbgUsers(Negatible, DPUsers);
+ SmallVector<uint64_t, 1> NegOps{dwarf::DW_OP_neg};
+ for (DbgVariableRecord *DVR : DPUsers) {
+ DIExpression *NewExpr = DVR->getExpression();
+ for (unsigned Idx = 0, N = DVR->getNumVariableLocationOps();
+ Idx < N; ++Idx) {
+ if (DVR->getVariableLocationOp(Idx) == Negatible)
+ NewExpr = DIExpression::appendOpsToArg(NewExpr, NegOps, Idx,
+ /*StackValue=*/true);
+ }
+ DVR->setExpression(NewExpr);
+ }
}
assert(MadeChange == true && "Negative constant candidate was not changed");
diff --git a/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll b/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
new file mode 100644
index 0000000000000..5d9e3cb4bbb98
--- /dev/null
+++ b/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
@@ -0,0 +1,62 @@
+; NOTE: Assertions have been autogenerated by utils/update_test_checks.py
+; RUN: opt -passes=reassociate -S < %s | FileCheck %s
+
+; When `reassociate` rewrites `fmul X, -K` (or `fdiv X, -K`) into the
+; canonical `fmul X, K` (or `fdiv X, K`), the SSA def is reused but its
+; computed value has flipped sign. Wrap any `#dbg_value` describing the
+; def with `DW_OP_neg` so the source variable continues to report its
+; source-level value: prepend it for simple `#dbg_value`s and insert it
+; after the matching `DW_OP_LLVM_arg, idx` for `DIArgList` references
+; (including the case where several negatibles from the same parent
+; expression are referenced by the same arg list).
+;
+; See https://github.com/llvm/llvm-project/issues/220208.
+
+define double @neg_mul_dbgvalue(double %x, double %y, double %z) !dbg !9 {
+; CHECK-LABEL: @neg_mul_dbgvalue(
+; CHECK-NEXT: entry:
+; CHECK-NEXT: [[A:%.*]] = fmul double [[Y:%.*]], 1.000000e+00, !dbg [[DBG11:![0-9]+]]
+; CHECK-NEXT: #dbg_value(double [[A]], [[META12:![0-9]+]], !DIExpression(DW_OP_neg, DW_OP_stack_value), [[META13:![0-9]+]])
+; CHECK-NEXT: #dbg_value(!DIArgList(i32 7, double [[A]]), [[META14:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_stack_value), [[META13]])
+; CHECK-NEXT: [[B:%.*]] = fdiv double [[Z:%.*]], 2.000000e+00, !dbg [[DBG15:![0-9]+]]
+; CHECK-NEXT: #dbg_value(!DIArgList(double [[A]], double [[B]]), [[META16:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_neg, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_stack_value), [[META13]])
+; CHECK-NEXT: [[PROD:%.*]] = fmul double [[A]], [[B]], !dbg [[DBG17:![0-9]+]]
+; CHECK-NEXT: [[SUM:%.*]] = fsub double [[X:%.*]], [[PROD]], !dbg [[DBG18:![0-9]+]]
+; CHECK-NEXT: ret double [[SUM]], !dbg [[DBG19:![0-9]+]]
+;
+entry:
+ %a = fmul double %y, -1.000000e+00, !dbg !15
+ #dbg_value(double %a, !16, !DIExpression(), !14)
+ #dbg_value(!DIArgList(i32 7, double %a), !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !14)
+ %b = fdiv double %z, -2.000000e+00, !dbg !18
+ #dbg_value(!DIArgList(double %a, double %b), !19, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !14)
+ ; `fmul %a, %b` chains both negatibles into one canonicalization call.
+ %prod = fmul double %a, %b, !dbg !20
+ %sum = fsub double %x, %prod, !dbg !21
+ ret double %sum, !dbg !22
+}
+
+!llvm.dbg.cu = !{!0}
+!llvm.module.flags = !{!2, !3, !4, !5, !6}
+
+!0 = distinct !DICompileUnit(language: DW_LANG_C11, file: !1, producer: "clang", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
+!1 = !DIFile(filename: "case.c", directory: ".")
+!2 = !{i32 7, !"Dwarf Version", i32 5}
+!3 = !{i32 2, !"Debug Info Version", i32 3}
+!4 = !{i32 1, !"wchar_size", i32 4}
+!5 = !{i32 7, !"PIC Level", i32 2}
+!6 = !{i32 7, !"PIE Level", i32 2}
+
+!9 = distinct !DISubprogram(name: "neg_mul_dbgvalue", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !0)
+!11 = distinct !DISubroutineType(types: !12)
+!12 = !{!13, !13, !13}
+!13 = !DIBasicType(name: "double", size: 64, encoding: DW_ATE_float)
+!14 = !DILocation(line: 0, scope: !9)
+!15 = !DILocation(line: 2, column: 24, scope: !9)
+!16 = !DILocalVariable(name: "a", scope: !9, file: !1, line: 2, type: !13)
+!17 = !DILocalVariable(name: "combo", scope: !9, file: !1, line: 2, type: !13)
+!18 = !DILocation(line: 3, column: 24, scope: !9)
+!19 = !DILocalVariable(name: "both", scope: !9, file: !1, line: 3, type: !13)
+!20 = !DILocation(line: 4, column: 17, scope: !9)
+!21 = !DILocation(line: 5, column: 10, scope: !9)
+!22 = !DILocation(line: 6, column: 3, scope: !9)
>From 731a09cc30e9c09f9ba8992acb69c480c9ccaa79 Mon Sep 17 00:00:00 2001
From: MrEven132 <13554966229 at 163.com>
Date: Mon, 21 Sep 2026 11:00:18 +0800
Subject: [PATCH 2/3] clang-format
---
llvm/lib/Transforms/Scalar/Reassociate.cpp | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/Reassociate.cpp b/llvm/lib/Transforms/Scalar/Reassociate.cpp
index 02859bc28a803..b05c7f9c754ba 100644
--- a/llvm/lib/Transforms/Scalar/Reassociate.cpp
+++ b/llvm/lib/Transforms/Scalar/Reassociate.cpp
@@ -2256,8 +2256,8 @@ Instruction *ReassociatePass::canonicalizeNegFPConstantsForOp(Instruction *I,
SmallVector<uint64_t, 1> NegOps{dwarf::DW_OP_neg};
for (DbgVariableRecord *DVR : DPUsers) {
DIExpression *NewExpr = DVR->getExpression();
- for (unsigned Idx = 0, N = DVR->getNumVariableLocationOps();
- Idx < N; ++Idx) {
+ for (unsigned Idx = 0, N = DVR->getNumVariableLocationOps(); Idx < N;
+ ++Idx) {
if (DVR->getVariableLocationOp(Idx) == Negatible)
NewExpr = DIExpression::appendOpsToArg(NewExpr, NegOps, Idx,
/*StackValue=*/true);
>From 6f52b294eca3eca9674f824e26cbccbb614e1e3d Mon Sep 17 00:00:00 2001
From: MrEven132 <13554966229 at 163.com>
Date: Mon, 21 Sep 2026 18:41:21 +0800
Subject: [PATCH 3/3] [DebugInfo][Reassociate] Avoid updating unreferenced
DIArgList operands
DIExpression::appendOpsToArg expects a DIArgList location operand to be
referenced by a corresponding DW_OP_LLVM_arg. A malformed debug record may
contain one or more location operands without corresponding DW_OP_LLVM_arg
operations, causing Reassociate to assert when attempting to update such an
operand. In builds without assertions, this could instead produce an incorrect
expression.
Only append DW_OP_neg when the matching DIArgList operand is referenced by a
corresponding DW_OP_LLVM_arg. This avoids assigning semantics to unreferenced
operands and preserves the backend's existing handling of malformed debug
information. Also skip dbg.declare records, which describe addresses rather
than values.
Add coverage for expressions that reference the same location operand multiple
times.
---
llvm/lib/Transforms/Scalar/Reassociate.cpp | 27 +++++++++++++------
.../canonicalize-neg-const-debuginfo.ll | 18 ++++++++-----
2 files changed, 30 insertions(+), 15 deletions(-)
diff --git a/llvm/lib/Transforms/Scalar/Reassociate.cpp b/llvm/lib/Transforms/Scalar/Reassociate.cpp
index b05c7f9c754ba..1824d0dec1606 100644
--- a/llvm/lib/Transforms/Scalar/Reassociate.cpp
+++ b/llvm/lib/Transforms/Scalar/Reassociate.cpp
@@ -2248,21 +2248,32 @@ Instruction *ReassociatePass::canonicalizeNegFPConstantsForOp(Instruction *I,
Negatible->setOperand(1, ConstantFP::get(Negatible->getType(), abs(*C)));
MadeChange = true;
}
- // The def's value has changed sign; wrap each `#dbg_value`
- // referencing it with `DW_OP_neg` so the variable keeps reporting
- // its source-level value.
+ // The def's value has changed sign; wrap each debug value referencing it
+ // with `DW_OP_neg` so the variable keeps reporting its source-level value.
SmallVector<DbgVariableRecord *, 1> DPUsers;
findDbgUsers(Negatible, DPUsers);
SmallVector<uint64_t, 1> NegOps{dwarf::DW_OP_neg};
for (DbgVariableRecord *DVR : DPUsers) {
- DIExpression *NewExpr = DVR->getExpression();
+ // A `#dbg_declare` describes an address, not the changed value.
+ if (DVR->isAddressOfVariable())
+ continue;
+ DIExpression *OldExpr = DVR->getExpression();
+ DIExpression *NewExpr = OldExpr;
for (unsigned Idx = 0, N = DVR->getNumVariableLocationOps(); Idx < N;
++Idx) {
- if (DVR->getVariableLocationOp(Idx) == Negatible)
- NewExpr = DIExpression::appendOpsToArg(NewExpr, NegOps, Idx,
- /*StackValue=*/true);
+ if (DVR->getVariableLocationOp(Idx) != Negatible)
+ continue;
+ // Ignore `DIArgList` operands without a corresponding `DW_OP_LLVM_arg`.
+ if (DVR->hasArgList() && none_of(NewExpr->expr_ops(), [Idx](auto Op) {
+ auto Arg = dyn_cast<DIExpression::ArgOp>(Op);
+ return Arg && Arg.getIndex() == Idx;
+ }))
+ continue;
+ NewExpr = DIExpression::appendOpsToArg(NewExpr, NegOps, Idx,
+ /*StackValue=*/true);
}
- DVR->setExpression(NewExpr);
+ if (NewExpr != OldExpr)
+ DVR->setExpression(NewExpr);
}
}
assert(MadeChange == true && "Negative constant candidate was not changed");
diff --git a/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll b/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
index 5d9e3cb4bbb98..55caf2ad9201e 100644
--- a/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
+++ b/llvm/test/Transforms/Reassociate/canonicalize-neg-const-debuginfo.ll
@@ -7,8 +7,9 @@
; def with `DW_OP_neg` so the source variable continues to report its
; source-level value: prepend it for simple `#dbg_value`s and insert it
; after the matching `DW_OP_LLVM_arg, idx` for `DIArgList` references
-; (including the case where several negatibles from the same parent
-; expression are referenced by the same arg list).
+; (including repeated references to the same location operand and the case
+; where several negatibles from the same parent expression are referenced by
+; the same arg list).
;
; See https://github.com/llvm/llvm-project/issues/220208.
@@ -18,16 +19,18 @@ define double @neg_mul_dbgvalue(double %x, double %y, double %z) !dbg !9 {
; CHECK-NEXT: [[A:%.*]] = fmul double [[Y:%.*]], 1.000000e+00, !dbg [[DBG11:![0-9]+]]
; CHECK-NEXT: #dbg_value(double [[A]], [[META12:![0-9]+]], !DIExpression(DW_OP_neg, DW_OP_stack_value), [[META13:![0-9]+]])
; CHECK-NEXT: #dbg_value(!DIArgList(i32 7, double [[A]]), [[META14:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_stack_value), [[META13]])
-; CHECK-NEXT: [[B:%.*]] = fdiv double [[Z:%.*]], 2.000000e+00, !dbg [[DBG15:![0-9]+]]
-; CHECK-NEXT: #dbg_value(!DIArgList(double [[A]], double [[B]]), [[META16:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_neg, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_stack_value), [[META13]])
-; CHECK-NEXT: [[PROD:%.*]] = fmul double [[A]], [[B]], !dbg [[DBG17:![0-9]+]]
-; CHECK-NEXT: [[SUM:%.*]] = fsub double [[X:%.*]], [[PROD]], !dbg [[DBG18:![0-9]+]]
-; CHECK-NEXT: ret double [[SUM]], !dbg [[DBG19:![0-9]+]]
+; CHECK-NEXT: #dbg_value(!DIArgList(i32 7, double [[A]]), [[META15:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_plus, DW_OP_stack_value), [[META13]])
+; CHECK-NEXT: [[B:%.*]] = fdiv double [[Z:%.*]], 2.000000e+00, !dbg [[DBG16:![0-9]+]]
+; CHECK-NEXT: #dbg_value(!DIArgList(double [[A]], double [[B]]), [[META17:![0-9]+]], !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_neg, DW_OP_LLVM_arg, 1, DW_OP_neg, DW_OP_plus, DW_OP_stack_value), [[META13]])
+; CHECK-NEXT: [[PROD:%.*]] = fmul double [[A]], [[B]], !dbg [[DBG18:![0-9]+]]
+; CHECK-NEXT: [[SUM:%.*]] = fsub double [[X:%.*]], [[PROD]], !dbg [[DBG19:![0-9]+]]
+; CHECK-NEXT: ret double [[SUM]], !dbg [[DBG20:![0-9]+]]
;
entry:
%a = fmul double %y, -1.000000e+00, !dbg !15
#dbg_value(double %a, !16, !DIExpression(), !14)
#dbg_value(!DIArgList(i32 7, double %a), !17, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !14)
+ #dbg_value(!DIArgList(i32 7, double %a), !23, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_plus, DW_OP_stack_value), !14)
%b = fdiv double %z, -2.000000e+00, !dbg !18
#dbg_value(!DIArgList(double %a, double %b), !19, !DIExpression(DW_OP_LLVM_arg, 0, DW_OP_LLVM_arg, 1, DW_OP_plus, DW_OP_stack_value), !14)
; `fmul %a, %b` chains both negatibles into one canonicalization call.
@@ -60,3 +63,4 @@ entry:
!20 = !DILocation(line: 4, column: 17, scope: !9)
!21 = !DILocation(line: 5, column: 10, scope: !9)
!22 = !DILocation(line: 6, column: 3, scope: !9)
+!23 = !DILocalVariable(name: "twice", scope: !9, file: !1, line: 2, type: !13)
More information about the llvm-commits
mailing list