cmd/compile,cmd/link: set DW_AT_decl_line for function declarations

DW_AT_decl_line provides the line number of function declarations (the
line containing the func keyword). This is the equivalent to CL 429638,
but provided via DWARF.

Note that the file of declarations (DW_AT_decl_file) is already provided
for non-inlined functions. It is omitted for inlined functions because
those DWARF subprograms may be generated outside of their source
compilation unit, where referencing the file table is difficult.

Fixes #57308.

Change-Id: I3ad12e1f366c4465c2a588297988a5825ef7efec
Reviewed-on: https://go-review.googlesource.com/c/go/+/458195
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Auto-Submit: Michael Pratt <mpratt@google.com>
Run-TryBot: Michael Pratt <mpratt@google.com>
Reviewed-by: Than McIntosh <thanm@google.com>
diff --git a/src/cmd/compile/internal/dwarfgen/dwarf.go b/src/cmd/compile/internal/dwarfgen/dwarf.go
index 90c331f..886250a 100644
--- a/src/cmd/compile/internal/dwarfgen/dwarf.go
+++ b/src/cmd/compile/internal/dwarfgen/dwarf.go
@@ -23,7 +23,7 @@
 	"cmd/internal/src"
 )
 
-func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) {
+func Info(fnsym *obj.LSym, infosym *obj.LSym, curfn interface{}) (scopes []dwarf.Scope, inlcalls dwarf.InlCalls, startPos src.XPos) {
 	fn := curfn.(*ir.Func)
 
 	if fn.Nname != nil {
@@ -124,12 +124,11 @@
 		varScopes = append(varScopes, findScope(fn.Marks, pos))
 	}
 
-	scopes := assembleScopes(fnsym, fn, dwarfVars, varScopes)
-	var inlcalls dwarf.InlCalls
+	scopes = assembleScopes(fnsym, fn, dwarfVars, varScopes)
 	if base.Flag.GenDwarfInl > 0 {
 		inlcalls = assembleInlines(fnsym, dwarfVars)
 	}
-	return scopes, inlcalls
+	return scopes, inlcalls, fn.Pos()
 }
 
 func declPos(decl *ir.Name) src.XPos {
diff --git a/src/cmd/internal/dwarf/dwarf.go b/src/cmd/internal/dwarf/dwarf.go
index a6d19c6..d4a4e33 100644
--- a/src/cmd/internal/dwarf/dwarf.go
+++ b/src/cmd/internal/dwarf/dwarf.go
@@ -94,6 +94,7 @@
 	Absfn         Sym
 	StartPC       Sym
 	Size          int64
+	StartLine     int32
 	External      bool
 	Scopes        []Scope
 	InlCalls      InlCalls
@@ -458,6 +459,7 @@
 			{DW_AT_high_pc, DW_FORM_addr},
 			{DW_AT_frame_base, DW_FORM_block1},
 			{DW_AT_decl_file, DW_FORM_data4},
+			{DW_AT_decl_line, DW_FORM_udata},
 			{DW_AT_external, DW_FORM_flag},
 		},
 	},
@@ -482,6 +484,7 @@
 		[]dwAttrForm{
 			{DW_AT_name, DW_FORM_string},
 			{DW_AT_inline, DW_FORM_data1},
+			{DW_AT_decl_line, DW_FORM_udata},
 			{DW_AT_external, DW_FORM_flag},
 		},
 	},
@@ -1254,6 +1257,8 @@
 	// DW_AT_inlined value
 	putattr(ctxt, s.Absfn, abbrev, DW_FORM_data1, DW_CLS_CONSTANT, int64(DW_INL_inlined), nil)
 
+	putattr(ctxt, s.Absfn, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(s.StartLine), nil)
+
 	var ev int64
 	if s.External {
 		ev = 1
@@ -1446,6 +1451,8 @@
 		putattr(ctxt, s.Info, abbrev, DW_FORM_flag, DW_CLS_FLAG, int64(1), 0)
 	} else {
 		ctxt.AddFileRef(s.Info, s.Filesym)
+		putattr(ctxt, s.Info, abbrev, DW_FORM_udata, DW_CLS_CONSTANT, int64(s.StartLine), nil)
+
 		var ev int64
 		if s.External {
 			ev = 1
diff --git a/src/cmd/internal/obj/dwarf.go b/src/cmd/internal/obj/dwarf.go
index a9c13fd..121a9f6 100644
--- a/src/cmd/internal/obj/dwarf.go
+++ b/src/cmd/internal/obj/dwarf.go
@@ -353,7 +353,9 @@
 	var scopes []dwarf.Scope
 	var inlcalls dwarf.InlCalls
 	if ctxt.DebugInfo != nil {
-		scopes, inlcalls = ctxt.DebugInfo(s, info, curfn)
+		// Don't need startPos because s.Func().StartLine is populated,
+		// as s is in this package.
+		scopes, inlcalls, _ = ctxt.DebugInfo(s, info, curfn)
 	}
 	var err error
 	dwctxt := dwCtxt{ctxt}
@@ -368,6 +370,7 @@
 		Absfn:         absfunc,
 		StartPC:       s,
 		Size:          s.Size,
+		StartLine:     s.Func().StartLine,
 		External:      !s.Static(),
 		Scopes:        scopes,
 		InlCalls:      inlcalls,
@@ -427,8 +430,12 @@
 	if s.Func() == nil {
 		s.NewFuncInfo()
 	}
-	scopes, _ := ctxt.DebugInfo(s, absfn, curfn)
+	scopes, _, startPos := ctxt.DebugInfo(s, absfn, curfn)
+	_, startLine := ctxt.getFileSymbolAndLine(startPos)
 	dwctxt := dwCtxt{ctxt}
+	// TODO(prattmic): this returns nil for symbols outside of the current
+	// package because s.Func() is empty. This doesn't matter because
+	// PutAbstractFunc doesn't use Filesym. Use startPos or remove.
 	filesym := ctxt.fileSymbol(s)
 	fnstate := dwarf.FnState{
 		Name:          s.Name,
@@ -436,6 +443,7 @@
 		Info:          absfn,
 		Filesym:       filesym,
 		Absfn:         absfn,
+		StartLine:     startLine,
 		External:      !s.Static(),
 		Scopes:        scopes,
 		UseBASEntries: ctxt.UseBASEntries,
diff --git a/src/cmd/internal/obj/link.go b/src/cmd/internal/obj/link.go
index 8037017..6d40b33 100644
--- a/src/cmd/internal/obj/link.go
+++ b/src/cmd/internal/obj/link.go
@@ -916,7 +916,7 @@
 	Imports            []goobj.ImportedPkg
 	DiagFunc           func(string, ...interface{})
 	DiagFlush          func()
-	DebugInfo          func(fn *LSym, info *LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls) // if non-nil, curfn is a *gc.Node
+	DebugInfo          func(fn *LSym, info *LSym, curfn interface{}) ([]dwarf.Scope, dwarf.InlCalls, src.XPos) // if non-nil, curfn is a *ir.Func
 	GenAbstractFunc    func(fn *LSym)
 	Errors             int
 
diff --git a/src/cmd/link/internal/ld/dwarf_test.go b/src/cmd/link/internal/ld/dwarf_test.go
index a3db4a9..a11541f 100644
--- a/src/cmd/link/internal/ld/dwarf_test.go
+++ b/src/cmd/link/internal/ld/dwarf_test.go
@@ -334,10 +334,12 @@
 	}
 }
 
-func varDeclCoordsAndSubrogramDeclFile(t *testing.T, testpoint string, expectFile string, expectLine int, directive string) {
+// expectLine is the expected line for main.
+func varDeclCoordsAndSubprogramDeclFile(t *testing.T, testpoint string, expectFile string, expectLine int, directive string) {
 	t.Parallel()
 
 	prog := fmt.Sprintf("package main\n%s\nfunc main() {\n\nvar i int\ni = i\n}\n", directive)
+	const iLineOffset = 2
 
 	dir := t.TempDir()
 
@@ -385,9 +387,12 @@
 	}
 
 	// Verify line/file attributes.
-	line := iEntry.Val(dwarf.AttrDeclLine)
-	if line == nil || line.(int64) != int64(expectLine) {
-		t.Errorf("DW_AT_decl_line for i is %v, want %d", line, expectLine)
+	line, lineOK := iEntry.Val(dwarf.AttrDeclLine).(int64)
+	if !lineOK {
+		t.Errorf("missing or invalid DW_AT_decl_line for i")
+	}
+	if line != int64(expectLine+iLineOffset) {
+		t.Errorf("DW_AT_decl_line for i is %v, want %d", line, expectLine+iLineOffset)
 	}
 
 	fileIdx, fileIdxOK := maindie.Val(dwarf.AttrDeclFile).(int64)
@@ -402,6 +407,14 @@
 	if base != expectFile {
 		t.Errorf("DW_AT_decl_file for main is %v, want %v", base, expectFile)
 	}
+
+	line, lineOK = maindie.Val(dwarf.AttrDeclLine).(int64)
+	if !lineOK {
+		t.Errorf("missing or invalid DW_AT_decl_line for main")
+	}
+	if line != int64(expectLine) {
+		t.Errorf("DW_AT_decl_line for main is %v, want %d", line, expectLine)
+	}
 }
 
 func TestVarDeclCoordsAndSubrogramDeclFile(t *testing.T) {
@@ -411,7 +424,7 @@
 		t.Skip("skipping on plan9; no DWARF symbol table in executables")
 	}
 
-	varDeclCoordsAndSubrogramDeclFile(t, "TestVarDeclCoords", "test.go", 5, "")
+	varDeclCoordsAndSubprogramDeclFile(t, "TestVarDeclCoords", "test.go", 3, "")
 }
 
 func TestVarDeclCoordsWithLineDirective(t *testing.T) {
@@ -421,8 +434,8 @@
 		t.Skip("skipping on plan9; no DWARF symbol table in executables")
 	}
 
-	varDeclCoordsAndSubrogramDeclFile(t, "TestVarDeclCoordsWithLineDirective",
-		"foobar.go", 202, "//line /foobar.go:200")
+	varDeclCoordsAndSubprogramDeclFile(t, "TestVarDeclCoordsWithLineDirective",
+		"foobar.go", 200, "//line /foobar.go:200")
 }
 
 func TestInlinedRoutineRecords(t *testing.T) {