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 }