]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: include register-resident output params in DWARF-gen
authorThan McIntosh <thanm@google.com>
Wed, 10 Nov 2021 15:44:00 +0000 (10:44 -0500)
committerThan McIntosh <thanm@google.com>
Thu, 11 Nov 2021 11:47:18 +0000 (11:47 +0000)
During the register ABI work, a change was made in CL 302071 to
"stackframe" to treat register-resident output parameter (PARAMOUT)
variables that same as locals, which meant that if they were unused,
we'd delete them from the "Dcl" slice. This has the effect of making
them invisible to DWARF generation later on in the pipeline, meaning
that we don't get DIEs for them in the debug info. This patch fixes
the problem by capturing these params prior to optimization and then
adding them back in for consideration when we're processing the
params/locals of a function during DWARF generation.

Fixes #48573.

Change-Id: I2b32882911c18f91c3e3d009486517522d262685
Reviewed-on: https://go-review.googlesource.com/c/go/+/362618
Trust: Than McIntosh <thanm@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
src/cmd/compile/internal/dwarfgen/dwarf.go
src/cmd/compile/internal/ssa/debug.go
src/cmd/compile/internal/ssagen/ssa.go
src/cmd/link/internal/ld/dwarf_test.go

index 3007262db9d1b784f0c0238f6873a32d80e72203..e249a52e57a9839b7c32c444be97c864f6929acc 100644 (file)
@@ -150,6 +150,19 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
        dcl := apDecls
        if fnsym.WasInlined() {
                dcl = preInliningDcls(fnsym)
+       } else {
+               // The backend's stackframe pass prunes away entries from the
+               // fn's Dcl list, including PARAMOUT nodes that correspond to
+               // output params passed in registers. Add back in these
+               // entries here so that we can process them properly during
+               // DWARF-gen. See issue 48573 for more details.
+               debugInfo := fn.DebugInfo.(*ssa.FuncDebug)
+               for _, n := range debugInfo.RegOutputParams {
+                       if n.Class != ir.PPARAMOUT || !n.IsOutputParamInRegisters() {
+                               panic("invalid ir.Name on debugInfo.RegOutputParams list")
+                       }
+                       dcl = append(dcl, n)
+               }
        }
 
        // If optimization is enabled, the list above will typically be
index fed152efbab192e3a6084f2dd9a2e7a1af83cc7b..aad59fa24ec9c46e60b49315bc4a2aeacfe77ec7 100644 (file)
@@ -34,6 +34,9 @@ type FuncDebug struct {
        VarSlots [][]SlotID
        // The location list data, indexed by VarID. Must be processed by PutLocationList.
        LocationLists [][]byte
+       // Register-resident output parameters for the function. This is filled in at
+       // SSA generation time.
+       RegOutputParams []*ir.Name
 
        // Filled in by the user. Translates Block and Value ID to PC.
        GetPC func(ID, ID) int64
@@ -548,10 +551,10 @@ func PopulateABIInRegArgOps(f *Func) {
        f.Entry.Values = append(newValues, f.Entry.Values...)
 }
 
-// BuildFuncDebug returns debug information for f.
+// BuildFuncDebug debug information for f, placing the results in "rval".
 // f must be fully processed, so that each Value is where it will be when
 // machine code is emitted.
-func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug {
+func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32, rval *FuncDebug) {
        if f.RegAlloc == nil {
                f.Fatalf("BuildFuncDebug on func %v that has not been fully processed", f)
        }
@@ -661,12 +664,11 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu
        blockLocs := state.liveness()
        state.buildLocationLists(blockLocs)
 
-       return &FuncDebug{
-               Slots:         state.slots,
-               VarSlots:      state.varSlots,
-               Vars:          state.vars,
-               LocationLists: state.lists,
-       }
+       // Populate "rval" with what we've computed.
+       rval.Slots = state.slots
+       rval.VarSlots = state.varSlots
+       rval.Vars = state.vars
+       rval.LocationLists = state.lists
 }
 
 // liveness walks the function in control flow order, calculating the start
@@ -1593,7 +1595,7 @@ func isNamedRegParam(p abi.ABIParamAssignment) bool {
        return true
 }
 
-// BuildFuncDebugNoOptimized constructs a FuncDebug object with
+// BuildFuncDebugNoOptimized populates a FuncDebug object "rval" with
 // entries corresponding to the register-resident input parameters for
 // the function "f"; it is used when we are compiling without
 // optimization but the register ABI is enabled. For each reg param,
@@ -1601,8 +1603,7 @@ func isNamedRegParam(p abi.ABIParamAssignment) bool {
 // the input register, and the second element holds the stack location
 // of the param (the assumption being that when optimization is off,
 // each input param reg will be spilled in the prolog.
-func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32) *FuncDebug {
-       fd := FuncDebug{}
+func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset func(LocalSlot) int32, rval *FuncDebug) {
 
        pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType())
 
@@ -1616,7 +1617,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
                }
        }
        if numRegParams == 0 {
-               return &fd
+               return
        }
 
        state := debugState{f: f}
@@ -1626,7 +1627,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
        }
 
        // Allocate location lists.
-       fd.LocationLists = make([][]byte, numRegParams)
+       rval.LocationLists = make([][]byte, numRegParams)
 
        // Locate the value corresponding to the last spill of
        // an input register.
@@ -1642,10 +1643,10 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
 
                n := inp.Name.(*ir.Name)
                sl := LocalSlot{N: n, Type: inp.Type, Off: 0}
-               fd.Vars = append(fd.Vars, n)
-               fd.Slots = append(fd.Slots, sl)
-               slid := len(fd.VarSlots)
-               fd.VarSlots = append(fd.VarSlots, []SlotID{SlotID(slid)})
+               rval.Vars = append(rval.Vars, n)
+               rval.Slots = append(rval.Slots, sl)
+               slid := len(rval.VarSlots)
+               rval.VarSlots = append(rval.VarSlots, []SlotID{SlotID(slid)})
 
                if afterPrologVal == ID(-1) {
                        // This can happen for degenerate functions with infinite
@@ -1662,7 +1663,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
                // Param is arriving in one or more registers. We need a 2-element
                // location expression for it. First entry in location list
                // will correspond to lifetime in input registers.
-               list, sizeIdx := setupLocList(ctxt, f, fd.LocationLists[pidx],
+               list, sizeIdx := setupLocList(ctxt, f, rval.LocationLists[pidx],
                        BlockStart.ID, afterPrologVal)
                if list == nil {
                        pidx++
@@ -1727,8 +1728,7 @@ func BuildFuncDebugNoOptimized(ctxt *obj.Link, f *Func, loggingEnabled bool, sta
                // fill in size
                ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
 
-               fd.LocationLists[pidx] = list
+               rval.LocationLists[pidx] = list
                pidx++
        }
-       return &fd
 }
index 0853242e6f72a213184ce8b72930db11cbe534e5..d6407af334b01e3c063ea70b1a3206af1ed3109d 100644 (file)
@@ -484,6 +484,19 @@ func buildssa(fn *ir.Func, worker int) *ssa.Func {
        var params *abi.ABIParamResultInfo
        params = s.f.ABISelf.ABIAnalyze(fn.Type(), true)
 
+       // The backend's stackframe pass prunes away entries from the fn's
+       // Dcl list, including PARAMOUT nodes that correspond to output
+       // params passed in registers. Walk the Dcl list and capture these
+       // nodes to a side list, so that we'll have them available during
+       // DWARF-gen later on. See issue 48573 for more details.
+       var debugInfo ssa.FuncDebug
+       for _, n := range fn.Dcl {
+               if n.Class == ir.PPARAMOUT && n.IsOutputParamInRegisters() {
+                       debugInfo.RegOutputParams = append(debugInfo.RegOutputParams, n)
+               }
+       }
+       fn.DebugInfo = &debugInfo
+
        // Generate addresses of local declarations
        s.decladdrs = map[*ir.Name]*ssa.Value{}
        for _, n := range fn.Dcl {
@@ -7003,12 +7016,12 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
 
        if base.Ctxt.Flag_locationlists {
                var debugInfo *ssa.FuncDebug
+               debugInfo = e.curfn.DebugInfo.(*ssa.FuncDebug)
                if e.curfn.ABI == obj.ABIInternal && base.Flag.N != 0 {
-                       debugInfo = ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset)
+                       ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset, debugInfo)
                } else {
-                       debugInfo = ssa.BuildFuncDebug(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset)
+                       ssa.BuildFuncDebug(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset, debugInfo)
                }
-               e.curfn.DebugInfo = debugInfo
                bstart := s.bstart
                idToIdx := make([]int, f.NumBlocks())
                for i, b := range f.Blocks {
index db9002491e0dc015db3ec5eb9f7cde6b97043f3d..9a163488e613bce3ad984818e2600b60700fa743 100644 (file)
@@ -1635,6 +1635,66 @@ func TestIssue42484(t *testing.T) {
        f.Close()
 }
 
+// processParams examines the formal parameter children of subprogram
+// DIE "die" using the explorer "ex" and returns a string that
+// captures the name, order, and classification of the subprogram's
+// input and output parameters. For example, for the go function
+//
+//     func foo(i1 int, f1 float64) (string, bool) {
+//
+// this function would return a string something like
+//
+//     i1:0:1 f1:1:1 ~r0:2:2 ~r1:3:2
+//
+// where each chunk above is of the form NAME:ORDER:INOUTCLASSIFICATION
+//
+func processParams(die *dwarf.Entry, ex *examiner) string {
+       // Values in the returned map are of the form <order>:<varparam>
+       // where order is the order within the child DIE list of the
+       // param, and <varparam> is an integer:
+       //
+       //  -1: varparm attr not found
+       //   1: varparm found with value false
+       //   2: varparm found with value true
+       //
+       foundParams := make(map[string]string)
+
+       // Walk ABCs's children looking for params.
+       abcIdx := ex.idxFromOffset(die.Offset)
+       childDies := ex.Children(abcIdx)
+       idx := 0
+       for _, child := range childDies {
+               if child.Tag == dwarf.TagFormalParameter {
+                       // NB: a setting of DW_AT_variable_parameter indicates
+                       // that the param in question is an output parameter; we
+                       // want to see this attribute set to TRUE for all Go
+                       // return params. It would be OK to have it missing for
+                       // input parameters, but for the moment we verify that the
+                       // attr is present but set to false.
+                       st := -1
+                       if vp, ok := child.Val(dwarf.AttrVarParam).(bool); ok {
+                               if vp {
+                                       st = 2
+                               } else {
+                                       st = 1
+                               }
+                       }
+                       if name, ok := child.Val(dwarf.AttrName).(string); ok {
+                               foundParams[name] = fmt.Sprintf("%d:%d", idx, st)
+                               idx++
+                       }
+               }
+       }
+
+       found := make([]string, 0, len(foundParams))
+       for k, v := range foundParams {
+               found = append(found, fmt.Sprintf("%s:%s", k, v))
+       }
+       sort.Strings(found)
+
+       return fmt.Sprintf("%+v", found)
+}
+
 func TestOutputParamAbbrevAndAttr(t *testing.T) {
        testenv.MustHaveGoBuild(t)
 
@@ -1694,56 +1754,15 @@ func main() {
                t.Fatalf("unexpected tag %v on main.ABC DIE", abcdie.Tag)
        }
 
-       // A setting of DW_AT_variable_parameter indicates that the
-       // param in question is an output parameter; we want to see this
-       // attribute set to TRUE for all Go return params. It would be
-       // OK to have it missing for input parameters, but for the moment
-       // we verify that the attr is present but set to false.
-
-       // Values in this map are of the form <order>:<varparam>
-       // where order is the order within the child DIE list of the param,
-       // and <varparam> is an integer:
-       //
-       //  -1: varparm attr not found
-       //   1: varparm found with value false
-       //   2: varparm found with value true
-       //
-       foundParams := make(map[string]string)
-
-       // Walk ABCs's children looking for params.
-       abcIdx := ex.idxFromOffset(abcdie.Offset)
-       childDies := ex.Children(abcIdx)
-       idx := 0
-       for _, child := range childDies {
-               if child.Tag == dwarf.TagFormalParameter {
-                       st := -1
-                       if vp, ok := child.Val(dwarf.AttrVarParam).(bool); ok {
-                               if vp {
-                                       st = 2
-                               } else {
-                                       st = 1
-                               }
-                       }
-                       if name, ok := child.Val(dwarf.AttrName).(string); ok {
-                               foundParams[name] = fmt.Sprintf("%d:%d", idx, st)
-                               idx++
-                       }
-               }
-       }
-
-       // Digest the result.
-       found := make([]string, 0, len(foundParams))
-       for k, v := range foundParams {
-               found = append(found, fmt.Sprintf("%s:%s", k, v))
-       }
-       sort.Strings(found)
+       // Call a helper to collect param info.
+       found := processParams(abcdie, &ex)
 
        // Make sure we see all of the expected params in the proper
-       // order, that they have the varparam attr, and the varparm is set
-       // for the returns.
+       // order, that they have the varparam attr, and the varparam is
+       // set for the returns.
        expected := "[c1:0:1 c2:1:1 c3:2:1 d1:3:1 d2:4:1 d3:5:1 d4:6:1 f1:7:1 f2:8:1 f3:9:1 g1:10:1 r1:11:2 r2:12:2 r3:13:2 r4:14:2 r5:15:2 r6:16:2]"
-       if fmt.Sprintf("%+v", found) != expected {
-               t.Errorf("param check failed, wanted %s got %s\n",
+       if found != expected {
+               t.Errorf("param check failed, wanted:\n%s\ngot:\n%s\n",
                        expected, found)
        }
 }
@@ -1849,3 +1868,156 @@ func main() {
                }
        }
 }
+
+func TestOptimizedOutParamHandling(t *testing.T) {
+       testenv.MustHaveGoBuild(t)
+
+       if runtime.GOOS == "plan9" {
+               t.Skip("skipping on plan9; no DWARF symbol table in executables")
+       }
+       t.Parallel()
+
+       // This test is intended to verify that the compiler emits DWARF
+       // DIE entries for all input and output parameters, and that:
+       //
+       //   - attributes are set correctly for output params,
+       //   - things appear in the proper order
+       //   - things work properly for both register-resident
+       //     params and params passed on the stack
+       //   - things work for both referenced and unreferenced params
+       //   - things work for named return values un-named return vals
+       //
+       // The scenarios below don't cover all possible permutations and
+       // combinations, but they hit a bunch of the high points.
+
+       const prog = `
+package main
+
+// First testcase. All input params in registers, all params used.
+
+//go:noinline
+func tc1(p1, p2 int, p3 string) (int, string) {
+       return p1 + p2, p3 + "foo"
+}
+
+// Second testcase. Some params in registers, some on stack.
+
+//go:noinline
+func tc2(p1 int, p2 [128]int, p3 string) (int, string, [128]int) {
+       return p1 + p2[p1], p3 + "foo", [128]int{p1}
+}
+
+// Third testcase. Named return params.
+
+//go:noinline
+func tc3(p1 int, p2 [128]int, p3 string) (r1 int, r2 bool, r3 string, r4 [128]int) {
+       if p1 == 101 {
+               r1 = p1 + p2[p1]
+               r2 = p3 == "foo"
+               r4 = [128]int{p1}
+               return
+       } else {
+               return p1 - p2[p1+3], false, "bar", [128]int{p1 + 2}
+       }
+}
+
+// Fourth testcase. Some thing are used, some are unused.
+
+//go:noinline
+func tc4(p1, p1un int, p2, p2un [128]int, p3, p3un string) (r1 int, r1un int, r2 bool, r3 string, r4, r4un [128]int) {
+       if p1 == 101 {
+               r1 = p1 + p2[p2[0]]
+               r2 = p3 == "foo"
+               r4 = [128]int{p1}
+               return
+       } else {
+               return p1, -1, true, "plex", [128]int{p1 + 2}, [128]int{-1}
+       }
+}
+
+func main() {
+       {
+               r1, r2 := tc1(3, 4, "five")
+               println(r1, r2)
+       }
+       {
+               x := [128]int{9}
+               r1, r2, r3 := tc2(3, x, "five")
+               println(r1, r2, r3[0])
+       }
+       {
+               x := [128]int{9}
+               r1, r2, r3, r4 := tc3(3, x, "five")
+               println(r1, r2, r3, r4[0])
+       }
+       {
+               x := [128]int{3}
+               y := [128]int{7}
+               r1, r1u, r2, r3, r4, r4u := tc4(0, 1, x, y, "a", "b")
+               println(r1, r1u, r2, r3, r4[0], r4u[1])
+       }
+
+}
+`
+       dir := t.TempDir()
+       f := gobuild(t, dir, prog, DefaultOpt)
+       defer f.Close()
+
+       d, err := f.DWARF()
+       if err != nil {
+               t.Fatalf("error reading DWARF: %v", err)
+       }
+
+       rdr := d.Reader()
+       ex := examiner{}
+       if err := ex.populate(rdr); err != nil {
+               t.Fatalf("error reading DWARF: %v", err)
+       }
+
+       testcases := []struct {
+               tag      string
+               expected string
+       }{
+               {
+                       tag:      "tc1",
+                       expected: "[p1:0:1 p2:1:1 p3:2:1 ~r0:3:2 ~r1:4:2]",
+               },
+               {
+                       tag:      "tc2",
+                       expected: "[p1:0:1 p2:1:1 p3:2:1 ~r0:3:2 ~r1:4:2 ~r2:5:2]",
+               },
+               {
+                       tag:      "tc3",
+                       expected: "[p1:0:1 p2:1:1 p3:2:1 r1:3:2 r2:4:2 r3:5:2 r4:6:2]",
+               },
+               {
+                       tag:      "tc4",
+                       expected: "[p1:0:1 p1un:1:1 p2:2:1 p2un:3:1 p3:4:1 p3un:5:1 r1:6:2 r1un:7:2 r2:8:2 r3:9:2 r4:10:2 r4un:11:2]",
+               },
+       }
+
+       for _, tc := range testcases {
+               // Locate the proper DIE
+               which := fmt.Sprintf("main.%s", tc.tag)
+               tcs := ex.Named(which)
+               if len(tcs) == 0 {
+                       t.Fatalf("unable to locate DIE for " + which)
+               }
+               if len(tcs) != 1 {
+                       t.Fatalf("more than one " + which + " DIE")
+               }
+               die := tcs[0]
+
+               // Vet the DIE
+               if die.Tag != dwarf.TagSubprogram {
+                       t.Fatalf("unexpected tag %v on "+which+" DIE", die.Tag)
+               }
+
+               // Examine params for this subprogram.
+               foundParams := processParams(die, &ex)
+               if foundParams != tc.expected {
+                       t.Errorf("check failed for testcase %s -- wanted:\n%s\ngot:%s\n",
+                               tc.tag, tc.expected, foundParams)
+               }
+       }
+}