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  }