Source file src/cmd/compile/internal/devirtualize/devirtualize.go

     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  const go126ImprovedConcreteTypeAnalysis = true
    22  
    23  // StaticCall devirtualizes the given call if possible when the concrete callee
    24  // is available statically.
    25  func StaticCall(s *State, call *ir.CallExpr) {
    26  	// For promoted methods (including value-receiver methods promoted
    27  	// to pointer-receivers), the interface method wrapper may contain
    28  	// expressions that can panic (e.g., ODEREF, ODOTPTR,
    29  	// ODOTINTER). Devirtualization involves inlining these expressions
    30  	// (and possible panics) to the call site. This normally isn't a
    31  	// problem, but for go/defer statements it can move the panic from
    32  	// when/where the call executes to the go/defer statement itself,
    33  	// which is a visible change in semantics (e.g., #52072). To prevent
    34  	// this, we skip devirtualizing calls within go/defer statements
    35  	// altogether.
    36  	if call.GoDefer {
    37  		return
    38  	}
    39  
    40  	if call.Op() != ir.OCALLINTER {
    41  		return
    42  	}
    43  
    44  	sel := call.Fun.(*ir.SelectorExpr)
    45  	var typ *types.Type
    46  	if go126ImprovedConcreteTypeAnalysis {
    47  		typ = concreteType(s, sel.X)
    48  		if typ == nil {
    49  			return
    50  		}
    51  
    52  		// Don't create type-assertions that would be impossible at compile-time.
    53  		// This can happen in such case: any(0).(interface {A()}).A(), this typechecks without
    54  		// any errors, but will cause a runtime panic. We statically know that int(0) does not
    55  		// implement that interface, thus we skip the devirtualization, as it is not possible
    56  		// to make an assertion: any(0).(interface{A()}).(int) (int does not implement interface{A()}).
    57  		if !typecheck.Implements(typ, sel.X.Type()) {
    58  			return
    59  		}
    60  	} else {
    61  		r := ir.StaticValue(sel.X)
    62  		if r.Op() != ir.OCONVIFACE {
    63  			return
    64  		}
    65  		recv := r.(*ir.ConvExpr)
    66  		typ = recv.X.Type()
    67  		if typ.IsInterface() {
    68  			return
    69  		}
    70  	}
    71  
    72  	// If typ is a shape type, then it was a type argument originally
    73  	// and we'd need an indirect call through the dictionary anyway.
    74  	// We're unable to devirtualize this call.
    75  	if typ.IsShape() {
    76  		return
    77  	}
    78  
    79  	// If typ *has* a shape type, then it's a shaped, instantiated
    80  	// type like T[go.shape.int], and its methods (may) have an extra
    81  	// dictionary parameter. We could devirtualize this call if we
    82  	// could derive an appropriate dictionary argument.
    83  	//
    84  	// TODO(mdempsky): If typ has a promoted non-generic method,
    85  	// then that method won't require a dictionary argument. We could
    86  	// still devirtualize those calls.
    87  	//
    88  	// TODO(mdempsky): We have the *runtime.itab in recv.TypeWord. It
    89  	// should be possible to compute the represented type's runtime
    90  	// dictionary from this (e.g., by adding a pointer from T[int]'s
    91  	// *runtime._type to .dict.T[int]; or by recognizing static
    92  	// references to go:itab.T[int],iface and constructing a direct
    93  	// reference to .dict.T[int]).
    94  	if typ.HasShape() {
    95  		if base.Flag.LowerM != 0 {
    96  			base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped receiver %v", call, typ)
    97  		}
    98  		return
    99  	}
   100  
   101  	// Further, if sel.X's type has a shape type, then it's a shaped
   102  	// interface type. In this case, the (non-dynamic) TypeAssertExpr
   103  	// we construct below would attempt to create an itab
   104  	// corresponding to this shaped interface type; but the actual
   105  	// itab pointer in the interface value will correspond to the
   106  	// original (non-shaped) interface type instead. These are
   107  	// functionally equivalent, but they have distinct pointer
   108  	// identities, which leads to the type assertion failing.
   109  	//
   110  	// TODO(mdempsky): We know the type assertion here is safe, so we
   111  	// could instead set a flag so that walk skips the itab check. For
   112  	// now, punting is easy and safe.
   113  	if sel.X.Type().HasShape() {
   114  		if base.Flag.LowerM != 0 {
   115  			base.WarnfAt(call.Pos(), "cannot devirtualize %v: shaped interface %v", call, sel.X.Type())
   116  		}
   117  		return
   118  	}
   119  
   120  	dt := ir.NewTypeAssertExpr(sel.Pos(), sel.X, typ)
   121  
   122  	if go126ImprovedConcreteTypeAnalysis {
   123  		// Consider:
   124  		//
   125  		//	var v Iface
   126  		//	v.A()
   127  		//	v = &Impl{}
   128  		//
   129  		// Here in the devirtualizer, we determine the concrete type of v as being an *Impl,
   130  		// but it can still be a nil interface, we have not detected that. The v.(*Impl)
   131  		// type assertion that we make here would also have failed, but with a different
   132  		// panic "pkg.Iface is nil, not *pkg.Impl", where previously we would get a nil panic.
   133  		// We fix this, by introducing an additional nilcheck on the itab.
   134  		// Calling a method on a nil interface (in most cases) is a bug in a program, so it is fine
   135  		// to devirtualize and further (possibly) inline them, even though we would never reach
   136  		// the called function.
   137  		dt.UseNilPanic = true
   138  		dt.SetPos(call.Pos())
   139  	}
   140  
   141  	x := typecheck.XDotMethod(sel.Pos(), dt, sel.Sel, true)
   142  	switch x.Op() {
   143  	case ir.ODOTMETH:
   144  		if base.Flag.LowerM != 0 {
   145  			base.WarnfAt(call.Pos(), "devirtualizing %v to %v", sel, typ)
   146  		}
   147  		call.SetOp(ir.OCALLMETH)
   148  		call.Fun = x
   149  	case ir.ODOTINTER:
   150  		// Promoted method from embedded interface-typed field (#42279).
   151  		if base.Flag.LowerM != 0 {
   152  			base.WarnfAt(call.Pos(), "partially devirtualizing %v to %v", sel, typ)
   153  		}
   154  		call.SetOp(ir.OCALLINTER)
   155  		call.Fun = x
   156  	default:
   157  		base.FatalfAt(call.Pos(), "failed to devirtualize %v (%v)", x, x.Op())
   158  	}
   159  
   160  	// Duplicated logic from typecheck for function call return
   161  	// value types.
   162  	//
   163  	// Receiver parameter size may have changed; need to update
   164  	// call.Type to get correct stack offsets for result
   165  	// parameters.
   166  	types.CheckSize(x.Type())
   167  	switch ft := x.Type(); ft.NumResults() {
   168  	case 0:
   169  	case 1:
   170  		call.SetType(ft.Result(0).Type)
   171  	default:
   172  		call.SetType(ft.ResultsTuple())
   173  	}
   174  
   175  	// Desugar OCALLMETH, if we created one (#57309).
   176  	typecheck.FixMethodCall(call)
   177  }
   178  
   179  const concreteTypeDebug = false
   180  
   181  // concreteType determines the concrete type of n, following OCONVIFACEs and type asserts.
   182  // Returns nil when the concrete type could not be determined, or when there are multiple
   183  // (different) types assigned to an interface.
   184  func concreteType(s *State, n ir.Node) (typ *types.Type) {
   185  	if concreteTypeDebug {
   186  		base.Warn("concreteType(%v) - analyzing", n)
   187  		defer func() {
   188  			t := typ.String()
   189  			if typ == nil {
   190  				t = "<nil> (unknown static type)"
   191  			}
   192  			base.Warn("concreteType(%v) -> %v", n, t)
   193  		}()
   194  	}
   195  
   196  	typ = concreteType1(s, n, make(map[*ir.Name]struct{}))
   197  	if typ == &noType {
   198  		return nil
   199  	}
   200  	if typ != nil && typ.IsInterface() {
   201  		base.FatalfAt(n.Pos(), "typ.IsInterface() = true; want = false; typ = %v", typ)
   202  	}
   203  	return typ
   204  }
   205  
   206  // noType is a sentinel value returned by [concreteType1].
   207  var noType types.Type
   208  
   209  // concreteType1 analyzes the node n and returns its concrete type if it is statically known.
   210  // Otherwise, it returns a nil Type, indicating that a concrete type was not determined.
   211  // When n is known to be statically nil or a self-assignment is detected, it returns a sentinel [noType] type instead.
   212  func concreteType1(s *State, n ir.Node, seen map[*ir.Name]struct{}) (outT *types.Type) {
   213  	nn := n // for debug messages
   214  
   215  	if concreteTypeDebug {
   216  		defer func() {
   217  			t := "&noType"
   218  			if outT != &noType {
   219  				t = outT.String()
   220  			}
   221  			if outT == nil {
   222  				t = "<nil> (unknown static type)"
   223  			}
   224  			base.Warn("concreteType1(%v) -> %v", nn, t)
   225  		}()
   226  	}
   227  
   228  	for {
   229  		if concreteTypeDebug {
   230  			base.Warn("concreteType1(%v): analyzing %v", nn, n)
   231  		}
   232  
   233  		if !n.Type().IsInterface() {
   234  			return n.Type()
   235  		}
   236  
   237  		switch n1 := n.(type) {
   238  		case *ir.ConvExpr:
   239  			if n1.Op() == ir.OCONVNOP {
   240  				if !n1.Type().IsInterface() || !types.Identical(n1.Type().Underlying(), n1.X.Type().Underlying()) {
   241  					// As we check (directly before this switch) whether n is an interface, thus we should only reach
   242  					// here for iface conversions where both operands are the same.
   243  					base.FatalfAt(n1.Pos(), "not identical/interface types found n1.Type = %v; n1.X.Type = %v", n1.Type(), n1.X.Type())
   244  				}
   245  				n = n1.X
   246  				continue
   247  			}
   248  			if n1.Op() == ir.OCONVIFACE {
   249  				n = n1.X
   250  				continue
   251  			}
   252  		case *ir.InlinedCallExpr:
   253  			if n1.Op() == ir.OINLCALL {
   254  				n = n1.SingleResult()
   255  				continue
   256  			}
   257  		case *ir.ParenExpr:
   258  			n = n1.X
   259  			continue
   260  		case *ir.TypeAssertExpr:
   261  			n = n1.X
   262  			continue
   263  		}
   264  		break
   265  	}
   266  
   267  	if n.Op() != ir.ONAME {
   268  		return nil
   269  	}
   270  
   271  	name := n.(*ir.Name).Canonical()
   272  	if name.Class != ir.PAUTO {
   273  		return nil
   274  	}
   275  
   276  	if name.Op() != ir.ONAME {
   277  		base.FatalfAt(name.Pos(), "name.Op = %v; want = ONAME", n.Op())
   278  	}
   279  
   280  	// name.Curfn must be set, as we checked name.Class != ir.PAUTO before.
   281  	if name.Curfn == nil {
   282  		base.FatalfAt(name.Pos(), "name.Curfn = nil; want not nil")
   283  	}
   284  
   285  	if name.Addrtaken() {
   286  		return nil // conservatively assume it's reassigned with a different type indirectly
   287  	}
   288  
   289  	if _, ok := seen[name]; ok {
   290  		return &noType // Already analyzed assignments to name, no need to do that twice.
   291  	}
   292  	seen[name] = struct{}{}
   293  
   294  	if concreteTypeDebug {
   295  		base.Warn("concreteType1(%v): analyzing assignments to %v", nn, name)
   296  	}
   297  
   298  	var typ *types.Type
   299  	for _, v := range s.assignments(name) {
   300  		var t *types.Type
   301  		switch v := v.(type) {
   302  		case *types.Type:
   303  			t = v
   304  		case ir.Node:
   305  			t = concreteType1(s, v, seen)
   306  			if t == &noType {
   307  				continue
   308  			}
   309  		}
   310  		if t == nil {
   311  			return nil // unknown concrete type
   312  		}
   313  
   314  		// Methods are only declared on named types, and each named type
   315  		// is represented by a unique [*types.Type], thus pointer comparison
   316  		// is fine here.
   317  		//
   318  		// The only scenario where [types.IdenticalStrict] could help here is with
   319  		// unnamed struct types that embed another type (e.g. foo = struct { Impl }{}).
   320  		// However, such patterns are uncommon and not worth the additional complexity
   321  		// in the devirtualizer.
   322  		if typ != nil && typ != t {
   323  			return nil // assigned with a different type
   324  		}
   325  
   326  		typ = t
   327  	}
   328  
   329  	if typ == nil {
   330  		// Variable either declared with zero value, or only assigned with nil.
   331  		return &noType
   332  	}
   333  
   334  	return typ
   335  }
   336  
   337  // assignment can be one of:
   338  // - nil - assignment from an interface type.
   339  // - *types.Type - assignment from a concrete type (non-interface).
   340  // - ir.Node - assignment from an ir.Node.
   341  //
   342  // In most cases assignment should be an [ir.Node], but in cases where we
   343  // do not follow the data-flow, we return either a concrete type (*types.Type) or a nil.
   344  // For example in range over a slice, if the slice elem is of an interface type, then we return
   345  // a nil, otherwise the elem's concrete type (We do so because we do not analyze assignment to the
   346  // slice being ranged-over).
   347  type assignment any
   348  
   349  // State holds precomputed state for use in [StaticCall].
   350  type State struct {
   351  	// ifaceAssignments maps interface variables to all their assignments
   352  	// defined inside functions stored in the analyzedFuncs set.
   353  	// Note: it does not include direct assignments to nil.
   354  	ifaceAssignments map[*ir.Name][]assignment
   355  
   356  	// ifaceCallExprAssigns stores every [*ir.CallExpr], which has an interface
   357  	// result, that is assigned to a variable.
   358  	ifaceCallExprAssigns map[*ir.CallExpr][]ifaceAssignRef
   359  
   360  	// analyzedFuncs is a set of Funcs that were analyzed for iface assignments.
   361  	analyzedFuncs map[*ir.Func]struct{}
   362  }
   363  
   364  type ifaceAssignRef struct {
   365  	name            *ir.Name // ifaceAssignments[name]
   366  	assignmentIndex int      // ifaceAssignments[name][assignmentIndex]
   367  	returnIndex     int      // (*ir.CallExpr).Result(returnIndex)
   368  }
   369  
   370  // InlinedCall updates the [State] to take into account a newly inlined call.
   371  func (s *State) InlinedCall(fun *ir.Func, origCall *ir.CallExpr, inlinedCall *ir.InlinedCallExpr) {
   372  	if _, ok := s.analyzedFuncs[fun]; !ok {
   373  		// Full analyze has not been yet executed for the provided function, so we can skip it for now.
   374  		// When no devirtualization happens in a function, it is unnecessary to analyze it.
   375  		return
   376  	}
   377  
   378  	// Analyze assignments in the newly inlined function.
   379  	s.analyze(inlinedCall.Init())
   380  	s.analyze(inlinedCall.Body)
   381  
   382  	refs, ok := s.ifaceCallExprAssigns[origCall]
   383  	if !ok {
   384  		return
   385  	}
   386  	delete(s.ifaceCallExprAssigns, origCall)
   387  
   388  	// Update assignments to reference the new ReturnVars of the inlined call.
   389  	for _, ref := range refs {
   390  		vt := &s.ifaceAssignments[ref.name][ref.assignmentIndex]
   391  		if *vt != nil {
   392  			base.Fatalf("unexpected non-nil assignment")
   393  		}
   394  		if concreteTypeDebug {
   395  			base.Warn(
   396  				"InlinedCall(%v, %v): replacing interface node in (%v,%v) to %v (typ %v)",
   397  				origCall, inlinedCall, ref.name, ref.assignmentIndex,
   398  				inlinedCall.ReturnVars[ref.returnIndex],
   399  				inlinedCall.ReturnVars[ref.returnIndex].Type(),
   400  			)
   401  		}
   402  
   403  		// Update ifaceAssignments with an ir.Node from the inlined function’s ReturnVars.
   404  		// This may enable future devirtualization of calls that reference ref.name.
   405  		// We will get calls to [StaticCall] from the interleaved package,
   406  		// to try devirtualize such calls afterwards.
   407  		*vt = inlinedCall.ReturnVars[ref.returnIndex]
   408  	}
   409  }
   410  
   411  // assignments returns all assignments to n.
   412  func (s *State) assignments(n *ir.Name) []assignment {
   413  	fun := n.Curfn
   414  	if fun == nil {
   415  		base.FatalfAt(n.Pos(), "n.Curfn = <nil>")
   416  	}
   417  	if n.Class != ir.PAUTO {
   418  		base.FatalfAt(n.Pos(), "n.Class = %v; want = PAUTO", n.Class)
   419  	}
   420  
   421  	if !n.Type().IsInterface() {
   422  		base.FatalfAt(n.Pos(), "name passed to assignments is not of an interface type: %v", n.Type())
   423  	}
   424  
   425  	// Analyze assignments in func, if not analyzed before.
   426  	if _, ok := s.analyzedFuncs[fun]; !ok {
   427  		if concreteTypeDebug {
   428  			base.Warn("assignments(): analyzing assignments in %v func", fun)
   429  		}
   430  		if s.analyzedFuncs == nil {
   431  			s.ifaceAssignments = make(map[*ir.Name][]assignment)
   432  			s.ifaceCallExprAssigns = make(map[*ir.CallExpr][]ifaceAssignRef)
   433  			s.analyzedFuncs = make(map[*ir.Func]struct{})
   434  		}
   435  		s.analyzedFuncs[fun] = struct{}{}
   436  		s.analyze(fun.Init())
   437  		s.analyze(fun.Body)
   438  	}
   439  
   440  	return s.ifaceAssignments[n]
   441  }
   442  
   443  // analyze analyzes every assignment to interface variables in nodes, updating [State].
   444  func (s *State) analyze(nodes ir.Nodes) {
   445  	assign := func(name ir.Node, assignment assignment) (*ir.Name, int) {
   446  		if name == nil || name.Op() != ir.ONAME || ir.IsBlank(name) {
   447  			return nil, -1
   448  		}
   449  
   450  		n, ok := ir.OuterValue(name).(*ir.Name)
   451  		if !ok || n.Curfn == nil {
   452  			return nil, -1
   453  		}
   454  
   455  		// Do not track variables that are not of interface types.
   456  		// For devirtualization they are unnecessary, we will not even look them up.
   457  		if !n.Type().IsInterface() {
   458  			return nil, -1
   459  		}
   460  
   461  		n = n.Canonical()
   462  		if n.Op() != ir.ONAME {
   463  			base.FatalfAt(n.Pos(), "n.Op = %v; want = ONAME", n.Op())
   464  		}
   465  		if n.Class != ir.PAUTO {
   466  			return nil, -1
   467  		}
   468  
   469  		switch a := assignment.(type) {
   470  		case nil:
   471  		case *types.Type:
   472  			if a != nil && a.IsInterface() {
   473  				assignment = nil // non-concrete type
   474  			}
   475  		case ir.Node:
   476  			// nil assignment, we can safely ignore them, see [StaticCall].
   477  			if ir.IsNil(a) {
   478  				return nil, -1
   479  			}
   480  		default:
   481  			base.Fatalf("unexpected type: %v", assignment)
   482  		}
   483  
   484  		if concreteTypeDebug {
   485  			base.Warn("analyze(): assignment found %v = %v", name, assignment)
   486  		}
   487  
   488  		s.ifaceAssignments[n] = append(s.ifaceAssignments[n], assignment)
   489  		return n, len(s.ifaceAssignments[n]) - 1
   490  	}
   491  
   492  	var do func(n ir.Node)
   493  	do = func(n ir.Node) {
   494  		switch n.Op() {
   495  		case ir.OAS:
   496  			n := n.(*ir.AssignStmt)
   497  			if rhs := n.Y; rhs != nil {
   498  				for {
   499  					if r, ok := rhs.(*ir.ParenExpr); ok {
   500  						rhs = r.X
   501  						continue
   502  					}
   503  					break
   504  				}
   505  				if call, ok := rhs.(*ir.CallExpr); ok && call.Fun != nil {
   506  					retTyp := call.Fun.Type().Results()[0].Type
   507  					n, idx := assign(n.X, retTyp)
   508  					if n != nil && retTyp.IsInterface() {
   509  						// We have a call expression, that returns an interface, store it for later evaluation.
   510  						// In case this func gets inlined later, we will update the assignment (added before)
   511  						// with a reference to ReturnVars, see [State.InlinedCall], which might allow for future devirtualizing of n.X.
   512  						s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, 0})
   513  					}
   514  				} else {
   515  					assign(n.X, rhs)
   516  				}
   517  			}
   518  		case ir.OAS2:
   519  			n := n.(*ir.AssignListStmt)
   520  			for i, p := range n.Lhs {
   521  				if n.Rhs[i] != nil {
   522  					assign(p, n.Rhs[i])
   523  				}
   524  			}
   525  		case ir.OAS2DOTTYPE:
   526  			n := n.(*ir.AssignListStmt)
   527  			if n.Rhs[0] == nil {
   528  				base.FatalfAt(n.Pos(), "n.Rhs[0] == nil; n = %v", n)
   529  			}
   530  			assign(n.Lhs[0], n.Rhs[0])
   531  			assign(n.Lhs[1], nil) // boolean does not have methods to devirtualize
   532  		case ir.OAS2MAPR, ir.OAS2RECV, ir.OSELRECV2:
   533  			n := n.(*ir.AssignListStmt)
   534  			if n.Rhs[0] == nil {
   535  				base.FatalfAt(n.Pos(), "n.Rhs[0] == nil; n = %v", n)
   536  			}
   537  			assign(n.Lhs[0], n.Rhs[0].Type())
   538  			assign(n.Lhs[1], nil) // boolean does not have methods to devirtualize
   539  		case ir.OAS2FUNC:
   540  			n := n.(*ir.AssignListStmt)
   541  			rhs := n.Rhs[0]
   542  			for {
   543  				if r, ok := rhs.(*ir.ParenExpr); ok {
   544  					rhs = r.X
   545  					continue
   546  				}
   547  				break
   548  			}
   549  			if call, ok := rhs.(*ir.CallExpr); ok {
   550  				for i, p := range n.Lhs {
   551  					retTyp := call.Fun.Type().Results()[i].Type
   552  					n, idx := assign(p, retTyp)
   553  					if n != nil && retTyp.IsInterface() {
   554  						// We have a call expression, that returns an interface, store it for later evaluation.
   555  						// In case this func gets inlined later, we will update the assignment (added before)
   556  						// with a reference to ReturnVars, see [State.InlinedCall], which might allow for future devirtualizing of n.X.
   557  						s.ifaceCallExprAssigns[call] = append(s.ifaceCallExprAssigns[call], ifaceAssignRef{n, idx, i})
   558  					}
   559  				}
   560  			} else if call, ok := rhs.(*ir.InlinedCallExpr); ok {
   561  				for i, p := range n.Lhs {
   562  					assign(p, call.ReturnVars[i])
   563  				}
   564  			} else {
   565  				base.FatalfAt(n.Pos(), "unexpected type %T in OAS2FUNC Rhs[0]", call)
   566  			}
   567  		case ir.ORANGE:
   568  			n := n.(*ir.RangeStmt)
   569  			xTyp := n.X.Type()
   570  
   571  			// Range over an array pointer.
   572  			if xTyp.IsPtr() && xTyp.Elem().IsArray() {
   573  				xTyp = xTyp.Elem()
   574  			}
   575  
   576  			if xTyp.IsArray() || xTyp.IsSlice() {
   577  				assign(n.Key, nil) // integer does not have methods to devirtualize
   578  				assign(n.Value, xTyp.Elem())
   579  			} else if xTyp.IsChan() {
   580  				assign(n.Key, xTyp.Elem())
   581  				base.AssertfAt(n.Value == nil, n.Pos(), "n.Value != nil in range over chan")
   582  			} else if xTyp.IsMap() {
   583  				assign(n.Key, xTyp.Key())
   584  				assign(n.Value, xTyp.Elem())
   585  			} else if xTyp.IsInteger() || xTyp.IsString() {
   586  				// Range over int/string, results do not have methods, so nothing to devirtualize.
   587  				assign(n.Key, nil)
   588  				assign(n.Value, nil)
   589  			} else {
   590  				// We will not reach here in case of a range-over-func, as it is
   591  				// rewritten to function calls in the noder package.
   592  				base.FatalfAt(n.Pos(), "range over unexpected type %v", n.X.Type())
   593  			}
   594  		case ir.OSWITCH:
   595  			n := n.(*ir.SwitchStmt)
   596  			if guard, ok := n.Tag.(*ir.TypeSwitchGuard); ok {
   597  				for _, v := range n.Cases {
   598  					if v.Var == nil {
   599  						base.Assert(guard.Tag == nil)
   600  						continue
   601  					}
   602  					assign(v.Var, guard.X)
   603  				}
   604  			}
   605  		case ir.OCLOSURE:
   606  			n := n.(*ir.ClosureExpr)
   607  			if _, ok := s.analyzedFuncs[n.Func]; !ok {
   608  				s.analyzedFuncs[n.Func] = struct{}{}
   609  				ir.Visit(n.Func, do)
   610  			}
   611  		}
   612  	}
   613  	ir.VisitList(nodes, do)
   614  }
   615  

View as plain text