summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/devirtualize/devirtualize.go
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-16 19:19:13 +0000
commitccd992355df7192993c666236047820244914598 (patch)
treef00fea65147227b7743083c6148396f74cd66935 /src/cmd/compile/internal/devirtualize/devirtualize.go
parentInitial commit. (diff)
downloadgolang-1.21-ccd992355df7192993c666236047820244914598.tar.xz
golang-1.21-ccd992355df7192993c666236047820244914598.zip
Adding upstream version 1.21.8.upstream/1.21.8
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/cmd/compile/internal/devirtualize/devirtualize.go')
-rw-r--r--src/cmd/compile/internal/devirtualize/devirtualize.go158
1 files changed, 158 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/devirtualize/devirtualize.go b/src/cmd/compile/internal/devirtualize/devirtualize.go
new file mode 100644
index 0000000..b156b66
--- /dev/null
+++ b/src/cmd/compile/internal/devirtualize/devirtualize.go
@@ -0,0 +1,158 @@
+// 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
+
+ // 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.
+ goDeferCall := make(map[*ir.CallExpr]bool)
+ ir.VisitList(fn.Body, func(n ir.Node) {
+ switch n := n.(type) {
+ case *ir.GoDeferStmt:
+ if call, ok := n.Call.(*ir.CallExpr); ok {
+ goDeferCall[call] = true
+ }
+ return
+ case *ir.CallExpr:
+ if !goDeferCall[n] {
+ staticCall(n)
+ }
+ }
+ })
+}
+
+// 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
+ }
+ sel := call.X.(*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.Callee(ir.NewSelectorExpr(sel.Pos(), ir.OXDOT, dt, sel.Sel))
+ switch x.Op() {
+ case ir.ODOTMETH:
+ x := x.(*ir.SelectorExpr)
+ if base.Flag.LowerM != 0 {
+ base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ)
+ }
+ call.SetOp(ir.OCALLMETH)
+ call.X = x
+ case ir.ODOTINTER:
+ // Promoted method from embedded interface-typed field (#42279).
+ x := x.(*ir.SelectorExpr)
+ if base.Flag.LowerM != 0 {
+ base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ)
+ }
+ call.SetOp(ir.OCALLINTER)
+ call.X = x
+ default:
+ // TODO(mdempsky): Turn back into Fatalf after more testing.
+ if base.Flag.LowerM != 0 {
+ base.WarnfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op())
+ }
+ return
+ }
+
+ // 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.Results().Field(0).Type)
+ default:
+ call.SetType(ft.Results())
+ }
+
+ // Desugar OCALLMETH, if we created one (#57309).
+ typecheck.FixMethodCall(call)
+}