1
0
mirror of https://github.com/golang/go synced 2024-11-23 05:30:07 -07:00

cmd/compile: regabi support for DWARF location expressions

Revise the code that generates DWARF location expressions for input
parameters to get it to work properly with the new register ABI when
optimization is turned off.

The previously implementation assumed stack locations for all
input+output parameters when -N (disable optimization) was in effect.
In the new implementation, a register-resident input parameter is
given a 2-element location list, the first list element pointing to
the ABI register(s) containing the param, and the second element
pointing to the stack home once it has been spilled.

NB, this change fixes a bunch of the Delve pkg/proc unit tests (maybe
about half of the outstanding failures). Still a good number that need
to be investigated, however.

Updates #40724.
Updates #45720.

Change-Id: I743bbb9af187bcdebeb8e690fdd6db58094ca415
Reviewed-on: https://go-review.googlesource.com/c/go/+/314431
Trust: Than McIntosh <thanm@google.com>
Trust: Cherry Zhang <cherryyz@google.com>
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cherry Zhang <cherryyz@google.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Than McIntosh 2021-04-27 12:40:43 -04:00
parent 93200b98c7
commit 162d4f9c92
3 changed files with 282 additions and 3 deletions

View File

@ -138,8 +138,11 @@ func createDwarfVars(fnsym *obj.LSym, complexOK bool, fn *ir.Func, apDecls []*ir
var vars []*dwarf.Var
var decls []*ir.Name
var selected ir.NameSet
if base.Ctxt.Flag_locationlists && base.Ctxt.Flag_optimize && fn.DebugInfo != nil && complexOK {
decls, vars, selected = createComplexVars(fnsym, fn)
} else if fn.ABI == obj.ABIInternal && base.Flag.N != 0 && complexOK {
decls, vars, selected = createABIVars(fnsym, fn, apDecls)
} else {
decls, vars, selected = createSimpleVars(fnsym, apDecls)
}
@ -314,6 +317,37 @@ func createSimpleVar(fnsym *obj.LSym, n *ir.Name) *dwarf.Var {
}
}
// createABIVars creates DWARF variables for functions in which the
// register ABI is enabled but optimization is turned off. It uses a
// hybrid approach in which register-resident input params are
// captured with location lists, and all other vars use the "simple"
// strategy.
func createABIVars(fnsym *obj.LSym, fn *ir.Func, apDecls []*ir.Name) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {
// Invoke createComplexVars to generate dwarf vars for input parameters
// that are register-allocated according to the ABI rules.
decls, vars, selected := createComplexVars(fnsym, fn)
// Now fill in the remainder of the variables: input parameters
// that are not register-resident, output parameters, and local
// variables.
for _, n := range apDecls {
if ir.IsAutoTmp(n) {
continue
}
if _, ok := selected[n]; ok {
// already handled
continue
}
decls = append(decls, n)
vars = append(vars, createSimpleVar(fnsym, n))
selected.Add(n)
}
return decls, vars, selected
}
// createComplexVars creates recomposed DWARF vars with location lists,
// suitable for describing optimized code.
func createComplexVars(fnsym *obj.LSym, fn *ir.Func) ([]*ir.Name, []*dwarf.Var, ir.NameSet) {

View File

@ -5,6 +5,7 @@
package ssa
import (
"cmd/compile/internal/abi"
"cmd/compile/internal/ir"
"cmd/internal/dwarf"
"cmd/internal/obj"
@ -1124,8 +1125,11 @@ func (debugInfo *FuncDebug) PutLocationList(list []byte, ctxt *obj.Link, listSym
listSym.WriteInt(ctxt, listSym.Size, ctxt.Arch.PtrSize, 0)
}
// Pack a value and block ID into an address-sized uint, returning ~0 if they
// don't fit.
// Pack a value and block ID into an address-sized uint, returning encoded
// value and boolean indicating whether the encoding succeeded. For
// 32-bit architectures the process may fail for very large procedures
// (the theory being that it's ok to have degraded debug quality in
// this case).
func encodeValue(ctxt *obj.Link, b, v ID) (uint64, bool) {
if ctxt.Arch.PtrSize == 8 {
result := uint64(b)<<32 | uint64(uint32(v))
@ -1192,3 +1196,239 @@ func readPtr(ctxt *obj.Link, buf []byte) uint64 {
}
}
// setupLocList creates the initial portion of a location list for a
// user variable. It emits the encoded start/end of the range and a
// placeholder for the size. Return value is the new list plus the
// slot in the list holding the size (to be updated later).
func setupLocList(ctxt *obj.Link, f *Func, list []byte, st, en ID) ([]byte, int) {
start, startOK := encodeValue(ctxt, f.Entry.ID, st)
end, endOK := encodeValue(ctxt, f.Entry.ID, en)
if !startOK || !endOK {
// This could happen if someone writes a function that uses
// >65K values on a 32-bit platform. Hopefully a degraded debugging
// experience is ok in that case.
return nil, 0
}
list = appendPtr(ctxt, list, start)
list = appendPtr(ctxt, list, end)
// Where to write the length of the location description once
// we know how big it is.
sizeIdx := len(list)
list = list[:len(list)+2]
return list, sizeIdx
}
// locatePrologEnd walks the entry block of a function with incoming
// register arguments and locates the last instruction in the prolog
// that spills a register arg. It returns the ID of that instruction
// Example:
//
// b1:
// v3 = ArgIntReg <int> {p1+0} [0] : AX
// ... more arg regs ..
// v4 = ArgFloatReg <float32> {f1+0} [0] : X0
// v52 = MOVQstore <mem> {p1} v2 v3 v1
// ... more stores ...
// v68 = MOVSSstore <mem> {f4} v2 v67 v66
// v38 = MOVQstoreconst <mem> {blob} [val=0,off=0] v2 v32
//
// Important: locatePrologEnd is expected to work properly only with
// optimization turned off (e.g. "-N"). If optimization is enabled
// we can't be assured of finding all input arguments spilled in the
// entry block prolog.
func locatePrologEnd(f *Func) ID {
// returns true if this instruction looks like it moves an ABI
// register to the stack, along with the value being stored.
isRegMoveLike := func(v *Value) (bool, ID) {
n, ok := v.Aux.(*ir.Name)
var r ID
if !ok || n.Class != ir.PPARAM {
return false, r
}
regInputs, memInputs, spInputs := 0, 0, 0
for _, a := range v.Args {
if a.Op == OpArgIntReg || a.Op == OpArgFloatReg {
regInputs++
r = a.ID
} else if a.Type.IsMemory() {
memInputs++
} else if a.Op == OpSP {
spInputs++
} else {
return false, r
}
}
return v.Type.IsMemory() && memInputs == 1 &&
regInputs == 1 && spInputs == 1, r
}
// OpArg*Reg values we've seen so far on our forward walk,
// for which we have not yet seen a corresponding spill.
regArgs := make([]ID, 0, 32)
// removeReg tries to remove a value from regArgs, returning true
// if found and removed, or false otherwise.
removeReg := func(r ID) bool {
for i := 0; i < len(regArgs); i++ {
if regArgs[i] == r {
regArgs = append(regArgs[:i], regArgs[i+1:]...)
return true
}
}
return false
}
// Walk forwards through the block. When we see OpArg*Reg, record
// the value it produces in the regArgs list. When see a store that uses
// the value, remove the entry. When we hit the last store (use)
// then we've arrived at the end of the prolog.
for k, v := range f.Entry.Values {
if v.Op == OpArgIntReg || v.Op == OpArgFloatReg {
regArgs = append(regArgs, v.ID)
continue
}
if ok, r := isRegMoveLike(v); ok {
if removed := removeReg(r); removed {
if len(regArgs) == 0 {
// Found our last spill; return the value after
// it. Note that it is possible that this spill is
// the last instruction in the block. If so, then
// return the "end of block" sentinel.
if k < len(f.Entry.Values)-1 {
return f.Entry.Values[k+1].ID
}
return BlockEnd.ID
}
}
}
if v.Op.IsCall() {
// if we hit a call, we've gone too far.
return v.ID
}
}
// nothing found
return ID(-1)
}
// isNamedRegParam returns true if the param corresponding to "p"
// is a named, non-blank input parameter assigned to one or more
// registers.
func isNamedRegParam(p abi.ABIParamAssignment) bool {
if p.Name == nil {
return false
}
n := p.Name.(*ir.Name)
if n.Sym() == nil || n.Sym().IsBlank() {
return false
}
if len(p.Registers) == 0 {
return false
}
return true
}
// BuildFuncDebugNoOptimized constructs a FuncDebug object 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,
// it constructs a 2-element location list: the first element holds
// 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{}
pri := f.ABISelf.ABIAnalyzeFuncType(f.Type.FuncType())
// Look to see if we have any named register-promoted parameters.
// If there are none, bail early and let the caller sort things
// out for the remainder of the params/locals.
numRegParams := 0
for _, inp := range pri.InParams() {
if isNamedRegParam(inp) {
numRegParams++
}
}
if numRegParams == 0 {
return &fd
}
// Allocate location lists.
fd.LocationLists = make([][]byte, numRegParams)
// Locate the value corresponding to the last spill of
// an input register.
afterPrologVal := locatePrologEnd(f)
if afterPrologVal == ID(-1) {
panic(fmt.Sprintf("internal error: f=%s: can't locate after prolog value", f.Name))
}
// Walk the input params again and process the register-resident elements.
pidx := 0
for _, inp := range pri.InParams() {
if !isNamedRegParam(inp) {
// will be sorted out elsewhere
continue
}
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)})
// 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],
BlockStart.ID, afterPrologVal)
if list == nil {
pidx++
continue
}
rtypes, _ := inp.RegisterTypesAndOffsets()
for k, r := range inp.Registers {
reg := ObjRegForAbiReg(r, f.Config)
dwreg := ctxt.Arch.DWARFRegisters[reg]
if dwreg < 32 {
list = append(list, dwarf.DW_OP_reg0+byte(dwreg))
} else {
list = append(list, dwarf.DW_OP_regx)
list = dwarf.AppendUleb128(list, uint64(dwreg))
}
if len(inp.Registers) > 1 {
list = append(list, dwarf.DW_OP_piece)
ts := rtypes[k].Width
list = dwarf.AppendUleb128(list, uint64(ts))
}
}
// fill in length of location expression element
ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
// Second entry in the location list will be the stack home
// of the param, once it has been spilled. Emit that now.
list, sizeIdx = setupLocList(ctxt, f, list,
afterPrologVal, FuncEnd.ID)
if list == nil {
pidx++
continue
}
soff := stackOffset(sl)
if soff == 0 {
list = append(list, dwarf.DW_OP_call_frame_cfa)
} else {
list = append(list, dwarf.DW_OP_fbreg)
list = dwarf.AppendSleb128(list, int64(soff))
}
// fill in size
ctxt.Arch.ByteOrder.PutUint16(list[sizeIdx:], uint16(len(list)-sizeIdx-2))
fd.LocationLists[pidx] = list
pidx++
}
return &fd
}

View File

@ -6961,7 +6961,12 @@ func genssa(f *ssa.Func, pp *objw.Progs) {
}
if base.Ctxt.Flag_locationlists {
debugInfo := ssa.BuildFuncDebug(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset)
var debugInfo *ssa.FuncDebug
if e.curfn.ABI == obj.ABIInternal && base.Flag.N != 0 {
debugInfo = ssa.BuildFuncDebugNoOptimized(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset)
} else {
debugInfo = ssa.BuildFuncDebug(base.Ctxt, f, base.Debug.LocationLists > 1, StackOffset)
}
e.curfn.DebugInfo = debugInfo
bstart := s.bstart
idToIdx := make([]int, f.NumBlocks())