2015-02-13 14:40:36 -05:00
// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package gc
import (
"cmd/internal/obj"
"fmt"
"strings"
)
// Run analysis on minimal sets of mutually recursive functions
// or single non-recursive functions, bottom up.
//
// Finding these sets is finding strongly connected components
// in the static call graph. The algorithm for doing that is taken
// from Sedgewick, Algorithms, Second Edition, p. 482, with two
// adaptations.
//
// First, a hidden closure function (n->curfn != N) cannot be the
// root of a connected component. Refusing to use it as a root
// forces it into the component of the function in which it appears.
2015-02-24 12:14:29 -05:00
// This is more convenient for escape analysis.
2015-02-13 14:40:36 -05:00
//
// Second, each function becomes two virtual nodes in the graph,
// with numbers n and n+1. We record the function's node number as n
// but search from node n+1. If the search tells us that the component
// number (min) is n+1, we know that this is a trivial component: one function
// plus its closures. If the search tells us that the component number is
// n, then there was a path from node n+1 back to node n, meaning that
// the function set is mutually recursive. The escape analysis can be
// more precise when analyzing a single non-recursive function than
// when analyzing a set of mutually recursive functions.
2015-02-24 12:14:29 -05:00
// TODO(rsc): Look into using a map[*Node]bool instead of walkgen,
// to allow analysis passes to use walkgen themselves.
2015-02-13 14:40:36 -05:00
2015-02-24 12:14:29 -05:00
type bottomUpVisitor struct {
analyze func ( * NodeList , bool )
visitgen uint32
stack * NodeList
}
2015-02-13 14:40:36 -05:00
2015-02-24 12:14:29 -05:00
// visitBottomUp invokes analyze on the ODCLFUNC nodes listed in list.
// It calls analyze with successive groups of functions, working from
// the bottom of the call graph upward. Each time analyze is called with
// a list of functions, every function on that list only calls other functions
// on the list or functions that have been passed in previous invocations of
// analyze. Closures appear in the same list as their outer functions.
// The lists are as short as possible while preserving those requirements.
// (In a typical program, many invocations of analyze will be passed just
// a single function.) The boolean argument 'recursive' passed to analyze
// specifies whether the functions on the list are mutually recursive.
// If recursive is false, the list consists of only a single function and its closures.
// If recursive is true, the list may still contain only a single function,
// if that function is itself recursive.
func visitBottomUp ( list * NodeList , analyze func ( list * NodeList , recursive bool ) ) {
for l := list ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
l . N . Walkgen = 0
}
2015-02-24 12:14:29 -05:00
var v bottomUpVisitor
v . analyze = analyze
for l := list ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
if l . N . Op == ODCLFUNC && l . N . Curfn == nil {
2015-02-24 12:14:29 -05:00
v . visit ( l . N )
2015-02-13 14:40:36 -05:00
}
}
2015-02-24 12:14:29 -05:00
for l := list ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
l . N . Walkgen = 0
}
}
2015-02-24 12:14:29 -05:00
func ( v * bottomUpVisitor ) visit ( n * Node ) uint32 {
2015-02-13 14:40:36 -05:00
if n . Walkgen > 0 {
// already visited
return n . Walkgen
}
2015-02-24 12:14:29 -05:00
v . visitgen ++
n . Walkgen = v . visitgen
v . visitgen ++
min := v . visitgen
2015-02-13 14:40:36 -05:00
2015-02-23 16:07:24 -05:00
l := new ( NodeList )
2015-02-24 12:14:29 -05:00
l . Next = v . stack
2015-02-13 14:40:36 -05:00
l . N = n
2015-02-24 12:14:29 -05:00
v . stack = l
min = v . visitcodelist ( n . Nbody , min )
2015-02-13 14:40:36 -05:00
if ( min == n . Walkgen || min == n . Walkgen + 1 ) && n . Curfn == nil {
// This node is the root of a strongly connected component.
// The original min passed to visitcodelist was n->walkgen+1.
// If visitcodelist found its way back to n->walkgen, then this
// block is a set of mutually recursive functions.
// Otherwise it's just a lone function that does not recurse.
2015-02-23 16:07:24 -05:00
recursive := min == n . Walkgen
2015-02-13 14:40:36 -05:00
// Remove connected component from stack.
// Mark walkgen so that future visits return a large number
// so as not to affect the caller's min.
2015-02-24 12:14:29 -05:00
block := v . stack
2015-02-13 14:40:36 -05:00
2015-02-23 16:07:24 -05:00
var l * NodeList
2015-02-24 12:14:29 -05:00
for l = v . stack ; l . N != n ; l = l . Next {
2015-02-13 14:40:36 -05:00
l . N . Walkgen = ^ uint32 ( 0 )
}
n . Walkgen = ^ uint32 ( 0 )
2015-02-24 12:14:29 -05:00
v . stack = l . Next
2015-02-13 14:40:36 -05:00
l . Next = nil
// Run escape analysis on this set of functions.
2015-02-24 12:14:29 -05:00
v . analyze ( block , recursive )
2015-02-13 14:40:36 -05:00
}
return min
}
2015-02-24 12:14:29 -05:00
func ( v * bottomUpVisitor ) visitcodelist ( l * NodeList , min uint32 ) uint32 {
2015-02-13 14:40:36 -05:00
for ; l != nil ; l = l . Next {
2015-02-24 12:14:29 -05:00
min = v . visitcode ( l . N , min )
2015-02-13 14:40:36 -05:00
}
return min
}
2015-02-24 12:14:29 -05:00
func ( v * bottomUpVisitor ) visitcode ( n * Node , min uint32 ) uint32 {
2015-02-13 14:40:36 -05:00
if n == nil {
return min
}
2015-02-24 12:14:29 -05:00
min = v . visitcodelist ( n . Ninit , min )
min = v . visitcode ( n . Left , min )
min = v . visitcode ( n . Right , min )
min = v . visitcodelist ( n . List , min )
min = v . visitcode ( n . Ntest , min )
min = v . visitcode ( n . Nincr , min )
min = v . visitcodelist ( n . Nbody , min )
min = v . visitcodelist ( n . Nelse , min )
min = v . visitcodelist ( n . Rlist , min )
2015-02-13 14:40:36 -05:00
if n . Op == OCALLFUNC || n . Op == OCALLMETH {
2015-02-23 16:07:24 -05:00
fn := n . Left
2015-02-13 14:40:36 -05:00
if n . Op == OCALLMETH {
fn = n . Left . Right . Sym . Def
}
if fn != nil && fn . Op == ONAME && fn . Class == PFUNC && fn . Defn != nil {
2015-02-24 12:14:29 -05:00
m := v . visit ( fn . Defn )
2015-02-13 14:40:36 -05:00
if m < min {
min = m
}
}
}
if n . Op == OCLOSURE {
2015-02-24 12:14:29 -05:00
m := v . visit ( n . Closure )
2015-02-13 14:40:36 -05:00
if m < min {
min = m
}
}
return min
}
2015-02-24 12:14:29 -05:00
// Escape analysis.
2015-02-13 14:40:36 -05:00
// An escape analysis pass for a set of functions.
2015-02-24 12:14:29 -05:00
// The analysis assumes that closures and the functions in which they
// appear are analyzed together, so that the aliasing between their
// variables can be modeled more precisely.
2015-02-13 14:40:36 -05:00
//
// First escfunc, esc and escassign recurse over the ast of each
// function to dig out flow(dst,src) edges between any
// pointer-containing nodes and store them in dst->escflowsrc. For
// variables assigned to a variable in an outer scope or used as a
// return value, they store a flow(theSink, src) edge to a fake node
// 'the Sink'. For variables referenced in closures, an edge
// flow(closure, &var) is recorded and the flow of a closure itself to
// an outer scope is tracked the same way as other variables.
//
// Then escflood walks the graph starting at theSink and tags all
// variables of it can reach an & node as escaping and all function
// parameters it can reach as leaking.
//
// If a value's address is taken but the address does not escape,
// then the value can stay on the stack. If the value new(T) does
// not escape, then new(T) can be rewritten into a stack allocation.
// The same is true of slice literals.
//
// If optimizations are disabled (-N), this code is not used.
// Instead, the compiler assumes that any value whose address
// is taken without being immediately dereferenced
// needs to be moved to the heap, and new(T) and slice
// literals are always real allocations.
2015-02-24 12:14:29 -05:00
func escapes ( all * NodeList ) {
visitBottomUp ( all , escAnalyze )
}
const (
EscFuncUnknown = 0 + iota
EscFuncPlanned
EscFuncStarted
EscFuncTagged
)
2015-02-13 14:40:36 -05:00
type EscState struct {
theSink Node
funcParam Node
dsts * NodeList
loopdepth int
pdepth int
dstcount int
edgecount int
noesc * NodeList
2015-02-17 22:13:49 -05:00
recursive bool
2015-02-13 14:40:36 -05:00
}
2015-03-02 16:03:26 -05:00
var tags [ 16 ] * string
2015-02-13 14:40:36 -05:00
2015-03-02 16:03:26 -05:00
func mktag ( mask int ) * string {
2015-02-13 14:40:36 -05:00
switch mask & EscMask {
case EscNone ,
EscReturn :
break
default :
Fatal ( "escape mktag" )
}
mask >>= EscBits
if mask < len ( tags ) && tags [ mask ] != nil {
return tags [ mask ]
}
2015-03-02 16:03:26 -05:00
s := fmt . Sprintf ( "esc:0x%x" , mask )
2015-02-13 14:40:36 -05:00
if mask < len ( tags ) {
2015-03-02 16:03:26 -05:00
tags [ mask ] = & s
2015-02-13 14:40:36 -05:00
}
2015-03-02 16:03:26 -05:00
return & s
2015-02-13 14:40:36 -05:00
}
2015-03-02 16:03:26 -05:00
func parsetag ( note * string ) int {
if note == nil || ! strings . HasPrefix ( * note , "esc:" ) {
2015-02-13 14:40:36 -05:00
return EscUnknown
}
2015-03-02 16:03:26 -05:00
em := atoi ( ( * note ) [ 4 : ] )
2015-02-13 14:40:36 -05:00
if em == 0 {
return EscNone
}
return EscReturn | em << EscBits
}
2015-02-24 12:14:29 -05:00
func escAnalyze ( all * NodeList , recursive bool ) {
2015-03-02 14:22:05 -05:00
var es EscState
2015-02-23 16:07:24 -05:00
e := & es
2015-02-13 14:40:36 -05:00
e . theSink . Op = ONAME
e . theSink . Orig = & e . theSink
e . theSink . Class = PEXTERN
e . theSink . Sym = Lookup ( ".sink" )
e . theSink . Escloopdepth = - 1
e . recursive = recursive
e . funcParam . Op = ONAME
e . funcParam . Orig = & e . funcParam
e . funcParam . Class = PAUTO
e . funcParam . Sym = Lookup ( ".param" )
e . funcParam . Escloopdepth = 10000000
2015-02-23 16:07:24 -05:00
for l := all ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
if l . N . Op == ODCLFUNC {
l . N . Esc = EscFuncPlanned
}
}
// flow-analyze functions
2015-02-23 16:07:24 -05:00
for l := all ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
if l . N . Op == ODCLFUNC {
escfunc ( e , l . N )
}
}
// print("escapes: %d e->dsts, %d edges\n", e->dstcount, e->edgecount);
// visit the upstream of each dst, mark address nodes with
// addrescapes, mark parameters unsafe
2015-02-23 16:07:24 -05:00
for l := e . dsts ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
escflood ( e , l . N )
}
// for all top level functions, tag the typenodes corresponding to the param nodes
2015-02-23 16:07:24 -05:00
for l := all ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
if l . N . Op == ODCLFUNC {
esctag ( e , l . N )
}
}
if Debug [ 'm' ] != 0 {
2015-02-23 16:07:24 -05:00
for l := e . noesc ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
if l . N . Esc == EscNone {
var tmp * Sym
if l . N . Curfn != nil && l . N . Curfn . Nname != nil {
tmp = l . N . Curfn . Nname . Sym
} else {
tmp = nil
}
Warnl ( int ( l . N . Lineno ) , "%v %v does not escape" , Sconv ( tmp , 0 ) , Nconv ( l . N , obj . FmtShort ) )
}
}
}
}
func escfunc ( e * EscState , func_ * Node ) {
// print("escfunc %N %s\n", func->nname, e->recursive?"(recursive)":"");
if func_ . Esc != 1 {
Fatal ( "repeat escfunc %v" , Nconv ( func_ . Nname , 0 ) )
}
func_ . Esc = EscFuncStarted
2015-02-23 16:07:24 -05:00
saveld := e . loopdepth
2015-02-13 14:40:36 -05:00
e . loopdepth = 1
2015-02-23 16:07:24 -05:00
savefn := Curfn
2015-02-13 14:40:36 -05:00
Curfn = func_
2015-02-23 16:07:24 -05:00
for ll := Curfn . Dcl ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
if ll . N . Op != ONAME {
continue
}
switch ll . N . Class {
// out params are in a loopdepth between the sink and all local variables
case PPARAMOUT :
ll . N . Escloopdepth = 0
case PPARAM :
ll . N . Escloopdepth = 1
if ll . N . Type != nil && ! haspointers ( ll . N . Type ) {
break
}
if Curfn . Nbody == nil && ! Curfn . Noescape {
ll . N . Esc = EscHeap
} else {
ll . N . Esc = EscNone // prime for escflood later
}
e . noesc = list ( e . noesc , ll . N )
}
}
// in a mutually recursive group we lose track of the return values
2015-02-17 22:13:49 -05:00
if e . recursive {
2015-02-23 16:07:24 -05:00
for ll := Curfn . Dcl ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
if ll . N . Op == ONAME && ll . N . Class == PPARAMOUT {
escflows ( e , & e . theSink , ll . N )
}
}
}
escloopdepthlist ( e , Curfn . Nbody )
esclist ( e , Curfn . Nbody , Curfn )
Curfn = savefn
e . loopdepth = saveld
}
// Mark labels that have no backjumps to them as not increasing e->loopdepth.
// Walk hasn't generated (goto|label)->left->sym->label yet, so we'll cheat
// and set it to one of the following two. Then in esc we'll clear it again.
var looping Label
var nonlooping Label
func escloopdepthlist ( e * EscState , l * NodeList ) {
for ; l != nil ; l = l . Next {
escloopdepth ( e , l . N )
}
}
func escloopdepth ( e * EscState , n * Node ) {
if n == nil {
return
}
escloopdepthlist ( e , n . Ninit )
switch n . Op {
case OLABEL :
2015-02-17 22:13:49 -05:00
if n . Left == nil || n . Left . Sym == nil {
2015-02-13 14:40:36 -05:00
Fatal ( "esc:label without label: %v" , Nconv ( n , obj . FmtSign ) )
}
// Walk will complain about this label being already defined, but that's not until
// after escape analysis. in the future, maybe pull label & goto analysis out of walk and put before esc
// if(n->left->sym->label != nil)
// fatal("escape analysis messed up analyzing label: %+N", n);
n . Left . Sym . Label = & nonlooping
case OGOTO :
2015-02-17 22:13:49 -05:00
if n . Left == nil || n . Left . Sym == nil {
2015-02-13 14:40:36 -05:00
Fatal ( "esc:goto without label: %v" , Nconv ( n , obj . FmtSign ) )
}
// If we come past one that's uninitialized, this must be a (harmless) forward jump
// but if it's set to nonlooping the label must have preceded this goto.
if n . Left . Sym . Label == & nonlooping {
n . Left . Sym . Label = & looping
}
}
escloopdepth ( e , n . Left )
escloopdepth ( e , n . Right )
escloopdepthlist ( e , n . List )
escloopdepth ( e , n . Ntest )
escloopdepth ( e , n . Nincr )
escloopdepthlist ( e , n . Nbody )
escloopdepthlist ( e , n . Nelse )
escloopdepthlist ( e , n . Rlist )
}
func esclist ( e * EscState , l * NodeList , up * Node ) {
for ; l != nil ; l = l . Next {
esc ( e , l . N , up )
}
}
func esc ( e * EscState , n * Node , up * Node ) {
var ll * NodeList
var lr * NodeList
if n == nil {
return
}
2015-02-23 16:07:24 -05:00
lno := int ( setlineno ( n ) )
2015-02-13 14:40:36 -05:00
// ninit logically runs at a different loopdepth than the rest of the for loop.
esclist ( e , n . Ninit , n )
if n . Op == OFOR || n . Op == ORANGE {
e . loopdepth ++
}
// type switch variables have no ODCL.
// process type switch as declaration.
// must happen before processing of switch body,
// so before recursion.
if n . Op == OSWITCH && n . Ntest != nil && n . Ntest . Op == OTYPESW {
for ll = n . List ; ll != nil ; ll = ll . Next { // cases
// ll->n->nname is the variable per case
if ll . N . Nname != nil {
ll . N . Nname . Escloopdepth = e . loopdepth
}
}
}
esc ( e , n . Left , n )
esc ( e , n . Right , n )
esc ( e , n . Ntest , n )
esc ( e , n . Nincr , n )
esclist ( e , n . Nbody , n )
esclist ( e , n . Nelse , n )
esclist ( e , n . List , n )
esclist ( e , n . Rlist , n )
if n . Op == OFOR || n . Op == ORANGE {
e . loopdepth --
}
if Debug [ 'm' ] > 1 {
var tmp * Sym
if Curfn != nil && Curfn . Nname != nil {
tmp = Curfn . Nname . Sym
} else {
tmp = nil
}
fmt . Printf ( "%v:[%d] %v esc: %v\n" , Ctxt . Line ( int ( lineno ) ) , e . loopdepth , Sconv ( tmp , 0 ) , Nconv ( n , 0 ) )
}
switch n . Op {
// Record loop depth at declaration.
case ODCL :
if n . Left != nil {
n . Left . Escloopdepth = e . loopdepth
}
case OLABEL :
if n . Left . Sym . Label == & nonlooping {
if Debug [ 'm' ] > 1 {
fmt . Printf ( "%v:%v non-looping label\n" , Ctxt . Line ( int ( lineno ) ) , Nconv ( n , 0 ) )
}
} else if n . Left . Sym . Label == & looping {
if Debug [ 'm' ] > 1 {
fmt . Printf ( "%v: %v looping label\n" , Ctxt . Line ( int ( lineno ) ) , Nconv ( n , 0 ) )
}
e . loopdepth ++
}
// See case OLABEL in escloopdepth above
// else if(n->left->sym->label == nil)
// fatal("escape analysis missed or messed up a label: %+N", n);
n . Left . Sym . Label = nil
// Everything but fixed array is a dereference.
case ORANGE :
2015-02-17 22:13:49 -05:00
if Isfixedarray ( n . Type ) && n . List != nil && n . List . Next != nil {
2015-02-13 14:40:36 -05:00
escassign ( e , n . List . Next . N , n . Right )
}
case OSWITCH :
if n . Ntest != nil && n . Ntest . Op == OTYPESW {
for ll = n . List ; ll != nil ; ll = ll . Next { // cases
// ntest->right is the argument of the .(type),
// ll->n->nname is the variable per case
escassign ( e , ll . N . Nname , n . Ntest . Right )
}
}
// Filter out the following special case.
//
// func (b *Buffer) Foo() {
// n, m := ...
// b.buf = b.buf[n:m]
// }
//
// This assignment is a no-op for escape analysis,
// it does not store any new pointers into b that were not already there.
// However, without this special case b will escape, because we assign to OIND/ODOTPTR.
case OAS ,
OASOP :
if ( n . Left . Op == OIND || n . Left . Op == ODOTPTR ) && n . Left . Left . Op == ONAME && ( n . Right . Op == OSLICE || n . Right . Op == OSLICE3 || n . Right . Op == OSLICESTR ) && ( n . Right . Left . Op == OIND || n . Right . Left . Op == ODOTPTR ) && n . Right . Left . Left . Op == ONAME && n . Left . Left == n . Right . Left . Left { // dst is ONAME dereference // src is slice operation // slice is applied to ONAME dereference // dst and src reference the same base ONAME
// Here we also assume that the statement will not contain calls,
// that is, that order will move any calls to init.
// Otherwise base ONAME value could change between the moments
// when we evaluate it for dst and for src.
//
// Note, this optimization does not apply to OSLICEARR,
// because it does introduce a new pointer into b that was not already there
// (pointer to b itself). After such assignment, if b contents escape,
// b escapes as well. If we ignore such OSLICEARR, we will conclude
// that b does not escape when b contents do.
if Debug [ 'm' ] != 0 {
var tmp * Sym
if n . Curfn != nil && n . Curfn . Nname != nil {
tmp = n . Curfn . Nname . Sym
} else {
tmp = nil
}
Warnl ( int ( n . Lineno ) , "%v ignoring self-assignment to %v" , Sconv ( tmp , 0 ) , Nconv ( n . Left , obj . FmtShort ) )
}
break
}
escassign ( e , n . Left , n . Right )
case OAS2 : // x,y = a,b
if count ( n . List ) == count ( n . Rlist ) {
ll = n . List
lr = n . Rlist
for ; ll != nil ; ( func ( ) { ll = ll . Next ; lr = lr . Next } ) ( ) {
escassign ( e , ll . N , lr . N )
}
}
case OAS2RECV , // v, ok = <-ch
OAS2MAPR , // v, ok = m[k]
OAS2DOTTYPE : // v, ok = x.(type)
escassign ( e , n . List . N , n . Rlist . N )
case OSEND : // ch <- x
escassign ( e , & e . theSink , n . Right )
case ODEFER :
if e . loopdepth == 1 { // top level
break
}
fallthrough
// go f(x) - f and x escape
// arguments leak out of scope
// TODO: leak to a dummy node instead
// fallthrough
case OPROC :
escassign ( e , & e . theSink , n . Left . Left )
escassign ( e , & e . theSink , n . Left . Right ) // ODDDARG for call
for ll = n . Left . List ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N )
}
case OCALLMETH ,
OCALLFUNC ,
OCALLINTER :
esccall ( e , n , up )
// esccall already done on n->rlist->n. tie it's escretval to n->list
case OAS2FUNC : // x,y = f()
lr = n . Rlist . N . Escretval
for ll = n . List ; lr != nil && ll != nil ; ( func ( ) { lr = lr . Next ; ll = ll . Next } ) ( ) {
escassign ( e , ll . N , lr . N )
}
if lr != nil || ll != nil {
Fatal ( "esc oas2func" )
}
case ORETURN :
ll = n . List
if count ( n . List ) == 1 && Curfn . Type . Outtuple > 1 {
// OAS2FUNC in disguise
// esccall already done on n->list->n
// tie n->list->n->escretval to curfn->dcl PPARAMOUT's
ll = n . List . N . Escretval
}
for lr = Curfn . Dcl ; lr != nil && ll != nil ; lr = lr . Next {
if lr . N . Op != ONAME || lr . N . Class != PPARAMOUT {
continue
}
escassign ( e , lr . N , ll . N )
ll = ll . Next
}
if ll != nil {
Fatal ( "esc return list" )
}
// Argument could leak through recover.
case OPANIC :
escassign ( e , & e . theSink , n . Left )
case OAPPEND :
2015-02-17 22:13:49 -05:00
if n . Isddd == 0 {
2015-02-13 14:40:36 -05:00
for ll = n . List . Next ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N ) // lose track of assign to dereference
}
}
case OCONV ,
OCONVNOP ,
OCONVIFACE :
escassign ( e , n , n . Left )
case OARRAYLIT :
2015-02-17 22:13:49 -05:00
if Isslice ( n . Type ) {
2015-02-13 14:40:36 -05:00
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
n . Escloopdepth = e . loopdepth
// Values make it to memory, lose track.
for ll = n . List ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N . Right )
}
} else {
// Link values to array.
for ll = n . List ; ll != nil ; ll = ll . Next {
escassign ( e , n , ll . N . Right )
}
}
// Link values to struct.
case OSTRUCTLIT :
for ll = n . List ; ll != nil ; ll = ll . Next {
escassign ( e , n , ll . N . Right )
}
case OPTRLIT :
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
n . Escloopdepth = e . loopdepth
// Link OSTRUCTLIT to OPTRLIT; if OPTRLIT escapes, OSTRUCTLIT elements do too.
escassign ( e , n , n . Left )
case OCALLPART :
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
n . Escloopdepth = e . loopdepth
// Contents make it to memory, lose track.
escassign ( e , & e . theSink , n . Left )
case OMAPLIT :
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
n . Escloopdepth = e . loopdepth
// Keys and values make it to memory, lose track.
for ll = n . List ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N . Left )
escassign ( e , & e . theSink , ll . N . Right )
}
// Link addresses of captured variables to closure.
case OCLOSURE :
2015-02-23 16:07:24 -05:00
var a * Node
var v * Node
2015-02-13 14:40:36 -05:00
for ll = n . Cvars ; ll != nil ; ll = ll . Next {
v = ll . N
if v . Op == OXXX { // unnamed out argument; see dcl.c:/^funcargs
continue
}
a = v . Closure
2015-02-17 22:13:49 -05:00
if v . Byval == 0 {
2015-02-13 14:40:36 -05:00
a = Nod ( OADDR , a , nil )
a . Lineno = v . Lineno
a . Escloopdepth = e . loopdepth
typecheck ( & a , Erv )
}
escassign ( e , n , a )
}
fallthrough
// fallthrough
case OMAKECHAN ,
OMAKEMAP ,
OMAKESLICE ,
ONEW ,
OARRAYRUNESTR ,
OARRAYBYTESTR ,
OSTRARRAYRUNE ,
OSTRARRAYBYTE ,
ORUNESTR :
n . Escloopdepth = e . loopdepth
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
case OADDSTR :
n . Escloopdepth = e . loopdepth
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
// Arguments of OADDSTR do not escape.
case OADDR :
n . Esc = EscNone // until proven otherwise
e . noesc = list ( e . noesc , n )
// current loop depth is an upper bound on actual loop depth
// of addressed value.
n . Escloopdepth = e . loopdepth
// for &x, use loop depth of x if known.
// it should always be known, but if not, be conservative
// and keep the current loop depth.
if n . Left . Op == ONAME {
switch n . Left . Class {
case PAUTO :
if n . Left . Escloopdepth != 0 {
n . Escloopdepth = n . Left . Escloopdepth
}
// PPARAM is loop depth 1 always.
// PPARAMOUT is loop depth 0 for writes
// but considered loop depth 1 for address-of,
// so that writing the address of one result
// to another (or the same) result makes the
// first result move to the heap.
case PPARAM ,
PPARAMOUT :
n . Escloopdepth = 1
}
}
}
lineno = int32 ( lno )
}
// Assert that expr somehow gets assigned to dst, if non nil. for
// dst==nil, any name node expr still must be marked as being
// evaluated in curfn. For expr==nil, dst must still be examined for
// evaluations inside it (e.g *f(x) = y)
func escassign ( e * EscState , dst * Node , src * Node ) {
if isblank ( dst ) || dst == nil || src == nil || src . Op == ONONAME || src . Op == OXXX {
return
}
if Debug [ 'm' ] > 1 {
var tmp * Sym
if Curfn != nil && Curfn . Nname != nil {
tmp = Curfn . Nname . Sym
} else {
tmp = nil
}
fmt . Printf ( "%v:[%d] %v escassign: %v(%v) = %v(%v)\n" , Ctxt . Line ( int ( lineno ) ) , e . loopdepth , Sconv ( tmp , 0 ) , Nconv ( dst , obj . FmtShort ) , Jconv ( dst , obj . FmtShort ) , Nconv ( src , obj . FmtShort ) , Jconv ( src , obj . FmtShort ) )
}
setlineno ( dst )
// Analyze lhs of assignment.
// Replace dst with e->theSink if we can't track it.
switch dst . Op {
default :
Dump ( "dst" , dst )
Fatal ( "escassign: unexpected dst" )
case OARRAYLIT ,
OCLOSURE ,
OCONV ,
OCONVIFACE ,
OCONVNOP ,
OMAPLIT ,
OSTRUCTLIT ,
OPTRLIT ,
OCALLPART :
break
case ONAME :
if dst . Class == PEXTERN {
dst = & e . theSink
}
case ODOT : // treat "dst.x = src" as "dst = src"
escassign ( e , dst . Left , src )
return
case OINDEX :
2015-02-17 22:13:49 -05:00
if Isfixedarray ( dst . Left . Type ) {
2015-02-13 14:40:36 -05:00
escassign ( e , dst . Left , src )
return
}
dst = & e . theSink // lose track of dereference
case OIND ,
ODOTPTR :
dst = & e . theSink // lose track of dereference
// lose track of key and value
case OINDEXMAP :
escassign ( e , & e . theSink , dst . Right )
dst = & e . theSink
}
2015-02-23 16:07:24 -05:00
lno := int ( setlineno ( src ) )
2015-02-13 14:40:36 -05:00
e . pdepth ++
switch src . Op {
case OADDR , // dst = &x
OIND , // dst = *x
ODOTPTR , // dst = (*x).f
ONAME ,
OPARAM ,
ODDDARG ,
OPTRLIT ,
OARRAYLIT ,
OMAPLIT ,
OSTRUCTLIT ,
OMAKECHAN ,
OMAKEMAP ,
OMAKESLICE ,
OARRAYRUNESTR ,
OARRAYBYTESTR ,
OSTRARRAYRUNE ,
OSTRARRAYBYTE ,
OADDSTR ,
ONEW ,
OCLOSURE ,
OCALLPART ,
ORUNESTR :
escflows ( e , dst , src )
// Flowing multiple returns to a single dst happens when
// analyzing "go f(g())": here g() flows to sink (issue 4529).
case OCALLMETH ,
OCALLFUNC ,
OCALLINTER :
2015-02-23 16:07:24 -05:00
for ll := src . Escretval ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
escflows ( e , dst , ll . N )
}
// A non-pointer escaping from a struct does not concern us.
case ODOT :
if src . Type != nil && ! haspointers ( src . Type ) {
break
}
fallthrough
// Conversions, field access, slice all preserve the input value.
// fallthrough
case OCONV ,
OCONVIFACE ,
OCONVNOP ,
ODOTMETH ,
// treat recv.meth as a value with recv in it, only happens in ODEFER and OPROC
// iface.method already leaks iface in esccall, no need to put in extra ODOTINTER edge here
ODOTTYPE ,
ODOTTYPE2 ,
OSLICE ,
OSLICE3 ,
OSLICEARR ,
OSLICE3ARR ,
OSLICESTR :
escassign ( e , dst , src . Left )
// Append returns first argument.
case OAPPEND :
escassign ( e , dst , src . List . N )
// Index of array preserves input value.
case OINDEX :
2015-02-17 22:13:49 -05:00
if Isfixedarray ( src . Left . Type ) {
2015-02-13 14:40:36 -05:00
escassign ( e , dst , src . Left )
}
// Might be pointer arithmetic, in which case
// the operands flow into the result.
// TODO(rsc): Decide what the story is here. This is unsettling.
case OADD ,
OSUB ,
OOR ,
OXOR ,
OMUL ,
ODIV ,
OMOD ,
OLSH ,
ORSH ,
OAND ,
OANDNOT ,
OPLUS ,
OMINUS ,
OCOM :
escassign ( e , dst , src . Left )
escassign ( e , dst , src . Right )
}
e . pdepth --
lineno = int32 ( lno )
}
2015-03-02 16:03:26 -05:00
func escassignfromtag ( e * EscState , note * string , dsts * NodeList , src * Node ) int {
2015-02-13 14:40:36 -05:00
var em int
em = parsetag ( note )
if em == EscUnknown {
escassign ( e , & e . theSink , src )
return em
}
if em == EscNone {
return em
}
// If content inside parameter (reached via indirection)
// escapes back to results, mark as such.
if em & EscContentEscapes != 0 {
escassign ( e , & e . funcParam , src )
}
2015-02-23 16:07:24 -05:00
em0 := em
2015-02-13 14:40:36 -05:00
for em >>= EscReturnBits ; em != 0 && dsts != nil ; ( func ( ) { em >>= 1 ; dsts = dsts . Next } ) ( ) {
if em & 1 != 0 {
escassign ( e , dsts . N , src )
}
}
if em != 0 && dsts == nil {
2015-03-02 16:03:26 -05:00
Fatal ( "corrupt esc tag %q or messed up escretval list\n" , note )
2015-02-13 14:40:36 -05:00
}
return em0
}
// This is a bit messier than fortunate, pulled out of esc's big
// switch for clarity. We either have the paramnodes, which may be
// connected to other things through flows or we have the parameter type
// nodes, which may be marked "noescape". Navigating the ast is slightly
// different for methods vs plain functions and for imported vs
// this-package
func esccall ( e * EscState , n * Node , up * Node ) {
var ll * NodeList
var lr * NodeList
var fntype * Type
2015-03-02 14:22:05 -05:00
var fn * Node
2015-02-13 14:40:36 -05:00
switch n . Op {
default :
Fatal ( "esccall" )
case OCALLFUNC :
fn = n . Left
fntype = fn . Type
case OCALLMETH :
fn = n . Left . Right . Sym . Def
if fn != nil {
fntype = fn . Type
} else {
fntype = n . Left . Type
}
case OCALLINTER :
fntype = n . Left . Type
}
ll = n . List
if n . List != nil && n . List . Next == nil {
2015-02-23 16:07:24 -05:00
a := n . List . N
2015-02-13 14:40:36 -05:00
if a . Type . Etype == TSTRUCT && a . Type . Funarg != 0 { // f(g()).
ll = a . Escretval
}
}
if fn != nil && fn . Op == ONAME && fn . Class == PFUNC && fn . Defn != nil && fn . Defn . Nbody != nil && fn . Ntype != nil && fn . Defn . Esc < EscFuncTagged {
// function in same mutually recursive group. Incorporate into flow graph.
// print("esc local fn: %N\n", fn->ntype);
if fn . Defn . Esc == EscFuncUnknown || n . Escretval != nil {
Fatal ( "graph inconsistency" )
}
// set up out list on this call node
for lr = fn . Ntype . Rlist ; lr != nil ; lr = lr . Next {
n . Escretval = list ( n . Escretval , lr . N . Left ) // type.rlist -> dclfield -> ONAME (PPARAMOUT)
}
// Receiver.
if n . Op != OCALLFUNC {
escassign ( e , fn . Ntype . Left . Left , n . Left . Left )
}
2015-02-23 16:07:24 -05:00
var src * Node
2015-02-13 14:40:36 -05:00
for lr = fn . Ntype . List ; ll != nil && lr != nil ; ( func ( ) { ll = ll . Next ; lr = lr . Next } ) ( ) {
src = ll . N
2015-02-17 22:13:49 -05:00
if lr . N . Isddd != 0 && n . Isddd == 0 {
2015-02-13 14:40:36 -05:00
// Introduce ODDDARG node to represent ... allocation.
src = Nod ( ODDDARG , nil , nil )
src . Type = typ ( TARRAY )
src . Type . Type = lr . N . Type . Type
src . Type . Bound = int64 ( count ( ll ) )
src . Type = Ptrto ( src . Type ) // make pointer so it will be tracked
src . Escloopdepth = e . loopdepth
src . Lineno = n . Lineno
src . Esc = EscNone // until we find otherwise
e . noesc = list ( e . noesc , src )
n . Right = src
}
if lr . N . Left != nil {
escassign ( e , lr . N . Left , src )
}
if src != ll . N {
break
}
}
// "..." arguments are untracked
for ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N )
}
return
}
// Imported or completely analyzed function. Use the escape tags.
if n . Escretval != nil {
Fatal ( "esc already decorated call %v\n" , Nconv ( n , obj . FmtSign ) )
}
// set up out list on this call node with dummy auto ONAMES in the current (calling) function.
2015-02-23 16:07:24 -05:00
i := 0
2015-02-13 14:40:36 -05:00
2015-02-23 16:07:24 -05:00
var src * Node
var buf string
for t := getoutargx ( fntype ) . Type ; t != nil ; t = t . Down {
2015-02-13 14:40:36 -05:00
src = Nod ( ONAME , nil , nil )
buf = fmt . Sprintf ( ".dum%d" , i )
i ++
src . Sym = Lookup ( buf )
src . Type = t . Type
src . Class = PAUTO
src . Curfn = Curfn
src . Escloopdepth = e . loopdepth
src . Used = 1
src . Lineno = n . Lineno
n . Escretval = list ( n . Escretval , src )
}
// print("esc analyzed fn: %#N (%+T) returning (%+H)\n", fn, fntype, n->escretval);
// Receiver.
if n . Op != OCALLFUNC {
2015-02-23 16:07:24 -05:00
t := getthisx ( fntype ) . Type
src := n . Left . Left
2015-02-13 14:40:36 -05:00
if haspointers ( t . Type ) {
escassignfromtag ( e , t . Note , n . Escretval , src )
}
}
2015-02-23 16:07:24 -05:00
var a * Node
for t := getinargx ( fntype ) . Type ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
src = ll . N
2015-02-17 22:13:49 -05:00
if t . Isddd != 0 && n . Isddd == 0 {
2015-02-13 14:40:36 -05:00
// Introduce ODDDARG node to represent ... allocation.
src = Nod ( ODDDARG , nil , nil )
src . Escloopdepth = e . loopdepth
src . Lineno = n . Lineno
src . Type = typ ( TARRAY )
src . Type . Type = t . Type . Type
src . Type . Bound = int64 ( count ( ll ) )
src . Type = Ptrto ( src . Type ) // make pointer so it will be tracked
src . Esc = EscNone // until we find otherwise
e . noesc = list ( e . noesc , src )
n . Right = src
}
if haspointers ( t . Type ) {
if escassignfromtag ( e , t . Note , n . Escretval , src ) == EscNone && up . Op != ODEFER && up . Op != OPROC {
a = src
for a . Op == OCONVNOP {
a = a . Left
}
switch a . Op {
// The callee has already been analyzed, so its arguments have esc tags.
// The argument is marked as not escaping at all.
// Record that fact so that any temporary used for
// synthesizing this expression can be reclaimed when
// the function returns.
// This 'noescape' is even stronger than the usual esc == EscNone.
// src->esc == EscNone means that src does not escape the current function.
// src->noescape = 1 here means that src does not escape this statement
// in the current function.
case OCALLPART ,
OCLOSURE ,
ODDDARG ,
OARRAYLIT ,
OPTRLIT ,
OSTRUCTLIT :
a . Noescape = true
}
}
}
if src != ll . N {
break
}
t = t . Down
}
// "..." arguments are untracked
for ; ll != nil ; ll = ll . Next {
escassign ( e , & e . theSink , ll . N )
}
}
// Store the link src->dst in dst, throwing out some quick wins.
func escflows ( e * EscState , dst * Node , src * Node ) {
if dst == nil || src == nil || dst == src {
return
}
// Don't bother building a graph for scalars.
if src . Type != nil && ! haspointers ( src . Type ) {
return
}
if Debug [ 'm' ] > 2 {
fmt . Printf ( "%v::flows:: %v <- %v\n" , Ctxt . Line ( int ( lineno ) ) , Nconv ( dst , obj . FmtShort ) , Nconv ( src , obj . FmtShort ) )
}
if dst . Escflowsrc == nil {
e . dsts = list ( e . dsts , dst )
e . dstcount ++
}
e . edgecount ++
dst . Escflowsrc = list ( dst . Escflowsrc , src )
}
// Whenever we hit a reference node, the level goes up by one, and whenever
// we hit an OADDR, the level goes down by one. as long as we're on a level > 0
// finding an OADDR just means we're following the upstream of a dereference,
// so this address doesn't leak (yet).
// If level == 0, it means the /value/ of this node can reach the root of this flood.
// so if this node is an OADDR, it's argument should be marked as escaping iff
// it's currfn/e->loopdepth are different from the flood's root.
// Once an object has been moved to the heap, all of it's upstream should be considered
// escaping to the global scope.
func escflood ( e * EscState , dst * Node ) {
switch dst . Op {
case ONAME ,
OCLOSURE :
break
default :
return
}
if Debug [ 'm' ] > 1 {
var tmp * Sym
if dst . Curfn != nil && dst . Curfn . Nname != nil {
tmp = dst . Curfn . Nname . Sym
} else {
tmp = nil
}
fmt . Printf ( "\nescflood:%d: dst %v scope:%v[%d]\n" , walkgen , Nconv ( dst , obj . FmtShort ) , Sconv ( tmp , 0 ) , dst . Escloopdepth )
}
2015-02-23 16:07:24 -05:00
for l := dst . Escflowsrc ; l != nil ; l = l . Next {
2015-02-13 14:40:36 -05:00
walkgen ++
escwalk ( e , 0 , dst , l . N )
}
}
// There appear to be some loops in the escape graph, causing
// arbitrary recursion into deeper and deeper levels.
// Cut this off safely by making minLevel sticky: once you
// get that deep, you cannot go down any further but you also
// cannot go up any further. This is a conservative fix.
// Making minLevel smaller (more negative) would handle more
// complex chains of indirections followed by address-of operations,
// at the cost of repeating the traversal once for each additional
// allowed level when a loop is encountered. Using -2 suffices to
// pass all the tests we have written so far, which we assume matches
// the level of complexity we want the escape analysis code to handle.
const (
MinLevel = - 2
)
func escwalk ( e * EscState , level int , dst * Node , src * Node ) {
if src . Walkgen == walkgen && src . Esclevel <= int32 ( level ) {
return
}
src . Walkgen = walkgen
src . Esclevel = int32 ( level )
if Debug [ 'm' ] > 1 {
var tmp * Sym
if src . Curfn != nil && src . Curfn . Nname != nil {
tmp = src . Curfn . Nname . Sym
} else {
tmp = nil
}
fmt . Printf ( "escwalk: level:%d depth:%d %.*s %v(%v) scope:%v[%d]\n" , level , e . pdepth , e . pdepth , "\t\t\t\t\t\t\t\t\t\t" , Nconv ( src , obj . FmtShort ) , Jconv ( src , obj . FmtShort ) , Sconv ( tmp , 0 ) , src . Escloopdepth )
}
e . pdepth ++
// Input parameter flowing to output parameter?
2015-02-23 16:07:24 -05:00
var leaks bool
2015-02-13 14:40:36 -05:00
if dst . Op == ONAME && dst . Class == PPARAMOUT && dst . Vargen <= 20 {
if src . Op == ONAME && src . Class == PPARAM && src . Curfn == dst . Curfn && src . Esc != EscScope && src . Esc != EscHeap {
if level == 0 {
if Debug [ 'm' ] != 0 {
Warnl ( int ( src . Lineno ) , "leaking param: %v to result %v" , Nconv ( src , obj . FmtShort ) , Sconv ( dst . Sym , 0 ) )
}
if src . Esc & EscMask != EscReturn {
src . Esc = EscReturn
}
src . Esc |= 1 << uint ( ( dst . Vargen - 1 ) + EscReturnBits )
goto recurse
} else if level > 0 {
if Debug [ 'm' ] != 0 {
Warnl ( int ( src . Lineno ) , "%v leaking param %v content to result %v" , Nconv ( src . Curfn . Nname , 0 ) , Nconv ( src , obj . FmtShort ) , Sconv ( dst . Sym , 0 ) )
}
if src . Esc & EscMask != EscReturn {
src . Esc = EscReturn
}
src . Esc |= EscContentEscapes
goto recurse
}
}
}
// The second clause is for values pointed at by an object passed to a call
// that returns something reached via indirect from the object.
// We don't know which result it is or how many indirects, so we treat it as leaking.
2015-02-17 22:13:49 -05:00
leaks = level <= 0 && dst . Escloopdepth < src . Escloopdepth || level < 0 && dst == & e . funcParam && haspointers ( src . Type )
2015-02-13 14:40:36 -05:00
switch src . Op {
case ONAME :
2015-02-17 22:13:49 -05:00
if src . Class == PPARAM && ( leaks || dst . Escloopdepth < 0 ) && src . Esc != EscHeap {
2015-02-13 14:40:36 -05:00
src . Esc = EscScope
if Debug [ 'm' ] != 0 {
Warnl ( int ( src . Lineno ) , "leaking param: %v" , Nconv ( src , obj . FmtShort ) )
}
}
// Treat a PPARAMREF closure variable as equivalent to the
// original variable.
if src . Class == PPARAMREF {
2015-02-17 22:13:49 -05:00
if leaks && Debug [ 'm' ] != 0 {
2015-02-13 14:40:36 -05:00
Warnl ( int ( src . Lineno ) , "leaking closure reference %v" , Nconv ( src , obj . FmtShort ) )
}
escwalk ( e , level , dst , src . Closure )
}
case OPTRLIT ,
OADDR :
2015-02-17 22:13:49 -05:00
if leaks {
2015-02-13 14:40:36 -05:00
src . Esc = EscHeap
addrescapes ( src . Left )
if Debug [ 'm' ] != 0 {
Warnl ( int ( src . Lineno ) , "%v escapes to heap" , Nconv ( src , obj . FmtShort ) )
}
}
2015-02-23 16:07:24 -05:00
newlevel := level
2015-02-13 14:40:36 -05:00
if level > MinLevel {
newlevel --
}
escwalk ( e , newlevel , dst , src . Left )
case OARRAYLIT :
2015-02-17 22:13:49 -05:00
if Isfixedarray ( src . Type ) {
2015-02-13 14:40:36 -05:00
break
}
fallthrough
// fall through
case ODDDARG ,
OMAKECHAN ,
OMAKEMAP ,
OMAKESLICE ,
OARRAYRUNESTR ,
OARRAYBYTESTR ,
OSTRARRAYRUNE ,
OSTRARRAYBYTE ,
OADDSTR ,
OMAPLIT ,
ONEW ,
OCLOSURE ,
OCALLPART ,
ORUNESTR :
2015-02-17 22:13:49 -05:00
if leaks {
2015-02-13 14:40:36 -05:00
src . Esc = EscHeap
if Debug [ 'm' ] != 0 {
Warnl ( int ( src . Lineno ) , "%v escapes to heap" , Nconv ( src , obj . FmtShort ) )
}
}
case ODOT ,
OSLICE ,
OSLICEARR ,
OSLICE3 ,
OSLICE3ARR ,
OSLICESTR :
escwalk ( e , level , dst , src . Left )
case OINDEX :
2015-02-17 22:13:49 -05:00
if Isfixedarray ( src . Left . Type ) {
2015-02-13 14:40:36 -05:00
escwalk ( e , level , dst , src . Left )
break
}
fallthrough
// fall through
case ODOTPTR ,
OINDEXMAP ,
OIND :
2015-02-23 16:07:24 -05:00
newlevel := level
2015-02-13 14:40:36 -05:00
if level > MinLevel {
newlevel ++
}
escwalk ( e , newlevel , dst , src . Left )
}
recurse :
2015-02-23 16:07:24 -05:00
for ll := src . Escflowsrc ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
escwalk ( e , level , dst , ll . N )
}
e . pdepth --
}
func esctag ( e * EscState , func_ * Node ) {
func_ . Esc = EscFuncTagged
// External functions are assumed unsafe,
// unless //go:noescape is given before the declaration.
if func_ . Nbody == nil {
if func_ . Noescape {
2015-02-23 16:07:24 -05:00
for t := getinargx ( func_ . Type ) . Type ; t != nil ; t = t . Down {
2015-02-13 14:40:36 -05:00
if haspointers ( t . Type ) {
t . Note = mktag ( EscNone )
}
}
}
return
}
2015-02-23 16:07:24 -05:00
savefn := Curfn
2015-02-13 14:40:36 -05:00
Curfn = func_
2015-02-23 16:07:24 -05:00
for ll := Curfn . Dcl ; ll != nil ; ll = ll . Next {
2015-02-13 14:40:36 -05:00
if ll . N . Op != ONAME || ll . N . Class != PPARAM {
continue
}
switch ll . N . Esc & EscMask {
case EscNone , // not touched by escflood
EscReturn :
if haspointers ( ll . N . Type ) { // don't bother tagging for scalars
ll . N . Paramfld . Note = mktag ( int ( ll . N . Esc ) )
}
case EscHeap , // touched by escflood, moved to heap
EscScope : // touched by escflood, value leaves scope
break
}
}
Curfn = savefn
}