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