mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
The devirtualizer and inliner both want to recognize call expressions that are part of a go or defer statement. This CL refactors them to use a single CallExpr.GoDefer flag, which gets set during normalization of go/defer statements during typecheck. While here, drop some OCALLMETH assertions. Typecheck has been responsible for desugaring them into OCALLFUNC for a while now, and ssagen will check this again for us later anyway. Change-Id: I3fc370f4417431aae97239313da6fe523f512a2e Reviewed-on: https://go-review.googlesource.com/c/go/+/543657 Reviewed-by: Than McIntosh <thanm@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Auto-Submit: Matthew Dempsky <mdempsky@google.com>
152 lines
5 KiB
Go
152 lines
5 KiB
Go
// Copyright 2020 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 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 (
|
|
"cmd/compile/internal/base"
|
|
"cmd/compile/internal/ir"
|
|
"cmd/compile/internal/typecheck"
|
|
"cmd/compile/internal/types"
|
|
)
|
|
|
|
// Static devirtualizes calls within fn where possible when the concrete callee
|
|
// is available statically.
|
|
func Static(fn *ir.Func) {
|
|
ir.CurFunc = fn
|
|
|
|
ir.VisitList(fn.Body, func(n ir.Node) {
|
|
switch n := n.(type) {
|
|
case *ir.CallExpr:
|
|
staticCall(n)
|
|
}
|
|
})
|
|
}
|
|
|
|
// staticCall devirtualizes the given call if possible when the concrete callee
|
|
// is available statically.
|
|
func staticCall(call *ir.CallExpr) {
|
|
// For promoted methods (including value-receiver methods promoted
|
|
// to pointer-receivers), the interface method wrapper may contain
|
|
// expressions that can panic (e.g., ODEREF, ODOTPTR,
|
|
// ODOTINTER). Devirtualization involves inlining these expressions
|
|
// (and possible panics) to the call site. This normally isn't a
|
|
// problem, but for go/defer statements it can move the panic from
|
|
// when/where the call executes to the go/defer statement itself,
|
|
// which is a visible change in semantics (e.g., #52072). To prevent
|
|
// this, we skip devirtualizing calls within go/defer statements
|
|
// altogether.
|
|
if call.GoDefer {
|
|
return
|
|
}
|
|
|
|
if call.Op() != ir.OCALLINTER {
|
|
return
|
|
}
|
|
sel := call.Fun.(*ir.SelectorExpr)
|
|
r := ir.StaticValue(sel.X)
|
|
if r.Op() != ir.OCONVIFACE {
|
|
return
|
|
}
|
|
recv := r.(*ir.ConvExpr)
|
|
|
|
typ := recv.X.Type()
|
|
if typ.IsInterface() {
|
|
return
|
|
}
|
|
|
|
// If typ is a shape type, then it was a type argument originally
|
|
// and we'd need an indirect call through the dictionary anyway.
|
|
// We're unable to devirtualize this call.
|
|
if typ.IsShape() {
|
|
return
|
|
}
|
|
|
|
// If typ *has* a shape type, then it's an shaped, instantiated
|
|
// type like T[go.shape.int], and its methods (may) have an extra
|
|
// dictionary parameter. We could devirtualize this call if we
|
|
// could derive an appropriate dictionary argument.
|
|
//
|
|
// TODO(mdempsky): If typ has has a promoted non-generic method,
|
|
// then that method won't require a dictionary argument. We could
|
|
// still devirtualize those calls.
|
|
//
|
|
// TODO(mdempsky): We have the *runtime.itab in recv.TypeWord. It
|
|
// should be possible to compute the represented type's runtime
|
|
// dictionary from this (e.g., by adding a pointer from T[int]'s
|
|
// *runtime._type to .dict.T[int]; or by recognizing static
|
|
// references to go:itab.T[int],iface and constructing a direct
|
|
// reference to .dict.T[int]).
|
|
if typ.HasShape() {
|
|
if base.Flag.LowerM != 0 {
|
|
base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ)
|
|
}
|
|
return
|
|
}
|
|
|
|
// Further, if sel.X's type has a shape type, then it's a shaped
|
|
// interface type. In this case, the (non-dynamic) TypeAssertExpr
|
|
// we construct below would attempt to create an itab
|
|
// corresponding to this shaped interface type; but the actual
|
|
// itab pointer in the interface value will correspond to the
|
|
// original (non-shaped) interface type instead. These are
|
|
// functionally equivalent, but they have distinct pointer
|
|
// identities, which leads to the type assertion failing.
|
|
//
|
|
// TODO(mdempsky): We know the type assertion here is safe, so we
|
|
// could instead set a flag so that walk skips the itab check. For
|
|
// now, punting is easy and safe.
|
|
if sel.X.Type().HasShape() {
|
|
if base.Flag.LowerM != 0 {
|
|
base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped interface %v", call, sel.X.Type())
|
|
}
|
|
return
|
|
}
|
|
|
|
dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil)
|
|
dt.SetType(typ)
|
|
x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true)
|
|
switch x.Op() {
|
|
case ir.ODOTMETH:
|
|
if base.Flag.LowerM != 0 {
|
|
base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ)
|
|
}
|
|
call.SetOp(ir.OCALLMETH)
|
|
call.Fun = x
|
|
case ir.ODOTINTER:
|
|
// Promoted method from embedded interface-typed field (#42279).
|
|
if base.Flag.LowerM != 0 {
|
|
base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ)
|
|
}
|
|
call.SetOp(ir.OCALLINTER)
|
|
call.Fun = x
|
|
default:
|
|
base.FatalfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op())
|
|
}
|
|
|
|
// Duplicated logic from typecheck for function call return
|
|
// value types.
|
|
//
|
|
// Receiver parameter size may have changed; need to update
|
|
// call.Type to get correct stack offsets for result
|
|
// parameters.
|
|
types.CheckSize(x.Type())
|
|
switch ft := x.Type(); ft.NumResults() {
|
|
case 0:
|
|
case 1:
|
|
call.SetType(ft.Result(0).Type)
|
|
default:
|
|
call.SetType(ft.ResultsTuple())
|
|
}
|
|
|
|
// Desugar OCALLMETH, if we created one (#57309).
|
|
typecheck.FixMethodCall(call)
|
|
}
|