diff options
Diffstat (limited to 'src/cmd/compile/internal/dwarfgen/dwinl.go')
-rw-r--r-- | src/cmd/compile/internal/dwarfgen/dwinl.go | 454 |
1 files changed, 454 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/dwarfgen/dwinl.go b/src/cmd/compile/internal/dwarfgen/dwinl.go new file mode 100644 index 0000000..c785e06 --- /dev/null +++ b/src/cmd/compile/internal/dwarfgen/dwinl.go @@ -0,0 +1,454 @@ +// 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) + } +} |