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