Unavailable variables under the Visual Studio Debugger at the function end
Nobody has claimed this yet.
- Dominant language
- Rust
- Stars
- 119k
- Forks
- 16.2k
- PR merge metrics
- PR metrics pending
Description
Rust version:
cargo 1.76.0-nightly (1aa9df1a5 2023-12-12)
release: 1.76.0-nightly
commit-hash: 1aa9df1a5be205cce621f0bc0ea6062a5e22a98c
commit-date: 2023-12-12
host: x86_64-pc-windows-msvc
libgit2: 1.7.1 (sys:0.18.1 vendored)
libcurl: 8.5.0-DEV (sys:0.4.70+curl-8.5.0 vendored ssl:Schannel)
os: Windows 10.0.19045 (Windows 10 Pro) [64-bit]
Rust code:
pub fn main() {
let x = 10;
let y = x + 10;
}
How it looks now:
The y variable never appears in the window in this case.
I slightly modified the LLVM output in order to be easily tested with clang (clang main.ll -g -gcodeview).
(Semi) Original LLVM IR
; ModuleID = 'example.1d7810305fd8544f-cgu.0'
source_filename = "example.1d7810305fd8544f-cgu.0"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc"
@alloc_f58f411d2051cb7654f7b8a078d6577a = private unnamed_addr constant <{ [69 x i8] }> <{ [69 x i8] c"/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse/example.rs" }>, align 1
@alloc_2aef29fe3608c713caacd9ceb2662f82 = private unnamed_addr constant <{ ptr, [16 x i8] }> <{ ptr @alloc_f58f411d2051cb7654f7b8a078d6577a, [16 x i8] c"E\00\00\00\00\00\00\00\03\00\00\00\0D\00\00\00" }>, align 8
@str.0 = internal unnamed_addr constant [28 x i8] c"attempt to add with overflow"
; example::main
; Function Attrs: uwtable
define void @main() unnamed_addr #0 !dbg !6 {
start:
%y.dbg.spill = alloca i32, align 4
%x.dbg.spill = alloca i32, align 4
store i32 10, ptr %x.dbg.spill, align 4, !dbg !20
call void @llvm.dbg.declare(metadata ptr %x.dbg.spill, metadata !12, metadata !DIExpression()), !dbg !20
%0 = call { i32, i1 } @llvm.sadd.with.overflow.i32(i32 10, i32 10), !dbg !21
%_3.0 = extractvalue { i32, i1 } %0, 0, !dbg !21
%_3.1 = extractvalue { i32, i1 } %0, 1, !dbg !21
%1 = call i1 @llvm.expect.i1(i1 %_3.1, i1 false), !dbg !21
br i1 %1, label %panic, label %bb1, !dbg !21
bb1: ; preds = %start
store i32 %_3.0, ptr %y.dbg.spill, align 4, !dbg !21
call void @llvm.dbg.declare(metadata ptr %y.dbg.spill, metadata !17, metadata !DIExpression()), !dbg !22
ret void, !dbg !23
panic: ; preds = %start
; call core_panicking_panic
call void @core_panicking_panic(ptr align 1 @str.0, i64 28, ptr align 8 @alloc_2aef29fe3608c713caacd9ceb2662f82) #4, !dbg !21
unreachable, !dbg !21
}
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare void @llvm.dbg.declare(metadata, metadata, metadata) #1
; Function Attrs: nocallback nofree nosync nounwind speculatable willreturn memory(none)
declare { i32, i1 } @llvm.sadd.with.overflow.i32(i32, i32) #1
; Function Attrs: nocallback nofree nosync nounwind willreturn memory(none)
declare i1 @llvm.expect.i1(i1, i1) #2
; core_panicking_panic
; Function Attrs: cold noinline noreturn uwtable
define void @core_panicking_panic(ptr align 1, i64, ptr align 8) unnamed_addr #3 {
start:
ret void;
}
attributes #0 = { uwtable "target-cpu"="x86-64" }
attributes #1 = { nocallback nofree nosync nounwind speculatable willreturn memory(none) }
attributes #2 = { nocallback nofree nosync nounwind willreturn memory(none) }
attributes #3 = { cold noinline noreturn uwtable "target-cpu"="x86-64" }
attributes #4 = { noreturn }
!llvm.module.flags = !{!0, !1, !2}
!llvm.ident = !{!3}
!llvm.dbg.cu = !{!4}
!0 = !{i32 8, !"PIC Level", i32 2}
!1 = !{i32 2, !"CodeView", i32 1}
!2 = !{i32 2, !"Debug Info Version", i32 3}
!3 = !{!"rustc version 1.76.0-nightly (6a6287132 2023-12-17)"}
!4 = distinct !DICompileUnit(language: DW_LANG_Rust, file: !5, producer: "clang LLVM (rustc version 1.76.0-nightly (6a6287132 2023-12-17))", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, splitDebugInlining: false)
!5 = !DIFile(filename: "main.rs", directory: "/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse")
!6 = distinct !DISubprogram(name: "main", linkageName: "main", scope: !8, file: !7, line: 1, type: !9, scopeLine: 1, flags: DIFlagPrototyped, spFlags: DISPFlagDefinition, unit: !4, templateParams: !19, retainedNodes: !11)
!7 = !DIFile(filename: "main.rs", directory: "/tmp/compiler-explorer-compiler20231118-8707-17mgdgd.x5dse", checksumkind: CSK_SHA256, checksum: "b560e17d45bb87e6939526cf009c50f61f7ef727de0fede23b15cee6d2ab18d6")
!8 = !DINamespace(name: "example", scope: null)
!9 = !DISubroutineType(types: !10)
!10 = !{null}
!11 = !{!12, !17}
!12 = !DILocalVariable(name: "x", scope: !13, file: !7, line: 2, type: !14, align: 4)
!13 = distinct !DILexicalBlock(scope: !6, file: !7, line: 2)
!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "i32", file: !15, baseType: !16)
!15 = !DIFile(filename: "<unknown>", directory: "")
!16 = !DIBasicType(name: "__int32", size: 32, encoding: DW_ATE_signed)
!17 = !DILocalVariable(name: "y", scope: !18, file: !7, line: 3, type: !14, align: 4)
!18 = distinct !DILexicalBlock(scope: !13, file: !7, line: 3)
!19 = !{}
!20 = !DILocation(line: 2, scope: !13)
!21 = !DILocation(line: 3, scope: !13)
!22 = !DILocation(line: 3, scope: !18)
!23 = !DILocation(line: 4, scope: !6)
I played with it a little and tried a similar code in C++ and see what clang generates, and it seems like the difference is the addition of DILexicalBlock in the Rust version. Removing them seems to make the debugging work as expected.
Diff with `DILexicalBlock` removed
diff --git a/exhibit_a.ll b/exhibit_b.ll
index 87d5ab8..942fab4 100755
--- a/exhibit_a.ll
+++ b/exhibit_b.ll
@@ -70,15 +70,13 @@ attributes #4 = { noreturn }
!9 = !DISubroutineType(types: !10)
!10 = !{null}
!11 = !{!12, !17}
-!12 = !DILocalVariable(name: "x", scope: !13, file: !7, line: 2, type: !14, align: 4)
-!13 = distinct !DILexicalBlock(scope: !6, file: !7, line: 2)
+!12 = !DILocalVariable(name: "x", scope: !6, file: !7, line: 2, type: !14, align: 4)
!14 = !DIDerivedType(tag: DW_TAG_typedef, name: "i32", file: !15, baseType: !16)
!15 = !DIFile(filename: "<unknown>", directory: "")
!16 = !DIBasicType(name: "__int32", size: 32, encoding: DW_ATE_signed)
-!17 = !DILocalVariable(name: "y", scope: !18, file: !7, line: 3, type: !14, align: 4)
-!18 = distinct !DILexicalBlock(scope: !13, file: !7, line: 3)
+!17 = !DILocalVariable(name: "y", scope: !6, file: !7, line: 3, type: !14, align: 4)
!19 = !{}
-!20 = !DILocation(line: 2, scope: !13)
-!21 = !DILocation(line: 3, scope: !13)
-!22 = !DILocation(line: 3, scope: !18)
+!20 = !DILocation(line: 2, scope: !6)
+!21 = !DILocation(line: 3, scope: !6)
+!22 = !DILocation(line: 3, scope: !6)
!23 = !DILocation(line: 4, scope: !6)
\ No newline at end of file
With the above changes:
I'm sure that DILexicalBlock makes a lot of sense, and clang seems to be generating them too when entering a new scope, but right now it seems like they do more harm than good. I'm not sure what the good solution for this would be, but I've been hit by this problem more times than I can remember debugging through rust code.
Can this be fixed somehow?
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by reproducing the minimal Rust example on Windows with the Visual Studio debugger and inspect the compiler's generated CodeView debug information, especially the DILexicalBlock scopes shown in the issue. Compare the result with the provided LLVM IR variant without lexical blocks; done means the local variable y appears at the function end while preserving correct scope behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- rust
- Domain
- compilers
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 35/100