diff --git a/src/cmd/compile/internal/amd64/ssa.go b/src/cmd/compile/internal/amd64/ssa.go index 088a4a16c72..43a4aed1d97 100644 --- a/src/cmd/compile/internal/amd64/ssa.go +++ b/src/cmd/compile/internal/amd64/ssa.go @@ -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") } diff --git a/src/cmd/compile/internal/gc/main.go b/src/cmd/compile/internal/gc/main.go index 428a74f26c7..4b6c8f25656 100644 --- a/src/cmd/compile/internal/gc/main.go +++ b/src/cmd/compile/internal/gc/main.go @@ -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() } diff --git a/src/cmd/compile/internal/gc/ssa.go b/src/cmd/compile/internal/gc/ssa.go index 054fb8cb867..48eb89bb3a6 100644 --- a/src/cmd/compile/internal/gc/ssa.go +++ b/src/cmd/compile/internal/gc/ssa.go @@ -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" diff --git a/src/cmd/compile/internal/logopt/escape.go b/src/cmd/compile/internal/logopt/escape.go new file mode 100644 index 00000000000..802f967aa66 --- /dev/null +++ b/src/cmd/compile/internal/logopt/escape.go @@ -0,0 +1,13 @@ +// 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) +} diff --git a/src/cmd/compile/internal/logopt/escape_bootstrap.go b/src/cmd/compile/internal/logopt/escape_bootstrap.go new file mode 100644 index 00000000000..66ff0b8f220 --- /dev/null +++ b/src/cmd/compile/internal/logopt/escape_bootstrap.go @@ -0,0 +1,12 @@ +// 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.") +} diff --git a/src/cmd/compile/internal/logopt/log_opts.go b/src/cmd/compile/internal/logopt/log_opts.go new file mode 100644 index 00000000000..2ce4d29ff86 --- /dev/null +++ b/src/cmd/compile/internal/logopt/log_opts.go @@ -0,0 +1,439 @@ +// 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,. +// +// 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. +// +// 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 (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 /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 ',' where is a number", flag) + } + v, err := strconv.Atoi(value[:commaAt]) + if err != nil { + log.Fatalf("-%s option should be ',' where 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 ',' but could not create : 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 ',' 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() + } + } +} diff --git a/src/cmd/compile/internal/logopt/logopt_test.go b/src/cmd/compile/internal/logopt/logopt_test.go new file mode 100644 index 00000000000..ef71a78a1a6 --- /dev/null +++ b/src/cmd/compile/internal/logopt/logopt_test.go @@ -0,0 +1,126 @@ +// 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 +} diff --git a/src/cmd/dist/buildtool.go b/src/cmd/dist/buildtool.go index 2458b439a84..788598873de 100644 --- a/src/cmd/dist/buildtool.go +++ b/src/cmd/dist/buildtool.go @@ -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", diff --git a/src/cmd/internal/obj/inl.go b/src/cmd/internal/obj/inl.go index 8860069e477..1b1d13a679c 100644 --- a/src/cmd/internal/obj/inl.go +++ b/src/cmd/internal/obj/inl.go @@ -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)