golang dwinl 代码
文件路径:/src/cmd/compile/internal/dwarfgen/dwinl.go
/ Copyright 2017 The Go Authors. All rights reserved.
/ Use of this source code is governed by a BSD-style
/ license that can be found in the LICENSE file.
package dwarfgen
import (
"fmt"
"strings"
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/internal/dwarf"
"cmd/internal/obj"
"cmd/internal/src"
)
/ To identify variables by original source position.
type varPos struct {
DeclName string
DeclFile string
DeclLine uint
DeclCol uint
}
/ This is the main entry point for collection of raw material to
/ drive generation of DWARF "inlined subroutine" DIEs. See proposal
/ 22080 for more details and background info.
func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls {
var inlcalls dwarf.InlCalls
if base.Debug.DwarfInl != 0 {
base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name)
}
/ This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls
imap := make(map[int]int)
/ Walk progs to build up the InlCalls data structure
var prevpos src.XPos
for p := fnsym.Func().Text; p != nil; p = p.Link {
if p.Pos == prevpos {
continue
}
ii := posInlIndex(p.Pos)
if ii >= 0 {
insertInlCall(&inlcalls, ii, imap)
}
prevpos = p.Pos
}
/ This is used to partition DWARF vars by inline index. Vars not
/ produced by the inliner will wind up in the vmap[0] entry.
vmap := make(map[int32][]*dwarf.Var)
/ Now walk the dwarf vars and partition them based on whether they
/ were produced by the inliner (dwv.InlIndex > 0) or were original
/ vars/params from the function (dwv.InlIndex == 0).
for _, dwv := range dwVars {
vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv)
/ Zero index => var was not produced by an inline
if dwv.InlIndex == 0 {
continue
}
/ Look up index in our map, then tack the var in question
/ onto the vars list for the correct inlined call.
ii := int(dwv.InlIndex) - 1
idx, ok := imap[ii]
if !ok {
/ We can occasionally encounter a var produced by the
/ inliner for which there is no remaining prog; add a new
/ entry to the call list in this scenario.
idx = insertInlCall(&inlcalls, ii, imap)
}
inlcalls.Calls[idx].InlVars =
append(inlcalls.Calls[idx].InlVars, dwv)
}
/ Post process the map above to assign child indices to vars.
/
/ A given variable is treated differently depending on whether it
/ is part of the top-level function (ii == 0) or if it was
/ produced as a result of an inline (ii != 0).
/
/ If a variable was not produced by an inline and its containing
/ function was not inlined, then we just assign an ordering of
/ based on variable name.
/
/ If a variable was not produced by an inline and its containing
/ function was inlined, then we need to assign a child index
/ based on the order of vars in the abstract function (in
/ addition, those vars that don't appear in the abstract
/ function, such as "~r1", are flagged as such).
/
/ If a variable was produced by an inline, then we locate it in
/ the pre-inlining decls for the target function and assign child
/ index accordingly.
for ii, sl := range vmap {
var m map[varPos]int
if ii == 0 {
if !fnsym.WasInlined() {
for j, v := range sl {
v.ChildIndex = int32(j)
}
continue
}
m = makePreinlineDclMap(fnsym)
} else {
ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1))
m = makePreinlineDclMap(ifnlsym)
}
/ Here we assign child indices to variables based on
/ pre-inlined decls, and set the "IsInAbstract" flag
/ appropriately. In addition: parameter and local variable
/ names are given "middle dot" version numbers as part of the
/ writing them out to export data (see issue 4326). If DWARF
/ inlined routine generation is turned on, we want to undo
/ this versioning, since DWARF variables in question will be
/ parented by the inlined routine and not the top-level
/ caller.
synthCount := len(m)
for _, v := range sl {
canonName := unversion(v.Name)
vp := varPos{
DeclName: canonName,
DeclFile: v.DeclFile,
DeclLine: v.DeclLine,
DeclCol: v.DeclCol,
}
synthesized := strings.HasPrefix(v.Name, "~r") || canonName == "_" || strings.HasPrefix(v.Name, "~b")
if idx, found := m[vp]; found {
v.ChildIndex = int32(idx)
v.IsInAbstract = !synthesized
v.Name = canonName
} else {
/ Variable can't be found in the pre-inline dcl list.
/ In the top-level case (ii=0) this can happen
/ because a composite variable was split into pieces,
/ and we're looking at a piece. We can also see
/ return temps (~r%d) that were created during
/ lowering, or unnamed params ("_").
v.ChildIndex = int32(synthCount)
synthCount++
}
}
}
/ Make a second pass through the progs to compute PC ranges for
/ the various inlined calls.
start := int64(-1)
curii := -1
var prevp *obj.Prog
for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link {
if prevp != nil && p.Pos == prevp.Pos {
continue
}
ii := posInlIndex(p.Pos)
if ii == curii {
continue
}
/ Close out the current range
if start != -1 {
addRange(inlcalls.Calls, start, p.Pc, curii, imap)
}
/ Begin new range
start = p.Pc
curii = ii
}
if start != -1 {
addRange(inlcalls.Calls, start, fnsym.Size, curii, imap)
}
/ Issue 33188: if II foo is a child of II bar, then ensure that
/ bar's ranges include the ranges of foo (the loop above will produce
/ disjoint ranges).
for k, c := range inlcalls.Calls {
if c.Root {
unifyCallRanges(inlcalls, k)
}
}
/ Debugging
if base.Debug.DwarfInl != 0 {
dumpInlCalls(inlcalls)
dumpInlVars(dwVars)
}
/ Perform a consistency check on inlined routine PC ranges
/ produced by unifyCallRanges above. In particular, complain in
/ cases where you have A -> B -> C (e.g. C is inlined into B, and
/ B is inlined into A) and the ranges for B are not enclosed
/ within the ranges for A, or C within B.
for k, c := range inlcalls.Calls {
if c.Root {
checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1)
}
}
return inlcalls
}
/ Secondary hook for DWARF inlined subroutine generation. This is called
/ late in the compilation when it is determined that we need an
/ abstract function DIE for an inlined routine imported from a
/ previously compiled package.
func AbstractFunc(fn *obj.LSym) {
ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn)
if ifn == nil {
base.Ctxt.Diag("failed to locate precursor fn for %v", fn)
return
}
_ = ifn.(*ir.Func)
if base.Debug.DwarfInl != 0 {
base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name)
}
base.Ctxt.DwarfAbstractFunc(ifn, fn, base.Ctxt.Pkgpath)
}
/ Undo any versioning performed when a name was written
/ out as part of export data.
func unversion(name string) string {
if i := strings.Index(name, "·"); i > 0 {
name = name[:i]
}
return name
}
/ Given a function that was inlined as part of the compilation, dig
/ up the pre-inlining DCL list for the function and create a map that
/ supports lookup of pre-inline dcl index, based on variable
/ position/name. NB: the recipe for computing variable pos/file/line
/ needs to be kept in sync with the similar code in gc.createSimpleVars
/ and related functions.
func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
dcl := preInliningDcls(fnsym)
m := make(map[varPos]int)
for i, n := range dcl {
pos := base.Ctxt.InnermostPos(n.Pos())
vp := varPos{
DeclName: unversion(n.Sym().Name),
DeclFile: pos.RelFilename(),
DeclLine: pos.RelLine(),
DeclCol: pos.RelCol(),
}
if _, found := m[vp]; found {
/ We can see collisions (variables with the same name/file/line/col) in obfuscated or machine-generated code -- see issue 44378 for an example. Skip duplicates in such cases, since it is unlikely that a human will be debugging such code.
continue
}
m[vp] = i
}
return m
}
func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
callIdx, found := imap[inlIdx]
if found {
return callIdx
}
/ Haven't seen this inline yet. Visit parent of inline if there
/ is one. We do this first so that parents appear before their
/ children in the resulting table.
parCallIdx := -1
parInlIdx := base.Ctxt.InlTree.Parent(inlIdx)
if parInlIdx >= 0 {
parCallIdx = insertInlCall(dwcalls, parInlIdx, imap)
}
/ Create new entry for this inline
inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx)
callXPos := base.Ctxt.InlTree.CallPos(inlIdx)
absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn)
pb := base.Ctxt.PosTable.Pos(callXPos).Base()
callFileSym := base.Ctxt.Lookup(pb.SymFilename())
ic := dwarf.InlCall{
InlIndex: inlIdx,
CallFile: callFileSym,
CallLine: uint32(callXPos.Line()),
AbsFunSym: absFnSym,
Root: parCallIdx == -1,
}
dwcalls.Calls = append(dwcalls.Calls, ic)
callIdx = len(dwcalls.Calls) - 1
imap[inlIdx] = callIdx
if parCallIdx != -1 {
/ Add this inline to parent's child list
dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx)
}
return callIdx
}
/ Given a src.XPos, return its associated inlining index if it
/ corresponds to something created as a result of an inline, or -1 if
/ there is no inline info. Note that the index returned will refer to
/ the deepest call in the inlined stack, e.g. if you have "A calls B
/ calls C calls D" and all three callees are inlined (B, C, and D),
/ the index for a node from the inlined body of D will refer to the
/ call to D from C. Whew.
func posInlIndex(xpos src.XPos) int {
pos := base.Ctxt.PosTable.Pos(xpos)
if b := pos.Base(); b != nil {
ii := b.InliningIndex()
if ii >= 0 {
return ii
}
}
return -1
}
func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) {
if start == -1 {
panic("bad range start")
}
if end == -1 {
panic("bad range end")
}
if ii == -1 {
return
}
if start == end {
return
}
/ Append range to correct inlined call
callIdx, found := imap[ii]
if !found {
base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start)
}
call := &calls[callIdx]
call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end})
}
func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) {
for i := 0; i < ilevel; i++ {
base.Ctxt.Logf(" ")
}
ic := inlcalls.Calls[idx]
callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex)
base.Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name)
for _, f := range ic.InlVars {
base.Ctxt.Logf(" %v", f.Name)
}
base.Ctxt.Logf(" ) C: (")
for _, k := range ic.Children {
base.Ctxt.Logf(" %v", k)
}
base.Ctxt.Logf(" ) R:")
for _, r := range ic.Ranges {
base.Ctxt.Logf(" [%d,%d)", r.Start, r.End)
}
base.Ctxt.Logf("\n")
for _, k := range ic.Children {
dumpInlCall(inlcalls, k, ilevel+1)
}
}
func dumpInlCalls(inlcalls dwarf.InlCalls) {
for k, c := range inlcalls.Calls {
if c.Root {
dumpInlCall(inlcalls, k, 0)
}
}
}
func dumpInlVars(dwvars []*dwarf.Var) {
for i, dwv := range dwvars {
typ := "local"
if dwv.Abbrev == dwarf.DW_ABRV_PARAM_LOCLIST || dwv.Abbrev == dwarf.DW_ABRV_PARAM {
typ = "param"
}
ia := 0
if dwv.IsInAbstract {
ia = 1
}
base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
}
}
func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) {
for _, r := range par {
if rng.Start >= r.Start && rng.End <= r.End {
return true, ""
}
}
msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End)
for _, r := range par {
msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End)
}
msg += " }"
return false, msg
}
func rangesContainsAll(parent, child []dwarf.Range) (bool, string) {
for _, r := range child {
c, m := rangesContains(parent, r)
if !c {
return false, m
}
}
return true, ""
}
/ checkInlCall verifies that the PC ranges for inline info 'idx' are
/ enclosed/contained within the ranges of its parent inline (or if
/ this is a root/toplevel inline, checks that the ranges fall within
/ the extent of the top level function). A panic is issued if a
/ malformed range is found.
func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) {
/ Callee
ic := inlCalls.Calls[idx]
callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name
calleeRanges := ic.Ranges
/ Caller
caller := funcName
parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}}
if parentIdx != -1 {
pic := inlCalls.Calls[parentIdx]
caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name
parentRanges = pic.Ranges
}
/ Callee ranges contained in caller ranges?
c, m := rangesContainsAll(parentRanges, calleeRanges)
if !c {
base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m)
}
/ Now visit kids
for _, k := range ic.Children {
checkInlCall(funcName, inlCalls, funcSize, k, idx)
}
}
/ unifyCallRanges ensures that the ranges for a given inline
/ transitively include all of the ranges for its child inlines.
func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) {
ic := &inlcalls.Calls[idx]
for _, childIdx := range ic.Children {
/ First make sure child ranges are unified.
unifyCallRanges(inlcalls, childIdx)
/ Then merge child ranges into ranges for this inline.
cic := inlcalls.Calls[childIdx]
ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges)
}
}