summaryrefslogtreecommitdiffstats
path: root/src/cmd/compile/internal/ssa/deadstore.go
diff options
context:
space:
mode:
Diffstat (limited to 'src/cmd/compile/internal/ssa/deadstore.go')
-rw-r--r--src/cmd/compile/internal/ssa/deadstore.go348
1 files changed, 348 insertions, 0 deletions
diff --git a/src/cmd/compile/internal/ssa/deadstore.go b/src/cmd/compile/internal/ssa/deadstore.go
new file mode 100644
index 0000000..0664013
--- /dev/null
+++ b/src/cmd/compile/internal/ssa/deadstore.go
@@ -0,0 +1,348 @@
+// 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.
+
+package ssa
+
+import (
+ "cmd/compile/internal/types"
+ "cmd/internal/src"
+)
+
+// dse does dead-store elimination on the Function.
+// Dead stores are those which are unconditionally followed by
+// another store to the same location, with no intervening load.
+// This implementation only works within a basic block. TODO: use something more global.
+func dse(f *Func) {
+ var stores []*Value
+ loadUse := f.newSparseSet(f.NumValues())
+ defer f.retSparseSet(loadUse)
+ storeUse := f.newSparseSet(f.NumValues())
+ defer f.retSparseSet(storeUse)
+ shadowed := f.newSparseMap(f.NumValues())
+ defer f.retSparseMap(shadowed)
+ for _, b := range f.Blocks {
+ // Find all the stores in this block. Categorize their uses:
+ // loadUse contains stores which are used by a subsequent load.
+ // storeUse contains stores which are used by a subsequent store.
+ loadUse.clear()
+ storeUse.clear()
+ stores = stores[:0]
+ for _, v := range b.Values {
+ if v.Op == OpPhi {
+ // Ignore phis - they will always be first and can't be eliminated
+ continue
+ }
+ if v.Type.IsMemory() {
+ stores = append(stores, v)
+ for _, a := range v.Args {
+ if a.Block == b && a.Type.IsMemory() {
+ storeUse.add(a.ID)
+ if v.Op != OpStore && v.Op != OpZero && v.Op != OpVarDef && v.Op != OpVarKill {
+ // CALL, DUFFCOPY, etc. are both
+ // reads and writes.
+ loadUse.add(a.ID)
+ }
+ }
+ }
+ } else {
+ for _, a := range v.Args {
+ if a.Block == b && a.Type.IsMemory() {
+ loadUse.add(a.ID)
+ }
+ }
+ }
+ }
+ if len(stores) == 0 {
+ continue
+ }
+
+ // find last store in the block
+ var last *Value
+ for _, v := range stores {
+ if storeUse.contains(v.ID) {
+ continue
+ }
+ if last != nil {
+ b.Fatalf("two final stores - simultaneous live stores %s %s", last.LongString(), v.LongString())
+ }
+ last = v
+ }
+ if last == nil {
+ b.Fatalf("no last store found - cycle?")
+ }
+
+ // Walk backwards looking for dead stores. Keep track of shadowed addresses.
+ // A "shadowed address" is a pointer and a size describing a memory region that
+ // is known to be written. We keep track of shadowed addresses in the shadowed
+ // map, mapping the ID of the address to the size of the shadowed region.
+ // Since we're walking backwards, writes to a shadowed region are useless,
+ // as they will be immediately overwritten.
+ shadowed.clear()
+ v := last
+
+ walkloop:
+ if loadUse.contains(v.ID) {
+ // Someone might be reading this memory state.
+ // Clear all shadowed addresses.
+ shadowed.clear()
+ }
+ if v.Op == OpStore || v.Op == OpZero {
+ var sz int64
+ if v.Op == OpStore {
+ sz = v.Aux.(*types.Type).Size()
+ } else { // OpZero
+ sz = v.AuxInt
+ }
+ if shadowedSize := int64(shadowed.get(v.Args[0].ID)); shadowedSize != -1 && shadowedSize >= sz {
+ // Modify the store/zero into a copy of the memory state,
+ // effectively eliding the store operation.
+ if v.Op == OpStore {
+ // store addr value mem
+ v.SetArgs1(v.Args[2])
+ } else {
+ // zero addr mem
+ v.SetArgs1(v.Args[1])
+ }
+ v.Aux = nil
+ v.AuxInt = 0
+ v.Op = OpCopy
+ } else {
+ if sz > 0x7fffffff { // work around sparseMap's int32 value type
+ sz = 0x7fffffff
+ }
+ shadowed.set(v.Args[0].ID, int32(sz), src.NoXPos)
+ }
+ }
+ // walk to previous store
+ if v.Op == OpPhi {
+ // At start of block. Move on to next block.
+ // The memory phi, if it exists, is always
+ // the first logical store in the block.
+ // (Even if it isn't the first in the current b.Values order.)
+ continue
+ }
+ for _, a := range v.Args {
+ if a.Block == b && a.Type.IsMemory() {
+ v = a
+ goto walkloop
+ }
+ }
+ }
+}
+
+// elimDeadAutosGeneric deletes autos that are never accessed. To achieve this
+// we track the operations that the address of each auto reaches and if it only
+// reaches stores then we delete all the stores. The other operations will then
+// be eliminated by the dead code elimination pass.
+func elimDeadAutosGeneric(f *Func) {
+ addr := make(map[*Value]GCNode) // values that the address of the auto reaches
+ elim := make(map[*Value]GCNode) // values that could be eliminated if the auto is
+ used := make(map[GCNode]bool) // used autos that must be kept
+
+ // visit the value and report whether any of the maps are updated
+ visit := func(v *Value) (changed bool) {
+ args := v.Args
+ switch v.Op {
+ case OpAddr, OpLocalAddr:
+ // Propagate the address if it points to an auto.
+ n, ok := v.Aux.(GCNode)
+ if !ok || n.StorageClass() != ClassAuto {
+ return
+ }
+ if addr[v] == nil {
+ addr[v] = n
+ changed = true
+ }
+ return
+ case OpVarDef, OpVarKill:
+ // v should be eliminated if we eliminate the auto.
+ n, ok := v.Aux.(GCNode)
+ if !ok || n.StorageClass() != ClassAuto {
+ return
+ }
+ if elim[v] == nil {
+ elim[v] = n
+ changed = true
+ }
+ return
+ case OpVarLive:
+ // Don't delete the auto if it needs to be kept alive.
+
+ // We depend on this check to keep the autotmp stack slots
+ // for open-coded defers from being removed (since they
+ // may not be used by the inline code, but will be used by
+ // panic processing).
+ n, ok := v.Aux.(GCNode)
+ if !ok || n.StorageClass() != ClassAuto {
+ return
+ }
+ if !used[n] {
+ used[n] = true
+ changed = true
+ }
+ return
+ case OpStore, OpMove, OpZero:
+ // v should be eliminated if we eliminate the auto.
+ n, ok := addr[args[0]]
+ if ok && elim[v] == nil {
+ elim[v] = n
+ changed = true
+ }
+ // Other args might hold pointers to autos.
+ args = args[1:]
+ }
+
+ // The code below assumes that we have handled all the ops
+ // with sym effects already. Sanity check that here.
+ // Ignore Args since they can't be autos.
+ if v.Op.SymEffect() != SymNone && v.Op != OpArg {
+ panic("unhandled op with sym effect")
+ }
+
+ if v.Uses == 0 && v.Op != OpNilCheck || len(args) == 0 {
+ // Nil check has no use, but we need to keep it.
+ return
+ }
+
+ // If the address of the auto reaches a memory or control
+ // operation not covered above then we probably need to keep it.
+ // We also need to keep autos if they reach Phis (issue #26153).
+ if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil {
+ for _, a := range args {
+ if n, ok := addr[a]; ok {
+ if !used[n] {
+ used[n] = true
+ changed = true
+ }
+ }
+ }
+ return
+ }
+
+ // Propagate any auto addresses through v.
+ node := GCNode(nil)
+ for _, a := range args {
+ if n, ok := addr[a]; ok && !used[n] {
+ if node == nil {
+ node = n
+ } else if node != n {
+ // Most of the time we only see one pointer
+ // reaching an op, but some ops can take
+ // multiple pointers (e.g. NeqPtr, Phi etc.).
+ // This is rare, so just propagate the first
+ // value to keep things simple.
+ used[n] = true
+ changed = true
+ }
+ }
+ }
+ if node == nil {
+ return
+ }
+ if addr[v] == nil {
+ // The address of an auto reaches this op.
+ addr[v] = node
+ changed = true
+ return
+ }
+ if addr[v] != node {
+ // This doesn't happen in practice, but catch it just in case.
+ used[node] = true
+ changed = true
+ }
+ return
+ }
+
+ iterations := 0
+ for {
+ if iterations == 4 {
+ // give up
+ return
+ }
+ iterations++
+ changed := false
+ for _, b := range f.Blocks {
+ for _, v := range b.Values {
+ changed = visit(v) || changed
+ }
+ // keep the auto if its address reaches a control value
+ for _, c := range b.ControlValues() {
+ if n, ok := addr[c]; ok && !used[n] {
+ used[n] = true
+ changed = true
+ }
+ }
+ }
+ if !changed {
+ break
+ }
+ }
+
+ // Eliminate stores to unread autos.
+ for v, n := range elim {
+ if used[n] {
+ continue
+ }
+ // replace with OpCopy
+ v.SetArgs1(v.MemoryArg())
+ v.Aux = nil
+ v.AuxInt = 0
+ v.Op = OpCopy
+ }
+}
+
+// elimUnreadAutos deletes stores (and associated bookkeeping ops VarDef and VarKill)
+// to autos that are never read from.
+func elimUnreadAutos(f *Func) {
+ // Loop over all ops that affect autos taking note of which
+ // autos we need and also stores that we might be able to
+ // eliminate.
+ seen := make(map[GCNode]bool)
+ var stores []*Value
+ for _, b := range f.Blocks {
+ for _, v := range b.Values {
+ n, ok := v.Aux.(GCNode)
+ if !ok {
+ continue
+ }
+ if n.StorageClass() != ClassAuto {
+ continue
+ }
+
+ effect := v.Op.SymEffect()
+ switch effect {
+ case SymNone, SymWrite:
+ // If we haven't seen the auto yet
+ // then this might be a store we can
+ // eliminate.
+ if !seen[n] {
+ stores = append(stores, v)
+ }
+ default:
+ // Assume the auto is needed (loaded,
+ // has its address taken, etc.).
+ // Note we have to check the uses
+ // because dead loads haven't been
+ // eliminated yet.
+ if v.Uses > 0 {
+ seen[n] = true
+ }
+ }
+ }
+ }
+
+ // Eliminate stores to unread autos.
+ for _, store := range stores {
+ n, _ := store.Aux.(GCNode)
+ if seen[n] {
+ continue
+ }
+
+ // replace store with OpCopy
+ store.SetArgs1(store.MemoryArg())
+ store.Aux = nil
+ store.AuxInt = 0
+ store.Op = OpCopy
+ }
+}