pos.go raw

   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 := &copy
 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  }
 471