Commit 86154654 authored by Russ Cox's avatar Russ Cox Committed by Gerrit Code Review

cmd/internal/obj: reimplement line history

In addition to possibly being clearer code,
this replaces an O(n) lookup with an O(log n) lookup.

Change-Id: I0a574c536a965a87f7ad6dcdcc30f737bc771cd5
Reviewed-on: https://go-review.googlesource.com/7623Reviewed-by: default avatarRob Pike <r@golang.org>
parent ebe3d693
...@@ -1544,14 +1544,15 @@ func getlinepragma() int { ...@@ -1544,14 +1544,15 @@ func getlinepragma() int {
} }
cp.WriteByte(byte(c)) cp.WriteByte(byte(c))
} }
cp = nil cp = nil
if strings.HasPrefix(lexbuf.String(), "go:cgo_") { text := lexbuf.String()
pragcgo(lexbuf.String())
if strings.HasPrefix(text, "go:cgo_") {
pragcgo(text)
} }
cmd = lexbuf.String() cmd = text
verb = cmd verb = cmd
if i := strings.Index(verb, " "); i >= 0 { if i := strings.Index(verb, " "); i >= 0 {
verb = verb[:i] verb = verb[:i]
...@@ -1630,8 +1631,9 @@ func getlinepragma() int { ...@@ -1630,8 +1631,9 @@ func getlinepragma() int {
if linep == 0 { if linep == 0 {
return c return c
} }
text := lexbuf.String()
n := 0 n := 0
for _, c := range lexbuf.String()[linep:] { for _, c := range text[linep:] {
if c < '0' || c > '9' { if c < '0' || c > '9' {
goto out goto out
} }
...@@ -1646,15 +1648,7 @@ func getlinepragma() int { ...@@ -1646,15 +1648,7 @@ func getlinepragma() int {
return c return c
} }
// try to avoid allocating file name over and over name = text[:linep-1]
name = lexbuf.String()[:linep-1]
for h := Ctxt.Hist; h != nil; h = h.Link {
if h.Name != "" && h.Name == name {
linehist(h.Name, int32(n), 0)
return c
}
}
linehist(name, int32(n), 0) linehist(name, int32(n), 0)
return c return c
......
...@@ -11,6 +11,7 @@ import ( ...@@ -11,6 +11,7 @@ import (
func TestLineHist(t *testing.T) { func TestLineHist(t *testing.T) {
ctxt := new(Link) ctxt := new(Link)
ctxt.Hash = make(map[SymVer]*LSym)
Linklinehist(ctxt, 1, "a.c", 0) Linklinehist(ctxt, 1, "a.c", 0)
Linklinehist(ctxt, 3, "a.h", 0) Linklinehist(ctxt, 3, "a.h", 0)
...@@ -22,18 +23,18 @@ func TestLineHist(t *testing.T) { ...@@ -22,18 +23,18 @@ func TestLineHist(t *testing.T) {
var expect = []string{ var expect = []string{
0: "??:0", 0: "??:0",
1: "/a.c:1", 1: "a.c:1",
2: "/a.c:2", 2: "a.c:2",
3: "/a.h:1", 3: "a.h:1",
4: "/a.h:2", 4: "a.h:2",
5: "/a.c:3", 5: "a.c:3",
6: "/a.c:4", 6: "a.c:4",
7: "/linedir:2", 7: "linedir:2",
8: "/linedir:3", 8: "linedir:3",
9: "??:0", 9: "??:0",
10: "??:0", 10: "??:0",
11: "/b.c:1", 11: "b.c:1",
12: "/b.c:2", 12: "b.c:2",
13: "??:0", 13: "??:0",
14: "??:0", 14: "??:0",
} }
......
...@@ -183,8 +183,8 @@ type Link struct { ...@@ -183,8 +183,8 @@ type Link struct {
Hash map[SymVer]*LSym Hash map[SymVer]*LSym
Allsym *LSym Allsym *LSym
Nsymbol int32 Nsymbol int32
Hist *Hist LineHist LineHist
Ehist *Hist Imports []string
Plist *Plist Plist *Plist
Plast *Plist Plast *Plist
Sym_div *LSym Sym_div *LSym
...@@ -580,3 +580,19 @@ const ( ...@@ -580,3 +580,19 @@ const (
) )
var linkbasepointer int var linkbasepointer int
/*
* start a new Prog list.
*/
func Linknewplist(ctxt *Link) *Plist {
pl := new(Plist)
*pl = Plist{}
if ctxt.Plist == nil {
ctxt.Plist = pl
} else {
ctxt.Plast.Link = pl
}
ctxt.Plast = pl
return pl
}
...@@ -7,107 +7,110 @@ package obj ...@@ -7,107 +7,110 @@ package obj
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"sort"
"strings" "strings"
) )
const ( // A LineHist records the history of the file input stack, which maps the virtual line number,
HISTSZ = 10 // an incrementing count of lines processed in any input file and typically named lineno,
NSYM = 50 // to a stack of file:line pairs showing the path of inclusions that led to that position.
) // The first line directive (//line in Go, #line in assembly) is treated as pushing
// a new entry on the stack, so that errors can report both the actual and translated
// line number.
//
// In typical use, the virtual lineno begins at 1, and file line numbers also begin at 1,
// but the only requirements placed upon the numbers by this code are:
// - calls to Push, Update, and Pop must be monotonically increasing in lineno
// - except as specified by those methods, virtual and file line number increase
// together, so that given (only) calls Push(10, "x.go", 1) and Pop(15),
// virtual line 12 corresponds to x.go line 3.
type LineHist struct {
Top *LineStack // current top of stack
Ranges []LineRange // ranges for lookup
Dir string // directory to qualify relative paths
TrimPathPrefix string // remove leading TrimPath from recorded file names
GOROOT string // current GOROOT
GOROOT_FINAL string // target GOROOT
}
type Hist struct { // A LineStack is an entry in the recorded line history.
Link *Hist // Although the history at any given line number is a stack,
Name string // the record for all line processed forms a tree, with common
Sym *LSym // stack prefixes acting as parents.
Line int32 type LineStack struct {
Offset int32 Parent *LineStack // parent in inclusion stack
Printed uint8 Lineno int // virtual line number where this entry takes effect
File string // file name used to open source file, for error messages
AbsFile string // absolute file name, for pcln tables
FileLine int // line number in file at Lineno
Directive bool
Sym *LSym // for linkgetline - TODO(rsc): remove
} }
func Linklinefmt(ctxt *Link, lno0 int, showAll, showFullPath bool) string { func (stk *LineStack) fileLineAt(lineno int) int {
var a [HISTSZ]struct { return stk.FileLine + lineno - stk.Lineno
incl *Hist }
idel int32
line *Hist // The span of valid linenos in the recorded line history can be broken
ldel int32 // into a set of ranges, each with a particular stack.
} // A LineRange records one such range.
lno := int32(lno0) type LineRange struct {
lno1 := lno Start int // starting lineno
var d int32 Stack *LineStack // top of stack for this range
n := 0 }
for h := ctxt.Hist; h != nil; h = h.Link {
if h.Offset < 0 { // startRange starts a new range with the given top of stack.
continue func (h *LineHist) startRange(lineno int, top *LineStack) {
} h.Top = top
if lno < h.Line { h.Ranges = append(h.Ranges, LineRange{top.Lineno, top})
break }
}
if h.Name != "<pop>" { // setFile sets stk.File = file and also derives stk.AbsFile.
if h.Offset > 0 { func (h *LineHist) setFile(stk *LineStack, file string) {
// #line directive // Note: The exclusion of stk.Directive may be wrong but matches what we've done before.
if n > 0 && n < int(HISTSZ) { // The check for < avoids putting a path prefix on "<autogenerated>".
a[n-1].line = h abs := file
a[n-1].ldel = h.Line - h.Offset + 1 if h.Dir != "" && !filepath.IsAbs(file) && !strings.HasPrefix(file, "<") && !stk.Directive {
abs = filepath.Join(h.Dir, file)
} }
// Remove leading TrimPathPrefix, or else rewrite $GOROOT to $GOROOT_FINAL.
if h.TrimPathPrefix != "" && hasPathPrefix(abs, h.TrimPathPrefix) {
if abs == h.TrimPathPrefix {
abs = ""
} else { } else {
// beginning of file abs = abs[len(h.TrimPathPrefix)+1:]
if n < int(HISTSZ) {
a[n].incl = h
a[n].idel = h.Line
a[n].line = nil
}
n++
}
continue
}
n--
if n > 0 && n < int(HISTSZ) {
d = h.Line - a[n].incl.Line
a[n-1].ldel += d
a[n-1].idel += d
}
}
if n > int(HISTSZ) {
n = int(HISTSZ)
} }
var fp string } else if h.GOROOT_FINAL != "" && h.GOROOT_FINAL != h.GOROOT && hasPathPrefix(abs, h.GOROOT) {
for i := n - 1; i >= 0; i-- { abs = h.GOROOT_FINAL + abs[len(h.GOROOT):]
if i != n-1 {
if !showAll {
break
} }
fp += " " if abs == "" {
abs = "??"
} }
if ctxt.Debugline != 0 || showFullPath { abs = filepath.Clean(abs)
fp += fmt.Sprintf("%s/", ctxt.Pathname) stk.AbsFile = abs
}
if a[i].line != nil { if file == "" {
fp += fmt.Sprintf("%s:%d[%s:%d]", a[i].line.Name, lno-a[i].ldel+1, a[i].incl.Name, lno-a[i].idel+1) file = "??"
} else {
fp += fmt.Sprintf("%s:%d", a[i].incl.Name, lno-a[i].idel+1)
}
lno = a[i].incl.Line - 1 // now print out start of this file
}
if n == 0 {
fp += fmt.Sprintf("<unknown line number %d %d %d %s>", lno1, ctxt.Hist.Offset, ctxt.Hist.Line, ctxt.Hist.Name)
} }
return fp stk.File = file
} }
// Does s have t as a path prefix? // Does s have t as a path prefix?
// That is, does s == t or does s begin with t followed by a slash? // That is, does s == t or does s begin with t followed by a slash?
// For portability, we allow ASCII case folding, so that haspathprefix("a/b/c", "A/B") is true. // For portability, we allow ASCII case folding, so that hasPathPrefix("a/b/c", "A/B") is true.
// Similarly, we allow slash folding, so that haspathprefix("a/b/c", "a\\b") is true. // Similarly, we allow slash folding, so that hasPathPrefix("a/b/c", "a\\b") is true.
func haspathprefix(s string, t string) bool { // We do not allow full Unicode case folding, for fear of causing more confusion
// or harm than good. (For an example of the kinds of things that can go wrong,
// see http://article.gmane.org/gmane.linux.kernel/1853266.)
func hasPathPrefix(s string, t string) bool {
if len(t) > len(s) { if len(t) > len(s) {
return false return false
} }
var i int var i int
var cs int
var ct int
for i = 0; i < len(t); i++ { for i = 0; i < len(t); i++ {
cs = int(s[i]) cs := int(s[i])
ct = int(t[i]) ct := int(t[i])
if 'A' <= cs && cs <= 'Z' { if 'A' <= cs && cs <= 'Z' {
cs += 'a' - 'A' cs += 'a' - 'A'
} }
...@@ -127,191 +130,187 @@ func haspathprefix(s string, t string) bool { ...@@ -127,191 +130,187 @@ func haspathprefix(s string, t string) bool {
return i >= len(s) || s[i] == '/' || s[i] == '\\' return i >= len(s) || s[i] == '/' || s[i] == '\\'
} }
// This is a simplified copy of linklinefmt above. // Push records that at that lineno a new file with the given name was pushed onto the input stack.
// It doesn't allow printing the full stack, and it returns the file name and line number separately. func (h *LineHist) Push(lineno int, file string) {
// TODO: Unify with linklinefmt somehow. stk := &LineStack{
func linkgetline(ctxt *Link, line int32, f **LSym, l *int32) { Parent: h.Top,
var a [HISTSZ]struct { Lineno: lineno,
incl *Hist FileLine: 1,
idel int32 }
line *Hist h.setFile(stk, file)
ldel int32 h.startRange(lineno, stk)
} }
var d int32
lno := int32(line) // Pop records that at lineno the current file was popped from the input stack.
n := 0 func (h *LineHist) Pop(lineno int) {
for h := ctxt.Hist; h != nil; h = h.Link { top := h.Top
if h.Offset < 0 { if top == nil {
continue
}
if lno < h.Line {
break
}
if h.Name != "<pop>" {
if h.Offset > 0 {
// #line directive
if n > 0 && n < HISTSZ {
a[n-1].line = h
a[n-1].ldel = h.Line - h.Offset + 1
}
} else {
// beginning of file
if n < HISTSZ {
a[n].incl = h
a[n].idel = h.Line
a[n].line = nil
}
n++
}
continue
}
n--
if n > 0 && n < HISTSZ {
d = h.Line - a[n].incl.Line
a[n-1].ldel += d
a[n-1].idel += d
}
}
if n > HISTSZ {
n = HISTSZ
}
if n <= 0 {
*f = Linklookup(ctxt, "??", HistVersion)
*l = 0
return return
} }
n-- if top.Directive && top.Parent != nil { // pop #line level too
var dlno int32 top = top.Parent
var file string
var sym *LSym
if a[n].line != nil {
file = a[n].line.Name
sym = a[n].line.Sym
dlno = a[n].ldel - 1
} else {
file = a[n].incl.Name
sym = a[n].incl.Sym
dlno = a[n].idel - 1
}
if sym == nil {
var buf string
if filepath.IsAbs(file) || strings.HasPrefix(file, "<") {
buf = file
} else {
buf = ctxt.Pathname + "/" + file
}
// Remove leading ctxt->trimpath, or else rewrite $GOROOT to $GOROOT_FINAL.
if ctxt.Trimpath != "" && haspathprefix(buf, ctxt.Trimpath) {
if len(buf) == len(ctxt.Trimpath) {
buf = "??"
} else {
buf1 := buf[len(ctxt.Trimpath)+1:]
if buf1[0] == '\x00' {
buf1 = "??"
}
buf = buf1
}
} else if ctxt.Goroot_final != "" && haspathprefix(buf, ctxt.Goroot) {
buf1 := fmt.Sprintf("%s%s", ctxt.Goroot_final, buf[len(ctxt.Goroot):])
buf = buf1
}
sym = Linklookup(ctxt, buf, HistVersion)
if a[n].line != nil {
a[n].line.Sym = sym
} else {
a[n].incl.Sym = sym
} }
next := top.Parent
if next == nil {
h.Top = nil
h.Ranges = append(h.Ranges, LineRange{lineno, nil})
return
} }
lno -= dlno
*f = sym // Popping included file. Update parent offset to account for
*l = lno // the virtual line number range taken by the included file.
// Cannot modify the LineStack directly, or else lookups
// for the earlier line numbers will get the wrong answers,
// so make a new one.
stk := new(LineStack)
*stk = *next
stk.Lineno = lineno
stk.FileLine = next.fileLineAt(top.Lineno)
h.startRange(lineno, stk)
} }
func Linklinehist(ctxt *Link, lineno int, f string, offset int) { // Update records that at lineno the file name and line number were changed using
if false { // debug['f'] // a line directive (//line in Go, #line in assembly).
if f != "" { func (h *LineHist) Update(lineno int, file string, line int) {
if offset != 0 { top := h.Top
fmt.Printf("%4d: %s (#line %d)\n", lineno, f, offset) if top == nil {
return // shouldn't happen
}
var stk *LineStack
if top.Directive {
// Update existing entry, except make copy to avoid changing earlier history.
stk = new(LineStack)
*stk = *top
} else { } else {
fmt.Printf("%4d: %s\n", lineno, f) // Push new entry.
stk = &LineStack{
Parent: top,
Directive: true,
} }
} else {
fmt.Printf("%4d: <pop>\n", lineno)
} }
stk.Lineno = lineno
if stk.File != file {
h.setFile(stk, file) // only retain string if needed
} }
stk.FileLine = line
h.startRange(lineno, stk)
}
h := new(Hist) // AddImport adds a package to the list of imported packages.
*h = Hist{} func (ctxt *Link) AddImport(pkg string) {
h.Name = f ctxt.Imports = append(ctxt.Imports, pkg)
h.Line = int32(lineno) }
h.Offset = int32(offset)
h.Link = nil
if ctxt.Ehist == nil {
ctxt.Hist = h
ctxt.Ehist = h
return
}
ctxt.Ehist.Link = h // At returns the input stack in effect at lineno.
ctxt.Ehist = h func (h *LineHist) At(lineno int) *LineStack {
i := sort.Search(len(h.Ranges), func(i int) bool {
return h.Ranges[i].Start > lineno
})
// Found first entry beyond lineno.
if i == 0 {
return nil
}
return h.Ranges[i-1].Stack
} }
func Linkprfile(ctxt *Link, line int) { // LineString returns a string giving the file and line number
l := int32(line) // corresponding to lineno, for use in error messages.
var i int func (h *LineHist) LineString(lineno int) string {
var a [HISTSZ]Hist stk := h.At(lineno)
var d int32 if stk == nil {
n := 0 return "<unknown line number>"
for h := ctxt.Hist; h != nil; h = h.Link {
if l < h.Line {
break
}
if h.Name != "<pop>" {
if h.Offset == 0 {
if n >= 0 && n < HISTSZ {
a[n] = *h
}
n++
continue
} }
if n > 0 && n < HISTSZ {
if a[n-1].Offset == 0 { text := fmt.Sprintf("%s:%d", stk.File, stk.fileLineAt(lineno))
a[n] = *h if stk.Directive && stk.Parent != nil {
n++ stk = stk.Parent
} else { text += fmt.Sprintf("[%s:%d]", stk.File, stk.fileLineAt(lineno))
a[n-1] = *h
}
}
continue
} }
n-- const showFullStack = false // was used by old C compilers
if n >= 0 && n < HISTSZ { if showFullStack {
d = h.Line - a[n].Line for stk.Parent != nil {
for i = 0; i < n; i++ { lineno = stk.Lineno - 1
a[i].Line += d stk = stk.Parent
text += fmt.Sprintf(" %s:%d", stk.File, stk.fileLineAt(lineno))
if stk.Directive && stk.Parent != nil {
stk = stk.Parent
text += fmt.Sprintf("[%s:%d]", stk.File, stk.fileLineAt(lineno))
} }
} }
} }
if n > HISTSZ { return text
n = HISTSZ }
// TODO(rsc): Replace call sites with use of ctxt.LineHist.
// Note that all call sites use showAll=false, showFullPath=false.
func Linklinefmt(ctxt *Link, lineno int, showAll, showFullPath bool) string {
return ctxt.LineHist.LineString(lineno)
}
// FileLine returns the file name and line number
// at the top of the stack for the given lineno.
func (h *LineHist) FileLine(lineno int) (file string, line int) {
stk := h.At(lineno)
if stk == nil {
return "??", 0
}
return stk.File, stk.fileLineAt(lineno)
}
// AbsFileLine returns the absolute file name and line number
// at the top of the stack for the given lineno.
func (h *LineHist) AbsFileLine(lineno int) (file string, line int) {
stk := h.At(lineno)
if stk == nil {
return "??", 0
}
return stk.AbsFile, stk.fileLineAt(lineno)
}
// This is a simplified copy of linklinefmt above.
// It doesn't allow printing the full stack, and it returns the file name and line number separately.
// TODO: Unify with linklinefmt somehow.
func linkgetline(ctxt *Link, lineno int32, f **LSym, l *int32) {
stk := ctxt.LineHist.At(int(lineno))
if stk == nil || stk.AbsFile == "" {
*f = Linklookup(ctxt, "??", HistVersion)
*l = 0
return
} }
for i := 0; i < n; i++ { if stk.Sym == nil {
fmt.Printf("%s:%d ", a[i].Name, int(l-a[i].Line+a[i].Offset+1)) stk.Sym = Linklookup(ctxt, stk.AbsFile, HistVersion)
} }
*f = stk.Sym
*l = int32(stk.fileLineAt(int(lineno)))
} }
/* func Linkprfile(ctxt *Link, line int) {
* start a new Prog list. fmt.Printf("%s ", ctxt.LineHist.LineString(line))
*/ }
func Linknewplist(ctxt *Link) *Plist {
pl := new(Plist) // Linklinehist pushes, amends, or pops an entry on the line history stack.
*pl = Plist{} // If f != "<pop>" and n == 0, the call pushes the start of a new file named f at lineno.
if ctxt.Plist == nil { // If f != "<pop>" and n > 0, the call amends the top of the stack to record that lineno
ctxt.Plist = pl // now corresponds to f at line n.
} else { // If f == "<pop>", the call pops the topmost entry from the stack, picking up
ctxt.Plast.Link = pl // the parent file at the line following the one where the corresponding push occurred.
} //
ctxt.Plast = pl // If n < 0, linklinehist records f as a package required by the current compilation
// (nothing to do with line numbers).
//
// TODO(rsc): Replace uses with direct calls to ctxt.Hist methods.
func Linklinehist(ctxt *Link, lineno int, f string, n int) {
switch {
case n < 0:
ctxt.AddImport(f)
case f == "<pop>":
ctxt.LineHist.Pop(lineno)
return pl case n == 0:
ctxt.LineHist.Push(lineno, f)
default:
ctxt.LineHist.Update(lineno, f, n)
}
} }
...@@ -306,10 +306,8 @@ func Writeobjdirect(ctxt *Link, b *Biobuf) { ...@@ -306,10 +306,8 @@ func Writeobjdirect(ctxt *Link, b *Biobuf) {
Bputc(b, 1) // version Bputc(b, 1) // version
// Emit autolib. // Emit autolib.
for h := ctxt.Hist; h != nil; h = h.Link { for _, pkg := range ctxt.Imports {
if h.Offset < 0 { wrstring(b, pkg)
wrstring(b, h.Name)
}
} }
wrstring(b, "") wrstring(b, "")
......
...@@ -142,9 +142,12 @@ func Linknew(arch *LinkArch) *Link { ...@@ -142,9 +142,12 @@ func Linknew(arch *LinkArch) *Link {
buf = "/???" buf = "/???"
} }
buf = filepath.ToSlash(buf) buf = filepath.ToSlash(buf)
ctxt.Pathname = buf ctxt.Pathname = buf
ctxt.LineHist.GOROOT = ctxt.Goroot
ctxt.LineHist.GOROOT_FINAL = ctxt.Goroot_final
ctxt.LineHist.Dir = ctxt.Pathname
ctxt.Headtype = headtype(Getgoos()) ctxt.Headtype = headtype(Getgoos())
if ctxt.Headtype < 0 { if ctxt.Headtype < 0 {
log.Fatalf("unknown goos %s", Getgoos()) log.Fatalf("unknown goos %s", Getgoos())
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment