1// Copyright 2015 The Go Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style
3// license that can be found in the LICENSE file.
4
5package ssa
6
7import (
8	"cmd/compile/internal/types"
9	"cmd/internal/src"
10)
11
12// dse does dead-store elimination on the Function.
13// Dead stores are those which are unconditionally followed by
14// another store to the same location, with no intervening load.
15// This implementation only works within a basic block. TODO: use something more global.
16func dse(f *Func) {
17	var stores []*Value
18	loadUse := f.newSparseSet(f.NumValues())
19	defer f.retSparseSet(loadUse)
20	storeUse := f.newSparseSet(f.NumValues())
21	defer f.retSparseSet(storeUse)
22	shadowed := f.newSparseMap(f.NumValues())
23	defer f.retSparseMap(shadowed)
24	for _, b := range f.Blocks {
25		// Find all the stores in this block. Categorize their uses:
26		//  loadUse contains stores which are used by a subsequent load.
27		//  storeUse contains stores which are used by a subsequent store.
28		loadUse.clear()
29		storeUse.clear()
30		stores = stores[:0]
31		for _, v := range b.Values {
32			if v.Op == OpPhi {
33				// Ignore phis - they will always be first and can't be eliminated
34				continue
35			}
36			if v.Type.IsMemory() {
37				stores = append(stores, v)
38				for _, a := range v.Args {
39					if a.Block == b && a.Type.IsMemory() {
40						storeUse.add(a.ID)
41						if v.Op != OpStore && v.Op != OpZero && v.Op != OpVarDef && v.Op != OpVarKill {
42							// CALL, DUFFCOPY, etc. are both
43							// reads and writes.
44							loadUse.add(a.ID)
45						}
46					}
47				}
48			} else {
49				for _, a := range v.Args {
50					if a.Block == b && a.Type.IsMemory() {
51						loadUse.add(a.ID)
52					}
53				}
54			}
55		}
56		if len(stores) == 0 {
57			continue
58		}
59
60		// find last store in the block
61		var last *Value
62		for _, v := range stores {
63			if storeUse.contains(v.ID) {
64				continue
65			}
66			if last != nil {
67				b.Fatalf("two final stores - simultaneous live stores %s %s", last.LongString(), v.LongString())
68			}
69			last = v
70		}
71		if last == nil {
72			b.Fatalf("no last store found - cycle?")
73		}
74
75		// Walk backwards looking for dead stores. Keep track of shadowed addresses.
76		// An "address" is an SSA Value which encodes both the address and size of
77		// the write. This code will not remove dead stores to the same address
78		// of different types.
79		shadowed.clear()
80		v := last
81
82	walkloop:
83		if loadUse.contains(v.ID) {
84			// Someone might be reading this memory state.
85			// Clear all shadowed addresses.
86			shadowed.clear()
87		}
88		if v.Op == OpStore || v.Op == OpZero {
89			var sz int64
90			if v.Op == OpStore {
91				sz = v.Aux.(*types.Type).Size()
92			} else { // OpZero
93				sz = v.AuxInt
94			}
95			if shadowedSize := int64(shadowed.get(v.Args[0].ID)); shadowedSize != -1 && shadowedSize >= sz {
96				// Modify store into a copy
97				if v.Op == OpStore {
98					// store addr value mem
99					v.SetArgs1(v.Args[2])
100				} else {
101					// zero addr mem
102					typesz := v.Args[0].Type.Elem().Size()
103					if sz != typesz {
104						f.Fatalf("mismatched zero/store sizes: %d and %d [%s]",
105							sz, typesz, v.LongString())
106					}
107					v.SetArgs1(v.Args[1])
108				}
109				v.Aux = nil
110				v.AuxInt = 0
111				v.Op = OpCopy
112			} else {
113				if sz > 0x7fffffff { // work around sparseMap's int32 value type
114					sz = 0x7fffffff
115				}
116				shadowed.set(v.Args[0].ID, int32(sz), src.NoXPos)
117			}
118		}
119		// walk to previous store
120		if v.Op == OpPhi {
121			// At start of block.  Move on to next block.
122			// The memory phi, if it exists, is always
123			// the first logical store in the block.
124			// (Even if it isn't the first in the current b.Values order.)
125			continue
126		}
127		for _, a := range v.Args {
128			if a.Block == b && a.Type.IsMemory() {
129				v = a
130				goto walkloop
131			}
132		}
133	}
134}
135
136// elimDeadAutosGeneric deletes autos that are never accessed. To achieve this
137// we track the operations that the address of each auto reaches and if it only
138// reaches stores then we delete all the stores. The other operations will then
139// be eliminated by the dead code elimination pass.
140func elimDeadAutosGeneric(f *Func) {
141	addr := make(map[*Value]GCNode) // values that the address of the auto reaches
142	elim := make(map[*Value]GCNode) // values that could be eliminated if the auto is
143	used := make(map[GCNode]bool)   // used autos that must be kept
144
145	// visit the value and report whether any of the maps are updated
146	visit := func(v *Value) (changed bool) {
147		args := v.Args
148		switch v.Op {
149		case OpAddr, OpLocalAddr:
150			// Propagate the address if it points to an auto.
151			n, ok := v.Aux.(GCNode)
152			if !ok || n.StorageClass() != ClassAuto {
153				return
154			}
155			if addr[v] == nil {
156				addr[v] = n
157				changed = true
158			}
159			return
160		case OpVarDef, OpVarKill:
161			// v should be eliminated if we eliminate the auto.
162			n, ok := v.Aux.(GCNode)
163			if !ok || n.StorageClass() != ClassAuto {
164				return
165			}
166			if elim[v] == nil {
167				elim[v] = n
168				changed = true
169			}
170			return
171		case OpVarLive:
172			// Don't delete the auto if it needs to be kept alive.
173
174			// We depend on this check to keep the autotmp stack slots
175			// for open-coded defers from being removed (since they
176			// may not be used by the inline code, but will be used by
177			// panic processing).
178			n, ok := v.Aux.(GCNode)
179			if !ok || n.StorageClass() != ClassAuto {
180				return
181			}
182			if !used[n] {
183				used[n] = true
184				changed = true
185			}
186			return
187		case OpStore, OpMove, OpZero:
188			// v should be eliminated if we eliminate the auto.
189			n, ok := addr[args[0]]
190			if ok && elim[v] == nil {
191				elim[v] = n
192				changed = true
193			}
194			// Other args might hold pointers to autos.
195			args = args[1:]
196		}
197
198		// The code below assumes that we have handled all the ops
199		// with sym effects already. Sanity check that here.
200		// Ignore Args since they can't be autos.
201		if v.Op.SymEffect() != SymNone && v.Op != OpArg {
202			panic("unhandled op with sym effect")
203		}
204
205		if v.Uses == 0 && v.Op != OpNilCheck || len(args) == 0 {
206			// Nil check has no use, but we need to keep it.
207			return
208		}
209
210		// If the address of the auto reaches a memory or control
211		// operation not covered above then we probably need to keep it.
212		// We also need to keep autos if they reach Phis (issue #26153).
213		if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil {
214			for _, a := range args {
215				if n, ok := addr[a]; ok {
216					if !used[n] {
217						used[n] = true
218						changed = true
219					}
220				}
221			}
222			return
223		}
224
225		// Propagate any auto addresses through v.
226		node := GCNode(nil)
227		for _, a := range args {
228			if n, ok := addr[a]; ok && !used[n] {
229				if node == nil {
230					node = n
231				} else if node != n {
232					// Most of the time we only see one pointer
233					// reaching an op, but some ops can take
234					// multiple pointers (e.g. NeqPtr, Phi etc.).
235					// This is rare, so just propagate the first
236					// value to keep things simple.
237					used[n] = true
238					changed = true
239				}
240			}
241		}
242		if node == nil {
243			return
244		}
245		if addr[v] == nil {
246			// The address of an auto reaches this op.
247			addr[v] = node
248			changed = true
249			return
250		}
251		if addr[v] != node {
252			// This doesn't happen in practice, but catch it just in case.
253			used[node] = true
254			changed = true
255		}
256		return
257	}
258
259	iterations := 0
260	for {
261		if iterations == 4 {
262			// give up
263			return
264		}
265		iterations++
266		changed := false
267		for _, b := range f.Blocks {
268			for _, v := range b.Values {
269				changed = visit(v) || changed
270			}
271			// keep the auto if its address reaches a control value
272			for _, c := range b.ControlValues() {
273				if n, ok := addr[c]; ok && !used[n] {
274					used[n] = true
275					changed = true
276				}
277			}
278		}
279		if !changed {
280			break
281		}
282	}
283
284	// Eliminate stores to unread autos.
285	for v, n := range elim {
286		if used[n] {
287			continue
288		}
289		// replace with OpCopy
290		v.SetArgs1(v.MemoryArg())
291		v.Aux = nil
292		v.AuxInt = 0
293		v.Op = OpCopy
294	}
295}
296
297// elimUnreadAutos deletes stores (and associated bookkeeping ops VarDef and VarKill)
298// to autos that are never read from.
299func elimUnreadAutos(f *Func) {
300	// Loop over all ops that affect autos taking note of which
301	// autos we need and also stores that we might be able to
302	// eliminate.
303	seen := make(map[GCNode]bool)
304	var stores []*Value
305	for _, b := range f.Blocks {
306		for _, v := range b.Values {
307			n, ok := v.Aux.(GCNode)
308			if !ok {
309				continue
310			}
311			if n.StorageClass() != ClassAuto {
312				continue
313			}
314
315			effect := v.Op.SymEffect()
316			switch effect {
317			case SymNone, SymWrite:
318				// If we haven't seen the auto yet
319				// then this might be a store we can
320				// eliminate.
321				if !seen[n] {
322					stores = append(stores, v)
323				}
324			default:
325				// Assume the auto is needed (loaded,
326				// has its address taken, etc.).
327				// Note we have to check the uses
328				// because dead loads haven't been
329				// eliminated yet.
330				if v.Uses > 0 {
331					seen[n] = true
332				}
333			}
334		}
335	}
336
337	// Eliminate stores to unread autos.
338	for _, store := range stores {
339		n, _ := store.Aux.(GCNode)
340		if seen[n] {
341			continue
342		}
343
344		// replace store with OpCopy
345		store.SetArgs1(store.MemoryArg())
346		store.Aux = nil
347		store.AuxInt = 0
348		store.Op = OpCopy
349	}
350}
351