mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
Change-Id: If2954bdfc551515403706b2cd0dde94e45936e08
GitHub-Last-Rev: d4cfc41a55
GitHub-Pull-Request: golang/go#28049
Reviewed-on: https://go-review.googlesource.com/c/140299
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
Run-TryBot: Brad Fitzpatrick <bradfitz@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
417 lines
11 KiB
Go
417 lines
11 KiB
Go
// Copyright 2015 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.
|
|
|
|
// TODO: live at start of block instead?
|
|
|
|
package ssa
|
|
|
|
import (
|
|
"cmd/compile/internal/types"
|
|
"cmd/internal/src"
|
|
"fmt"
|
|
)
|
|
|
|
type stackAllocState struct {
|
|
f *Func
|
|
|
|
// live is the output of stackalloc.
|
|
// live[b.id] = live values at the end of block b.
|
|
live [][]ID
|
|
|
|
// The following slices are reused across multiple users
|
|
// of stackAllocState.
|
|
values []stackValState
|
|
interfere [][]ID // interfere[v.id] = values that interfere with v.
|
|
names []LocalSlot
|
|
slots []int
|
|
used []bool
|
|
|
|
nArgSlot, // Number of Values sourced to arg slot
|
|
nNotNeed, // Number of Values not needing a stack slot
|
|
nNamedSlot, // Number of Values using a named stack slot
|
|
nReuse, // Number of values reusing a stack slot
|
|
nAuto, // Number of autos allocated for stack slots.
|
|
nSelfInterfere int32 // Number of self-interferences
|
|
}
|
|
|
|
func newStackAllocState(f *Func) *stackAllocState {
|
|
s := f.Cache.stackAllocState
|
|
if s == nil {
|
|
return new(stackAllocState)
|
|
}
|
|
if s.f != nil {
|
|
f.fe.Fatalf(src.NoXPos, "newStackAllocState called without previous free")
|
|
}
|
|
return s
|
|
}
|
|
|
|
func putStackAllocState(s *stackAllocState) {
|
|
for i := range s.values {
|
|
s.values[i] = stackValState{}
|
|
}
|
|
for i := range s.interfere {
|
|
s.interfere[i] = nil
|
|
}
|
|
for i := range s.names {
|
|
s.names[i] = LocalSlot{}
|
|
}
|
|
for i := range s.slots {
|
|
s.slots[i] = 0
|
|
}
|
|
for i := range s.used {
|
|
s.used[i] = false
|
|
}
|
|
s.f.Cache.stackAllocState = s
|
|
s.f = nil
|
|
s.live = nil
|
|
s.nArgSlot, s.nNotNeed, s.nNamedSlot, s.nReuse, s.nAuto, s.nSelfInterfere = 0, 0, 0, 0, 0, 0
|
|
}
|
|
|
|
type stackValState struct {
|
|
typ *types.Type
|
|
spill *Value
|
|
needSlot bool
|
|
isArg bool
|
|
}
|
|
|
|
// stackalloc allocates storage in the stack frame for
|
|
// all Values that did not get a register.
|
|
// Returns a map from block ID to the stack values live at the end of that block.
|
|
func stackalloc(f *Func, spillLive [][]ID) [][]ID {
|
|
if f.pass.debug > stackDebug {
|
|
fmt.Println("before stackalloc")
|
|
fmt.Println(f.String())
|
|
}
|
|
s := newStackAllocState(f)
|
|
s.init(f, spillLive)
|
|
defer putStackAllocState(s)
|
|
|
|
s.stackalloc()
|
|
if f.pass.stats > 0 {
|
|
f.LogStat("stack_alloc_stats",
|
|
s.nArgSlot, "arg_slots", s.nNotNeed, "slot_not_needed",
|
|
s.nNamedSlot, "named_slots", s.nAuto, "auto_slots",
|
|
s.nReuse, "reused_slots", s.nSelfInterfere, "self_interfering")
|
|
}
|
|
|
|
return s.live
|
|
}
|
|
|
|
func (s *stackAllocState) init(f *Func, spillLive [][]ID) {
|
|
s.f = f
|
|
|
|
// Initialize value information.
|
|
if n := f.NumValues(); cap(s.values) >= n {
|
|
s.values = s.values[:n]
|
|
} else {
|
|
s.values = make([]stackValState, n)
|
|
}
|
|
for _, b := range f.Blocks {
|
|
for _, v := range b.Values {
|
|
s.values[v.ID].typ = v.Type
|
|
s.values[v.ID].needSlot = !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && f.getHome(v.ID) == nil && !v.rematerializeable() && !v.OnWasmStack
|
|
s.values[v.ID].isArg = v.Op == OpArg
|
|
if f.pass.debug > stackDebug && s.values[v.ID].needSlot {
|
|
fmt.Printf("%s needs a stack slot\n", v)
|
|
}
|
|
if v.Op == OpStoreReg {
|
|
s.values[v.Args[0].ID].spill = v
|
|
}
|
|
}
|
|
}
|
|
|
|
// Compute liveness info for values needing a slot.
|
|
s.computeLive(spillLive)
|
|
|
|
// Build interference graph among values needing a slot.
|
|
s.buildInterferenceGraph()
|
|
}
|
|
|
|
func (s *stackAllocState) stackalloc() {
|
|
f := s.f
|
|
|
|
// Build map from values to their names, if any.
|
|
// A value may be associated with more than one name (e.g. after
|
|
// the assignment i=j). This step picks one name per value arbitrarily.
|
|
if n := f.NumValues(); cap(s.names) >= n {
|
|
s.names = s.names[:n]
|
|
} else {
|
|
s.names = make([]LocalSlot, n)
|
|
}
|
|
names := s.names
|
|
for _, name := range f.Names {
|
|
// Note: not "range f.NamedValues" above, because
|
|
// that would be nondeterministic.
|
|
for _, v := range f.NamedValues[name] {
|
|
names[v.ID] = name
|
|
}
|
|
}
|
|
|
|
// Allocate args to their assigned locations.
|
|
for _, v := range f.Entry.Values {
|
|
if v.Op != OpArg {
|
|
continue
|
|
}
|
|
loc := LocalSlot{N: v.Aux.(GCNode), Type: v.Type, Off: v.AuxInt}
|
|
if f.pass.debug > stackDebug {
|
|
fmt.Printf("stackalloc %s to %s\n", v, loc)
|
|
}
|
|
f.setHome(v, loc)
|
|
}
|
|
|
|
// For each type, we keep track of all the stack slots we
|
|
// have allocated for that type.
|
|
// TODO: share slots among equivalent types. We would need to
|
|
// only share among types with the same GC signature. See the
|
|
// type.Equal calls below for where this matters.
|
|
locations := map[*types.Type][]LocalSlot{}
|
|
|
|
// Each time we assign a stack slot to a value v, we remember
|
|
// the slot we used via an index into locations[v.Type].
|
|
slots := s.slots
|
|
if n := f.NumValues(); cap(slots) >= n {
|
|
slots = slots[:n]
|
|
} else {
|
|
slots = make([]int, n)
|
|
s.slots = slots
|
|
}
|
|
for i := range slots {
|
|
slots[i] = -1
|
|
}
|
|
|
|
// Pick a stack slot for each value needing one.
|
|
var used []bool
|
|
if n := f.NumValues(); cap(s.used) >= n {
|
|
used = s.used[:n]
|
|
} else {
|
|
used = make([]bool, n)
|
|
s.used = used
|
|
}
|
|
for _, b := range f.Blocks {
|
|
for _, v := range b.Values {
|
|
if !s.values[v.ID].needSlot {
|
|
s.nNotNeed++
|
|
continue
|
|
}
|
|
if v.Op == OpArg {
|
|
s.nArgSlot++
|
|
continue // already picked
|
|
}
|
|
|
|
// If this is a named value, try to use the name as
|
|
// the spill location.
|
|
var name LocalSlot
|
|
if v.Op == OpStoreReg {
|
|
name = names[v.Args[0].ID]
|
|
} else {
|
|
name = names[v.ID]
|
|
}
|
|
if name.N != nil && v.Type.Compare(name.Type) == types.CMPeq {
|
|
for _, id := range s.interfere[v.ID] {
|
|
h := f.getHome(id)
|
|
if h != nil && h.(LocalSlot).N == name.N && h.(LocalSlot).Off == name.Off {
|
|
// A variable can interfere with itself.
|
|
// It is rare, but it can happen.
|
|
s.nSelfInterfere++
|
|
goto noname
|
|
}
|
|
}
|
|
if f.pass.debug > stackDebug {
|
|
fmt.Printf("stackalloc %s to %s\n", v, name)
|
|
}
|
|
s.nNamedSlot++
|
|
f.setHome(v, name)
|
|
continue
|
|
}
|
|
|
|
noname:
|
|
// Set of stack slots we could reuse.
|
|
locs := locations[v.Type]
|
|
// Mark all positions in locs used by interfering values.
|
|
for i := 0; i < len(locs); i++ {
|
|
used[i] = false
|
|
}
|
|
for _, xid := range s.interfere[v.ID] {
|
|
slot := slots[xid]
|
|
if slot >= 0 {
|
|
used[slot] = true
|
|
}
|
|
}
|
|
// Find an unused stack slot.
|
|
var i int
|
|
for i = 0; i < len(locs); i++ {
|
|
if !used[i] {
|
|
s.nReuse++
|
|
break
|
|
}
|
|
}
|
|
// If there is no unused stack slot, allocate a new one.
|
|
if i == len(locs) {
|
|
s.nAuto++
|
|
locs = append(locs, LocalSlot{N: f.fe.Auto(v.Pos, v.Type), Type: v.Type, Off: 0})
|
|
locations[v.Type] = locs
|
|
}
|
|
// Use the stack variable at that index for v.
|
|
loc := locs[i]
|
|
if f.pass.debug > stackDebug {
|
|
fmt.Printf("stackalloc %s to %s\n", v, loc)
|
|
}
|
|
f.setHome(v, loc)
|
|
slots[v.ID] = i
|
|
}
|
|
}
|
|
}
|
|
|
|
// computeLive computes a map from block ID to a list of
|
|
// stack-slot-needing value IDs live at the end of that block.
|
|
// TODO: this could be quadratic if lots of variables are live across lots of
|
|
// basic blocks. Figure out a way to make this function (or, more precisely, the user
|
|
// of this function) require only linear size & time.
|
|
func (s *stackAllocState) computeLive(spillLive [][]ID) {
|
|
s.live = make([][]ID, s.f.NumBlocks())
|
|
var phis []*Value
|
|
live := s.f.newSparseSet(s.f.NumValues())
|
|
defer s.f.retSparseSet(live)
|
|
t := s.f.newSparseSet(s.f.NumValues())
|
|
defer s.f.retSparseSet(t)
|
|
|
|
// Instead of iterating over f.Blocks, iterate over their postordering.
|
|
// Liveness information flows backward, so starting at the end
|
|
// increases the probability that we will stabilize quickly.
|
|
po := s.f.postorder()
|
|
for {
|
|
changed := false
|
|
for _, b := range po {
|
|
// Start with known live values at the end of the block
|
|
live.clear()
|
|
live.addAll(s.live[b.ID])
|
|
|
|
// Propagate backwards to the start of the block
|
|
phis = phis[:0]
|
|
for i := len(b.Values) - 1; i >= 0; i-- {
|
|
v := b.Values[i]
|
|
live.remove(v.ID)
|
|
if v.Op == OpPhi {
|
|
// Save phi for later.
|
|
// Note: its args might need a stack slot even though
|
|
// the phi itself doesn't. So don't use needSlot.
|
|
if !v.Type.IsMemory() && !v.Type.IsVoid() {
|
|
phis = append(phis, v)
|
|
}
|
|
continue
|
|
}
|
|
for _, a := range v.Args {
|
|
if s.values[a.ID].needSlot {
|
|
live.add(a.ID)
|
|
}
|
|
}
|
|
}
|
|
|
|
// for each predecessor of b, expand its list of live-at-end values
|
|
// invariant: s contains the values live at the start of b (excluding phi inputs)
|
|
for i, e := range b.Preds {
|
|
p := e.b
|
|
t.clear()
|
|
t.addAll(s.live[p.ID])
|
|
t.addAll(live.contents())
|
|
t.addAll(spillLive[p.ID])
|
|
for _, v := range phis {
|
|
a := v.Args[i]
|
|
if s.values[a.ID].needSlot {
|
|
t.add(a.ID)
|
|
}
|
|
if spill := s.values[a.ID].spill; spill != nil {
|
|
//TODO: remove? Subsumed by SpillUse?
|
|
t.add(spill.ID)
|
|
}
|
|
}
|
|
if t.size() == len(s.live[p.ID]) {
|
|
continue
|
|
}
|
|
// grow p's live set
|
|
s.live[p.ID] = append(s.live[p.ID][:0], t.contents()...)
|
|
changed = true
|
|
}
|
|
}
|
|
|
|
if !changed {
|
|
break
|
|
}
|
|
}
|
|
if s.f.pass.debug > stackDebug {
|
|
for _, b := range s.f.Blocks {
|
|
fmt.Printf("stacklive %s %v\n", b, s.live[b.ID])
|
|
}
|
|
}
|
|
}
|
|
|
|
func (f *Func) getHome(vid ID) Location {
|
|
if int(vid) >= len(f.RegAlloc) {
|
|
return nil
|
|
}
|
|
return f.RegAlloc[vid]
|
|
}
|
|
|
|
func (f *Func) setHome(v *Value, loc Location) {
|
|
for v.ID >= ID(len(f.RegAlloc)) {
|
|
f.RegAlloc = append(f.RegAlloc, nil)
|
|
}
|
|
f.RegAlloc[v.ID] = loc
|
|
}
|
|
|
|
func (s *stackAllocState) buildInterferenceGraph() {
|
|
f := s.f
|
|
if n := f.NumValues(); cap(s.interfere) >= n {
|
|
s.interfere = s.interfere[:n]
|
|
} else {
|
|
s.interfere = make([][]ID, n)
|
|
}
|
|
live := f.newSparseSet(f.NumValues())
|
|
defer f.retSparseSet(live)
|
|
for _, b := range f.Blocks {
|
|
// Propagate liveness backwards to the start of the block.
|
|
// Two values interfere if one is defined while the other is live.
|
|
live.clear()
|
|
live.addAll(s.live[b.ID])
|
|
for i := len(b.Values) - 1; i >= 0; i-- {
|
|
v := b.Values[i]
|
|
if s.values[v.ID].needSlot {
|
|
live.remove(v.ID)
|
|
for _, id := range live.contents() {
|
|
// Note: args can have different types and still interfere
|
|
// (with each other or with other values). See issue 23522.
|
|
if s.values[v.ID].typ.Compare(s.values[id].typ) == types.CMPeq || v.Op == OpArg || s.values[id].isArg {
|
|
s.interfere[v.ID] = append(s.interfere[v.ID], id)
|
|
s.interfere[id] = append(s.interfere[id], v.ID)
|
|
}
|
|
}
|
|
}
|
|
for _, a := range v.Args {
|
|
if s.values[a.ID].needSlot {
|
|
live.add(a.ID)
|
|
}
|
|
}
|
|
if v.Op == OpArg && s.values[v.ID].needSlot {
|
|
// OpArg is an input argument which is pre-spilled.
|
|
// We add back v.ID here because we want this value
|
|
// to appear live even before this point. Being live
|
|
// all the way to the start of the entry block prevents other
|
|
// values from being allocated to the same slot and clobbering
|
|
// the input value before we have a chance to load it.
|
|
live.add(v.ID)
|
|
}
|
|
}
|
|
}
|
|
if f.pass.debug > stackDebug {
|
|
for vid, i := range s.interfere {
|
|
if len(i) > 0 {
|
|
fmt.Printf("v%d interferes with", vid)
|
|
for _, x := range i {
|
|
fmt.Printf(" v%d", x)
|
|
}
|
|
fmt.Println()
|
|
}
|
|
}
|
|
}
|
|
}
|