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. 12 package devirtualize 13 14 import ( 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. 23 func 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