github.com/freddyisaac/sicortex-golang@v0.0.0-20231019035217-e03519e66f60/src/cmd/compile/internal/ssa/schedule.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  package ssa
     6  
     7  import "container/heap"
     8  
     9  const (
    10  	ScorePhi = iota // towards top of block
    11  	ScoreNilCheck
    12  	ScoreReadTuple
    13  	ScoreVarDef
    14  	ScoreMemory
    15  	ScoreDefault
    16  	ScoreFlags
    17  	ScoreSelectCall
    18  	ScoreControl // towards bottom of block
    19  )
    20  
    21  type ValHeap struct {
    22  	a     []*Value
    23  	score []int8
    24  }
    25  
    26  func (h ValHeap) Len() int      { return len(h.a) }
    27  func (h ValHeap) Swap(i, j int) { a := h.a; a[i], a[j] = a[j], a[i] }
    28  
    29  func (h *ValHeap) Push(x interface{}) {
    30  	// Push and Pop use pointer receivers because they modify the slice's length,
    31  	// not just its contents.
    32  	v := x.(*Value)
    33  	h.a = append(h.a, v)
    34  }
    35  func (h *ValHeap) Pop() interface{} {
    36  	old := h.a
    37  	n := len(old)
    38  	x := old[n-1]
    39  	h.a = old[0 : n-1]
    40  	return x
    41  }
    42  func (h ValHeap) Less(i, j int) bool {
    43  	x := h.a[i]
    44  	y := h.a[j]
    45  	sx := h.score[x.ID]
    46  	sy := h.score[y.ID]
    47  	if c := sx - sy; c != 0 {
    48  		return c > 0 // higher score comes later.
    49  	}
    50  	if x.Line != y.Line { // Favor in-order line stepping
    51  		return x.Line > y.Line
    52  	}
    53  	if x.Op != OpPhi {
    54  		if c := len(x.Args) - len(y.Args); c != 0 {
    55  			return c < 0 // smaller args comes later
    56  		}
    57  	}
    58  	return x.ID > y.ID
    59  }
    60  
    61  // Schedule the Values in each Block. After this phase returns, the
    62  // order of b.Values matters and is the order in which those values
    63  // will appear in the assembly output. For now it generates a
    64  // reasonable valid schedule using a priority queue. TODO(khr):
    65  // schedule smarter.
    66  func schedule(f *Func) {
    67  	// For each value, the number of times it is used in the block
    68  	// by values that have not been scheduled yet.
    69  	uses := make([]int32, f.NumValues())
    70  
    71  	// reusable priority queue
    72  	priq := new(ValHeap)
    73  
    74  	// "priority" for a value
    75  	score := make([]int8, f.NumValues())
    76  
    77  	// scheduling order. We queue values in this list in reverse order.
    78  	var order []*Value
    79  
    80  	// maps mem values to the next live memory value
    81  	nextMem := make([]*Value, f.NumValues())
    82  	// additional pretend arguments for each Value. Used to enforce load/store ordering.
    83  	additionalArgs := make([][]*Value, f.NumValues())
    84  
    85  	for _, b := range f.Blocks {
    86  		// Compute score. Larger numbers are scheduled closer to the end of the block.
    87  		for _, v := range b.Values {
    88  			switch {
    89  			case v.Op == OpAMD64LoweredGetClosurePtr || v.Op == OpPPC64LoweredGetClosurePtr ||
    90  				v.Op == OpARMLoweredGetClosurePtr || v.Op == OpARM64LoweredGetClosurePtr ||
    91  				v.Op == Op386LoweredGetClosurePtr || v.Op == OpMIPS64LoweredGetClosurePtr ||
    92  				v.Op == OpS390XLoweredGetClosurePtr || v.Op == OpMIPSLoweredGetClosurePtr:
    93  				// We also score GetLoweredClosurePtr as early as possible to ensure that the
    94  				// context register is not stomped. GetLoweredClosurePtr should only appear
    95  				// in the entry block where there are no phi functions, so there is no
    96  				// conflict or ambiguity here.
    97  				if b != f.Entry {
    98  					f.Fatalf("LoweredGetClosurePtr appeared outside of entry block, b=%s", b.String())
    99  				}
   100  				score[v.ID] = ScorePhi
   101  			case v.Op == OpAMD64LoweredNilCheck || v.Op == OpPPC64LoweredNilCheck ||
   102  				v.Op == OpARMLoweredNilCheck || v.Op == OpARM64LoweredNilCheck ||
   103  				v.Op == Op386LoweredNilCheck || v.Op == OpMIPS64LoweredNilCheck ||
   104  				v.Op == OpS390XLoweredNilCheck || v.Op == OpMIPSLoweredNilCheck:
   105  				// Nil checks must come before loads from the same address.
   106  				score[v.ID] = ScoreNilCheck
   107  			case v.Op == OpPhi:
   108  				// We want all the phis first.
   109  				score[v.ID] = ScorePhi
   110  			case v.Op == OpVarDef:
   111  				// We want all the vardefs next.
   112  				score[v.ID] = ScoreVarDef
   113  			case v.Type.IsMemory():
   114  				// Don't schedule independent operations after call to those functions.
   115  				// runtime.selectgo will jump to next instruction after this call,
   116  				// causing extra execution of those operations. Prevent it, by setting
   117  				// priority to high value.
   118  				if (v.Op == OpAMD64CALLstatic || v.Op == OpPPC64CALLstatic ||
   119  					v.Op == OpARMCALLstatic || v.Op == OpARM64CALLstatic ||
   120  					v.Op == Op386CALLstatic || v.Op == OpMIPS64CALLstatic ||
   121  					v.Op == OpS390XCALLstatic || v.Op == OpMIPSCALLstatic) &&
   122  					(isSameSym(v.Aux, "runtime.selectrecv") ||
   123  						isSameSym(v.Aux, "runtime.selectrecv2") ||
   124  						isSameSym(v.Aux, "runtime.selectsend") ||
   125  						isSameSym(v.Aux, "runtime.selectdefault")) {
   126  					score[v.ID] = ScoreSelectCall
   127  				} else {
   128  					// Schedule stores as early as possible. This tends to
   129  					// reduce register pressure. It also helps make sure
   130  					// VARDEF ops are scheduled before the corresponding LEA.
   131  					score[v.ID] = ScoreMemory
   132  				}
   133  			case v.Op == OpSelect0 || v.Op == OpSelect1:
   134  				// Schedule the pseudo-op of reading part of a tuple
   135  				// immediately after the tuple-generating op, since
   136  				// this value is already live. This also removes its
   137  				// false dependency on the other part of the tuple.
   138  				// Also ensures tuple is never spilled.
   139  				score[v.ID] = ScoreReadTuple
   140  			case v.Type.IsFlags() || v.Type.IsTuple():
   141  				// Schedule flag register generation as late as possible.
   142  				// This makes sure that we only have one live flags
   143  				// value at a time.
   144  				score[v.ID] = ScoreFlags
   145  			default:
   146  				score[v.ID] = ScoreDefault
   147  			}
   148  		}
   149  	}
   150  
   151  	// TODO: make this logic permanent in types.IsMemory?
   152  	isMem := func(v *Value) bool {
   153  		return v.Type.IsMemory() || v.Type.IsTuple() && v.Type.FieldType(1).IsMemory()
   154  	}
   155  
   156  	for _, b := range f.Blocks {
   157  		// Find store chain for block.
   158  		// Store chains for different blocks overwrite each other, so
   159  		// the calculated store chain is good only for this block.
   160  		for _, v := range b.Values {
   161  			if v.Op != OpPhi && isMem(v) {
   162  				for _, w := range v.Args {
   163  					if isMem(w) {
   164  						nextMem[w.ID] = v
   165  					}
   166  				}
   167  			}
   168  		}
   169  
   170  		// Compute uses.
   171  		for _, v := range b.Values {
   172  			if v.Op == OpPhi {
   173  				// If a value is used by a phi, it does not induce
   174  				// a scheduling edge because that use is from the
   175  				// previous iteration.
   176  				continue
   177  			}
   178  			for _, w := range v.Args {
   179  				if w.Block == b {
   180  					uses[w.ID]++
   181  				}
   182  				// Any load must come before the following store.
   183  				if !isMem(v) && isMem(w) {
   184  					// v is a load.
   185  					s := nextMem[w.ID]
   186  					if s == nil || s.Block != b {
   187  						continue
   188  					}
   189  					additionalArgs[s.ID] = append(additionalArgs[s.ID], v)
   190  					uses[v.ID]++
   191  				}
   192  			}
   193  		}
   194  
   195  		if b.Control != nil && b.Control.Op != OpPhi {
   196  			// Force the control value to be scheduled at the end,
   197  			// unless it is a phi value (which must be first).
   198  			score[b.Control.ID] = ScoreControl
   199  
   200  			// Schedule values dependent on the control value at the end.
   201  			// This reduces the number of register spills. We don't find
   202  			// all values that depend on the control, just values with a
   203  			// direct dependency. This is cheaper and in testing there
   204  			// was no difference in the number of spills.
   205  			for _, v := range b.Values {
   206  				if v.Op != OpPhi {
   207  					for _, a := range v.Args {
   208  						if a == b.Control {
   209  							score[v.ID] = ScoreControl
   210  						}
   211  					}
   212  				}
   213  			}
   214  		}
   215  
   216  		// To put things into a priority queue
   217  		// The values that should come last are least.
   218  		priq.score = score
   219  		priq.a = priq.a[:0]
   220  
   221  		// Initialize priority queue with schedulable values.
   222  		for _, v := range b.Values {
   223  			if uses[v.ID] == 0 {
   224  				heap.Push(priq, v)
   225  			}
   226  		}
   227  
   228  		// Schedule highest priority value, update use counts, repeat.
   229  		order = order[:0]
   230  		tuples := make(map[ID][]*Value)
   231  		for {
   232  			// Find highest priority schedulable value.
   233  			// Note that schedule is assembled backwards.
   234  
   235  			if priq.Len() == 0 {
   236  				break
   237  			}
   238  
   239  			v := heap.Pop(priq).(*Value)
   240  
   241  			// Add it to the schedule.
   242  			// Do not emit tuple-reading ops until we're ready to emit the tuple-generating op.
   243  			//TODO: maybe remove ReadTuple score above, if it does not help on performance
   244  			switch {
   245  			case v.Op == OpSelect0:
   246  				if tuples[v.Args[0].ID] == nil {
   247  					tuples[v.Args[0].ID] = make([]*Value, 2)
   248  				}
   249  				tuples[v.Args[0].ID][0] = v
   250  			case v.Op == OpSelect1:
   251  				if tuples[v.Args[0].ID] == nil {
   252  					tuples[v.Args[0].ID] = make([]*Value, 2)
   253  				}
   254  				tuples[v.Args[0].ID][1] = v
   255  			case v.Type.IsTuple() && tuples[v.ID] != nil:
   256  				if tuples[v.ID][1] != nil {
   257  					order = append(order, tuples[v.ID][1])
   258  				}
   259  				if tuples[v.ID][0] != nil {
   260  					order = append(order, tuples[v.ID][0])
   261  				}
   262  				delete(tuples, v.ID)
   263  				fallthrough
   264  			default:
   265  				order = append(order, v)
   266  			}
   267  
   268  			// Update use counts of arguments.
   269  			for _, w := range v.Args {
   270  				if w.Block != b {
   271  					continue
   272  				}
   273  				uses[w.ID]--
   274  				if uses[w.ID] == 0 {
   275  					// All uses scheduled, w is now schedulable.
   276  					heap.Push(priq, w)
   277  				}
   278  			}
   279  			for _, w := range additionalArgs[v.ID] {
   280  				uses[w.ID]--
   281  				if uses[w.ID] == 0 {
   282  					// All uses scheduled, w is now schedulable.
   283  					heap.Push(priq, w)
   284  				}
   285  			}
   286  		}
   287  		if len(order) != len(b.Values) {
   288  			f.Fatalf("schedule does not include all values")
   289  		}
   290  		for i := 0; i < len(b.Values); i++ {
   291  			b.Values[i] = order[len(b.Values)-1-i]
   292  		}
   293  	}
   294  
   295  	f.scheduled = true
   296  }