mirror of
https://github.com/golang/go
synced 2024-11-18 10:24:42 -07:00
cmd/compile: refactor inlining parameters; inline panic
Inlining was refactored to perform tuning experiments, with the "knobs" now set to also inline functions/methods that include panic(), and -l=4 (inline calls) now expressed as a change to costs, rather than scattered if-thens. The -l=4 inline-calls penalty is chosen to be the best found during experiments; it makes some programs much larger and slower (notably, the compiler itself) and is believed to be risky for machine-generated code in general, which is why it is not the default. It is also not well-tested with the debugger and DWARF output. This change includes an explicit go:noinline applied to the method that is the largest cause of compiler binary growth and slowdown for midstack inlining; there are others, ideally whatever heuristic eventually appears will make this unnecessary. Change-Id: Idf7056ed2f961472cf49d2fd154ee98bef9421e2 Reviewed-on: https://go-review.googlesource.com/109918 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Austin Clements <austin@google.com>
This commit is contained in:
parent
7d61ad25f8
commit
87a18c6109
@ -11,7 +11,7 @@
|
|||||||
// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and
|
// making 1 the default and -l disable. Additional levels (beyond -l) may be buggy and
|
||||||
// are not supported.
|
// are not supported.
|
||||||
// 0: disabled
|
// 0: disabled
|
||||||
// 1: 80-nodes leaf functions, oneliners, lazy typechecking (default)
|
// 1: 80-nodes leaf functions, oneliners, panic, lazy typechecking (default)
|
||||||
// 2: (unassigned)
|
// 2: (unassigned)
|
||||||
// 3: (unassigned)
|
// 3: (unassigned)
|
||||||
// 4: allow non-leaf functions
|
// 4: allow non-leaf functions
|
||||||
@ -34,6 +34,15 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Inlining budget parameters, gathered in one place
|
||||||
|
const (
|
||||||
|
inlineMaxBudget = 80
|
||||||
|
inlineExtraAppendCost = 0
|
||||||
|
inlineExtraCallCost = inlineMaxBudget // default is do not inline, -l=4 enables by using 1 instead.
|
||||||
|
inlineExtraPanicCost = 1 // do not penalize inlining panics.
|
||||||
|
inlineExtraThrowCost = inlineMaxBudget // with current (2018-05/1.11) code, inlining runtime.throw does not help.
|
||||||
|
)
|
||||||
|
|
||||||
// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods
|
// Get the function's package. For ordinary functions it's on the ->sym, but for imported methods
|
||||||
// the ->sym can be re-used in the local package, so peel it off the receiver's type.
|
// the ->sym can be re-used in the local package, so peel it off the receiver's type.
|
||||||
func fnpkg(fn *Node) *types.Pkg {
|
func fnpkg(fn *Node) *types.Pkg {
|
||||||
@ -155,19 +164,23 @@ func caninl(fn *Node) {
|
|||||||
}
|
}
|
||||||
defer n.Func.SetInlinabilityChecked(true)
|
defer n.Func.SetInlinabilityChecked(true)
|
||||||
|
|
||||||
const maxBudget = 80
|
cc := int32(inlineExtraCallCost)
|
||||||
visitor := hairyVisitor{budget: maxBudget}
|
if Debug['l'] == 4 {
|
||||||
|
cc = 1 // this appears to yield better performance than 0.
|
||||||
|
}
|
||||||
|
|
||||||
|
visitor := hairyVisitor{budget: inlineMaxBudget, extraCallCost: cc}
|
||||||
if visitor.visitList(fn.Nbody) {
|
if visitor.visitList(fn.Nbody) {
|
||||||
reason = visitor.reason
|
reason = visitor.reason
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if visitor.budget < 0 {
|
if visitor.budget < 0 {
|
||||||
reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", maxBudget-visitor.budget, maxBudget)
|
reason = fmt.Sprintf("function too complex: cost %d exceeds budget %d", inlineMaxBudget-visitor.budget, inlineMaxBudget)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
n.Func.Inl = &Inline{
|
n.Func.Inl = &Inline{
|
||||||
Cost: maxBudget - visitor.budget,
|
Cost: inlineMaxBudget - visitor.budget,
|
||||||
Dcl: inlcopylist(n.Name.Defn.Func.Dcl),
|
Dcl: inlcopylist(n.Name.Defn.Func.Dcl),
|
||||||
Body: inlcopylist(fn.Nbody.Slice()),
|
Body: inlcopylist(fn.Nbody.Slice()),
|
||||||
}
|
}
|
||||||
@ -229,8 +242,9 @@ func inlFlood(n *Node) {
|
|||||||
// hairyVisitor visits a function body to determine its inlining
|
// hairyVisitor visits a function body to determine its inlining
|
||||||
// hairiness and whether or not it can be inlined.
|
// hairiness and whether or not it can be inlined.
|
||||||
type hairyVisitor struct {
|
type hairyVisitor struct {
|
||||||
budget int32
|
budget int32
|
||||||
reason string
|
reason string
|
||||||
|
extraCallCost int32
|
||||||
}
|
}
|
||||||
|
|
||||||
// Look for anything we want to punt on.
|
// Look for anything we want to punt on.
|
||||||
@ -257,12 +271,18 @@ func (v *hairyVisitor) visit(n *Node) bool {
|
|||||||
}
|
}
|
||||||
// Functions that call runtime.getcaller{pc,sp} can not be inlined
|
// Functions that call runtime.getcaller{pc,sp} can not be inlined
|
||||||
// because getcaller{pc,sp} expect a pointer to the caller's first argument.
|
// because getcaller{pc,sp} expect a pointer to the caller's first argument.
|
||||||
|
//
|
||||||
|
// runtime.throw is a "cheap call" like panic in normal code.
|
||||||
if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) {
|
if n.Left.Op == ONAME && n.Left.Class() == PFUNC && isRuntimePkg(n.Left.Sym.Pkg) {
|
||||||
fn := n.Left.Sym.Name
|
fn := n.Left.Sym.Name
|
||||||
if fn == "getcallerpc" || fn == "getcallersp" {
|
if fn == "getcallerpc" || fn == "getcallersp" {
|
||||||
v.reason = "call to " + fn
|
v.reason = "call to " + fn
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
if fn == "throw" {
|
||||||
|
v.budget -= inlineExtraThrowCost
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if fn := n.Left.Func; fn != nil && fn.Inl != nil {
|
if fn := n.Left.Func; fn != nil && fn.Inl != nil {
|
||||||
@ -277,10 +297,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
|
|||||||
}
|
}
|
||||||
// TODO(mdempsky): Budget for OCLOSURE calls if we
|
// TODO(mdempsky): Budget for OCLOSURE calls if we
|
||||||
// ever allow that. See #15561 and #23093.
|
// ever allow that. See #15561 and #23093.
|
||||||
if Debug['l'] < 4 {
|
|
||||||
v.reason = "non-leaf function"
|
// Call cost for non-leaf inlining.
|
||||||
return true
|
v.budget -= v.extraCallCost
|
||||||
}
|
|
||||||
|
|
||||||
// Call is okay if inlinable and we have the budget for the body.
|
// Call is okay if inlinable and we have the budget for the body.
|
||||||
case OCALLMETH:
|
case OCALLMETH:
|
||||||
@ -310,17 +329,16 @@ func (v *hairyVisitor) visit(n *Node) bool {
|
|||||||
v.budget -= inlfn.Inl.Cost
|
v.budget -= inlfn.Inl.Cost
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
if Debug['l'] < 4 {
|
// Call cost for non-leaf inlining.
|
||||||
v.reason = "non-leaf method"
|
v.budget -= v.extraCallCost
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// Things that are too hairy, irrespective of the budget
|
// Things that are too hairy, irrespective of the budget
|
||||||
case OCALL, OCALLINTER, OPANIC:
|
case OCALL, OCALLINTER:
|
||||||
if Debug['l'] < 4 {
|
// Call cost for non-leaf inlining.
|
||||||
v.reason = "non-leaf op " + n.Op.String()
|
v.budget -= v.extraCallCost
|
||||||
return true
|
|
||||||
}
|
case OPANIC:
|
||||||
|
v.budget -= v.extraCallCost
|
||||||
|
|
||||||
case ORECOVER:
|
case ORECOVER:
|
||||||
// recover matches the argument frame pointer to find
|
// recover matches the argument frame pointer to find
|
||||||
@ -343,6 +361,9 @@ func (v *hairyVisitor) visit(n *Node) bool {
|
|||||||
v.reason = "unhandled op " + n.Op.String()
|
v.reason = "unhandled op " + n.Op.String()
|
||||||
return true
|
return true
|
||||||
|
|
||||||
|
case OAPPEND:
|
||||||
|
v.budget -= inlineExtraAppendCost
|
||||||
|
|
||||||
case ODCLCONST, OEMPTY, OFALL, OLABEL:
|
case ODCLCONST, OEMPTY, OFALL, OLABEL:
|
||||||
// These nodes don't produce code; omit from inlining budget.
|
// These nodes don't produce code; omit from inlining budget.
|
||||||
return false
|
return false
|
||||||
|
@ -200,6 +200,9 @@ func (v *Value) auxString() string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If/when midstack inlining is enabled (-l=4), the compiler gets both larger and slower.
|
||||||
|
// Not-inlining this method is a help (*Value.reset and *Block.NewValue0 are similar).
|
||||||
|
//go:noinline
|
||||||
func (v *Value) AddArg(w *Value) {
|
func (v *Value) AddArg(w *Value) {
|
||||||
if v.Args == nil {
|
if v.Args == nil {
|
||||||
v.resetArgs() // use argstorage
|
v.resetArgs() // use argstorage
|
||||||
|
Loading…
Reference in New Issue
Block a user