Commit cd53fdda authored by David Chase's avatar David Chase

cmd/compile: add framework for logging optimizer (non)actions to LSP

This is intended to allow IDEs to note where the optimizer
was not able to improve users' code.  There may be other
applications for this, for example in studying effectiveness
of optimizer changes more quickly than running benchmarks,
or in verifying that code changes did not accidentally disable
optimizations in performance-critical code.

Logging of nilcheck (bad) for amd64 is implemented as
proof-of-concept.  In general, the intent is that optimizations
that didn't happen are what will be logged, because that is
believed to be what IDE users want.

Added flag -json=version,dest

Check that version=0.  (Future compilers will support a
few recent versions, I hope that version is always <=3.)

Dest is expected to be one of:

/path (or \path in Windows)
  will create directory /path and fill it w/ json files
file://path
  will create directory path, intended either for
     I:\dont\know\enough\about\windows\paths
     trustme_I_know_what_I_am_doing_probably_testing

Not passing an absolute path name usually leads to
json splattered all over source directories,
or failure when those directories are not writeable.
If you want a foot-gun, you have to ask for it.

The JSON output is directed to subdirectories of dest,
where each subdirectory is net/url.PathEscape of the
package name, and each for each foo.go in the package,
net/url.PathEscape(foo).json is created.  The first line
of foo.json contains version and context information,
and subsequent lines contains LSP-conforming JSON
describing the missing optimizations.

Change-Id: Ib83176a53a8c177ee9081aefc5ae05604ccad8a0
Reviewed-on: https://go-review.googlesource.com/c/go/+/204338
Run-TryBot: David Chase <drchase@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarCherry Zhang <cherryyz@google.com>
parent 4d4ddd86
......@@ -5,6 +5,7 @@
package amd64
import (
"cmd/compile/internal/logopt"
"fmt"
"math"
......@@ -1081,6 +1082,9 @@ func ssaGenValue(s *gc.SSAGenState, v *ssa.Value) {
p.To.Type = obj.TYPE_MEM
p.To.Reg = v.Args[0].Reg()
gc.AddAux(&p.To, v)
if logopt.Enabled() {
logopt.LogOpt(v.Pos, "nilcheck", "genssa", v.Block.Func.Name)
}
if gc.Debug_checknil != 0 && v.Pos.Line() > 1 { // v.Pos.Line()==1 in generated wrappers
gc.Warnl(v.Pos, "generated nil check")
}
......
......@@ -9,6 +9,7 @@ package gc
import (
"bufio"
"bytes"
"cmd/compile/internal/logopt"
"cmd/compile/internal/ssa"
"cmd/compile/internal/types"
"cmd/internal/bio"
......@@ -203,6 +204,7 @@ func Main(archInit func(*Arch)) {
// Whether the limit for stack-allocated objects is much smaller than normal.
// This can be helpful for diagnosing certain causes of GC latency. See #27732.
smallFrames := false
jsonLogOpt := ""
flag.BoolVar(&compiling_runtime, "+", false, "compiling runtime")
flag.BoolVar(&compiling_std, "std", false, "compiling standard library")
......@@ -276,6 +278,7 @@ func Main(archInit func(*Arch)) {
flag.BoolVar(&smallFrames, "smallframes", false, "reduce the size limit for stack allocated objects")
flag.BoolVar(&Ctxt.UseBASEntries, "dwarfbasentries", Ctxt.UseBASEntries, "use base address selection entries in DWARF")
flag.BoolVar(&Ctxt.Flag_newobj, "newobj", false, "use new object file format")
flag.StringVar(&jsonLogOpt, "json", "", "version,destination for JSON compiler/optimizer logging")
objabi.Flagparse(usage)
......@@ -478,6 +481,10 @@ func Main(archInit func(*Arch)) {
Debug['l'] = 1 - Debug['l']
}
if jsonLogOpt != "" { // parse version,destination from json logging optimization.
logopt.LogJsonOption(jsonLogOpt)
}
ssaDump = os.Getenv("GOSSAFUNC")
if ssaDump != "" {
if strings.HasSuffix(ssaDump, "+") {
......@@ -772,6 +779,8 @@ func Main(archInit func(*Arch)) {
Fatalf("%d uncompiled functions", len(compilequeue))
}
logopt.FlushLoggedOpts(Ctxt, myimportpath)
if nerrors+nsavederrors != 0 {
errorexit()
}
......
......@@ -5,14 +5,14 @@
package gc
import (
"bufio"
"bytes"
"encoding/binary"
"fmt"
"html"
"os"
"sort"
"bufio"
"bytes"
"cmd/compile/internal/ssa"
"cmd/compile/internal/types"
"cmd/internal/obj"
......
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build go1.8
package logopt
import "net/url"
func pathEscape(s string) string {
return url.PathEscape(s)
}
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
// +build !go1.8
package logopt
// For bootstrapping with an early version of Go
func pathEscape(s string) string {
panic("This should never be called; the compiler is not fully bootstrapped if it is.")
}
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logopt
import (
"cmd/internal/obj"
"cmd/internal/objabi"
"cmd/internal/src"
"encoding/json"
"fmt"
"io"
"log"
"net/url"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"sync"
)
// This implements (non)optimization logging for -json option to the Go compiler
// The option is -json 0,<destination>.
//
// 0 is the version number; to avoid the need for synchronized updates, if
// new versions of the logging appear, the compiler will support both, for a while,
// and clients will specify what they need.
//
// <destination> is a directory.
// Directories are specified with a leading / or os.PathSeparator,
// or more explicitly with file://directory. The second form is intended to
// deal with corner cases on Windows, and to allow specification of a relative
// directory path (which is normally a bad idea, because the local directory
// varies a lot in a build, especially with modules and/or vendoring, and may
// not be writeable).
//
// For each package pkg compiled, a url.PathEscape(pkg)-named subdirectory
// is created. For each source file.go in that package that generates
// diagnostics (no diagnostics means no file),
// a url.PathEscape(file)+".json"-named file is created and contains the
// logged diagnostics.
//
// For example, "cmd%2Finternal%2Fdwarf/%3Cautogenerated%3E.json"
// for "cmd/internal/dwarf" and <autogenerated> (which is not really a file, but the compiler sees it)
//
// If the package string is empty, it is replaced internally with string(0) which encodes to %00.
//
// Each log file begins with a JSON record identifying version,
// platform, and other context, followed by optimization-relevant
// LSP Diagnostic records, one per line (LSP version 3.15, no difference from 3.14 on the subset used here
// see https://microsoft.github.io/language-server-protocol/specifications/specification-3-15/ )
//
// The fields of a Diagnostic are used in the following way:
// Range: the outermost source position, for now begin and end are equal.
// Severity: (always) SeverityInformation (3)
// Source: (always) "go compiler"
// Code: a string describing the missed optimization, e.g., "nilcheck", "cannotInline", "isInBounds", "escape"
// Message: depending on code, additional information, e.g., the reason a function cannot be inlined.
// RelatedInformation: if the missed optimization actually occurred at a function inlined at Range,
// then the sequence of inlined locations appears here, from (second) outermost to innermost,
// each with message="inlineLoc".
//
// In the case of escape analysis explanations, after any outer inlining locations,
// the lines of the explanation appear, each potentially followed with its own inlining
// location if the escape flow occurred within an inlined function.
//
// For example <destination>/cmd%2Fcompile%2Finternal%2Fssa/prove.json
// might begin with the following line (wrapped for legibility):
//
// {"version":0,"package":"cmd/compile/internal/ssa","goos":"darwin","goarch":"amd64",
// "gc_version":"devel +e1b9a57852 Fri Nov 1 15:07:00 2019 -0400",
// "file":"/Users/drchase/work/go/src/cmd/compile/internal/ssa/prove.go"}
//
// and later contain (also wrapped for legibility):
//
// {"range":{"start":{"line":191,"character":24},"end":{"line":191,"character":24}},
// "severity":3,"code":"nilcheck","source":"go compiler","message":"",
// "relatedInformation":[
// {"location":{"uri":"file:///Users/drchase/work/go/src/cmd/compile/internal/ssa/func.go",
// "range":{"start":{"line":153,"character":16},"end":{"line":153,"character":16}}},
// "message":"inlineLoc"}]}
//
// That is, at prove.go (implicit from context, provided in both filename and header line),
// line 191, column 24, a nilcheck occurred in the generated code.
// The relatedInformation indicates that this code actually came from
// an inlined call to func.go, line 153, character 16.
//
// prove.go:191:
// ft.orderS = f.newPoset()
// func.go:152 and 153:
// func (f *Func) newPoset() *poset {
// if len(f.Cache.scrPoset) > 0 {
//
// In the case that the package is empty, the string(0) package name is also used in the header record, for example
//
// go tool compile -json=0,file://logopt x.go # no -p option to set the package
// head -1 logopt/%00/x.json
// {"version":0,"package":"\u0000","goos":"darwin","goarch":"amd64","gc_version":"devel +86487adf6a Thu Nov 7 19:34:56 2019 -0500","file":"x.go"}
type VersionHeader struct {
Version int `json:"version"`
Package string `json:"package"`
Goos string `json:"goos"`
Goarch string `json:"goarch"`
GcVersion string `json:"gc_version"`
File string `json:"file,omitempty"` // LSP requires an enclosing resource, i.e., a file
}
// DocumentURI, Position, Range, Location, Diagnostic, DiagnosticRelatedInformation all reuse json definitions from gopls.
// See https://github.com/golang/tools/blob/22afafe3322a860fcd3d88448768f9db36f8bc5f/internal/lsp/protocol/tsprotocol.go
type DocumentURI string
type Position struct {
Line uint `json:"line"` // gopls uses float64, but json output is the same for integers
Character uint `json:"character"` // gopls uses float64, but json output is the same for integers
}
// A Range in a text document expressed as (zero-based) start and end positions.
// A range is comparable to a selection in an editor. Therefore the end position is exclusive.
// If you want to specify a range that contains a line including the line ending character(s)
// then use an end position denoting the start of the next line.
type Range struct {
/*Start defined:
* The range's start position
*/
Start Position `json:"start"`
/*End defined:
* The range's end position
*/
End Position `json:"end"` // exclusive
}
// A Location represents a location inside a resource, such as a line inside a text file.
type Location struct {
// URI is
URI DocumentURI `json:"uri"`
// Range is
Range Range `json:"range"`
}
/* DiagnosticRelatedInformation defined:
* Represents a related message and source code location for a diagnostic. This should be
* used to point to code locations that cause or related to a diagnostics, e.g when duplicating
* a symbol in a scope.
*/
type DiagnosticRelatedInformation struct {
/*Location defined:
* The location of this related diagnostic information.
*/
Location Location `json:"location"`
/*Message defined:
* The message of this related diagnostic information.
*/
Message string `json:"message"`
}
// DiagnosticSeverity defines constants
type DiagnosticSeverity uint
const (
/*SeverityInformation defined:
* Reports an information.
*/
SeverityInformation DiagnosticSeverity = 3
)
// DiagnosticTag defines constants
type DiagnosticTag uint
/*Diagnostic defined:
* Represents a diagnostic, such as a compiler error or warning. Diagnostic objects
* are only valid in the scope of a resource.
*/
type Diagnostic struct {
/*Range defined:
* The range at which the message applies
*/
Range Range `json:"range"`
/*Severity defined:
* The diagnostic's severity. Can be omitted. If omitted it is up to the
* client to interpret diagnostics as error, warning, info or hint.
*/
Severity DiagnosticSeverity `json:"severity,omitempty"` // always SeverityInformation for optimizer logging.
/*Code defined:
* The diagnostic's code, which usually appear in the user interface.
*/
Code string `json:"code,omitempty"` // LSP uses 'number | string' = gopls interface{}, but only string here, e.g. "boundsCheck", "nilcheck", etc.
/*Source defined:
* A human-readable string describing the source of this
* diagnostic, e.g. 'typescript' or 'super lint'. It usually
* appears in the user interface.
*/
Source string `json:"source,omitempty"` // "go compiler"
/*Message defined:
* The diagnostic's message. It usually appears in the user interface
*/
Message string `json:"message"` // sometimes used, provides additional information.
/*Tags defined:
* Additional metadata about the diagnostic.
*/
Tags []DiagnosticTag `json:"tags,omitempty"` // always empty for logging optimizations.
/*RelatedInformation defined:
* An array of related diagnostic information, e.g. when symbol-names within
* a scope collide all definitions can be marked via this property.
*/
RelatedInformation []DiagnosticRelatedInformation `json:"relatedInformation,omitempty"`
}
// A LoggedOpt is what the compiler produces and accumulates,
// to be converted to JSON for human or IDE consumption.
type LoggedOpt struct {
pos src.XPos // Source code position at which the event occurred. If it is inlined, outer and all inlined locations will appear in JSON.
pass string // For human/adhoc consumption; does not appear in JSON (yet)
fname string // For human/adhoc consumption; does not appear in JSON (yet)
what string // The (non) optimization; "nilcheck", "boundsCheck", "inline", "noInline"
target []interface{} // Optional target(s) or parameter(s) of "what" -- what was inlined, why it was not, size of copy, etc. 1st is most important/relevant.
}
type logFormat uint8
const (
None logFormat = iota
Json0 // version 0 for LSP 3.14, 3.15; future versions of LSP may change the format and the compiler may need to support both as clients are updated.
)
var Format = None
var dest string
func LogJsonOption(flagValue string) {
version, directory := parseLogFlag("json", flagValue)
if version != 0 {
log.Fatal("-json version must be 0")
}
checkLogPath("json", directory)
Format = Json0
}
// parseLogFlag checks the flag passed to -json
// for version,destination format and returns the two parts.
func parseLogFlag(flag, value string) (version int, directory string) {
if Format != None {
log.Fatal("Cannot repeat -json flag")
}
commaAt := strings.Index(value, ",")
if commaAt <= 0 {
log.Fatalf("-%s option should be '<version>,<destination>' where <version> is a number", flag)
}
v, err := strconv.Atoi(value[:commaAt])
if err != nil {
log.Fatalf("-%s option should be '<version>,<destination>' where <version> is a number: err=%v", flag, err)
}
version = v
directory = value[commaAt+1:]
return
}
// checkLogPath does superficial early checking of the string specifying
// the directory to which optimizer logging is directed, and if
// it passes the test, stores the string in LO_dir
func checkLogPath(flag, destination string) {
sep := string(os.PathSeparator)
if strings.HasPrefix(destination, "/") || strings.HasPrefix(destination, sep) {
err := os.MkdirAll(destination, 0755)
if err != nil {
log.Fatalf("optimizer logging destination '<version>,<directory>' but could not create <directory>: err=%v", err)
}
} else if strings.HasPrefix(destination, "file://") { // IKWIAD, or Windows C:\foo\bar\baz
uri, err := url.Parse(destination)
if err != nil {
log.Fatalf("optimizer logging destination looked like file:// URI but failed to parse: err=%v", err)
}
destination = uri.Host + uri.Path
err = os.MkdirAll(destination, 0755)
if err != nil {
log.Fatalf("optimizer logging destination '<version>,<directory>' but could not create %s: err=%v", destination, err)
}
} else {
log.Fatalf("optimizer logging destination %s was neither %s-prefixed directory nor file://-prefixed file URI", destination, sep)
}
dest = destination
}
var loggedOpts []LoggedOpt
var mu = sync.Mutex{} // mu protects loggedOpts.
func LogOpt(pos src.XPos, what, pass, fname string, args ...interface{}) {
if Format == None {
return
}
pass = strings.Replace(pass, " ", "_", -1)
mu.Lock()
defer mu.Unlock()
// Because of concurrent calls from back end, no telling what the order will be, but is stable-sorted by outer Pos before use.
loggedOpts = append(loggedOpts, LoggedOpt{pos, pass, fname, what, args})
}
func Enabled() bool {
switch Format {
case None:
return false
case Json0:
return true
}
panic("Unexpected optimizer-logging level")
}
// byPos sorts diagnostics by source position.
type byPos struct {
ctxt *obj.Link
a []LoggedOpt
}
func (x byPos) Len() int { return len(x.a) }
func (x byPos) Less(i, j int) bool { return x.ctxt.OutermostPos(x.a[i].pos).Before(x.ctxt.OutermostPos(x.a[j].pos)) }
func (x byPos) Swap(i, j int) { x.a[i], x.a[j] = x.a[j], x.a[i] }
func writerForLSP(subdirpath, file string) io.WriteCloser {
basename := file
lastslash := strings.LastIndexAny(basename, "\\/")
if lastslash != -1 {
basename = basename[lastslash+1:]
}
lastdot := strings.LastIndex(basename, ".go")
if lastdot != -1 {
basename = basename[:lastdot]
}
basename = pathEscape(basename)
// Assume a directory, make a file
p := filepath.Join(subdirpath, basename+".json")
w, err := os.Create(p)
if err != nil {
log.Fatalf("Could not create file %s for logging optimizer actions, %v", p, err)
}
return w
}
func fixSlash(f string) string {
if os.PathSeparator == '/' {
return f
}
return strings.Replace(f, string(os.PathSeparator), "/", -1)
}
func uriIfy(f string) DocumentURI {
url := url.URL{
Scheme: "file",
Path: fixSlash(f),
}
return DocumentURI(url.String())
}
// FlushLoggedOpts flushes all the accumulated optimization log entries.
func FlushLoggedOpts(ctxt *obj.Link, slashPkgPath string) {
if Format == None {
return
}
sort.Stable(byPos{ctxt,loggedOpts}) // Stable is necessary to preserve the per-function order, which is repeatable.
switch Format {
case Json0: // LSP 3.15
var posTmp []src.Pos
var encoder *json.Encoder
var w io.WriteCloser
if slashPkgPath == "" {
slashPkgPath = string(0)
}
subdirpath := filepath.Join(dest, pathEscape(slashPkgPath))
err := os.MkdirAll(subdirpath, 0755)
if err != nil {
log.Fatalf("Could not create directory %s for logging optimizer actions, %v", subdirpath, err)
}
diagnostic := Diagnostic{Source: "go compiler", Severity: SeverityInformation}
// For LSP, make a subdirectory for the package, and for each file foo.go, create foo.json in that subdirectory.
currentFile := ""
for _, x := range loggedOpts {
posTmp = ctxt.AllPos(x.pos, posTmp)
// Reverse posTmp to put outermost first.
l := len(posTmp)
for i := 0; i < l/2; i++ {
posTmp[i], posTmp[l-i-1] = posTmp[l-i-1], posTmp[i]
}
p0 := posTmp[0]
if currentFile != p0.Filename() {
if w != nil {
w.Close()
}
currentFile = p0.Filename()
w = writerForLSP(subdirpath, currentFile)
encoder = json.NewEncoder(w)
encoder.Encode(VersionHeader{Version: 0, Package: slashPkgPath, Goos: objabi.GOOS, Goarch: objabi.GOARCH, GcVersion: objabi.Version, File: currentFile})
}
// The first "target" is the most important one.
var target string
if len(x.target) > 0 {
target = fmt.Sprint(x.target[0])
}
diagnostic.Code = x.what
diagnostic.Message = target
diagnostic.Range = Range{Start: Position{p0.Line(), p0.Col()},
End: Position{p0.Line(), p0.Col()}}
diagnostic.RelatedInformation = diagnostic.RelatedInformation[:0]
for i := 1; i < l; i++ {
p := posTmp[i]
loc := Location{URI: uriIfy(p.Filename()),
Range: Range{Start: Position{p.Line(), p.Col()},
End: Position{p.Line(), p.Col()}}}
diagnostic.RelatedInformation = append(diagnostic.RelatedInformation, DiagnosticRelatedInformation{Location: loc, Message: "inlineLoc"})
}
encoder.Encode(diagnostic)
}
if w != nil {
w.Close()
}
}
}
// Copyright 2019 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package logopt
import (
"internal/testenv"
"io/ioutil"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
"testing"
)
const srcCode = `package x
type pair struct {a,b int}
func bar(y *pair) *int {
return &y.b
}
func foo(w, z *pair) *int {
if *bar(w) > 0 {
return bar(z)
}
return nil
}
`
func want(t *testing.T, out string, desired string) {
if !strings.Contains(out, desired) {
t.Errorf("did not see phrase %s in \n%s", desired, out)
}
}
func TestLogOpt(t *testing.T) {
t.Parallel()
testenv.MustHaveGoBuild(t)
dir, err := ioutil.TempDir("", "TestLogOpt")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
dir = fixSlash(dir) // Normalize the directory name as much as possible, for Windows testing
src := filepath.Join(dir, "file.go")
if err := ioutil.WriteFile(src, []byte(srcCode), 0644); err != nil {
t.Fatal(err)
}
outfile := filepath.Join(dir, "file.o")
t.Run("JSON_fails", func(t *testing.T) {
// Test malformed flag
out, err := testLogOpt(t, "-json=foo", src, outfile)
if err == nil {
t.Error("-json=foo succeeded unexpectedly")
}
want(t, out, "option should be")
want(t, out, "number")
// Test a version number that is currently unsupported (and should remain unsupported for a while)
out, err = testLogOpt(t, "-json=9,foo", src, outfile)
if err == nil {
t.Error("-json=0,foo succeeded unexpectedly")
}
want(t, out, "version must be")
})
// Some architectures don't fault on nil dereference, so nilchecks are eliminated differently.
if runtime.GOARCH != "amd64" {
return
}
t.Run("Success", func(t *testing.T) {
// This test is supposed to succeed
// replace d (dir) with t ("tmpdir") and convert path separators to '/'
normalize := func(out []byte, d, t string) string {
s := string(out)
s = strings.ReplaceAll(s, d, t)
s = strings.ReplaceAll(s, string(os.PathSeparator), "/")
return s
}
// Note 'file://' is the I-Know-What-I-Am-Doing way of specifying a file, also to deal with corner cases for Windows.
_, err := testLogOptDir(t, dir, "-json=0,file://log/opt", src, outfile)
if err != nil {
t.Error("-json=0,file://log/opt should have succeeded")
}
logged, err := ioutil.ReadFile(filepath.Join(dir, "log", "opt", "x", "file.json"))
if err != nil {
t.Error("-json=0,file://log/opt missing expected log file")
}
// All this delicacy with uriIfy and filepath.Join is to get this test to work right on Windows.
slogged := normalize(logged, string(uriIfy(dir)), string(uriIfy("tmpdir")))
t.Logf("%s", slogged)
// below shows proper inlining and nilcheck
want(t, slogged, `{"range":{"start":{"line":9,"character":13},"end":{"line":9,"character":13}},"severity":3,"code":"nilcheck","source":"go compiler","message":"","relatedInformation":[{"location":{"uri":"file://tmpdir/file.go","range":{"start":{"line":4,"character":11},"end":{"line":4,"character":11}}},"message":"inlineLoc"}]}`)
})
}
func testLogOpt(t *testing.T, flag, src, outfile string) (string, error) {
run := []string{testenv.GoToolPath(t), "tool", "compile", flag, "-o", outfile, src}
t.Log(run)
cmd := exec.Command(run[0], run[1:]...)
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
return string(out), err
}
func testLogOptDir(t *testing.T, dir, flag, src, outfile string) (string, error) {
// Notice the specified import path "x"
run := []string{testenv.GoToolPath(t), "tool", "compile", "-p", "x", flag, "-o", outfile, src}
t.Log(run)
cmd := exec.Command(run[0], run[1:]...)
cmd.Dir = dir
out, err := cmd.CombinedOutput()
t.Logf("%s", out)
return string(out), err
}
......@@ -41,6 +41,7 @@ var bootstrapDirs = []string{
"cmd/compile/internal/arm",
"cmd/compile/internal/arm64",
"cmd/compile/internal/gc",
"cmd/compile/internal/logopt",
"cmd/compile/internal/mips",
"cmd/compile/internal/mips64",
"cmd/compile/internal/ppc64",
......
......@@ -108,6 +108,21 @@ func (ctxt *Link) InnermostPos(xpos src.XPos) src.Pos {
return ctxt.PosTable.Pos(xpos)
}
// AllPos returns a slice of the positions inlined at xpos, from
// innermost (index zero) to outermost. To avoid gratuitous allocation
// the result is passed in and extended if necessary.
func (ctxt *Link) AllPos(xpos src.XPos, result []src.Pos) []src.Pos {
pos := ctxt.InnermostPos(xpos)
result = result[:0]
result = append(result, ctxt.PosTable.Pos(xpos))
for ix := pos.Base().InliningIndex(); ix >= 0; {
call := ctxt.InlTree.nodes[ix]
ix = call.Parent
result = append(result, ctxt.PosTable.Pos(call.Pos))
}
return result
}
func dumpInlTree(ctxt *Link, tree InlTree) {
for i, call := range tree.nodes {
pos := ctxt.PosTable.Pos(call.Pos)
......
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