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