]> Cypherpunks repositories - gostls13.git/commitdiff
cmd/compile: reorganize init functions
authorKeith Randall <khr@google.com>
Wed, 6 Feb 2019 00:22:38 +0000 (16:22 -0800)
committerKeith Randall <khr@golang.org>
Mon, 18 Mar 2019 20:10:55 +0000 (20:10 +0000)
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 <khr@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
Reviewed-by: Josh Bleecher Snyder <josharian@gmail.com>
src/cmd/compile/internal/gc/init.go
src/cmd/link/internal/ld/data.go
src/cmd/link/internal/ld/deadcode.go
src/plugin/plugin_dlopen.go
src/runtime/panic.go
src/runtime/proc.go
test/fixedbugs/issue29919.dir/a.go
test/init.go

index 6fd2c3427fbd32f6bc8e8e73500fa048079294c0..01421eee36621dd52f267340f24cd9570485ad5d 100644 (file)
@@ -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
-       }
+// 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) {
+       nf := initfix(n)
 
-       // is there an explicit init function
-       if renameinitgen > 0 {
-               return true
-       }
+       var deps []*obj.LSym // initTask records for packages the current package depends on
+       var fns []*obj.LSym  // functions to call for package initialization
 
-       // are there any imported init functions
-       for _, s := range types.InitSyms {
-               if s.Def != nil {
-                       return true
+       // Find imported packages with init tasks.
+       for _, p := range types.ImportedPkgList() {
+               if s, ok := p.LookupOK(".inittask"); ok {
+                       deps = append(deps, s.Linksym())
                }
        }
 
-       // then none
-       return false
-}
-
-// fninit hand-crafts package initialization code.
-//
-//      func init.ializers() {                          (0)
-//              <init stmts>
-//      }
-//      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
-//                      <pkg>.init()                    (6)
-//              init.ializers()                         (7)
-//              init.<n>() // if any                    (8)
-//              initdone· = 2                           (9)
-//              return                                  (10)
-//      }
-func fninit(n []*Node) {
-       lineno = autogeneratedPos
-       nf := initfix(n)
-       if !anyinit(nf) {
-               return
-       }
-
-       // (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() {
index d31d1352733c77cda20e87393f4f7994a9653e4d..ff339af303af8541e3a32d1161eb51fc595349e0 100644 (file)
@@ -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
index 627ce05d7a1fdf504e3c7a622c0ea4dfd6ecabe9..f9a0ee0f96605fe70e3b0a6dfbc5efd90f9e9164 100644 (file)
@@ -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.
index f24093989fd6fdbc6302ab23ffd0484314fe95ef..03d3f08717c16dbe16174da7919575f9134dba6a 100644 (file)
@@ -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
index 918ab7682a5d5bc43a1157fe455a347c2bda4ff1..543fd23c01c08dc15e1149326af136565d09dd88 100644 (file)
@@ -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
index 6e56b4b1d1b6fee4817e54c6a26eec1242f8726d..a077a5da03012104c00557fe659d17e0df81c4a0 100644 (file)
@@ -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
+       }
+}
index cfccc4aabb671f78f8aa50c0f3aa17c268a8f543..2452127ae6221eb606af399cc77782bed40d6924 100644 (file)
@@ -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
index f4689443cf1415be71abe719d0c89aaebdecba76..317f2472cb82c3cb6990f37f3e5a33f660626404 100644 (file)
@@ -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"
 }