1// Copyright 2017 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package dwarfgen
6
7import (
8	"fmt"
9	"strings"
10
11	"cmd/compile/internal/base"
12	"cmd/compile/internal/ir"
13	"cmd/internal/dwarf"
14	"cmd/internal/obj"
15	"cmd/internal/src"
16)
17
18// To identify variables by original source position.
19type varPos struct {
20	DeclName string
21	DeclFile string
22	DeclLine uint
23	DeclCol  uint
24}
25
26// This is the main entry point for collection of raw material to
27// drive generation of DWARF "inlined subroutine" DIEs. See proposal
28// 22080 for more details and background info.
29func assembleInlines(fnsym *obj.LSym, dwVars []*dwarf.Var) dwarf.InlCalls {
30	var inlcalls dwarf.InlCalls
31
32	if base.Debug.DwarfInl != 0 {
33		base.Ctxt.Logf("assembling DWARF inlined routine info for %v\n", fnsym.Name)
34	}
35
36	// This maps inline index (from Ctxt.InlTree) to index in inlcalls.Calls
37	imap := make(map[int]int)
38
39	// Walk progs to build up the InlCalls data structure
40	var prevpos src.XPos
41	for p := fnsym.Func().Text; p != nil; p = p.Link {
42		if p.Pos == prevpos {
43			continue
44		}
45		ii := posInlIndex(p.Pos)
46		if ii >= 0 {
47			insertInlCall(&inlcalls, ii, imap)
48		}
49		prevpos = p.Pos
50	}
51
52	// This is used to partition DWARF vars by inline index. Vars not
53	// produced by the inliner will wind up in the vmap[0] entry.
54	vmap := make(map[int32][]*dwarf.Var)
55
56	// Now walk the dwarf vars and partition them based on whether they
57	// were produced by the inliner (dwv.InlIndex > 0) or were original
58	// vars/params from the function (dwv.InlIndex == 0).
59	for _, dwv := range dwVars {
60
61		vmap[dwv.InlIndex] = append(vmap[dwv.InlIndex], dwv)
62
63		// Zero index => var was not produced by an inline
64		if dwv.InlIndex == 0 {
65			continue
66		}
67
68		// Look up index in our map, then tack the var in question
69		// onto the vars list for the correct inlined call.
70		ii := int(dwv.InlIndex) - 1
71		idx, ok := imap[ii]
72		if !ok {
73			// We can occasionally encounter a var produced by the
74			// inliner for which there is no remaining prog; add a new
75			// entry to the call list in this scenario.
76			idx = insertInlCall(&inlcalls, ii, imap)
77		}
78		inlcalls.Calls[idx].InlVars =
79			append(inlcalls.Calls[idx].InlVars, dwv)
80	}
81
82	// Post process the map above to assign child indices to vars.
83	//
84	// A given variable is treated differently depending on whether it
85	// is part of the top-level function (ii == 0) or if it was
86	// produced as a result of an inline (ii != 0).
87	//
88	// If a variable was not produced by an inline and its containing
89	// function was not inlined, then we just assign an ordering of
90	// based on variable name.
91	//
92	// If a variable was not produced by an inline and its containing
93	// function was inlined, then we need to assign a child index
94	// based on the order of vars in the abstract function (in
95	// addition, those vars that don't appear in the abstract
96	// function, such as "~r1", are flagged as such).
97	//
98	// If a variable was produced by an inline, then we locate it in
99	// the pre-inlining decls for the target function and assign child
100	// index accordingly.
101	for ii, sl := range vmap {
102		var m map[varPos]int
103		if ii == 0 {
104			if !fnsym.WasInlined() {
105				for j, v := range sl {
106					v.ChildIndex = int32(j)
107				}
108				continue
109			}
110			m = makePreinlineDclMap(fnsym)
111		} else {
112			ifnlsym := base.Ctxt.InlTree.InlinedFunction(int(ii - 1))
113			m = makePreinlineDclMap(ifnlsym)
114		}
115
116		// Here we assign child indices to variables based on
117		// pre-inlined decls, and set the "IsInAbstract" flag
118		// appropriately. In addition: parameter and local variable
119		// names are given "middle dot" version numbers as part of the
120		// writing them out to export data (see issue 4326). If DWARF
121		// inlined routine generation is turned on, we want to undo
122		// this versioning, since DWARF variables in question will be
123		// parented by the inlined routine and not the top-level
124		// caller.
125		synthCount := len(m)
126		for _, v := range sl {
127			vp := varPos{
128				DeclName: v.Name,
129				DeclFile: v.DeclFile,
130				DeclLine: v.DeclLine,
131				DeclCol:  v.DeclCol,
132			}
133			synthesized := strings.HasPrefix(v.Name, "~") || v.Name == "_"
134			if idx, found := m[vp]; found {
135				v.ChildIndex = int32(idx)
136				v.IsInAbstract = !synthesized
137			} else {
138				// Variable can't be found in the pre-inline dcl list.
139				// In the top-level case (ii=0) this can happen
140				// because a composite variable was split into pieces,
141				// and we're looking at a piece. We can also see
142				// return temps (~r%d) that were created during
143				// lowering, or unnamed params ("_").
144				v.ChildIndex = int32(synthCount)
145				synthCount++
146			}
147		}
148	}
149
150	// Make a second pass through the progs to compute PC ranges for
151	// the various inlined calls.
152	start := int64(-1)
153	curii := -1
154	var prevp *obj.Prog
155	for p := fnsym.Func().Text; p != nil; prevp, p = p, p.Link {
156		if prevp != nil && p.Pos == prevp.Pos {
157			continue
158		}
159		ii := posInlIndex(p.Pos)
160		if ii == curii {
161			continue
162		}
163		// Close out the current range
164		if start != -1 {
165			addRange(inlcalls.Calls, start, p.Pc, curii, imap)
166		}
167		// Begin new range
168		start = p.Pc
169		curii = ii
170	}
171	if start != -1 {
172		addRange(inlcalls.Calls, start, fnsym.Size, curii, imap)
173	}
174
175	// Issue 33188: if II foo is a child of II bar, then ensure that
176	// bar's ranges include the ranges of foo (the loop above will produce
177	// disjoint ranges).
178	for k, c := range inlcalls.Calls {
179		if c.Root {
180			unifyCallRanges(inlcalls, k)
181		}
182	}
183
184	// Debugging
185	if base.Debug.DwarfInl != 0 {
186		dumpInlCalls(inlcalls)
187		dumpInlVars(dwVars)
188	}
189
190	// Perform a consistency check on inlined routine PC ranges
191	// produced by unifyCallRanges above. In particular, complain in
192	// cases where you have A -> B -> C (e.g. C is inlined into B, and
193	// B is inlined into A) and the ranges for B are not enclosed
194	// within the ranges for A, or C within B.
195	for k, c := range inlcalls.Calls {
196		if c.Root {
197			checkInlCall(fnsym.Name, inlcalls, fnsym.Size, k, -1)
198		}
199	}
200
201	return inlcalls
202}
203
204// Secondary hook for DWARF inlined subroutine generation. This is called
205// late in the compilation when it is determined that we need an
206// abstract function DIE for an inlined routine imported from a
207// previously compiled package.
208func AbstractFunc(fn *obj.LSym) {
209	ifn := base.Ctxt.DwFixups.GetPrecursorFunc(fn)
210	if ifn == nil {
211		base.Ctxt.Diag("failed to locate precursor fn for %v", fn)
212		return
213	}
214	_ = ifn.(*ir.Func)
215	if base.Debug.DwarfInl != 0 {
216		base.Ctxt.Logf("DwarfAbstractFunc(%v)\n", fn.Name)
217	}
218	base.Ctxt.DwarfAbstractFunc(ifn, fn)
219}
220
221// Given a function that was inlined as part of the compilation, dig
222// up the pre-inlining DCL list for the function and create a map that
223// supports lookup of pre-inline dcl index, based on variable
224// position/name. NB: the recipe for computing variable pos/file/line
225// needs to be kept in sync with the similar code in gc.createSimpleVars
226// and related functions.
227func makePreinlineDclMap(fnsym *obj.LSym) map[varPos]int {
228	dcl := preInliningDcls(fnsym)
229	m := make(map[varPos]int)
230	for i, n := range dcl {
231		pos := base.Ctxt.InnermostPos(n.Pos())
232		vp := varPos{
233			DeclName: n.Sym().Name,
234			DeclFile: pos.RelFilename(),
235			DeclLine: pos.RelLine(),
236			DeclCol:  pos.RelCol(),
237		}
238		if _, found := m[vp]; found {
239			// 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.
240			continue
241		}
242		m[vp] = i
243	}
244	return m
245}
246
247func insertInlCall(dwcalls *dwarf.InlCalls, inlIdx int, imap map[int]int) int {
248	callIdx, found := imap[inlIdx]
249	if found {
250		return callIdx
251	}
252
253	// Haven't seen this inline yet. Visit parent of inline if there
254	// is one. We do this first so that parents appear before their
255	// children in the resulting table.
256	parCallIdx := -1
257	parInlIdx := base.Ctxt.InlTree.Parent(inlIdx)
258	if parInlIdx >= 0 {
259		parCallIdx = insertInlCall(dwcalls, parInlIdx, imap)
260	}
261
262	// Create new entry for this inline
263	inlinedFn := base.Ctxt.InlTree.InlinedFunction(inlIdx)
264	callXPos := base.Ctxt.InlTree.CallPos(inlIdx)
265	callPos := base.Ctxt.InnermostPos(callXPos)
266	absFnSym := base.Ctxt.DwFixups.AbsFuncDwarfSym(inlinedFn)
267	ic := dwarf.InlCall{
268		InlIndex:  inlIdx,
269		CallPos:   callPos,
270		AbsFunSym: absFnSym,
271		Root:      parCallIdx == -1,
272	}
273	dwcalls.Calls = append(dwcalls.Calls, ic)
274	callIdx = len(dwcalls.Calls) - 1
275	imap[inlIdx] = callIdx
276
277	if parCallIdx != -1 {
278		// Add this inline to parent's child list
279		dwcalls.Calls[parCallIdx].Children = append(dwcalls.Calls[parCallIdx].Children, callIdx)
280	}
281
282	return callIdx
283}
284
285// Given a src.XPos, return its associated inlining index if it
286// corresponds to something created as a result of an inline, or -1 if
287// there is no inline info. Note that the index returned will refer to
288// the deepest call in the inlined stack, e.g. if you have "A calls B
289// calls C calls D" and all three callees are inlined (B, C, and D),
290// the index for a node from the inlined body of D will refer to the
291// call to D from C. Whew.
292func posInlIndex(xpos src.XPos) int {
293	pos := base.Ctxt.PosTable.Pos(xpos)
294	if b := pos.Base(); b != nil {
295		ii := b.InliningIndex()
296		if ii >= 0 {
297			return ii
298		}
299	}
300	return -1
301}
302
303func addRange(calls []dwarf.InlCall, start, end int64, ii int, imap map[int]int) {
304	if start == -1 {
305		panic("bad range start")
306	}
307	if end == -1 {
308		panic("bad range end")
309	}
310	if ii == -1 {
311		return
312	}
313	if start == end {
314		return
315	}
316	// Append range to correct inlined call
317	callIdx, found := imap[ii]
318	if !found {
319		base.Fatalf("can't find inlIndex %d in imap for prog at %d\n", ii, start)
320	}
321	call := &calls[callIdx]
322	call.Ranges = append(call.Ranges, dwarf.Range{Start: start, End: end})
323}
324
325func dumpInlCall(inlcalls dwarf.InlCalls, idx, ilevel int) {
326	for i := 0; i < ilevel; i++ {
327		base.Ctxt.Logf("  ")
328	}
329	ic := inlcalls.Calls[idx]
330	callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex)
331	base.Ctxt.Logf("  %d: II:%d (%s) V: (", idx, ic.InlIndex, callee.Name)
332	for _, f := range ic.InlVars {
333		base.Ctxt.Logf(" %v", f.Name)
334	}
335	base.Ctxt.Logf(" ) C: (")
336	for _, k := range ic.Children {
337		base.Ctxt.Logf(" %v", k)
338	}
339	base.Ctxt.Logf(" ) R:")
340	for _, r := range ic.Ranges {
341		base.Ctxt.Logf(" [%d,%d)", r.Start, r.End)
342	}
343	base.Ctxt.Logf("\n")
344	for _, k := range ic.Children {
345		dumpInlCall(inlcalls, k, ilevel+1)
346	}
347
348}
349
350func dumpInlCalls(inlcalls dwarf.InlCalls) {
351	for k, c := range inlcalls.Calls {
352		if c.Root {
353			dumpInlCall(inlcalls, k, 0)
354		}
355	}
356}
357
358func dumpInlVars(dwvars []*dwarf.Var) {
359	for i, dwv := range dwvars {
360		typ := "local"
361		if dwv.Tag == dwarf.DW_TAG_formal_parameter {
362			typ = "param"
363		}
364		ia := 0
365		if dwv.IsInAbstract {
366			ia = 1
367		}
368		base.Ctxt.Logf("V%d: %s CI:%d II:%d IA:%d %s\n", i, dwv.Name, dwv.ChildIndex, dwv.InlIndex-1, ia, typ)
369	}
370}
371
372func rangesContains(par []dwarf.Range, rng dwarf.Range) (bool, string) {
373	for _, r := range par {
374		if rng.Start >= r.Start && rng.End <= r.End {
375			return true, ""
376		}
377	}
378	msg := fmt.Sprintf("range [%d,%d) not contained in {", rng.Start, rng.End)
379	for _, r := range par {
380		msg += fmt.Sprintf(" [%d,%d)", r.Start, r.End)
381	}
382	msg += " }"
383	return false, msg
384}
385
386func rangesContainsAll(parent, child []dwarf.Range) (bool, string) {
387	for _, r := range child {
388		c, m := rangesContains(parent, r)
389		if !c {
390			return false, m
391		}
392	}
393	return true, ""
394}
395
396// checkInlCall verifies that the PC ranges for inline info 'idx' are
397// enclosed/contained within the ranges of its parent inline (or if
398// this is a root/toplevel inline, checks that the ranges fall within
399// the extent of the top level function). A panic is issued if a
400// malformed range is found.
401func checkInlCall(funcName string, inlCalls dwarf.InlCalls, funcSize int64, idx, parentIdx int) {
402
403	// Callee
404	ic := inlCalls.Calls[idx]
405	callee := base.Ctxt.InlTree.InlinedFunction(ic.InlIndex).Name
406	calleeRanges := ic.Ranges
407
408	// Caller
409	caller := funcName
410	parentRanges := []dwarf.Range{dwarf.Range{Start: int64(0), End: funcSize}}
411	if parentIdx != -1 {
412		pic := inlCalls.Calls[parentIdx]
413		caller = base.Ctxt.InlTree.InlinedFunction(pic.InlIndex).Name
414		parentRanges = pic.Ranges
415	}
416
417	// Callee ranges contained in caller ranges?
418	c, m := rangesContainsAll(parentRanges, calleeRanges)
419	if !c {
420		base.Fatalf("** malformed inlined routine range in %s: caller %s callee %s II=%d %s\n", funcName, caller, callee, idx, m)
421	}
422
423	// Now visit kids
424	for _, k := range ic.Children {
425		checkInlCall(funcName, inlCalls, funcSize, k, idx)
426	}
427}
428
429// unifyCallRanges ensures that the ranges for a given inline
430// transitively include all of the ranges for its child inlines.
431func unifyCallRanges(inlcalls dwarf.InlCalls, idx int) {
432	ic := &inlcalls.Calls[idx]
433	for _, childIdx := range ic.Children {
434		// First make sure child ranges are unified.
435		unifyCallRanges(inlcalls, childIdx)
436
437		// Then merge child ranges into ranges for this inline.
438		cic := inlcalls.Calls[childIdx]
439		ic.Ranges = dwarf.MergeRanges(ic.Ranges, cic.Ranges)
440	}
441}
442