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.
13 // Package initialization
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
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
30 // For example, for an initialization like:
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
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.
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.
52 // Static initialization phase.
53 // These values are stored in two bits in Node.flags.
60 type InitOrder struct {
61 // blocking maps initialization assignments to the assignments
63 blocking map[*Node][]*Node
65 // ready is the queue of Pending initialization assignments
66 // that are ready for initialization.
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
74 func initOrder(l []*Node) []*Node {
76 initplans: make(map[*Node]*InitPlan),
77 inittemps: make(map[*Node]*Node),
80 blocking: make(map[*Node][]*Node),
83 // Process all package-level assignment in declaration order.
86 case OAS, OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
88 o.flushReady(s.staticInit)
89 case ODCLCONST, ODCLFUNC, ODCLTYPE:
92 Fatalf("unexpected package-level statement: %v", n)
96 // Check that all assignments are now Done; if not, there must
97 // have been a dependency cycle.
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
111 findInitLoopAndExit(firstLHS(n), new([]*Node), make(map[*Node]bool))
112 Fatalf("initialization unfinished, but failed to identify loop")
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)
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)
131 n.SetInitorder(InitPending)
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 {
144 o.blocking[defn] = append(o.blocking[defn], n)
148 heap.Push(&o.ready, n)
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)
163 n.SetInitorder(InitDone)
166 blocked := o.blocking[n]
167 delete(o.blocking, n)
169 for _, m := range blocked {
172 heap.Push(&o.ready, m)
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.
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, ok map[*Node]bool) {
185 for i, x := range *path {
187 reportInitLoopAndExit((*path)[i:])
192 // There might be multiple loops involving n; by sorting
193 // references, we deterministically pick the one reported.
194 refers := collectDeps(n.Name.Defn, false).Sorted(func(ni, nj *Node) bool {
195 return ni.Pos.Before(nj.Pos)
198 *path = append(*path, n)
199 for _, ref := range refers {
200 // Short-circuit variables that were initialized.
201 if ref.Class() == PEXTERN && ref.Name.Defn.Initorder() == InitDone || ok[ref] {
204 findInitLoopAndExit(ref, path, ok)
207 // n is not involved in a cycle.
208 // Record that fact to avoid checking it again when reached another way,
209 // or else this traversal will take exponential time traversing all paths
210 // through the part of the package's call graph implicated in the cycle.
213 *path = (*path)[:len(*path)-1]
216 // reportInitLoopAndExit reports and initialization loop as an error
217 // and exits. However, if l is not actually an initialization loop, it
218 // simply returns instead.
219 func reportInitLoopAndExit(l []*Node) {
220 // Rotate loop so that the earliest variable declaration is at
223 for j, n := range l {
224 if n.Class() == PEXTERN && (i == -1 || n.Pos.Before(l[i].Pos)) {
229 // False positive: loop only involves recursive
230 // functions. Return so that findInitLoop can continue
234 l = append(l[i:], l[:i]...)
236 // TODO(mdempsky): Method values are printed as "T.m-fm"
237 // rather than "T.m". Figure out how to avoid that.
240 fmt.Fprintf(&msg, "initialization loop:\n")
241 for _, n := range l {
242 fmt.Fprintf(&msg, "\t%v: %v refers to\n", n.Line(), n)
244 fmt.Fprintf(&msg, "\t%v: %v", l[0].Line(), l[0])
246 yyerrorl(l[0].Pos, msg.String())
250 // collectDeps returns all of the package-level functions and
251 // variables that declaration n depends on. If transitive is true,
252 // then it also includes the transitive dependencies of any depended
253 // upon functions (but not variables).
254 func collectDeps(n *Node, transitive bool) NodeSet {
255 d := initDeps{transitive: transitive}
259 case OAS2DOTTYPE, OAS2FUNC, OAS2MAPR, OAS2RECV:
262 d.inspectList(n.Nbody)
264 Fatalf("unexpected Op: %v", n.Op)
269 type initDeps struct {
274 func (d *initDeps) inspect(n *Node) { inspect(n, d.visit) }
275 func (d *initDeps) inspectList(l Nodes) { inspectList(l, d.visit) }
277 // visit calls foundDep on any package-level functions or variables
278 // referenced by n, if any.
279 func (d *initDeps) visit(n *Node) bool {
282 if n.isMethodExpression() {
283 d.foundDep(asNode(n.Type.FuncType().Nname))
293 d.inspectList(n.Func.Closure.Nbody)
295 case ODOTMETH, OCALLPART:
296 d.foundDep(asNode(n.Type.FuncType().Nname))
302 // foundDep records that we've found a dependency on n by adding it to
304 func (d *initDeps) foundDep(n *Node) {
305 // Can happen with method expressions involving interface
306 // types; e.g., fixedbugs/issue4495.go.
311 // Names without definitions aren't interesting as far as
312 // initialization ordering goes.
313 if n.Name.Defn == nil {
321 if d.transitive && n.Class() == PFUNC {
322 d.inspectList(n.Name.Defn.Nbody)
326 // declOrder implements heap.Interface, ordering assignment statements
327 // by the position of their first LHS expression.
329 // N.B., the Pos of the first LHS expression is used because because
330 // an OAS node's Pos may not be unique. For example, given the
331 // declaration "var a, b = f(), g()", "a" must be ordered before "b",
332 // but both OAS nodes use the "=" token's position as their Pos.
333 type declOrder []*Node
335 func (s declOrder) Len() int { return len(s) }
336 func (s declOrder) Less(i, j int) bool { return firstLHS(s[i]).Pos.Before(firstLHS(s[j]).Pos) }
337 func (s declOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
339 func (s *declOrder) Push(x interface{}) { *s = append(*s, x.(*Node)) }
340 func (s *declOrder) Pop() interface{} {
342 *s = (*s)[:len(*s)-1]
346 // firstLHS returns the first expression on the left-hand side of
348 func firstLHS(n *Node) *Node {
352 case OAS2DOTTYPE, OAS2FUNC, OAS2RECV, OAS2MAPR:
353 return n.List.First()
356 Fatalf("unexpected Op: %v", n.Op)