github.com/bir3/gocompiler@v0.9.2202/src/cmd/compile/internal/ssa/stackalloc.go (about) 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 5 // TODO: live at start of block instead? 6 7 package ssa 8 9 import ( 10 "github.com/bir3/gocompiler/src/cmd/compile/internal/ir" 11 "github.com/bir3/gocompiler/src/cmd/compile/internal/types" 12 "github.com/bir3/gocompiler/src/cmd/internal/src" 13 "fmt" 14 ) 15 16 type stackAllocState struct { 17 f *Func 18 19 // live is the output of stackalloc. 20 // live[b.id] = live values at the end of block b. 21 live [][]ID 22 23 // The following slices are reused across multiple users 24 // of stackAllocState. 25 values []stackValState 26 interfere [][]ID // interfere[v.id] = values that interfere with v. 27 names []LocalSlot 28 29 nArgSlot, // Number of Values sourced to arg slot 30 nNotNeed, // Number of Values not needing a stack slot 31 nNamedSlot, // Number of Values using a named stack slot 32 nReuse, // Number of values reusing a stack slot 33 nAuto, // Number of autos allocated for stack slots. 34 nSelfInterfere int32 // Number of self-interferences 35 } 36 37 func newStackAllocState(f *Func) *stackAllocState { 38 s := f.Cache.stackAllocState 39 if s == nil { 40 return new(stackAllocState) 41 } 42 if s.f != nil { 43 f.fe.Fatalf(src.NoXPos, "newStackAllocState called without previous free") 44 } 45 return s 46 } 47 48 func putStackAllocState(s *stackAllocState) { 49 for i := range s.values { 50 s.values[i] = stackValState{} 51 } 52 for i := range s.interfere { 53 s.interfere[i] = nil 54 } 55 for i := range s.names { 56 s.names[i] = LocalSlot{} 57 } 58 s.f.Cache.stackAllocState = s 59 s.f = nil 60 s.live = nil 61 s.nArgSlot, s.nNotNeed, s.nNamedSlot, s.nReuse, s.nAuto, s.nSelfInterfere = 0, 0, 0, 0, 0, 0 62 } 63 64 type stackValState struct { 65 typ *types.Type 66 spill *Value 67 needSlot bool 68 isArg bool 69 } 70 71 // stackalloc allocates storage in the stack frame for 72 // all Values that did not get a register. 73 // Returns a map from block ID to the stack values live at the end of that block. 74 func stackalloc(f *Func, spillLive [][]ID) [][]ID { 75 if f.pass.debug > stackDebug { 76 fmt.Println("before stackalloc") 77 fmt.Println(f.String()) 78 } 79 s := newStackAllocState(f) 80 s.init(f, spillLive) 81 defer putStackAllocState(s) 82 83 s.stackalloc() 84 if f.pass.stats > 0 { 85 f.LogStat("stack_alloc_stats", 86 s.nArgSlot, "arg_slots", s.nNotNeed, "slot_not_needed", 87 s.nNamedSlot, "named_slots", s.nAuto, "auto_slots", 88 s.nReuse, "reused_slots", s.nSelfInterfere, "self_interfering") 89 } 90 91 return s.live 92 } 93 94 func (s *stackAllocState) init(f *Func, spillLive [][]ID) { 95 s.f = f 96 97 // Initialize value information. 98 if n := f.NumValues(); cap(s.values) >= n { 99 s.values = s.values[:n] 100 } else { 101 s.values = make([]stackValState, n) 102 } 103 for _, b := range f.Blocks { 104 for _, v := range b.Values { 105 s.values[v.ID].typ = v.Type 106 s.values[v.ID].needSlot = !v.Type.IsMemory() && !v.Type.IsVoid() && !v.Type.IsFlags() && f.getHome(v.ID) == nil && !v.rematerializeable() && !v.OnWasmStack 107 s.values[v.ID].isArg = hasAnyArgOp(v) 108 if f.pass.debug > stackDebug && s.values[v.ID].needSlot { 109 fmt.Printf("%s needs a stack slot\n", v) 110 } 111 if v.Op == OpStoreReg { 112 s.values[v.Args[0].ID].spill = v 113 } 114 } 115 } 116 117 // Compute liveness info for values needing a slot. 118 s.computeLive(spillLive) 119 120 // Build interference graph among values needing a slot. 121 s.buildInterferenceGraph() 122 } 123 124 func (s *stackAllocState) stackalloc() { 125 f := s.f 126 127 // Build map from values to their names, if any. 128 // A value may be associated with more than one name (e.g. after 129 // the assignment i=j). This step picks one name per value arbitrarily. 130 if n := f.NumValues(); cap(s.names) >= n { 131 s.names = s.names[:n] 132 } else { 133 s.names = make([]LocalSlot, n) 134 } 135 names := s.names 136 empty := LocalSlot{} 137 for _, name := range f.Names { 138 // Note: not "range f.NamedValues" above, because 139 // that would be nondeterministic. 140 for _, v := range f.NamedValues[*name] { 141 if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { 142 aux := v.Aux.(*AuxNameOffset) 143 // Never let an arg be bound to a differently named thing. 144 if name.N != aux.Name || name.Off != aux.Offset { 145 if f.pass.debug > stackDebug { 146 fmt.Printf("stackalloc register arg %s skipping name %s\n", v, name) 147 } 148 continue 149 } 150 } else if name.N.Class == ir.PPARAM && v.Op != OpArg { 151 // PPARAM's only bind to OpArg 152 if f.pass.debug > stackDebug { 153 fmt.Printf("stackalloc PPARAM name %s skipping non-Arg %s\n", name, v) 154 } 155 continue 156 } 157 158 if names[v.ID] == empty { 159 if f.pass.debug > stackDebug { 160 fmt.Printf("stackalloc value %s to name %s\n", v, *name) 161 } 162 names[v.ID] = *name 163 } 164 } 165 } 166 167 // Allocate args to their assigned locations. 168 for _, v := range f.Entry.Values { 169 if !hasAnyArgOp(v) { 170 continue 171 } 172 if v.Aux == nil { 173 f.Fatalf("%s has nil Aux\n", v.LongString()) 174 } 175 if v.Op == OpArg { 176 loc := LocalSlot{N: v.Aux.(*ir.Name), Type: v.Type, Off: v.AuxInt} 177 if f.pass.debug > stackDebug { 178 fmt.Printf("stackalloc OpArg %s to %s\n", v, loc) 179 } 180 f.setHome(v, loc) 181 continue 182 } 183 // You might think this below would be the right idea, but you would be wrong. 184 // It almost works; as of 105a6e9518 - 2021-04-23, 185 // GOSSAHASH=11011011001011111 == cmd/compile/internal/noder.(*noder).embedded 186 // is compiled incorrectly. I believe the cause is one of those SSA-to-registers 187 // puzzles that the register allocator untangles; in the event that a register 188 // parameter does not end up bound to a name, "fixing" it is a bad idea. 189 // 190 //if f.DebugTest { 191 // if v.Op == OpArgIntReg || v.Op == OpArgFloatReg { 192 // aux := v.Aux.(*AuxNameOffset) 193 // loc := LocalSlot{N: aux.Name, Type: v.Type, Off: aux.Offset} 194 // if f.pass.debug > stackDebug { 195 // fmt.Printf("stackalloc Op%s %s to %s\n", v.Op, v, loc) 196 // } 197 // names[v.ID] = loc 198 // continue 199 // } 200 //} 201 202 } 203 204 // For each type, we keep track of all the stack slots we 205 // have allocated for that type. 206 // TODO: share slots among equivalent types. We would need to 207 // only share among types with the same GC signature. See the 208 // type.Equal calls below for where this matters. 209 locations := map[*types.Type][]LocalSlot{} 210 211 // Each time we assign a stack slot to a value v, we remember 212 // the slot we used via an index into locations[v.Type]. 213 slots := f.Cache.allocIntSlice(f.NumValues()) 214 defer f.Cache.freeIntSlice(slots) 215 for i := range slots { 216 slots[i] = -1 217 } 218 219 // Pick a stack slot for each value needing one. 220 used := f.Cache.allocBoolSlice(f.NumValues()) 221 defer f.Cache.freeBoolSlice(used) 222 for _, b := range f.Blocks { 223 for _, v := range b.Values { 224 if !s.values[v.ID].needSlot { 225 s.nNotNeed++ 226 continue 227 } 228 if hasAnyArgOp(v) { 229 s.nArgSlot++ 230 continue // already picked 231 } 232 233 // If this is a named value, try to use the name as 234 // the spill location. 235 var name LocalSlot 236 if v.Op == OpStoreReg { 237 name = names[v.Args[0].ID] 238 } else { 239 name = names[v.ID] 240 } 241 if name.N != nil && v.Type.Compare(name.Type) == types.CMPeq { 242 for _, id := range s.interfere[v.ID] { 243 h := f.getHome(id) 244 if h != nil && h.(LocalSlot).N == name.N && h.(LocalSlot).Off == name.Off { 245 // A variable can interfere with itself. 246 // It is rare, but it can happen. 247 s.nSelfInterfere++ 248 goto noname 249 } 250 } 251 if f.pass.debug > stackDebug { 252 fmt.Printf("stackalloc %s to %s\n", v, name) 253 } 254 s.nNamedSlot++ 255 f.setHome(v, name) 256 continue 257 } 258 259 noname: 260 // Set of stack slots we could reuse. 261 locs := locations[v.Type] 262 // Mark all positions in locs used by interfering values. 263 for i := 0; i < len(locs); i++ { 264 used[i] = false 265 } 266 for _, xid := range s.interfere[v.ID] { 267 slot := slots[xid] 268 if slot >= 0 { 269 used[slot] = true 270 } 271 } 272 // Find an unused stack slot. 273 var i int 274 for i = 0; i < len(locs); i++ { 275 if !used[i] { 276 s.nReuse++ 277 break 278 } 279 } 280 // If there is no unused stack slot, allocate a new one. 281 if i == len(locs) { 282 s.nAuto++ 283 locs = append(locs, LocalSlot{N: f.NewLocal(v.Pos, v.Type), Type: v.Type, Off: 0}) 284 locations[v.Type] = locs 285 } 286 // Use the stack variable at that index for v. 287 loc := locs[i] 288 if f.pass.debug > stackDebug { 289 fmt.Printf("stackalloc %s to %s\n", v, loc) 290 } 291 f.setHome(v, loc) 292 slots[v.ID] = i 293 } 294 } 295 } 296 297 // computeLive computes a map from block ID to a list of 298 // stack-slot-needing value IDs live at the end of that block. 299 // TODO: this could be quadratic if lots of variables are live across lots of 300 // basic blocks. Figure out a way to make this function (or, more precisely, the user 301 // of this function) require only linear size & time. 302 func (s *stackAllocState) computeLive(spillLive [][]ID) { 303 s.live = make([][]ID, s.f.NumBlocks()) 304 var phis []*Value 305 live := s.f.newSparseSet(s.f.NumValues()) 306 defer s.f.retSparseSet(live) 307 t := s.f.newSparseSet(s.f.NumValues()) 308 defer s.f.retSparseSet(t) 309 310 // Instead of iterating over f.Blocks, iterate over their postordering. 311 // Liveness information flows backward, so starting at the end 312 // increases the probability that we will stabilize quickly. 313 po := s.f.postorder() 314 for { 315 changed := false 316 for _, b := range po { 317 // Start with known live values at the end of the block 318 live.clear() 319 live.addAll(s.live[b.ID]) 320 321 // Propagate backwards to the start of the block 322 phis = phis[:0] 323 for i := len(b.Values) - 1; i >= 0; i-- { 324 v := b.Values[i] 325 live.remove(v.ID) 326 if v.Op == OpPhi { 327 // Save phi for later. 328 // Note: its args might need a stack slot even though 329 // the phi itself doesn't. So don't use needSlot. 330 if !v.Type.IsMemory() && !v.Type.IsVoid() { 331 phis = append(phis, v) 332 } 333 continue 334 } 335 for _, a := range v.Args { 336 if s.values[a.ID].needSlot { 337 live.add(a.ID) 338 } 339 } 340 } 341 342 // for each predecessor of b, expand its list of live-at-end values 343 // invariant: s contains the values live at the start of b (excluding phi inputs) 344 for i, e := range b.Preds { 345 p := e.b 346 t.clear() 347 t.addAll(s.live[p.ID]) 348 t.addAll(live.contents()) 349 t.addAll(spillLive[p.ID]) 350 for _, v := range phis { 351 a := v.Args[i] 352 if s.values[a.ID].needSlot { 353 t.add(a.ID) 354 } 355 if spill := s.values[a.ID].spill; spill != nil { 356 //TODO: remove? Subsumed by SpillUse? 357 t.add(spill.ID) 358 } 359 } 360 if t.size() == len(s.live[p.ID]) { 361 continue 362 } 363 // grow p's live set 364 s.live[p.ID] = append(s.live[p.ID][:0], t.contents()...) 365 changed = true 366 } 367 } 368 369 if !changed { 370 break 371 } 372 } 373 if s.f.pass.debug > stackDebug { 374 for _, b := range s.f.Blocks { 375 fmt.Printf("stacklive %s %v\n", b, s.live[b.ID]) 376 } 377 } 378 } 379 380 func (f *Func) getHome(vid ID) Location { 381 if int(vid) >= len(f.RegAlloc) { 382 return nil 383 } 384 return f.RegAlloc[vid] 385 } 386 387 func (f *Func) setHome(v *Value, loc Location) { 388 for v.ID >= ID(len(f.RegAlloc)) { 389 f.RegAlloc = append(f.RegAlloc, nil) 390 } 391 f.RegAlloc[v.ID] = loc 392 } 393 394 func (s *stackAllocState) buildInterferenceGraph() { 395 f := s.f 396 if n := f.NumValues(); cap(s.interfere) >= n { 397 s.interfere = s.interfere[:n] 398 } else { 399 s.interfere = make([][]ID, n) 400 } 401 live := f.newSparseSet(f.NumValues()) 402 defer f.retSparseSet(live) 403 for _, b := range f.Blocks { 404 // Propagate liveness backwards to the start of the block. 405 // Two values interfere if one is defined while the other is live. 406 live.clear() 407 live.addAll(s.live[b.ID]) 408 for i := len(b.Values) - 1; i >= 0; i-- { 409 v := b.Values[i] 410 if s.values[v.ID].needSlot { 411 live.remove(v.ID) 412 for _, id := range live.contents() { 413 // Note: args can have different types and still interfere 414 // (with each other or with other values). See issue 23522. 415 if s.values[v.ID].typ.Compare(s.values[id].typ) == types.CMPeq || hasAnyArgOp(v) || s.values[id].isArg { 416 s.interfere[v.ID] = append(s.interfere[v.ID], id) 417 s.interfere[id] = append(s.interfere[id], v.ID) 418 } 419 } 420 } 421 for _, a := range v.Args { 422 if s.values[a.ID].needSlot { 423 live.add(a.ID) 424 } 425 } 426 if hasAnyArgOp(v) && s.values[v.ID].needSlot { 427 // OpArg is an input argument which is pre-spilled. 428 // We add back v.ID here because we want this value 429 // to appear live even before this point. Being live 430 // all the way to the start of the entry block prevents other 431 // values from being allocated to the same slot and clobbering 432 // the input value before we have a chance to load it. 433 434 // TODO(register args) this is apparently not wrong for register args -- is it necessary? 435 live.add(v.ID) 436 } 437 } 438 } 439 if f.pass.debug > stackDebug { 440 for vid, i := range s.interfere { 441 if len(i) > 0 { 442 fmt.Printf("v%d interferes with", vid) 443 for _, x := range i { 444 fmt.Printf(" v%d", x) 445 } 446 fmt.Println() 447 } 448 } 449 } 450 } 451 452 func hasAnyArgOp(v *Value) bool { 453 return v.Op == OpArg || v.Op == OpArgIntReg || v.Op == OpArgFloatReg 454 }