Skip to content

Commit 8bf5bf5

Browse files
committed
cmd/compile: improve debug locations for partially live in-params
During DWARF debug location generation, as a preamble to the main data flow analysis, examine the function entry block to look for in-params arriving in registers that are partially or completely dead, and insert new OpArg{Int,Float}Reg values for the dead or partially-dead pieces. In addition, add entries to the f.NamedValues table for incoming live register-resident params that don't already have entries. This helps create better/saner DWARF location expressions for params. Example: func foo(s string, used int, notused int) int { return len(s) + used } When optimization is complete for this function, the parameter "notused" is completely dead, meaning that there is no entry for it in the f.NamedValues table (which then means we don't emit a DWARF variable location expression for it in the function enty block). In addition, since only the length field of "s" is used, there is no DWARF location expression for the other component of "s", leading to degraded DWARF. There are still problems/issues with DWARF location generation, but this does improve things with respect to being able to print the values of incoming parameters when stopped in the debugger at the entry point of a function (when optimization is enabled). Updates #40724. Change-Id: I5bb5253648942f9fd33b081fe1a5a36208e75785 Reviewed-on: https://go-review.googlesource.com/c/go/+/322631 Trust: Than McIntosh <[email protected]> Run-TryBot: Than McIntosh <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 56af34f commit 8bf5bf5

File tree

1 file changed

+217
-0
lines changed

1 file changed

+217
-0
lines changed

src/cmd/compile/internal/ssa/debug.go

+217
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ package ssa
77
import (
88
"cmd/compile/internal/abi"
99
"cmd/compile/internal/ir"
10+
"cmd/compile/internal/types"
1011
"cmd/internal/dwarf"
1112
"cmd/internal/obj"
13+
"cmd/internal/src"
1214
"encoding/hex"
1315
"fmt"
16+
"internal/buildcfg"
1417
"math/bits"
1518
"sort"
1619
"strings"
@@ -335,6 +338,216 @@ func (s *debugState) stateString(state stateAtPC) string {
335338
return strings.Join(strs, "")
336339
}
337340

341+
// slotCanonicalizer is a table used to lookup and canonicalize
342+
// LocalSlot's in a type insensitive way (e.g. taking into account the
343+
// base name, offset, and width of the slot, but ignoring the slot
344+
// type).
345+
type slotCanonicalizer struct {
346+
slmap map[slotKey]SlKeyIdx
347+
slkeys []LocalSlot
348+
}
349+
350+
func newSlotCanonicalizer() *slotCanonicalizer {
351+
return &slotCanonicalizer{
352+
slmap: make(map[slotKey]SlKeyIdx),
353+
slkeys: []LocalSlot{LocalSlot{N: nil}},
354+
}
355+
}
356+
357+
type SlKeyIdx uint32
358+
359+
const noSlot = SlKeyIdx(0)
360+
361+
// slotKey is a type-insensitive encapsulation of a LocalSlot; it
362+
// is used to key a map within slotCanonicalizer.
363+
type slotKey struct {
364+
name *ir.Name
365+
offset int64
366+
width int64
367+
splitOf SlKeyIdx // idx in slkeys slice in slotCanonicalizer
368+
splitOffset int64
369+
}
370+
371+
// lookup looks up a LocalSlot in the slot canonicalizer "sc", returning
372+
// a canonical index for the slot, and adding it to the table if need
373+
// be. Return value is the canonical slot index, and a boolean indicating
374+
// whether the slot was found in the table already (TRUE => found).
375+
func (sc *slotCanonicalizer) lookup(ls LocalSlot) (SlKeyIdx, bool) {
376+
split := noSlot
377+
if ls.SplitOf != nil {
378+
split, _ = sc.lookup(*ls.SplitOf)
379+
}
380+
k := slotKey{
381+
name: ls.N, offset: ls.Off, width: ls.Type.Width,
382+
splitOf: split, splitOffset: ls.SplitOffset,
383+
}
384+
if idx, ok := sc.slmap[k]; ok {
385+
return idx, true
386+
}
387+
rv := SlKeyIdx(len(sc.slkeys))
388+
sc.slkeys = append(sc.slkeys, ls)
389+
sc.slmap[k] = rv
390+
return rv, false
391+
}
392+
393+
func (sc *slotCanonicalizer) canonSlot(idx SlKeyIdx) LocalSlot {
394+
return sc.slkeys[idx]
395+
}
396+
397+
// PopulateABIInRegArgOps examines the entry block of the function
398+
// and looks for incoming parameters that have missing or partial
399+
// OpArg{Int,Float}Reg values, inserting additional values in
400+
// cases where they are missing. Example:
401+
//
402+
// func foo(s string, used int, notused int) int {
403+
// return len(s) + used
404+
// }
405+
//
406+
// In the function above, the incoming parameter "used" is fully live,
407+
// "notused" is not live, and "s" is partially live (only the length
408+
// field of the string is used). At the point where debug value
409+
// analysis runs, we might expect to see an entry block with:
410+
//
411+
// b1:
412+
// v4 = ArgIntReg <uintptr> {s+8} [0] : BX
413+
// v5 = ArgIntReg <int> {used} [0] : CX
414+
//
415+
// While this is an accurate picture of the live incoming params,
416+
// we also want to have debug locations for non-live params (or
417+
// their non-live pieces), e.g. something like
418+
//
419+
// b1:
420+
// v9 = ArgIntReg <*uint8> {s+0} [0] : AX
421+
// v4 = ArgIntReg <uintptr> {s+8} [0] : BX
422+
// v5 = ArgIntReg <int> {used} [0] : CX
423+
// v10 = ArgIntReg <int> {unused} [0] : DI
424+
//
425+
// This function examines the live OpArg{Int,Float}Reg values and
426+
// synthesizes new (dead) values for the non-live params or the
427+
// non-live pieces of partially live params.
428+
//
429+
func PopulateABIInRegArgOps(f *Func) {
430+
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType())
431+
432+
// When manufacturing new slots that correspond to splits of
433+
// composite parameters, we want to avoid creating a new sub-slot
434+
// that differs from some existing sub-slot only by type, since
435+
// the debug location analysis will treat that slot as a separate
436+
// entity. To achieve this, create a lookup table of existing
437+
// slots that is type-insenstitive.
438+
sc := newSlotCanonicalizer()
439+
for _, sl := range f.Names {
440+
sc.lookup(*sl)
441+
}
442+
443+
// Add slot -> value entry to f.NamedValues if not already present.
444+
addToNV := func(v *Value, sl LocalSlot) {
445+
values, ok := f.NamedValues[sl]
446+
if !ok {
447+
// Haven't seen this slot yet.
448+
sla := f.localSlotAddr(sl)
449+
f.Names = append(f.Names, sla)
450+
} else {
451+
for _, ev := range values {
452+
if v == ev {
453+
return
454+
}
455+
}
456+
}
457+
values = append(values, v)
458+
f.NamedValues[sl] = values
459+
}
460+
461+
newValues := []*Value{}
462+
463+
abiRegIndexToRegister := func(reg abi.RegIndex) int8 {
464+
i := f.ABISelf.FloatIndexFor(reg)
465+
if i >= 0 { // float PR
466+
return f.Config.floatParamRegs[i]
467+
} else {
468+
return f.Config.intParamRegs[reg]
469+
}
470+
}
471+
472+
// Helper to construct a new OpArg{Float,Int}Reg op value.
473+
var pos src.XPos
474+
if len(f.Entry.Values) != 0 {
475+
pos = f.Entry.Values[0].Pos
476+
}
477+
synthesizeOpIntFloatArg := func(n *ir.Name, t *types.Type, reg abi.RegIndex, sl LocalSlot) *Value {
478+
aux := &AuxNameOffset{n, sl.Off}
479+
op, auxInt := ArgOpAndRegisterFor(reg, f.ABISelf)
480+
v := f.newValueNoBlock(op, t, pos)
481+
v.AuxInt = auxInt
482+
v.Aux = aux
483+
v.Args = nil
484+
v.Block = f.Entry
485+
newValues = append(newValues, v)
486+
addToNV(v, sl)
487+
f.setHome(v, &f.Config.registers[abiRegIndexToRegister(reg)])
488+
return v
489+
}
490+
491+
// Make a pass through the entry block looking for
492+
// OpArg{Int,Float}Reg ops. Record the slots they use in a table
493+
// ("sc"). We use a type-insensitive lookup for the slot table,
494+
// since the type we get from the ABI analyzer won't always match
495+
// what the compiler uses when creating OpArg{Int,Float}Reg ops.
496+
for _, v := range f.Entry.Values {
497+
if v.Op == OpArgIntReg || v.Op == OpArgFloatReg {
498+
aux := v.Aux.(*AuxNameOffset)
499+
sl := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset}
500+
// install slot in lookup table
501+
idx, _ := sc.lookup(sl)
502+
// add to f.NamedValues if not already present
503+
addToNV(v, sc.canonSlot(idx))
504+
} else if v.Op.IsCall() {
505+
// if we hit a call, we've gone too far.
506+
break
507+
}
508+
}
509+
510+
// Now make a pass through the ABI in-params, looking for params
511+
// or pieces of params that we didn't encounter in the loop above.
512+
for _, inp := range pri.InParams() {
513+
if !isNamedRegParam(inp) {
514+
continue
515+
}
516+
n := inp.Name.(*ir.Name)
517+
518+
// Param is spread across one or more registers. Walk through
519+
// each piece to see whether we've seen an arg reg op for it.
520+
types, offsets := inp.RegisterTypesAndOffsets()
521+
for k, t := range types {
522+
// Note: this recipe for creating a LocalSlot is designed
523+
// to be compatible with the one used in expand_calls.go
524+
// as opposed to decompose.go. The expand calls code just
525+
// takes the base name and creates an offset into it,
526+
// without using the SplitOf/SplitOffset fields. The code
527+
// in decompose.go does the opposite -- it creates a
528+
// LocalSlot object with "Off" set to zero, but with
529+
// SplitOf pointing to a parent slot, and SplitOffset
530+
// holding the offset into the parent object.
531+
pieceSlot := LocalSlot{N: n, Type: t, Off: offsets[k]}
532+
533+
// Look up this piece to see if we've seen a reg op
534+
// for it. If not, create one.
535+
_, found := sc.lookup(pieceSlot)
536+
if !found {
537+
// This slot doesn't appear in the map, meaning it
538+
// corresponds to an in-param that is not live, or
539+
// a portion of an in-param that is not live/used.
540+
// Add a new dummy OpArg{Int,Float}Reg for it.
541+
synthesizeOpIntFloatArg(n, t, inp.Registers[k],
542+
pieceSlot)
543+
}
544+
}
545+
}
546+
547+
// Insert the new values into the head of the block.
548+
f.Entry.Values = append(newValues, f.Entry.Values...)
549+
}
550+
338551
// BuildFuncDebug returns debug information for f.
339552
// f must be fully processed, so that each Value is where it will be when
340553
// machine code is emitted.
@@ -349,6 +562,10 @@ func BuildFuncDebug(ctxt *obj.Link, f *Func, loggingEnabled bool, stackOffset fu
349562
state.stackOffset = stackOffset
350563
state.ctxt = ctxt
351564

565+
if buildcfg.Experiment.RegabiArgs {
566+
PopulateABIInRegArgOps(f)
567+
}
568+
352569
if state.loggingEnabled {
353570
state.logf("Generating location lists for function %q\n", f.Name)
354571
}

0 commit comments

Comments
 (0)