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  }