github.com/bir3/gocompiler@v0.3.205/src/cmd/compile/internal/devirtualize/devirtualize.go (about) 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 a simple "devirtualization" 6 // optimization pass, which replaces interface method calls with 7 // direct concrete-type method calls where possible. 8 package devirtualize 9 10 import ( 11 "github.com/bir3/gocompiler/src/cmd/compile/internal/base" 12 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 13 "github.com/bir3/gocompiler/src/cmd/compile/internal/typecheck" 14 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 15 ) 16 17 // Func devirtualizes calls within fn where possible. 18 func Func(fn *ir.Func) { 19 ir.CurFunc = fn 20 21 // For promoted methods (including value-receiver methods promoted to pointer-receivers), 22 // the interface method wrapper may contain expressions that can panic (e.g., ODEREF, ODOTPTR, ODOTINTER). 23 // Devirtualization involves inlining these expressions (and possible panics) to the call site. 24 // This normally isn't a problem, but for go/defer statements it can move the panic from when/where 25 // the call executes to the go/defer statement itself, which is a visible change in semantics (e.g., #52072). 26 // To prevent this, we skip devirtualizing calls within go/defer statements altogether. 27 goDeferCall := make(map[*ir.CallExpr]bool) 28 ir.VisitList(fn.Body, func(n ir.Node) { 29 switch n := n.(type) { 30 case *ir.GoDeferStmt: 31 if call, ok := n.Call.(*ir.CallExpr); ok { 32 goDeferCall[call] = true 33 } 34 return 35 case *ir.CallExpr: 36 if !goDeferCall[n] { 37 Call(n) 38 } 39 } 40 }) 41 } 42 43 // Call devirtualizes the given call if possible. 44 func Call(call *ir.CallExpr) { 45 if call.Op() != ir.OCALLINTER { 46 return 47 } 48 sel := call.X.(*ir.SelectorExpr) 49 r := ir.StaticValue(sel.X) 50 if r.Op() != ir.OCONVIFACE { 51 return 52 } 53 recv := r.(*ir.ConvExpr) 54 55 typ := recv.X.Type() 56 if typ.IsInterface() { 57 return 58 } 59 60 if base.Debug.Unified != 0 { 61 // N.B., stencil.go converts shape-typed values to interface type 62 // using OEFACE instead of OCONVIFACE, so devirtualization fails 63 // above instead. That's why this code is specific to unified IR. 64 65 // If typ is a shape type, then it was a type argument originally 66 // and we'd need an indirect call through the dictionary anyway. 67 // We're unable to devirtualize this call. 68 if typ.IsShape() { 69 return 70 } 71 72 // If typ *has* a shape type, then it's an shaped, instantiated 73 // type like T[go.shape.int], and its methods (may) have an extra 74 // dictionary parameter. We could devirtualize this call if we 75 // could derive an appropriate dictionary argument. 76 // 77 // TODO(mdempsky): If typ has has a promoted non-generic method, 78 // then that method won't require a dictionary argument. We could 79 // still devirtualize those calls. 80 // 81 // TODO(mdempsky): We have the *runtime.itab in recv.TypeWord. It 82 // should be possible to compute the represented type's runtime 83 // dictionary from this (e.g., by adding a pointer from T[int]'s 84 // *runtime._type to .dict.T[int]; or by recognizing static 85 // references to go:itab.T[int],iface and constructing a direct 86 // reference to .dict.T[int]). 87 if typ.HasShape() { 88 if base.Flag.LowerM != 0 { 89 base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ) 90 } 91 return 92 } 93 94 // Further, if sel.X's type has a shape type, then it's a shaped 95 // interface type. In this case, the (non-dynamic) TypeAssertExpr 96 // we construct below would attempt to create an itab 97 // corresponding to this shaped interface type; but the actual 98 // itab pointer in the interface value will correspond to the 99 // original (non-shaped) interface type instead. These are 100 // functionally equivalent, but they have distinct pointer 101 // identities, which leads to the type assertion failing. 102 // 103 // TODO(mdempsky): We know the type assertion here is safe, so we 104 // could instead set a flag so that walk skips the itab check. For 105 // now, punting is easy and safe. 106 if sel.X.Type().HasShape() { 107 if base.Flag.LowerM != 0 { 108 base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped interface %v", call, sel.X.Type()) 109 } 110 return 111 } 112 } 113 114 dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, nil) 115 dt.SetType(typ) 116 x := typecheck.Callee(ir.NewSelectorExpr(sel.Pos(), ir.OXDOT, dt, sel.Sel)) 117 switch x.Op() { 118 case ir.ODOTMETH: 119 x := x.(*ir.SelectorExpr) 120 if base.Flag.LowerM != 0 { 121 base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ) 122 } 123 call.SetOp(ir.OCALLMETH) 124 call.X = x 125 case ir.ODOTINTER: 126 // Promoted method from embedded interface-typed field (#42279). 127 x := x.(*ir.SelectorExpr) 128 if base.Flag.LowerM != 0 { 129 base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ) 130 } 131 call.SetOp(ir.OCALLINTER) 132 call.X = x 133 default: 134 // TODO(mdempsky): Turn back into Fatalf after more testing. 135 if base.Flag.LowerM != 0 { 136 base.WarnfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op()) 137 } 138 return 139 } 140 141 // Duplicated logic from typecheck for function call return 142 // value types. 143 // 144 // Receiver parameter size may have changed; need to update 145 // call.Type to get correct stack offsets for result 146 // parameters. 147 types.CheckSize(x.Type()) 148 switch ft := x.Type(); ft.NumResults() { 149 case 0: 150 case 1: 151 call.SetType(ft.Results().Field(0).Type) 152 default: 153 call.SetType(ft.Results()) 154 } 155 156 // Desugar OCALLMETH, if we created one (#57309). 157 typecheck.FixMethodCall(call) 158 }