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.
12 "cmd/compile/internal/base"
13 "cmd/compile/internal/ir"
14 "cmd/compile/internal/staticinit"
17 // Package initialization
19 // Here we implement the algorithm for ordering package-level variable
20 // initialization. The spec is written in terms of variable
21 // initialization, but multiple variables initialized by a single
22 // assignment are handled together, so here we instead focus on
23 // ordering initialization assignments. Conveniently, this maps well
24 // to how we represent package-level initializations using the Node
27 // Assignments are in one of three phases: NotStarted, Pending, or
28 // Done. For assignments in the Pending phase, we use Xoffset to
29 // record the number of unique variable dependencies whose
30 // initialization assignment is not yet Done. We also maintain a
31 // "blocking" map that maps assignments back to all of the assignments
34 // For example, for an initialization like:
39 // the "x = f(a, b, b)" assignment depends on two variables (a and b),
40 // so its Xoffset will be 2. Correspondingly, the "a, b = g()"
41 // assignment's "blocking" entry will have two entries back to x's
44 // Logically, initialization works by (1) taking all NotStarted
45 // assignments, calculating their dependencies, and marking them
46 // Pending; (2) adding all Pending assignments with Xoffset==0 to a
47 // "ready" priority queue (ordered by variable declaration position);
48 // and (3) iteratively processing the next Pending assignment from the
49 // queue, decreasing the Xoffset of assignments it's blocking, and
50 // adding them to the queue if decremented to 0.
52 // As an optimization, we actually apply each of these three steps for
53 // each assignment. This yields the same order, but keeps queue size
54 // down and thus also heap operation costs.
56 // Static initialization phase.
57 // These values are stored in two bits in Node.flags.
64 type InitOrder struct {
65 // blocking maps initialization assignments to the assignments
67 blocking map[ir.Node][]ir.Node
69 // ready is the queue of Pending initialization assignments
70 // that are ready for initialization.
76 // initOrder computes initialization order for a list l of
77 // package-level declarations (in declaration order) and outputs the
78 // corresponding list of statements to include in the init() function
80 func initOrder(l []ir.Node) []ir.Node {
81 s := staticinit.Schedule{
82 Plans: make(map[ir.Node]*staticinit.Plan),
83 Temps: make(map[ir.Node]*ir.Name),
86 blocking: make(map[ir.Node][]ir.Node),
87 order: make(map[ir.Node]int),
90 // Process all package-level assignment in declaration order.
93 case ir.OAS, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
95 o.flushReady(s.StaticInit)
96 case ir.ODCLCONST, ir.ODCLFUNC, ir.ODCLTYPE:
99 base.Fatalf("unexpected package-level statement: %v", n)
103 // Check that all assignments are now Done; if not, there must
104 // have been a dependency cycle.
105 for _, n := range l {
107 case ir.OAS, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
108 if o.order[n] != orderDone {
109 // If there have already been errors
110 // printed, those errors may have
111 // confused us and there might not be
112 // a loop. Let the user fix those
116 o.findInitLoopAndExit(firstLHS(n), new([]*ir.Name), new(ir.NameSet))
117 base.Fatalf("initialization unfinished, but failed to identify loop")
122 // Invariant consistency check. If this is non-zero, then we
123 // should have found a cycle above.
124 if len(o.blocking) != 0 {
125 base.Fatalf("expected empty map: %v", o.blocking)
131 func (o *InitOrder) processAssign(n ir.Node) {
132 if _, ok := o.order[n]; ok {
133 base.Fatalf("unexpected state: %v, %v", n, o.order[n])
137 // Compute number of variable dependencies and build the
138 // inverse dependency ("blocking") graph.
139 for dep := range collectDeps(n, true) {
141 // Skip dependencies on functions (PFUNC) and
142 // variables already initialized (InitDone).
143 if dep.Class != ir.PEXTERN || o.order[defn] == orderDone {
147 o.blocking[defn] = append(o.blocking[defn], n)
151 heap.Push(&o.ready, n)
155 const orderDone = -1000
157 // flushReady repeatedly applies initialize to the earliest (in
158 // declaration order) assignment ready for initialization and updates
159 // the inverse dependency ("blocking") graph.
160 func (o *InitOrder) flushReady(initialize func(ir.Node)) {
161 for o.ready.Len() != 0 {
162 n := heap.Pop(&o.ready).(ir.Node)
163 if order, ok := o.order[n]; !ok || order != 0 {
164 base.Fatalf("unexpected state: %v, %v, %v", n, ok, order)
168 o.order[n] = orderDone
170 blocked := o.blocking[n]
171 delete(o.blocking, n)
173 for _, m := range blocked {
174 if o.order[m]--; o.order[m] == 0 {
175 heap.Push(&o.ready, m)
181 // findInitLoopAndExit searches for an initialization loop involving variable
182 // or function n. If one is found, it reports the loop as an error and exits.
184 // path points to a slice used for tracking the sequence of
185 // variables/functions visited. Using a pointer to a slice allows the
186 // slice capacity to grow and limit reallocations.
187 func (o *InitOrder) findInitLoopAndExit(n *ir.Name, path *[]*ir.Name, ok *ir.NameSet) {
188 for i, x := range *path {
190 reportInitLoopAndExit((*path)[i:])
195 // There might be multiple loops involving n; by sorting
196 // references, we deterministically pick the one reported.
197 refers := collectDeps(n.Defn, false).Sorted(func(ni, nj *ir.Name) bool {
198 return ni.Pos().Before(nj.Pos())
201 *path = append(*path, n)
202 for _, ref := range refers {
203 // Short-circuit variables that were initialized.
204 if ref.Class == ir.PEXTERN && o.order[ref.Defn] == orderDone || ok.Has(ref) {
208 o.findInitLoopAndExit(ref, path, ok)
211 // n is not involved in a cycle.
212 // Record that fact to avoid checking it again when reached another way,
213 // or else this traversal will take exponential time traversing all paths
214 // through the part of the package's call graph implicated in the cycle.
217 *path = (*path)[:len(*path)-1]
220 // reportInitLoopAndExit reports and initialization loop as an error
221 // and exits. However, if l is not actually an initialization loop, it
222 // simply returns instead.
223 func reportInitLoopAndExit(l []*ir.Name) {
224 // Rotate loop so that the earliest variable declaration is at
227 for j, n := range l {
228 if n.Class == ir.PEXTERN && (i == -1 || n.Pos().Before(l[i].Pos())) {
233 // False positive: loop only involves recursive
234 // functions. Return so that findInitLoop can continue
238 l = append(l[i:], l[:i]...)
240 // TODO(mdempsky): Method values are printed as "T.m-fm"
241 // rather than "T.m". Figure out how to avoid that.
244 fmt.Fprintf(&msg, "initialization loop:\n")
245 for _, n := range l {
246 fmt.Fprintf(&msg, "\t%v: %v refers to\n", ir.Line(n), n)
248 fmt.Fprintf(&msg, "\t%v: %v", ir.Line(l[0]), l[0])
250 base.ErrorfAt(l[0].Pos(), msg.String())
254 // collectDeps returns all of the package-level functions and
255 // variables that declaration n depends on. If transitive is true,
256 // then it also includes the transitive dependencies of any depended
257 // upon functions (but not variables).
258 func collectDeps(n ir.Node, transitive bool) ir.NameSet {
259 d := initDeps{transitive: transitive}
262 n := n.(*ir.AssignStmt)
264 case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
265 n := n.(*ir.AssignListStmt)
269 d.inspectList(n.Body)
271 base.Fatalf("unexpected Op: %v", n.Op())
276 type initDeps struct {
282 func (d *initDeps) cachedVisit() func(ir.Node) {
284 d.cvisit = d.visit // cache closure
289 func (d *initDeps) inspect(n ir.Node) { ir.Visit(n, d.cachedVisit()) }
290 func (d *initDeps) inspectList(l ir.Nodes) { ir.VisitList(l, d.cachedVisit()) }
292 // visit calls foundDep on any package-level functions or variables
293 // referenced by n, if any.
294 func (d *initDeps) visit(n ir.Node) {
299 case ir.PEXTERN, ir.PFUNC:
304 n := n.(*ir.ClosureExpr)
305 d.inspectList(n.Func.Body)
307 case ir.ODOTMETH, ir.OCALLPART, ir.OMETHEXPR:
308 d.foundDep(ir.MethodExprName(n))
312 // foundDep records that we've found a dependency on n by adding it to
314 func (d *initDeps) foundDep(n *ir.Name) {
315 // Can happen with method expressions involving interface
316 // types; e.g., fixedbugs/issue4495.go.
321 // Names without definitions aren't interesting as far as
322 // initialization ordering goes.
331 if d.transitive && n.Class == ir.PFUNC {
332 d.inspectList(n.Defn.(*ir.Func).Body)
336 // declOrder implements heap.Interface, ordering assignment statements
337 // by the position of their first LHS expression.
339 // N.B., the Pos of the first LHS expression is used because because
340 // an OAS node's Pos may not be unique. For example, given the
341 // declaration "var a, b = f(), g()", "a" must be ordered before "b",
342 // but both OAS nodes use the "=" token's position as their Pos.
343 type declOrder []ir.Node
345 func (s declOrder) Len() int { return len(s) }
346 func (s declOrder) Less(i, j int) bool {
347 return firstLHS(s[i]).Pos().Before(firstLHS(s[j]).Pos())
349 func (s declOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
351 func (s *declOrder) Push(x interface{}) { *s = append(*s, x.(ir.Node)) }
352 func (s *declOrder) Pop() interface{} {
354 *s = (*s)[:len(*s)-1]
358 // firstLHS returns the first expression on the left-hand side of
360 func firstLHS(n ir.Node) *ir.Name {
363 n := n.(*ir.AssignStmt)
365 case ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2RECV, ir.OAS2MAPR:
366 n := n.(*ir.AssignListStmt)
367 return n.Lhs[0].Name()
370 base.Fatalf("unexpected Op: %v", n.Op())