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