github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/link/ld/inittask.go (about) 1 // Copyright 2022 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 package ld 6 7 import ( 8 "fmt" 9 "sort" 10 11 "github.com/go-asm/go/cmd/link/loader" 12 "github.com/go-asm/go/cmd/link/sym" 13 "github.com/go-asm/go/cmd/objabi" 14 ) 15 16 // Inittasks finds inittask records, figures out a good 17 // order to execute them in, and emits that order for the 18 // runtime to use. 19 // 20 // An inittask represents the initialization code that needs 21 // to be run for a package. For package p, the p..inittask 22 // symbol contains a list of init functions to run, both 23 // explicit user init functions and implicit compiler-generated 24 // init functions for initializing global variables like maps. 25 // 26 // In addition, inittask records have dependencies between each 27 // other, mirroring the import dependencies. So if package p 28 // imports package q, then there will be a dependency p -> q. 29 // We can't initialize package p until after package q has 30 // already been initialized. 31 // 32 // Package dependencies are encoded with relocations. If package 33 // p imports package q, then package p's inittask record will 34 // have a R_INITORDER relocation pointing to package q's inittask 35 // record. See github.com/go-asm/go/cmd/compile/pkginit/init.go. 36 // 37 // This function computes an ordering of all of the inittask 38 // records so that the order respects all the dependencies, 39 // and given that restriction, orders the inittasks in 40 // lexicographic order. 41 func (ctxt *Link) inittasks() { 42 switch ctxt.BuildMode { 43 case BuildModeExe, BuildModePIE, BuildModeCArchive, BuildModeCShared: 44 // Normally the inittask list will be run on program startup. 45 ctxt.mainInittasks = ctxt.inittaskSym([]string{"main..inittask"}, "go:main.inittasks") 46 case BuildModePlugin: 47 // For plugins, the list will be run on plugin load. 48 ctxt.mainInittasks = ctxt.inittaskSym([]string{fmt.Sprintf("%s..inittask", objabi.PathToPrefix(*flagPluginPath))}, "go:plugin.inittasks") 49 // Make symbol local so multiple plugins don't clobber each other's inittask list. 50 ctxt.loader.SetAttrLocal(ctxt.mainInittasks, true) 51 case BuildModeShared: 52 // For a shared library, all packages are roots. 53 var roots []string 54 for _, lib := range ctxt.Library { 55 roots = append(roots, fmt.Sprintf("%s..inittask", objabi.PathToPrefix(lib.Pkg))) 56 } 57 ctxt.mainInittasks = ctxt.inittaskSym(roots, "go:shlib.inittasks") 58 // Make symbol local so multiple plugins don't clobber each other's inittask list. 59 ctxt.loader.SetAttrLocal(ctxt.mainInittasks, true) 60 default: 61 Exitf("unhandled build mode %d", ctxt.BuildMode) 62 } 63 64 // If the runtime is one of the packages we are building, 65 // initialize the runtime_inittasks variable. 66 ldr := ctxt.loader 67 if ldr.Lookup("runtime.runtime_inittasks", 0) != 0 { 68 t := ctxt.inittaskSym([]string{"runtime..inittask"}, "go:runtime.inittasks") 69 70 // This slice header is already defined in runtime/proc.go, so we update it here with new contents. 71 sh := ldr.Lookup("runtime.runtime_inittasks", 0) 72 sb := ldr.MakeSymbolUpdater(sh) 73 sb.SetSize(0) 74 sb.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see issue 58857. 75 sb.AddAddr(ctxt.Arch, t) 76 sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize))) 77 sb.AddUint(ctxt.Arch, uint64(ldr.SymSize(t)/int64(ctxt.Arch.PtrSize))) 78 } 79 } 80 81 // inittaskSym builds a symbol containing pointers to all the inittasks 82 // that need to be run, given a list of root inittask symbols. 83 func (ctxt *Link) inittaskSym(rootNames []string, symName string) loader.Sym { 84 ldr := ctxt.loader 85 var roots []loader.Sym 86 for _, n := range rootNames { 87 p := ldr.Lookup(n, 0) 88 if p != 0 { 89 roots = append(roots, p) 90 } 91 } 92 if len(roots) == 0 { 93 // Nothing to do 94 return 0 95 } 96 97 // Edges record dependencies between packages. 98 // {from,to} is in edges if from's package imports to's package. 99 // This list is used to implement reverse edge lookups. 100 type edge struct { 101 from, to loader.Sym 102 } 103 var edges []edge 104 105 // List of packages that are ready to schedule. We use a lexicographic 106 // ordered heap to pick the lexically earliest uninitialized but 107 // inititalizeable package at each step. 108 var h lexHeap 109 110 // m maps from an inittask symbol for package p to the number of 111 // p's direct imports that have not yet been scheduled. 112 m := map[loader.Sym]int{} 113 114 // Find all reachable inittask records from the roots. 115 // Keep track of the dependency edges between them in edges. 116 // Keep track of how many imports each package has in m. 117 // q is the list of found but not yet explored packages. 118 var q []loader.Sym 119 for _, p := range roots { 120 m[p] = 0 121 q = append(q, p) 122 } 123 for len(q) > 0 { 124 x := q[len(q)-1] 125 q = q[:len(q)-1] 126 relocs := ldr.Relocs(x) 127 n := relocs.Count() 128 ndeps := 0 129 for i := 0; i < n; i++ { 130 r := relocs.At(i) 131 if r.Type() != objabi.R_INITORDER { 132 continue 133 } 134 ndeps++ 135 s := r.Sym() 136 edges = append(edges, edge{from: x, to: s}) 137 if _, ok := m[s]; ok { 138 continue // already found 139 } 140 q = append(q, s) 141 m[s] = 0 // mark as found 142 } 143 m[x] = ndeps 144 if ndeps == 0 { 145 h.push(ldr, x) 146 } 147 } 148 149 // Sort edges so we can look them up by edge destination. 150 sort.Slice(edges, func(i, j int) bool { 151 return edges[i].to < edges[j].to 152 }) 153 154 // Figure out the schedule. 155 sched := ldr.MakeSymbolBuilder(symName) 156 sched.SetType(sym.SNOPTRDATA) // Could be SRODATA, but see isue 58857. 157 for !h.empty() { 158 // Pick the lexicographically first initializable package. 159 s := h.pop(ldr) 160 161 // Add s to the schedule. 162 if ldr.SymSize(s) > 8 { 163 // Note: don't add s if it has no functions to run. We need 164 // s during linking to compute an ordering, but the runtime 165 // doesn't need to know about it. About 1/2 of stdlib packages 166 // fit in this bucket. 167 sched.AddAddr(ctxt.Arch, s) 168 } 169 170 // Find all incoming edges into s. 171 a := sort.Search(len(edges), func(i int) bool { return edges[i].to >= s }) 172 b := sort.Search(len(edges), func(i int) bool { return edges[i].to > s }) 173 174 // Decrement the import count for all packages that import s. 175 // If the count reaches 0, that package is now ready to schedule. 176 for _, e := range edges[a:b] { 177 m[e.from]-- 178 if m[e.from] == 0 { 179 h.push(ldr, e.from) 180 } 181 } 182 } 183 184 for s, n := range m { 185 if n != 0 { 186 Exitf("inittask for %s is not schedulable %d", ldr.SymName(s), n) 187 } 188 } 189 return sched.Sym() 190 }