// 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 gc import ( "cmd/internal/dwarf" "cmd/internal/obj" "cmd/internal/src" "sort" "strings" ) // 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, fn *Node, dwVars []*dwarf.Var) dwarf.InlCalls { var inlcalls dwarf.InlCalls if Debug_gendwarfinl != 0 { 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 { sort.Sort(byClassThenName(sl)) var m map[varPos]int if ii == 0 { if !fnsym.WasInlined() { for j := 0; j < len(sl); j++ { sl[j].ChildIndex = int32(j) } continue } m = makePreinlineDclMap(fnsym) } else { ifnlsym := 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 j := 0; j < len(sl); j++ { canonName := unversion(sl[j].Name) vp := varPos{ DeclName: canonName, DeclFile: sl[j].DeclFile, DeclLine: sl[j].DeclLine, DeclCol: sl[j].DeclCol, } synthesized := strings.HasPrefix(sl[j].Name, "~r") || canonName == "_" if idx, found := m[vp]; found { sl[j].ChildIndex = int32(idx) sl[j].IsInAbstract = !synthesized sl[j].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 ("_"). sl[j].ChildIndex = int32(synthCount) synthCount += 1 } } } // Make a second pass through the progs to compute PC ranges for // the various inlined calls. curii := -1 var crange *dwarf.Range 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 } else { // Close out the current range endRange(crange, prevp) // Begin new range crange = beginRange(inlcalls.Calls, p, ii, imap) curii = ii } } if prevp != nil { endRange(crange, prevp) } // Debugging if Debug_gendwarfinl != 0 { dumpInlCalls(inlcalls) dumpInlVars(dwVars) } 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 genAbstractFunc(fn *obj.LSym) { ifn := Ctxt.DwFixups.GetPrecursorFunc(fn) if ifn == nil { Ctxt.Diag("failed to locate precursor fn for %v", fn) return } if Debug_gendwarfinl != 0 { Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name) } Ctxt.DwarfAbstractFunc(ifn, fn, myimportpath) } // 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. func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int { dcl := preInliningDcls(fnsym) m := make(map[varPos]int) for i := 0; i < len(dcl); i++ { n := dcl[i] pos := Ctxt.InnermostPos(n.Pos) vp := varPos{ DeclName: unversion(n.Sym.Name), DeclFile: pos.Base().SymFilename(), DeclLine: pos.Line(), DeclCol: pos.Col(), } if _, found := m[vp]; found { Fatalf("child dcl collision on symbol %s within %v\n", n.Sym.Name, fnsym.Name) } 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 := Ctxt.InlTree.Parent(inlIdx) if parInlIdx >= 0 { parCallIdx = insertInlCall(dwcalls, parInlIdx, imap) } // Create new entry for this inline inlinedFn := Ctxt.InlTree.InlinedFunction(int(inlIdx)) callXPos := Ctxt.InlTree.CallPos(int(inlIdx)) absFnSym := Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn) pb := Ctxt.PosTable.Pos(callXPos).Base() callFileSym := 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 := Ctxt.PosTable.Pos(xpos) if b := pos.Base(); b != nil { ii := b.InliningIndex() if ii >= 0 { return ii } } return -1 } func endRange(crange *dwarf.Range, p *obj.Prog) { if crange == nil { return } crange.End = p.Pc } func beginRange(calls []dwarf.InlCall, p *obj.Prog, ii int, imap map[int]int) *dwarf.Range { if ii == -1 { return nil } callIdx, found := imap[ii] if !found { Fatalf("internal error: can't find inlIndex %d in imap for prog at %d\n", ii, p.Pc) } call := &calls[callIdx] // Set up range and append to correct inlined call call.Ranges = append(call.Ranges, dwarf.Range{Start: p.Pc, End: -1}) return &call.Ranges[len(call.Ranges)-1] } func cmpDwarfVar(a, b *dwarf.Var) bool { // named before artificial aart := 0 if strings.HasPrefix(a.Name, "~r") { aart = 1 } bart := 0 if strings.HasPrefix(b.Name, "~r") { bart = 1 } if aart != bart { return aart < bart } // otherwise sort by name return a.Name < b.Name } // byClassThenName implements sort.Interface for []*dwarf.Var using cmpDwarfVar. type byClassThenName []*dwarf.Var func (s byClassThenName) Len() int { return len(s) } func (s byClassThenName) Less(i, j int) bool { return cmpDwarfVar(s[i], s[j]) } func (s byClassThenName) Swap(i, j int) { s[i], s[j] = s[j], s[i] } func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) { for i := 0; i < ilevel; i += 1 { Ctxt.Logf(" ") } ic := inlcalls.Calls[idx] callee := Ctxt.InlTree.InlinedFunction(ic.InlIndex) Ctxt.Logf(" %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name) for _, f := range ic.InlVars { Ctxt.Logf(" %v", f.Name) } Ctxt.Logf(" ) C: (") for _, k := range ic.Children { Ctxt.Logf(" %v", k) } Ctxt.Logf(" ) R:") for _, r := range ic.Ranges { Ctxt.Logf(" [%d,%d)", r.Start, r.End) } Ctxt.Logf("\n") for _, k := range ic.Children { dumpInlCall(inlcalls, k, ilevel+1) } } func dumpInlCalls(inlcalls dwarf.InlCalls) { n := len(inlcalls.Calls) for k := 0; k < n; k += 1 { if inlcalls.Calls[k].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 } Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ) } }