Skip to content

Commit d949d0b

Browse files
committed
cmd/compile: reorganize init functions
Instead of writing an init function per package that does the same thing for every package, just write that implementation once in the runtime. Change the compiler to generate a data structure that encodes the required initialization operations. Reduces cmd/go binary size by 0.3%+. Most of the init code is gone, including all the corresponding stack map info. The .inittask structures that replace them are quite a bit smaller. Most usefully to me, there is no longer an init function in every -S output. (There is an .inittask global there, but it's much less distracting.) After this CL we could change the name of the "init.ializers" function back to just "init". Update #6853 R=go1.13 Change-Id: Iec82b205cc52fe3ade4d36406933c97dbc9c01b1 Reviewed-on: https://go-review.googlesource.com/c/go/+/161337 Run-TryBot: Keith Randall <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Josh Bleecher Snyder <[email protected]>
1 parent 991c85a commit d949d0b

File tree

8 files changed

+98
-220
lines changed

8 files changed

+98
-220
lines changed

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

Lines changed: 43 additions & 193 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ package gc
66

77
import (
88
"cmd/compile/internal/types"
9+
"cmd/internal/obj"
910
)
1011

1112
// A function named init is a special case.
@@ -23,77 +24,29 @@ func renameinit() *types.Sym {
2324
return s
2425
}
2526

26-
// anyinit reports whether there any interesting init statements.
27-
func anyinit(n []*Node) bool {
28-
for _, ln := range n {
29-
switch ln.Op {
30-
case ODCLFUNC, ODCLCONST, ODCLTYPE, OEMPTY:
31-
case OAS:
32-
if !ln.Left.isBlank() || !candiscard(ln.Right) {
33-
return true
34-
}
35-
default:
36-
return true
37-
}
38-
}
39-
40-
// is this main
41-
if localpkg.Name == "main" {
42-
return true
43-
}
27+
// fninit makes an initialization record for the package.
28+
// See runtime/proc.go:initTask for its layout.
29+
// The 3 tasks for initialization are:
30+
// 1) Initialize all of the packages the current package depends on.
31+
// 2) Initialize all the variables that have initializers.
32+
// 3) Run any init functions.
33+
func fninit(n []*Node) {
34+
nf := initfix(n)
4435

45-
// is there an explicit init function
46-
if renameinitgen > 0 {
47-
return true
48-
}
36+
var deps []*obj.LSym // initTask records for packages the current package depends on
37+
var fns []*obj.LSym // functions to call for package initialization
4938

50-
// are there any imported init functions
51-
for _, s := range types.InitSyms {
52-
if s.Def != nil {
53-
return true
39+
// Find imported packages with init tasks.
40+
for _, p := range types.ImportedPkgList() {
41+
if s, ok := p.LookupOK(".inittask"); ok {
42+
deps = append(deps, s.Linksym())
5443
}
5544
}
5645

57-
// then none
58-
return false
59-
}
60-
61-
// fninit hand-crafts package initialization code.
62-
//
63-
// func init.ializers() { (0)
64-
// <init stmts>
65-
// }
66-
// var initdone· uint8 (1)
67-
// func init() { (2)
68-
// if initdone· > 1 { (3)
69-
// return (3a)
70-
// }
71-
// if initdone· == 1 { (4)
72-
// throw() (4a)
73-
// }
74-
// initdone· = 1 (5)
75-
// // over all matching imported symbols
76-
// <pkg>.init() (6)
77-
// init.ializers() (7)
78-
// init.<n>() // if any (8)
79-
// initdone· = 2 (9)
80-
// return (10)
81-
// }
82-
func fninit(n []*Node) {
83-
lineno = autogeneratedPos
84-
nf := initfix(n)
85-
if !anyinit(nf) {
86-
return
87-
}
88-
89-
// (0)
9046
// Make a function that contains all the initialization statements.
91-
// This is a separate function because we want it to appear in
92-
// stack traces, where the init function itself does not.
93-
var initializers *types.Sym
9447
if len(nf) > 0 {
9548
lineno = nf[0].Pos // prolog/epilog gets line number of first init stmt
96-
initializers = lookup("init.ializers")
49+
initializers := lookup("init.ializers")
9750
disableExport(initializers)
9851
fn := dclfunc(initializers, nod(OTFUNC, nil, nil))
9952
for _, dcl := range dummyInitFn.Func.Dcl {
@@ -110,7 +63,7 @@ func fninit(n []*Node) {
11063
typecheckslice(nf, ctxStmt)
11164
Curfn = nil
11265
funccompile(fn)
113-
lineno = autogeneratedPos
66+
fns = append(fns, initializers.Linksym())
11467
}
11568
if dummyInitFn.Func.Dcl != nil {
11669
// We only generate temps using dummyInitFn if there
@@ -119,140 +72,37 @@ func fninit(n []*Node) {
11972
Fatalf("dummyInitFn still has declarations")
12073
}
12174

122-
var r []*Node
123-
124-
// (1)
125-
gatevar := newname(lookup("initdone·"))
126-
addvar(gatevar, types.Types[TUINT8], PEXTERN)
127-
128-
// (2)
129-
initsym := lookup("init")
130-
fn := dclfunc(initsym, nod(OTFUNC, nil, nil))
131-
132-
// (3)
133-
a := nod(OIF, nil, nil)
134-
a.Left = nod(OGT, gatevar, nodintconst(1))
135-
a.SetLikely(true)
136-
r = append(r, a)
137-
// (3a)
138-
a.Nbody.Set1(nod(ORETURN, nil, nil))
139-
140-
// (4)
141-
b := nod(OIF, nil, nil)
142-
b.Left = nod(OEQ, gatevar, nodintconst(1))
143-
// this actually isn't likely, but code layout is better
144-
// like this: no JMP needed after the call.
145-
b.SetLikely(true)
146-
r = append(r, b)
147-
// (4a)
148-
b.Nbody.Set1(nod(OCALL, syslook("throwinit"), nil))
149-
150-
// (5)
151-
a = nod(OAS, gatevar, nodintconst(1))
152-
153-
r = append(r, a)
154-
155-
// (6)
156-
for _, s := range types.InitSyms {
157-
if s == initsym {
158-
continue
159-
}
160-
n := resolve(oldname(s))
161-
if n.Op == ONONAME {
162-
// No package-scope init function; just a
163-
// local variable, field name, or something.
164-
continue
165-
}
166-
n.checkInitFuncSignature()
167-
a = nod(OCALL, n, nil)
168-
r = append(r, a)
75+
// Record user init functions.
76+
for i := 0; i < renameinitgen; i++ {
77+
s := lookupN("init.", i)
78+
fns = append(fns, s.Linksym())
16979
}
17080

171-
// (7)
172-
if initializers != nil {
173-
n := newname(initializers)
174-
addvar(n, functype(nil, nil, nil), PFUNC)
175-
r = append(r, nod(OCALL, n, nil))
81+
if len(deps) == 0 && len(fns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" {
82+
return // nothing to initialize
17683
}
17784

178-
// (8)
179-
180-
// maxInlineInitCalls is the threshold at which we switch
181-
// from generating calls inline to generating a static array
182-
// of functions and calling them in a loop.
183-
// See CL 41500 for more discussion.
184-
const maxInlineInitCalls = 500
185-
186-
if renameinitgen < maxInlineInitCalls {
187-
// Not many init functions. Just call them all directly.
188-
for i := 0; i < renameinitgen; i++ {
189-
s := lookupN("init.", i)
190-
n := asNode(s.Def)
191-
n.checkInitFuncSignature()
192-
a = nod(OCALL, n, nil)
193-
r = append(r, a)
194-
}
195-
} else {
196-
// Lots of init functions.
197-
// Set up an array of functions and loop to call them.
198-
// This is faster to compile and similar at runtime.
199-
200-
// Build type [renameinitgen]func().
201-
typ := types.NewArray(functype(nil, nil, nil), int64(renameinitgen))
202-
203-
// Make and fill array.
204-
fnarr := staticname(typ)
205-
fnarr.Name.SetReadonly(true)
206-
for i := 0; i < renameinitgen; i++ {
207-
s := lookupN("init.", i)
208-
lhs := nod(OINDEX, fnarr, nodintconst(int64(i)))
209-
rhs := asNode(s.Def)
210-
rhs.checkInitFuncSignature()
211-
as := nod(OAS, lhs, rhs)
212-
as = typecheck(as, ctxStmt)
213-
genAsStatic(as)
214-
}
215-
216-
// Generate a loop that calls each function in turn.
217-
// for i := 0; i < renameinitgen; i++ {
218-
// fnarr[i]()
219-
// }
220-
i := temp(types.Types[TINT])
221-
fnidx := nod(OINDEX, fnarr, i)
222-
fnidx.SetBounded(true)
223-
224-
zero := nod(OAS, i, nodintconst(0))
225-
cond := nod(OLT, i, nodintconst(int64(renameinitgen)))
226-
incr := nod(OAS, i, nod(OADD, i, nodintconst(1)))
227-
body := nod(OCALL, fnidx, nil)
228-
229-
loop := nod(OFOR, cond, incr)
230-
loop.Nbody.Set1(body)
231-
loop.Ninit.Set1(zero)
232-
233-
loop = typecheck(loop, ctxStmt)
234-
r = append(r, loop)
85+
// Make an .inittask structure.
86+
sym := lookup(".inittask")
87+
nn := newname(sym)
88+
nn.Type = types.Types[TUINT8] // dummy type
89+
nn.SetClass(PEXTERN)
90+
sym.Def = asTypesNode(nn)
91+
exportsym(nn)
92+
lsym := sym.Linksym()
93+
ot := 0
94+
ot = duintptr(lsym, ot, 0) // state: not initialized yet
95+
ot = duintptr(lsym, ot, uint64(len(deps)))
96+
ot = duintptr(lsym, ot, uint64(len(fns)))
97+
for _, d := range deps {
98+
ot = dsymptr(lsym, ot, d, 0)
23599
}
236-
237-
// (9)
238-
a = nod(OAS, gatevar, nodintconst(2))
239-
240-
r = append(r, a)
241-
242-
// (10)
243-
a = nod(ORETURN, nil, nil)
244-
245-
r = append(r, a)
246-
exportsym(fn.Func.Nname)
247-
248-
fn.Nbody.Set(r)
249-
funcbody()
250-
251-
Curfn = fn
252-
fn = typecheck(fn, ctxStmt)
253-
typecheckslice(r, ctxStmt)
254-
Curfn = nil
255-
funccompile(fn)
100+
for _, f := range fns {
101+
ot = dsymptr(lsym, ot, f, 0)
102+
}
103+
// An initTask has pointers, but none into the Go heap.
104+
// It's not quite read only, the state field must be modifiable.
105+
ggloblsym(lsym, int32(ot), obj.NOPTR)
256106
}
257107

258108
func (n *Node) checkInitFuncSignature() {

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ func relocsym(ctxt *Link, s *sym.Symbol) {
148148
// When putting the runtime but not main into a shared library
149149
// these symbols are undefined and that's OK.
150150
if ctxt.BuildMode == BuildModeShared {
151-
if r.Sym.Name == "main.main" || r.Sym.Name == "main.init" {
151+
if r.Sym.Name == "main.main" || r.Sym.Name == "main..inittask" {
152152
r.Sym.Type = sym.SDYNIMPORT
153153
} else if strings.HasPrefix(r.Sym.Name, "go.info.") {
154154
// Skip go.info symbols. They are only needed to communicate

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,7 @@ func (d *deadcodepass) init() {
222222
// functions and mark what is reachable from there.
223223

224224
if d.ctxt.linkShared && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) {
225-
names = append(names, "main.main", "main.init")
225+
names = append(names, "main.main", "main..inittask")
226226
} else {
227227
// The external linker refers main symbol directly.
228228
if d.ctxt.LinkMode == LinkExternal && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) {
@@ -234,7 +234,7 @@ func (d *deadcodepass) init() {
234234
}
235235
names = append(names, *flagEntrySymbol)
236236
if d.ctxt.BuildMode == BuildModePlugin {
237-
names = append(names, objabi.PathToPrefix(*flagPluginPath)+".init", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs")
237+
names = append(names, objabi.PathToPrefix(*flagPluginPath)+"..inittask", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs")
238238

239239
// We don't keep the go.plugin.exports symbol,
240240
// but we do keep the symbols it refers to.

src/plugin/plugin_dlopen.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,15 +92,13 @@ func open(name string) (*Plugin, error) {
9292
plugins[filepath] = p
9393
pluginsMu.Unlock()
9494

95-
initStr := make([]byte, len(pluginpath)+6)
95+
initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL
9696
copy(initStr, pluginpath)
97-
copy(initStr[len(pluginpath):], ".init")
97+
copy(initStr[len(pluginpath):], "..inittask")
9898

99-
initFuncPC := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
100-
if initFuncPC != nil {
101-
initFuncP := &initFuncPC
102-
initFunc := *(*func())(unsafe.Pointer(&initFuncP))
103-
initFunc()
99+
initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr)
100+
if initTask != nil {
101+
doInit(initTask)
104102
}
105103

106104
// Fill out the value of each plugin symbol.
@@ -150,3 +148,7 @@ var (
150148

151149
// lastmoduleinit is defined in package runtime
152150
func lastmoduleinit() (pluginpath string, syms map[string]interface{}, errstr string)
151+
152+
// doInit is defined in package runtime
153+
//go:linkname doInit runtime.doInit
154+
func doInit(t unsafe.Pointer) // t should be a *runtime.initTask

src/runtime/panic.go

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -183,10 +183,6 @@ func panicmem() {
183183
panic(memoryError)
184184
}
185185

186-
func throwinit() {
187-
throw("recursive call during initialization - linker skew")
188-
}
189-
190186
// Create a new deferred function fn with siz bytes of arguments.
191187
// The compiler turns a defer statement into a call to this.
192188
//go:nosplit

0 commit comments

Comments
 (0)