src/cmd/compile/internal/ssa/stackalloc.go GO 464 lines View on github.com → Search inside
1// Copyright 2015 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.45// TODO: live at start of block instead?67package ssa89import (10	"fmt"1112	"cmd/compile/internal/ir"13	"cmd/compile/internal/ssa/ssaop"14	"cmd/compile/internal/types"15	"cmd/internal/src"16)1718func NewStackAllocState(f *Func) *StackAllocState {19	s := f.Cache.stackAllocState20	if s == nil {21		return new(StackAllocState)22	}23	if s.f != nil {24		f.Fe.Fatalf(src.NoXPos, "newStackAllocState called without previous free")25	}26	return s27}2829func PutStackAllocState(s *StackAllocState) {30	clear(s.values)31	clear(s.interfere)32	clear(s.names)33	s.f.Cache.stackAllocState = s34	s.f = nil35	s.Live = nil36	s.NArgSlot, s.NNotNeed, s.NNamedSlot, s.NReuse, s.NAuto, s.NSelfInterfere = 0, 0, 0, 0, 0, 037}3839type StackAllocState struct {40	f *Func4142	// Live is the output of stackalloc.43	// Live[b.id] = Live values at the end of block b.44	Live [][]ID4546	// The following slices are reused across multiple users47	// of stackAllocState.48	values    []stackValState49	interfere [][]ID // interfere[v.id] = values that interfere with v.50	names     []LocalSlot5152	NArgSlot, // Number of Values sourced to arg slot53	NNotNeed, // Number of Values not needing a stack slot54	NNamedSlot, // Number of Values using a named stack slot55	NReuse, // Number of values reusing a stack slot56	NAuto, // Number of autos allocated for stack slots.57	NSelfInterfere int32 // Number of self-interferences58}5960func hasAnyArgOp(v *Value) bool {61	return v.Op == ssaop.OpArg || v.Op == ssaop.OpArgIntReg || v.Op == ssaop.OpArgFloatReg62}6364type stackUseBlock struct {65	b       *Block66	liveout bool67}6869type stackValState struct {70	typ       *types.Type71	spill     *Value72	needSlot  bool73	isArg     bool74	defBlock  ID75	useBlocks []stackUseBlock76}7778// addUseBlock adds a block to the set of blocks that uses this value.79// Note that we only loosely enforce the set property by checking the last80// block that was appended to the list and duplicates may occur.81// Because we add values block by block (barring phi-nodes), the number of duplicates is82// small and we deduplicate as part of the liveness algorithm later anyway.83func (sv *stackValState) addUseBlock(b *Block, liveout bool) {84	entry := stackUseBlock{85		b:       b,86		liveout: liveout,87	}88	if sv.useBlocks == nil || sv.useBlocks[len(sv.useBlocks)-1] != entry {89		sv.useBlocks = append(sv.useBlocks, stackUseBlock{90			b:       b,91			liveout: liveout,92		})93	}94}9596func (s *StackAllocState) Init(f *Func, spillLive [][]ID) {97	s.f = f9899	// Initialize value information.100	if n := f.NumValues(); cap(s.values) >= n {101		s.values = s.values[:n]102	} else {103		s.values = make([]stackValState, n)104	}105	for _, b := range f.Blocks {106		for _, v := range b.Values {107			s.values[v.ID].typ = v.Type108			s.values[v.ID].needSlot = !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && f.GetHome(v.ID) == nil && !v.Rematerializeable() && !v.OnWasmStack109			s.values[v.ID].isArg = hasAnyArgOp(v)110			s.values[v.ID].defBlock = b.ID111			if f.Pass.Debug > StackDebug && s.values[v.ID].needSlot {112				fmt.Printf("%s needs a stack slot\n", v)113			}114			if v.Op == ssaop.OpStoreReg {115				s.values[v.Args[0].ID].spill = v116			}117		}118	}119120	// Compute liveness info for values needing a slot.121	s.computeLive(spillLive)122123	// Build interference graph among values needing a slot.124	s.buildInterferenceGraph()125}126127func (s *StackAllocState) Stackalloc() {128	f := s.f129130	// Build map from values to their names, if any.131	// A value may be associated with more than one name (e.g. after132	// the assignment i=j). This step picks one name per value arbitrarily.133	if n := f.NumValues(); cap(s.names) >= n {134		s.names = s.names[:n]135	} else {136		s.names = make([]LocalSlot, n)137	}138	names := s.names139	empty := LocalSlot{}140	for _, name := range f.Names {141		// Note: not "range f.NamedValues" above, because142		// that would be nondeterministic.143		for _, v := range f.NamedValues[name] {144			if v.Op == ssaop.OpArgIntReg || v.Op == ssaop.OpArgFloatReg {145				aux := v.Aux.(*AuxNameOffset)146				// Never let an arg be bound to a differently named thing.147				if name.N != aux.Name || name.Off != aux.Offset {148					if f.Pass.Debug > StackDebug {149						fmt.Printf("stackalloc register arg %s skipping name %s\n", v, name)150					}151					continue152				}153			} else if name.N.Class == ir.PPARAM && v.Op != ssaop.OpArg {154				// PPARAM's only bind to OpArg155				if f.Pass.Debug > StackDebug {156					fmt.Printf("stackalloc PPARAM name %s skipping non-Arg %s\n", name, v)157				}158				continue159			}160161			if names[v.ID] == empty {162				if f.Pass.Debug > StackDebug {163					fmt.Printf("stackalloc value %s to name %s\n", v, name)164				}165				names[v.ID] = name166			}167		}168	}169170	// Allocate args to their assigned locations.171	for _, v := range f.Entry.Values {172		if !hasAnyArgOp(v) {173			continue174		}175		if v.Aux == nil {176			f.Fatalf("%s has nil Aux\n", v.LongString())177		}178		if v.Op == ssaop.OpArg {179			loc := LocalSlot{N: v.Aux.(*ir.Name), Type: v.Type, Off: v.AuxInt}180			if f.Pass.Debug > StackDebug {181				fmt.Printf("stackalloc OpArg %s to %s\n", v, loc)182			}183			f.SetHome(v, loc)184			continue185		}186		// You might think this below would be the right idea, but you would be wrong.187		// It almost works; as of 105a6e9518 - 2021-04-23,188		// GOSSAHASH=11011011001011111 == cmd/compile/internal/noder.(*noder).embedded189		// is compiled incorrectly.  I believe the cause is one of those SSA-to-registers190		// puzzles that the register allocator untangles; in the event that a register191		// parameter does not end up bound to a name, "fixing" it is a bad idea.192		//193		//if f.DebugTest {194		//	if v.Op == OpArgIntReg || v.Op == OpArgFloatReg {195		//		aux := v.Aux.(*AuxNameOffset)196		//		loc := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset}197		//		if f.pass.debug > stackDebug {198		//			fmt.Printf("stackalloc Op%s %s to %s\n", v.Op, v, loc)199		//		}200		//		names[v.ID] = loc201		//		continue202		//	}203		//}204205	}206207	// For each type, we keep track of all the stack slots we208	// have allocated for that type. This map is keyed by209	// strings returned by types.LinkString. This guarantees210	// type equality, but also lets us match the same type represented211	// by two different types.Type structures. See issue 65783.212	locations := map[string][]LocalSlot{}213214	// Each time we assign a stack slot to a value v, we remember215	// the slot we used via an index into locations[v.Type].216	slots := f.Cache.AllocIntSlice(f.NumValues())217	defer f.Cache.FreeIntSlice(slots)218	for i := range slots {219		slots[i] = -1220	}221222	// Pick a stack slot for each value needing one.223	used := f.Cache.AllocBoolSlice(f.NumValues())224	defer f.Cache.FreeBoolSlice(used)225	for _, b := range f.Blocks {226		for _, v := range b.Values {227			if !s.values[v.ID].needSlot {228				s.NNotNeed++229				continue230			}231			if hasAnyArgOp(v) {232				s.NArgSlot++233				continue // already picked234			}235236			// If this is a named value, try to use the name as237			// the spill location.238			var name LocalSlot239			if v.Op == ssaop.OpStoreReg {240				name = names[v.Args[0].ID]241			} else {242				name = names[v.ID]243			}244			if name.N != nil && v.Type.Compare(name.Type) == types.CMPeq {245				for _, id := range s.interfere[v.ID] {246					h := f.GetHome(id)247					if h != nil && h.(LocalSlot).N == name.N && h.(LocalSlot).Off == name.Off {248						// A variable can interfere with itself.249						// It is rare, but it can happen.250						s.NSelfInterfere++251						goto noname252					}253				}254				if f.Pass.Debug > StackDebug {255					fmt.Printf("stackalloc %s to %s\n", v, name)256				}257				s.NNamedSlot++258				f.SetHome(v, name)259				continue260			}261262		noname:263			// Set of stack slots we could reuse.264			typeKey := v.Type.LinkString()265			locs := locations[typeKey]266			// Mark all positions in locs used by interfering values.267			for i := 0; i < len(locs); i++ {268				used[i] = false269			}270			for _, xid := range s.interfere[v.ID] {271				slot := slots[xid]272				if slot >= 0 {273					used[slot] = true274				}275			}276			// Find an unused stack slot.277			var i int278			for i = 0; i < len(locs); i++ {279				if !used[i] {280					s.NReuse++281					break282				}283			}284			// If there is no unused stack slot, allocate a new one.285			if i == len(locs) {286				s.NAuto++287				locs = append(locs, LocalSlot{N: f.NewLocal(v.Pos, v.Type), Type: v.Type, Off: 0})288				locations[typeKey] = locs289			}290			// Use the stack variable at that index for v.291			loc := locs[i]292			if f.Pass.Debug > StackDebug {293				fmt.Printf("stackalloc %s to %s\n", v, loc)294			}295			f.SetHome(v, loc)296			slots[v.ID] = i297		}298	}299}300301// computeLive computes a map from block ID to a list of302// stack-slot-needing value IDs live at the end of that block.303func (s *StackAllocState) computeLive(spillLive [][]ID) {304305	// Because values using stack slots are few and far inbetween306	// (compared to the set of all values), we use a path exploration307	// algorithm to calculate liveness here.308	f := s.f309	for _, b := range f.Blocks {310		for _, spillvid := range spillLive[b.ID] {311			val := &s.values[spillvid]312			val.addUseBlock(b, true)313		}314		for _, v := range b.Values {315			for i, a := range v.Args {316				val := &s.values[a.ID]317				useBlock := b318				forceLiveout := false319				if v.Op == ssaop.OpPhi {320					useBlock = b.Preds[i].B321					forceLiveout = true322					if spill := val.spill; spill != nil {323						//TODO: remove?  Subsumed by SpillUse?324						s.values[spill.ID].addUseBlock(useBlock, true)325					}326				}327				if !val.needSlot {328					continue329				}330				val.addUseBlock(useBlock, forceLiveout)331			}332		}333	}334335	s.Live = make([][]ID, f.NumBlocks())336	push := func(bid, vid ID) {337		l := s.Live[bid]338		if l == nil || l[len(l)-1] != vid {339			l = append(l, vid)340			s.Live[bid] = l341		}342	}343	// TODO: If we can help along the interference graph by calculating livein sets,344	// we can do so trivially by turning this sparse set into an array of arrays345	// and checking the top for the current value instead of inclusion in the sparse set.346	seen := f.NewSparseSet(f.NumBlocks())347	defer f.RetSparseSet(seen)348	// instead of pruning out duplicate blocks when we build the useblocks slices349	// or when we add them to the queue, rely on the seen set to stop considering350	// them. This is slightly faster than building the workqueues as sets351	//352	// However, this means that the queue can grow larger than the number of blocks,353	// usually in very short functions. Returning a slice with values appended beyond the354	// original allocation can corrupt the allocator state, so cap the queue and return355	// the originally allocated slice regardless.356	allocedBqueue := f.Cache.AllocBlockSlice(f.NumBlocks())357	defer f.Cache.FreeBlockSlice(allocedBqueue)358	bqueue := allocedBqueue[:0:f.NumBlocks()]359360	for vid, v := range s.values {361		if !v.needSlot {362			continue363		}364		seen.Clear()365		bqueue = bqueue[:0]366		for _, b := range v.useBlocks {367			if b.liveout {368				push(b.b.ID, ID(vid))369			}370			bqueue = append(bqueue, b.b)371		}372		for len(bqueue) > 0 {373			work := bqueue[len(bqueue)-1]374			bqueue = bqueue[:len(bqueue)-1]375			if seen.Contains(work.ID) || work.ID == v.defBlock {376				continue377			}378			seen.Add(work.ID)379			for _, e := range work.Preds {380				push(e.B.ID, ID(vid))381				bqueue = append(bqueue, e.B)382			}383		}384	}385386	if s.f.Pass.Debug > StackDebug {387		for _, b := range s.f.Blocks {388			fmt.Printf("stacklive %s %v\n", b, s.Live[b.ID])389		}390	}391}392393func (f *Func) GetHome(vid ID) Location {394	if int(vid) >= len(f.RegAlloc) {395		return nil396	}397	return f.RegAlloc[vid]398}399400func (f *Func) SetHome(v *Value, loc Location) {401	for v.ID >= ID(len(f.RegAlloc)) {402		f.RegAlloc = append(f.RegAlloc, nil)403	}404	f.RegAlloc[v.ID] = loc405}406407func (s *StackAllocState) buildInterferenceGraph() {408	f := s.f409	if n := f.NumValues(); cap(s.interfere) >= n {410		s.interfere = s.interfere[:n]411	} else {412		s.interfere = make([][]ID, n)413	}414	live := f.NewSparseSet(f.NumValues())415	defer f.RetSparseSet(live)416	for _, b := range f.Blocks {417		// Propagate liveness backwards to the start of the block.418		// Two values interfere if one is defined while the other is live.419		live.Clear()420		live.addAll(s.Live[b.ID])421		for i := len(b.Values) - 1; i >= 0; i-- {422			v := b.Values[i]423			if s.values[v.ID].needSlot {424				live.Remove(v.ID)425				for _, id := range live.Contents() {426					// Note: args can have different types and still interfere427					// (with each other or with other values). See issue 23522.428					if s.values[v.ID].typ.Compare(s.values[id].typ) == types.CMPeq || hasAnyArgOp(v) || s.values[id].isArg {429						s.interfere[v.ID] = append(s.interfere[v.ID], id)430						s.interfere[id] = append(s.interfere[id], v.ID)431					}432				}433			}434			for _, a := range v.Args {435				if s.values[a.ID].needSlot {436					live.Add(a.ID)437				}438			}439			if hasAnyArgOp(v) && s.values[v.ID].needSlot {440				// OpArg is an input argument which is pre-spilled.441				// We add back v.ID here because we want this value442				// to appear live even before this point. Being live443				// all the way to the start of the entry block prevents other444				// values from being allocated to the same slot and clobbering445				// the input value before we have a chance to load it.446447				// TODO(register args) this is apparently not wrong for register args -- is it necessary?448				live.Add(v.ID)449			}450		}451	}452	if f.Pass.Debug > StackDebug {453		for vid, i := range s.interfere {454			if len(i) > 0 {455				fmt.Printf("v%d interferes with", vid)456				for _, x := range i {457					fmt.Printf(" v%d", x)458				}459				fmt.Println()460			}461		}462	}463}

Code quality findings 37

Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer f.Cache.FreeIntSlice(slots)
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer f.Cache.FreeBoolSlice(used)
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer f.Cache.FreeBlockSlice(allocedBqueue)
Defer inside loop; deferred calls accumulate until the function returns, not until the loop iteration ends. This can cause resource leaks
warning correctness defer-in-loop
defer f.RetSparseSet(live)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
sv.useBlocks = append(sv.useBlocks, stackUseBlock{
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("%s needs a stack slot\n", v)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, name := range f.Names {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, v := range f.NamedValues[name] {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if f.Pass.Debug > StackDebug {
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc register arg %s skipping name %s\n", v, name)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc PPARAM name %s skipping non-Arg %s\n", name, v)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc value %s to name %s\n", v, name)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc OpArg %s to %s\n", v, loc)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
// fmt.Printf("stackalloc Op%s %s to %s\n", v.Op, v, loc)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc %s to %s\n", v, name)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
locs = append(locs, LocalSlot{N: f.NewLocal(v.Pos, v.Type), Type: v.Type, Off: 0})
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stackalloc %s to %s\n", v, loc)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, spillvid := range spillLive[b.ID] {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, v := range b.Values {
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for i, a := range v.Args {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for i, a := range v.Args {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
if v.Op == ssaop.OpPhi {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
l = append(l, vid)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for vid, v := range s.values {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
bqueue = append(bqueue, b.b)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
bqueue = append(bqueue, e.B)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("stacklive %s %v\n", b, s.Live[b.ID])
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
f.RegAlloc = append(f.RegAlloc, nil)
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, b := range f.Blocks {
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
// Two values interfere if one is defined while the other is live.
Deeply nested control structures reduce readability; consider extracting to functions or using early returns
info maintainability deep-nesting
for _, id := range live.Contents() {
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
s.interfere[v.ID] = append(s.interfere[v.ID], id)
Multiple appends without pre-allocation; use make() with capacity when size is known
info performance append-without-prealloc
s.interfere[id] = append(s.interfere[id], v.ID)
Range over slice copies each element by value; use index or pointer receiver for large structs to avoid copies
info performance copy-large-struct
for vid, i := range s.interfere {
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf("v%d interferes with", vid)
Formatted output to console; prefer structured logging for consistency
info correctness fmt-printf
fmt.Printf(" v%d", x)
Unstructured output; use a structured logging library (e.g., slog, zap, zerolog, logrus)
info correctness fmt-println
fmt.Println()

Get this view in your editor

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