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