pos.go (15759B)
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 // SymFilename() returns the absolute filename recorded with the position's base, 120 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 121 func (p Pos) SymFilename() string { return p.base.SymFilename() } 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 symFilename string // cached symbol file name, to avoid repeated string concatenation 197 line, col uint // relative line, column number at pos 198 inl int // inlining index (see cmd/internal/obj/inl.go) 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 symFilename: FileSymPrefix + absFilename, 208 line: 1, 209 col: 1, 210 inl: -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 // //line filename:line:col 218 // /*line filename:line:col*/ 219 // at position pos. 220 func NewLinePragmaBase(pos Pos, filename, absFilename string, line, col uint) *PosBase { 221 return &PosBase{pos, filename, absFilename, FileSymPrefix + absFilename, line, col, -1} 222 } 223 224 // NewInliningBase returns a copy of the old PosBase with the given inlining 225 // index. If old == nil, the resulting PosBase has no filename. 226 func NewInliningBase(old *PosBase, inlTreeIndex int) *PosBase { 227 if old == nil { 228 base := &PosBase{line: 1, col: 1, inl: inlTreeIndex} 229 base.pos = MakePos(base, 1, 1) 230 return base 231 } 232 copy := *old 233 base := © 234 base.inl = inlTreeIndex 235 if old == old.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 const FileSymPrefix = "gofile.." 271 272 // SymFilename returns the absolute filename recorded with the base, 273 // prefixed by FileSymPrefix to make it appropriate for use as a linker symbol. 274 // If b is nil, SymFilename returns FileSymPrefix + "??". 275 func (b *PosBase) SymFilename() string { 276 if b != nil { 277 return b.symFilename 278 } 279 return FileSymPrefix + "??" 280 } 281 282 // Line returns the line number recorded with the base. 283 // If b == nil, the result is 0. 284 func (b *PosBase) Line() uint { 285 if b != nil { 286 return b.line 287 } 288 return 0 289 } 290 291 // Col returns the column number recorded with the base. 292 // If b == nil, the result is 0. 293 func (b *PosBase) Col() uint { 294 if b != nil { 295 return b.col 296 } 297 return 0 298 } 299 300 // InliningIndex returns the index into the global inlining 301 // tree recorded with the base. If b == nil or the base has 302 // not been inlined, the result is < 0. 303 func (b *PosBase) InliningIndex() int { 304 if b != nil { 305 return b.inl 306 } 307 return -1 308 } 309 310 // ---------------------------------------------------------------------------- 311 // lico 312 313 // A lico is a compact encoding of a LIne and COlumn number. 314 type lico uint32 315 316 // Layout constants: 20 bits for line, 8 bits for column, 2 for isStmt, 2 for pro/epilogue 317 // (If this is too tight, we can either make lico 64b wide, 318 // or we can introduce a tiered encoding where we remove column 319 // information as line numbers grow bigger; similar to what gcc 320 // does.) 321 // The bitfield order is chosen to make IsStmt be the least significant 322 // part of a position; its use is to communicate statement edges through 323 // instruction scrambling in code generation, not to impose an order. 324 // TODO: Prologue and epilogue are perhaps better handled as pseudo-ops for the assembler, 325 // because they have almost no interaction with other uses of the position. 326 const ( 327 lineBits, lineMax = 20, 1<<lineBits - 2 328 bogusLine = 1 // Used to disrupt infinite loops to prevent debugger looping 329 isStmtBits, isStmtMax = 2, 1<<isStmtBits - 1 330 xlogueBits, xlogueMax = 2, 1<<xlogueBits - 1 331 colBits, colMax = 32 - lineBits - xlogueBits - isStmtBits, 1<<colBits - 1 332 333 isStmtShift = 0 334 isStmtMask = isStmtMax << isStmtShift 335 xlogueShift = isStmtBits + isStmtShift 336 xlogueMask = xlogueMax << xlogueShift 337 colShift = xlogueBits + xlogueShift 338 lineShift = colBits + colShift 339 ) 340 const ( 341 // It is expected that the front end or a phase in SSA will usually generate positions tagged with 342 // PosDefaultStmt, but note statement boundaries with PosIsStmt. Simple statements will have a single 343 // boundary; for loops with initialization may have one for their entry and one for their back edge 344 // (this depends on exactly how the loop is compiled; the intent is to provide a good experience to a 345 // user debugging a program; the goal is that a breakpoint set on the loop line fires both on entry 346 // and on iteration). Proper treatment of non-gofmt input with multiple simple statements on a single 347 // line is TBD. 348 // 349 // Optimizing compilation will move instructions around, and some of these will become known-bad as 350 // step targets for debugging purposes (examples: register spills and reloads; code generated into 351 // the entry block; invariant code hoisted out of loops) but those instructions will still have interesting 352 // positions for profiling purposes. To reflect this these positions will be changed to PosNotStmt. 353 // 354 // When the optimizer removes an instruction marked PosIsStmt; it should attempt to find a nearby 355 // instruction with the same line marked PosDefaultStmt to be the new statement boundary. I.e., the 356 // optimizer should make a best-effort to conserve statement boundary positions, and might be enhanced 357 // to note when a statement boundary is not conserved. 358 // 359 // Code cloning, e.g. loop unrolling or loop unswitching, is an exception to the conservation rule 360 // because a user running a debugger would expect to see breakpoints active in the copies of the code. 361 // 362 // In non-optimizing compilation there is still a role for PosNotStmt because of code generation 363 // into the entry block. PosIsStmt statement positions should be conserved. 364 // 365 // When code generation occurs any remaining default-marked positions are replaced with not-statement 366 // positions. 367 // 368 PosDefaultStmt uint = iota // Default; position is not a statement boundary, but might be if optimization removes the designated statement boundary 369 PosIsStmt // Position is a statement boundary; if optimization removes the corresponding instruction, it should attempt to find a new instruction to be the boundary. 370 PosNotStmt // Position should not be a statement boundary, but line should be preserved for profiling and low-level debugging purposes. 371 ) 372 373 type PosXlogue uint 374 375 const ( 376 PosDefaultLogue PosXlogue = iota 377 PosPrologueEnd 378 PosEpilogueBegin 379 ) 380 381 func makeLicoRaw(line, col uint) lico { 382 return lico(line<<lineShift | col<<colShift) 383 } 384 385 // This is a not-position that will not be elided. 386 // Depending on the debugger (gdb or delve) it may or may not be displayed. 387 func makeBogusLico() lico { 388 return makeLicoRaw(bogusLine, 0).withIsStmt() 389 } 390 391 func makeLico(line, col uint) lico { 392 if line > lineMax { 393 // cannot represent line, use max. line so we have some information 394 line = lineMax 395 } 396 if col > colMax { 397 // cannot represent column, use max. column so we have some information 398 col = colMax 399 } 400 // default is not-sure-if-statement 401 return makeLicoRaw(line, col) 402 } 403 404 func (x lico) Line() uint { return uint(x) >> lineShift } 405 func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) } 406 func (x lico) Col() uint { return uint(x) >> colShift & colMax } 407 func (x lico) IsStmt() uint { 408 if x == 0 { 409 return PosNotStmt 410 } 411 return uint(x) >> isStmtShift & isStmtMax 412 } 413 func (x lico) Xlogue() PosXlogue { 414 return PosXlogue(uint(x) >> xlogueShift & xlogueMax) 415 } 416 417 // withNotStmt returns a lico for the same location, but not a statement 418 func (x lico) withNotStmt() lico { 419 return x.withStmt(PosNotStmt) 420 } 421 422 // withDefaultStmt returns a lico for the same location, with default isStmt 423 func (x lico) withDefaultStmt() lico { 424 return x.withStmt(PosDefaultStmt) 425 } 426 427 // withIsStmt returns a lico for the same location, tagged as definitely a statement 428 func (x lico) withIsStmt() lico { 429 return x.withStmt(PosIsStmt) 430 } 431 432 // withLogue attaches a prologue/epilogue attribute to a lico 433 func (x lico) withXlogue(xlogue PosXlogue) lico { 434 if x == 0 { 435 if xlogue == 0 { 436 return x 437 } 438 // Normalize 0 to "not a statement" 439 x = lico(PosNotStmt << isStmtShift) 440 } 441 return lico(uint(x) & ^uint(xlogueMax<<xlogueShift) | (uint(xlogue) << xlogueShift)) 442 } 443 444 // withStmt returns a lico for the same location with specified is_stmt attribute 445 func (x lico) withStmt(stmt uint) lico { 446 if x == 0 { 447 return lico(0) 448 } 449 return lico(uint(x) & ^uint(isStmtMax<<isStmtShift) | (stmt << isStmtShift)) 450 } 451 452 func (x lico) lineNumber() string { 453 return fmt.Sprintf("%d", x.Line()) 454 } 455 456 func (x lico) lineNumberHTML() string { 457 if x.IsStmt() == PosDefaultStmt { 458 return fmt.Sprintf("%d", x.Line()) 459 } 460 style, pfx := "b", "+" 461 if x.IsStmt() == PosNotStmt { 462 style = "s" // /strike not supported in HTML5 463 pfx = "" 464 } 465 return fmt.Sprintf("<%s>%s%d</%s>", style, pfx, x.Line(), style) 466 } 467 468 func (x lico) atColumn1() lico { 469 return makeLico(x.Line(), 1).withIsStmt() 470 }