compiler,linker: support for DWARF inlined instances

Compiler and linker changes to support DWARF inlined instances,
see https://go.googlesource.com/proposal/+/HEAD/design/22080-dwarf-inlining.md
for design details.

This functionality is gated via the cmd/compile option -gendwarfinl=N,
where N={0,1,2}, where a value of 0 disables dwarf inline generation,
a value of 1 turns on dwarf generation without tracking of formal/local
vars from inlined routines, and a value of 2 enables inlines with
variable tracking.

Updates #22080

Change-Id: I69309b3b815d9fed04aebddc0b8d33d0dbbfad6e
Reviewed-on: https://go-review.googlesource.com/75550
Run-TryBot: Than McIntosh <thanm@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
Than McIntosh 2017-10-06 11:32:28 -04:00
parent dbb1d198ab
commit 4435fcfd6c
17 changed files with 1950 additions and 152 deletions

View file

@ -15,6 +15,7 @@ import (
"log"
"path/filepath"
"sort"
"sync"
)
// objWriter writes Go object files.
@ -473,8 +474,33 @@ func (c dwCtxt) AddFileRef(s dwarf.Sym, f interface{}) {
r.Type = objabi.R_DWARFFILEREF
}
// dwarfSym returns the DWARF symbols for TEXT symbol.
func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *LSym) {
func (c dwCtxt) CurrentOffset(s dwarf.Sym) int64 {
ls := s.(*LSym)
return ls.Size
}
// Here "from" is a symbol corresponding to an inlined or concrete
// function, "to" is the symbol for the corresponding abstract
// function, and "dclIdx" is the index of the symbol of interest with
// respect to the Dcl slice of the original pre-optimization version
// of the inlined function.
func (c dwCtxt) RecordDclReference(from dwarf.Sym, to dwarf.Sym, dclIdx int, inlIndex int) {
ls := from.(*LSym)
tls := to.(*LSym)
ridx := len(ls.R) - 1
c.Link.DwFixups.ReferenceChildDIE(ls, ridx, tls, dclIdx, inlIndex)
}
func (c dwCtxt) RecordChildDieOffsets(s dwarf.Sym, vars []*dwarf.Var, offsets []int32) {
ls := s.(*LSym)
c.Link.DwFixups.RegisterChildDIEOffsets(ls, vars, offsets)
}
func (c dwCtxt) Logf(format string, args ...interface{}) {
c.Link.Logf(format, args...)
}
func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym, dwarfAbsFnSym *LSym) {
if s.Type != objabi.STEXT {
ctxt.Diag("dwarfSym of non-TEXT %v", s)
}
@ -484,8 +510,12 @@ func (ctxt *Link) dwarfSym(s *LSym) (dwarfInfoSym, dwarfLocSym, dwarfRangesSym *
s.Func.dwarfLocSym = ctxt.LookupDerived(s, dwarf.LocPrefix+s.Name)
}
s.Func.dwarfRangesSym = ctxt.LookupDerived(s, dwarf.RangePrefix+s.Name)
if s.WasInlined() {
s.Func.dwarfAbsFnSym = ctxt.DwFixups.AbsFuncDwarfSym(s)
}
}
return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym
return s.Func.dwarfInfoSym, s.Func.dwarfLocSym, s.Func.dwarfRangesSym, s.Func.dwarfAbsFnSym
}
func (s *LSym) Len() int64 {
@ -505,20 +535,45 @@ func (ctxt *Link) fileSymbol(fn *LSym) *LSym {
return nil
}
// populateDWARF fills in the DWARF Debugging Information Entries for TEXT symbol s.
// The DWARFs symbol must already have been initialized in InitTextSym.
func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym) {
info, loc, ranges := ctxt.dwarfSym(s)
// populateDWARF fills in the DWARF Debugging Information Entries for
// TEXT symbol 's'. The various DWARF symbols must already have been
// initialized in InitTextSym.
func (ctxt *Link) populateDWARF(curfn interface{}, s *LSym, myimportpath string) {
info, loc, ranges, absfunc := ctxt.dwarfSym(s)
if info.Size != 0 {
ctxt.Diag("makeFuncDebugEntry double process %v", s)
}
var scopes []dwarf.Scope
var inlcalls dwarf.InlCalls
if ctxt.DebugInfo != nil {
scopes = ctxt.DebugInfo(s, curfn)
scopes, inlcalls = ctxt.DebugInfo(s, curfn)
}
var err error
dwctxt := dwCtxt{ctxt}
filesym := ctxt.fileSymbol(s)
fnstate := &dwarf.FnState{
Name: s.Name,
Importpath: myimportpath,
Info: info,
Filesym: filesym,
Loc: loc,
Ranges: ranges,
Absfn: absfunc,
StartPC: s,
Size: s.Size,
External: !s.Static(),
Scopes: scopes,
InlCalls: inlcalls,
}
if absfunc != nil {
err = dwarf.PutAbstractFunc(dwctxt, fnstate)
if err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
err = dwarf.PutConcreteFunc(dwctxt, fnstate)
} else {
err = dwarf.PutDefaultFunc(dwctxt, fnstate)
}
fs := ctxt.fileSymbol(s)
err := dwarf.PutFunc(dwCtxt{ctxt}, info, loc, ranges, fs, s.Name, !s.Static(), s, s.Size, scopes)
if err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
@ -536,3 +591,285 @@ func (ctxt *Link) DwarfIntConst(myimportpath, name, typename string, val int64)
})
dwarf.PutIntConst(dwCtxt{ctxt}, s, ctxt.Lookup(dwarf.InfoPrefix+typename), myimportpath+"."+name, val)
}
func (ctxt *Link) DwarfAbstractFunc(curfn interface{}, s *LSym, myimportpath string) {
absfn := ctxt.DwFixups.AbsFuncDwarfSym(s)
if absfn.Size != 0 {
ctxt.Diag("internal error: DwarfAbstractFunc double process %v", s)
}
if s.Func == nil {
s.Func = new(FuncInfo)
}
scopes, _ := ctxt.DebugInfo(s, curfn)
dwctxt := dwCtxt{ctxt}
filesym := ctxt.fileSymbol(s)
fnstate := dwarf.FnState{
Name: s.Name,
Importpath: myimportpath,
Info: absfn,
Filesym: filesym,
Absfn: absfn,
External: !s.Static(),
Scopes: scopes,
}
if err := dwarf.PutAbstractFunc(dwctxt, &fnstate); err != nil {
ctxt.Diag("emitting DWARF for %s failed: %v", s.Name, err)
}
}
// This table is designed to aid in the creation of references betweeen
// DWARF subprogram DIEs.
//
// In most cases when one DWARF DIE has to refer to another DWARF DIE,
// the target of the reference has an LSym, which makes it easy to use
// the existing relocation mechanism. For DWARF inlined routine DIEs,
// however, the subprogram DIE has to refer to a child
// parameter/variable DIE of the abstract subprogram. This child DIE
// doesn't have an LSym, and also of interest is the fact that when
// DWARF generation is happening for inlined function F within caller
// G, it's possible that DWARF generation hasn't happened yet for F,
// so there is no way to know the offset of a child DIE within F's
// abstract function. Making matters more complex, each inlined
// instance of F may refer to a subset of the original F's variables
// (depending on what happens with optimization, some vars may be
// eliminated).
//
// The fixup table below helps overcome this hurdle. At the point
// where a parameter/variable reference is made (via a call to
// "ReferenceChildDIE"), a fixup record is generate that records
// the relocation that is targeting that child variable. At a later
// point when the abstract function DIE is emitted, there will be
// a call to "RegisterChildDIEOffsets", at which point the offsets
// needed to apply fixups are captured. Finally, once the parallel
// portion of the compilation is done, fixups can actually be applied
// during the "Finalize" method (this can't be done during the
// parallel portion of the compile due to the possibility of data
// races).
//
// This table is also used to record the "precursor" function node for
// each function that is the target of an inline -- child DIE references
// have to be made with respect to the original pre-optimization
// version of the function (to allow for the fact that each inlined
// body may be optimized differently).
type DwarfFixupTable struct {
ctxt *Link
mu sync.Mutex
symtab map[*LSym]int // maps abstract fn LSYM to index in svec
svec []symFixups
precursor map[*LSym]fnState // maps fn Lsym to precursor Node, absfn sym
}
type symFixups struct {
fixups []relFixup
doffsets []declOffset
inlIndex int32
defseen bool
}
type declOffset struct {
// Index of variable within DCL list of pre-optimization function
dclIdx int32
// Offset of var's child DIE with respect to containing subprogram DIE
offset int32
}
type relFixup struct {
refsym *LSym
relidx int32
dclidx int32
}
type fnState struct {
// precursor function (really *gc.Node)
precursor interface{}
// abstract function symbol
absfn *LSym
}
func NewDwarfFixupTable(ctxt *Link) *DwarfFixupTable {
return &DwarfFixupTable{
ctxt: ctxt,
symtab: make(map[*LSym]int),
precursor: make(map[*LSym]fnState),
}
}
func (ft *DwarfFixupTable) GetPrecursorFunc(s *LSym) interface{} {
if fnstate, found := ft.precursor[s]; found {
return fnstate.precursor
}
return nil
}
func (ft *DwarfFixupTable) SetPrecursorFunc(s *LSym, fn interface{}) {
if _, found := ft.precursor[s]; found {
ft.ctxt.Diag("internal error: DwarfFixupTable.SetPrecursorFunc double call on %v", s)
}
// initialize abstract function symbol now. This is done here so
// as to avoid data races later on during the parallel portion of
// the back end.
absfn := ft.ctxt.LookupDerived(s, dwarf.InfoPrefix+s.Name+dwarf.AbstractFuncSuffix)
absfn.Set(AttrDuplicateOK, true)
absfn.Type = objabi.SDWARFINFO
ft.ctxt.Data = append(ft.ctxt.Data, absfn)
ft.precursor[s] = fnState{precursor: fn, absfn: absfn}
}
// Make a note of a child DIE reference: relocation 'ridx' within symbol 's'
// is targeting child 'c' of DIE with symbol 'tgt'.
func (ft *DwarfFixupTable) ReferenceChildDIE(s *LSym, ridx int, tgt *LSym, dclidx int, inlIndex int) {
// Protect against concurrent access if multiple backend workers
ft.mu.Lock()
defer ft.mu.Unlock()
// Create entry for symbol if not already present.
idx, found := ft.symtab[tgt]
if !found {
ft.svec = append(ft.svec, symFixups{inlIndex: int32(inlIndex)})
idx = len(ft.svec) - 1
ft.symtab[tgt] = idx
}
// Do we have child DIE offsets available? If so, then apply them,
// otherwise create a fixup record.
sf := &ft.svec[idx]
if len(sf.doffsets) > 0 {
found := false
for _, do := range sf.doffsets {
if do.dclIdx == int32(dclidx) {
off := do.offset
s.R[ridx].Add += int64(off)
found = true
break
}
}
if !found {
ft.ctxt.Diag("internal error: DwarfFixupTable.ReferenceChildDIE unable to locate child DIE offset for dclIdx=%d src=%v tgt=%v", dclidx, s, tgt)
}
} else {
sf.fixups = append(sf.fixups, relFixup{s, int32(ridx), int32(dclidx)})
}
}
// Called once DWARF generation is complete for a given abstract function,
// whose children might have been referenced via a call above. Stores
// the offsets for any child DIEs (vars, params) so that they can be
// consumed later in on DwarfFixupTable.Finalize, which applies any
// outstanding fixups.
func (ft *DwarfFixupTable) RegisterChildDIEOffsets(s *LSym, vars []*dwarf.Var, coffsets []int32) {
// Length of these two slices should agree
if len(vars) != len(coffsets) {
ft.ctxt.Diag("internal error: RegisterChildDIEOffsets vars/offsets length mismatch")
return
}
// Generate the slice of declOffset's based in vars/coffsets
doffsets := make([]declOffset, len(coffsets))
for i := 0; i < len(coffsets); i++ {
doffsets[i].dclIdx = vars[i].ChildIndex
doffsets[i].offset = coffsets[i]
}
ft.mu.Lock()
defer ft.mu.Unlock()
// Store offsets for this symbol.
idx, found := ft.symtab[s]
if !found {
sf := symFixups{inlIndex: -1, defseen: true, doffsets: doffsets}
ft.svec = append(ft.svec, sf)
ft.symtab[s] = len(ft.svec) - 1
} else {
sf := &ft.svec[idx]
sf.doffsets = doffsets
sf.defseen = true
}
}
func (ft *DwarfFixupTable) processFixups(slot int, s *LSym) {
sf := &ft.svec[slot]
for _, f := range sf.fixups {
dfound := false
for i := 0; i < len(sf.doffsets); i++ {
if sf.doffsets[i].dclIdx == f.dclidx {
f.refsym.R[f.relidx].Add += int64(sf.doffsets[i].offset)
dfound = true
break
}
}
if !dfound {
ft.ctxt.Diag("internal error: DwarfFixupTable has orphaned fixup on %v targeting %v relidx=%d dclidx=%d", f.refsym, s, f.relidx, f.dclidx)
}
}
}
// return the LSym corresponding to the 'abstract subprogram' DWARF
// info entry for a function.
func (ft *DwarfFixupTable) AbsFuncDwarfSym(fnsym *LSym) *LSym {
// Protect against concurrent access if multiple backend workers
ft.mu.Lock()
defer ft.mu.Unlock()
if fnstate, found := ft.precursor[fnsym]; found {
return fnstate.absfn
}
ft.ctxt.Diag("internal error: AbsFuncDwarfSym requested for %v, not seen during inlining", fnsym)
return nil
}
// Called after all functions have been compiled; the main job of this
// function is to identify cases where there are outstanding fixups.
// This scenario crops up when we have references to variables of an
// inlined routine, but that routine is defined in some other package.
// This helper walks through and locate these fixups, then invokes a
// helper to create an abstract subprogram DIE for each one.
func (ft *DwarfFixupTable) Finalize(myimportpath string, trace bool) {
if trace {
ft.ctxt.Logf("DwarfFixupTable.Finalize invoked for %s\n", myimportpath)
}
// Collect up the keys from the precursor map, then sort the
// resulting list (don't want to rely on map ordering here).
fns := make([]*LSym, len(ft.precursor))
idx := 0
for fn, _ := range ft.precursor {
fns[idx] = fn
idx++
}
sort.Sort(bySymName(fns))
// Should not be called during parallel portion of compilation.
if ft.ctxt.InParallel {
ft.ctxt.Diag("internal error: DwarfFixupTable.Finalize call during parallel backend")
}
// Generate any missing abstract functions.
for i := 0; i < len(fns); i++ {
s := fns[i]
absfn := ft.AbsFuncDwarfSym(s)
slot, found := ft.symtab[absfn]
if !found || !ft.svec[slot].defseen {
ft.ctxt.GenAbstractFunc(s)
}
}
// Apply fixups.
for i := 0; i < len(fns); i++ {
s := fns[i]
absfn := ft.AbsFuncDwarfSym(s)
slot, found := ft.symtab[absfn]
if !found {
ft.ctxt.Diag("internal error: DwarfFixupTable.Finalize orphan abstract function for %v", s)
} else {
ft.processFixups(slot, s)
}
}
}
type bySymName []*LSym
func (s bySymName) Len() int { return len(s) }
func (s bySymName) Less(i, j int) bool { return s[i].Name < s[j].Name }
func (s bySymName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }