[Lldb-commits] [lldb] 474b21a - [DebugInfo][LLDB] Fix generic DW_OP_const handling (#204353)
via lldb-commits
lldb-commits at lists.llvm.org
Wed Jun 24 00:13:47 PDT 2026
Author: firmiana402
Date: 2026-06-24T15:13:41+08:00
New Revision: 474b21aa99899756ede1e767abed2be63783c8ec
URL: https://github.com/llvm/llvm-project/commit/474b21aa99899756ede1e767abed2be63783c8ec
DIFF: https://github.com/llvm/llvm-project/commit/474b21aa99899756ede1e767abed2be63783c8ec.diff
LOG: [DebugInfo][LLDB] Fix generic DW_OP_const handling (#204353)
This PR fixes two related DWARF constant-handling bugs that were
blocking each other.
First, LLDB's DWARF expression evaluator in
[`DWARFExpression.cpp`](https://github.com/llvm/llvm-project/blob/main/lldb/source/Expression/DWARFExpression.cpp)
handled `DW_OP_constu` and `DW_OP_consts` without going through
`to_generic`. Under DWARF, these operators push a generic value: an
address-sized integral value with unspecified signedness. That means the
result should be truncated to the target address size (via
`to_generic`).
Second, LLVM already had a producer-side issue tracked as
[#47431](https://github.com/llvm/llvm-project/issues/47431): on 32-bit
targets, LLVM could emit `DW_OP_consts` / `DW_OP_constu` for source
integer constants wider than the target generic type. If LLDB were fixed
alone, those producer-emitted constants would become truncated as DWARF
requires, exposing incorrect debug info for wide source values.
This patch fixes both sides together.
## What Changed
On the LLDB consumer side:
- `DW_OP_constu` now uses `to_generic`.
- `DW_OP_consts` now uses `to_generic`.
- The corresponding LLDB DWARF expression tests were updated to expect
address-sized generic values.
On the LLVM producer side:
- Wide integer debug-location constants that cannot be represented by
the target generic type are emitted as `DW_OP_implicit_value` instead of
`DW_OP_const*`.
- This preserves the source value bytes instead of relying on an
address-sized DWARF generic constant.
- The producer-side change is limited to complete constant values, where
there are no remaining `DIExpression` operations.
## Validation
Locally verified with:
```text
build/tools/lldb/unittests/Expression/ExpressionTests --gtest_filter='DWARFExpression.*'
74 tests passed
build/bin/llvm-lit -sv llvm/test/DebugInfo/X86/constant-loclist.ll
1 test passed
ninja -C build check-lldb -j12
No unexpected failures
ninja -C build check-all -j12
Completed with one unrelated local failure in Clang Tools :: clang-doc/DR-141990.cpp, caused by host warning-option output. No DebugInfo, DWARF, LLDB expression, or AsmPrinter-related failures were observed.
```
Added:
Modified:
lldb/source/Expression/DWARFExpression.cpp
lldb/unittests/Expression/DWARFExpressionTest.cpp
llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
llvm/lib/CodeGen/AsmPrinter/DwarfExpression.h
llvm/test/DebugInfo/X86/constant-loclist.ll
Removed:
################################################################################
diff --git a/lldb/source/Expression/DWARFExpression.cpp b/lldb/source/Expression/DWARFExpression.cpp
index dd436e0c8afd9..7966673bb65ed 100644
--- a/lldb/source/Expression/DWARFExpression.cpp
+++ b/lldb/source/Expression/DWARFExpression.cpp
@@ -1411,13 +1411,11 @@ llvm::Expected<Value> DWARFExpression::Evaluate(
case DW_OP_const8s:
stack.push_back(to_generic(static_cast<int64_t>(op->getRawOperand(0))));
break;
- // These should also use to_generic, but we can't do that due to a
- // producer-side bug in llvm. See llvm.org/pr48087.
case DW_OP_constu:
- stack.push_back(Scalar(op->getRawOperand(0)));
+ stack.push_back(to_generic(op->getRawOperand(0)));
break;
case DW_OP_consts:
- stack.push_back(Scalar(static_cast<int64_t>(op->getRawOperand(0))));
+ stack.push_back(to_generic(static_cast<int64_t>(op->getRawOperand(0))));
break;
case DW_OP_dup:
diff --git a/lldb/unittests/Expression/DWARFExpressionTest.cpp b/lldb/unittests/Expression/DWARFExpressionTest.cpp
index 6d4a624505390..7feacb2b9da24 100644
--- a/lldb/unittests/Expression/DWARFExpressionTest.cpp
+++ b/lldb/unittests/Expression/DWARFExpressionTest.cpp
@@ -388,13 +388,13 @@ TEST(DWARFExpression, DW_OP_const) {
Evaluate({DW_OP_const8s, 0x00, 0x11, 0x22, 0x33, 0x44, 0x42, 0x47, 0x88}),
ExpectScalar(0x33221100));
- // Don't truncate to address size for compatibility with clang (pr48087).
+ // LEB constants also push generic values, so truncate to address size.
EXPECT_THAT_EXPECTED(
Evaluate({DW_OP_constu, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}),
- ExpectScalar(0x01010101010101));
+ ExpectScalar(32, 0x01010101, false));
EXPECT_THAT_EXPECTED(
Evaluate({DW_OP_consts, 0x81, 0x82, 0x84, 0x88, 0x90, 0xa0, 0x40}),
- ExpectScalar(0xffff010101010101));
+ ExpectScalar(32, 0x01010101, true));
}
TEST(DWARFExpression, DW_OP_skip) {
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
index 3479485cf866c..64b6b27346575 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfDebug.cpp
@@ -50,6 +50,7 @@
#include "llvm/Support/Debug.h"
#include "llvm/Support/ErrorHandling.h"
#include "llvm/Support/MD5.h"
+#include "llvm/Support/MathExtras.h"
#include "llvm/Support/raw_ostream.h"
#include "llvm/Target/TargetLoweringObjectFile.h"
#include "llvm/Target/TargetMachine.h"
@@ -3251,10 +3252,39 @@ void DwarfDebug::emitDebugLocValue(const AsmPrinter &AP, const DIBasicType *BT,
&AP](const DbgValueLocEntry &Entry,
DIExpressionCursor &Cursor) -> bool {
if (Entry.isInt()) {
- if (BT && (BT->getEncoding() == dwarf::DW_ATE_boolean))
+ if (BT && (BT->getEncoding() == dwarf::DW_ATE_boolean)) {
DwarfExpr.addBooleanConstant(Entry.getInt());
- else if (BT && (BT->getEncoding() == dwarf::DW_ATE_signed ||
- BT->getEncoding() == dwarf::DW_ATE_signed_char))
+ return true;
+ }
+
+ bool IsSigned = BT && (BT->getEncoding() == dwarf::DW_ATE_signed ||
+ BT->getEncoding() == dwarf::DW_ATE_signed_char);
+ if (BT && AP.getDwarfVersion() >= 4 &&
+ !AP.getDwarfDebug()->tuneForSCE() && !Cursor) {
+ // DW_OP_const* pushes a generic, address-sized value. For a wider
+ // source integer value that cannot fit in the generic type, use
+ // DW_OP_implicit_value to preserve the source bytes instead. Keep this
+ // limited to complete constant values: SCE tuning already avoids
+ // DW_OP_implicit_value for compatibility, and expressions with
+ // remaining operations may need a scalar stack value rather than an
+ // implicit value block.
+ unsigned GenericBitSize = AP.MAI.getCodePointerSize() * 8;
+ uint64_t TypeBitSize = BT->getSizeInBits();
+ bool IsByteSized = TypeBitSize % 8 == 0;
+ bool IsOutOfRange =
+ IsSigned ? !isIntN(GenericBitSize, Entry.getInt())
+ : !isUIntN(GenericBitSize,
+ static_cast<uint64_t>(Entry.getInt()));
+ if (TypeBitSize > GenericBitSize && IsByteSized && IsOutOfRange) {
+ DwarfExpr.addImplicitValue(
+ APInt(static_cast<unsigned>(TypeBitSize),
+ static_cast<uint64_t>(Entry.getInt()), IsSigned),
+ AP);
+ return true;
+ }
+ }
+
+ if (IsSigned)
DwarfExpr.addSignedConstant(Entry.getInt());
else
DwarfExpr.addUnsignedConstant(Entry.getInt());
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
index d9b2deb6ccf3d..ef83a877ad37c 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.cpp
@@ -236,6 +236,28 @@ void DwarfExpression::addUnsignedConstant(const APInt &Value) {
}
}
+void DwarfExpression::addImplicitValue(const APInt &Value,
+ const AsmPrinter &AP) {
+ assert(isImplicitLocation() || isUnknownLocation());
+ assert(DwarfVersion >= 4);
+
+ APInt API = Value;
+ unsigned NumBytes = API.getBitWidth() / 8;
+ assert(API.getBitWidth() == NumBytes * 8 &&
+ "implicit value must be byte-sized");
+
+ emitOp(dwarf::DW_OP_implicit_value);
+ emitUnsigned(NumBytes);
+
+ // The loop below is emitting the value starting at the least significant
+ // byte, so byte-swap first for big-endian targets.
+ if (AP.getDataLayout().isBigEndian())
+ API = API.byteSwap();
+
+ for (unsigned I = 0; I < NumBytes; ++I)
+ emitData1(API.extractBits(8, I * 8).getZExtValue());
+}
+
void DwarfExpression::addConstantFP(const APFloat &APF, const AsmPrinter &AP) {
assert(isImplicitLocation() || isUnknownLocation());
APInt API = APF.bitcastToAPInt();
diff --git a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.h b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.h
index c4929aed1c197..2525938c78d86 100644
--- a/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.h
+++ b/llvm/lib/CodeGen/AsmPrinter/DwarfExpression.h
@@ -240,6 +240,9 @@ class DwarfExpression {
/// Emit an unsigned constant.
void addUnsignedConstant(const APInt &Value);
+ /// Emit an implicit value.
+ void addImplicitValue(const APInt &Value, const AsmPrinter &AP);
+
/// Emit an floating point constant.
void addConstantFP(const APFloat &Value, const AsmPrinter &AP);
diff --git a/llvm/test/DebugInfo/X86/constant-loclist.ll b/llvm/test/DebugInfo/X86/constant-loclist.ll
index 630c652ba0ccf..702f25ab2b71d 100644
--- a/llvm/test/DebugInfo/X86/constant-loclist.ll
+++ b/llvm/test/DebugInfo/X86/constant-loclist.ll
@@ -1,4 +1,6 @@
; RUN: llc -filetype=obj %s -o - -experimental-debug-variable-locations=true | llvm-dwarfdump -v -debug-info - | FileCheck %s
+; RUN: llc -filetype=obj %s -o - -experimental-debug-variable-locations=true -mtriple=i386-unknown-linux-gnu -dwarf-version=4 | llvm-dwarfdump -v -debug-info - | FileCheck %s --check-prefix=I386
+; RUN: llc -filetype=obj %s -o - -experimental-debug-variable-locations=true -mtriple=i386-unknown-linux-gnu -dwarf-version=2 -debugger-tune=sce | llvm-dwarfdump -v -debug-info - | FileCheck %s --check-prefix=I386-COMPAT
; A hand-written testcase to check 64-bit constant handling in location lists.
@@ -18,6 +20,36 @@
; CHECK-NEXT: {{.*}}: DW_OP_constu 0x4000000000000000)
; CHECK-NEXT: DW_AT_name {{.*}}"d"
+; On 32-bit targets, source integer constants that do not fit in the
+; address-sized DWARF generic type must use DW_OP_implicit_value to avoid
+; truncating the source value.
+; I386: .debug_info contents:
+; I386: DW_TAG_variable
+; I386-NEXT: DW_AT_location
+; I386-NEXT: {{.*}}: DW_OP_lit0
+; I386-NEXT: {{.*}}: DW_OP_implicit_value 0x8 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x40)
+; I386-NEXT: DW_AT_name {{.*}}"u"
+; I386: DW_TAG_variable
+; I386-NEXT: DW_AT_location
+; I386-NEXT: {{.*}}: DW_OP_consts +0
+; I386-NEXT: {{.*}}: DW_OP_implicit_value 0x8 0x00 0x00 0x00 0x00 0x00 0x00 0x00 0x40)
+; I386-NEXT: DW_AT_name {{.*}}"i"
+
+; DWARF v2 and SCE tuning keep using DW_OP_const* for compatibility.
+; I386-COMPAT: .debug_info contents:
+; I386-COMPAT: DW_TAG_variable
+; I386-COMPAT-NEXT: DW_AT_location
+; I386-COMPAT-NEXT: {{.*}}: DW_OP_lit0
+; I386-COMPAT-NOT: DW_OP_implicit_value
+; I386-COMPAT-NEXT: {{.*}}: DW_OP_constu 0x4000000000000000)
+; I386-COMPAT-NEXT: DW_AT_name {{.*}}"u"
+; I386-COMPAT: DW_TAG_variable
+; I386-COMPAT-NEXT: DW_AT_location
+; I386-COMPAT-NEXT: {{.*}}: DW_OP_consts +0
+; I386-COMPAT-NOT: DW_OP_implicit_value
+; I386-COMPAT-NEXT: {{.*}}: DW_OP_consts +4611686018427387904)
+; I386-COMPAT-NEXT: DW_AT_name {{.*}}"i"
+
source_filename = "test.c"
target datalayout = "e-m:o-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-apple-macosx"
More information about the lldb-commits
mailing list