github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/compile/internal/gc/initorder.go (about) 1 // Copyright 2019 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 gc 6 7 import ( 8 "bytes" 9 "container/heap" 10 "fmt" 11 ) 12 13 // Package initialization 14 // 15 // Here we implement the algorithm for ordering package-level variable 16 // initialization. The spec is written in terms of variable 17 // initialization, but multiple variables initialized by a single 18 // assignment are handled together, so here we instead focus on 19 // ordering initialization assignments. Conveniently, this maps well 20 // to how we represent package-level initializations using the Node 21 // AST. 22 // 23 // Assignments are in one of three phases: NotStarted, Pending, or 24 // Done. For assignments in the Pending phase, we use Xoffset to 25 // record the number of unique variable dependencies whose 26 // initialization assignment is not yet Done. We also maintain a 27 // "blocking" map that maps assignments back to all of the assignments 28 // that depend on it. 29 // 30 // For example, for an initialization like: 31 // 32 // var x = f(a, b, b) 33 // var a, b = g() 34 // 35 // the "x = f(a, b, b)" assignment depends on two variables (a and b), 36 // so its Xoffset will be 2. Correspondingly, the "a, b = g()" 37 // assignment's "blocking" entry will have two entries back to x's 38 // assignment. 39 // 40 // Logically, initialization works by (1) taking all NotStarted 41 // assignments, calculating their dependencies, and marking them 42 // Pending; (2) adding all Pending assignments with Xoffset==0 to a 43 // "ready" priority queue (ordered by variable declaration position); 44 // and (3) iteratively processing the next Pending assignment from the 45 // queue, decreasing the Xoffset of assignments it's blocking, and 46 // adding them to the queue if decremented to 0. 47 // 48 // As an optimization, we actually apply each of these three steps for 49 // each assignment. This yields the same order, but keeps queue size 50 // down and thus also heap operation costs. 51 52 // Static initialization phase. 53 // These values are stored in two bits in Node.flags. 54 const ( 55 InitNotStarted = iota 56 InitDone 57 InitPending 58 ) 59 60 type InitOrder struct { 61 // blocking maps initialization assignments to the assignments 62 // that depend on it. 63 blocking map[*Node][]*Node 64 65 // ready is the queue of Pending initialization assignments 66 // that are ready for initialization. 67 ready declOrder 68 } 69 70 // initOrder computes initialization order for a list l of 71 // package-level declarations (in declaration order) and outputs the 72 // corresponding list of statements to include in the init() function 73 // body. 74 func initOrder(l []*Node) []*Node { 75 s := InitSchedule{ 76 initplans: make(map[*Node]*InitPlan), 77 inittemps: make(map[*Node]*Node), 78 } 79 o := InitOrder{ 80 blocking: make(map[*Node][]*Node), 81 } 82 83 // Process all package-level assignment in declaration order. 84 for _, n := range l { 85 switch n.Op { 86 case OAS, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: 87 o.processAssign(n) 88 o.flushReady(s.staticInit) 89 case ODCLCONST, ODCLFUNC, ODCLTYPE: 90 // nop 91 default: 92 Fatalf("unexpected package-level statement: %v", n) 93 } 94 } 95 96 // Check that all assignments are now Done; if not, there must 97 // have been a dependency cycle. 98 for _, n := range l { 99 switch n.Op { 100 case OAS, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: 101 if n.Initorder() != InitDone { 102 // If there have already been errors 103 // printed, those errors may have 104 // confused us and there might not be 105 // a loop. Let the user fix those 106 // first. 107 if nerrors > 0 { 108 errorexit() 109 } 110 111 findInitLoopAndExit(firstLHS(n), new([]*Node)) 112 Fatalf("initialization unfinished, but failed to identify loop") 113 } 114 } 115 } 116 117 // Invariant consistency check. If this is non-zero, then we 118 // should have found a cycle above. 119 if len(o.blocking) != 0 { 120 Fatalf("expected empty map: %v", o.blocking) 121 } 122 123 return s.out 124 } 125 126 func (o *InitOrder) processAssign(n *Node) { 127 if n.Initorder() != InitNotStarted || n.Xoffset != BADWIDTH { 128 Fatalf("unexpected state: %v, %v, %v", n, n.Initorder(), n.Xoffset) 129 } 130 131 n.SetInitorder(InitPending) 132 n.Xoffset = 0 133 134 // Compute number of variable dependencies and build the 135 // inverse dependency ("blocking") graph. 136 for dep := range collectDeps(n, true) { 137 defn := dep.Name.Defn 138 // Skip dependencies on functions (PFUNC) and 139 // variables already initialized (InitDone). 140 if dep.Class() != PEXTERN || defn.Initorder() == InitDone { 141 continue 142 } 143 n.Xoffset++ 144 o.blocking[defn] = append(o.blocking[defn], n) 145 } 146 147 if n.Xoffset == 0 { 148 heap.Push(&o.ready, n) 149 } 150 } 151 152 // flushReady repeatedly applies initialize to the earliest (in 153 // declaration order) assignment ready for initialization and updates 154 // the inverse dependency ("blocking") graph. 155 func (o *InitOrder) flushReady(initialize func(*Node)) { 156 for o.ready.Len() != 0 { 157 n := heap.Pop(&o.ready).(*Node) 158 if n.Initorder() != InitPending || n.Xoffset != 0 { 159 Fatalf("unexpected state: %v, %v, %v", n, n.Initorder(), n.Xoffset) 160 } 161 162 initialize(n) 163 n.SetInitorder(InitDone) 164 n.Xoffset = BADWIDTH 165 166 blocked := o.blocking[n] 167 delete(o.blocking, n) 168 169 for _, m := range blocked { 170 m.Xoffset-- 171 if m.Xoffset == 0 { 172 heap.Push(&o.ready, m) 173 } 174 } 175 } 176 } 177 178 // findInitLoopAndExit searches for an initialization loop involving variable 179 // or function n. If one is found, it reports the loop as an error and exits. 180 // 181 // path points to a slice used for tracking the sequence of 182 // variables/functions visited. Using a pointer to a slice allows the 183 // slice capacity to grow and limit reallocations. 184 func findInitLoopAndExit(n *Node, path *[]*Node) { 185 // We implement a simple DFS loop-finding algorithm. This 186 // could be faster, but initialization cycles are rare. 187 188 for i, x := range *path { 189 if x == n { 190 reportInitLoopAndExit((*path)[i:]) 191 return 192 } 193 } 194 195 // There might be multiple loops involving n; by sorting 196 // references, we deterministically pick the one reported. 197 refers := collectDeps(n.Name.Defn, false).Sorted(func(ni, nj *Node) bool { 198 return ni.Pos.Before(nj.Pos) 199 }) 200 201 *path = append(*path, n) 202 for _, ref := range refers { 203 // Short-circuit variables that were initialized. 204 if ref.Class() == PEXTERN && ref.Name.Defn.Initorder() == InitDone { 205 continue 206 } 207 208 findInitLoopAndExit(ref, path) 209 } 210 *path = (*path)[:len(*path)-1] 211 } 212 213 // reportInitLoopAndExit reports and initialization loop as an error 214 // and exits. However, if l is not actually an initialization loop, it 215 // simply returns instead. 216 func reportInitLoopAndExit(l []*Node) { 217 // Rotate loop so that the earliest variable declaration is at 218 // the start. 219 i := -1 220 for j, n := range l { 221 if n.Class() == PEXTERN && (i == -1 || n.Pos.Before(l[i].Pos)) { 222 i = j 223 } 224 } 225 if i == -1 { 226 // False positive: loop only involves recursive 227 // functions. Return so that findInitLoop can continue 228 // searching. 229 return 230 } 231 l = append(l[i:], l[:i]...) 232 233 // TODO(mdempsky): Method values are printed as "T.m-fm" 234 // rather than "T.m". Figure out how to avoid that. 235 236 var msg bytes.Buffer 237 fmt.Fprintf(&msg, "initialization loop:\n") 238 for _, n := range l { 239 fmt.Fprintf(&msg, "\t%v: %v refers to\n", n.Line(), n) 240 } 241 fmt.Fprintf(&msg, "\t%v: %v", l[0].Line(), l[0]) 242 243 yyerrorl(l[0].Pos, msg.String()) 244 errorexit() 245 } 246 247 // collectDeps returns all of the package-level functions and 248 // variables that declaration n depends on. If transitive is true, 249 // then it also includes the transitive dependencies of any depended 250 // upon functions (but not variables). 251 func collectDeps(n *Node, transitive bool) NodeSet { 252 d := initDeps{transitive: transitive} 253 switch n.Op { 254 case OAS: 255 d.inspect(n.Right) 256 case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV: 257 d.inspect(n.Right) 258 case ODCLFUNC: 259 d.inspectList(n.Nbody) 260 default: 261 Fatalf("unexpected Op: %v", n.Op) 262 } 263 return d.seen 264 } 265 266 type initDeps struct { 267 transitive bool 268 seen NodeSet 269 } 270 271 func (d *initDeps) inspect(n *Node) { inspect(n, d.visit) } 272 func (d *initDeps) inspectList(l Nodes) { inspectList(l, d.visit) } 273 274 // visit calls foundDep on any package-level functions or variables 275 // referenced by n, if any. 276 func (d *initDeps) visit(n *Node) bool { 277 switch n.Op { 278 case ONAME: 279 if n.isMethodExpression() { 280 d.foundDep(asNode(n.Type.FuncType().Nname)) 281 return false 282 } 283 284 switch n.Class() { 285 case PEXTERN, PFUNC: 286 d.foundDep(n) 287 } 288 289 case OCLOSURE: 290 d.inspectList(n.Func.Closure.Nbody) 291 292 case ODOTMETH, OCALLPART: 293 d.foundDep(asNode(n.Type.FuncType().Nname)) 294 } 295 296 return true 297 } 298 299 // foundDep records that we've found a dependency on n by adding it to 300 // seen. 301 func (d *initDeps) foundDep(n *Node) { 302 // Can happen with method expressions involving interface 303 // types; e.g., fixedbugs/issue4495.go. 304 if n == nil { 305 return 306 } 307 308 // Names without definitions aren't interesting as far as 309 // initialization ordering goes. 310 if n.Name.Defn == nil { 311 return 312 } 313 314 if d.seen.Has(n) { 315 return 316 } 317 d.seen.Add(n) 318 if d.transitive && n.Class() == PFUNC { 319 d.inspectList(n.Name.Defn.Nbody) 320 } 321 } 322 323 // declOrder implements heap.Interface, ordering assignment statements 324 // by the position of their first LHS expression. 325 // 326 // N.B., the Pos of the first LHS expression is used because because 327 // an OAS node's Pos may not be unique. For example, given the 328 // declaration "var a, b = f(), g()", "a" must be ordered before "b", 329 // but both OAS nodes use the "=" token's position as their Pos. 330 type declOrder []*Node 331 332 func (s declOrder) Len() int { return len(s) } 333 func (s declOrder) Less(i, j int) bool { return firstLHS(s[i]).Pos.Before(firstLHS(s[j]).Pos) } 334 func (s declOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] } 335 336 func (s *declOrder) Push(x interface{}) { *s = append(*s, x.(*Node)) } 337 func (s *declOrder) Pop() interface{} { 338 n := (*s)[len(*s)-1] 339 *s = (*s)[:len(*s)-1] 340 return n 341 } 342 343 // firstLHS returns the first expression on the left-hand side of 344 // assignment n. 345 func firstLHS(n *Node) *Node { 346 switch n.Op { 347 case OAS: 348 return n.Left 349 case OAS2DOTTYPE, OAS2FUNC, OAS2RECV, OAS2MAPR: 350 return n.List.First() 351 } 352 353 Fatalf("unexpected Op: %v", n.Op) 354 return nil 355 }