Commit 5d0d87ae authored by Matthew Dempsky's avatar Matthew Dempsky

cmd/compile: fix package initialization ordering

This CL rewrites cmd/compile's package-level initialization ordering
algorithm to be compliant with the Go spec. See documentation in
initorder.go for details.

Incidentally, this CL also improves fidelity of initialization loop
diagnostics by including referenced functions in the emitted output
like go/types does.

Fixes #22326.

Change-Id: I7c9ac47ff563df4d4f700cf6195387a0f372cc7b
Reviewed-on: https://go-review.googlesource.com/c/go/+/170062
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: default avatarRobert Griesemer <gri@golang.org>
parent e883d000
......@@ -31,7 +31,7 @@ func renameinit() *types.Sym {
// 2) Initialize all the variables that have initializers.
// 3) Run any init functions.
func fninit(n []*Node) {
nf := initfix(n)
nf := initOrder(n)
var deps []*obj.LSym // initTask records for packages the current package depends on
var fns []*obj.LSym // functions to call for package initialization
......
This diff is collapsed.
......@@ -9,14 +9,6 @@ import (
"fmt"
)
// Static initialization ordering state.
// These values are stored in two bits in Node.flags.
const (
InitNotStarted = iota
InitDone
InitPending
)
type InitEntry struct {
Xoffset int64 // struct, array only
Expr *Node // bytes of run-time computed expressions
......@@ -26,9 +18,15 @@ type InitPlan struct {
E []InitEntry
}
// An InitSchedule is used to decompose assignment statements into
// static and dynamic initialization parts. Static initializations are
// handled by populating variables' linker symbol data, while dynamic
// initializations are accumulated to be executed in order.
type InitSchedule struct {
out []*Node
initlist []*Node
// out is the ordered list of dynamic initialization
// statements.
out []*Node
initplans map[*Node]*InitPlan
inittemps map[*Node]*Node
}
......@@ -37,239 +35,33 @@ func (s *InitSchedule) append(n *Node) {
s.out = append(s.out, n)
}
// init1 walks the AST starting at n, and accumulates in out
// the list of definitions needing init code in dependency order.
func (s *InitSchedule) init1(n *Node) {
if n == nil {
return
}
s.init1(n.Left)
s.init1(n.Right)
for _, n1 := range n.List.Slice() {
s.init1(n1)
}
if n.isMethodExpression() {
// Methods called as Type.Method(receiver, ...).
// Definitions for method expressions are stored in type->nname.
s.init1(asNode(n.Type.FuncType().Nname))
}
if n.Op != ONAME {
return
}
switch n.Class() {
case PEXTERN, PFUNC:
default:
if n.isBlank() && n.Name.Curfn == nil && n.Name.Defn != nil && n.Name.Defn.Initorder() == InitNotStarted {
// blank names initialization is part of init() but not
// when they are inside a function.
break
// staticInit adds an initialization statement n to the schedule.
func (s *InitSchedule) staticInit(n *Node) {
if !s.tryStaticInit(n) {
if Debug['%'] != 0 {
Dump("nonstatic", n)
}
return
}
if n.Initorder() == InitDone {
return
}
if n.Initorder() == InitPending {
// Since mutually recursive sets of functions are allowed,
// we don't necessarily raise an error if n depends on a node
// which is already waiting for its dependencies to be visited.
//
// initlist contains a cycle of identifiers referring to each other.
// If this cycle contains a variable, then this variable refers to itself.
// Conversely, if there exists an initialization cycle involving
// a variable in the program, the tree walk will reach a cycle
// involving that variable.
if n.Class() != PFUNC {
s.foundinitloop(n, n)
}
for i := len(s.initlist) - 1; i >= 0; i-- {
x := s.initlist[i]
if x == n {
break
}
if x.Class() != PFUNC {
s.foundinitloop(n, x)
}
}
// The loop involves only functions, ok.
return
}
// reached a new unvisited node.
n.SetInitorder(InitPending)
s.initlist = append(s.initlist, n)
// make sure that everything n depends on is initialized.
// n->defn is an assignment to n
if defn := n.Name.Defn; defn != nil {
switch defn.Op {
default:
Dump("defn", defn)
Fatalf("init1: bad defn")
case ODCLFUNC:
s.init2list(defn.Nbody)
case OAS:
if defn.Left != n {
Dump("defn", defn)
Fatalf("init1: bad defn")
}
if defn.Left.isBlank() && candiscard(defn.Right) {
defn.Op = OEMPTY
defn.Left = nil
defn.Right = nil
break
}
s.init2(defn.Right)
if Debug['j'] != 0 {
fmt.Printf("%v\n", n.Sym)
}
if n.isBlank() || !s.staticinit(n) {
if Debug['%'] != 0 {
Dump("nonstatic", defn)
}
s.append(defn)
}
case OAS2FUNC, OAS2MAPR, OAS2DOTTYPE, OAS2RECV:
if defn.Initorder() == InitDone {
break
}
defn.SetInitorder(InitPending)
for _, n2 := range defn.Rlist.Slice() {
s.init1(n2)
}
if Debug['%'] != 0 {
Dump("nonstatic", defn)
}
s.append(defn)
defn.SetInitorder(InitDone)
}
}
last := len(s.initlist) - 1
if s.initlist[last] != n {
Fatalf("bad initlist %v", s.initlist)
}
s.initlist[last] = nil // allow GC
s.initlist = s.initlist[:last]
n.SetInitorder(InitDone)
}
// foundinitloop prints an init loop error and exits.
func (s *InitSchedule) foundinitloop(node, visited *Node) {
// If there have already been errors printed,
// those errors probably confused us and
// there might not be a loop. Let the user
// fix those first.
flusherrors()
if nerrors > 0 {
errorexit()
}
// Find the index of node and visited in the initlist.
var nodeindex, visitedindex int
for ; s.initlist[nodeindex] != node; nodeindex++ {
}
for ; s.initlist[visitedindex] != visited; visitedindex++ {
}
// There is a loop involving visited. We know about node and
// initlist = n1 <- ... <- visited <- ... <- node <- ...
fmt.Printf("%v: initialization loop:\n", visited.Line())
// Print visited -> ... -> n1 -> node.
for _, n := range s.initlist[visitedindex:] {
fmt.Printf("\t%v %v refers to\n", n.Line(), n.Sym)
}
// Print node -> ... -> visited.
for _, n := range s.initlist[nodeindex:visitedindex] {
fmt.Printf("\t%v %v refers to\n", n.Line(), n.Sym)
}
fmt.Printf("\t%v %v\n", visited.Line(), visited.Sym)
errorexit()
}
// recurse over n, doing init1 everywhere.
func (s *InitSchedule) init2(n *Node) {
if n == nil || n.Initorder() == InitDone {
return
}
if n.Op == ONAME && n.Ninit.Len() != 0 {
Fatalf("name %v with ninit: %+v\n", n.Sym, n)
}
s.init1(n)
s.init2(n.Left)
s.init2(n.Right)
s.init2list(n.Ninit)
s.init2list(n.List)
s.init2list(n.Rlist)
s.init2list(n.Nbody)
switch n.Op {
case OCLOSURE:
s.init2list(n.Func.Closure.Nbody)
case ODOTMETH, OCALLPART:
s.init2(asNode(n.Type.FuncType().Nname))
s.append(n)
}
}
func (s *InitSchedule) init2list(l Nodes) {
for _, n := range l.Slice() {
s.init2(n)
}
}
func (s *InitSchedule) initreorder(l []*Node) {
for _, n := range l {
switch n.Op {
case ODCLFUNC, ODCLCONST, ODCLTYPE:
continue
}
s.initreorder(n.Ninit.Slice())
n.Ninit.Set(nil)
s.init1(n)
}
}
// initfix computes initialization order for a list l of top-level
// declarations and outputs the corresponding list of statements
// to include in the init() function body.
func initfix(l []*Node) []*Node {
s := InitSchedule{
initplans: make(map[*Node]*InitPlan),
inittemps: make(map[*Node]*Node),
// tryStaticInit attempts to statically execute an initialization
// statement and reports whether it succeeded.
func (s *InitSchedule) tryStaticInit(n *Node) bool {
// Only worry about simple "l = r" assignments. Multiple
// variable/expression OAS2 assignments have already been
// replaced by multiple simple OAS assignments, and the other
// OAS2* assignments mostly necessitate dynamic execution
// anyway.
if n.Op != OAS {
return false
}
lno := lineno
s.initreorder(l)
lineno = lno
return s.out
}
// compilation of top-level (static) assignments
// into DATA statements if at all possible.
func (s *InitSchedule) staticinit(n *Node) bool {
if n.Op != ONAME || n.Class() != PEXTERN || n.Name.Defn == nil || n.Name.Defn.Op != OAS {
Fatalf("staticinit")
if n.Left.isBlank() && candiscard(n.Right) {
return true
}
lineno = n.Pos
l := n.Name.Defn.Left
r := n.Name.Defn.Right
return s.staticassign(l, r)
lno := setlineno(n)
defer func() { lineno = lno }()
return s.staticassign(n.Left, n.Right)
}
// like staticassign but we are copying an already
......
......@@ -12,6 +12,7 @@ import (
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
"sort"
)
// A Node is a single node in the syntax tree.
......@@ -970,3 +971,30 @@ func (q *nodeQueue) popLeft() *Node {
q.head++
return n
}
// NodeSet is a set of Nodes.
type NodeSet map[*Node]struct{}
// Has reports whether s contains n.
func (s NodeSet) Has(n *Node) bool {
_, isPresent := s[n]
return isPresent
}
// Add adds n to s.
func (s *NodeSet) Add(n *Node) {
if *s == nil {
*s = make(map[*Node]struct{})
}
(*s)[n] = struct{}{}
}
// Sorted returns s sorted according to less.
func (s NodeSet) Sorted(less func(*Node, *Node) bool) []*Node {
var res []*Node
for n := range s {
res = append(res, n)
}
sort.Slice(res, func(i, j int) bool { return less(res[i], res[j]) })
return res
}
// run
// Copyright 2019 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 main
var (
_ = d
_ = f("_", c, b)
a = f("a")
b = f("b")
c = f("c")
d = f("d")
)
func f(s string, rest ...int) int {
print(s)
return 0
}
func main() {
println()
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment