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 7package src 8 9import ( 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. 28type Pos struct { 29 base *PosBase 30 lico 31} 32 33// NoPos is a valid unknown position. 34var NoPos Pos 35 36// MakePos creates a new Pos value with the given base, and (file-absolute) 37// line and column. 38func 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. 45func (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. 51func (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. 58func (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 63func (p Pos) LineNumber() string { 64 if !p.IsKnown() { 65 return "?" 66 } 67 return p.lico.lineNumber() 68} 69 70func (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. 78func (p Pos) Filename() string { return p.base.Pos().RelFilename() } 79 80// Base returns the position base. 81func (p Pos) Base() *PosBase { return p.base } 82 83// SetBase sets the position base. 84func (p *Pos) SetBase(base *PosBase) { p.base = base } 85 86// RelFilename returns the filename recorded with the position's base. 87func (p Pos) RelFilename() string { return p.base.Filename() } 88 89// RelLine returns the line number relative to the position's base. 90func (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. 100func (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. 117func (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. 121func (p Pos) FileIndex() int { return p.base.FileIndex() } 122 123func (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. 132func (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. 139func (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). 169func 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. 181func 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. 192type 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. 203func 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. 222func 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. 228func 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 241var noPos Pos 242 243// Pos returns the position at which base is located. 244// If b == nil, the result is the zero position. 245func (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. 254func (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. 263func (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. 272const 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. 277func (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. 289func (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. 298func (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. 308func (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. 319type 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. 331const ( 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) 345const ( 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 378type PosXlogue uint 379 380const ( 381 PosDefaultLogue PosXlogue = iota 382 PosPrologueEnd 383 PosEpilogueBegin 384) 385 386func 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. 392func makeBogusLico() lico { 393 return makeLicoRaw(bogusLine, 0).withIsStmt() 394} 395 396func 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 412func (x lico) Line() uint { return uint(x) >> lineShift } 413func (x lico) SameLine(y lico) bool { return 0 == (x^y)&^lico(1<<lineShift-1) } 414func (x lico) Col() uint { return uint(x) >> colShift & colMax } 415func (x lico) IsStmt() uint { 416 if x == 0 { 417 return PosNotStmt 418 } 419 return uint(x) >> isStmtShift & isStmtMax 420} 421func (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 426func (x lico) withNotStmt() lico { 427 return x.withStmt(PosNotStmt) 428} 429 430// withDefaultStmt returns a lico for the same location, with default isStmt 431func (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 436func (x lico) withIsStmt() lico { 437 return x.withStmt(PosIsStmt) 438} 439 440// withXlogue attaches a prologue/epilogue attribute to a lico 441func (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 453func (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 460func (x lico) lineNumber() string { 461 return fmt.Sprintf("%d", x.Line()) 462} 463 464func (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 476func (x lico) atColumn1() lico { 477 return makeLico(x.Line(), 1).withIsStmt() 478} 479