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  

View as plain text