gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

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