// Copyright 2022 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ld import ( "cmd/internal/objabi" "cmd/link/internal/loader" "cmd/link/internal/sym" "fmt" "sort" ) // Inittasks finds inittask records, figures out a good // order to execute them in, and emits that order for the // runtime to use. // // An inittask represents the initialization code that needs // to be run for a package. For package p, the p..inittask // symbol contains a list of init functions to run, both // explicit user init functions and implicit compiler-generated // init functions for initializing global variables like maps. // // In addition, inittask records have dependencies between each // other, mirroring the import dependencies. So if package p // imports package q, then there will be a dependency p -> q. // We can't initialize package p until after package q has // already been initialized. // // Package dependencies are encoded with relocations. If package // p imports package q, then package p's inittask record will // have a R_INITORDER relocation pointing to package q's inittask // record. See cmd/compile/internal/pkginit/init.go. // // This function computes an ordering of all of the inittask // records so that the order respects all the dependencies, // and given that restriction, orders the inittasks in // lexicographic order. func (ctxt *Link) inittasks() { switch ctxt.BuildMode { case BuildModeExe, BuildModePIE, BuildModeCArchive, BuildModeCShared: // Normally the inittask list will be run on program startup. ctxt.mainInittasks = ctxt.inittaskSym([]string{"main..inittask"}, "go:main.inittasks") case BuildModePlugin: // For plugins, the list will be run on plugin load. ctxt.mainInittasks = ctxt.inittaskSym([]string{fmt.Sprintf("%s..inittask", objabi.PathToPrefix(*flagPluginPath))}, "go:plugin.inittasks") // Make symbol local so multiple plugins don't clobber each other's inittask list. ctxt.loader.SetAttrLocal(ctxt.mainInittasks, true) case BuildModeShared: // For a shared library, all packages are roots. var roots []string for _, lib := range ctxt.Library { roots = append(roots, fmt.Sprintf("%s..inittask", objabi.PathToPrefix(lib.Pkg))) } ctxt.mainInittasks = ctxt.inittaskSym(roots, "go:shlib.inittasks") // Make symbol local so multiple plugins don't clobber each other's inittask list. ctxt.loader.SetAttrLocal(ctxt.mainInittasks, true) default: Exitf("unhandled build mode %d", ctxt.BuildMode) } // If the runtime is one of the packages we are building, // initialize the runtime_inittasks variable. ldr := ctxt.loader if ldr.Lookup("runtime.runtime_inittasks", 0) != 0 { t := ctxt.inittaskSym([]string{"runtime..inittask"}, "go:runtime.inittasks") // This slice header is already defined in runtime/proc.go, so we update it here with new contents. sh := ldr.Lookup("runtime.runtime_inittasks", 0) sb := ldr.MakeSymbolUpdater(sh) sb.SetSize(0) sb.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see issue 58857. sb.AddAddr(ctxt.Arch, t) sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize))) sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize))) } } // inittaskSym builds a symbol containing pointers to all the inittasks // that need to be run, given a list of root inittask symbols. func (ctxt *Link) inittaskSym(rootNames []string, symName string) loader.Sym { ldr := ctxt.loader var roots []loader.Sym for _, n := range rootNames { p := ldr.Lookup(n, 0) if p != 0 { roots = append(roots, p) } } if len(roots) == 0 { // Nothing to do return 0 } // Edges record dependencies between packages. // {from,to} is in edges if from's package imports to's package. // This list is used to implement reverse edge lookups. type edge struct { from, to loader.Sym } var edges []edge // List of packages that are ready to schedule. We use a lexicographic // ordered heap to pick the lexically earliest uninitialized but // inititalizeable package at each step. var h lexHeap // m maps from an inittask symbol for package p to the number of // p's direct imports that have not yet been scheduled. m := map[loader.Sym]int{} // Find all reachable inittask records from the roots. // Keep track of the dependency edges between them in edges. // Keep track of how many imports each package has in m. // q is the list of found but not yet explored packages. var q []loader.Sym for _, p := range roots { m[p] = 0 q = append(q, p) } for len(q) > 0 { x := q[len(q)-1] q = q[:len(q)-1] relocs := ldr.Relocs(x) n := relocs.Count() ndeps := 0 for i := 0; i < n; i++ { r := relocs.At(i) if r.Type() != objabi.R_INITORDER { continue } ndeps++ s := r.Sym() edges = append(edges, edge{from: x, to: s}) if _, ok := m[s]; ok { continue // already found } q = append(q, s) m[s] = 0 // mark as found } m[x] = ndeps if ndeps == 0 { h.push(ldr, x) } } // Sort edges so we can look them up by edge destination. sort.Slice(edges, func(i, j int) bool { return edges[i].to < edges[j].to }) // Figure out the schedule. sched := ldr.MakeSymbolBuilder(symName) sched.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see issue 58857. for !h.empty() { // Pick the lexicographically first initializable package. s := h.pop(ldr) // Add s to the schedule. if ldr.SymSize(s) > 8 { // Note: don't add s if it has no functions to run. We need // s during linking to compute an ordering, but the runtime // doesn't need to know about it. About 1/2 of stdlib packages // fit in this bucket. sched.AddAddr(ctxt.Arch, s) } // Find all incoming edges into s. a := sort.Search(len(edges), func(i int) bool { return edges[i].to >= s }) b := sort.Search(len(edges), func(i int) bool { return edges[i].to > s }) // Decrement the import count for all packages that import s. // If the count reaches 0, that package is now ready to schedule. for _, e := range edges[a:b] { m[e.from]-- if m[e.from] == 0 { h.push(ldr, e.from) } } } for s, n := range m { if n != 0 { Exitf("inittask for %s is not schedulable %d", ldr.SymName(s), n) } } return sched.Sym() }