src/cmd/compile/internal/escape/solve.go GO 490 lines View on github.com → Search inside
1// Copyright 2018 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45package escape67import (8	"cmd/compile/internal/base"9	"cmd/compile/internal/ir"10	"cmd/compile/internal/logopt"11	"cmd/internal/src"12	"fmt"13	"math/bits"14	"strings"15)1617// walkState contains the root properties used by a walk. Roots with equal18// states can be analyzed together.19type walkState struct {20	sink      *location // canonical leak sink; not necessarily a walk root21	curfn     *ir.Func22	loopDepth int23	attrs     locAttr24}2526func (s walkState) hasAttr(attr locAttr) bool { return s.attrs&attr != 0 }2728// walkPath is an immutable path from a location to one of the roots of the29// current walk.30type walkPath struct {31	root    *location // root reached by this path32	dst     *location // destination of the first edge33	edgeIdx int       // index of the first edge in dst.edges34	next    *walkPath // path from dst to root35}3637// walkState returns the normalized walk state for loc.38func (b *batch) walkState(loc *location) walkState {39	s := walkState{40		sink:      &b.heapLoc,41		curfn:     loc.curfn,42		loopDepth: loc.loopDepth,43		attrs:     loc.attrs,44	}45	if loc.paramOut || loc == &b.mutatorLoc || loc == &b.calleeLoc {46		s.sink = loc47		return s48	}49	if loc.hasAttr(attrEscapes) {50		// outlives returns true for all escaping roots.51		s.curfn = nil52		s.loopDepth = 053	}54	return s55}5657// walkAll computes the minimal dereferences from each group of roots to58// all other locations.59func (b *batch) walkAll() {60	// We use a work queue to keep track of locations that we need61	// to visit, and repeatedly walk until we reach a fixed point.62	//63	// We walk once from each group of locations with the same state, since64	// their effects depend only on the minimum dereference count from the65	// group. We re-enqueue locations when their attributes change, grouping66	// them using their new state.6768	// Queue of locations to walk. Has enough room for b.allLocs69	// plus b.heapLoc, b.mutatorLoc, b.calleeLoc.70	todo := newQueue(len(b.allLocs) + 3)7172	enqueue := func(loc *location) {73		if !loc.queuedWalkAll {74			loc.queuedWalkAll = true75			if loc.hasAttr(attrEscapes) {76				// Favor locations that escape to the heap,77				// which in some cases allows attrEscape to78				// propagate faster.79				todo.pushFront(loc)80			} else {81				todo.pushBack(loc)82			}83		}84	}8586	for _, loc := range b.allLocs {87		todo.pushFront(loc)88		// TODO(thepudds): clean up setting queuedWalkAll.89		loc.queuedWalkAll = true90	}91	todo.pushFront(&b.mutatorLoc)92	todo.pushFront(&b.calleeLoc)93	todo.pushFront(&b.heapLoc)9495	b.mutatorLoc.queuedWalkAll = true96	b.calleeLoc.queuedWalkAll = true97	b.heapLoc.queuedWalkAll = true9899	var walkgen uint32100	walkTodo := newQueue(len(b.allLocs) + 3)101	groups := make(map[walkState][]*location)102	var states []walkState103	for todo.len() > 0 {104		// Process the queue in rounds. At the start of a round, group roots105		// whose walk states are equal. Each walk uses the captured state rather106		// than the roots' live attributes, so the roots only need to agree when107		// they are grouped.108		//109		// An earlier walk may add attributes to a root scheduled for a later110		// group. The root is then re-enqueued to be walked with its new state111		// in the next round. Walking it here with its old state is still safe,112		// because attributes and the effects propagated from them only grow.113		clear(groups)114		states = states[:0]115		for todo.len() > 0 {116			root := todo.popFront()117			root.queuedWalkAll = false118			state := b.walkState(root)119			if _, ok := groups[state]; !ok {120				states = append(states, state)121			}122			groups[state] = append(groups[state], root)123		}124		for _, state := range states {125			walkgen++126			b.walk(state, groups[state], walkgen, enqueue, walkTodo)127		}128	}129}130131// walk computes the minimal number of dereferences from roots that were132// scheduled with state s to all other locations. A root's live attributes may133// have grown since it was scheduled; the resulting new state is walked in a134// later round.135func (b *batch) walk(s walkState, roots []*location, walkgen uint32, enqueue func(*location), todo *queue) {136	// The data flow graph has negative edges (from addressing137	// operations), so we use the Bellman-Ford algorithm. However,138	// we don't have to worry about infinite negative cycles since139	// we bound intermediate dereference counts to 0.140141	diagnose := base.Flag.LowerM >= 2 || logopt.Enabled()142	var paths map[*location]*walkPath143	if diagnose {144		paths = make(map[*location]*walkPath)145	}146	todo.reset()147	for _, r := range roots {148		r.walkgen = walkgen149		r.derefs = 0150		r.queuedWalk = walkgen151		todo.pushBack(r)152153		if s.hasAttr(attrCalls) {154			if clo, ok := r.n.(*ir.ClosureExpr); ok {155				if fn := clo.Func; b.inMutualBatch(fn.Nname) && !fn.ClosureResultsLost() {156					fn.SetClosureResultsLost(true)157158					// Re-flow from the closure's results, now that we're aware159					// we lost track of them.160					for _, result := range fn.Type().Results() {161						enqueue(b.oldLoc(result.Nname.(*ir.Name)))162					}163				}164			}165		}166	}167168	for todo.len() > 0 {169		l := todo.popFront()170		l.queuedWalk = 0 // no longer queued for walk171172		derefs := l.derefs173		var newAttrs locAttr174175		// If l.derefs < 0, then l's address flows to root.176		addressOf := derefs < 0177		if addressOf {178			// For a flow path like "root = &l; l = x",179			// l's address flows to root, but x's does180			// not. We recognize this by lower bounding181			// derefs at 0.182			derefs = 0183184			// If l's address flows somewhere that185			// outlives it, then l needs to be heap186			// allocated.187			if s.outlives(b, l) {188				if !l.hasAttr(attrEscapes) && diagnose {189					if base.Flag.LowerM >= 2 {190						fmt.Printf("%s: %v escapes to heap in %v:\n", base.FmtPos(l.n.Pos()), l.n, ir.FuncName(l.curfn))191					}192					root := walkRoot(l, paths)193					explanation := b.explainPath(root, l, paths[l])194					if logopt.Enabled() {195						var e_curfn *ir.Func // TODO(mdempsky): Fix.196						logopt.LogOpt(l.n.Pos(), "escape", "escape", ir.FuncName(e_curfn), fmt.Sprintf("%v escapes to heap", l.n), explanation)197					}198				}199				newAttrs |= attrEscapes | attrPersists | attrMutates | attrCalls200			} else201			// If l's address flows to a persistent location, then l needs202			// to persist too.203			if s.hasAttr(attrPersists) {204				newAttrs |= attrPersists205			}206		}207208		if derefs == 0 {209			newAttrs |= s.attrs & (attrMutates | attrCalls)210		}211212		// l's value flows to root. If l is a function213		// parameter and root is the heap or a214		// corresponding result parameter, then record215		// that value flow for tagging the function216		// later.217		if l.param {218			if s.outlives(b, l) {219				if !l.hasAttr(attrEscapes) && diagnose {220					root := walkRoot(l, paths)221					if base.Flag.LowerM >= 2 {222						fmt.Printf("%s: parameter %v leaks to %s for %v with derefs=%d:\n", base.FmtPos(l.n.Pos()), l.n, b.explainLoc(root), ir.FuncName(l.curfn), derefs)223					}224					explanation := b.explainPath(root, l, paths[l])225					if logopt.Enabled() {226						var e_curfn *ir.Func // TODO(mdempsky): Fix.227						logopt.LogOpt(l.n.Pos(), "leak", "escape", ir.FuncName(e_curfn),228							fmt.Sprintf("parameter %v leaks to %s with derefs=%d", l.n, b.explainLoc(root), derefs), explanation)229					}230				}231				l.leakTo(s.sink, derefs)232			}233			if s.hasAttr(attrMutates) {234				l.paramEsc.AddMutator(derefs)235			}236			if s.hasAttr(attrCalls) {237				l.paramEsc.AddCallee(derefs)238			}239		}240241		if newAttrs&^l.attrs != 0 {242			l.attrs |= newAttrs243			enqueue(l)244			if l.attrs&attrEscapes != 0 {245				continue246			}247		}248249		for i, edge := range l.edges {250			if edge.src.hasAttr(attrEscapes) {251				continue252			}253			d := derefs + edge.derefs254			if edge.src.walkgen != walkgen || edge.src.derefs > d {255				edge.src.walkgen = walkgen256				edge.src.derefs = d257				if diagnose {258					paths[edge.src] = &walkPath{259						root:    walkRoot(l, paths),260						dst:     l,261						edgeIdx: i,262						next:    paths[l],263					}264				}265				// Check if already queued in todo.266				if edge.src.queuedWalk != walkgen {267					edge.src.queuedWalk = walkgen // Mark queued for this walkgen.268269					// Place at the back to possibly give time for270					// other possible attribute changes to src.271					todo.pushBack(edge.src)272				}273			}274		}275	}276}277278func walkRoot(l *location, paths map[*location]*walkPath) *location {279	if path := paths[l]; path != nil {280		return path.root281	}282	return l283}284285// explainPath prints an explanation of how src flows to the walk root.286func (b *batch) explainPath(root, src *location, path *walkPath) []*logopt.LoggedOpt {287	visited := make(map[*location]bool)288	pos := base.FmtPos(src.n.Pos())289	var explanation []*logopt.LoggedOpt290	for ; path != nil; path = path.next {291		// Prevent infinite loop.292		if visited[src] {293			if base.Flag.LowerM >= 2 {294				fmt.Printf("%s:   warning: truncated explanation due to assignment cycle; see golang.org/issue/35518\n", pos)295			}296			return explanation297		}298		visited[src] = true299		dst := path.dst300		edge := &dst.edges[path.edgeIdx]301		if edge.src != src {302			base.Fatalf("path inconsistency: %v != %v", edge.src, src)303		}304305		explanation = b.explainFlow(pos, dst, src, edge.derefs, edge.notes, explanation)306307		src = dst308	}309	if src != root {310		base.Fatalf("path root inconsistency: %v != %v", src, root)311	}312313	return explanation314}315316func (b *batch) explainFlow(pos string, dst, srcloc *location, derefs int, notes *note, explanation []*logopt.LoggedOpt) []*logopt.LoggedOpt {317	ops := "&"318	if derefs >= 0 {319		ops = strings.Repeat("*", derefs)320	}321	print := base.Flag.LowerM >= 2322323	flow := fmt.Sprintf("   flow: %s ← %s%v:", b.explainLoc(dst), ops, b.explainLoc(srcloc))324	if print {325		fmt.Printf("%s:%s\n", pos, flow)326	}327	if logopt.Enabled() {328		var epos src.XPos329		if notes != nil {330			epos = notes.where.Pos()331		} else if srcloc != nil && srcloc.n != nil {332			epos = srcloc.n.Pos()333		}334		var e_curfn *ir.Func // TODO(mdempsky): Fix.335		explanation = append(explanation, logopt.NewLoggedOpt(epos, epos, "escflow", "escape", ir.FuncName(e_curfn), flow))336	}337338	for note := notes; note != nil; note = note.next {339		if print {340			fmt.Printf("%s:     from %v (%v) at %s\n", pos, note.where, note.why, base.FmtPos(note.where.Pos()))341		}342		if logopt.Enabled() {343			var e_curfn *ir.Func // TODO(mdempsky): Fix.344			notePos := note.where.Pos()345			explanation = append(explanation, logopt.NewLoggedOpt(notePos, notePos, "escflow", "escape", ir.FuncName(e_curfn),346				fmt.Sprintf("     from %v (%v)", note.where, note.why)))347		}348	}349	return explanation350}351352func (b *batch) explainLoc(l *location) string {353	if l == &b.heapLoc {354		return "{heap}"355	}356	if l.n == nil {357		// TODO(mdempsky): Omit entirely.358		return "{temp}"359	}360	if l.n.Op() == ir.ONAME {361		return fmt.Sprintf("%v", l.n)362	}363	return fmt.Sprintf("{storage for %v}", l.n)364}365366// outlives reports whether values stored in roots with state s may survive367// beyond other's lifetime if stack allocated.368func (s walkState) outlives(b *batch, other *location) bool {369	// The heap outlives everything.370	if s.hasAttr(attrEscapes) {371		return true372	}373374	// Pseudo-locations that don't really exist.375	if s.sink == &b.mutatorLoc || s.sink == &b.calleeLoc {376		return false377	}378379	// We don't know what callers do with returned values, so380	// pessimistically we need to assume they flow to the heap and381	// outlive everything too.382	if s.sink != nil && s.sink.paramOut {383		// Exception: Closures can return locations allocated outside of384		// them without forcing them to the heap, if we can statically385		// identify all call sites. For example:386		//387		//	var u int  // okay to stack allocate388		//	fn := func() *int { return &u }()389		//	*fn() = 42390		if ir.ContainsClosure(other.curfn, s.curfn) && !s.curfn.ClosureResultsLost() {391			return false392		}393394		return true395	}396397	// If root and other are within the same function, then root398	// outlives other if it was declared outside other's loop399	// scope. For example:400	//401	//	var l *int402	//	for {403	//		l = new(int) // must heap allocate: outlives for loop404	//	}405	if s.curfn == other.curfn && s.loopDepth < other.loopDepth {406		return true407	}408409	// If other is declared within a child closure of where root is410	// declared, then root outlives it. For example:411	//412	//	var l *int413	//	func() {414	//		l = new(int) // must heap allocate: outlives call frame (if not inlined)415	//	}()416	if ir.ContainsClosure(s.curfn, other.curfn) {417		return true418	}419420	return false421}422423// queue implements a queue of locations for use in walkAll and walk.424// It supports pushing to front & back, and popping from front.425// TODO(thepudds): does cmd/compile have a deque or similar somewhere?426type queue struct {427	locs  []*location428	head  int // index of front element429	tail  int // next back element430	elems int431}432433func newQueue(capacity int) *queue {434	capacity = max(capacity, 2)435	capacity = 1 << bits.Len64(uint64(capacity-1)) // round up to a power of 2436	return &queue{locs: make([]*location, capacity)}437}438439func (q *queue) reset() {440	q.head = 0441	q.tail = 0442	q.elems = 0443}444445// pushFront adds an element to the front of the queue.446func (q *queue) pushFront(loc *location) {447	if q.elems == len(q.locs) {448		q.grow()449	}450	q.head = q.wrap(q.head - 1)451	q.locs[q.head] = loc452	q.elems++453}454455// pushBack adds an element to the back of the queue.456func (q *queue) pushBack(loc *location) {457	if q.elems == len(q.locs) {458		q.grow()459	}460	q.locs[q.tail] = loc461	q.tail = q.wrap(q.tail + 1)462	q.elems++463}464465// popFront removes the front of the queue.466func (q *queue) popFront() *location {467	if q.elems == 0 {468		return nil469	}470	loc := q.locs[q.head]471	q.head = q.wrap(q.head + 1)472	q.elems--473	return loc474}475476// grow doubles the capacity.477func (q *queue) grow() {478	newLocs := make([]*location, len(q.locs)*2)479	for i := range q.elems {480		// Copy over our elements in order.481		newLocs[i] = q.locs[q.wrap(q.head+i)]482	}483	q.locs = newLocs484	q.head = 0485	q.tail = q.elems486}487488func (q *queue) len() int       { return q.elems }489func (q *queue) wrap(i int) int { return i & (len(q.locs) - 1) }

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.