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  }