Source file src/cmd/compile/internal/ssa/loopreschedchecks.go

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

View as plain text