Skip to content

Commit 2b92c39

Browse files
committed
cmd/link: establish dependable package initialization order
(This is a retry of CL 462035 which was reverted at 474976. The only change from that CL is the aix fix SRODATA->SNOPTRDATA at inittask.go:141) As described here: #31636 (comment) "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: I28c09451d6aa677d7394c179d23c2c02c503fc56 Reviewed-on: https://go-review.googlesource.com/c/go/+/478916 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>
1 parent d4bcfe4 commit 2b92c39

File tree

14 files changed

+324
-56
lines changed

14 files changed

+324
-56
lines changed

src/cmd/compile/internal/pkginit/init.go

+12-6
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"cmd/compile/internal/typecheck"
1414
"cmd/compile/internal/types"
1515
"cmd/internal/obj"
16+
"cmd/internal/objabi"
1617
"cmd/internal/src"
1718
"fmt"
1819
"os"
@@ -201,15 +202,20 @@ func Task() *ir.Name {
201202
sym.Def = task
202203
lsym := task.Linksym()
203204
ot := 0
204-
ot = objw.Uintptr(lsym, ot, 0) // state: not initialized yet
205-
ot = objw.Uintptr(lsym, ot, uint64(len(deps)))
206-
ot = objw.Uintptr(lsym, ot, uint64(len(fns)))
207-
for _, d := range deps {
208-
ot = objw.SymPtr(lsym, ot, d, 0)
209-
}
205+
ot = objw.Uint32(lsym, ot, 0) // state: not initialized yet
206+
ot = objw.Uint32(lsym, ot, uint32(len(fns)))
210207
for _, f := range fns {
211208
ot = objw.SymPtr(lsym, ot, f, 0)
212209
}
210+
211+
// Add relocations which tell the linker all of the packages
212+
// that this package depends on (and thus, all of the packages
213+
// that need to be initialized before this one).
214+
for _, d := range deps {
215+
r := obj.Addrel(lsym)
216+
r.Type = objabi.R_INITORDER
217+
r.Sym = d
218+
}
213219
// An initTask has pointers, but none into the Go heap.
214220
// It's not quite read only, the state field must be modifiable.
215221
objw.Global(lsym, int32(ot), obj.NOPTR)

src/cmd/internal/objabi/reloctype.go

+6
Original file line numberDiff line numberDiff line change
@@ -342,6 +342,12 @@ const (
342342
// the executable file is mapped in memory.
343343
R_PEIMAGEOFF
344344

345+
// R_INITORDER specifies an ordering edge between two inittask records.
346+
// (From one p..inittask record to another one.)
347+
// This relocation does not apply any changes to the actual data, it is
348+
// just used in the linker to order the inittask records appropriately.
349+
R_INITORDER
350+
345351
// R_WEAK marks the relocation as a weak reference.
346352
// A weak relocation does not make the symbol it refers to reachable,
347353
// and is only honored by the linker if the symbol is in some other way

src/cmd/link/internal/ld/deadcode.go

+8
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,9 @@ func (d *deadcodePass) init() {
113113
if d.mapinitnoop == 0 {
114114
panic("could not look up runtime.mapinitnoop")
115115
}
116+
if d.ctxt.mainInittasks != 0 {
117+
d.mark(d.ctxt.mainInittasks, 0)
118+
}
116119
}
117120

118121
func (d *deadcodePass) flood() {
@@ -208,6 +211,11 @@ func (d *deadcodePass) flood() {
208211
}
209212
d.genericIfaceMethod[name] = true
210213
continue // don't mark referenced symbol - it is not needed in the final binary.
214+
case objabi.R_INITORDER:
215+
// inittasks has already run, so any R_INITORDER links are now
216+
// superfluous - the only live inittask records are those which are
217+
// in a scheduled list somewhere (e.g. runtime.moduledata.inittasks).
218+
continue
211219
}
212220
rs := r.Sym()
213221
if isgotype && usedInIface && d.ldr.IsGoType(rs) && !d.ldr.AttrUsedInIface(rs) {

src/cmd/link/internal/ld/heap.go

+47
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,50 @@ func (h *heap) pop() loader.Sym {
5252
}
5353

5454
func (h *heap) empty() bool { return len(*h) == 0 }
55+
56+
// Same as heap, but sorts alphabetically instead of by index.
57+
// (Note that performance is not so critical here, as it is
58+
// in the case above. Some simplification might be in order.)
59+
type lexHeap []loader.Sym
60+
61+
func (h *lexHeap) push(ldr *loader.Loader, s loader.Sym) {
62+
*h = append(*h, s)
63+
// sift up
64+
n := len(*h) - 1
65+
for n > 0 {
66+
p := (n - 1) / 2 // parent
67+
if ldr.SymName((*h)[p]) <= ldr.SymName((*h)[n]) {
68+
break
69+
}
70+
(*h)[n], (*h)[p] = (*h)[p], (*h)[n]
71+
n = p
72+
}
73+
}
74+
75+
func (h *lexHeap) pop(ldr *loader.Loader) loader.Sym {
76+
r := (*h)[0]
77+
n := len(*h) - 1
78+
(*h)[0] = (*h)[n]
79+
*h = (*h)[:n]
80+
81+
// sift down
82+
i := 0
83+
for {
84+
c := 2*i + 1 // left child
85+
if c >= n {
86+
break
87+
}
88+
if c1 := c + 1; c1 < n && ldr.SymName((*h)[c1]) < ldr.SymName((*h)[c]) {
89+
c = c1 // right child
90+
}
91+
if ldr.SymName((*h)[i]) <= ldr.SymName((*h)[c]) {
92+
break
93+
}
94+
(*h)[i], (*h)[c] = (*h)[c], (*h)[i]
95+
i = c
96+
}
97+
98+
return r
99+
}
100+
101+
func (h *lexHeap) empty() bool { return len(*h) == 0 }

src/cmd/link/internal/ld/inittask.go

+175
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
// Copyright 2022 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package ld
6+
7+
import (
8+
"cmd/internal/objabi"
9+
"cmd/link/internal/loader"
10+
"cmd/link/internal/sym"
11+
"fmt"
12+
"sort"
13+
)
14+
15+
// Inittasks finds inittask records, figures out a good
16+
// order to execute them in, and emits that order for the
17+
// runtime to use.
18+
//
19+
// An inittask represents the initialization code that needs
20+
// to be run for a package. For package p, the p..inittask
21+
// symbol contains a list of init functions to run, both
22+
// explicit user init functions and implicit compiler-generated
23+
// init functions for initializing global variables like maps.
24+
//
25+
// In addition, inittask records have dependencies between each
26+
// other, mirroring the import dependencies. So if package p
27+
// imports package q, then there will be a dependency p -> q.
28+
// We can't initialize package p until after package q has
29+
// already been initialized.
30+
//
31+
// Package dependencies are encoded with relocations. If package
32+
// p imports package q, then package p's inittask record will
33+
// have a R_INITORDER relocation pointing to package q's inittask
34+
// record. See cmd/compile/internal/pkginit/init.go.
35+
//
36+
// This function computes an ordering of all of the inittask
37+
// records so that the order respects all the dependencies,
38+
// and given that restriction, orders the inittasks in
39+
// lexicographic order.
40+
func (ctxt *Link) inittasks() {
41+
switch ctxt.BuildMode {
42+
case BuildModeExe, BuildModePIE, BuildModeCArchive, BuildModeCShared:
43+
// Normally the inittask list will be run on program startup.
44+
ctxt.mainInittasks = ctxt.inittaskSym("main..inittask", "go:main.inittasks")
45+
case BuildModePlugin:
46+
// For plugins, the list will be run on plugin load.
47+
ctxt.mainInittasks = ctxt.inittaskSym(fmt.Sprintf("%s..inittask", objabi.PathToPrefix(*flagPluginPath)), "go:plugin.inittasks")
48+
// Make symbol local so multiple plugins don't clobber each other's inittask list.
49+
ctxt.loader.SetAttrLocal(ctxt.mainInittasks, true)
50+
case BuildModeShared:
51+
// Nothing to do. The inittask list will be built by
52+
// the final build (with the -linkshared option).
53+
default:
54+
Exitf("unhandled build mode %d", ctxt.BuildMode)
55+
}
56+
57+
// If the runtime is one of the packages we are building,
58+
// initialize the runtime_inittasks variable.
59+
ldr := ctxt.loader
60+
if ldr.Lookup("runtime.runtime_inittasks", 0) != 0 {
61+
t := ctxt.inittaskSym("runtime..inittask", "go:runtime.inittasks")
62+
63+
// This slice header is already defined in runtime/proc.go, so we update it here with new contents.
64+
sh := ldr.Lookup("runtime.runtime_inittasks", 0)
65+
sb := ldr.MakeSymbolUpdater(sh)
66+
sb.SetSize(0)
67+
sb.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see issue 58857.
68+
sb.AddAddr(ctxt.Arch, t)
69+
sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize)))
70+
sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize)))
71+
}
72+
}
73+
74+
// inittaskSym builds a symbol containing pointers to all the inittasks
75+
// that need to be run, given the root inittask symbol.
76+
func (ctxt *Link) inittaskSym(rootName, symName string) loader.Sym {
77+
ldr := ctxt.loader
78+
root := ldr.Lookup(rootName, 0)
79+
if root == 0 {
80+
// Nothing to do
81+
return 0
82+
}
83+
84+
// Edges record dependencies between packages.
85+
// {from,to} is in edges if from's package imports to's package.
86+
// This list is used to implement reverse edge lookups.
87+
type edge struct {
88+
from, to loader.Sym
89+
}
90+
var edges []edge
91+
92+
// List of packages that are ready to schedule. We use a lexicographic
93+
// ordered heap to pick the lexically earliest uninitialized but
94+
// inititalizeable package at each step.
95+
var h lexHeap
96+
97+
// m maps from an inittask symbol for package p to the number of
98+
// p's direct imports that have not yet been scheduled.
99+
m := map[loader.Sym]int{}
100+
101+
// Find all reachable inittask records from the root.
102+
// Keep track of the dependency edges between them in edges.
103+
// Keep track of how many imports each package has in m.
104+
// q is the list of found but not yet explored packages.
105+
var q []loader.Sym
106+
m[root] = 0
107+
q = append(q, root)
108+
for len(q) > 0 {
109+
x := q[len(q)-1]
110+
q = q[:len(q)-1]
111+
relocs := ldr.Relocs(x)
112+
n := relocs.Count()
113+
ndeps := 0
114+
for i := 0; i < n; i++ {
115+
r := relocs.At(i)
116+
if r.Type() != objabi.R_INITORDER {
117+
continue
118+
}
119+
ndeps++
120+
s := r.Sym()
121+
edges = append(edges, edge{from: x, to: s})
122+
if _, ok := m[s]; ok {
123+
continue // already found
124+
}
125+
q = append(q, s)
126+
m[s] = 0 // mark as found
127+
}
128+
m[x] = ndeps
129+
if ndeps == 0 {
130+
h.push(ldr, x)
131+
}
132+
}
133+
134+
// Sort edges so we can look them up by edge destination.
135+
sort.Slice(edges, func(i, j int) bool {
136+
return edges[i].to < edges[j].to
137+
})
138+
139+
// Figure out the schedule.
140+
sched := ldr.MakeSymbolBuilder(symName)
141+
sched.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see isue 58857.
142+
for !h.empty() {
143+
// Pick the lexicographically first initializable package.
144+
s := h.pop(ldr)
145+
146+
// Add s to the schedule.
147+
if ldr.SymSize(s) > 8 {
148+
// Note: don't add s if it has no functions to run. We need
149+
// s during linking to compute an ordering, but the runtime
150+
// doesn't need to know about it. About 1/2 of stdlib packages
151+
// fit in this bucket.
152+
sched.AddAddr(ctxt.Arch, s)
153+
}
154+
155+
// Find all incoming edges into s.
156+
a := sort.Search(len(edges), func(i int) bool { return edges[i].to >= s })
157+
b := sort.Search(len(edges), func(i int) bool { return edges[i].to > s })
158+
159+
// Decrement the import count for all packages that import s.
160+
// If the count reaches 0, that package is now ready to schedule.
161+
for _, e := range edges[a:b] {
162+
m[e.from]--
163+
if m[e.from] == 0 {
164+
h.push(ldr, e.from)
165+
}
166+
}
167+
}
168+
169+
for s, n := range m {
170+
if n != 0 {
171+
Exitf("inittask for %s is not schedulable %d", ldr.SymName(s), n)
172+
}
173+
}
174+
return sched.Sym()
175+
}

src/cmd/link/internal/ld/lib.go

+4
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,10 @@ type ArchSyms struct {
119119
DynStr loader.Sym
120120

121121
unreachableMethod loader.Sym
122+
123+
// Symbol containing a list of all the inittasks that need
124+
// to be run at startup.
125+
mainInittasks loader.Sym
122126
}
123127

124128
// mkArchSym is a helper for setArchSyms, to set up a special symbol.

src/cmd/link/internal/ld/main.go

+3
Original file line numberDiff line numberDiff line change
@@ -276,6 +276,9 @@ func Main(arch *sys.Arch, theArch Arch) {
276276
bench.Start("loadlib")
277277
ctxt.loadlib()
278278

279+
bench.Start("inittasks")
280+
ctxt.inittasks()
281+
279282
bench.Start("deadcode")
280283
deadcode(ctxt)
281284

src/cmd/link/internal/ld/symtab.go

+16
Original file line numberDiff line numberDiff line change
@@ -765,6 +765,22 @@ func (ctxt *Link) symtab(pcln *pclntab) []sym.SymKind {
765765
moduledata.AddUint(ctxt.Arch, 0)
766766
moduledata.AddUint(ctxt.Arch, 0)
767767
}
768+
// Add inittasks slice
769+
t := ctxt.mainInittasks
770+
if t != 0 {
771+
moduledata.AddAddr(ctxt.Arch, t)
772+
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize)))
773+
moduledata.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize)))
774+
} else {
775+
// Some build modes have no inittasks, like a shared library.
776+
// Its inittask list will be constructed by a higher-level
777+
// linking step.
778+
// This branch can also happen if there are no init tasks at all.
779+
moduledata.AddUint(ctxt.Arch, 0)
780+
moduledata.AddUint(ctxt.Arch, 0)
781+
moduledata.AddUint(ctxt.Arch, 0)
782+
}
783+
768784
if len(ctxt.Shlibs) > 0 {
769785
thismodulename := filepath.Base(*flagOutfile)
770786
switch ctxt.BuildMode {

src/plugin/plugin_dlopen.go

+9-11
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ func open(name string) (*Plugin, error) {
7474
if plugins == nil {
7575
plugins = make(map[string]*Plugin)
7676
}
77-
pluginpath, syms, errstr := lastmoduleinit()
77+
pluginpath, syms, initTasks, errstr := lastmoduleinit()
7878
if errstr != "" {
7979
plugins[filepath] = &Plugin{
8080
pluginpath: pluginpath,
@@ -92,14 +92,7 @@ func open(name string) (*Plugin, error) {
9292
plugins[filepath] = p
9393
pluginsMu.Unlock()
9494

95-
initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL
96-
copy(initStr, pluginpath)
97-
copy(initStr[len(pluginpath):], "..inittask")
98-
99-
initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
100-
if initTask != nil {
101-
doInit(initTask)
102-
}
95+
doInit(initTasks)
10396

10497
// Fill out the value of each plugin symbol.
10598
updatedSyms := map[string]any{}
@@ -147,9 +140,14 @@ var (
147140
)
148141

149142
// lastmoduleinit is defined in package runtime.
150-
func lastmoduleinit() (pluginpath string, syms map[string]any, errstr string)
143+
func lastmoduleinit() (pluginpath string, syms map[string]any, inittasks []*initTask, errstr string)
151144

152145
// doInit is defined in package runtime.
153146
//
154147
//go:linkname doInit runtime.doInit
155-
func doInit(t unsafe.Pointer) // t should be a *runtime.initTask
148+
func doInit(t []*initTask)
149+
150+
type initTask struct {
151+
// fields defined in runtime.initTask. We only handle pointers to an initTask
152+
// in this package, so the contents are irrelevant.
153+
}

0 commit comments

Comments
 (0)