| // 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, OpPhi, OpITab, OpIData, |
| OpIMake, OpStringMake, OpSliceMake, OpStructMake0, OpStructMake1, OpStructMake2, OpStructMake3, OpStructMake4, |
| 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 |
| } |
| // Skip 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++ { |
| u := b.Values[j] |
| if u.Pos.IsStmt() == src.PosNotStmt { // ignore non-statements |
| continue |
| } |
| if u.Pos.SameFileAndLine(v.Pos) { |
| if isPoorStatementOp(u.Op) { |
| continue // Keep looking, this is also not a good statement op |
| } |
| 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, OpVarLive, 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. |
| line := src.NoXPos |
| for _, p := range b.Preds { |
| pbi := p.Block().ID |
| if !endlines[pbi].SameFileAndLine(line) { |
| if line == src.NoXPos { |
| line = endlines[pbi] |
| continue |
| } else { |
| line = src.NoXPos |
| break |
| } |
| |
| } |
| } |
| // If the block has no statement itself and is effectively empty, tag it w/ predecessor(s) but not as a statement |
| if b.Pos.IsStmt() == src.PosNotStmt { |
| b.Pos = line |
| endlines[b.ID] = line |
| continue |
| } |
| // If the block differs from its predecessors, mark it as a statement |
| if line == src.NoXPos || !line.SameFileAndLine(b.Pos) { |
| b.Pos = b.Pos.WithIsStmt() |
| if f.pass.debug > 0 { |
| fmt.Printf("Mark stmt effectively-empty-block %s %s %s\n", f.Name, b, flc(b.Pos)) |
| } |
| } |
| endlines[b.ID] = b.Pos |
| 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].SameFileAndLine(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.SameFileAndLine(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.SameFileAndLine(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) |
| } |