diff --git a/src/cmd/compile/internal/gc/init.go b/src/cmd/compile/internal/gc/init.go index 6fd2c3427fb..01421eee366 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 d31d1352733..ff339af303a 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 627ce05d7a1..f9a0ee0f966 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 f24093989fd..03d3f08717c 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 918ab7682a5..543fd23c01c 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 6e56b4b1d1b..a077a5da030 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 cfccc4aabb6..2452127ae62 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 f4689443cf1..317f2472cb8 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" }