github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ssa/layout.go (about)

     1  // Copyright 2015 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 ssa
     6  
     7  // layout orders basic blocks in f with the goal of minimizing control flow instructions.
     8  // After this phase returns, the order of f.Blocks matters and is the order
     9  // in which those blocks will appear in the assembly output.
    10  func layout(f *Func) {
    11  	f.Blocks = layoutOrder(f)
    12  }
    13  
    14  // Register allocation may use a different order which has constraints
    15  // imposed by the linear-scan algorithm.
    16  func layoutRegallocOrder(f *Func) []*Block {
    17  	// remnant of an experiment; perhaps there will be another.
    18  	return layoutOrder(f)
    19  }
    20  
    21  func layoutOrder(f *Func) []*Block {
    22  	order := make([]*Block, 0, f.NumBlocks())
    23  	scheduled := f.Cache.allocBoolSlice(f.NumBlocks())
    24  	defer f.Cache.freeBoolSlice(scheduled)
    25  	idToBlock := f.Cache.allocBlockSlice(f.NumBlocks())
    26  	defer f.Cache.freeBlockSlice(idToBlock)
    27  	indegree := f.Cache.allocIntSlice(f.NumBlocks())
    28  	defer f.Cache.freeIntSlice(indegree)
    29  	posdegree := f.newSparseSet(f.NumBlocks()) // blocks with positive remaining degree
    30  	defer f.retSparseSet(posdegree)
    31  	// blocks with zero remaining degree. Use slice to simulate a LIFO queue to implement
    32  	// the depth-first topology sorting algorithm.
    33  	var zerodegree []ID
    34  	// LIFO queue. Track the successor blocks of the scheduled block so that when we
    35  	// encounter loops, we choose to schedule the successor block of the most recently
    36  	// scheduled block.
    37  	var succs []ID
    38  	exit := f.newSparseSet(f.NumBlocks()) // exit blocks
    39  	defer f.retSparseSet(exit)
    40  
    41  	// Populate idToBlock and find exit blocks.
    42  	for _, b := range f.Blocks {
    43  		idToBlock[b.ID] = b
    44  		if b.Kind == BlockExit {
    45  			exit.add(b.ID)
    46  		}
    47  	}
    48  
    49  	// Expand exit to include blocks post-dominated by exit blocks.
    50  	for {
    51  		changed := false
    52  		for _, id := range exit.contents() {
    53  			b := idToBlock[id]
    54  		NextPred:
    55  			for _, pe := range b.Preds {
    56  				p := pe.b
    57  				if exit.contains(p.ID) {
    58  					continue
    59  				}
    60  				for _, s := range p.Succs {
    61  					if !exit.contains(s.b.ID) {
    62  						continue NextPred
    63  					}
    64  				}
    65  				// All Succs are in exit; add p.
    66  				exit.add(p.ID)
    67  				changed = true
    68  			}
    69  		}
    70  		if !changed {
    71  			break
    72  		}
    73  	}
    74  
    75  	// Initialize indegree of each block
    76  	for _, b := range f.Blocks {
    77  		if exit.contains(b.ID) {
    78  			// exit blocks are always scheduled last
    79  			continue
    80  		}
    81  		indegree[b.ID] = len(b.Preds)
    82  		if len(b.Preds) == 0 {
    83  			// Push an element to the tail of the queue.
    84  			zerodegree = append(zerodegree, b.ID)
    85  		} else {
    86  			posdegree.add(b.ID)
    87  		}
    88  	}
    89  
    90  	bid := f.Entry.ID
    91  blockloop:
    92  	for {
    93  		// add block to schedule
    94  		b := idToBlock[bid]
    95  		order = append(order, b)
    96  		scheduled[bid] = true
    97  		if len(order) == len(f.Blocks) {
    98  			break
    99  		}
   100  
   101  		// Here, the order of traversing the b.Succs affects the direction in which the topological
   102  		// sort advances in depth. Take the following cfg as an example, regardless of other factors.
   103  		//           b1
   104  		//         0/ \1
   105  		//        b2   b3
   106  		// Traverse b.Succs in order, the right child node b3 will be scheduled immediately after
   107  		// b1, traverse b.Succs in reverse order, the left child node b2 will be scheduled
   108  		// immediately after b1. The test results show that reverse traversal performs a little
   109  		// better.
   110  		// Note: You need to consider both layout and register allocation when testing performance.
   111  		for i := len(b.Succs) - 1; i >= 0; i-- {
   112  			c := b.Succs[i].b
   113  			indegree[c.ID]--
   114  			if indegree[c.ID] == 0 {
   115  				posdegree.remove(c.ID)
   116  				zerodegree = append(zerodegree, c.ID)
   117  			} else {
   118  				succs = append(succs, c.ID)
   119  			}
   120  		}
   121  
   122  		// Pick the next block to schedule
   123  		// Pick among the successor blocks that have not been scheduled yet.
   124  
   125  		// Use likely direction if we have it.
   126  		var likely *Block
   127  		switch b.Likely {
   128  		case BranchLikely:
   129  			likely = b.Succs[0].b
   130  		case BranchUnlikely:
   131  			likely = b.Succs[1].b
   132  		}
   133  		if likely != nil && !scheduled[likely.ID] {
   134  			bid = likely.ID
   135  			continue
   136  		}
   137  
   138  		// Use degree for now.
   139  		bid = 0
   140  		// TODO: improve this part
   141  		// No successor of the previously scheduled block works.
   142  		// Pick a zero-degree block if we can.
   143  		for len(zerodegree) > 0 {
   144  			// Pop an element from the tail of the queue.
   145  			cid := zerodegree[len(zerodegree)-1]
   146  			zerodegree = zerodegree[:len(zerodegree)-1]
   147  			if !scheduled[cid] {
   148  				bid = cid
   149  				continue blockloop
   150  			}
   151  		}
   152  
   153  		// Still nothing, pick the unscheduled successor block encountered most recently.
   154  		for len(succs) > 0 {
   155  			// Pop an element from the tail of the queue.
   156  			cid := succs[len(succs)-1]
   157  			succs = succs[:len(succs)-1]
   158  			if !scheduled[cid] {
   159  				bid = cid
   160  				continue blockloop
   161  			}
   162  		}
   163  
   164  		// Still nothing, pick any non-exit block.
   165  		for posdegree.size() > 0 {
   166  			cid := posdegree.pop()
   167  			if !scheduled[cid] {
   168  				bid = cid
   169  				continue blockloop
   170  			}
   171  		}
   172  		// Pick any exit block.
   173  		// TODO: Order these to minimize jump distances?
   174  		for {
   175  			cid := exit.pop()
   176  			if !scheduled[cid] {
   177  				bid = cid
   178  				continue blockloop
   179  			}
   180  		}
   181  	}
   182  	f.laidout = true
   183  	return order
   184  	//f.Blocks = order
   185  }