| // 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 | |
| } | |
| } |