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