Source file src/cmd/link/internal/ld/inittask.go

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

View as plain text