Skip to content

Issues with file information for types in LLVM debug info #28310

@jabraham17

Description

@jabraham17

There are a few compounding issues with how we encode file information for types in the LLVM debug info

  1. 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 EXECUTEROOT for lldb and set substitute-path for gdb)

  2. When we codegen debug info, each module symbol gets its own llvm::DICompileUnit, which becomes DW_TAG_compile_unit in 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 myRec is actually defined in ChapelBase.chpl
    Image

    This occurs because chpl__autoDestroy happens to be the first function codegened that uses myRec, so even though the llvm::DI* debug info has myRec correctly defined in the compile unit for bar.chpl, the DWARF info has myRec in the ChapelBase compile unit

    The following LLVM IR Metadata shows this. !253 is myRec and is correctly declared in !242 (the compile unit for bar), but because !245 (chpl__autoDestroy) refers to !253 and is declared in !7 (ChapelBase), myRec shows up in ChapelBase

!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

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions