github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/src/cmd/compile/internal/ssa/loopreschedchecks.go (about) 1 // Copyright 2016 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 package ssa 6 7 import "fmt" 8 9 // an edgeMem records a backedge, together with the memory 10 // phi functions at the target of the backedge that must 11 // be updated when a rescheduling check replaces the backedge. 12 type edgeMem struct { 13 e Edge 14 m *Value // phi for memory at dest of e 15 } 16 17 // a rewriteTarget is a a value-argindex pair indicating 18 // where a rewrite is applied. Note that this is for values, 19 // not for block controls, because block controls are not targets 20 // for the rewrites performed in inserting rescheduling checks. 21 type rewriteTarget struct { 22 v *Value 23 i int 24 } 25 26 type rewrite struct { 27 before, after *Value // before is the expected value before rewrite, after is the new value installed. 28 rewrites []rewriteTarget // all the targets for this rewrite. 29 } 30 31 func (r *rewrite) String() string { 32 s := "\n\tbefore=" + r.before.String() + ", after=" + r.after.String() 33 for _, rw := range r.rewrites { 34 s += ", (i=" + fmt.Sprint(rw.i) + ", v=" + rw.v.LongString() + ")" 35 } 36 s += "\n" 37 return s 38 } 39 40 // insertLoopReschedChecks inserts rescheduling checks on loop backedges. 41 func insertLoopReschedChecks(f *Func) { 42 // TODO: when split information is recorded in export data, insert checks only on backedges that can be reached on a split-call-free path. 43 44 // Loop reschedule checks compare the stack pointer with 45 // the per-g stack bound. If the pointer appears invalid, 46 // that means a reschedule check is needed. 47 // 48 // Steps: 49 // 1. locate backedges. 50 // 2. Record memory definitions at block end so that 51 // the SSA graph for mem can be properly modified. 52 // 3. Ensure that phi functions that will-be-needed for mem 53 // are present in the graph, initially with trivial inputs. 54 // 4. Record all to-be-modified uses of mem; 55 // apply modifications (split into two steps to simplify and 56 // avoided nagging order-dependences). 57 // 5. Rewrite backedges to include reschedule check, 58 // and modify destination phi function appropriately with new 59 // definitions for mem. 60 61 if f.NoSplit { // nosplit functions don't reschedule. 62 return 63 } 64 65 backedges := backedges(f) 66 if len(backedges) == 0 { // no backedges means no rescheduling checks. 67 return 68 } 69 70 lastMems := findLastMems(f) 71 72 idom := f.Idom() 73 sdom := f.sdom() 74 75 if f.pass.debug > 2 { 76 fmt.Printf("before %s = %s\n", f.Name, sdom.treestructure(f.Entry)) 77 } 78 79 tofixBackedges := []edgeMem{} 80 81 for _, e := range backedges { // TODO: could filter here by calls in loops, if declared and inferred nosplit are recorded in export data. 82 tofixBackedges = append(tofixBackedges, edgeMem{e, nil}) 83 } 84 85 // It's possible that there is no memory state (no global/pointer loads/stores or calls) 86 if lastMems[f.Entry.ID] == nil { 87 lastMems[f.Entry.ID] = f.Entry.NewValue0(f.Entry.Pos, OpInitMem, TypeMem) 88 } 89 90 memDefsAtBlockEnds := make([]*Value, f.NumBlocks()) // For each block, the mem def seen at its bottom. Could be from earlier block. 91 92 // Propagate last mem definitions forward through successor blocks. 93 po := f.postorder() 94 for i := len(po) - 1; i >= 0; i-- { 95 b := po[i] 96 mem := lastMems[b.ID] 97 for j := 0; mem == nil; j++ { // if there's no def, then there's no phi, so the visible mem is identical in all predecessors. 98 // loop because there might be backedges that haven't been visited yet. 99 mem = memDefsAtBlockEnds[b.Preds[j].b.ID] 100 } 101 memDefsAtBlockEnds[b.ID] = mem 102 } 103 104 // Maps from block to newly-inserted phi function in block. 105 newmemphis := make(map[*Block]rewrite) 106 107 // Insert phi functions as necessary for future changes to flow graph. 108 for i, emc := range tofixBackedges { 109 e := emc.e 110 h := e.b 111 112 // find the phi function for the memory input at "h", if there is one. 113 var headerMemPhi *Value // look for header mem phi 114 115 for _, v := range h.Values { 116 if v.Op == OpPhi && v.Type.IsMemory() { 117 headerMemPhi = v 118 } 119 } 120 121 if headerMemPhi == nil { 122 // if the header is nil, make a trivial phi from the dominator 123 mem0 := memDefsAtBlockEnds[idom[h.ID].ID] 124 headerMemPhi = newPhiFor(h, mem0) 125 newmemphis[h] = rewrite{before: mem0, after: headerMemPhi} 126 addDFphis(mem0, h, h, f, memDefsAtBlockEnds, newmemphis) 127 128 } 129 tofixBackedges[i].m = headerMemPhi 130 131 } 132 133 rewriteNewPhis(f.Entry, f.Entry, f, memDefsAtBlockEnds, newmemphis) 134 135 if f.pass.debug > 0 { 136 for b, r := range newmemphis { 137 fmt.Printf("b=%s, rewrite=%s\n", b, r.String()) 138 } 139 } 140 141 // Apply collected rewrites. 142 for _, r := range newmemphis { 143 for _, rw := range r.rewrites { 144 rw.v.SetArg(rw.i, r.after) 145 } 146 } 147 148 // Rewrite backedges to include reschedule checks. 149 for _, emc := range tofixBackedges { 150 e := emc.e 151 headerMemPhi := emc.m 152 h := e.b 153 i := e.i 154 p := h.Preds[i] 155 bb := p.b 156 mem0 := headerMemPhi.Args[i] 157 // bb e->p h, 158 // Because we're going to insert a rare-call, make sure the 159 // looping edge still looks likely. 160 likely := BranchLikely 161 if p.i != 0 { 162 likely = BranchUnlikely 163 } 164 bb.Likely = likely 165 166 // rewrite edge to include reschedule check 167 // existing edges: 168 // 169 // bb.Succs[p.i] == Edge{h, i} 170 // h.Preds[i] == p == Edge{bb,p.i} 171 // 172 // new block(s): 173 // test: 174 // if sp < g.limit { goto sched } 175 // goto join 176 // sched: 177 // mem1 := call resched (mem0) 178 // goto join 179 // join: 180 // mem2 := phi(mem0, mem1) 181 // goto h 182 // 183 // and correct arg i of headerMemPhi and headerCtrPhi 184 // 185 // EXCEPT: join block containing only phi functions is bad 186 // for the register allocator. Therefore, there is no 187 // join, and branches targeting join must instead target 188 // the header, and the other phi functions within header are 189 // adjusted for the additional input. 190 191 test := f.NewBlock(BlockIf) 192 sched := f.NewBlock(BlockPlain) 193 194 test.Pos = bb.Pos 195 sched.Pos = bb.Pos 196 197 // if sp < g.limit { goto sched } 198 // goto header 199 200 types := &f.Config.Types 201 pt := types.Uintptr 202 g := test.NewValue1(bb.Pos, OpGetG, pt, mem0) 203 sp := test.NewValue0(bb.Pos, OpSP, pt) 204 cmpOp := OpLess64U 205 if pt.Size() == 4 { 206 cmpOp = OpLess32U 207 } 208 limaddr := test.NewValue1I(bb.Pos, OpOffPtr, pt, 2*pt.Size(), g) 209 lim := test.NewValue2(bb.Pos, OpLoad, pt, limaddr, mem0) 210 cmp := test.NewValue2(bb.Pos, cmpOp, types.Bool, sp, lim) 211 test.SetControl(cmp) 212 213 // if true, goto sched 214 test.AddEdgeTo(sched) 215 216 // if false, rewrite edge to header. 217 // do NOT remove+add, because that will perturb all the other phi functions 218 // as well as messing up other edges to the header. 219 test.Succs = append(test.Succs, Edge{h, i}) 220 h.Preds[i] = Edge{test, 1} 221 headerMemPhi.SetArg(i, mem0) 222 223 test.Likely = BranchUnlikely 224 225 // sched: 226 // mem1 := call resched (mem0) 227 // goto header 228 resched := f.fe.Syslook("goschedguarded") 229 mem1 := sched.NewValue1A(bb.Pos, OpStaticCall, TypeMem, resched, mem0) 230 sched.AddEdgeTo(h) 231 headerMemPhi.AddArg(mem1) 232 233 bb.Succs[p.i] = Edge{test, 0} 234 test.Preds = append(test.Preds, Edge{bb, p.i}) 235 236 // Must correct all the other phi functions in the header for new incoming edge. 237 // Except for mem phis, it will be the same value seen on the original 238 // backedge at index i. 239 for _, v := range h.Values { 240 if v.Op == OpPhi && v != headerMemPhi { 241 v.AddArg(v.Args[i]) 242 } 243 } 244 } 245 246 f.invalidateCFG() 247 248 if f.pass.debug > 2 { 249 sdom = newSparseTree(f, f.Idom()) 250 fmt.Printf("after %s = %s\n", f.Name, sdom.treestructure(f.Entry)) 251 } 252 253 return 254 } 255 256 // newPhiFor inserts a new Phi function into b, 257 // with all inputs set to v. 258 func newPhiFor(b *Block, v *Value) *Value { 259 phiV := b.NewValue0(b.Pos, OpPhi, v.Type) 260 261 for range b.Preds { 262 phiV.AddArg(v) 263 } 264 return phiV 265 } 266 267 // rewriteNewPhis updates newphis[h] to record all places where the new phi function inserted 268 // in block h will replace a previous definition. Block b is the block currently being processed; 269 // if b has its own phi definition then it takes the place of h. 270 // defsForUses provides information about other definitions of the variable that are present 271 // (and if nil, indicates that the variable is no longer live) 272 func rewriteNewPhis(h, b *Block, f *Func, defsForUses []*Value, newphis map[*Block]rewrite) { 273 // If b is a block with a new phi, then a new rewrite applies below it in the dominator tree. 274 if _, ok := newphis[b]; ok { 275 h = b 276 } 277 change := newphis[h] 278 x := change.before 279 y := change.after 280 281 // Apply rewrites to this block 282 if x != nil { // don't waste time on the common case of no definition. 283 p := &change.rewrites 284 for _, v := range b.Values { 285 if v == y { // don't rewrite self -- phi inputs are handled below. 286 continue 287 } 288 for i, w := range v.Args { 289 if w != x { 290 continue 291 } 292 *p = append(*p, rewriteTarget{v, i}) 293 } 294 } 295 296 // Rewrite appropriate inputs of phis reached in successors 297 // in dominance frontier, self, and dominated. 298 // If the variable def reaching uses in b is itself defined in b, then the new phi function 299 // does not reach the successors of b. (This assumes a bit about the structure of the 300 // phi use-def graph, but it's true for memory.) 301 if dfu := defsForUses[b.ID]; dfu != nil && dfu.Block != b { 302 for _, e := range b.Succs { 303 s := e.b 304 if sphi, ok := newphis[s]; ok { // saves time to find the phi this way. 305 *p = append(*p, rewriteTarget{sphi.after, e.i}) 306 continue 307 } 308 for _, v := range s.Values { 309 if v.Op == OpPhi && v.Args[e.i] == x { 310 *p = append(*p, rewriteTarget{v, e.i}) 311 break 312 } 313 } 314 } 315 } 316 newphis[h] = change 317 } 318 319 sdom := f.sdom() 320 321 for c := sdom[b.ID].child; c != nil; c = sdom[c.ID].sibling { 322 rewriteNewPhis(h, c, f, defsForUses, newphis) // TODO: convert to explicit stack from recursion. 323 } 324 } 325 326 // addDFphis creates new trivial phis that are necessary to correctly reflect (within SSA) 327 // a new definition for variable "x" inserted at h (usually but not necessarily a phi). 328 // These new phis can only occur at the dominance frontier of h; block s is in the dominance 329 // frontier of h if h does not strictly dominate s and if s is a successor of a block b where 330 // either b = h or h strictly dominates b. 331 // These newly created phis are themselves new definitions that may require addition of their 332 // own trivial phi functions in their own dominance frontier, and this is handled recursively. 333 func addDFphis(x *Value, h, b *Block, f *Func, defForUses []*Value, newphis map[*Block]rewrite) { 334 oldv := defForUses[b.ID] 335 if oldv != x { // either a new definition replacing x, or nil if it is proven that there are no uses reachable from b 336 return 337 } 338 sdom := f.sdom() 339 idom := f.Idom() 340 outer: 341 for _, e := range b.Succs { 342 s := e.b 343 // check phi functions in the dominance frontier 344 if sdom.isAncestor(h, s) { 345 continue // h dominates s, successor of b, therefore s is not in the frontier. 346 } 347 if _, ok := newphis[s]; ok { 348 continue // successor s of b already has a new phi function, so there is no need to add another. 349 } 350 if x != nil { 351 for _, v := range s.Values { 352 if v.Op == OpPhi && v.Args[e.i] == x { 353 continue outer // successor s of b has an old phi function, so there is no need to add another. 354 } 355 } 356 } 357 358 old := defForUses[idom[s.ID].ID] // new phi function is correct-but-redundant, combining value "old" on all inputs. 359 headerPhi := newPhiFor(s, old) 360 // the new phi will replace "old" in block s and all blocks dominated by s. 361 newphis[s] = rewrite{before: old, after: headerPhi} // record new phi, to have inputs labeled "old" rewritten to "headerPhi" 362 addDFphis(old, s, s, f, defForUses, newphis) // the new definition may also create new phi functions. 363 } 364 for c := sdom[b.ID].child; c != nil; c = sdom[c.ID].sibling { 365 addDFphis(x, h, c, f, defForUses, newphis) // TODO: convert to explicit stack from recursion. 366 } 367 } 368 369 // findLastMems maps block ids to last memory-output op in a block, if any 370 func findLastMems(f *Func) []*Value { 371 372 var stores []*Value 373 lastMems := make([]*Value, f.NumBlocks()) 374 storeUse := f.newSparseSet(f.NumValues()) 375 defer f.retSparseSet(storeUse) 376 for _, b := range f.Blocks { 377 // Find all the stores in this block. Categorize their uses: 378 // storeUse contains stores which are used by a subsequent store. 379 storeUse.clear() 380 stores = stores[:0] 381 var memPhi *Value 382 for _, v := range b.Values { 383 if v.Op == OpPhi { 384 if v.Type.IsMemory() { 385 memPhi = v 386 } 387 continue 388 } 389 if v.Type.IsMemory() { 390 stores = append(stores, v) 391 if v.Op == OpSelect1 { 392 // Use the arg of the tuple-generating op. 393 v = v.Args[0] 394 } 395 for _, a := range v.Args { 396 if a.Block == b && a.Type.IsMemory() { 397 storeUse.add(a.ID) 398 } 399 } 400 } 401 } 402 if len(stores) == 0 { 403 lastMems[b.ID] = memPhi 404 continue 405 } 406 407 // find last store in the block 408 var last *Value 409 for _, v := range stores { 410 if storeUse.contains(v.ID) { 411 continue 412 } 413 if last != nil { 414 b.Fatalf("two final stores - simultaneous live stores %s %s", last, v) 415 } 416 last = v 417 } 418 if last == nil { 419 b.Fatalf("no last store found - cycle?") 420 } 421 lastMems[b.ID] = last 422 } 423 return lastMems 424 } 425 426 type backedgesState struct { 427 b *Block 428 i int 429 } 430 431 // backedges returns a slice of successor edges that are back 432 // edges. For reducible loops, edge.b is the header. 433 func backedges(f *Func) []Edge { 434 edges := []Edge{} 435 mark := make([]markKind, f.NumBlocks()) 436 stack := []backedgesState{} 437 438 mark[f.Entry.ID] = notExplored 439 stack = append(stack, backedgesState{f.Entry, 0}) 440 441 for len(stack) > 0 { 442 l := len(stack) 443 x := stack[l-1] 444 if x.i < len(x.b.Succs) { 445 e := x.b.Succs[x.i] 446 stack[l-1].i++ 447 s := e.b 448 if mark[s.ID] == notFound { 449 mark[s.ID] = notExplored 450 stack = append(stack, backedgesState{s, 0}) 451 } else if mark[s.ID] == notExplored { 452 edges = append(edges, e) 453 } 454 } else { 455 mark[x.b.ID] = done 456 stack = stack[0 : l-1] 457 } 458 } 459 return edges 460 }