Skip to content

Commit

Permalink
cmd/compile/internal/inline: add framework to compute func "properties"
Browse files Browse the repository at this point in the history
Add some machinery to support computing function "properties" for use
in driving inlining heuristics, and a unit testing framework to check
to see if the property computations are correct for a given set of
canned Go source files. This CL is mainly the analysis skeleton and a
testing framework; the code to compute the actual props will arrive in
a later patch.

Updates #61502.

Change-Id: I7970b64f713d17d7fdd7e8e9ccc7d9b0490571bf
Reviewed-on: https://go-review.googlesource.com/c/go/+/511557
Reviewed-by: Matthew Dempsky <[email protected]>
Run-TryBot: Than McIntosh <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
thanm committed Aug 10, 2023
1 parent 03d457a commit b888ec4
Show file tree
Hide file tree
Showing 8 changed files with 854 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/cmd/compile/internal/base/debug.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ type DebugFlags struct {
Closure int `help:"print information about closure compilation"`
Defer int `help:"print information about defer compilation"`
DisableNil int `help:"disable nil checks" concurrent:"ok"`
DumpInlFuncProps string `help:"dump function properties from inl heuristics to specified file"`
DumpPtrs int `help:"show Node pointers values in dump output"`
DwarfInl int `help:"print information about DWARF inlined function creation"`
Export int `help:"print export data"`
Expand Down
9 changes: 9 additions & 0 deletions src/cmd/compile/internal/inline/inl.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import (
"strconv"

"cmd/compile/internal/base"
"cmd/compile/internal/inline/inlheur"
"cmd/compile/internal/ir"
"cmd/compile/internal/logopt"
"cmd/compile/internal/pgo"
Expand Down Expand Up @@ -166,6 +167,10 @@ func InlinePackage(p *pgo.Profile) {
// are no longer reachable from top-level functions following
// inlining. See #59404 and #59638 for more context.
garbageCollectUnreferencedHiddenClosures()

if base.Debug.DumpInlFuncProps != "" {
inlheur.DumpFuncProps(nil, base.Debug.DumpInlFuncProps)
}
}

// InlineDecls applies inlining to the given batch of declarations.
Expand Down Expand Up @@ -269,6 +274,10 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
base.Fatalf("CanInline no nname %+v", fn)
}

if base.Debug.DumpInlFuncProps != "" {
defer inlheur.DumpFuncProps(fn, base.Debug.DumpInlFuncProps)
}

var reason string // reason, if any, that the function was not inlined
if base.Flag.LowerM > 1 || logopt.Enabled() {
defer func() {
Expand Down
168 changes: 168 additions & 0 deletions src/cmd/compile/internal/inline/inlheur/analyze.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// Copyright 2023 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 inlheur

import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"encoding/json"
"fmt"
"io"
"os"
"path/filepath"
"sort"
"strings"
)

const (
debugTraceFuncs = 1 << iota
)

// fnInlHeur contains inline heuristics state information about
// a specific Go function being analyzed/considered by the inliner.
type fnInlHeur struct {
fname string
file string
line uint
props *FuncProps
}

// computeFuncProps examines the Go function 'fn' and computes for it
// a function "properties" object, to be used to drive inlining
// heuristics. See comments on the FuncProps type for more info.
func computeFuncProps(fn *ir.Func) *FuncProps {
if debugTrace&debugTraceFuncs != 0 {
fmt.Fprintf(os.Stderr, "=-= starting analysis of func %v:\n%+v\n",
fn.Sym().Name, fn)
}
// implementation stubbed out for now
return &FuncProps{}
}

func fnFileLine(fn *ir.Func) (string, uint) {
p := base.Ctxt.InnermostPos(fn.Pos())
return filepath.Base(p.Filename()), p.Line()
}

// DumpFuncProps computes and caches function properties for the func
// 'fn', or if fn is nil, writes out the cached set of properties to
// the file given in 'dumpfile'. Used for the "-d=dumpinlfuncprops=..."
// command line flag, intended for use primarily in unit testing.
func DumpFuncProps(fn *ir.Func, dumpfile string) {
if fn != nil {
captureFuncDumpEntry(fn)
} else {
emitDumpToFile(dumpfile)
}
}

// emitDumpToFile writes out the buffer function property dump entries
// to a file, for unit testing. Dump entries need to be sorted by
// definition line, and due to generics we need to account for the
// possibility that several ir.Func's will have the same def line.
func emitDumpToFile(dumpfile string) {
outf, err := os.OpenFile(dumpfile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644)
if err != nil {
base.Fatalf("opening function props dump file %q: %v\n", dumpfile, err)
}
defer outf.Close()
dumpFilePreamble(outf)

atline := map[uint]uint{}
sl := make([]fnInlHeur, 0, len(dumpBuffer))
for _, e := range dumpBuffer {
sl = append(sl, e)
atline[e.line] = atline[e.line] + 1
}
sl = sortFnInlHeurSlice(sl)

prevline := uint(0)
for _, entry := range sl {
idx := uint(0)
if prevline == entry.line {
idx++
}
prevline = entry.line
atl := atline[entry.line]
if err := dumpFnPreamble(outf, &entry, idx, atl); err != nil {
base.Fatalf("function props dump: %v\n", err)
}
}
dumpBuffer = nil
}

// captureFuncDumpEntry analyzes function 'fn' and adds a entry
// for it to 'dumpBuffer'. Used for unit testing.
func captureFuncDumpEntry(fn *ir.Func) {
// avoid capturing compiler-generated equality funcs.
if strings.HasPrefix(fn.Sym().Name, ".eq.") {
return
}
if dumpBuffer == nil {
dumpBuffer = make(map[*ir.Func]fnInlHeur)
}
if _, ok := dumpBuffer[fn]; ok {
// we can wind up seeing closures multiple times here,
// so don't add them more than once.
return
}
fp := computeFuncProps(fn)
file, line := fnFileLine(fn)
entry := fnInlHeur{
fname: fn.Sym().Name,
file: file,
line: line,
props: fp,
}
dumpBuffer[fn] = entry
}

// dumpFilePreamble writes out a file-level preamble for a given
// Go function as part of a function properties dump.
func dumpFilePreamble(w io.Writer) {
fmt.Fprintf(w, "// DO NOT EDIT (use 'go test -v -update-expected' instead.)\n")
fmt.Fprintf(w, "// See cmd/compile/internal/inline/inlheur/testdata/props/README.txt\n")
fmt.Fprintf(w, "// for more information on the format of this file.\n")
fmt.Fprintf(w, "// %s\n", preambleDelimiter)
}

// dumpFilePreamble writes out a function-level preamble for a given
// Go function as part of a function properties dump. See the
// README.txt file in testdata/props for more on the format of
// this preamble.
func dumpFnPreamble(w io.Writer, fih *fnInlHeur, idx, atl uint) error {
fmt.Fprintf(w, "// %s %s %d %d %d\n",
fih.file, fih.fname, fih.line, idx, atl)
// emit props as comments, followed by delimiter
fmt.Fprintf(w, "%s// %s\n", fih.props.ToString("// "), comDelimiter)
data, err := json.Marshal(fih.props)
if err != nil {
return fmt.Errorf("marshall error %v\n", err)
}
fmt.Fprintf(w, "// %s\n// %s\n", string(data), fnDelimiter)
return nil
}

// sortFnInlHeurSlice sorts a slice of fnInlHeur based on
// the starting line of the function definition, then by name.
func sortFnInlHeurSlice(sl []fnInlHeur) []fnInlHeur {
sort.SliceStable(sl, func(i, j int) bool {
if sl[i].line != sl[j].line {
return sl[i].line < sl[j].line
}
return sl[i].fname < sl[j].fname
})
return sl
}

// delimiters written to various preambles to make parsing of
// dumps easier.
const preambleDelimiter = "<endfilepreamble>"
const fnDelimiter = "<endfuncpreamble>"
const comDelimiter = "<endpropsdump>"

// dumpBuffer stores up function properties dumps when
// "-d=dumpinlfuncprops=..." is in effect.
var dumpBuffer map[*ir.Func]fnInlHeur
Loading

0 comments on commit b888ec4

Please sign in to comment.