]> Cypherpunks repositories - gostls13.git/commitdiff
[dev.ssa] cmd/compile: remember names of values
authorKeith Randall <khr@golang.org>
Thu, 22 Oct 2015 21:22:38 +0000 (14:22 -0700)
committerKeith Randall <khr@golang.org>
Wed, 28 Oct 2015 17:00:31 +0000 (17:00 +0000)
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>
src/cmd/compile/internal/gc/closure.go
src/cmd/compile/internal/gc/ssa.go
src/cmd/compile/internal/ssa/config.go
src/cmd/compile/internal/ssa/deadcode.go
src/cmd/compile/internal/ssa/decompose.go
src/cmd/compile/internal/ssa/export_test.go
src/cmd/compile/internal/ssa/func.go
src/cmd/compile/internal/ssa/location.go
src/cmd/compile/internal/ssa/stackalloc.go
src/cmd/compile/internal/ssa/value.go

index e7bece8bd9eda5f73826083735af9164d0bd2bb4..8ebdd665538a0eb06c2dc19bc56113829b8bd636 100644 (file)
@@ -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) {
index f7100fefbeb1cad75d2ca85047a4065e61683046..c988465e9f746f460ff87db2489522cfb36bee95 100644 (file)
@@ -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
+}
index efb8b146a1f345a6140635dac58fc3f4176bb6db..cfba10bc244ae53bdf6695cd4f2c800618399baf 100644 (file)
@@ -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...) }
index be25eddb47f16835c35567e98992a176ae546f0a..3351589fda70cc00786b5fff5fc419ba7aed5a2e 100644 (file)
@@ -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?
 }
index 3ef20ef34fae2c68fa0534548b7a802209f6d1bf..2057d8ea5cb65fd1cfdcdef5409be4c5ad9ec46b 100644 (file)
@@ -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)
index 76a05f91d9734c55d37ce220db84b61c37a73980..d0ba7b1c09566d4ab2c3cf83f99345459eee55be 100644 (file)
@@ -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
 }
 
index 1ea7c2e2de90d1c298fcf1e08e820dd284ca6b0b..772fffce3318723dacd553eca2cea23c814c357f 100644 (file)
@@ -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.
index 9f445e5b5a5471ca0d76b82232f30950457bf027..0f9fb33eeb73e82e38b11f9a6231fc10ae569a61 100644 (file)
@@ -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 {
index 17d1f66ceaf33fb254d4b13fd235e7773a33d8ee..793162a797eef528aa73af7b68a5b15a49990c01 100644 (file)
@@ -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) {
index a5915da025f495d6a64009d65fed534b84334bf9..661a05989a215da99cc64d65f3159ef6220ffdc6 100644 (file)
@@ -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 {