1// Copyright 2020 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5// Package devirtualize implements two "devirtualization" optimization passes:
6//
7//   - "Static" devirtualization which replaces interface method calls with
8//     direct concrete-type method calls where possible.
9//   - "Profile-guided" devirtualization which replaces indirect calls with a
10//     conditional direct call to the hottest concrete callee from a profile, as
11//     well as a fallback using the original indirect call.
12package devirtualize
13
14import (
15	"cmd/compile/internal/base"
16	"cmd/compile/internal/ir"
17	"cmd/compile/internal/typecheck"
18	"cmd/compile/internal/types"
19)
20
21// StaticCall devirtualizes the given call if possible when the concrete callee
22// is available statically.
23func StaticCall(call *ir.CallExpr) {
24	// For promoted methods (including value-receiver methods promoted
25	// to pointer-receivers), the interface method wrapper may contain
26	// expressions that can panic (e.g., ODEREF, ODOTPTR,
27	// ODOTINTER). Devirtualization involves inlining these expressions
28	// (and possible panics) to the call site. This normally isn't a
29	// problem, but for go/defer statements it can move the panic from
30	// when/where the call executes to the go/defer statement itself,
31	// which is a visible change in semantics (e.g., #52072). To prevent
32	// this, we skip devirtualizing calls within go/defer statements
33	// altogether.
34	if call.GoDefer {
35		return
36	}
37
38	if call.Op() != ir.OCALLINTER {
39		return
40	}
41
42	sel := call.Fun.(*ir.SelectorExpr)
43	r := ir.StaticValue(sel.X)
44	if r.Op() != ir.OCONVIFACE {
45		return
46	}
47	recv := r.(*ir.ConvExpr)
48
49	typ := recv.X.Type()
50	if typ.IsInterface() {
51		return
52	}
53
54	// If typ is a shape type, then it was a type argument originally
55	// and we'd need an indirect call through the dictionary anyway.
56	// We're unable to devirtualize this call.
57	if typ.IsShape() {
58		return
59	}
60
61	// If typ *has* a shape type, then it's a shaped, instantiated
62	// type like T[go.shape.int], and its methods (may) have an extra
63	// dictionary parameter. We could devirtualize this call if we
64	// could derive an appropriate dictionary argument.
65	//
66	// TODO(mdempsky): If typ has has a promoted non-generic method,
67	// then that method won't require a dictionary argument. We could
68	// still devirtualize those calls.
69	//
70	// TODO(mdempsky): We have the *runtime.itab in recv.TypeWord. It
71	// should be possible to compute the represented type's runtime
72	// dictionary from this (e.g., by adding a pointer from T[int]'s
73	// *runtime._type to .dict.T[int]; or by recognizing static
74	// references to go:itab.T[int],iface and constructing a direct
75	// reference to .dict.T[int]).
76	if typ.HasShape() {
77		if base.Flag.LowerM != 0 {
78			base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ)
79		}
80		return
81	}
82
83	// Further, if sel.X's type has a shape type, then it's a shaped
84	// interface type. In this case, the (non-dynamic) TypeAssertExpr
85	// we construct below would attempt to create an itab
86	// corresponding to this shaped interface type; but the actual
87	// itab pointer in the interface value will correspond to the
88	// original (non-shaped) interface type instead. These are
89	// functionally equivalent, but they have distinct pointer
90	// identities, which leads to the type assertion failing.
91	//
92	// TODO(mdempsky): We know the type assertion here is safe, so we
93	// could instead set a flag so that walk skips the itab check. For
94	// now, punting is easy and safe.
95	if sel.X.Type().HasShape() {
96		if base.Flag.LowerM != 0 {
97			base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped interface %v", call, sel.X.Type())
98		}
99		return
100	}
101
102	dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil)
103	dt.SetType(typ)
104	x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true)
105	switch x.Op() {
106	case ir.ODOTMETH:
107		if base.Flag.LowerM != 0 {
108			base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ)
109		}
110		call.SetOp(ir.OCALLMETH)
111		call.Fun = x
112	case ir.ODOTINTER:
113		// Promoted method from embedded interface-typed field (#42279).
114		if base.Flag.LowerM != 0 {
115			base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ)
116		}
117		call.SetOp(ir.OCALLINTER)
118		call.Fun = x
119	default:
120		base.FatalfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op())
121	}
122
123	// Duplicated logic from typecheck for function call return
124	// value types.
125	//
126	// Receiver parameter size may have changed; need to update
127	// call.Type to get correct stack offsets for result
128	// parameters.
129	types.CheckSize(x.Type())
130	switch ft := x.Type(); ft.NumResults() {
131	case 0:
132	case 1:
133		call.SetType(ft.Result(0).Type)
134	default:
135		call.SetType(ft.ResultsTuple())
136	}
137
138	// Desugar OCALLMETH, if we created one (#57309).
139	typecheck.FixMethodCall(call)
140}
141