From d949d0b9252be1fffeadd65183a6bab3acf3de7a Mon Sep 17 00:00:00 2001 From: Keith Randall Date: Tue, 5 Feb 2019 16:22:38 -0800 Subject: [PATCH] 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 TryBot-Result: Gobot Gobot Reviewed-by: Josh Bleecher Snyder --- src/cmd/compile/internal/gc/init.go | 238 +++++---------------------- src/cmd/link/internal/ld/data.go | 2 +- src/cmd/link/internal/ld/deadcode.go | 4 +- src/plugin/plugin_dlopen.go | 16 +- src/runtime/panic.go | 4 - src/runtime/proc.go | 48 +++++- test/fixedbugs/issue29919.dir/a.go | 4 +- test/init.go | 4 +- 8 files changed, 99 insertions(+), 221 deletions(-) diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go index 6fd2c3427f..01421eee36 100644 --- a/src/cmd/compile/internal/gc/init.go +++ b/src/cmd/compile/internal/gc/init.go @@ -6,6 +6,7 @@ package gc import ( "cmd/compile/internal/types" + "cmd/internal/obj" ) // A function named init is a special case. @@ -23,77 +24,29 @@ func renameinit() *types.Sym { return s } -// anyinit reports whether there any interesting init statements. -func anyinit(n []*Node) bool { - for _, ln := range n { - switch ln.Op { - case ODCLFUNC, ODCLCONST, ODCLTYPE, OEMPTY: - case OAS: - if !ln.Left.isBlank() || !candiscard(ln.Right) { - return true - } - default: - return true - } - } - - // is this main - if localpkg.Name == "main" { - return true - } - - // is there an explicit init function - if renameinitgen > 0 { - return true - } - - // are there any imported init functions - for _, s := range types.InitSyms { - if s.Def != nil { - return true - } - } - - // then none - return false -} - -// fninit hand-crafts package initialization code. -// -// func init.ializers() { (0) -// -// } -// var initdone· uint8 (1) -// func init() { (2) -// if initdone· > 1 { (3) -// return (3a) -// } -// if initdone· == 1 { (4) -// throw() (4a) -// } -// initdone· = 1 (5) -// // over all matching imported symbols -// .init() (6) -// init.ializers() (7) -// init.() // if any (8) -// initdone· = 2 (9) -// return (10) -// } +// fninit makes an initialization record for the package. +// See runtime/proc.go:initTask for its layout. +// The 3 tasks for initialization are: +// 1) Initialize all of the packages the current package depends on. +// 2) Initialize all the variables that have initializers. +// 3) Run any init functions. func fninit(n []*Node) { - lineno = autogeneratedPos nf := initfix(n) - if !anyinit(nf) { - return + + var deps []*obj.LSym // initTask records for packages the current package depends on + var fns []*obj.LSym // functions to call for package initialization + + // Find imported packages with init tasks. + for _, p := range types.ImportedPkgList() { + if s, ok := p.LookupOK(".inittask"); ok { + deps = append(deps, s.Linksym()) + } } - // (0) // Make a function that contains all the initialization statements. - // This is a separate function because we want it to appear in - // stack traces, where the init function itself does not. - var initializers *types.Sym if len(nf) > 0 { lineno = nf[0].Pos // prolog/epilog gets line number of first init stmt - initializers = lookup("init.ializers") + initializers := lookup("init.ializers") disableExport(initializers) fn := dclfunc(initializers, nod(OTFUNC, nil, nil)) for _, dcl := range dummyInitFn.Func.Dcl { @@ -110,7 +63,7 @@ func fninit(n []*Node) { typecheckslice(nf, ctxStmt) Curfn = nil funccompile(fn) - lineno = autogeneratedPos + fns = append(fns, initializers.Linksym()) } if dummyInitFn.Func.Dcl != nil { // We only generate temps using dummyInitFn if there @@ -119,140 +72,37 @@ func fninit(n []*Node) { Fatalf("dummyInitFn still has declarations") } - var r []*Node - - // (1) - gatevar := newname(lookup("initdone·")) - addvar(gatevar, types.Types[TUINT8], PEXTERN) - - // (2) - initsym := lookup("init") - fn := dclfunc(initsym, nod(OTFUNC, nil, nil)) - - // (3) - a := nod(OIF, nil, nil) - a.Left = nod(OGT, gatevar, nodintconst(1)) - a.SetLikely(true) - r = append(r, a) - // (3a) - a.Nbody.Set1(nod(ORETURN, nil, nil)) - - // (4) - b := nod(OIF, nil, nil) - b.Left = nod(OEQ, gatevar, nodintconst(1)) - // this actually isn't likely, but code layout is better - // like this: no JMP needed after the call. - b.SetLikely(true) - r = append(r, b) - // (4a) - b.Nbody.Set1(nod(OCALL, syslook("throwinit"), nil)) - - // (5) - a = nod(OAS, gatevar, nodintconst(1)) - - r = append(r, a) - - // (6) - for _, s := range types.InitSyms { - if s == initsym { - continue - } - n := resolve(oldname(s)) - if n.Op == ONONAME { - // No package-scope init function; just a - // local variable, field name, or something. - continue - } - n.checkInitFuncSignature() - a = nod(OCALL, n, nil) - r = append(r, a) + // Record user init functions. + for i := 0; i < renameinitgen; i++ { + s := lookupN("init.", i) + fns = append(fns, s.Linksym()) } - // (7) - if initializers != nil { - n := newname(initializers) - addvar(n, functype(nil, nil, nil), PFUNC) - r = append(r, nod(OCALL, n, nil)) + if len(deps) == 0 && len(fns) == 0 && localpkg.Name != "main" && localpkg.Name != "runtime" { + return // nothing to initialize } - // (8) - - // maxInlineInitCalls is the threshold at which we switch - // from generating calls inline to generating a static array - // of functions and calling them in a loop. - // See CL 41500 for more discussion. - const maxInlineInitCalls = 500 - - if renameinitgen < maxInlineInitCalls { - // Not many init functions. Just call them all directly. - for i := 0; i < renameinitgen; i++ { - s := lookupN("init.", i) - n := asNode(s.Def) - n.checkInitFuncSignature() - a = nod(OCALL, n, nil) - r = append(r, a) - } - } else { - // Lots of init functions. - // Set up an array of functions and loop to call them. - // This is faster to compile and similar at runtime. - - // Build type [renameinitgen]func(). - typ := types.NewArray(functype(nil, nil, nil), int64(renameinitgen)) - - // Make and fill array. - fnarr := staticname(typ) - fnarr.Name.SetReadonly(true) - for i := 0; i < renameinitgen; i++ { - s := lookupN("init.", i) - lhs := nod(OINDEX, fnarr, nodintconst(int64(i))) - rhs := asNode(s.Def) - rhs.checkInitFuncSignature() - as := nod(OAS, lhs, rhs) - as = typecheck(as, ctxStmt) - genAsStatic(as) - } - - // Generate a loop that calls each function in turn. - // for i := 0; i < renameinitgen; i++ { - // fnarr[i]() - // } - i := temp(types.Types[TINT]) - fnidx := nod(OINDEX, fnarr, i) - fnidx.SetBounded(true) - - zero := nod(OAS, i, nodintconst(0)) - cond := nod(OLT, i, nodintconst(int64(renameinitgen))) - incr := nod(OAS, i, nod(OADD, i, nodintconst(1))) - body := nod(OCALL, fnidx, nil) - - loop := nod(OFOR, cond, incr) - loop.Nbody.Set1(body) - loop.Ninit.Set1(zero) - - loop = typecheck(loop, ctxStmt) - r = append(r, loop) + // Make an .inittask structure. + sym := lookup(".inittask") + nn := newname(sym) + nn.Type = types.Types[TUINT8] // dummy type + nn.SetClass(PEXTERN) + sym.Def = asTypesNode(nn) + exportsym(nn) + lsym := sym.Linksym() + ot := 0 + ot = duintptr(lsym, ot, 0) // state: not initialized yet + ot = duintptr(lsym, ot, uint64(len(deps))) + ot = duintptr(lsym, ot, uint64(len(fns))) + for _, d := range deps { + ot = dsymptr(lsym, ot, d, 0) } - - // (9) - a = nod(OAS, gatevar, nodintconst(2)) - - r = append(r, a) - - // (10) - a = nod(ORETURN, nil, nil) - - r = append(r, a) - exportsym(fn.Func.Nname) - - fn.Nbody.Set(r) - funcbody() - - Curfn = fn - fn = typecheck(fn, ctxStmt) - typecheckslice(r, ctxStmt) - Curfn = nil - funccompile(fn) + for _, f := range fns { + ot = dsymptr(lsym, ot, f, 0) + } + // An initTask has pointers, but none into the Go heap. + // It's not quite read only, the state field must be modifiable. + ggloblsym(lsym, int32(ot), obj.NOPTR) } func (n *Node) checkInitFuncSignature() { diff --git a/src/cmd/link/internal/ld/data.go b/src/cmd/link/internal/ld/data.go index d31d135273..ff339af303 100644 --- a/src/cmd/link/internal/ld/data.go +++ b/src/cmd/link/internal/ld/data.go @@ -148,7 +148,7 @@ func relocsym(ctxt *Link, s *sym.Symbol) { // When putting the runtime but not main into a shared library // these symbols are undefined and that's OK. if ctxt.BuildMode == BuildModeShared { - if r.Sym.Name == "main.main" || r.Sym.Name == "main.init" { + if r.Sym.Name == "main.main" || r.Sym.Name == "main..inittask" { r.Sym.Type = sym.SDYNIMPORT } else if strings.HasPrefix(r.Sym.Name, "go.info.") { // Skip go.info symbols. They are only needed to communicate diff --git a/src/cmd/link/internal/ld/deadcode.go b/src/cmd/link/internal/ld/deadcode.go index 627ce05d7a..f9a0ee0f96 100644 --- a/src/cmd/link/internal/ld/deadcode.go +++ b/src/cmd/link/internal/ld/deadcode.go @@ -222,7 +222,7 @@ func (d *deadcodepass) init() { // functions and mark what is reachable from there. if d.ctxt.linkShared && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) { - names = append(names, "main.main", "main.init") + names = append(names, "main.main", "main..inittask") } else { // The external linker refers main symbol directly. if d.ctxt.LinkMode == LinkExternal && (d.ctxt.BuildMode == BuildModeExe || d.ctxt.BuildMode == BuildModePIE) { @@ -234,7 +234,7 @@ func (d *deadcodepass) init() { } names = append(names, *flagEntrySymbol) if d.ctxt.BuildMode == BuildModePlugin { - names = append(names, objabi.PathToPrefix(*flagPluginPath)+".init", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs") + names = append(names, objabi.PathToPrefix(*flagPluginPath)+"..inittask", objabi.PathToPrefix(*flagPluginPath)+".main", "go.plugin.tabs") // We don't keep the go.plugin.exports symbol, // but we do keep the symbols it refers to. diff --git a/src/plugin/plugin_dlopen.go b/src/plugin/plugin_dlopen.go index f24093989f..03d3f08717 100644 --- a/src/plugin/plugin_dlopen.go +++ b/src/plugin/plugin_dlopen.go @@ -92,15 +92,13 @@ func open(name string) (*Plugin, error) { plugins[filepath] = p pluginsMu.Unlock() - initStr := make([]byte, len(pluginpath)+6) + initStr := make([]byte, len(pluginpath)+len("..inittask")+1) // +1 for terminating NUL copy(initStr, pluginpath) - copy(initStr[len(pluginpath):], ".init") + copy(initStr[len(pluginpath):], "..inittask") - initFuncPC := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr) - if initFuncPC != nil { - initFuncP := &initFuncPC - initFunc := *(*func())(unsafe.Pointer(&initFuncP)) - initFunc() + initTask := C.pluginLookup(h, (*C.char)(unsafe.Pointer(&initStr[0])), &cErr) + if initTask != nil { + doInit(initTask) } // Fill out the value of each plugin symbol. @@ -150,3 +148,7 @@ var ( // lastmoduleinit is defined in package runtime func lastmoduleinit() (pluginpath string, syms map[string]interface{}, errstr string) + +// doInit is defined in package runtime +//go:linkname doInit runtime.doInit +func doInit(t unsafe.Pointer) // t should be a *runtime.initTask diff --git a/src/runtime/panic.go b/src/runtime/panic.go index 918ab7682a..543fd23c01 100644 --- a/src/runtime/panic.go +++ b/src/runtime/panic.go @@ -183,10 +183,6 @@ func panicmem() { panic(memoryError) } -func throwinit() { - throw("recursive call during initialization - linker skew") -} - // Create a new deferred function fn with siz bytes of arguments. // The compiler turns a defer statement into a call to this. //go:nosplit diff --git a/src/runtime/proc.go b/src/runtime/proc.go index 6e56b4b1d1..a077a5da03 100644 --- a/src/runtime/proc.go +++ b/src/runtime/proc.go @@ -82,11 +82,11 @@ var ( raceprocctx0 uintptr ) -//go:linkname runtime_init runtime.init -func runtime_init() +//go:linkname runtime_inittask runtime..inittask +var runtime_inittask initTask -//go:linkname main_init main.init -func main_init() +//go:linkname main_inittask main..inittask +var main_inittask initTask // main_init_done is a signal used by cgocallbackg that initialization // has been completed. It is made before _cgo_notify_runtime_init_done, @@ -144,7 +144,7 @@ func main() { throw("runtime.main not on m0") } - runtime_init() // must be before defer + doInit(&runtime_inittask) // must be before defer if nanotime() == 0 { throw("nanotime returning zero") } @@ -184,8 +184,8 @@ func main() { cgocall(_cgo_notify_runtime_init_done, nil) } - fn := main_init // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime - fn() + doInit(&main_inittask) + close(main_init_done) needUnlock = false @@ -196,7 +196,7 @@ func main() { // has a main, but it is not executed. return } - fn = main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime + fn := main_main // make an indirect call, as the linker doesn't know the address of the main package when laying down the runtime fn() if raceenabled { racefini() @@ -5185,3 +5185,35 @@ func gcd(a, b uint32) uint32 { } return a } + +// An initTask represents the set of initializations that need to be done for a package. +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 +} + +func doInit(t *initTask) { + switch t.state { + case 2: // fully initialized + return + case 1: // initialization in progress + throw("recursive call during initialization - linker skew") + 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)*sys.PtrSize) + t2 := *(**initTask)(p) + doInit(t2) + } + for i := uintptr(0); i < t.nfns; i++ { + p := add(unsafe.Pointer(t), (3+t.ndeps+i)*sys.PtrSize) + f := *(*func())(unsafe.Pointer(&p)) + f() + } + t.state = 2 // initialization done + } +} diff --git a/test/fixedbugs/issue29919.dir/a.go b/test/fixedbugs/issue29919.dir/a.go index cfccc4aabb..2452127ae6 100644 --- a/test/fixedbugs/issue29919.dir/a.go +++ b/test/fixedbugs/issue29919.dir/a.go @@ -65,8 +65,8 @@ func f() int { panic("traceback truncated after init.ializers") } f, _ = iter.Next() - if f.Function != "runtime.main" { - panic("runtime.main missing") + if !strings.HasPrefix(f.Function, "runtime.") { + panic("runtime. driver missing") } return 0 diff --git a/test/init.go b/test/init.go index f4689443cf..317f2472cb 100644 --- a/test/init.go +++ b/test/init.go @@ -9,13 +9,11 @@ package main -import "runtime" - func init() { } func main() { init() // ERROR "undefined.*init" - runtime.init() // ERROR "unexported.*runtime\.init" + runtime.init() // ERROR "undefined.*runtime\.init" var _ = init // ERROR "undefined.*init" }