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

     1  // Copyright 2018 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 escape
     6  
     7  import (
     8  	"fmt"
     9  	"go/constant"
    10  	"go/token"
    11  	"internal/goexperiment"
    12  
    13  	"cmd/compile/internal/base"
    14  	"cmd/compile/internal/ir"
    15  	"cmd/compile/internal/logopt"
    16  	"cmd/compile/internal/typecheck"
    17  	"cmd/compile/internal/types"
    18  	"cmd/internal/src"
    19  )
    20  
    21  // Escape analysis.
    22  //
    23  // Here we analyze functions to determine which Go variables
    24  // (including implicit allocations such as calls to "new" or "make",
    25  // composite literals, etc.) can be allocated on the stack. The two
    26  // key invariants we have to ensure are: (1) pointers to stack objects
    27  // cannot be stored in the heap, and (2) pointers to a stack object
    28  // cannot outlive that object (e.g., because the declaring function
    29  // returned and destroyed the object's stack frame, or its space is
    30  // reused across loop iterations for logically distinct variables).
    31  //
    32  // We implement this with a static data-flow analysis of the AST.
    33  // First, we construct a directed weighted graph where vertices
    34  // (termed "locations") represent variables allocated by statements
    35  // and expressions, and edges represent assignments between variables
    36  // (with weights representing addressing/dereference counts).
    37  //
    38  // Next we walk the graph looking for assignment paths that might
    39  // violate the invariants stated above. If a variable v's address is
    40  // stored in the heap or elsewhere that may outlive it, then v is
    41  // marked as requiring heap allocation.
    42  //
    43  // To support interprocedural analysis, we also record data-flow from
    44  // each function's parameters to the heap and to its result
    45  // parameters. This information is summarized as "parameter tags",
    46  // which are used at static call sites to improve escape analysis of
    47  // function arguments.
    48  
    49  // Constructing the location graph.
    50  //
    51  // Every allocating statement (e.g., variable declaration) or
    52  // expression (e.g., "new" or "make") is first mapped to a unique
    53  // "location."
    54  //
    55  // We also model every Go assignment as a directed edges between
    56  // locations. The number of dereference operations minus the number of
    57  // addressing operations is recorded as the edge's weight (termed
    58  // "derefs"). For example:
    59  //
    60  //     p = &q    // -1
    61  //     p = q     //  0
    62  //     p = *q    //  1
    63  //     p = **q   //  2
    64  //
    65  //     p = **&**&q  // 2
    66  //
    67  // Note that the & operator can only be applied to addressable
    68  // expressions, and the expression &x itself is not addressable, so
    69  // derefs cannot go below -1.
    70  //
    71  // Every Go language construct is lowered into this representation,
    72  // generally without sensitivity to flow, path, or context; and
    73  // without distinguishing elements within a compound variable. For
    74  // example:
    75  //
    76  //     var x struct { f, g *int }
    77  //     var u []*int
    78  //
    79  //     x.f = u[0]
    80  //
    81  // is modeled simply as
    82  //
    83  //     x = *u
    84  //
    85  // That is, we don't distinguish x.f from x.g, or u[0] from u[1],
    86  // u[2], etc. However, we do record the implicit dereference involved
    87  // in indexing a slice.
    88  
    89  // A batch holds escape analysis state that's shared across an entire
    90  // batch of functions being analyzed at once.
    91  type batch struct {
    92  	allLocs         []*location
    93  	closures        []closure
    94  	reassignOracles map[*ir.Func]*ir.ReassignOracle
    95  
    96  	heapLoc    location
    97  	mutatorLoc location
    98  	calleeLoc  location
    99  	blankLoc   location
   100  }
   101  
   102  // A closure holds a closure expression and its spill hole (i.e.,
   103  // where the hole representing storing into its closure record).
   104  type closure struct {
   105  	k   hole
   106  	clo *ir.ClosureExpr
   107  }
   108  
   109  // An escape holds state specific to a single function being analyzed
   110  // within a batch.
   111  type escape struct {
   112  	*batch
   113  
   114  	curfn *ir.Func // function being analyzed
   115  
   116  	labels map[*types.Sym]labelState // known labels
   117  
   118  	// loopDepth counts the current loop nesting depth within
   119  	// curfn. It increments within each "for" loop and at each
   120  	// label with a corresponding backwards "goto" (i.e.,
   121  	// unstructured loop).
   122  	loopDepth int
   123  }
   124  
   125  func Funcs(all []*ir.Func) {
   126  	// Make a cache of ir.ReassignOracles. The cache is lazily populated.
   127  	// TODO(thepudds): consider adding a field on ir.Func instead. We might also be able
   128  	// to use that field elsewhere, like in walk. See discussion in https://go.dev/cl/688075.
   129  	reassignOracles := make(map[*ir.Func]*ir.ReassignOracle)
   130  
   131  	ir.VisitFuncsBottomUp(all, func(list []*ir.Func, recursive bool) {
   132  		Batch(list, reassignOracles)
   133  	})
   134  }
   135  
   136  // Batch performs escape analysis on a minimal batch of
   137  // functions.
   138  func Batch(fns []*ir.Func, reassignOracles map[*ir.Func]*ir.ReassignOracle) {
   139  	var b batch
   140  	b.heapLoc.attrs = attrEscapes | attrPersists | attrMutates | attrCalls
   141  	b.mutatorLoc.attrs = attrMutates
   142  	b.calleeLoc.attrs = attrCalls
   143  	b.reassignOracles = reassignOracles
   144  
   145  	// Construct data-flow graph from syntax trees.
   146  	for _, fn := range fns {
   147  		if base.Flag.W > 1 {
   148  			s := fmt.Sprintf("\nbefore escape %v", fn)
   149  			ir.Dump(s, fn)
   150  		}
   151  		b.initFunc(fn)
   152  	}
   153  	for _, fn := range fns {
   154  		if !fn.IsClosure() {
   155  			b.walkFunc(fn)
   156  		}
   157  	}
   158  
   159  	// We've walked the function bodies, so we've seen everywhere a
   160  	// variable might be reassigned or have its address taken. Now we
   161  	// can decide whether closures should capture their free variables
   162  	// by value or reference.
   163  	for _, closure := range b.closures {
   164  		b.flowClosure(closure.k, closure.clo)
   165  	}
   166  	b.closures = nil
   167  
   168  	for _, loc := range b.allLocs {
   169  		// Try to replace some non-constant expressions with literals.
   170  		b.rewriteWithLiterals(loc.n, loc.curfn)
   171  
   172  		// Check if the node must be heap allocated for certain reasons
   173  		// such as OMAKESLICE for a large slice.
   174  		if why := HeapAllocReason(loc.n); why != "" {
   175  			b.flow(b.heapHole().addr(loc.n, why), loc)
   176  		}
   177  	}
   178  
   179  	b.walkAll()
   180  	b.finish(fns)
   181  }
   182  
   183  func (b *batch) with(fn *ir.Func) *escape {
   184  	return &escape{
   185  		batch:     b,
   186  		curfn:     fn,
   187  		loopDepth: 1,
   188  	}
   189  }
   190  
   191  func (b *batch) initFunc(fn *ir.Func) {
   192  	e := b.with(fn)
   193  	if fn.Esc() != escFuncUnknown {
   194  		base.Fatalf("unexpected node: %v", fn)
   195  	}
   196  	fn.SetEsc(escFuncPlanned)
   197  	if base.Flag.LowerM > 3 {
   198  		ir.Dump("escAnalyze", fn)
   199  	}
   200  
   201  	// Allocate locations for local variables.
   202  	for _, n := range fn.Dcl {
   203  		e.newLoc(n, true)
   204  	}
   205  
   206  	// Also for hidden parameters (e.g., the ".this" parameter to a
   207  	// method value wrapper).
   208  	if fn.OClosure == nil {
   209  		for _, n := range fn.ClosureVars {
   210  			e.newLoc(n.Canonical(), true)
   211  		}
   212  	}
   213  
   214  	// Initialize resultIndex for result parameters.
   215  	for i, f := range fn.Type().Results() {
   216  		e.oldLoc(f.Nname.(*ir.Name)).resultIndex = 1 + i
   217  	}
   218  }
   219  
   220  func (b *batch) walkFunc(fn *ir.Func) {
   221  	e := b.with(fn)
   222  	fn.SetEsc(escFuncStarted)
   223  
   224  	// Identify labels that mark the head of an unstructured loop.
   225  	ir.Visit(fn, func(n ir.Node) {
   226  		switch n.Op() {
   227  		case ir.OLABEL:
   228  			n := n.(*ir.LabelStmt)
   229  			if n.Label.IsBlank() {
   230  				break
   231  			}
   232  			if e.labels == nil {
   233  				e.labels = make(map[*types.Sym]labelState)
   234  			}
   235  			e.labels[n.Label] = nonlooping
   236  
   237  		case ir.OGOTO:
   238  			// If we visited the label before the goto,
   239  			// then this is a looping label.
   240  			n := n.(*ir.BranchStmt)
   241  			if e.labels[n.Label] == nonlooping {
   242  				e.labels[n.Label] = looping
   243  			}
   244  		}
   245  	})
   246  
   247  	e.block(fn.Body)
   248  
   249  	if len(e.labels) != 0 {
   250  		base.FatalfAt(fn.Pos(), "leftover labels after walkFunc")
   251  	}
   252  }
   253  
   254  func (b *batch) flowClosure(k hole, clo *ir.ClosureExpr) {
   255  	for _, cv := range clo.Func.ClosureVars {
   256  		n := cv.Canonical()
   257  		loc := b.oldLoc(cv)
   258  		if !loc.captured {
   259  			base.FatalfAt(cv.Pos(), "closure variable never captured: %v", cv)
   260  		}
   261  
   262  		// Capture by value for variables <= 128 bytes that are never reassigned.
   263  		n.SetByval(!loc.addrtaken && !loc.reassigned && n.Type().Size() <= 128)
   264  		if !n.Byval() {
   265  			n.SetAddrtaken(true)
   266  			if n.Sym().Name == typecheck.LocalDictName {
   267  				base.FatalfAt(n.Pos(), "dictionary variable not captured by value")
   268  			}
   269  		}
   270  
   271  		if base.Flag.LowerM > 1 {
   272  			how := "ref"
   273  			if n.Byval() {
   274  				how = "value"
   275  			}
   276  			base.WarnfAt(n.Pos(), "%v capturing by %s: %v (addr=%v assign=%v width=%d)", n.Curfn, how, n, loc.addrtaken, loc.reassigned, n.Type().Size())
   277  		}
   278  
   279  		// Flow captured variables to closure.
   280  		k := k
   281  		if !cv.Byval() {
   282  			k = k.addr(cv, "reference")
   283  		}
   284  		b.flow(k.note(cv, "captured by a closure"), loc)
   285  	}
   286  }
   287  
   288  func (b *batch) finish(fns []*ir.Func) {
   289  	// Record parameter tags for package export data.
   290  	for _, fn := range fns {
   291  		fn.SetEsc(escFuncTagged)
   292  
   293  		for i, param := range fn.Type().RecvParams() {
   294  			param.Note = b.paramTag(fn, 1+i, param)
   295  		}
   296  	}
   297  
   298  	for _, loc := range b.allLocs {
   299  		n := loc.n
   300  		if n == nil {
   301  			continue
   302  		}
   303  
   304  		if n.Op() == ir.ONAME {
   305  			n := n.(*ir.Name)
   306  			n.Opt = nil
   307  		}
   308  
   309  		// Update n.Esc based on escape analysis results.
   310  
   311  		// Omit escape diagnostics for go/defer wrappers, at least for now.
   312  		// Historically, we haven't printed them, and test cases don't expect them.
   313  		// TODO(mdempsky): Update tests to expect this.
   314  		goDeferWrapper := n.Op() == ir.OCLOSURE && n.(*ir.ClosureExpr).Func.Wrapper()
   315  
   316  		if loc.hasAttr(attrEscapes) {
   317  			if n.Op() == ir.ONAME {
   318  				if base.Flag.CompilingRuntime {
   319  					base.ErrorfAt(n.Pos(), 0, "%v escapes to heap, not allowed in runtime", n)
   320  				}
   321  				if base.Flag.LowerM != 0 {
   322  					base.WarnfAt(n.Pos(), "moved to heap: %v", n)
   323  				}
   324  			} else {
   325  				if base.Flag.LowerM != 0 && !goDeferWrapper {
   326  					if n.Op() == ir.OAPPEND {
   327  						base.WarnfAt(n.Pos(), "append escapes to heap")
   328  					} else {
   329  						base.WarnfAt(n.Pos(), "%v escapes to heap", n)
   330  					}
   331  				}
   332  				if logopt.Enabled() {
   333  					var e_curfn *ir.Func // TODO(mdempsky): Fix.
   334  					logopt.LogOpt(n.Pos(), "escape", "escape", ir.FuncName(e_curfn))
   335  				}
   336  			}
   337  			n.SetEsc(ir.EscHeap)
   338  		} else {
   339  			if base.Flag.LowerM != 0 && n.Op() != ir.ONAME && !goDeferWrapper {
   340  				if n.Op() == ir.OAPPEND {
   341  					base.WarnfAt(n.Pos(), "append does not escape")
   342  				} else {
   343  					base.WarnfAt(n.Pos(), "%v does not escape", n)
   344  				}
   345  			}
   346  			n.SetEsc(ir.EscNone)
   347  			if !loc.hasAttr(attrPersists) {
   348  				switch n.Op() {
   349  				case ir.OCLOSURE:
   350  					n := n.(*ir.ClosureExpr)
   351  					n.SetTransient(true)
   352  				case ir.OMETHVALUE:
   353  					n := n.(*ir.SelectorExpr)
   354  					n.SetTransient(true)
   355  				case ir.OSLICELIT:
   356  					n := n.(*ir.CompLitExpr)
   357  					n.SetTransient(true)
   358  				}
   359  			}
   360  		}
   361  
   362  		// If the result of a string->[]byte conversion is never mutated,
   363  		// then it can simply reuse the string's memory directly.
   364  		if base.Debug.ZeroCopy != 0 {
   365  			if n, ok := n.(*ir.ConvExpr); ok && n.Op() == ir.OSTR2BYTES && !loc.hasAttr(attrMutates) {
   366  				if base.Flag.LowerM >= 1 {
   367  					base.WarnfAt(n.Pos(), "zero-copy string->[]byte conversion")
   368  				}
   369  				n.SetOp(ir.OSTR2BYTESTMP)
   370  			}
   371  		}
   372  	}
   373  
   374  	if goexperiment.RuntimeFreegc {
   375  		// Look for specific patterns of usage, such as appends
   376  		// to slices that we can prove are not aliased.
   377  		for _, fn := range fns {
   378  			a := aliasAnalysis{}
   379  			a.analyze(fn)
   380  		}
   381  	}
   382  
   383  }
   384  
   385  // inMutualBatch reports whether function fn is in the batch of
   386  // mutually recursive functions being analyzed. When this is true,
   387  // fn has not yet been analyzed, so its parameters and results
   388  // should be incorporated directly into the flow graph instead of
   389  // relying on its escape analysis tagging.
   390  func (b *batch) inMutualBatch(fn *ir.Name) bool {
   391  	if fn.Defn != nil && fn.Defn.Esc() < escFuncTagged {
   392  		if fn.Defn.Esc() == escFuncUnknown {
   393  			base.FatalfAt(fn.Pos(), "graph inconsistency: %v", fn)
   394  		}
   395  		return true
   396  	}
   397  	return false
   398  }
   399  
   400  const (
   401  	escFuncUnknown = 0 + iota
   402  	escFuncPlanned
   403  	escFuncStarted
   404  	escFuncTagged
   405  )
   406  
   407  // Mark labels that have no backjumps to them as not increasing e.loopdepth.
   408  type labelState int
   409  
   410  const (
   411  	looping labelState = 1 + iota
   412  	nonlooping
   413  )
   414  
   415  func (b *batch) paramTag(fn *ir.Func, narg int, f *types.Field) string {
   416  	name := func() string {
   417  		if f.Nname != nil {
   418  			return f.Nname.Sym().Name
   419  		}
   420  		return fmt.Sprintf("arg#%d", narg)
   421  	}
   422  
   423  	// Only report diagnostics for user code;
   424  	// not for wrappers generated around them.
   425  	// TODO(mdempsky): Generalize this.
   426  	diagnose := base.Flag.LowerM != 0 && !(fn.Wrapper() || fn.Dupok())
   427  
   428  	if len(fn.Body) == 0 {
   429  		// Assume that uintptr arguments must be held live across the call.
   430  		// This is most important for syscall.Syscall.
   431  		// See golang.org/issue/13372.
   432  		// This really doesn't have much to do with escape analysis per se,
   433  		// but we are reusing the ability to annotate an individual function
   434  		// argument and pass those annotations along to importing code.
   435  		fn.Pragma |= ir.UintptrKeepAlive
   436  
   437  		if f.Type.IsUintptr() {
   438  			if diagnose {
   439  				base.WarnfAt(f.Pos, "assuming %v is unsafe uintptr", name())
   440  			}
   441  			return ""
   442  		}
   443  
   444  		if !f.Type.HasPointers() { // don't bother tagging for scalars
   445  			return ""
   446  		}
   447  
   448  		var esc leaks
   449  
   450  		// External functions are assumed unsafe, unless
   451  		// //go:noescape is given before the declaration.
   452  		if fn.Pragma&ir.Noescape != 0 {
   453  			if diagnose && f.Sym != nil {
   454  				base.WarnfAt(f.Pos, "%v does not escape", name())
   455  			}
   456  			esc.AddMutator(0)
   457  			esc.AddCallee(0)
   458  		} else {
   459  			if diagnose && f.Sym != nil {
   460  				base.WarnfAt(f.Pos, "leaking param: %v", name())
   461  			}
   462  			esc.AddHeap(0)
   463  		}
   464  
   465  		return esc.Encode()
   466  	}
   467  
   468  	if fn.Pragma&ir.UintptrEscapes != 0 {
   469  		if f.Type.IsUintptr() {
   470  			if diagnose {
   471  				base.WarnfAt(f.Pos, "marking %v as escaping uintptr", name())
   472  			}
   473  			return ""
   474  		}
   475  		if f.IsDDD() && f.Type.Elem().IsUintptr() {
   476  			// final argument is ...uintptr.
   477  			if diagnose {
   478  				base.WarnfAt(f.Pos, "marking %v as escaping ...uintptr", name())
   479  			}
   480  			return ""
   481  		}
   482  	}
   483  
   484  	if !f.Type.HasPointers() { // don't bother tagging for scalars
   485  		return ""
   486  	}
   487  
   488  	// Unnamed parameters are unused and therefore do not escape.
   489  	if f.Sym == nil || f.Sym.IsBlank() {
   490  		var esc leaks
   491  		return esc.Encode()
   492  	}
   493  
   494  	n := f.Nname.(*ir.Name)
   495  	loc := b.oldLoc(n)
   496  	esc := loc.paramEsc
   497  	esc.Optimize()
   498  
   499  	if diagnose && !loc.hasAttr(attrEscapes) {
   500  		b.reportLeaks(f.Pos, name(), esc, fn.Type())
   501  	}
   502  
   503  	return esc.Encode()
   504  }
   505  
   506  func (b *batch) reportLeaks(pos src.XPos, name string, esc leaks, sig *types.Type) {
   507  	warned := false
   508  	if x := esc.Heap(); x >= 0 {
   509  		if x == 0 {
   510  			base.WarnfAt(pos, "leaking param: %v", name)
   511  		} else {
   512  			// TODO(mdempsky): Mention level=x like below?
   513  			base.WarnfAt(pos, "leaking param content: %v", name)
   514  		}
   515  		warned = true
   516  	}
   517  	for i := 0; i < numEscResults; i++ {
   518  		if x := esc.Result(i); x >= 0 {
   519  			res := sig.Result(i).Nname.Sym().Name
   520  			base.WarnfAt(pos, "leaking param: %v to result %v level=%d", name, res, x)
   521  			warned = true
   522  		}
   523  	}
   524  
   525  	if base.Debug.EscapeMutationsCalls <= 0 {
   526  		if !warned {
   527  			base.WarnfAt(pos, "%v does not escape", name)
   528  		}
   529  		return
   530  	}
   531  
   532  	if x := esc.Mutator(); x >= 0 {
   533  		base.WarnfAt(pos, "mutates param: %v derefs=%v", name, x)
   534  		warned = true
   535  	}
   536  	if x := esc.Callee(); x >= 0 {
   537  		base.WarnfAt(pos, "calls param: %v derefs=%v", name, x)
   538  		warned = true
   539  	}
   540  
   541  	if !warned {
   542  		base.WarnfAt(pos, "%v does not escape, mutate, or call", name)
   543  	}
   544  }
   545  
   546  // rewriteWithLiterals attempts to replace certain non-constant expressions
   547  // within n with a literal if possible.
   548  func (b *batch) rewriteWithLiterals(n ir.Node, fn *ir.Func) {
   549  	if n == nil || fn == nil {
   550  		return
   551  	}
   552  
   553  	assignTemp := func(pos src.XPos, n ir.Node, init *ir.Nodes) {
   554  		// Preserve any side effects of n by assigning it to an otherwise unused temp.
   555  		tmp := typecheck.TempAt(pos, fn, n.Type())
   556  		init.Append(typecheck.Stmt(ir.NewDecl(pos, ir.ODCL, tmp)))
   557  		init.Append(typecheck.Stmt(ir.NewAssignStmt(pos, tmp, n)))
   558  	}
   559  
   560  	switch n.Op() {
   561  	case ir.OMAKESLICE:
   562  		// Check if we can replace a non-constant argument to make with
   563  		// a literal to allow for this slice to be stack allocated if otherwise allowed.
   564  		n := n.(*ir.MakeExpr)
   565  
   566  		r := &n.Cap
   567  		if n.Cap == nil {
   568  			r = &n.Len
   569  		}
   570  
   571  		if (*r).Op() != ir.OLITERAL {
   572  			// Look up a cached ReassignOracle for the function, lazily computing one if needed.
   573  			ro := b.reassignOracle(fn)
   574  			if ro == nil {
   575  				base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
   576  			}
   577  
   578  			s := ro.StaticValue(*r)
   579  			switch s.Op() {
   580  			case ir.OLITERAL:
   581  				lit, ok := s.(*ir.BasicLit)
   582  				if !ok || lit.Val().Kind() != constant.Int {
   583  					base.Fatalf("unexpected BasicLit Kind")
   584  				}
   585  				if constant.Compare(lit.Val(), token.GEQ, constant.MakeInt64(0)) {
   586  					if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
   587  						// De-selected by literal alloc optimizations debug hash.
   588  						return
   589  					}
   590  					// Preserve any side effects of the original expression, then replace it.
   591  					assignTemp(n.Pos(), *r, n.PtrInit())
   592  					*r = ir.NewBasicLit(n.Pos(), (*r).Type(), lit.Val())
   593  				}
   594  			case ir.OLEN:
   595  				x := ro.StaticValue(s.(*ir.UnaryExpr).X)
   596  				if x.Op() == ir.OSLICELIT {
   597  					x := x.(*ir.CompLitExpr)
   598  					// Preserve any side effects of the original expression, then update the value.
   599  					assignTemp(n.Pos(), *r, n.PtrInit())
   600  					*r = ir.NewBasicLit(n.Pos(), types.Types[types.TINT], constant.MakeInt64(x.Len))
   601  				}
   602  			}
   603  		}
   604  	case ir.OCONVIFACE:
   605  		// Check if we can replace a non-constant expression in an interface conversion with
   606  		// a literal to avoid heap allocating the underlying interface value.
   607  		conv := n.(*ir.ConvExpr)
   608  		if conv.X.Op() != ir.OLITERAL && !conv.X.Type().IsInterface() {
   609  			// TODO(thepudds): likely could avoid some work by tightening the check of conv.X's type.
   610  			// Look up a cached ReassignOracle for the function, lazily computing one if needed.
   611  			ro := b.reassignOracle(fn)
   612  			if ro == nil {
   613  				base.Fatalf("no ReassignOracle for function %v with closure parent %v", fn, fn.ClosureParent)
   614  			}
   615  			v := ro.StaticValue(conv.X)
   616  			if v != nil && v.Op() == ir.OLITERAL && ir.ValidTypeForConst(conv.X.Type(), v.Val()) {
   617  				if !base.LiteralAllocHash.MatchPos(n.Pos(), nil) {
   618  					// De-selected by literal alloc optimizations debug hash.
   619  					return
   620  				}
   621  				if base.Debug.EscapeDebug >= 3 {
   622  					base.WarnfAt(n.Pos(), "rewriting OCONVIFACE value from %v (%v) to %v (%v)", conv.X, conv.X.Type(), v, v.Type())
   623  				}
   624  				// Preserve any side effects of the original expression, then replace it.
   625  				assignTemp(conv.Pos(), conv.X, conv.PtrInit())
   626  				v := v.(*ir.BasicLit)
   627  				conv.X = ir.NewBasicLit(conv.Pos(), conv.X.Type(), v.Val())
   628  				typecheck.Expr(conv)
   629  			}
   630  		}
   631  	}
   632  }
   633  
   634  // reassignOracle returns an initialized *ir.ReassignOracle for fn.
   635  // If fn is a closure, it returns the ReassignOracle for the ultimate parent.
   636  //
   637  // A new ReassignOracle is initialized lazily if needed, and the result
   638  // is cached to reduce duplicative work of preparing a ReassignOracle.
   639  func (b *batch) reassignOracle(fn *ir.Func) *ir.ReassignOracle {
   640  	if ro, ok := b.reassignOracles[fn]; ok {
   641  		return ro // Hit.
   642  	}
   643  
   644  	// For closures, we want the ultimate parent's ReassignOracle,
   645  	// so walk up the parent chain, if any.
   646  	f := fn
   647  	for f.ClosureParent != nil && !f.ClosureParent.IsPackageInit() {
   648  		f = f.ClosureParent
   649  	}
   650  
   651  	if f != fn {
   652  		// We found a parent.
   653  		ro := b.reassignOracles[f]
   654  		if ro != nil {
   655  			// Hit, via a parent. Before returning, store this ro for the original fn as well.
   656  			b.reassignOracles[fn] = ro
   657  			return ro
   658  		}
   659  	}
   660  
   661  	// Miss. We did not find a ReassignOracle for fn or a parent, so lazily create one.
   662  	ro := &ir.ReassignOracle{}
   663  	ro.Init(f)
   664  
   665  	// Cache the answer for the original fn.
   666  	b.reassignOracles[fn] = ro
   667  	if f != fn {
   668  		// Cache for the parent as well.
   669  		b.reassignOracles[f] = ro
   670  	}
   671  	return ro
   672  }
   673  

View as plain text