mirror of
https://github.com/golang/go
synced 2024-10-05 11:41:22 -06:00
[dev.ssa] cmd/compile: remember names of values
For debugging, spill values to named variables instead of autotmp_ variables if possible. We do this by keeping a name -> value map for each function, keep it up-to-date during deadcode elim, and use it to override spill decisions in stackalloc. It might even make stack frames a bit smaller, as it makes it easy to identify a set of spills which are likely not to interfere. This just works for one-word variables for now. Strings/slices will be a separate CL. Change-Id: Ie89eba8cab16bcd41b311c479ec46dd7e64cdb67 Reviewed-on: https://go-review.googlesource.com/16336 Run-TryBot: Keith Randall <khr@golang.org> Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
parent
d43f2e37ed
commit
c24681ae2e
@ -604,6 +604,7 @@ func makepartialcall(fn *Node, t0 *Type, meth *Node) *Node {
|
||||
ptr.Ullman = 1
|
||||
ptr.Used = true
|
||||
ptr.Name.Curfn = xfunc
|
||||
ptr.Xoffset = 0
|
||||
xfunc.Func.Dcl = list(xfunc.Func.Dcl, ptr)
|
||||
var body *NodeList
|
||||
if Isptr[rcvrtype.Etype] || Isinter(rcvrtype) {
|
||||
|
@ -304,14 +304,14 @@ func (s *state) Unimplementedf(msg string, args ...interface{}) { s.config.Unimp
|
||||
|
||||
var (
|
||||
// dummy node for the memory variable
|
||||
memVar = Node{Op: ONAME, Sym: &Sym{Name: "mem"}}
|
||||
memVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "mem"}}
|
||||
|
||||
// dummy nodes for temporary variables
|
||||
ptrVar = Node{Op: ONAME, Sym: &Sym{Name: "ptr"}}
|
||||
capVar = Node{Op: ONAME, Sym: &Sym{Name: "cap"}}
|
||||
typVar = Node{Op: ONAME, Sym: &Sym{Name: "typ"}}
|
||||
idataVar = Node{Op: ONAME, Sym: &Sym{Name: "idata"}}
|
||||
okVar = Node{Op: ONAME, Sym: &Sym{Name: "ok"}}
|
||||
ptrVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ptr"}}
|
||||
capVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "cap"}}
|
||||
typVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "typ"}}
|
||||
idataVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "idata"}}
|
||||
okVar = Node{Op: ONAME, Class: Pxxx, Sym: &Sym{Name: "ok"}}
|
||||
)
|
||||
|
||||
// startBlock sets the current block we're generating code in to b.
|
||||
@ -2021,6 +2021,7 @@ func (s *state) assign(left *Node, right *ssa.Value, wb bool) {
|
||||
if left.Op == ONAME && canSSA(left) {
|
||||
// Update variable assignment.
|
||||
s.vars[left] = right
|
||||
s.addNamedValue(left, right)
|
||||
return
|
||||
}
|
||||
// not ssa-able. Treat as a store.
|
||||
@ -2245,13 +2246,14 @@ func (s *state) lookupSymbol(n *Node, sym interface{}) interface{} {
|
||||
// If bounded is true then this address does not require a nil check for its operand
|
||||
// even if that would otherwise be implied.
|
||||
func (s *state) addr(n *Node, bounded bool) *ssa.Value {
|
||||
t := Ptrto(n.Type)
|
||||
switch n.Op {
|
||||
case ONAME:
|
||||
switch n.Class {
|
||||
case PEXTERN:
|
||||
// global variable
|
||||
aux := &ssa.ExternSymbol{n.Type, n.Sym}
|
||||
v := s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sb)
|
||||
v := s.entryNewValue1A(ssa.OpAddr, t, aux, s.sb)
|
||||
// TODO: Make OpAddr use AuxInt as well as Aux.
|
||||
if n.Xoffset != 0 {
|
||||
v = s.entryNewValue1I(ssa.OpOffPtr, v.Type, n.Xoffset, v)
|
||||
@ -2277,12 +2279,12 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
|
||||
// getting lucky. We might need a real dependency edge
|
||||
// between vardef and addr ops.
|
||||
aux := &ssa.AutoSymbol{Typ: n.Type, Node: n}
|
||||
return s.newValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
|
||||
return s.newValue1A(ssa.OpAddr, t, aux, s.sp)
|
||||
case PPARAMOUT: // Same as PAUTO -- cannot generate LEA early.
|
||||
// ensure that we reuse symbols for out parameters so
|
||||
// that cse works on their addresses
|
||||
aux := s.lookupSymbol(n, &ssa.ArgSymbol{Typ: n.Type, Node: n})
|
||||
return s.newValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
|
||||
return s.newValue1A(ssa.OpAddr, t, aux, s.sp)
|
||||
case PAUTO | PHEAP, PPARAM | PHEAP, PPARAMOUT | PHEAP, PPARAMREF:
|
||||
return s.expr(n.Name.Heapaddr)
|
||||
default:
|
||||
@ -2296,18 +2298,18 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
|
||||
s.Unimplementedf("OINDREG of non-SP register %s in addr: %v", obj.Rconv(int(n.Reg)), n)
|
||||
return nil
|
||||
}
|
||||
return s.entryNewValue1I(ssa.OpOffPtr, Ptrto(n.Type), n.Xoffset, s.sp)
|
||||
return s.entryNewValue1I(ssa.OpOffPtr, t, n.Xoffset, s.sp)
|
||||
case OINDEX:
|
||||
if n.Left.Type.IsSlice() {
|
||||
a := s.expr(n.Left)
|
||||
i := s.expr(n.Right)
|
||||
i = s.extendIndex(i)
|
||||
len := s.newValue1(ssa.OpSliceLen, Types[TUINTPTR], a)
|
||||
len := s.newValue1(ssa.OpSliceLen, Types[TINT], a)
|
||||
if !n.Bounded {
|
||||
s.boundsCheck(i, len)
|
||||
}
|
||||
p := s.newValue1(ssa.OpSlicePtr, Ptrto(n.Left.Type.Type), a)
|
||||
return s.newValue2(ssa.OpPtrIndex, Ptrto(n.Left.Type.Type), p, i)
|
||||
p := s.newValue1(ssa.OpSlicePtr, t, a)
|
||||
return s.newValue2(ssa.OpPtrIndex, t, p, i)
|
||||
} else { // array
|
||||
a := s.addr(n.Left, bounded)
|
||||
i := s.expr(n.Right)
|
||||
@ -2326,15 +2328,15 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
|
||||
return p
|
||||
case ODOT:
|
||||
p := s.addr(n.Left, bounded)
|
||||
return s.newValue2(ssa.OpAddPtr, p.Type, p, s.constIntPtr(Types[TUINTPTR], n.Xoffset))
|
||||
return s.newValue2(ssa.OpAddPtr, t, p, s.constIntPtr(Types[TUINTPTR], n.Xoffset))
|
||||
case ODOTPTR:
|
||||
p := s.expr(n.Left)
|
||||
if !bounded {
|
||||
s.nilCheck(p)
|
||||
}
|
||||
return s.newValue2(ssa.OpAddPtr, p.Type, p, s.constIntPtr(Types[TUINTPTR], n.Xoffset))
|
||||
return s.newValue2(ssa.OpAddPtr, t, p, s.constIntPtr(Types[TUINTPTR], n.Xoffset))
|
||||
case OCLOSUREVAR:
|
||||
return s.newValue2(ssa.OpAddPtr, Ptrto(n.Type),
|
||||
return s.newValue2(ssa.OpAddPtr, t,
|
||||
s.entryNewValue0(ssa.OpGetClosurePtr, Ptrto(Types[TUINT8])),
|
||||
s.constIntPtr(Types[TUINTPTR], n.Xoffset))
|
||||
case OPARAM:
|
||||
@ -2347,11 +2349,10 @@ func (s *state) addr(n *Node, bounded bool) *ssa.Value {
|
||||
original_p := *p
|
||||
original_p.Xoffset = n.Xoffset
|
||||
aux := &ssa.ArgSymbol{Typ: n.Type, Node: &original_p}
|
||||
return s.entryNewValue1A(ssa.OpAddr, Ptrto(n.Type), aux, s.sp)
|
||||
return s.entryNewValue1A(ssa.OpAddr, t, aux, s.sp)
|
||||
case OCONVNOP:
|
||||
addr := s.addr(n.Left, bounded)
|
||||
to := Ptrto(n.Type)
|
||||
return s.newValue1(ssa.OpCopy, to, addr) // ensure that addr has the right type
|
||||
return s.newValue1(ssa.OpCopy, t, addr) // ensure that addr has the right type
|
||||
|
||||
default:
|
||||
s.Unimplementedf("unhandled addr %v", Oconv(int(n.Op), 0))
|
||||
@ -3155,6 +3156,7 @@ func (s *state) lookupVarIncoming(b *ssa.Block, t ssa.Type, name *Node) *ssa.Val
|
||||
// need a phi value
|
||||
v := b.NewValue0(s.peekLine(), ssa.OpPhi, t)
|
||||
v.AddArgs(vals...)
|
||||
s.addNamedValue(name, v)
|
||||
return v
|
||||
}
|
||||
}
|
||||
@ -3182,6 +3184,33 @@ func (s *state) lookupVarOutgoing(b *ssa.Block, t ssa.Type, name *Node) *ssa.Val
|
||||
|
||||
// TODO: the above mutually recursive functions can lead to very deep stacks. Fix that.
|
||||
|
||||
func (s *state) addNamedValue(n *Node, v *ssa.Value) {
|
||||
if n.Class == Pxxx {
|
||||
// Don't track our dummy nodes (&memVar etc.).
|
||||
return
|
||||
}
|
||||
if n.Sym == nil {
|
||||
// TODO: What the heck is this?
|
||||
return
|
||||
}
|
||||
if strings.HasPrefix(n.Sym.Name, "autotmp_") {
|
||||
// Don't track autotmp_ variables.
|
||||
return
|
||||
}
|
||||
if n.Class == PPARAM || n.Class == PPARAMOUT {
|
||||
// TODO: Remove this
|
||||
return
|
||||
}
|
||||
if n.Class == PAUTO && n.Xoffset != 0 {
|
||||
s.Fatalf("AUTO var with offset %s %d", n, n.Xoffset)
|
||||
}
|
||||
values, ok := s.f.NamedValues[n]
|
||||
if !ok {
|
||||
s.f.Names = append(s.f.Names, n)
|
||||
}
|
||||
s.f.NamedValues[n] = append(values, v)
|
||||
}
|
||||
|
||||
// an unresolved branch
|
||||
type branch struct {
|
||||
p *obj.Prog // branch instruction
|
||||
@ -4441,7 +4470,7 @@ func (*ssaExport) StringData(s string) interface{} {
|
||||
return &ssa.ExternSymbol{Typ: idealstring, Sym: data}
|
||||
}
|
||||
|
||||
func (e *ssaExport) Auto(t ssa.Type) fmt.Stringer {
|
||||
func (e *ssaExport) Auto(t ssa.Type) ssa.GCNode {
|
||||
n := temp(t.(*Type)) // Note: adds new auto to Curfn.Func.Dcl list
|
||||
e.mustImplement = true // This modifies the input to SSA, so we want to make sure we succeed from here!
|
||||
return n
|
||||
@ -4480,3 +4509,7 @@ func (e *ssaExport) Unimplementedf(msg string, args ...interface{}) {
|
||||
}
|
||||
e.unimplemented = true
|
||||
}
|
||||
|
||||
func (n *Node) Typ() ssa.Type {
|
||||
return n.Type
|
||||
}
|
||||
|
@ -4,10 +4,7 @@
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"cmd/internal/obj"
|
||||
"fmt"
|
||||
)
|
||||
import "cmd/internal/obj"
|
||||
|
||||
type Config struct {
|
||||
arch string // "amd64", etc.
|
||||
@ -63,7 +60,14 @@ type Frontend interface {
|
||||
|
||||
// Auto returns a Node for an auto variable of the given type.
|
||||
// The SSA compiler uses this function to allocate space for spills.
|
||||
Auto(Type) fmt.Stringer // returns *gc.Node
|
||||
Auto(Type) GCNode
|
||||
}
|
||||
|
||||
// interface used to hold *gc.Node. We'd use *gc.Node directly but
|
||||
// that would lead to an import cycle.
|
||||
type GCNode interface {
|
||||
Typ() Type
|
||||
String() string
|
||||
}
|
||||
|
||||
// NewConfig returns a new configuration object for the given architecture.
|
||||
@ -93,7 +97,7 @@ func (c *Config) Frontend() Frontend { return c.fe }
|
||||
// NewFunc returns a new, empty function object
|
||||
func (c *Config) NewFunc() *Func {
|
||||
// TODO(khr): should this function take name, type, etc. as arguments?
|
||||
return &Func{Config: c}
|
||||
return &Func{Config: c, NamedValues: map[GCNode][]*Value{}}
|
||||
}
|
||||
|
||||
func (c *Config) Logf(msg string, args ...interface{}) { c.fe.Logf(msg, args...) }
|
||||
|
@ -162,6 +162,25 @@ func deadcode(f *Func) {
|
||||
}
|
||||
f.Blocks = f.Blocks[:i]
|
||||
|
||||
// Remove dead entries from namedValues map.
|
||||
for name, values := range f.NamedValues {
|
||||
i := 0
|
||||
for _, v := range values {
|
||||
for v.Op == OpCopy {
|
||||
v = v.Args[0]
|
||||
}
|
||||
if live[v.ID] {
|
||||
values[i] = v
|
||||
i++
|
||||
}
|
||||
}
|
||||
f.NamedValues[name] = values[:i]
|
||||
tail := values[i:]
|
||||
for j := range tail {
|
||||
tail[j] = nil
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: renumber Blocks and Values densely?
|
||||
// TODO: save dead Values and Blocks for reuse? Or should we just let GC handle it?
|
||||
}
|
||||
|
@ -36,7 +36,7 @@ func decompose(f *Func) {
|
||||
func decomposeStringPhi(v *Value) {
|
||||
fe := v.Block.Func.Config.fe
|
||||
ptrType := fe.TypeBytePtr()
|
||||
lenType := fe.TypeUintptr()
|
||||
lenType := fe.TypeInt()
|
||||
|
||||
ptr := v.Block.NewValue0(v.Line, OpPhi, ptrType)
|
||||
len := v.Block.NewValue0(v.Line, OpPhi, lenType)
|
||||
@ -55,7 +55,7 @@ func decomposeStringPhi(v *Value) {
|
||||
func decomposeSlicePhi(v *Value) {
|
||||
fe := v.Block.Func.Config.fe
|
||||
ptrType := fe.TypeBytePtr()
|
||||
lenType := fe.TypeUintptr()
|
||||
lenType := fe.TypeInt()
|
||||
|
||||
ptr := v.Block.NewValue0(v.Line, OpPhi, ptrType)
|
||||
len := v.Block.NewValue0(v.Line, OpPhi, lenType)
|
||||
|
@ -6,7 +6,6 @@ package ssa
|
||||
|
||||
import (
|
||||
"cmd/internal/obj"
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
@ -29,7 +28,7 @@ type DummyFrontend struct {
|
||||
func (DummyFrontend) StringData(s string) interface{} {
|
||||
return nil
|
||||
}
|
||||
func (DummyFrontend) Auto(t Type) fmt.Stringer {
|
||||
func (DummyFrontend) Auto(t Type) GCNode {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -25,6 +25,13 @@ type Func struct {
|
||||
|
||||
// when register allocation is done, maps value ids to locations
|
||||
RegAlloc []Location
|
||||
|
||||
// map from *gc.Node to set of Values that represent that Node.
|
||||
// The Node must be an ONAME with PPARAM, PPARAMOUT, or PAUTO class.
|
||||
NamedValues map[GCNode][]*Value
|
||||
// Names is a copy of NamedValues.Keys. We keep a separate list
|
||||
// of keys to make iteration order deterministic.
|
||||
Names []GCNode
|
||||
}
|
||||
|
||||
// NumBlocks returns an integer larger than the id of any Block in the Func.
|
||||
|
@ -4,10 +4,6 @@
|
||||
|
||||
package ssa
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// A place that an ssa variable can reside.
|
||||
type Location interface {
|
||||
Name() string // name to use in assembly templates: %rax, 16(%rsp), ...
|
||||
@ -26,7 +22,7 @@ func (r *Register) Name() string {
|
||||
|
||||
// A LocalSlot is a location in the stack frame.
|
||||
type LocalSlot struct {
|
||||
N fmt.Stringer // a *gc.Node for an auto variable
|
||||
N GCNode // a *gc.Node for an auto variable
|
||||
}
|
||||
|
||||
func (s *LocalSlot) Name() string {
|
||||
|
@ -36,7 +36,8 @@ func stackalloc(f *Func) {
|
||||
case v.Op == OpStoreReg, v.isStackPhi():
|
||||
s.remove(v.ID)
|
||||
for _, id := range s.contents() {
|
||||
if v.Type == types[id] {
|
||||
if v.Type.Equal(types[id]) {
|
||||
// Only need interferences between equivalent types.
|
||||
interfere[v.ID] = append(interfere[v.ID], id)
|
||||
interfere[id] = append(interfere[id], v.ID)
|
||||
}
|
||||
@ -47,6 +48,18 @@ func stackalloc(f *Func) {
|
||||
}
|
||||
}
|
||||
|
||||
// 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.
|
||||
names := make([]GCNode, f.NumValues())
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
// Figure out which StoreReg ops are phi args. We don't pick slots for
|
||||
// phi args because a stack phi and its args must all use the same stack slot.
|
||||
phiArg := make([]bool, f.NumValues())
|
||||
@ -67,6 +80,7 @@ func stackalloc(f *Func) {
|
||||
|
||||
// Each time we assign a stack slot to a value v, we remember
|
||||
// the slot we used via an index into locations[v.Type].
|
||||
// TODO: share slots among equivalent types.
|
||||
slots := make([]int, f.NumValues())
|
||||
for i := f.NumValues() - 1; i >= 0; i-- {
|
||||
slots[i] = -1
|
||||
@ -82,6 +96,45 @@ func stackalloc(f *Func) {
|
||||
if phiArg[v.ID] {
|
||||
continue
|
||||
}
|
||||
|
||||
// If this is a named value, try to use the name as
|
||||
// the spill location.
|
||||
var name GCNode
|
||||
if v.Op == OpStoreReg {
|
||||
name = names[v.Args[0].ID]
|
||||
} else {
|
||||
name = names[v.ID]
|
||||
}
|
||||
if name != nil && v.Type.Equal(name.Typ()) {
|
||||
for _, id := range interfere[v.ID] {
|
||||
h := f.getHome(id)
|
||||
if h != nil && h.(*LocalSlot).N == name {
|
||||
// A variable can interfere with itself.
|
||||
// It is rare, but but it can happen.
|
||||
goto noname
|
||||
}
|
||||
}
|
||||
if v.Op == OpPhi {
|
||||
for _, a := range v.Args {
|
||||
for _, id := range interfere[a.ID] {
|
||||
h := f.getHome(id)
|
||||
if h != nil && h.(*LocalSlot).N == name {
|
||||
goto noname
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
loc := &LocalSlot{name}
|
||||
f.setHome(v, loc)
|
||||
if v.Op == OpPhi {
|
||||
for _, a := range v.Args {
|
||||
f.setHome(a, loc)
|
||||
}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
noname:
|
||||
// Set of stack slots we could reuse.
|
||||
locs := locations[v.Type]
|
||||
// Mark all positions in locs used by interfering values.
|
||||
@ -96,7 +149,7 @@ func stackalloc(f *Func) {
|
||||
}
|
||||
if v.Op == OpPhi {
|
||||
// Stack phi and args must get the same stack slot, so
|
||||
// anything they interfere with is something v the phi
|
||||
// anything the args interfere with is something the phi
|
||||
// interferes with.
|
||||
for _, a := range v.Args {
|
||||
for _, xid := range interfere[a.ID] {
|
||||
@ -209,11 +262,11 @@ func (f *Func) liveSpills() [][][]ID {
|
||||
return live
|
||||
}
|
||||
|
||||
func (f *Func) getHome(v *Value) Location {
|
||||
if int(v.ID) >= len(f.RegAlloc) {
|
||||
func (f *Func) getHome(vid ID) Location {
|
||||
if int(vid) >= len(f.RegAlloc) {
|
||||
return nil
|
||||
}
|
||||
return f.RegAlloc[v.ID]
|
||||
return f.RegAlloc[vid]
|
||||
}
|
||||
|
||||
func (f *Func) setHome(v *Value, loc Location) {
|
||||
|
@ -142,15 +142,15 @@ type ExternSymbol struct {
|
||||
// ArgSymbol is an aux value that encodes an argument or result
|
||||
// variable's constant offset from FP (FP = SP + framesize).
|
||||
type ArgSymbol struct {
|
||||
Typ Type // Go type
|
||||
Node fmt.Stringer // A *gc.Node referring to the argument/result variable.
|
||||
Typ Type // Go type
|
||||
Node GCNode // A *gc.Node referring to the argument/result variable.
|
||||
}
|
||||
|
||||
// AutoSymbol is an aux value that encodes a local variable's
|
||||
// constant offset from SP.
|
||||
type AutoSymbol struct {
|
||||
Typ Type // Go type
|
||||
Node fmt.Stringer // A *gc.Node referring to a local (auto) variable.
|
||||
Typ Type // Go type
|
||||
Node GCNode // A *gc.Node referring to a local (auto) variable.
|
||||
}
|
||||
|
||||
func (s *ExternSymbol) String() string {
|
||||
|
Loading…
Reference in New Issue
Block a user