...
Run Format

Source file src/cmd/internal/src/pos.go

Documentation: cmd/internal/src

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

View as plain text