github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/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 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  	"github.com/go-asm/go/cmd/compile/base"
    16  	"github.com/go-asm/go/cmd/compile/ir"
    17  	"github.com/go-asm/go/cmd/compile/typecheck"
    18  	"github.com/go-asm/go/cmd/compile/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  }