1// Copyright 2018 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 ssa
6
7import (
8	"cmd/internal/src"
9	"fmt"
10	"sort"
11)
12
13func isPoorStatementOp(op Op) bool {
14	switch op {
15	// Note that Nilcheck often vanishes, but when it doesn't, you'd love to start the statement there
16	// so that a debugger-user sees the stop before the panic, and can examine the value.
17	case OpAddr, OpLocalAddr, OpOffPtr, OpStructSelect, OpPhi, OpITab, OpIData,
18		OpIMake, OpStringMake, OpSliceMake, OpStructMake0, OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4,
19		OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F, OpSB, OpSP,
20		OpArgIntReg, OpArgFloatReg:
21		return true
22	}
23	return false
24}
25
26// nextGoodStatementIndex returns an index at i or later that is believed
27// to be a good place to start the statement for b.  This decision is
28// based on v's Op, the possibility of a better later operation, and
29// whether the values following i are the same line as v.
30// If a better statement index isn't found, then i is returned.
31func nextGoodStatementIndex(v *Value, i int, b *Block) int {
32	// If the value is the last one in the block, too bad, it will have to do
33	// (this assumes that the value ordering vaguely corresponds to the source
34	// program execution order, which tends to be true directly after ssa is
35	// first built).
36	if i >= len(b.Values)-1 {
37		return i
38	}
39	// Skip the likely-ephemeral/fragile opcodes expected to vanish in a rewrite.
40	if !isPoorStatementOp(v.Op) {
41		return i
42	}
43	// Look ahead to see what the line number is on the next thing that could be a boundary.
44	for j := i + 1; j < len(b.Values); j++ {
45		u := b.Values[j]
46		if u.Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
47			continue
48		}
49		if u.Pos.SameFileAndLine(v.Pos) {
50			if isPoorStatementOp(u.Op) {
51				continue // Keep looking, this is also not a good statement op
52			}
53			return j
54		}
55		return i
56	}
57	return i
58}
59
60// notStmtBoundary reports whether a value with opcode op can never be a statement
61// boundary. Such values don't correspond to a user's understanding of a
62// statement boundary.
63func notStmtBoundary(op Op) bool {
64	switch op {
65	case OpCopy, OpPhi, OpVarDef, OpVarLive, OpUnknown, OpFwdRef, OpArg, OpArgIntReg, OpArgFloatReg:
66		return true
67	}
68	return false
69}
70
71func (b *Block) FirstPossibleStmtValue() *Value {
72	for _, v := range b.Values {
73		if notStmtBoundary(v.Op) {
74			continue
75		}
76		return v
77	}
78	return nil
79}
80
81func flc(p src.XPos) string {
82	if p == src.NoXPos {
83		return "none"
84	}
85	return fmt.Sprintf("(%d):%d:%d", p.FileIndex(), p.Line(), p.Col())
86}
87
88type fileAndPair struct {
89	f  int32
90	lp lineRange
91}
92
93type fileAndPairs []fileAndPair
94
95func (fap fileAndPairs) Len() int {
96	return len(fap)
97}
98func (fap fileAndPairs) Less(i, j int) bool {
99	return fap[i].f < fap[j].f
100}
101func (fap fileAndPairs) Swap(i, j int) {
102	fap[i], fap[j] = fap[j], fap[i]
103}
104
105// -d=ssa/number_lines/stats=1 (that bit) for line and file distribution statistics
106// -d=ssa/number_lines/debug for information about why particular values are marked as statements.
107func numberLines(f *Func) {
108	po := f.Postorder()
109	endlines := make(map[ID]src.XPos)
110	ranges := make(map[int]lineRange)
111	note := func(p src.XPos) {
112		line := uint32(p.Line())
113		i := int(p.FileIndex())
114		lp, found := ranges[i]
115		change := false
116		if line < lp.first || !found {
117			lp.first = line
118			change = true
119		}
120		if line > lp.last {
121			lp.last = line
122			change = true
123		}
124		if change {
125			ranges[i] = lp
126		}
127	}
128
129	// Visit in reverse post order so that all non-loop predecessors come first.
130	for j := len(po) - 1; j >= 0; j-- {
131		b := po[j]
132		// Find the first interesting position and check to see if it differs from any predecessor
133		firstPos := src.NoXPos
134		firstPosIndex := -1
135		if b.Pos.IsStmt() != src.PosNotStmt {
136			note(b.Pos)
137		}
138		for i := 0; i < len(b.Values); i++ {
139			v := b.Values[i]
140			if v.Pos.IsStmt() != src.PosNotStmt {
141				note(v.Pos)
142				// skip ahead to better instruction for this line if possible
143				i = nextGoodStatementIndex(v, i, b)
144				v = b.Values[i]
145				firstPosIndex = i
146				firstPos = v.Pos
147				v.Pos = firstPos.WithDefaultStmt() // default to default
148				break
149			}
150		}
151
152		if firstPosIndex == -1 { // Effectively empty block, check block's own Pos, consider preds.
153			line := src.NoXPos
154			for _, p := range b.Preds {
155				pbi := p.Block().ID
156				if !endlines[pbi].SameFileAndLine(line) {
157					if line == src.NoXPos {
158						line = endlines[pbi]
159						continue
160					} else {
161						line = src.NoXPos
162						break
163					}
164
165				}
166			}
167			// If the block has no statement itself and is effectively empty, tag it w/ predecessor(s) but not as a statement
168			if b.Pos.IsStmt() == src.PosNotStmt {
169				b.Pos = line
170				endlines[b.ID] = line
171				continue
172			}
173			// If the block differs from its predecessors, mark it as a statement
174			if line == src.NoXPos || !line.SameFileAndLine(b.Pos) {
175				b.Pos = b.Pos.WithIsStmt()
176				if f.pass.debug > 0 {
177					fmt.Printf("Mark stmt effectively-empty-block %s %s %s\n", f.Name, b, flc(b.Pos))
178				}
179			}
180			endlines[b.ID] = b.Pos
181			continue
182		}
183		// check predecessors for any difference; if firstPos differs, then it is a boundary.
184		if len(b.Preds) == 0 { // Don't forget the entry block
185			b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
186			if f.pass.debug > 0 {
187				fmt.Printf("Mark stmt entry-block %s %s %s %s\n", f.Name, b, b.Values[firstPosIndex], flc(firstPos))
188			}
189		} else { // differing pred
190			for _, p := range b.Preds {
191				pbi := p.Block().ID
192				if !endlines[pbi].SameFileAndLine(firstPos) {
193					b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
194					if f.pass.debug > 0 {
195						fmt.Printf("Mark stmt differing-pred %s %s %s %s, different=%s ending %s\n",
196							f.Name, b, b.Values[firstPosIndex], flc(firstPos), p.Block(), flc(endlines[pbi]))
197					}
198					break
199				}
200			}
201		}
202		// iterate forward setting each new (interesting) position as a statement boundary.
203		for i := firstPosIndex + 1; i < len(b.Values); i++ {
204			v := b.Values[i]
205			if v.Pos.IsStmt() == src.PosNotStmt {
206				continue
207			}
208			note(v.Pos)
209			// skip ahead if possible
210			i = nextGoodStatementIndex(v, i, b)
211			v = b.Values[i]
212			if !v.Pos.SameFileAndLine(firstPos) {
213				if f.pass.debug > 0 {
214					fmt.Printf("Mark stmt new line %s %s %s %s prev pos = %s\n", f.Name, b, v, flc(v.Pos), flc(firstPos))
215				}
216				firstPos = v.Pos
217				v.Pos = v.Pos.WithIsStmt()
218			} else {
219				v.Pos = v.Pos.WithDefaultStmt()
220			}
221		}
222		if b.Pos.IsStmt() != src.PosNotStmt && !b.Pos.SameFileAndLine(firstPos) {
223			if f.pass.debug > 0 {
224				fmt.Printf("Mark stmt end of block differs %s %s %s prev pos = %s\n", f.Name, b, flc(b.Pos), flc(firstPos))
225			}
226			b.Pos = b.Pos.WithIsStmt()
227			firstPos = b.Pos
228		}
229		endlines[b.ID] = firstPos
230	}
231	if f.pass.stats&1 != 0 {
232		// Report summary statistics on the shape of the sparse map about to be constructed
233		// TODO use this information to make sparse maps faster.
234		var entries fileAndPairs
235		for k, v := range ranges {
236			entries = append(entries, fileAndPair{int32(k), v})
237		}
238		sort.Sort(entries)
239		total := uint64(0)            // sum over files of maxline(file) - minline(file)
240		maxfile := int32(0)           // max(file indices)
241		minline := uint32(0xffffffff) // min over files of minline(file)
242		maxline := uint32(0)          // max over files of maxline(file)
243		for _, v := range entries {
244			if f.pass.stats > 1 {
245				f.LogStat("file", v.f, "low", v.lp.first, "high", v.lp.last)
246			}
247			total += uint64(v.lp.last - v.lp.first)
248			if maxfile < v.f {
249				maxfile = v.f
250			}
251			if minline > v.lp.first {
252				minline = v.lp.first
253			}
254			if maxline < v.lp.last {
255				maxline = v.lp.last
256			}
257		}
258		f.LogStat("SUM_LINE_RANGE", total, "MAXMIN_LINE_RANGE", maxline-minline, "MAXFILE", maxfile, "NFILES", len(entries))
259	}
260	// cachedLineStarts is an empty sparse map for values that are included within ranges.
261	f.cachedLineStarts = newXposmap(ranges)
262}
263