Skip to content

Commit

Permalink
go.tools/go/types: compute correct initialization order
Browse files Browse the repository at this point in the history
- Replaced check.initDependencies with check.initOrder;
  this is the only semantic change, it affects only the
  value of Info.InitOrder.
- Added additional init order test cases and adjusted
  existing tests.
- Moved orderedSetObjects from resolver.go to ordering.go.

Fixes golang/go#7964.

LGTM=adonovan
R=adonovan
CC=golang-codereviews
https://golang.org/cl/91450043
  • Loading branch information
griesemer committed Jun 11, 2014
1 parent 459aaad commit 3827909
Show file tree
Hide file tree
Showing 8 changed files with 273 additions and 130 deletions.
2 changes: 1 addition & 1 deletion go/ssa/interp/testdata/coverage.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ var a, b = create(1), create(2)

// Initialization order of package-level value specs.
func init() {
if x := fmt.Sprint(order); x != "[2 3 1]" {
if x := fmt.Sprint(order); x != "[1 2 3]" {
panic(x)
}
if c != 3 {
Expand Down
4 changes: 2 additions & 2 deletions go/ssa/interp/testdata/initorder.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ func main() {
// - {f,b,c/d,e} < order (ref graph traversal)
// - order < {a} (lexical order)
// - b < c/d < e < f (lexical order)
// Solution: b c/d e f a
// Solution: a b c/d e f
abcdef := [6]int{a, b, c, d, e, f}
if abcdef != [6]int{5, 0, 1, 2, 3, 4} {
if abcdef != [6]int{0, 1, 2, 3, 4, 5} {
panic(abcdef)
}
}
Expand Down
26 changes: 25 additions & 1 deletion go/types/api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -406,7 +406,31 @@ func TestInitOrderInfo(t *testing.T) {
"y = 1", "x = T.m",
}},
{`package p10; var (d = c + b; a = 0; b = 0; c = 0)`, []string{
"b = 0", "c = 0", "d = c + b", "a = 0",
"a = 0", "b = 0", "c = 0", "d = c + b",
}},
{`package p11; var (a = e + c; b = d + c; c = 0; d = 0; e = 0)`, []string{
"c = 0", "d = 0", "b = d + c", "e = 0", "a = e + c",
}},
// emit an initializer for n:1 initializations only once (not for each node
// on the lhs which may appear in different order in the dependency graph)
{`package p12; var (a = x; b = 0; x, y = m[0]; m map[int]int)`, []string{
"b = 0", "x, y = m[0]", "a = x",
}},
// test case from spec section on package initialization
{`package p12
var (
a = c + b
b = f()
c = f()
d = 3
)
func f() int {
d++
return d
}`, []string{
"d = 3", "b = f()", "c = f()", "a = c + b",
}},
// test case for issue 7131
{`package main
Expand Down
15 changes: 6 additions & 9 deletions go/types/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,14 @@ type checker struct {
indent int // indentation for tracing
}

// addDeclDep adds the dependency edge (check.decl -> to)
// if check.decl exists and to has an initializer.
// addDeclDep adds the dependency edge (check.decl -> to) if check.decl exists
func (check *checker) addDeclDep(to Object) {
from := check.decl
if from == nil {
return // not in a package-level init expression
}
if decl := check.objMap[to]; decl == nil || !decl.hasInitializer() {
return // to is not a package-level object or has no initializer
if _, found := check.objMap[to]; !found {
return // to is not a package-level object
}
from.addDep(to)
}
Expand Down Expand Up @@ -189,7 +188,7 @@ func (check *checker) initFiles(files []*ast.File) {
}
}

// A bailout panic is raised to indicate early termination.
// A bailout panic is used for early termination.
type bailout struct{}

func (check *checker) handleBailout(err *error) {
Expand All @@ -211,13 +210,11 @@ func (check *checker) Files(files []*ast.File) (err error) {

check.collectObjects()

objList := check.resolveOrder()

check.packageObjects(objList)
check.packageObjects(check.resolveOrder())

check.functionBodies()

check.initDependencies(objList)
check.initOrder()

check.unusedImports()

Expand Down
218 changes: 218 additions & 0 deletions go/types/initorder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,218 @@
// Copyright 2014 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package types

import (
"container/heap"
"fmt"
)

// initOrder computes the Info.InitOrder for package variables.
func (check *checker) initOrder() {
// compute the object dependency graph and
// initialize a priority queue with the list
// of graph nodes
pq := nodeQueue(dependencyGraph(check.objMap))
heap.Init(&pq)

const debug = false
if debug {
fmt.Printf("package %s: object dependency graph\n", check.pkg.Name())
for _, n := range pq {
for _, o := range n.out {
fmt.Printf("\t%s -> %s\n", n.obj.Name(), o.obj.Name())
}
}
fmt.Println()
fmt.Printf("package %s: initialization order\n", check.pkg.Name())
}

// determine initialization order by removing the highest priority node
// (the one with the fewest dependencies) and its edges from the graph,
// repeatedly, until there are no nodes left.
// In a valid Go program, those nodes always have zero dependencies (after
// removing all incoming dependencies), otherwise there are initialization
// cycles.
mark := 0
emitted := make(map[*declInfo]bool)
for len(pq) > 0 {
// get the next node
n := heap.Pop(&pq).(*objNode)

// if n still depends on other nodes, we have a cycle
if n.in > 0 {
mark++ // mark nodes using a different value each time
cycle := findPath(n, n, mark)
if i := valIndex(cycle); i >= 0 {
check.reportCycle(cycle, i)
}
// ok to continue, but the variable initialization order
// will be incorrect at this point since it assumes no
// cycle errors
}

// reduce dependency count of all dependent nodes
// and update priority queue
for _, out := range n.out {
out.in--
heap.Fix(&pq, out.index)
}

// record the init order for variables with initializers only
v, _ := n.obj.(*Var)
info := check.objMap[v]
if v == nil || !info.hasInitializer() {
continue
}

// n:1 variable declarations such as: a, b = f()
// introduce a node for each lhs variable (here: a, b);
// but they all have the same initializer - emit only
// one, for the first variable seen
if emitted[info] {
continue // initializer already emitted, if any
}
emitted[info] = true

infoLhs := info.lhs // possibly nil (see declInfo.lhs field comment)
if infoLhs == nil {
infoLhs = []*Var{v}
}
init := &Initializer{infoLhs, info.init}
check.Info.InitOrder = append(check.Info.InitOrder, init)

if debug {
fmt.Printf("\t%s\n", init)
}
}

if debug {
fmt.Println()
}
}

// findPath returns the (reversed) list of nodes z, ... c, b, a,
// such that there is a path (list of edges) from a to z.
// If there is no such path, the result is nil.
// Nodes marked with the value mark are considered "visited";
// unvisited nodes are marked during the graph search.
func findPath(a, z *objNode, mark int) []*objNode {
if a.mark == mark {
return nil // node already seen
}
a.mark = mark

for _, n := range a.out {
if n == z {
return []*objNode{z}
}
if P := findPath(n, z, mark); P != nil {
return append(P, n)
}
}

return nil
}

// valIndex returns the index of the first constant or variable in a,
// if any; or a value < 0.
func valIndex(a []*objNode) int {
for i, n := range a {
switch n.obj.(type) {
case *Const, *Var:
return i
}
}
return -1
}

// reportCycle reports an error for the cycle starting at i.
func (check *checker) reportCycle(cycle []*objNode, i int) {
obj := cycle[i].obj
check.errorf(obj.Pos(), "initialization cycle for %s", obj.Name())
// print cycle
for _ = range cycle {
check.errorf(obj.Pos(), "\t%s refers to", obj.Name()) // secondary error, \t indented
i++
if i >= len(cycle) {
i = 0
}
obj = cycle[i].obj
}
check.errorf(obj.Pos(), "\t%s", obj.Name())
}

// An objNode represents a node in the object dependency graph.
// Each node b in a.out represents an edge a->b indicating that
// b depends on a.
// Nodes may be marked for cycle detection. A node n is marked
// if n.mark corresponds to the current mark value.
type objNode struct {
obj Object // object represented by this node
in int // number of nodes this node depends on
out []*objNode // list of nodes that depend on this node
index int // node index in list of nodes
mark int // for cycle detection
}

// dependencyGraph computes the transposed object dependency graph
// from the given objMap. The transposed graph is returned as a list
// of nodes; an edge d->n indicates that node n depends on node d.
func dependencyGraph(objMap map[Object]*declInfo) []*objNode {
// M maps each object to its corresponding node
M := make(map[Object]*objNode, len(objMap))
for obj := range objMap {
M[obj] = &objNode{obj: obj}
}

// G is the graph of nodes n
G := make([]*objNode, len(M))
i := 0
for obj, n := range M {
deps := objMap[obj].deps
n.in = len(deps)
for d := range deps {
d := M[d] // node n depends on node d
d.out = append(d.out, n) // add edge d->n
}

G[i] = n
n.index = i
i++
}

return G
}

// nodeQueue implements the container/heap interface;
// a nodeQueue may be used as a priority queue.
type nodeQueue []*objNode

func (a nodeQueue) Len() int { return len(a) }

func (a nodeQueue) Swap(i, j int) {
x, y := a[i], a[j]
a[i], a[j] = y, x
x.index, y.index = j, i
}

func (a nodeQueue) Less(i, j int) bool {
x, y := a[i], a[j]
// nodes are prioritized by number of incoming dependencies (1st key)
// and source positions (2nd key)
return x.in < y.in || x.in == y.in && x.obj.Pos() < y.obj.Pos()
}

func (a *nodeQueue) Push(x interface{}) {
panic("unreachable")
}

func (a *nodeQueue) Pop() interface{} {
n := len(*a)
x := (*a)[n-1]
x.index = -1 // for safety
*a = (*a)[:n-1]
return x
}
19 changes: 19 additions & 0 deletions go/types/ordering.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,22 @@ func (check *checker) appendInPostOrder(order *[]Object, obj Object) {

*order = append(*order, obj)
}

func orderedSetObjects(set map[Object]bool) []Object {
list := make([]Object, len(set))
i := 0
for obj := range set {
// we don't care about the map element value
list[i] = obj
i++
}
sort.Sort(inSourceOrder(list))
return list
}

// inSourceOrder implements the sort.Sort interface.
type inSourceOrder []Object

func (a inSourceOrder) Len() int { return len(a) }
func (a inSourceOrder) Less(i, j int) bool { return a[i].Pos() < a[j].Pos() }
func (a inSourceOrder) Swap(i, j int) { a[i], a[j] = a[j], a[i] }

0 comments on commit 3827909

Please sign in to comment.