mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
cmd/compile: enable PGO-driven call devirtualization
This CL is originally based on CL 484838 from rajbarik@uber.com. Add a new PGO-based devirtualize pass. This pass conditionally devirtualizes interface calls for the hottest callee. That is, it performs a transformation like: type Iface interface { Foo() } type Concrete struct{} func (Concrete) Foo() {} func foo(i Iface) { i.Foo() } to: func foo(i Iface) { if c, ok := i.(Concrete); ok { c.Foo() } else { i.Foo() } } The primary benefit of this transformation is enabling inlining of the direct calls. Today this change has no impact on the escape behavior, as the fallback interface always forces an escape. But improving escape analysis to take advantage of this is an area of potential work. This CL is the bare minimum of a devirtualization implementation. There are still numerous limitations: * Callees not directly referenced in the current package can be missed (even if they are in the transitive dependences). * Callees not in the transitive dependencies of the current package are missed. * Only interface method calls are supported, not other indirect function calls. * Multiple calls to compatible interfaces on the same line cannot be distinguished and will use the same callee target. * Callees that only partially implement an interface (they are embedded in another type that completes the interface) cannot be devirtualized. * Others, mentioned in TODOs. Fixes #59959 Change-Id: I8bedb516139695ee4069650b099d05957b7ce5ee Reviewed-on: https://go-review.googlesource.com/c/go/+/492436 Reviewed-by: Cherry Mui <cherryyz@google.com> Reviewed-by: Matthew Dempsky <mdempsky@google.com> Run-TryBot: Michael Pratt <mpratt@google.com> Auto-Submit: Michael Pratt <mpratt@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
parent
6761bff433
commit
8c445b7c9f
11 changed files with 1049 additions and 122 deletions
|
|
@ -101,13 +101,13 @@ func pgoInlinePrologue(p *pgo.Profile, decls []ir.Node) {
|
|||
candHotCalleeMap[callee] = struct{}{}
|
||||
}
|
||||
// mark hot call sites
|
||||
if caller := p.WeightedCG.IRNodes[n.CallerName]; caller != nil {
|
||||
if caller := p.WeightedCG.IRNodes[n.CallerName]; caller != nil && caller.AST != nil {
|
||||
csi := pgo.CallSiteInfo{LineOffset: n.CallSiteOffset, Caller: caller.AST}
|
||||
candHotEdgeMap[csi] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
if base.Debug.PGODebug >= 2 {
|
||||
if base.Debug.PGODebug >= 3 {
|
||||
fmt.Printf("hot-cg before inline in dot format:")
|
||||
p.PrintWeightedCallGraphDOT(inlineHotCallSiteThresholdPercent)
|
||||
}
|
||||
|
|
@ -283,71 +283,10 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
|
|||
}()
|
||||
}
|
||||
|
||||
// If marked "go:noinline", don't inline
|
||||
if fn.Pragma&ir.Noinline != 0 {
|
||||
reason = "marked go:noinline"
|
||||
reason = InlineImpossible(fn)
|
||||
if reason != "" {
|
||||
return
|
||||
}
|
||||
|
||||
// If marked "go:norace" and -race compilation, don't inline.
|
||||
if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
|
||||
reason = "marked go:norace with -race compilation"
|
||||
return
|
||||
}
|
||||
|
||||
// If marked "go:nocheckptr" and -d checkptr compilation, don't inline.
|
||||
if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
|
||||
reason = "marked go:nocheckptr"
|
||||
return
|
||||
}
|
||||
|
||||
// If marked "go:cgo_unsafe_args", don't inline, since the
|
||||
// function makes assumptions about its argument frame layout.
|
||||
if fn.Pragma&ir.CgoUnsafeArgs != 0 {
|
||||
reason = "marked go:cgo_unsafe_args"
|
||||
return
|
||||
}
|
||||
|
||||
// If marked as "go:uintptrkeepalive", don't inline, since the
|
||||
// keep alive information is lost during inlining.
|
||||
//
|
||||
// TODO(prattmic): This is handled on calls during escape analysis,
|
||||
// which is after inlining. Move prior to inlining so the keep-alive is
|
||||
// maintained after inlining.
|
||||
if fn.Pragma&ir.UintptrKeepAlive != 0 {
|
||||
reason = "marked as having a keep-alive uintptr argument"
|
||||
return
|
||||
}
|
||||
|
||||
// If marked as "go:uintptrescapes", don't inline, since the
|
||||
// escape information is lost during inlining.
|
||||
if fn.Pragma&ir.UintptrEscapes != 0 {
|
||||
reason = "marked as having an escaping uintptr argument"
|
||||
return
|
||||
}
|
||||
|
||||
// The nowritebarrierrec checker currently works at function
|
||||
// granularity, so inlining yeswritebarrierrec functions can
|
||||
// confuse it (#22342). As a workaround, disallow inlining
|
||||
// them for now.
|
||||
if fn.Pragma&ir.Yeswritebarrierrec != 0 {
|
||||
reason = "marked go:yeswritebarrierrec"
|
||||
return
|
||||
}
|
||||
|
||||
// If fn has no body (is defined outside of Go), cannot inline it.
|
||||
if len(fn.Body) == 0 {
|
||||
reason = "no function body"
|
||||
return
|
||||
}
|
||||
|
||||
// If fn is synthetic hash or eq function, cannot inline it.
|
||||
// The function is not generated in Unified IR frontend at this moment.
|
||||
if ir.IsEqOrHashFunc(fn) {
|
||||
reason = "type eq/hash function"
|
||||
return
|
||||
}
|
||||
|
||||
if fn.Typecheck() == 0 {
|
||||
base.Fatalf("CanInline on non-typechecked function %v", fn)
|
||||
}
|
||||
|
|
@ -415,6 +354,82 @@ func CanInline(fn *ir.Func, profile *pgo.Profile) {
|
|||
}
|
||||
}
|
||||
|
||||
// InlineImpossible returns a non-empty reason string if fn is impossible to
|
||||
// inline regardless of cost or contents.
|
||||
func InlineImpossible(fn *ir.Func) string {
|
||||
var reason string // reason, if any, that the function can not be inlined.
|
||||
if fn.Nname == nil {
|
||||
reason = "no name"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked "go:noinline", don't inline.
|
||||
if fn.Pragma&ir.Noinline != 0 {
|
||||
reason = "marked go:noinline"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked "go:norace" and -race compilation, don't inline.
|
||||
if base.Flag.Race && fn.Pragma&ir.Norace != 0 {
|
||||
reason = "marked go:norace with -race compilation"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked "go:nocheckptr" and -d checkptr compilation, don't inline.
|
||||
if base.Debug.Checkptr != 0 && fn.Pragma&ir.NoCheckPtr != 0 {
|
||||
reason = "marked go:nocheckptr"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked "go:cgo_unsafe_args", don't inline, since the function
|
||||
// makes assumptions about its argument frame layout.
|
||||
if fn.Pragma&ir.CgoUnsafeArgs != 0 {
|
||||
reason = "marked go:cgo_unsafe_args"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked as "go:uintptrkeepalive", don't inline, since the keep
|
||||
// alive information is lost during inlining.
|
||||
//
|
||||
// TODO(prattmic): This is handled on calls during escape analysis,
|
||||
// which is after inlining. Move prior to inlining so the keep-alive is
|
||||
// maintained after inlining.
|
||||
if fn.Pragma&ir.UintptrKeepAlive != 0 {
|
||||
reason = "marked as having a keep-alive uintptr argument"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If marked as "go:uintptrescapes", don't inline, since the escape
|
||||
// information is lost during inlining.
|
||||
if fn.Pragma&ir.UintptrEscapes != 0 {
|
||||
reason = "marked as having an escaping uintptr argument"
|
||||
return reason
|
||||
}
|
||||
|
||||
// The nowritebarrierrec checker currently works at function
|
||||
// granularity, so inlining yeswritebarrierrec functions can confuse it
|
||||
// (#22342). As a workaround, disallow inlining them for now.
|
||||
if fn.Pragma&ir.Yeswritebarrierrec != 0 {
|
||||
reason = "marked go:yeswritebarrierrec"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If fn has no body (is defined outside of Go), cannot inline it.
|
||||
if len(fn.Body) == 0 {
|
||||
reason = "no function body"
|
||||
return reason
|
||||
}
|
||||
|
||||
// If fn is synthetic hash or eq function, cannot inline it.
|
||||
// The function is not generated in Unified IR frontend at this moment.
|
||||
if ir.IsEqOrHashFunc(fn) {
|
||||
reason = "type eq/hash function"
|
||||
return reason
|
||||
}
|
||||
|
||||
return ""
|
||||
}
|
||||
|
||||
// canDelayResults reports whether inlined calls to fn can delay
|
||||
// declaring the result parameter until the "return" statement.
|
||||
func canDelayResults(fn *ir.Func) bool {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue