blockopt.go raw

   1  // Copyright 2013 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  // Simple block optimizations to simplify the control flow graph.
   8  
   9  // TODO(adonovan): opt: instead of creating several "unreachable" blocks
  10  // per function in the Builder, reuse a single one (e.g. at Blocks[1])
  11  // to reduce garbage.
  12  
  13  import (
  14  	"fmt"
  15  	"os"
  16  )
  17  
  18  // If true, perform sanity checking and show progress at each
  19  // successive iteration of optimizeBlocks.  Very verbose.
  20  const debugBlockOpt = false
  21  
  22  // markReachable sets Index=-1 for all blocks reachable from b.
  23  func markReachable(b *BasicBlock) {
  24  	b.Index = -1
  25  	for _, succ := range b.Succs {
  26  		if succ.Index == 0 {
  27  			markReachable(succ)
  28  		}
  29  	}
  30  }
  31  
  32  // deleteUnreachableBlocks marks all reachable blocks of f and
  33  // eliminates (nils) all others, including possibly cyclic subgraphs.
  34  func deleteUnreachableBlocks(f *Function) {
  35  	const white, black = 0, -1
  36  	// We borrow b.Index temporarily as the mark bit.
  37  	for _, b := range f.Blocks {
  38  		b.Index = white
  39  	}
  40  	markReachable(f.Blocks[0])
  41  	if f.Recover != nil {
  42  		markReachable(f.Recover)
  43  	}
  44  	for i, b := range f.Blocks {
  45  		if b.Index == white {
  46  			for _, c := range b.Succs {
  47  				if c.Index == black {
  48  					c.removePred(b) // delete white->black edge
  49  				}
  50  			}
  51  			if debugBlockOpt {
  52  				fmt.Fprintln(os.Stderr, "unreachable", b)
  53  			}
  54  			f.Blocks[i] = nil // delete b
  55  		}
  56  	}
  57  	f.removeNilBlocks()
  58  }
  59  
  60  // jumpThreading attempts to apply simple jump-threading to block b,
  61  // in which a->b->c become a->c if b is just a Jump.
  62  // The result is true if the optimization was applied.
  63  func jumpThreading(f *Function, b *BasicBlock) bool {
  64  	if b.Index == 0 {
  65  		return false // don't apply to entry block
  66  	}
  67  	if b.Instrs == nil {
  68  		return false
  69  	}
  70  	if _, ok := b.Instrs[0].(*Jump); !ok {
  71  		return false // not just a jump
  72  	}
  73  	c := b.Succs[0]
  74  	if c == b {
  75  		return false // don't apply to degenerate jump-to-self.
  76  	}
  77  	if c.hasPhi() {
  78  		return false // not sound without more effort
  79  	}
  80  	for j, a := range b.Preds {
  81  		a.replaceSucc(b, c)
  82  
  83  		// If a now has two edges to c, replace its degenerate If by Jump.
  84  		if len(a.Succs) == 2 && a.Succs[0] == c && a.Succs[1] == c {
  85  			jump := new(Jump)
  86  			jump.setBlock(a)
  87  			a.Instrs[len(a.Instrs)-1] = jump
  88  			a.Succs = a.Succs[:1]
  89  			c.removePred(b)
  90  		} else {
  91  			if j == 0 {
  92  				c.replacePred(b, a)
  93  			} else {
  94  				c.Preds = append(c.Preds, a)
  95  			}
  96  		}
  97  
  98  		if debugBlockOpt {
  99  			fmt.Fprintln(os.Stderr, "jumpThreading", a, b, c)
 100  		}
 101  	}
 102  	f.Blocks[b.Index] = nil // delete b
 103  	return true
 104  }
 105  
 106  // fuseBlocks attempts to apply the block fusion optimization to block
 107  // a, in which a->b becomes ab if len(a.Succs)==len(b.Preds)==1.
 108  // The result is true if the optimization was applied.
 109  func fuseBlocks(f *Function, a *BasicBlock) bool {
 110  	if len(a.Succs) != 1 {
 111  		return false
 112  	}
 113  	b := a.Succs[0]
 114  	if len(b.Preds) != 1 {
 115  		return false
 116  	}
 117  
 118  	// Degenerate &&/|| ops may result in a straight-line CFG
 119  	// containing φ-nodes. (Ideally we'd replace such them with
 120  	// their sole operand but that requires Referrers, built later.)
 121  	if b.hasPhi() {
 122  		return false // not sound without further effort
 123  	}
 124  
 125  	// Eliminate jump at end of A, then copy all of B across.
 126  	a.Instrs = append(a.Instrs[:len(a.Instrs)-1], b.Instrs...)
 127  	for _, instr := range b.Instrs {
 128  		instr.setBlock(a)
 129  	}
 130  
 131  	// A inherits B's successors
 132  	a.Succs = append(a.succs2[:0], b.Succs...)
 133  
 134  	// Fix up Preds links of all successors of B.
 135  	for _, c := range b.Succs {
 136  		c.replacePred(b, a)
 137  	}
 138  
 139  	if debugBlockOpt {
 140  		fmt.Fprintln(os.Stderr, "fuseBlocks", a, b)
 141  	}
 142  
 143  	f.Blocks[b.Index] = nil // delete b
 144  	return true
 145  }
 146  
 147  // optimizeBlocks() performs some simple block optimizations on a
 148  // completed function: dead block elimination, block fusion, jump
 149  // threading.
 150  func optimizeBlocks(f *Function) {
 151  	deleteUnreachableBlocks(f)
 152  
 153  	// Loop until no further progress.
 154  	changed := true
 155  	for changed {
 156  		changed = false
 157  
 158  		if debugBlockOpt {
 159  			f.WriteTo(os.Stderr)
 160  			mustSanityCheck(f, nil)
 161  		}
 162  
 163  		for _, b := range f.Blocks {
 164  			// f.Blocks will temporarily contain nils to indicate
 165  			// deleted blocks; we remove them at the end.
 166  			if b == nil {
 167  				continue
 168  			}
 169  
 170  			// Fuse blocks.  b->c becomes bc.
 171  			if fuseBlocks(f, b) {
 172  				changed = true
 173  			}
 174  
 175  			// a->b->c becomes a->c if b contains only a Jump.
 176  			if jumpThreading(f, b) {
 177  				changed = true
 178  				continue // (b was disconnected)
 179  			}
 180  		}
 181  	}
 182  	f.removeNilBlocks()
 183  }
 184