github.com/go-asm/go@v1.21.1-0.20240213172139-40c5ead50c48/cmd/compile/ssa/dom.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  // This file contains code to compute the dominator tree
     8  // of a control-flow graph.
     9  
    10  // postorder computes a postorder traversal ordering for the
    11  // basic blocks in f. Unreachable blocks will not appear.
    12  func postorder(f *Func) []*Block {
    13  	return postorderWithNumbering(f, nil)
    14  }
    15  
    16  type blockAndIndex struct {
    17  	b     *Block
    18  	index int // index is the number of successor edges of b that have already been explored.
    19  }
    20  
    21  // postorderWithNumbering provides a DFS postordering.
    22  // This seems to make loop-finding more robust.
    23  func postorderWithNumbering(f *Func, ponums []int32) []*Block {
    24  	seen := f.Cache.allocBoolSlice(f.NumBlocks())
    25  	defer f.Cache.freeBoolSlice(seen)
    26  
    27  	// result ordering
    28  	order := make([]*Block, 0, len(f.Blocks))
    29  
    30  	// stack of blocks and next child to visit
    31  	// A constant bound allows this to be stack-allocated. 32 is
    32  	// enough to cover almost every postorderWithNumbering call.
    33  	s := make([]blockAndIndex, 0, 32)
    34  	s = append(s, blockAndIndex{b: f.Entry})
    35  	seen[f.Entry.ID] = true
    36  	for len(s) > 0 {
    37  		tos := len(s) - 1
    38  		x := s[tos]
    39  		b := x.b
    40  		if i := x.index; i < len(b.Succs) {
    41  			s[tos].index++
    42  			bb := b.Succs[i].Block()
    43  			if !seen[bb.ID] {
    44  				seen[bb.ID] = true
    45  				s = append(s, blockAndIndex{b: bb})
    46  			}
    47  			continue
    48  		}
    49  		s = s[:tos]
    50  		if ponums != nil {
    51  			ponums[b.ID] = int32(len(order))
    52  		}
    53  		order = append(order, b)
    54  	}
    55  	return order
    56  }
    57  
    58  type linkedBlocks func(*Block) []Edge
    59  
    60  func dominators(f *Func) []*Block {
    61  	preds := func(b *Block) []Edge { return b.Preds }
    62  	succs := func(b *Block) []Edge { return b.Succs }
    63  
    64  	//TODO: benchmark and try to find criteria for swapping between
    65  	// dominatorsSimple and dominatorsLT
    66  	return f.dominatorsLTOrig(f.Entry, preds, succs)
    67  }
    68  
    69  // dominatorsLTOrig runs Lengauer-Tarjan to compute a dominator tree starting at
    70  // entry and using predFn/succFn to find predecessors/successors to allow
    71  // computing both dominator and post-dominator trees.
    72  func (f *Func) dominatorsLTOrig(entry *Block, predFn linkedBlocks, succFn linkedBlocks) []*Block {
    73  	// Adapted directly from the original TOPLAS article's "simple" algorithm
    74  
    75  	maxBlockID := entry.Func.NumBlocks()
    76  	scratch := f.Cache.allocIDSlice(7 * maxBlockID)
    77  	defer f.Cache.freeIDSlice(scratch)
    78  	semi := scratch[0*maxBlockID : 1*maxBlockID]
    79  	vertex := scratch[1*maxBlockID : 2*maxBlockID]
    80  	label := scratch[2*maxBlockID : 3*maxBlockID]
    81  	parent := scratch[3*maxBlockID : 4*maxBlockID]
    82  	ancestor := scratch[4*maxBlockID : 5*maxBlockID]
    83  	bucketHead := scratch[5*maxBlockID : 6*maxBlockID]
    84  	bucketLink := scratch[6*maxBlockID : 7*maxBlockID]
    85  
    86  	// This version uses integers for most of the computation,
    87  	// to make the work arrays smaller and pointer-free.
    88  	// fromID translates from ID to *Block where that is needed.
    89  	fromID := f.Cache.allocBlockSlice(maxBlockID)
    90  	defer f.Cache.freeBlockSlice(fromID)
    91  	for _, v := range f.Blocks {
    92  		fromID[v.ID] = v
    93  	}
    94  	idom := make([]*Block, maxBlockID)
    95  
    96  	// Step 1. Carry out a depth first search of the problem graph. Number
    97  	// the vertices from 1 to n as they are reached during the search.
    98  	n := f.dfsOrig(entry, succFn, semi, vertex, label, parent)
    99  
   100  	for i := n; i >= 2; i-- {
   101  		w := vertex[i]
   102  
   103  		// step2 in TOPLAS paper
   104  		for _, e := range predFn(fromID[w]) {
   105  			v := e.b
   106  			if semi[v.ID] == 0 {
   107  				// skip unreachable predecessor
   108  				// not in original, but we're using existing pred instead of building one.
   109  				continue
   110  			}
   111  			u := evalOrig(v.ID, ancestor, semi, label)
   112  			if semi[u] < semi[w] {
   113  				semi[w] = semi[u]
   114  			}
   115  		}
   116  
   117  		// add w to bucket[vertex[semi[w]]]
   118  		// implement bucket as a linked list implemented
   119  		// in a pair of arrays.
   120  		vsw := vertex[semi[w]]
   121  		bucketLink[w] = bucketHead[vsw]
   122  		bucketHead[vsw] = w
   123  
   124  		linkOrig(parent[w], w, ancestor)
   125  
   126  		// step3 in TOPLAS paper
   127  		for v := bucketHead[parent[w]]; v != 0; v = bucketLink[v] {
   128  			u := evalOrig(v, ancestor, semi, label)
   129  			if semi[u] < semi[v] {
   130  				idom[v] = fromID[u]
   131  			} else {
   132  				idom[v] = fromID[parent[w]]
   133  			}
   134  		}
   135  	}
   136  	// step 4 in toplas paper
   137  	for i := ID(2); i <= n; i++ {
   138  		w := vertex[i]
   139  		if idom[w].ID != vertex[semi[w]] {
   140  			idom[w] = idom[idom[w].ID]
   141  		}
   142  	}
   143  
   144  	return idom
   145  }
   146  
   147  // dfsOrig performs a depth first search over the blocks starting at entry block
   148  // (in arbitrary order).  This is a de-recursed version of dfs from the
   149  // original Tarjan-Lengauer TOPLAS article.  It's important to return the
   150  // same values for parent as the original algorithm.
   151  func (f *Func) dfsOrig(entry *Block, succFn linkedBlocks, semi, vertex, label, parent []ID) ID {
   152  	n := ID(0)
   153  	s := make([]*Block, 0, 256)
   154  	s = append(s, entry)
   155  
   156  	for len(s) > 0 {
   157  		v := s[len(s)-1]
   158  		s = s[:len(s)-1]
   159  		// recursing on v
   160  
   161  		if semi[v.ID] != 0 {
   162  			continue // already visited
   163  		}
   164  		n++
   165  		semi[v.ID] = n
   166  		vertex[n] = v.ID
   167  		label[v.ID] = v.ID
   168  		// ancestor[v] already zero
   169  		for _, e := range succFn(v) {
   170  			w := e.b
   171  			// if it has a dfnum, we've already visited it
   172  			if semi[w.ID] == 0 {
   173  				// yes, w can be pushed multiple times.
   174  				s = append(s, w)
   175  				parent[w.ID] = v.ID // keep overwriting this till it is visited.
   176  			}
   177  		}
   178  	}
   179  	return n
   180  }
   181  
   182  // compressOrig is the "simple" compress function from LT paper.
   183  func compressOrig(v ID, ancestor, semi, label []ID) {
   184  	if ancestor[ancestor[v]] != 0 {
   185  		compressOrig(ancestor[v], ancestor, semi, label)
   186  		if semi[label[ancestor[v]]] < semi[label[v]] {
   187  			label[v] = label[ancestor[v]]
   188  		}
   189  		ancestor[v] = ancestor[ancestor[v]]
   190  	}
   191  }
   192  
   193  // evalOrig is the "simple" eval function from LT paper.
   194  func evalOrig(v ID, ancestor, semi, label []ID) ID {
   195  	if ancestor[v] == 0 {
   196  		return v
   197  	}
   198  	compressOrig(v, ancestor, semi, label)
   199  	return label[v]
   200  }
   201  
   202  func linkOrig(v, w ID, ancestor []ID) {
   203  	ancestor[w] = v
   204  }
   205  
   206  // dominatorsSimple computes the dominator tree for f. It returns a slice
   207  // which maps block ID to the immediate dominator of that block.
   208  // Unreachable blocks map to nil. The entry block maps to nil.
   209  func dominatorsSimple(f *Func) []*Block {
   210  	// A simple algorithm for now
   211  	// Cooper, Harvey, Kennedy
   212  	idom := make([]*Block, f.NumBlocks())
   213  
   214  	// Compute postorder walk
   215  	post := f.postorder()
   216  
   217  	// Make map from block id to order index (for intersect call)
   218  	postnum := f.Cache.allocIntSlice(f.NumBlocks())
   219  	defer f.Cache.freeIntSlice(postnum)
   220  	for i, b := range post {
   221  		postnum[b.ID] = i
   222  	}
   223  
   224  	// Make the entry block a self-loop
   225  	idom[f.Entry.ID] = f.Entry
   226  	if postnum[f.Entry.ID] != len(post)-1 {
   227  		f.Fatalf("entry block %v not last in postorder", f.Entry)
   228  	}
   229  
   230  	// Compute relaxation of idom entries
   231  	for {
   232  		changed := false
   233  
   234  		for i := len(post) - 2; i >= 0; i-- {
   235  			b := post[i]
   236  			var d *Block
   237  			for _, e := range b.Preds {
   238  				p := e.b
   239  				if idom[p.ID] == nil {
   240  					continue
   241  				}
   242  				if d == nil {
   243  					d = p
   244  					continue
   245  				}
   246  				d = intersect(d, p, postnum, idom)
   247  			}
   248  			if d != idom[b.ID] {
   249  				idom[b.ID] = d
   250  				changed = true
   251  			}
   252  		}
   253  		if !changed {
   254  			break
   255  		}
   256  	}
   257  	// Set idom of entry block to nil instead of itself.
   258  	idom[f.Entry.ID] = nil
   259  	return idom
   260  }
   261  
   262  // intersect finds the closest dominator of both b and c.
   263  // It requires a postorder numbering of all the blocks.
   264  func intersect(b, c *Block, postnum []int, idom []*Block) *Block {
   265  	// TODO: This loop is O(n^2). It used to be used in nilcheck,
   266  	// see BenchmarkNilCheckDeep*.
   267  	for b != c {
   268  		if postnum[b.ID] < postnum[c.ID] {
   269  			b = idom[b.ID]
   270  		} else {
   271  			c = idom[c.ID]
   272  		}
   273  	}
   274  	return b
   275  }