blob: 4807da731c3687d255c54b797a3b3384d025c18b [file] [log] [blame]
// Copyright 2018 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 ssa
import (
"cmd/internal/obj"
"cmd/internal/src"
"fmt"
"sort"
)
func isPoorStatementOp(op Op) bool {
switch op {
// Note that Nilcheck often vanishes, but when it doesn't, you'd love to start the statement there
// so that a debugger-user sees the stop before the panic, and can examine the value.
case OpAddr, OpLocalAddr, OpOffPtr, OpStructSelect, OpConstBool, OpConst8, OpConst16, OpConst32, OpConst64, OpConst32F, OpConst64F:
return true
}
return false
}
// LosesStmtMark reports whether a prog with op as loses its statement mark on the way to DWARF.
// The attributes from some opcodes are lost in translation.
// TODO: this is an artifact of how funcpctab combines information for instructions at a single PC.
// Should try to fix it there.
func LosesStmtMark(as obj.As) bool {
// is_stmt does not work for these; it DOES for ANOP even though that generates no code.
return as == obj.APCDATA || as == obj.AFUNCDATA
}
// nextGoodStatementIndex returns an index at i or later that is believed
// to be a good place to start the statement for b. This decision is
// based on v's Op, the possibility of a better later operation, and
// whether the values following i are the same line as v.
// If a better statement index isn't found, then i is returned.
func nextGoodStatementIndex(v *Value, i int, b *Block) int {
// If the value is the last one in the block, too bad, it will have to do
// (this assumes that the value ordering vaguely corresponds to the source
// program execution order, which tends to be true directly after ssa is
// first built.
if i >= len(b.Values)-1 {
return i
}
// Only consider the likely-ephemeral/fragile opcodes expected to vanish in a rewrite.
if !isPoorStatementOp(v.Op) {
return i
}
// Look ahead to see what the line number is on the next thing that could be a boundary.
for j := i + 1; j < len(b.Values); j++ {
if b.Values[j].Pos.IsStmt() == src.PosNotStmt { // ignore non-statements
continue
}
if b.Values[j].Pos.Line() == v.Pos.Line() && v.Pos.SameFile(b.Values[j].Pos) {
return j
}
return i
}
return i
}
// notStmtBoundary indicates which value opcodes can never be a statement
// boundary because they don't correspond to a user's understanding of a
// statement boundary. Called from *Value.reset(), and *Func.newValue(),
// located here to keep all the statement boundary heuristics in one place.
// Note: *Value.reset() filters out OpCopy because of how that is used in
// rewrite.
func notStmtBoundary(op Op) bool {
switch op {
case OpCopy, OpPhi, OpVarKill, OpVarDef, OpUnknown, OpFwdRef, OpArg:
return true
}
return false
}
func (b *Block) FirstPossibleStmtValue() *Value {
for _, v := range b.Values {
if notStmtBoundary(v.Op) {
continue
}
return v
}
return nil
}
func flc(p src.XPos) string {
if p == src.NoXPos {
return "none"
}
return fmt.Sprintf("(%d):%d:%d", p.FileIndex(), p.Line(), p.Col())
}
type fileAndPair struct {
f int32
lp lineRange
}
type fileAndPairs []fileAndPair
func (fap fileAndPairs) Len() int {
return len(fap)
}
func (fap fileAndPairs) Less(i, j int) bool {
return fap[i].f < fap[j].f
}
func (fap fileAndPairs) Swap(i, j int) {
fap[i], fap[j] = fap[j], fap[i]
}
// -d=ssa/number_lines/stats=1 (that bit) for line and file distribution statistics
// -d=ssa/number_lines/debug for information about why particular values are marked as statements.
func numberLines(f *Func) {
po := f.Postorder()
endlines := make(map[ID]src.XPos)
ranges := make(map[int]lineRange)
note := func(p src.XPos) {
line := uint32(p.Line())
i := int(p.FileIndex())
lp, found := ranges[i]
change := false
if line < lp.first || !found {
lp.first = line
change = true
}
if line > lp.last {
lp.last = line
change = true
}
if change {
ranges[i] = lp
}
}
// Visit in reverse post order so that all non-loop predecessors come first.
for j := len(po) - 1; j >= 0; j-- {
b := po[j]
// Find the first interesting position and check to see if it differs from any predecessor
firstPos := src.NoXPos
firstPosIndex := -1
if b.Pos.IsStmt() != src.PosNotStmt {
note(b.Pos)
}
for i := 0; i < len(b.Values); i++ {
v := b.Values[i]
if v.Pos.IsStmt() != src.PosNotStmt {
note(v.Pos)
// skip ahead to better instruction for this line if possible
i = nextGoodStatementIndex(v, i, b)
v = b.Values[i]
firstPosIndex = i
firstPos = v.Pos
v.Pos = firstPos.WithDefaultStmt() // default to default
break
}
}
if firstPosIndex == -1 { // Effectively empty block, check block's own Pos, consider preds.
if b.Pos.IsStmt() != src.PosNotStmt {
b.Pos = b.Pos.WithIsStmt()
endlines[b.ID] = b.Pos
if f.pass.debug > 0 {
fmt.Printf("Mark stmt effectively-empty-block %s %s %s\n", f.Name, b, flc(b.Pos))
}
continue
}
line := src.NoXPos
for _, p := range b.Preds {
pbi := p.Block().ID
if endlines[pbi] != line {
if line == src.NoXPos {
line = endlines[pbi]
continue
} else {
line = src.NoXPos
break
}
}
}
endlines[b.ID] = line
continue
}
// check predecessors for any difference; if firstPos differs, then it is a boundary.
if len(b.Preds) == 0 { // Don't forget the entry block
b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
if f.pass.debug > 0 {
fmt.Printf("Mark stmt entry-block %s %s %s %s\n", f.Name, b, b.Values[firstPosIndex], flc(firstPos))
}
} else { // differing pred
for _, p := range b.Preds {
pbi := p.Block().ID
if endlines[pbi].Line() != firstPos.Line() || !endlines[pbi].SameFile(firstPos) {
b.Values[firstPosIndex].Pos = firstPos.WithIsStmt()
if f.pass.debug > 0 {
fmt.Printf("Mark stmt differing-pred %s %s %s %s, different=%s ending %s\n",
f.Name, b, b.Values[firstPosIndex], flc(firstPos), p.Block(), flc(endlines[pbi]))
}
break
}
}
}
// iterate forward setting each new (interesting) position as a statement boundary.
for i := firstPosIndex + 1; i < len(b.Values); i++ {
v := b.Values[i]
if v.Pos.IsStmt() == src.PosNotStmt {
continue
}
note(v.Pos)
// skip ahead if possible
i = nextGoodStatementIndex(v, i, b)
v = b.Values[i]
if v.Pos.Line() != firstPos.Line() || !v.Pos.SameFile(firstPos) {
if f.pass.debug > 0 {
fmt.Printf("Mark stmt new line %s %s %s %s prev pos = %s\n", f.Name, b, v, flc(v.Pos), flc(firstPos))
}
firstPos = v.Pos
v.Pos = v.Pos.WithIsStmt()
} else {
v.Pos = v.Pos.WithDefaultStmt()
}
}
if b.Pos.IsStmt() != src.PosNotStmt && (b.Pos.Line() != firstPos.Line() || !b.Pos.SameFile(firstPos)) {
if f.pass.debug > 0 {
fmt.Printf("Mark stmt end of block differs %s %s %s prev pos = %s\n", f.Name, b, flc(b.Pos), flc(firstPos))
}
b.Pos = b.Pos.WithIsStmt()
firstPos = b.Pos
}
endlines[b.ID] = firstPos
}
if f.pass.stats&1 != 0 {
// Report summary statistics on the shape of the sparse map about to be constructed
// TODO use this information to make sparse maps faster.
var entries fileAndPairs
for k, v := range ranges {
entries = append(entries, fileAndPair{int32(k), v})
}
sort.Sort(entries)
total := uint64(0) // sum over files of maxline(file) - minline(file)
maxfile := int32(0) // max(file indices)
minline := uint32(0xffffffff) // min over files of minline(file)
maxline := uint32(0) // max over files of maxline(file)
for _, v := range entries {
if f.pass.stats > 1 {
f.LogStat("file", v.f, "low", v.lp.first, "high", v.lp.last)
}
total += uint64(v.lp.last - v.lp.first)
if maxfile < v.f {
maxfile = v.f
}
if minline > v.lp.first {
minline = v.lp.first
}
if maxline < v.lp.last {
maxline = v.lp.last
}
}
f.LogStat("SUM_LINE_RANGE", total, "MAXMIN_LINE_RANGE", maxline-minline, "MAXFILE", maxfile, "NFILES", len(entries))
}
// cachedLineStarts is an empty sparse map for values that are included within ranges.
f.cachedLineStarts = newXposmap(ranges)
}