-
Notifications
You must be signed in to change notification settings - Fork 445
Description
There are a few compounding issues with how we encode file information for types in the LLVM debug info
-
All file paths (except for internal/standard modules) are encoded as relative paths. This works, but only when the executable is run from the same directory where it lives. Most compilers (e.g. clang, gcc) encode absolute paths. We should adjust the compiler to encode absolute paths. If users need the relative path, or some alternative absolute path due to mismatch file tree structures on login/compute nodes, they can change the base absolute path at runtime (
settings set target.source-map BUILDROOT EXECUTEROOTfor lldb andset substitute-pathfor gdb) -
When we codegen debug info, each module symbol gets its own llvm::DICompileUnit, which becomes
DW_TAG_compile_unitin DWRARF. This was implemented in Fix LLVM debug information for module scope symbols #27212, because I observed that to get global symbols to work in the debugger the scope of the global variables must be a DICompileUnit (although the llvm DI types and dwarf info allow any scoping object).However, as noted in Improve the codegen of type symbols for debugging #27613, because Types are codegened on demand, whichever DICompileUnit codegens the type first wins.
This is seen in the following example
record myRec { var a: int; var b: real; } proc main() { var myRecInstance = new myRec(a=10, b=20.5); writeln(myRecInstance); }
This results in debug info saying that
myRecis actually defined in ChapelBase.chpl

This occurs because
chpl__autoDestroyhappens to be the first function codegened that usesmyRec, so even though the llvm::DI* debug info hasmyReccorrectly defined in the compile unit forbar.chpl, the DWARF info hasmyRecin theChapelBasecompile unitThe following LLVM IR Metadata shows this.
!253ismyRecand is correctly declared in!242(the compile unit forbar), but because !245 (chpl__autoDestroy) refers to!253and is declared in!7(ChapelBase),myRecshows up inChapelBase
!0 = !{i32 2, !"Debug Info Version", i32 3}
!1 = distinct !DICompileUnit(language: DW_LANG_C11, file: !2, producer: "Homebrew clang version 20.1.8", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !3, retainedTypes: !3, globals: !3, splitDebugInlining: false, nameTableKind: Apple, sysroot: "/Library/Developer/CommandLineTools/SDKs/MacOSX26.sdk", sdk: "MacOSX26.sdk")
!2 = !DIFile(filename: "/opt/homebrew/Cellar/chapel/2.7.0/libexec/runtime/etc/rtmain.c", directory: "/Users/jade/Development/chapel-lang/chapel", checksumkind: CSK_MD5, checksum: "8df6fdfb83389128ab74c44801257e23")
!3 = !{}
!4 = distinct !DICompileUnit(language: DW_LANG_C99, file: !5, producer: "Chapel version 2.7.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !3)
!5 = !DIFile(filename: "<internal>", directory: "./")
!6 = distinct !DICompileUnit(language: DW_LANG_C99, file: !5, producer: "Chapel version 2.7.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, globals: !3)
!7 = distinct !DICompileUnit(language: DW_LANG_C99, file: !8, producer: "Chapel version 2.7.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug, enums: !3, globals: !3)
!8 = !DIFile(filename: "/opt/homebrew/Cellar/chapel/2.7.0/libexec/modules/internal/ChapelBase.chpl", directory: "./")
!242 = distinct !DICompileUnit(language: DW_LANG_C99, file: !243, producer: "Chapel version 2.7.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
!243 = !DIFile(filename: "bar.chpl", directory: "./")
!244 = distinct !DICompileUnit(language: DW_LANG_C99, file: !5, producer: "Chapel version 2.7.0", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
!245 = distinct !DISubprogram(name: "chpl__autoDestroy", linkageName: "chpl__autoDestroy40", scope: !247, file: !246, line: 2574, type: !248, scopeLine: 2574, spFlags: DISPFlagLocalToUnit | DISPFlagDefinition, unit: !7, retainedNodes: !249)
!246 = !DIFile(filename: "ChapelBase.chpl", directory: "/opt/homebrew/Cellar/chapel/2.7.0/libexec/modules/internal")
!247 = !DINamespace(name: "ChapelBase", scope: !246)
!248 = !DISubroutineType(types: !3)
!249 = !{!250}
!250 = !DILocalVariable(name: "x", arg: 1, scope: !245, type: !251)
!251 = !DIDerivedType(tag: DW_TAG_typedef, name: "_ref(myRec)", baseType: !252)
!252 = !DIDerivedType(tag: DW_TAG_reference_type, baseType: !253)
!253 = !DICompositeType(tag: DW_TAG_structure_type, name: "myRec", scope: !254, file: !243, line: 1, size: 128, align: 64, elements: !3)
!254 = !DINamespace(name: "bar", scope: !243)
!255 = !DILocation(line: 2575, scope: !245)LLVM IR produced as
chpl --debug bar.chpl --savec gen
/opt/homebrew/opt/llvm@20/bin/opt -passes='mem2reg' gen/chpl__module-nopt.bc -S -o gen/chpl__module-nopt.ll
/opt/homebrew/opt/llvm@20/bin/llvm-reduce --test=reduce.sh gen/chpl__module-nopt.ll
# reduce.sh
/opt/homebrew/opt/llvm@20/bin/llc $1 -filetype=obj -o test.o && dwarfdump test.o -n myRec -p | grep 'ChapelBase.chpl'Both of these problems result in degraded debug info. Problem 1 is more readily discernible by users, but we cannot fix it without fixing Problem 2. If we "fix" Problem 1 (i did it locally as a quick test), it completely borks the file info because of Problem 2. Right now, we are sorta getting away with problem 2 because of relative paths, but without relative paths the debugger thinks that bar.chpl is in the internal modules path and so we can't set breakpoints like b bar.chpl:10 anymore.
I'm not totally convinced that the LLVM behavior of just putting the type in whatever compile unit goes first is correct, but even if thats a bug that we fix in LLVM it will be a long time before most Chapel users see the benefit. I think the right approach is to go back to having a single DICompileUnit and each ModuleSymbol gets its own namespace. Testing with C++, this seems to work ok and debug symbols for global variables are correct (and have the expected DWARF structure). This also matches Chapels compilation, which treats all the Chapel source as a single compilation unit