github.com/golangci/go-tools@v0.0.0-20190318060251-af6baa5dc196/ssa/blockopt.go (about)

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