cmd/link: establish dependable package initialization order

As described here:

https://github.com/golang/go/issues/31636#issuecomment-493271830

"Find the lexically earliest package that is not initialized yet,
but has had all its dependencies initialized, initialize that package,
 and repeat."

Simplify the runtime a bit, by just computing the ordering required
in the linker and giving a list to the runtime.

Update #31636
Fixes #57411

RELNOTE=yes

Change-Id: I1e4d3878ebe6e8953527aedb730824971d722cac
Reviewed-on: https://go-review.googlesource.com/c/go/+/462035
Reviewed-by: Than McIntosh <thanm@google.com>
Reviewed-by: Cherry Mui <cherryyz@google.com>
Run-TryBot: Keith Randall <khr@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
This commit is contained in:
Keith Randall 2023-01-12 20:25:39 -08:00
parent cd6d225bd3
commit ce2a609909
14 changed files with 324 additions and 56 deletions

View file

@ -117,11 +117,9 @@ var (
raceprocctx0 uintptr
)
//go:linkname runtime_inittask runtime..inittask
var runtime_inittask initTask
//go:linkname main_inittask main..inittask
var main_inittask initTask
// This slice records the initializing tasks that need to be
// done to start up the runtime. It is built by the linker.
var runtime_inittasks []*initTask
// main_init_done is a signal used by cgocallbackg that initialization
// has been completed. It is made before _cgo_notify_runtime_init_done,
@ -196,7 +194,7 @@ func main() {
inittrace.active = true
}
doInit(&runtime_inittask) // Must be before defer.
doInit(runtime_inittasks) // Must be before defer.
// Defer unlock so that runtime.Goexit during init does the unlock too.
needUnlock := true
@ -230,7 +228,14 @@ func main() {
cgocall(_cgo_notify_runtime_init_done, nil)
}
doInit(&main_inittask)
// Run the initializing tasks. Depending on build mode this
// list can arrive a few different ways, but it will always
// contain the init tasks computed by the linker for all the
// packages in the program (excluding those added at runtime
// by package plugin).
for _, m := range activeModules() {
doInit(m.inittasks)
}
// Disable init tracing after main init done to avoid overhead
// of collecting statistics in malloc and newproc
@ -6437,14 +6442,11 @@ func gcd(a, b uint32) uint32 {
}
// An initTask represents the set of initializations that need to be done for a package.
// Keep in sync with ../../test/initempty.go:initTask
// Keep in sync with ../../test/noinit.go:initTask
type initTask struct {
// TODO: pack the first 3 fields more tightly?
state uintptr // 0 = uninitialized, 1 = in progress, 2 = done
ndeps uintptr
nfns uintptr
// followed by ndeps instances of an *initTask, one per package depended on
// followed by nfns pcs, one per init function to run
state uint32 // 0 = uninitialized, 1 = in progress, 2 = done
nfns uint32
// followed by nfns pcs, uintptr sized, one per init function to run
}
// inittrace stores statistics for init functions which are
@ -6458,7 +6460,13 @@ type tracestat struct {
bytes uint64 // heap allocated bytes
}
func doInit(t *initTask) {
func doInit(ts []*initTask) {
for _, t := range ts {
doInit1(t)
}
}
func doInit1(t *initTask) {
switch t.state {
case 2: // fully initialized
return
@ -6467,17 +6475,6 @@ func doInit(t *initTask) {
default: // not initialized yet
t.state = 1 // initialization in progress
for i := uintptr(0); i < t.ndeps; i++ {
p := add(unsafe.Pointer(t), (3+i)*goarch.PtrSize)
t2 := *(**initTask)(p)
doInit(t2)
}
if t.nfns == 0 {
t.state = 2 // initialization done
return
}
var (
start int64
before tracestat
@ -6489,9 +6486,14 @@ func doInit(t *initTask) {
before = inittrace
}
firstFunc := add(unsafe.Pointer(t), (3+t.ndeps)*goarch.PtrSize)
for i := uintptr(0); i < t.nfns; i++ {
p := add(firstFunc, i*goarch.PtrSize)
if t.nfns == 0 {
// We should have pruned all of these in the linker.
throw("inittask with no functions")
}
firstFunc := add(unsafe.Pointer(t), 8)
for i := uint32(0); i < t.nfns; i++ {
p := add(firstFunc, uintptr(i)*goarch.PtrSize)
f := *(*func())(unsafe.Pointer(&p))
f()
}