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 }