Screenshot

Here’s a program in my silly language:

PRINT "hi"; // line 1

FUNCTION bar0 ( ) // line 3
{
    PRINT "bar0"; // line 5
    RETURN; // line 6
};

CALL bar0(); // line 9

I noticed that line stepping for this program has a “line number glitch”:

xpg:/home/pjoot/toycalculator/samples> gdb out/f
Reading symbols from out/f...
(gdb) b main
Breakpoint 1 at 0x400491: file f.silly, line 1.
(gdb) run
Starting program: /home/pjoot/toycalculator/samples/out/f 
Downloading separate debug info for system-supplied DSO at 0x7ffff7fc5000
[Thread debugging using libthread_db enabled]                                                                                           
Using host libthread_db library "/lib64/libthread_db.so.1".

Breakpoint 1, main () at f.silly:1
1       PRINT "hi"; // line 1
(gdb) b 9
Breakpoint 2 at 0x4004a0: file f.silly, line 9.
(gdb) c
Continuing.
hi

Breakpoint 2, main () at f.silly:9
9       CALL bar0(); // line 9
(gdb) s
bar0 () at f.silly:1
1       PRINT "hi"; // line 1
(gdb) n
5           PRINT "bar0"; // line 5
(gdb) 
bar0
6           RETURN; // line 6

(i.e.: from line 9, we should jump to line 3, then 5, but we first end up at line 1 instead of 3.)

I can see that things are wrong in the dwarfdump:

LOCAL_SYMBOLS:
< 1><0x0000002a>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00000000
                      DW_AT_high_pc                18 
                      DW_AT_frame_base            len 0x0001: 0x57: 
                          DW_OP_reg7
                      DW_AT_linkage_name          bar0
                      DW_AT_name                  bar0
                      DW_AT_decl_file             0x00000001 ./f.silly
                      DW_AT_decl_line             0x00000001
                      DW_AT_type                  <0x00000064> Refers to: void
                      DW_AT_external              yes(1)
< 1><0x00000047>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00000020
                      DW_AT_high_pc                25 
                      DW_AT_frame_base            len 0x0001: 0x57: 
                          DW_OP_reg7
                      DW_AT_linkage_name          main
                      DW_AT_name                  main
                      DW_AT_decl_file             0x00000001 ./f.silly
                      DW_AT_decl_line             0x00000001
                      DW_AT_type                  <0x0000006b> Refers to: int
                      DW_AT_external              yes(1)

The DW_AT_decl_line for the implicit main function for the program has a valid value (1), but the DW_AT_decl_line for the bar0 function shouldn’t be line 1.

Let’s have a peek at the LLVM-IR representation:

; ModuleID = 'f.silly'
source_filename = "f.silly"
target datalayout = "e-m:e-p270:32:32-p271:32:32-p272:64:64-i64:64-i128:128-f80:128-n8:16:32:64-S128"
target triple = "x86_64-unknown-linux-gnu"

@str_1 = private constant [2 x i8] c"hi"
@str_0 = private constant [4 x i8] c"bar0"

declare void @__silly_print_string(i64, ptr)

define void @bar0() !dbg !4 {
  call void @__silly_print_string(i64 4, ptr @str_0), !dbg !8
  ret void, !dbg !9
}

define i32 @main() !dbg !10 {
  call void @__silly_print_string(i64 2, ptr @str_1), !dbg !14
  call void @bar0(), !dbg !15
  ret i32 0, !dbg !16
}

!llvm.dbg.cu = !{!0}
!llvm.ident = !{!2}
!llvm.module.flags = !{!3}

!0 = distinct !DICompileUnit(language: DW_LANG_C, file: !1, producer: "silly", isOptimized: false, runtimeVersion: 0, emissionKind: FullDebug)
!1 = !DIFile(filename: "f.silly", directory: ".")
!2 = !{!"silly V7"}
!3 = !{i32 2, !"Debug Info Version", i32 3}
!4 = distinct !DISubprogram(name: "bar0", linkageName: "bar0", scope: !1, file: !1, line: 1, type: !5, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
!5 = !DISubroutineType(types: !6)
!6 = !{!7}
!7 = !DIBasicType(name: "void")
!8 = !DILocation(line: 5, column: 11, scope: !4)
!9 = !DILocation(line: 6, column: 5, scope: !4)
!10 = distinct !DISubprogram(name: "main", linkageName: "main", scope: !1, file: !1, line: 1, type: !11, scopeLine: 1, spFlags: DISPFlagDefinition, unit: !0)
!11 = !DISubroutineType(types: !12)
!12 = !{!13}
!13 = !DIBasicType(name: "int", size: 32, encoding: DW_ATE_signed)
!14 = !DILocation(line: 1, column: 7, scope: !10)
!15 = !DILocation(line: 9, column: 11, scope: !10)
!16 = !DILocation(line: 1, column: 1, scope: !10)

The error is right there in the ‘!4 DISubprogram’, which has line 1, not 3. Also note that the scopeLine should also be 5, not 1.

Sure enough, I’ve got the line number hardcoded in lowering when I generate my DISubprogramAttr

    void LoweringContext::createFuncDebug( mlir::func::FuncOp funcOp )
    {
        if ( driverState.wantDebug )
        {
            ModuleInsertionPointGuard ip( mod, builder );

            mlir::MLIRContext* context = builder.getContext();
            std::string funcName = funcOp.getSymName().str();

            mlir::LLVM::DISubroutineTypeAttr subprogramType = createDISubroutineType( funcOp );

            mlir::LLVM::DISubprogramAttr sub = mlir::LLVM::DISubprogramAttr::get(
                context, mlir::DistinctAttr::create( builder.getUnitAttr() ), compileUnitAttr, fileAttr,
                builder.getStringAttr( funcName ), builder.getStringAttr( funcName ), fileAttr, 1, 1,
                mlir::LLVM::DISubprogramFlags::Definition, subprogramType, llvm::ArrayRef<mlir::LLVM::DINodeAttr>{},
                llvm::ArrayRef<mlir::LLVM::DINodeAttr>{} );

            funcOp->setAttr( "llvm.debug.subprogram", sub );

            // This is the key to ensure that translateModuleToLLVMIR does not strip the location info (instead
            // converts loc's into !dbg's)
            funcOp->setLoc( builder.getFusedLoc( { mod.getLoc() }, sub ) );

            subprogramAttr[funcName] = sub;
        }
    }

Those ‘1, 1’ parameters are line (first line of function declaration) and scopeLine (first line of code in the function body) respectively, and it takes a little bit of work to get both:

--- a/src/lowering.cpp
+++ b/src/lowering.cpp
@@ -411,9 +411,27 @@ namespace silly
 
             mlir::LLVM::DISubroutineTypeAttr subprogramType = createDISubroutineType( funcOp );
 
+            mlir::Location funcLoc = funcOp.getLoc();
+            mlir::FileLineColLoc loc = getLocation( funcLoc );
+            unsigned line = loc.getLine();
+            unsigned scopeLine = line;
+
+            mlir::Region ®ion = funcOp.getRegion();
+
+            mlir::Block &entryBlock = region.front();
+
+            // Get the location of the First operation in the block for the scopeLine:
+            if (!entryBlock.empty()) {
+              mlir::Operation *firstOp = &entryBlock.front();
+              mlir::Location firstLoc = firstOp->getLoc();
+              mlir::FileLineColLoc scopeLoc = getLocation( firstLoc );
+
+              scopeLine = scopeLoc.getLine();
+            }
+
             mlir::LLVM::DISubprogramAttr sub = mlir::LLVM::DISubprogramAttr::get(
                 context, mlir::DistinctAttr::create( builder.getUnitAttr() ), compileUnitAttr, fileAttr,
-                builder.getStringAttr( funcName ), builder.getStringAttr( funcName ), fileAttr, 1, 1,
+                builder.getStringAttr( funcName ), builder.getStringAttr( funcName ), fileAttr, line, scopeLine,
                 mlir::LLVM::DISubprogramFlags::Definition, subprogramType, llvm::ArrayRef{},
                 llvm::ArrayRef{} );

Here’s the new DWARF dump for bar0:

< 1><0x0000002a>    DW_TAG_subprogram
                      DW_AT_low_pc                0x00000000
                      DW_AT_high_pc                18 
                      DW_AT_frame_base            len 0x0001: 0x57: 
                          DW_OP_reg7
                      DW_AT_linkage_name          bar0
                      DW_AT_name                  bar0
                      DW_AT_decl_file             0x00000001 ./f.silly
                      DW_AT_decl_line             0x00000003
                      DW_AT_type                  <0x00000064> Refers to: void
                      DW_AT_external              yes(1)

scopeLine doesn’t show there, but it’s in the LLVM-IR dump:

!4 = distinct !DISubprogram(name: "bar0", linkageName: "bar0", scope: !1, file: !1, line: 3, type: !5, scopeLine: 3, spFlags: DISPFlagDefinition, unit: !0)