github.com/dannin/go@v0.0.0-20161031215817-d35dfd405eaa/src/cmd/compile/internal/ssa/compile.go (about) 1 // Copyright 2015 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 import ( 8 "fmt" 9 "log" 10 "os" 11 "regexp" 12 "runtime" 13 "strings" 14 "time" 15 ) 16 17 // Compile is the main entry point for this package. 18 // Compile modifies f so that on return: 19 // · all Values in f map to 0 or 1 assembly instructions of the target architecture 20 // · the order of f.Blocks is the order to emit the Blocks 21 // · the order of b.Values is the order to emit the Values in each Block 22 // · f has a non-nil regAlloc field 23 func Compile(f *Func) { 24 // TODO: debugging - set flags to control verbosity of compiler, 25 // which phases to dump IR before/after, etc. 26 if f.Log() { 27 f.Logf("compiling %s\n", f.Name) 28 } 29 30 // hook to print function & phase if panic happens 31 phaseName := "init" 32 defer func() { 33 if phaseName != "" { 34 err := recover() 35 stack := make([]byte, 16384) 36 n := runtime.Stack(stack, false) 37 stack = stack[:n] 38 f.Fatalf("panic during %s while compiling %s:\n\n%v\n\n%s\n", phaseName, f.Name, err, stack) 39 } 40 }() 41 42 // Run all the passes 43 printFunc(f) 44 f.Config.HTML.WriteFunc("start", f) 45 if BuildDump != "" && BuildDump == f.Name { 46 f.dumpFile("build") 47 } 48 if checkEnabled { 49 checkFunc(f) 50 } 51 const logMemStats = false 52 for _, p := range passes { 53 if !f.Config.optimize && !p.required || p.disabled { 54 continue 55 } 56 f.pass = &p 57 phaseName = p.name 58 if f.Log() { 59 f.Logf(" pass %s begin\n", p.name) 60 } 61 // TODO: capture logging during this pass, add it to the HTML 62 var mStart runtime.MemStats 63 if logMemStats || p.mem { 64 runtime.ReadMemStats(&mStart) 65 } 66 67 tStart := time.Now() 68 p.fn(f) 69 tEnd := time.Now() 70 71 // Need something less crude than "Log the whole intermediate result". 72 if f.Log() || f.Config.HTML != nil { 73 time := tEnd.Sub(tStart).Nanoseconds() 74 var stats string 75 if logMemStats { 76 var mEnd runtime.MemStats 77 runtime.ReadMemStats(&mEnd) 78 nBytes := mEnd.TotalAlloc - mStart.TotalAlloc 79 nAllocs := mEnd.Mallocs - mStart.Mallocs 80 stats = fmt.Sprintf("[%d ns %d allocs %d bytes]", time, nAllocs, nBytes) 81 } else { 82 stats = fmt.Sprintf("[%d ns]", time) 83 } 84 85 f.Logf(" pass %s end %s\n", p.name, stats) 86 printFunc(f) 87 f.Config.HTML.WriteFunc(fmt.Sprintf("after %s <span class=\"stats\">%s</span>", phaseName, stats), f) 88 } 89 if p.time || p.mem { 90 // Surround timing information w/ enough context to allow comparisons. 91 time := tEnd.Sub(tStart).Nanoseconds() 92 if p.time { 93 f.LogStat("TIME(ns)", time) 94 } 95 if p.mem { 96 var mEnd runtime.MemStats 97 runtime.ReadMemStats(&mEnd) 98 nBytes := mEnd.TotalAlloc - mStart.TotalAlloc 99 nAllocs := mEnd.Mallocs - mStart.Mallocs 100 f.LogStat("TIME(ns):BYTES:ALLOCS", time, nBytes, nAllocs) 101 } 102 } 103 if p.dump != nil && p.dump[f.Name] { 104 // Dump function to appropriately named file 105 f.dumpFile(phaseName) 106 } 107 if checkEnabled { 108 checkFunc(f) 109 } 110 } 111 112 // Squash error printing defer 113 phaseName = "" 114 } 115 116 // TODO: should be a config field 117 var dumpFileSeq int 118 119 // dumpFile creates a file from the phase name and function name 120 // Dumping is done to files to avoid buffering huge strings before 121 // output. 122 func (f *Func) dumpFile(phaseName string) { 123 dumpFileSeq++ 124 fname := fmt.Sprintf("%s__%s_%d.dump", phaseName, f.Name, dumpFileSeq) 125 fname = strings.Replace(fname, " ", "_", -1) 126 fname = strings.Replace(fname, "/", "_", -1) 127 fname = strings.Replace(fname, ":", "_", -1) 128 129 fi, err := os.Create(fname) 130 if err != nil { 131 f.Config.Warnl(0, "Unable to create after-phase dump file %s", fname) 132 return 133 } 134 135 p := stringFuncPrinter{w: fi} 136 fprintFunc(p, f) 137 fi.Close() 138 } 139 140 type pass struct { 141 name string 142 fn func(*Func) 143 required bool 144 disabled bool 145 time bool // report time to run pass 146 mem bool // report mem stats to run pass 147 stats int // pass reports own "stats" (e.g., branches removed) 148 debug int // pass performs some debugging. =1 should be in error-testing-friendly Warnl format. 149 test int // pass-specific ad-hoc option, perhaps useful in development 150 dump map[string]bool // dump if function name matches 151 } 152 153 func (p *pass) addDump(s string) { 154 if p.dump == nil { 155 p.dump = make(map[string]bool) 156 } 157 p.dump[s] = true 158 } 159 160 // Run consistency checker between each phase 161 var checkEnabled = false 162 163 // Debug output 164 var IntrinsicsDebug int 165 var IntrinsicsDisable bool 166 167 var BuildDebug int 168 var BuildTest int 169 var BuildStats int 170 var BuildDump string // name of function to dump after initial build of ssa 171 172 // PhaseOption sets the specified flag in the specified ssa phase, 173 // returning empty string if this was successful or a string explaining 174 // the error if it was not. 175 // A version of the phase name with "_" replaced by " " is also checked for a match. 176 // If the phase name begins a '~' then the rest of the underscores-replaced-with-blanks 177 // version is used as a regular expression to match the phase name(s). 178 // 179 // Special cases that have turned out to be useful: 180 // ssa/check/on enables checking after each phase 181 // ssa/all/time enables time reporting for all phases 182 // 183 // See gc/lex.go for dissection of the option string. 184 // Example uses: 185 // 186 // GO_GCFLAGS=-d=ssa/generic_cse/time,ssa/generic_cse/stats,ssa/generic_cse/debug=3 ./make.bash 187 // 188 // BOOT_GO_GCFLAGS=-d='ssa/~^.*scc$/off' GO_GCFLAGS='-d=ssa/~^.*scc$/off' ./make.bash 189 // 190 func PhaseOption(phase, flag string, val int, valString string) string { 191 if phase == "help" { 192 lastcr := 0 193 phasenames := "check, all, build, intrinsics" 194 for _, p := range passes { 195 pn := strings.Replace(p.name, " ", "_", -1) 196 if len(pn)+len(phasenames)-lastcr > 70 { 197 phasenames += "\n" 198 lastcr = len(phasenames) 199 phasenames += pn 200 } else { 201 phasenames += ", " + pn 202 } 203 } 204 return "" + 205 `GcFlag -d=ssa/<phase>/<flag>[=<value>]|[:<function_name>] 206 <phase> is one of: 207 ` + phasenames + ` 208 <flag> is one of on, off, debug, mem, time, test, stats, dump 209 <value> defaults to 1 210 <function_name> is required for "dump", specifies name of function to dump after <phase> 211 Except for dump, output is directed to standard out; dump appears in a file. 212 Phase "all" supports flags "time", "mem", and "dump". 213 Phases "intrinsics" supports flags "on", "off", and "debug". 214 Interpretation of the "debug" value depends on the phase. 215 Dump files are named <phase>__<function_name>_<seq>.dump. 216 ` 217 } 218 219 if phase == "check" && flag == "on" { 220 checkEnabled = val != 0 221 return "" 222 } 223 if phase == "check" && flag == "off" { 224 checkEnabled = val == 0 225 return "" 226 } 227 228 alltime := false 229 allmem := false 230 alldump := false 231 if phase == "all" { 232 if flag == "time" { 233 alltime = val != 0 234 } else if flag == "mem" { 235 allmem = val != 0 236 } else if flag == "dump" { 237 alldump = val != 0 238 if alldump { 239 BuildDump = valString 240 } 241 } else { 242 return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase) 243 } 244 } 245 246 if phase == "intrinsics" { 247 switch flag { 248 case "on": 249 IntrinsicsDisable = val == 0 250 case "off": 251 IntrinsicsDisable = val != 0 252 case "debug": 253 IntrinsicsDebug = val 254 default: 255 return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase) 256 } 257 return "" 258 } 259 if phase == "build" { 260 switch flag { 261 case "debug": 262 BuildDebug = val 263 case "test": 264 BuildTest = val 265 case "stats": 266 BuildStats = val 267 case "dump": 268 BuildDump = valString 269 default: 270 return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase) 271 } 272 return "" 273 } 274 275 underphase := strings.Replace(phase, "_", " ", -1) 276 var re *regexp.Regexp 277 if phase[0] == '~' { 278 r, ok := regexp.Compile(underphase[1:]) 279 if ok != nil { 280 return fmt.Sprintf("Error %s in regexp for phase %s, flag %s", ok.Error(), phase, flag) 281 } 282 re = r 283 } 284 matchedOne := false 285 for i, p := range passes { 286 if phase == "all" { 287 p.time = alltime 288 p.mem = allmem 289 if alldump { 290 p.addDump(valString) 291 } 292 passes[i] = p 293 matchedOne = true 294 } else if p.name == phase || p.name == underphase || re != nil && re.MatchString(p.name) { 295 switch flag { 296 case "on": 297 p.disabled = val == 0 298 case "off": 299 p.disabled = val != 0 300 case "time": 301 p.time = val != 0 302 case "mem": 303 p.mem = val != 0 304 case "debug": 305 p.debug = val 306 case "stats": 307 p.stats = val 308 case "test": 309 p.test = val 310 case "dump": 311 p.addDump(valString) 312 default: 313 return fmt.Sprintf("Did not find a flag matching %s in -d=ssa/%s debug option", flag, phase) 314 } 315 if p.disabled && p.required { 316 return fmt.Sprintf("Cannot disable required SSA phase %s using -d=ssa/%s debug option", phase, phase) 317 } 318 passes[i] = p 319 matchedOne = true 320 } 321 } 322 if matchedOne { 323 return "" 324 } 325 return fmt.Sprintf("Did not find a phase matching %s in -d=ssa/... debug option", phase) 326 } 327 328 // list of passes for the compiler 329 var passes = [...]pass{ 330 // TODO: combine phielim and copyelim into a single pass? 331 {name: "early phielim", fn: phielim}, 332 {name: "early copyelim", fn: copyelim}, 333 {name: "early deadcode", fn: deadcode}, // remove generated dead code to avoid doing pointless work during opt 334 {name: "short circuit", fn: shortcircuit}, 335 {name: "decompose user", fn: decomposeUser, required: true}, 336 {name: "opt", fn: opt, required: true}, // TODO: split required rules and optimizing rules 337 {name: "zero arg cse", fn: zcse, required: true}, // required to merge OpSB values 338 {name: "opt deadcode", fn: deadcode, required: true}, // remove any blocks orphaned during opt 339 {name: "generic cse", fn: cse}, 340 {name: "phiopt", fn: phiopt}, 341 {name: "nilcheckelim", fn: nilcheckelim}, 342 {name: "prove", fn: prove}, 343 {name: "loopbce", fn: loopbce}, 344 {name: "decompose builtin", fn: decomposeBuiltIn, required: true}, 345 {name: "dec", fn: dec, required: true}, 346 {name: "late opt", fn: opt, required: true}, // TODO: split required rules and optimizing rules 347 {name: "generic deadcode", fn: deadcode}, 348 {name: "check bce", fn: checkbce}, 349 {name: "writebarrier", fn: writebarrier, required: true}, // expand write barrier ops 350 {name: "fuse", fn: fuse}, 351 {name: "dse", fn: dse}, 352 {name: "tighten", fn: tighten}, // move values closer to their uses 353 {name: "lower", fn: lower, required: true}, 354 {name: "lowered cse", fn: cse}, 355 {name: "lowered deadcode", fn: deadcode, required: true}, 356 {name: "checkLower", fn: checkLower, required: true}, 357 {name: "late phielim", fn: phielim}, 358 {name: "late copyelim", fn: copyelim}, 359 {name: "phi tighten", fn: phiTighten}, 360 {name: "late deadcode", fn: deadcode}, 361 {name: "critical", fn: critical, required: true}, // remove critical edges 362 {name: "likelyadjust", fn: likelyadjust}, 363 {name: "layout", fn: layout, required: true}, // schedule blocks 364 {name: "schedule", fn: schedule, required: true}, // schedule values 365 {name: "late nilcheck", fn: nilcheckelim2}, 366 {name: "flagalloc", fn: flagalloc, required: true}, // allocate flags register 367 {name: "regalloc", fn: regalloc, required: true}, // allocate int & float registers + stack slots 368 {name: "stackframe", fn: stackframe, required: true}, 369 {name: "trim", fn: trim}, // remove empty blocks 370 } 371 372 // Double-check phase ordering constraints. 373 // This code is intended to document the ordering requirements 374 // between different phases. It does not override the passes 375 // list above. 376 type constraint struct { 377 a, b string // a must come before b 378 } 379 380 var passOrder = [...]constraint{ 381 // prove reliese on common-subexpression elimination for maximum benefits. 382 {"generic cse", "prove"}, 383 // deadcode after prove to eliminate all new dead blocks. 384 {"prove", "generic deadcode"}, 385 // common-subexpression before dead-store elim, so that we recognize 386 // when two address expressions are the same. 387 {"generic cse", "dse"}, 388 // cse substantially improves nilcheckelim efficacy 389 {"generic cse", "nilcheckelim"}, 390 // allow deadcode to clean up after nilcheckelim 391 {"nilcheckelim", "generic deadcode"}, 392 // nilcheckelim generates sequences of plain basic blocks 393 {"nilcheckelim", "fuse"}, 394 // nilcheckelim relies on opt to rewrite user nil checks 395 {"opt", "nilcheckelim"}, 396 // tighten should happen before lowering to avoid splitting naturally paired instructions such as CMP/SET 397 {"tighten", "lower"}, 398 // tighten will be most effective when as many values have been removed as possible 399 {"generic deadcode", "tighten"}, 400 {"generic cse", "tighten"}, 401 // checkbce needs the values removed 402 {"generic deadcode", "check bce"}, 403 // don't run optimization pass until we've decomposed builtin objects 404 {"decompose builtin", "late opt"}, 405 // don't layout blocks until critical edges have been removed 406 {"critical", "layout"}, 407 // regalloc requires the removal of all critical edges 408 {"critical", "regalloc"}, 409 // regalloc requires all the values in a block to be scheduled 410 {"schedule", "regalloc"}, 411 // checkLower must run after lowering & subsequent dead code elim 412 {"lower", "checkLower"}, 413 {"lowered deadcode", "checkLower"}, 414 // late nilcheck needs instructions to be scheduled. 415 {"schedule", "late nilcheck"}, 416 // flagalloc needs instructions to be scheduled. 417 {"schedule", "flagalloc"}, 418 // regalloc needs flags to be allocated first. 419 {"flagalloc", "regalloc"}, 420 // stackframe needs to know about spilled registers. 421 {"regalloc", "stackframe"}, 422 // trim needs regalloc to be done first. 423 {"regalloc", "trim"}, 424 } 425 426 func init() { 427 for _, c := range passOrder { 428 a, b := c.a, c.b 429 i := -1 430 j := -1 431 for k, p := range passes { 432 if p.name == a { 433 i = k 434 } 435 if p.name == b { 436 j = k 437 } 438 } 439 if i < 0 { 440 log.Panicf("pass %s not found", a) 441 } 442 if j < 0 { 443 log.Panicf("pass %s not found", b) 444 } 445 if i >= j { 446 log.Panicf("passes %s and %s out of order", a, b) 447 } 448 } 449 }