mirror of
https://github.com/golang/go.git
synced 2025-12-08 06:10:04 +00:00
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: Robert Griesemer <gri@golang.org>
This commit is contained in:
parent
e883d000f4
commit
5d0d87ae16
6 changed files with 439 additions and 238 deletions
|
|
@ -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)
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
if n.Left.isBlank() && candiscard(n.Right) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// 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),
|
||||
}
|
||||
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")
|
||||
}
|
||||
|
||||
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
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue