1
0
mirror of https://github.com/golang/go synced 2024-11-26 05:57:58 -07:00

cmd/compile/internal/pkginit: separate "init" and "inittask" logic

This CL splits the creation of the "init" function responsible for
executing package-scope variable initialization statemens from the
creation of the "inittask" record that tells the runtime how to
sequence all program-wide package initialization.

Longer term, this is desirable because sorting variable initialization
is already handled by types2 (with Info.InitOrder), so we might as
well reuse that.

As a more immediate impetus, for unified IR, I want to defer method
wrapper generation until after inlining (to know which wrappers are
needed). But the staticinit optimization used to decide whether to
emit the inittask calls into reflectdata, which in turn tries to
generate its own method wrappers. So separating the work allows to
create the "init" function early and then emit "inittask" after
inlining is done.

Change-Id: Ice1d421f92feecaaeafdf7da6b9647c0f27e3571
Reviewed-on: https://go-review.googlesource.com/c/go/+/346629
Trust: Matthew Dempsky <mdempsky@google.com>
Run-TryBot: Matthew Dempsky <mdempsky@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Cuong Manh Le <cuong.manhle.vn@gmail.com>
This commit is contained in:
Matthew Dempsky 2021-08-31 13:21:11 -07:00
parent 8f397bc118
commit 6c5f028242
3 changed files with 85 additions and 48 deletions

View File

@ -195,18 +195,19 @@ func Main(archInit func(*ssagen.ArchInfo)) {
// because it generates itabs for initializing global variables.
ssagen.InitConfig()
// Build init task.
if initTask := pkginit.Task(); initTask != nil {
typecheck.Export(initTask)
}
// Create "init" function for package-scope variable initialization
// statements, if any.
//
// Note: This needs to happen early, before any optimizations. The
// Go spec defines a precise order than initialization should be
// carried out in, and even mundane optimizations like dead code
// removal can skew the results (e.g., #43444).
pkginit.MakeInit()
// Stability quirk: sort top-level declarations, so we're not
// sensitive to the order that functions are added. In particular,
// the order that noder+typecheck add function closures is very
// subtle, and not important to reproduce.
//
// Note: This needs to happen after pkginit.Task, otherwise it risks
// changing the order in which top-level variables are initialized.
if base.Debug.UnifiedQuirks != 0 {
s := typecheck.Target.Decls
sort.SliceStable(s, func(i, j int) bool {
@ -253,6 +254,11 @@ func Main(archInit func(*ssagen.ArchInfo)) {
}
ir.CurFunc = nil
// Build init task, if needed.
if initTask := pkginit.Task(); initTask != nil {
typecheck.Export(initTask)
}
// Generate ABI wrappers. Must happen before escape analysis
// and doesn't benefit from dead-coding or inlining.
symABIs.GenABIWrappers()

View File

@ -6,14 +6,62 @@ package pkginit
import (
"cmd/compile/internal/base"
"cmd/compile/internal/deadcode"
"cmd/compile/internal/ir"
"cmd/compile/internal/objw"
"cmd/compile/internal/staticinit"
"cmd/compile/internal/typecheck"
"cmd/compile/internal/types"
"cmd/internal/obj"
"cmd/internal/src"
)
// MakeInit creates a synthetic init function to handle any
// package-scope initialization statements.
//
// TODO(mdempsky): Move into noder, so that the types2-based frontends
// can use Info.InitOrder instead.
func MakeInit() {
nf := initOrder(typecheck.Target.Decls)
if len(nf) == 0 {
return
}
// Make a function that contains all the initialization statements.
base.Pos = nf[0].Pos() // prolog/epilog gets line number of first init stmt
initializers := typecheck.Lookup("init")
fn := typecheck.DeclFunc(initializers, ir.NewFuncType(base.Pos, nil, nil, nil))
for _, dcl := range typecheck.InitTodoFunc.Dcl {
dcl.Curfn = fn
}
fn.Dcl = append(fn.Dcl, typecheck.InitTodoFunc.Dcl...)
typecheck.InitTodoFunc.Dcl = nil
// Suppress useless "can inline" diagnostics.
// Init functions are only called dynamically.
fn.SetInlinabilityChecked(true)
fn.Body = nf
typecheck.FinishFuncBody()
typecheck.Func(fn)
ir.WithFunc(fn, func() {
typecheck.Stmts(nf)
})
typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
// Prepend to Inits, so it runs first, before any user-declared init
// functions.
typecheck.Target.Inits = append([]*ir.Func{fn}, typecheck.Target.Inits...)
if typecheck.InitTodoFunc.Dcl != nil {
// We only generate temps using InitTodoFunc if there
// are package-scope initialization statements, so
// something's weird if we get here.
base.Fatalf("InitTodoFunc still has declarations")
}
typecheck.InitTodoFunc = nil
}
// Task makes and returns an initialization record for the package.
// See runtime/proc.go:initTask for its layout.
// The 3 tasks for initialization are:
@ -21,8 +69,6 @@ import (
// 2) Initialize all the variables that have initializers.
// 3) Run any init functions.
func Task() *ir.Name {
nf := initOrder(typecheck.Target.Decls)
var deps []*obj.LSym // initTask records for packages the current package depends on
var fns []*obj.LSym // functions to call for package initialization
@ -38,39 +84,28 @@ func Task() *ir.Name {
deps = append(deps, n.(*ir.Name).Linksym())
}
// Make a function that contains all the initialization statements.
if len(nf) > 0 {
base.Pos = nf[0].Pos() // prolog/epilog gets line number of first init stmt
initializers := typecheck.Lookup("init")
fn := typecheck.DeclFunc(initializers, ir.NewFuncType(base.Pos, nil, nil, nil))
for _, dcl := range typecheck.InitTodoFunc.Dcl {
dcl.Curfn = fn
}
fn.Dcl = append(fn.Dcl, typecheck.InitTodoFunc.Dcl...)
typecheck.InitTodoFunc.Dcl = nil
fn.Body = nf
typecheck.FinishFuncBody()
typecheck.Func(fn)
ir.CurFunc = fn
typecheck.Stmts(nf)
ir.CurFunc = nil
typecheck.Target.Decls = append(typecheck.Target.Decls, fn)
fns = append(fns, fn.Linksym())
}
if typecheck.InitTodoFunc.Dcl != nil {
// We only generate temps using InitTodoFunc if there
// are package-scope initialization statements, so
// something's weird if we get here.
base.Fatalf("InitTodoFunc still has declarations")
}
typecheck.InitTodoFunc = nil
// Record user init functions.
for _, fn := range typecheck.Target.Inits {
// Must happen after initOrder; see #43444.
deadcode.Func(fn)
if fn.Sym().Name == "init" {
// Synthetic init function for initialization of package-scope
// variables. We can use staticinit to optimize away static
// assignments.
s := staticinit.Schedule{
Plans: make(map[ir.Node]*staticinit.Plan),
Temps: make(map[ir.Node]*ir.Name),
}
for _, n := range fn.Body {
s.StaticInit(n)
}
fn.Body = s.Out
ir.WithFunc(fn, func() {
typecheck.Stmts(fn.Body)
})
if len(fn.Body) == 0 {
fn.Body = []ir.Node{ir.NewBlockStmt(src.NoXPos, nil)}
}
}
// Skip init functions with empty bodies.
if len(fn.Body) == 1 {

View File

@ -11,7 +11,6 @@ import (
"cmd/compile/internal/base"
"cmd/compile/internal/ir"
"cmd/compile/internal/staticinit"
)
// Package initialization
@ -78,10 +77,7 @@ type InitOrder struct {
// corresponding list of statements to include in the init() function
// body.
func initOrder(l []ir.Node) []ir.Node {
s := staticinit.Schedule{
Plans: make(map[ir.Node]*staticinit.Plan),
Temps: make(map[ir.Node]*ir.Name),
}
var res ir.Nodes
o := InitOrder{
blocking: make(map[ir.Node][]ir.Node),
order: make(map[ir.Node]int),
@ -92,7 +88,7 @@ func initOrder(l []ir.Node) []ir.Node {
switch n.Op() {
case ir.OAS, ir.OAS2DOTTYPE, ir.OAS2FUNC, ir.OAS2MAPR, ir.OAS2RECV:
o.processAssign(n)
o.flushReady(s.StaticInit)
o.flushReady(func(n ir.Node) { res.Append(n) })
case ir.ODCLCONST, ir.ODCLFUNC, ir.ODCLTYPE:
// nop
default:
@ -125,7 +121,7 @@ func initOrder(l []ir.Node) []ir.Node {
base.Fatalf("expected empty map: %v", o.blocking)
}
return s.Out
return res
}
func (o *InitOrder) processAssign(n ir.Node) {