github.com/bir3/gocompiler@v0.9.2202/src/cmd/link/internal/ld/stackcheck.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  	"github.com/bir3/gocompiler/src/cmd/internal/obj"
     9  	"github.com/bir3/gocompiler/src/cmd/internal/objabi"
    10  	"github.com/bir3/gocompiler/src/cmd/link/internal/loader"
    11  	"fmt"
    12  	"github.com/bir3/gocompiler/src/internal/buildcfg"
    13  	"sort"
    14  	"strings"
    15  )
    16  
    17  type stackCheck struct {
    18  	ctxt		*Link
    19  	ldr		*loader.Loader
    20  	morestack	loader.Sym
    21  	callSize	int	// The number of bytes added by a CALL
    22  
    23  	// height records the maximum number of bytes a function and
    24  	// its callees can add to the stack without a split check.
    25  	height	map[loader.Sym]int16
    26  
    27  	// graph records the out-edges from each symbol. This is only
    28  	// populated on a second pass if the first pass reveals an
    29  	// over-limit function.
    30  	graph	map[loader.Sym][]stackCheckEdge
    31  }
    32  
    33  type stackCheckEdge struct {
    34  	growth	int		// Stack growth in bytes at call to target
    35  	target	loader.Sym	// 0 for stack growth without a call
    36  }
    37  
    38  // stackCheckCycle is a sentinel stored in the height map to detect if
    39  // we've found a cycle. This is effectively an "infinite" stack
    40  // height, so we use the closest value to infinity that we can.
    41  const stackCheckCycle int16 = 1<<15 - 1
    42  
    43  // stackCheckIndirect is a sentinel Sym value used to represent the
    44  // target of an indirect/closure call.
    45  const stackCheckIndirect loader.Sym = ^loader.Sym(0)
    46  
    47  // doStackCheck walks the call tree to check that there is always
    48  // enough stack space for call frames, especially for a chain of
    49  // nosplit functions.
    50  //
    51  // It walks all functions to accumulate the number of bytes they can
    52  // grow the stack by without a split check and checks this against the
    53  // limit.
    54  func (ctxt *Link) doStackCheck() {
    55  	sc := newStackCheck(ctxt, false)
    56  
    57  	// limit is number of bytes a splittable function ensures are
    58  	// available on the stack. If any call chain exceeds this
    59  	// depth, the stack check test fails.
    60  	//
    61  	// The call to morestack in every splittable function ensures
    62  	// that there are at least StackLimit bytes available below SP
    63  	// when morestack returns.
    64  	limit := objabi.StackNosplit(*flagRace) - sc.callSize
    65  	if buildcfg.GOARCH == "arm64" {
    66  		// Need an extra 8 bytes below SP to save FP.
    67  		limit -= 8
    68  	}
    69  
    70  	// Compute stack heights without any back-tracking information.
    71  	// This will almost certainly succeed and we can simply
    72  	// return. If it fails, we do a second pass with back-tracking
    73  	// to produce a good error message.
    74  	//
    75  	// This accumulates stack heights bottom-up so it only has to
    76  	// visit every function once.
    77  	var failed []loader.Sym
    78  	for _, s := range ctxt.Textp {
    79  		if sc.check(s) > limit {
    80  			failed = append(failed, s)
    81  		}
    82  	}
    83  
    84  	if len(failed) > 0 {
    85  		// Something was over-limit, so now we do the more
    86  		// expensive work to report a good error. First, for
    87  		// the over-limit functions, redo the stack check but
    88  		// record the graph this time.
    89  		sc = newStackCheck(ctxt, true)
    90  		for _, s := range failed {
    91  			sc.check(s)
    92  		}
    93  
    94  		// Find the roots of the graph (functions that are not
    95  		// called by any other function).
    96  		roots := sc.findRoots()
    97  
    98  		// Find and report all paths that go over the limit.
    99  		// This accumulates stack depths top-down. This is
   100  		// much less efficient because we may have to visit
   101  		// the same function multiple times at different
   102  		// depths, but lets us find all paths.
   103  		for _, root := range roots {
   104  			ctxt.Errorf(root, "nosplit stack over %d byte limit", limit)
   105  			chain := []stackCheckChain{{stackCheckEdge{0, root}, false}}
   106  			sc.report(root, limit, &chain)
   107  		}
   108  	}
   109  }
   110  
   111  func newStackCheck(ctxt *Link, graph bool) *stackCheck {
   112  	sc := &stackCheck{
   113  		ctxt:		ctxt,
   114  		ldr:		ctxt.loader,
   115  		morestack:	ctxt.loader.Lookup("runtime.morestack", 0),
   116  		height:		make(map[loader.Sym]int16, len(ctxt.Textp)),
   117  	}
   118  	// Compute stack effect of a CALL operation. 0 on LR machines.
   119  	// 1 register pushed on non-LR machines.
   120  	if !ctxt.Arch.HasLR {
   121  		sc.callSize = ctxt.Arch.RegSize
   122  	}
   123  
   124  	if graph {
   125  		// We're going to record the call graph.
   126  		sc.graph = make(map[loader.Sym][]stackCheckEdge)
   127  	}
   128  
   129  	return sc
   130  }
   131  
   132  func (sc *stackCheck) symName(sym loader.Sym) string {
   133  	switch sym {
   134  	case stackCheckIndirect:
   135  		return "indirect"
   136  	case 0:
   137  		return "leaf"
   138  	}
   139  	return fmt.Sprintf("%s<%d>", sc.ldr.SymName(sym), sc.ldr.SymVersion(sym))
   140  }
   141  
   142  // check returns the stack height of sym. It populates sc.height and
   143  // sc.graph for sym and every function in its call tree.
   144  func (sc *stackCheck) check(sym loader.Sym) int {
   145  	if h, ok := sc.height[sym]; ok {
   146  		// We've already visited this symbol or we're in a cycle.
   147  		return int(h)
   148  	}
   149  	// Store the sentinel so we can detect cycles.
   150  	sc.height[sym] = stackCheckCycle
   151  	// Compute and record the height and optionally edges.
   152  	h, edges := sc.computeHeight(sym, *flagDebugNosplit || sc.graph != nil)
   153  	if h > int(stackCheckCycle) {	// Prevent integer overflow
   154  		h = int(stackCheckCycle)
   155  	}
   156  	sc.height[sym] = int16(h)
   157  	if sc.graph != nil {
   158  		sc.graph[sym] = edges
   159  	}
   160  
   161  	if *flagDebugNosplit {
   162  		for _, edge := range edges {
   163  			fmt.Printf("nosplit: %s +%d", sc.symName(sym), edge.growth)
   164  			if edge.target == 0 {
   165  				// Local stack growth or leaf function.
   166  				fmt.Printf("\n")
   167  			} else {
   168  				fmt.Printf(" -> %s\n", sc.symName(edge.target))
   169  			}
   170  		}
   171  	}
   172  
   173  	return h
   174  }
   175  
   176  // computeHeight returns the stack height of sym. If graph is true, it
   177  // also returns the out-edges of sym.
   178  //
   179  // Caching is applied to this in check. Call check instead of calling
   180  // this directly.
   181  func (sc *stackCheck) computeHeight(sym loader.Sym, graph bool) (int, []stackCheckEdge) {
   182  	ldr := sc.ldr
   183  
   184  	// Check special cases.
   185  	if sym == sc.morestack {
   186  		// morestack looks like it calls functions, but they
   187  		// either happen only when already on the system stack
   188  		// (where there is ~infinite space), or after
   189  		// switching to the system stack. Hence, its stack
   190  		// height on the user stack is 0.
   191  		return 0, nil
   192  	}
   193  	if sym == stackCheckIndirect {
   194  		// Assume that indirect/closure calls are always to
   195  		// splittable functions, so they just need enough room
   196  		// to call morestack.
   197  		return sc.callSize, []stackCheckEdge{{sc.callSize, sc.morestack}}
   198  	}
   199  
   200  	// Ignore calls to external functions. Assume that these calls
   201  	// are only ever happening on the system stack, where there's
   202  	// plenty of room.
   203  	if ldr.AttrExternal(sym) {
   204  		return 0, nil
   205  	}
   206  	if info := ldr.FuncInfo(sym); !info.Valid() {	// also external
   207  		return 0, nil
   208  	}
   209  
   210  	// Track the maximum height of this function and, if we're
   211  	// recording the graph, its out-edges.
   212  	var edges []stackCheckEdge
   213  	maxHeight := 0
   214  	ctxt := sc.ctxt
   215  	// addEdge adds a stack growth out of this function to
   216  	// function "target" or, if target == 0, a local stack growth
   217  	// within the function.
   218  	addEdge := func(growth int, target loader.Sym) {
   219  		if graph {
   220  			edges = append(edges, stackCheckEdge{growth, target})
   221  		}
   222  		height := growth
   223  		if target != 0 {	// Don't walk into the leaf "edge"
   224  			height += sc.check(target)
   225  		}
   226  		if height > maxHeight {
   227  			maxHeight = height
   228  		}
   229  	}
   230  
   231  	if !ldr.IsNoSplit(sym) {
   232  		// Splittable functions start with a call to
   233  		// morestack, after which their height is 0. Account
   234  		// for the height of the call to morestack.
   235  		addEdge(sc.callSize, sc.morestack)
   236  		return maxHeight, edges
   237  	}
   238  
   239  	// This function is nosplit, so it adjusts SP without a split
   240  	// check.
   241  	//
   242  	// Walk through SP adjustments in function, consuming relocs
   243  	// and following calls.
   244  	maxLocalHeight := 0
   245  	relocs, ri := ldr.Relocs(sym), 0
   246  	pcsp := obj.NewPCIter(uint32(ctxt.Arch.MinLC))
   247  	for pcsp.Init(ldr.Data(ldr.Pcsp(sym))); !pcsp.Done; pcsp.Next() {
   248  		// pcsp.value is in effect for [pcsp.pc, pcsp.nextpc).
   249  		height := int(pcsp.Value)
   250  		if height > maxLocalHeight {
   251  			maxLocalHeight = height
   252  		}
   253  
   254  		// Process calls in this span.
   255  		for ; ri < relocs.Count(); ri++ {
   256  			r := relocs.At(ri)
   257  			if uint32(r.Off()) >= pcsp.NextPC {
   258  				break
   259  			}
   260  			t := r.Type()
   261  			if t.IsDirectCall() || t == objabi.R_CALLIND {
   262  				growth := height + sc.callSize
   263  				var target loader.Sym
   264  				if t == objabi.R_CALLIND {
   265  					target = stackCheckIndirect
   266  				} else {
   267  					target = r.Sym()
   268  				}
   269  				addEdge(growth, target)
   270  			}
   271  		}
   272  	}
   273  	if maxLocalHeight > maxHeight {
   274  		// This is either a leaf function, or the function
   275  		// grew its stack to larger than the maximum call
   276  		// height between calls. Either way, record that local
   277  		// stack growth.
   278  		addEdge(maxLocalHeight, 0)
   279  	}
   280  
   281  	return maxHeight, edges
   282  }
   283  
   284  func (sc *stackCheck) findRoots() []loader.Sym {
   285  	// Collect all nodes.
   286  	nodes := make(map[loader.Sym]struct{})
   287  	for k := range sc.graph {
   288  		nodes[k] = struct{}{}
   289  	}
   290  
   291  	// Start a DFS from each node and delete all reachable
   292  	// children. If we encounter an unrooted cycle, this will
   293  	// delete everything in that cycle, so we detect this case and
   294  	// track the lowest-numbered node encountered in the cycle and
   295  	// put that node back as a root.
   296  	var walk func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym)
   297  	walk = func(origin, sym loader.Sym) (cycle bool, lowest loader.Sym) {
   298  		if _, ok := nodes[sym]; !ok {
   299  			// We already deleted this node.
   300  			return false, 0
   301  		}
   302  		delete(nodes, sym)
   303  
   304  		if origin == sym {
   305  			// We found an unrooted cycle. We already
   306  			// deleted all children of this node. Walk
   307  			// back up, tracking the lowest numbered
   308  			// symbol in this cycle.
   309  			return true, sym
   310  		}
   311  
   312  		// Delete children of this node.
   313  		for _, out := range sc.graph[sym] {
   314  			if c, l := walk(origin, out.target); c {
   315  				cycle = true
   316  				if lowest == 0 {
   317  					// On first cycle detection,
   318  					// add sym to the set of
   319  					// lowest-numbered candidates.
   320  					lowest = sym
   321  				}
   322  				if l < lowest {
   323  					lowest = l
   324  				}
   325  			}
   326  		}
   327  		return
   328  	}
   329  	for k := range nodes {
   330  		// Delete all children of k.
   331  		for _, out := range sc.graph[k] {
   332  			if cycle, lowest := walk(k, out.target); cycle {
   333  				// This is an unrooted cycle so we
   334  				// just deleted everything. Put back
   335  				// the lowest-numbered symbol.
   336  				nodes[lowest] = struct{}{}
   337  			}
   338  		}
   339  	}
   340  
   341  	// Sort roots by height. This makes the result deterministic
   342  	// and also improves the error reporting.
   343  	var roots []loader.Sym
   344  	for k := range nodes {
   345  		roots = append(roots, k)
   346  	}
   347  	sort.Slice(roots, func(i, j int) bool {
   348  		h1, h2 := sc.height[roots[i]], sc.height[roots[j]]
   349  		if h1 != h2 {
   350  			return h1 > h2
   351  		}
   352  		// Secondary sort by Sym.
   353  		return roots[i] < roots[j]
   354  	})
   355  	return roots
   356  }
   357  
   358  type stackCheckChain struct {
   359  	stackCheckEdge
   360  	printed	bool
   361  }
   362  
   363  func (sc *stackCheck) report(sym loader.Sym, depth int, chain *[]stackCheckChain) {
   364  	// Walk the out-edges of sym. We temporarily pull the edges
   365  	// out of the graph to detect cycles and prevent infinite
   366  	// recursion.
   367  	edges, ok := sc.graph[sym]
   368  	isCycle := !(ok || sym == 0)
   369  	delete(sc.graph, sym)
   370  	for _, out := range edges {
   371  		*chain = append(*chain, stackCheckChain{out, false})
   372  		sc.report(out.target, depth-out.growth, chain)
   373  		*chain = (*chain)[:len(*chain)-1]
   374  	}
   375  	sc.graph[sym] = edges
   376  
   377  	// If we've reached the end of a chain and it went over the
   378  	// stack limit or was a cycle that would eventually go over,
   379  	// print the whole chain.
   380  	//
   381  	// We should either be in morestack (which has no out-edges)
   382  	// or the sentinel 0 Sym "called" from a leaf function (which
   383  	// has no out-edges), or we came back around a cycle (possibly
   384  	// to ourselves) and edges was temporarily nil'd.
   385  	if len(edges) == 0 && (depth < 0 || isCycle) {
   386  		var indent string
   387  		for i := range *chain {
   388  			ent := &(*chain)[i]
   389  			if ent.printed {
   390  				// Already printed on an earlier part
   391  				// of this call tree.
   392  				continue
   393  			}
   394  			ent.printed = true
   395  
   396  			if i == 0 {
   397  				// chain[0] is just the root function,
   398  				// not a stack growth.
   399  				fmt.Printf("%s\n", sc.symName(ent.target))
   400  				continue
   401  			}
   402  
   403  			indent = strings.Repeat("    ", i)
   404  			fmt.Print(indent)
   405  			// Grows the stack X bytes and (maybe) calls Y.
   406  			fmt.Printf("grows %d bytes", ent.growth)
   407  			if ent.target == 0 {
   408  				// Not a call, just a leaf. Print nothing.
   409  			} else {
   410  				fmt.Printf(", calls %s", sc.symName(ent.target))
   411  			}
   412  			fmt.Printf("\n")
   413  		}
   414  		// Print how far over this chain went.
   415  		if isCycle {
   416  			fmt.Printf("%sinfinite cycle\n", indent)
   417  		} else {
   418  			fmt.Printf("%s%d bytes over limit\n", indent, -depth)
   419  		}
   420  	}
   421  }