Skip to content

Commit

Permalink
cmd/compile: enable PGO-driven call devirtualization
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
prattmic authored and gopherbot committed May 22, 2023
1 parent 6761bff commit 8c445b7
Show file tree
Hide file tree
Showing 11 changed files with 1,049 additions and 122 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 @@ -54,6 +54,7 @@ type DebugFlags struct {
PGOInline int `help:"enable profile-guided inlining" concurrent:"ok"`
PGOInlineCDFThreshold string `help:"cumulative threshold percentage for determining call sites as hot candidates for inlining" concurrent:"ok"`
PGOInlineBudget int `help:"inline budget for hot functions" concurrent:"ok"`
PGODevirtualize int `help:"enable profile-guided devirtualization" concurrent:"ok"`
WrapGlobalMapDbg int `help:"debug trace output for global map init wrapping"`
WrapGlobalMapCtl int `help:"global map init wrap control (0 => default, 1 => off, 2 => stress mode, no size cutoff)"`

Expand Down
1 change: 1 addition & 0 deletions src/cmd/compile/internal/base/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,7 @@ func ParseFlags() {
Debug.InlFuncsWithClosures = 1
Debug.InlStaticInit = 1
Debug.PGOInline = 1
Debug.PGODevirtualize = 1
Debug.SyncFrames = -1 // disable sync markers by default

Debug.Checkptr = -1 // so we can tell whether it is set explicitly
Expand Down
22 changes: 14 additions & 8 deletions src/cmd/compile/internal/devirtualize/devirtualize.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package devirtualize implements a simple "devirtualization"
// optimization pass, which replaces interface method calls with
// direct concrete-type method calls where possible.
// Package devirtualize implements two "devirtualization" optimization passes:
//
// - "Static" devirtualization which replaces interface method calls with
// direct concrete-type method calls where possible.
// - "Profile-guided" devirtualization which replaces indirect calls with a
// conditional direct call to the hottest concrete callee from a profile, as
// well as a fallback using the original indirect call.
package devirtualize

import (
Expand All @@ -14,8 +18,9 @@ import (
"cmd/compile/internal/types"
)

// Func devirtualizes calls within fn where possible.
func Func(fn *ir.Func) {
// Static devirtualizes calls within fn where possible when the concrete callee
// is available statically.
func Static(fn *ir.Func) {
ir.CurFunc = fn

// For promoted methods (including value-receiver methods promoted to pointer-receivers),
Expand All @@ -34,14 +39,15 @@ func Func(fn *ir.Func) {
return
case *ir.CallExpr:
if !goDeferCall[n] {
Call(n)
staticCall(n)
}
}
})
}

// Call devirtualizes the given call if possible.
func Call(call *ir.CallExpr) {
// staticCall devirtualizes the given call if possible when the concrete callee
// is available statically.
func staticCall(call *ir.CallExpr) {
if call.Op() != ir.OCALLINTER {
return
}
Expand Down
Loading

0 comments on commit 8c445b7

Please sign in to comment.