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/ir" 9 "cmd/compile/internal/types" 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 // localAddrs maps from a local variable (the Aux field of a LocalAddr value) to an instance of a LocalAddr value for that variable in the current block. 25 localAddrs := map[any]*Value{} 26 for _, b := range f.Blocks { 27 // Find all the stores in this block. Categorize their uses: 28 // loadUse contains stores which are used by a subsequent load. 29 // storeUse contains stores which are used by a subsequent store. 30 loadUse.clear() 31 storeUse.clear() 32 // TODO(deparker): use the 'clear' builtin once compiler bootstrap minimum version is raised to 1.21. 33 for k := range localAddrs { 34 delete(localAddrs, k) 35 } 36 stores = stores[:0] 37 for _, v := range b.Values { 38 if v.Op == OpPhi { 39 // Ignore phis - they will always be first and can't be eliminated 40 continue 41 } 42 if v.Type.IsMemory() { 43 stores = append(stores, v) 44 for _, a := range v.Args { 45 if a.Block == b && a.Type.IsMemory() { 46 storeUse.add(a.ID) 47 if v.Op != OpStore && v.Op != OpZero && v.Op != OpVarDef { 48 // CALL, DUFFCOPY, etc. are both 49 // reads and writes. 50 loadUse.add(a.ID) 51 } 52 } 53 } 54 } else { 55 if v.Op == OpLocalAddr { 56 if _, ok := localAddrs[v.Aux]; !ok { 57 localAddrs[v.Aux] = v 58 } else { 59 continue 60 } 61 } 62 for _, a := range v.Args { 63 if a.Block == b && a.Type.IsMemory() { 64 loadUse.add(a.ID) 65 } 66 } 67 } 68 } 69 if len(stores) == 0 { 70 continue 71 } 72 73 // find last store in the block 74 var last *Value 75 for _, v := range stores { 76 if storeUse.contains(v.ID) { 77 continue 78 } 79 if last != nil { 80 b.Fatalf("two final stores - simultaneous live stores %s %s", last.LongString(), v.LongString()) 81 } 82 last = v 83 } 84 if last == nil { 85 b.Fatalf("no last store found - cycle?") 86 } 87 88 // Walk backwards looking for dead stores. Keep track of shadowed addresses. 89 // A "shadowed address" is a pointer, offset, and size describing a memory region that 90 // is known to be written. We keep track of shadowed addresses in the shadowed map, 91 // mapping the ID of the address to a shadowRange where future writes will happen. 92 // Since we're walking backwards, writes to a shadowed region are useless, 93 // as they will be immediately overwritten. 94 shadowed.clear() 95 v := last 96 97 walkloop: 98 if loadUse.contains(v.ID) { 99 // Someone might be reading this memory state. 100 // Clear all shadowed addresses. 101 shadowed.clear() 102 } 103 if v.Op == OpStore || v.Op == OpZero { 104 ptr := v.Args[0] 105 var off int64 106 for ptr.Op == OpOffPtr { // Walk to base pointer 107 off += ptr.AuxInt 108 ptr = ptr.Args[0] 109 } 110 var sz int64 111 if v.Op == OpStore { 112 sz = v.Aux.(*types.Type).Size() 113 } else { // OpZero 114 sz = v.AuxInt 115 } 116 if ptr.Op == OpLocalAddr { 117 if la, ok := localAddrs[ptr.Aux]; ok { 118 ptr = la 119 } 120 } 121 sr := shadowRange(shadowed.get(ptr.ID)) 122 if sr.contains(off, off+sz) { 123 // Modify the store/zero into a copy of the memory state, 124 // effectively eliding the store operation. 125 if v.Op == OpStore { 126 // store addr value mem 127 v.SetArgs1(v.Args[2]) 128 } else { 129 // zero addr mem 130 v.SetArgs1(v.Args[1]) 131 } 132 v.Aux = nil 133 v.AuxInt = 0 134 v.Op = OpCopy 135 } else { 136 // Extend shadowed region. 137 shadowed.set(ptr.ID, int32(sr.merge(off, off+sz))) 138 } 139 } 140 // walk to previous store 141 if v.Op == OpPhi { 142 // At start of block. Move on to next block. 143 // The memory phi, if it exists, is always 144 // the first logical store in the block. 145 // (Even if it isn't the first in the current b.Values order.) 146 continue 147 } 148 for _, a := range v.Args { 149 if a.Block == b && a.Type.IsMemory() { 150 v = a 151 goto walkloop 152 } 153 } 154 } 155} 156 157// A shadowRange encodes a set of byte offsets [lo():hi()] from 158// a given pointer that will be written to later in the block. 159// A zero shadowRange encodes an empty shadowed range (and so 160// does a -1 shadowRange, which is what sparsemap.get returns 161// on a failed lookup). 162type shadowRange int32 163 164func (sr shadowRange) lo() int64 { 165 return int64(sr & 0xffff) 166} 167 168func (sr shadowRange) hi() int64 { 169 return int64((sr >> 16) & 0xffff) 170} 171 172// contains reports whether [lo:hi] is completely within sr. 173func (sr shadowRange) contains(lo, hi int64) bool { 174 return lo >= sr.lo() && hi <= sr.hi() 175} 176 177// merge returns the union of sr and [lo:hi]. 178// merge is allowed to return something smaller than the union. 179func (sr shadowRange) merge(lo, hi int64) shadowRange { 180 if lo < 0 || hi > 0xffff { 181 // Ignore offsets that are too large or small. 182 return sr 183 } 184 if sr.lo() == sr.hi() { 185 // Old range is empty - use new one. 186 return shadowRange(lo + hi<<16) 187 } 188 if hi < sr.lo() || lo > sr.hi() { 189 // The two regions don't overlap or abut, so we would 190 // have to keep track of multiple disjoint ranges. 191 // Because we can only keep one, keep the larger one. 192 if sr.hi()-sr.lo() >= hi-lo { 193 return sr 194 } 195 return shadowRange(lo + hi<<16) 196 } 197 // Regions overlap or abut - compute the union. 198 return shadowRange(min(lo, sr.lo()) + max(hi, sr.hi())<<16) 199} 200 201// elimDeadAutosGeneric deletes autos that are never accessed. To achieve this 202// we track the operations that the address of each auto reaches and if it only 203// reaches stores then we delete all the stores. The other operations will then 204// be eliminated by the dead code elimination pass. 205func elimDeadAutosGeneric(f *Func) { 206 addr := make(map[*Value]*ir.Name) // values that the address of the auto reaches 207 elim := make(map[*Value]*ir.Name) // values that could be eliminated if the auto is 208 var used ir.NameSet // used autos that must be kept 209 210 // visit the value and report whether any of the maps are updated 211 visit := func(v *Value) (changed bool) { 212 args := v.Args 213 switch v.Op { 214 case OpAddr, OpLocalAddr: 215 // Propagate the address if it points to an auto. 216 n, ok := v.Aux.(*ir.Name) 217 if !ok || n.Class != ir.PAUTO { 218 return 219 } 220 if addr[v] == nil { 221 addr[v] = n 222 changed = true 223 } 224 return 225 case OpVarDef: 226 // v should be eliminated if we eliminate the auto. 227 n, ok := v.Aux.(*ir.Name) 228 if !ok || n.Class != ir.PAUTO { 229 return 230 } 231 if elim[v] == nil { 232 elim[v] = n 233 changed = true 234 } 235 return 236 case OpVarLive: 237 // Don't delete the auto if it needs to be kept alive. 238 239 // We depend on this check to keep the autotmp stack slots 240 // for open-coded defers from being removed (since they 241 // may not be used by the inline code, but will be used by 242 // panic processing). 243 n, ok := v.Aux.(*ir.Name) 244 if !ok || n.Class != ir.PAUTO { 245 return 246 } 247 if !used.Has(n) { 248 used.Add(n) 249 changed = true 250 } 251 return 252 case OpStore, OpMove, OpZero: 253 // v should be eliminated if we eliminate the auto. 254 n, ok := addr[args[0]] 255 if ok && elim[v] == nil { 256 elim[v] = n 257 changed = true 258 } 259 // Other args might hold pointers to autos. 260 args = args[1:] 261 } 262 263 // The code below assumes that we have handled all the ops 264 // with sym effects already. Sanity check that here. 265 // Ignore Args since they can't be autos. 266 if v.Op.SymEffect() != SymNone && v.Op != OpArg { 267 panic("unhandled op with sym effect") 268 } 269 270 if v.Uses == 0 && v.Op != OpNilCheck && !v.Op.IsCall() && !v.Op.HasSideEffects() || len(args) == 0 { 271 // We need to keep nil checks even if they have no use. 272 // Also keep calls and values that have side effects. 273 return 274 } 275 276 // If the address of the auto reaches a memory or control 277 // operation not covered above then we probably need to keep it. 278 // We also need to keep autos if they reach Phis (issue #26153). 279 if v.Type.IsMemory() || v.Type.IsFlags() || v.Op == OpPhi || v.MemoryArg() != nil { 280 for _, a := range args { 281 if n, ok := addr[a]; ok { 282 if !used.Has(n) { 283 used.Add(n) 284 changed = true 285 } 286 } 287 } 288 return 289 } 290 291 // Propagate any auto addresses through v. 292 var node *ir.Name 293 for _, a := range args { 294 if n, ok := addr[a]; ok && !used.Has(n) { 295 if node == nil { 296 node = n 297 } else if node != n { 298 // Most of the time we only see one pointer 299 // reaching an op, but some ops can take 300 // multiple pointers (e.g. NeqPtr, Phi etc.). 301 // This is rare, so just propagate the first 302 // value to keep things simple. 303 used.Add(n) 304 changed = true 305 } 306 } 307 } 308 if node == nil { 309 return 310 } 311 if addr[v] == nil { 312 // The address of an auto reaches this op. 313 addr[v] = node 314 changed = true 315 return 316 } 317 if addr[v] != node { 318 // This doesn't happen in practice, but catch it just in case. 319 used.Add(node) 320 changed = true 321 } 322 return 323 } 324 325 iterations := 0 326 for { 327 if iterations == 4 { 328 // give up 329 return 330 } 331 iterations++ 332 changed := false 333 for _, b := range f.Blocks { 334 for _, v := range b.Values { 335 changed = visit(v) || changed 336 } 337 // keep the auto if its address reaches a control value 338 for _, c := range b.ControlValues() { 339 if n, ok := addr[c]; ok && !used.Has(n) { 340 used.Add(n) 341 changed = true 342 } 343 } 344 } 345 if !changed { 346 break 347 } 348 } 349 350 // Eliminate stores to unread autos. 351 for v, n := range elim { 352 if used.Has(n) { 353 continue 354 } 355 // replace with OpCopy 356 v.SetArgs1(v.MemoryArg()) 357 v.Aux = nil 358 v.AuxInt = 0 359 v.Op = OpCopy 360 } 361} 362 363// elimUnreadAutos deletes stores (and associated bookkeeping ops VarDef and VarKill) 364// to autos that are never read from. 365func elimUnreadAutos(f *Func) { 366 // Loop over all ops that affect autos taking note of which 367 // autos we need and also stores that we might be able to 368 // eliminate. 369 var seen ir.NameSet 370 var stores []*Value 371 for _, b := range f.Blocks { 372 for _, v := range b.Values { 373 n, ok := v.Aux.(*ir.Name) 374 if !ok { 375 continue 376 } 377 if n.Class != ir.PAUTO { 378 continue 379 } 380 381 effect := v.Op.SymEffect() 382 switch effect { 383 case SymNone, SymWrite: 384 // If we haven't seen the auto yet 385 // then this might be a store we can 386 // eliminate. 387 if !seen.Has(n) { 388 stores = append(stores, v) 389 } 390 default: 391 // Assume the auto is needed (loaded, 392 // has its address taken, etc.). 393 // Note we have to check the uses 394 // because dead loads haven't been 395 // eliminated yet. 396 if v.Uses > 0 { 397 seen.Add(n) 398 } 399 } 400 } 401 } 402 403 // Eliminate stores to unread autos. 404 for _, store := range stores { 405 n, _ := store.Aux.(*ir.Name) 406 if seen.Has(n) { 407 continue 408 } 409 410 // replace store with OpCopy 411 store.SetArgs1(store.MemoryArg()) 412 store.Aux = nil 413 store.AuxInt = 0 414 store.Op = OpCopy 415 } 416} 417