LEFT | RIGHT |
(no file at all) | |
| 1 package ssa |
| 2 |
| 3 // An optional pass for sanity checking invariants of the SSA representation. |
| 4 // Currently it checks CFG invariants but little at the instruction level. |
| 5 |
| 6 import ( |
| 7 "bytes" |
| 8 "fmt" |
| 9 "io" |
| 10 "os" |
| 11 ) |
| 12 |
| 13 type sanity struct { |
| 14 reporter io.Writer |
| 15 fn *Function |
| 16 block *BasicBlock |
| 17 insane bool |
| 18 } |
| 19 |
| 20 // SanityCheck performs integrity checking of the SSA representation |
| 21 // of the function fn and returns true if it was valid. Diagnostics |
| 22 // are written to reporter if non-nil, os.Stderr otherwise. Some |
| 23 // diagnostics are only warnings and do not imply a negative result. |
| 24 // |
| 25 // Sanity checking is intended to facilitate the debugging of code |
| 26 // transformation passes. |
| 27 // |
| 28 func SanityCheck(fn *Function, reporter io.Writer) bool { |
| 29 if reporter == nil { |
| 30 reporter = os.Stderr |
| 31 } |
| 32 return (&sanity{reporter: reporter}).checkFunction(fn) |
| 33 } |
| 34 |
| 35 // MustSanityCheck is like SanityCheck but panics instead of returning |
| 36 // a negative result. |
| 37 // |
| 38 func MustSanityCheck(fn *Function, reporter io.Writer) { |
| 39 if !SanityCheck(fn, reporter) { |
| 40 panic("SanityCheck failed") |
| 41 } |
| 42 } |
| 43 |
| 44 // blockNames returns the names of the specified blocks as a |
| 45 // human-readable string. |
| 46 // |
| 47 func blockNames(blocks []*BasicBlock) string { |
| 48 var buf bytes.Buffer |
| 49 for i, b := range blocks { |
| 50 if i > 0 { |
| 51 io.WriteString(&buf, ", ") |
| 52 } |
| 53 io.WriteString(&buf, b.Name) |
| 54 } |
| 55 return buf.String() |
| 56 } |
| 57 |
| 58 func (s *sanity) diagnostic(prefix, format string, args ...interface{}) { |
| 59 fmt.Fprintf(s.reporter, "%s: function %s", prefix, s.fn.FullName()) |
| 60 if s.block != nil { |
| 61 fmt.Fprintf(s.reporter, ", block %s", s.block.Name) |
| 62 } |
| 63 io.WriteString(s.reporter, ": ") |
| 64 fmt.Fprintf(s.reporter, format, args...) |
| 65 io.WriteString(s.reporter, "\n") |
| 66 } |
| 67 |
| 68 func (s *sanity) errorf(format string, args ...interface{}) { |
| 69 s.insane = true |
| 70 s.diagnostic("Error", format, args...) |
| 71 } |
| 72 |
| 73 func (s *sanity) warnf(format string, args ...interface{}) { |
| 74 s.diagnostic("Warning", format, args...) |
| 75 } |
| 76 |
| 77 // findDuplicate returns an arbitrary basic block that appeared more |
| 78 // than once in blocks, or nil if all were unique. |
| 79 func findDuplicate(blocks []*BasicBlock) *BasicBlock { |
| 80 if len(blocks) < 2 { |
| 81 return nil |
| 82 } |
| 83 if blocks[0] == blocks[1] { |
| 84 return blocks[0] |
| 85 } |
| 86 // Slow path: |
| 87 m := make(map[*BasicBlock]bool) |
| 88 for _, b := range blocks { |
| 89 if m[b] { |
| 90 return b |
| 91 } |
| 92 m[b] = true |
| 93 } |
| 94 return nil |
| 95 } |
| 96 |
| 97 func (s *sanity) checkInstr(idx int, instr Instruction) { |
| 98 switch instr := instr.(type) { |
| 99 case *If, *Jump, *Ret: |
| 100 s.errorf("control flow instruction not at end of block") |
| 101 case *Phi: |
| 102 if idx == 0 { |
| 103 // It suffices to apply this check to just the first phi
node. |
| 104 if dup := findDuplicate(s.block.Preds); dup != nil { |
| 105 s.errorf("phi node in block with duplicate prede
cessor %s", dup.Name) |
| 106 } |
| 107 } else { |
| 108 prev := s.block.Instrs[idx-1] |
| 109 if _, ok := prev.(*Phi); !ok { |
| 110 s.errorf("Phi instruction follows a non-Phi: %T"
, prev) |
| 111 } |
| 112 } |
| 113 if ne, np := len(instr.Edges), len(s.block.Preds); ne != np { |
| 114 s.errorf("phi node has %d edges but %d predecessors", ne
, np) |
| 115 } |
| 116 |
| 117 case *Alloc: |
| 118 case *Call: |
| 119 case *BinOp: |
| 120 case *UnOp: |
| 121 case *MakeClosure: |
| 122 case *MakeChan: |
| 123 case *MakeMap: |
| 124 case *MakeSlice: |
| 125 case *Slice: |
| 126 case *Field: |
| 127 case *FieldAddr: |
| 128 case *IndexAddr: |
| 129 case *Index: |
| 130 case *Select: |
| 131 case *Range: |
| 132 case *TypeAssert: |
| 133 case *Extract: |
| 134 case *Go: |
| 135 case *Defer: |
| 136 case *Send: |
| 137 case *Store: |
| 138 case *MapUpdate: |
| 139 case *Next: |
| 140 case *Lookup: |
| 141 case *Conv: |
| 142 case *ChangeInterface: |
| 143 case *MakeInterface: |
| 144 // TODO(adonovan): implement checks. |
| 145 default: |
| 146 panic(fmt.Sprintf("Unknown instruction type: %T", instr)) |
| 147 } |
| 148 } |
| 149 |
| 150 func (s *sanity) checkFinalInstr(idx int, instr Instruction) { |
| 151 switch instr.(type) { |
| 152 case *If: |
| 153 if nsuccs := len(s.block.Succs); nsuccs != 2 { |
| 154 s.errorf("If-terminated block has %d successors; expecte
d 2", nsuccs) |
| 155 return |
| 156 } |
| 157 if s.block.Succs[0] == s.block.Succs[1] { |
| 158 s.errorf("If-instruction has same True, False target blo
cks: %s", s.block.Succs[0].Name) |
| 159 return |
| 160 } |
| 161 |
| 162 case *Jump: |
| 163 if nsuccs := len(s.block.Succs); nsuccs != 1 { |
| 164 s.errorf("Jump-terminated block has %d successors; expec
ted 1", nsuccs) |
| 165 return |
| 166 } |
| 167 |
| 168 case *Ret: |
| 169 if nsuccs := len(s.block.Succs); nsuccs != 0 { |
| 170 s.errorf("Ret-terminated block has %d successors; expect
ed none", nsuccs) |
| 171 return |
| 172 } |
| 173 // TODO(adonovan): check number and types of results |
| 174 |
| 175 default: |
| 176 s.errorf("non-control flow instruction at end of block") |
| 177 } |
| 178 } |
| 179 |
| 180 func (s *sanity) checkBlock(b *BasicBlock, isEntry bool) { |
| 181 s.block = b |
| 182 |
| 183 // Check all blocks are reachable. |
| 184 // (The entry block is always implicitly reachable.) |
| 185 if !isEntry && len(b.Preds) == 0 { |
| 186 s.warnf("unreachable block") |
| 187 if b.Instrs == nil { |
| 188 // Since this block is about to be pruned, |
| 189 // tolerating transient problems in it |
| 190 // simplifies other optimisations. |
| 191 return |
| 192 } |
| 193 } |
| 194 |
| 195 // Check predecessor and successor relations are dual. |
| 196 for _, a := range b.Preds { |
| 197 found := false |
| 198 for _, bb := range a.Succs { |
| 199 if bb == b { |
| 200 found = true |
| 201 break |
| 202 } |
| 203 } |
| 204 if !found { |
| 205 s.errorf("expected successor edge in predecessor %s; fou
nd only: %s", a.Name, blockNames(a.Succs)) |
| 206 } |
| 207 } |
| 208 for _, c := range b.Succs { |
| 209 found := false |
| 210 for _, bb := range c.Preds { |
| 211 if bb == b { |
| 212 found = true |
| 213 break |
| 214 } |
| 215 } |
| 216 if !found { |
| 217 s.errorf("expected predecessor edge in successor %s; fou
nd only: %s", c.Name, blockNames(c.Preds)) |
| 218 } |
| 219 } |
| 220 |
| 221 // Check each instruction is sane. |
| 222 n := len(b.Instrs) |
| 223 if n == 0 { |
| 224 s.errorf("basic block contains no instructions") |
| 225 } |
| 226 for j, instr := range b.Instrs { |
| 227 if b2 := instr.Block(); b2 == nil { |
| 228 s.errorf("nil Block() for instruction at index %d", j) |
| 229 continue |
| 230 } else if b2 != b { |
| 231 s.errorf("wrong Block() (%s) for instruction at index %d
", b2.Name, j) |
| 232 continue |
| 233 } |
| 234 if j < n-1 { |
| 235 s.checkInstr(j, instr) |
| 236 } else { |
| 237 s.checkFinalInstr(j, instr) |
| 238 } |
| 239 } |
| 240 } |
| 241 |
| 242 func (s *sanity) checkFunction(fn *Function) bool { |
| 243 // TODO(adonovan): check Function invariants: |
| 244 // - check owning Package (if any) contains this function. |
| 245 // - check params match signature |
| 246 // - check locals are all !Heap |
| 247 // - check transient fields are nil |
| 248 // - check block labels are unique (warning) |
| 249 s.fn = fn |
| 250 if fn.Prog == nil { |
| 251 s.errorf("nil Prog") |
| 252 } |
| 253 for i, b := range fn.Blocks { |
| 254 if b == nil { |
| 255 s.warnf("nil *BasicBlock at f.Blocks[%d]", i) |
| 256 continue |
| 257 } |
| 258 s.checkBlock(b, i == 0) |
| 259 } |
| 260 s.block = nil |
| 261 s.fn = nil |
| 262 return !s.insane |
| 263 } |
LEFT | RIGHT |