github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/internal/src/pos.go (about) 1 // Copyright 2016 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 // This file implements the encoding of source positions. 6 7 package src 8 9 import ( 10 "fmt" 11 "strconv" 12 ) 13 14 // A Pos encodes a source position consisting of a (line, column) number pair 15 // and a position base. A zero Pos is a ready to use "unknown" position (nil 16 // position base and zero line number). 17 // 18 // The (line, column) values refer to a position in a file independent of any 19 // position base ("absolute" file position). 20 // 21 // The position base is used to determine the "relative" position, that is the 22 // filename and line number relative to the position base. If the base refers 23 // to the current file, there is no difference between absolute and relative 24 // positions. If it refers to a //line directive, a relative position is relative 25 // to that directive. A position base in turn contains the position at which it 26 // was introduced in the current file. 27 type Pos struct { 28 base *PosBase 29 lico 30 } 31 32 // NoPos is a valid unknown position. 33 var NoPos Pos 34 35 // MakePos creates a new Pos value with the given base, and (file-absolute) 36 // line and column. 37 func MakePos(base *PosBase, line, col uint) Pos { 38 return Pos{base, makeLico(line, col)} 39 } 40 41 // IsKnown reports whether the position p is known. 42 // A position is known if it either has a non-nil 43 // position base, or a non-zero line number. 44 func (p Pos) IsKnown() bool { 45 return p.base != nil || p.Line() != 0 46 } 47 48 // Before reports whether the position p comes before q in the source. 49 // For positions in different files, ordering is by filename. 50 func (p Pos) Before(q Pos) bool { 51 n, m := p.Filename(), q.Filename() 52 return n < m || n == m && p.lico < q.lico 53 } 54 55 // After reports whether the position p comes after q in the source. 56 // For positions in different files, ordering is by filename. 57 func (p Pos) After(q Pos) bool { 58 n, m := p.Filename(), q.Filename() 59 return n > m || n == m && p.lico > q.lico 60 } 61 62 func (p Pos) LineNumber() string { 63 if !p.IsKnown() { 64 return "?" 65 } 66 return p.lico.lineNumber() 67 } 68 69 func (p Pos) LineNumberHTML() string { 70 if !p.IsKnown() { 71 return "?" 72 } 73 return p.lico.lineNumberHTML() 74 } 75 76 // Filename returns the name of the actual file containing this position. 77 func (p Pos) Filename() string { return p.base.Pos().RelFilename() } 78 79 // Base returns the position base. 80 func (p Pos) Base() *PosBase { return p.base } 81 82 // SetBase sets the position base. 83 func (p *Pos) SetBase(base *PosBase) { p.base = base } 84 85 // RelFilename returns the filename recorded with the position's base. 86 func (p Pos) RelFilename() string { return p.base.Filename() } 87 88 // RelLine returns the line number relative to the position's base. 89 func (p Pos) RelLine() uint { 90 b := p.base 91 if b.Line() == 0 { 92 // base line is unknown => relative line is unknown 93 return 0 94 } 95 return b.Line() + (p.Line() - b.Pos().Line()) 96 } 97 98 // RelCol returns the column number relative to the position's base. 99 func (p Pos) RelCol() uint { 100 b := p.base 101 if b.Col() == 0 { 102 // base column is unknown => relative column is unknown 103 // (the current specification for line directives requires 104 // this to apply until the next PosBase/line directive, 105 // not just until the new newline) 106 return 0 107 } 108 if p.Line() == b.Pos().Line() { 109 // p on same line as p's base => column is relative to p's base 110 return b.Col() + (p.Col() - b.Pos().Col()) 111 } 112 return p.Col() 113 } 114 115 // AbsFilename() returns the absolute filename recorded with the position's base. 116 func (p Pos) AbsFilename() string { return p.base.AbsFilename() } 117 118 // SymFilename() returns the absolute filename recorded with the position's base, 119 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 120 func (p Pos) SymFilename() string { return p.base.SymFilename() } 121 122 func (p Pos) String() string { 123 return p.Format(true, true) 124 } 125 126 // Format formats a position as "filename:line" or "filename:line:column", 127 // controlled by the showCol flag and if the column is known (!= 0). 128 // For positions relative to line directives, the original position is 129 // shown as well, as in "filename:line[origfile:origline:origcolumn] if 130 // showOrig is set. 131 func (p Pos) Format(showCol, showOrig bool) string { 132 if !p.IsKnown() { 133 return "<unknown line number>" 134 } 135 136 if b := p.base; b == b.Pos().base { 137 // base is file base (incl. nil) 138 return format(p.Filename(), p.Line(), p.Col(), showCol) 139 } 140 141 // base is relative 142 // Print the column only for the original position since the 143 // relative position's column information may be bogus (it's 144 // typically generated code and we can't say much about the 145 // original source at that point but for the file:line info 146 // that's provided via a line directive). 147 // TODO(gri) This may not be true if we have an inlining base. 148 // We may want to differentiate at some point. 149 s := format(p.RelFilename(), p.RelLine(), p.RelCol(), showCol) 150 if showOrig { 151 s += "[" + format(p.Filename(), p.Line(), p.Col(), showCol) + "]" 152 } 153 return s 154 } 155 156 // format formats a (filename, line, col) tuple as "filename:line" (showCol 157 // is false or col == 0) or "filename:line:column" (showCol is true and col != 0). 158 func format(filename string, line, col uint, showCol bool) string { 159 s := filename + ":" + strconv.FormatUint(uint64(line), 10) 160 // col == 0 and col == colMax are interpreted as unknown column values 161 if showCol && 0 < col && col < colMax { 162 s += ":" + strconv.FormatUint(uint64(col), 10) 163 } 164 return s 165 } 166 167 // ---------------------------------------------------------------------------- 168 // PosBase 169 170 // A PosBase encodes a filename and base position. 171 // Typically, each file and line directive introduce a PosBase. 172 type PosBase struct { 173 pos Pos // position at which the relative position is (line, col) 174 filename string // file name used to open source file, for error messages 175 absFilename string // absolute file name, for PC-Line tables 176 symFilename string // cached symbol file name, to avoid repeated string concatenation 177 line, col uint // relative line, column number at pos 178 inl int // inlining index (see cmd/internal/obj/inl.go) 179 } 180 181 // NewFileBase returns a new *PosBase for a file with the given (relative and 182 // absolute) filenames. 183 func NewFileBase(filename, absFilename string) *PosBase { 184 base := &PosBase{ 185 filename: filename, 186 absFilename: absFilename, 187 symFilename: FileSymPrefix + absFilename, 188 line: 1, 189 col: 1, 190 inl: -1, 191 } 192 base.pos = MakePos(base, 1, 1) 193 return base 194 } 195 196 // NewLinePragmaBase returns a new *PosBase for a line directive of the form 197 // //line filename:line:col 198 // /*line filename:line:col*/ 199 // at position pos. 200 func NewLinePragmaBase(pos Pos, filename, absFilename string, line, col uint) *PosBase { 201 return &PosBase{pos, filename, absFilename, FileSymPrefix + absFilename, line, col, -1} 202 } 203 204 // NewInliningBase returns a copy of the old PosBase with the given inlining 205 // index. If old == nil, the resulting PosBase has no filename. 206 func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase { 207 if old == nil { 208 base := &PosBase{line: 1, col: 1, inl: inlTreeIndex} 209 base.pos = MakePos(base, 1, 1) 210 return base 211 } 212 copy := *old 213 base := © 214 base.inl = inlTreeIndex 215 if old == old.pos.base { 216 base.pos.base = base 217 } 218 return base 219 } 220 221 var noPos Pos 222 223 // Pos returns the position at which base is located. 224 // If b == nil, the result is the zero position. 225 func (b *PosBase) Pos() *Pos { 226 if b != nil { 227 return &b.pos 228 } 229 return &noPos 230 } 231 232 // Filename returns the filename recorded with the base. 233 // If b == nil, the result is the empty string. 234 func (b *PosBase) Filename() string { 235 if b != nil { 236 return b.filename 237 } 238 return "" 239 } 240 241 // AbsFilename returns the absolute filename recorded with the base. 242 // If b == nil, the result is the empty string. 243 func (b *PosBase) AbsFilename() string { 244 if b != nil { 245 return b.absFilename 246 } 247 return "" 248 } 249 250 const FileSymPrefix = "gofile.." 251 252 // SymFilename returns the absolute filename recorded with the base, 253 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 254 // If b is nil, SymFilename returns FileSymPrefix + "??". 255 func (b *PosBase) SymFilename() string { 256 if b != nil { 257 return b.symFilename 258 } 259 return FileSymPrefix + "??" 260 } 261 262 // Line returns the line number recorded with the base. 263 // If b == nil, the result is 0. 264 func (b *PosBase) Line() uint { 265 if b != nil { 266 return b.line 267 } 268 return 0 269 } 270 271 // Col returns the column number recorded with the base. 272 // If b == nil, the result is 0. 273 func (b *PosBase) Col() uint { 274 if b != nil { 275 return b.col 276 } 277 return 0 278 } 279 280 // InliningIndex returns the index into the global inlining 281 // tree recorded with the base. If b == nil or the base has 282 // not been inlined, the result is < 0. 283 func (b *PosBase) InliningIndex() int { 284 if b != nil { 285 return b.inl 286 } 287 return -1 288 } 289 290 // ---------------------------------------------------------------------------- 291 // lico 292 293 // A lico is a compact encoding of a LIne and COlumn number. 294 type lico uint32 295 296 // Layout constants: 20 bits for line, 8 bits for column, 2 for isStmt, 2 for pro/epilogue 297 // (If this is too tight, we can either make lico 64b wide, 298 // or we can introduce a tiered encoding where we remove column 299 // information as line numbers grow bigger; similar to what gcc 300 // does.) 301 // The bitfield order is chosen to make IsStmt be the least significant 302 // part of a position; its use is to communicate statement edges through 303 // instruction scrambling in code generation, not to impose an order. 304 // TODO: Prologue and epilogue are perhaps better handled as pseudo-ops for the assembler, 305 // because they have almost no interaction with other uses of the position. 306 const ( 307 lineBits, lineMax = 20, 1<<lineBits - 2 308 bogusLine = 1 // Used to disrupt infinite loops to prevent debugger looping 309 isStmtBits, isStmtMax = 2, 1<<isStmtBits - 1 310 xlogueBits, xlogueMax = 2, 1<<xlogueBits - 1 311 colBits, colMax = 32 - lineBits - xlogueBits - isStmtBits, 1<<colBits - 1 312 313 isStmtShift = 0 314 isStmtMask = isStmtMax << isStmtShift 315 xlogueShift = isStmtBits + isStmtShift 316 xlogueMask = xlogueMax << xlogueShift 317 colShift = xlogueBits + xlogueShift 318 lineShift = colBits + colShift 319 ) 320 const ( 321 // It is expected that the front end or a phase in SSA will usually generate positions tagged with 322 // PosDefaultStmt, but note statement boundaries with PosIsStmt. Simple statements will have a single 323 // boundary; for loops with initialization may have one for their entry and one for their back edge 324 // (this depends on exactly how the loop is compiled; the intent is to provide a good experience to a 325 // user debugging a program; the goal is that a breakpoint set on the loop line fires both on entry 326 // and on iteration). Proper treatment of non-gofmt input with multiple simple statements on a single 327 // line is TBD. 328 // 329 // Optimizing compilation will move instructions around, and some of these will become known-bad as 330 // step targets for debugging purposes (examples: register spills and reloads; code generated into 331 // the entry block; invariant code hoisted out of loops) but those instructions will still have interesting 332 // positions for profiling purposes. To reflect this these positions will be changed to PosNotStmt. 333 // 334 // When the optimizer removes an instruction marked PosIsStmt; it should attempt to find a nearby 335 // instruction with the same line marked PosDefaultStmt to be the new statement boundary. I.e., the 336 // optimizer should make a best-effort to conserve statement boundary positions, and might be enhanced 337 // to note when a statement boundary is not conserved. 338 // 339 // Code cloning, e.g. loop unrolling or loop unswitching, is an exception to the conservation rule 340 // because a user running a debugger would expect to see breakpoints active in the copies of the code. 341 // 342 // In non-optimizing compilation there is still a role for PosNotStmt because of code generation 343 // into the entry block. PosIsStmt statement positions should be conserved. 344 // 345 // When code generation occurs any remaining default-marked positions are replaced with not-statement 346 // positions. 347 // 348 PosDefaultStmt uint = iota // Default; position is not a statement boundary, but might be if optimization removes the designated statement boundary 349 PosIsStmt // Position is a statement boundary; if optimization removes the corresponding instruction, it should attempt to find a new instruction to be the boundary. 350 PosNotStmt // Position should not be a statement boundary, but line should be preserved for profiling and low-level debugging purposes. 351 ) 352 353 type PosXlogue uint 354 355 const ( 356 PosDefaultLogue PosXlogue = iota 357 PosPrologueEnd 358 PosEpilogueBegin 359 ) 360 361 func makeLicoRaw(line, col uint) lico { 362 return lico(line<<lineShift | col<<colShift) 363 } 364 365 // This is a not-position that will not be elided. 366 // Depending on the debugger (gdb or delve) it may or may not be displayed. 367 func makeBogusLico() lico { 368 return makeLicoRaw(bogusLine, 0).withIsStmt() 369 } 370 371 func makeLico(line, col uint) lico { 372 if line > lineMax { 373 // cannot represent line, use max. line so we have some information 374 line = lineMax 375 } 376 if col > colMax { 377 // cannot represent column, use max. column so we have some information 378 col = colMax 379 } 380 // default is not-sure-if-statement 381 return makeLicoRaw(line, col) 382 } 383 384 func (x lico) Line() uint { return uint(x) >> lineShift } 385 func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) } 386 func (x lico) Col() uint { return uint(x) >> colShift & colMax } 387 func (x lico) IsStmt() uint { 388 if x == 0 { 389 return PosNotStmt 390 } 391 return uint(x) >> isStmtShift & isStmtMax 392 } 393 func (x lico) Xlogue() PosXlogue { 394 return PosXlogue(uint(x) >> xlogueShift & xlogueMax) 395 } 396 397 // withNotStmt returns a lico for the same location, but not a statement 398 func (x lico) withNotStmt() lico { 399 return x.withStmt(PosNotStmt) 400 } 401 402 // withDefaultStmt returns a lico for the same location, with default isStmt 403 func (x lico) withDefaultStmt() lico { 404 return x.withStmt(PosDefaultStmt) 405 } 406 407 // withIsStmt returns a lico for the same location, tagged as definitely a statement 408 func (x lico) withIsStmt() lico { 409 return x.withStmt(PosIsStmt) 410 } 411 412 // withLogue attaches a prologue/epilogue attribute to a lico 413 func (x lico) withXlogue(xlogue PosXlogue) lico { 414 if x == 0 { 415 if xlogue == 0 { 416 return x 417 } 418 // Normalize 0 to "not a statement" 419 x = lico(PosNotStmt << isStmtShift) 420 } 421 return lico(uint(x) & ^uint(xlogueMax<<xlogueShift) | (uint(xlogue) << xlogueShift)) 422 } 423 424 // withStmt returns a lico for the same location with specified is_stmt attribute 425 func (x lico) withStmt(stmt uint) lico { 426 if x == 0 { 427 return lico(0) 428 } 429 return lico(uint(x) & ^uint(isStmtMax<<isStmtShift) | (stmt << isStmtShift)) 430 } 431 432 func (x lico) lineNumber() string { 433 return fmt.Sprintf("%d", x.Line()) 434 } 435 436 func (x lico) lineNumberHTML() string { 437 if x.IsStmt() == PosDefaultStmt { 438 return fmt.Sprintf("%d", x.Line()) 439 } 440 style, pfx := "b", "+" 441 if x.IsStmt() == PosNotStmt { 442 style = "s" // /strike not supported in HTML5 443 pfx = "" 444 } 445 return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style) 446 } 447 448 func (x lico) atColumn1() lico { 449 return makeLico(x.Line(), 1).withIsStmt() 450 }