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 := ©
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