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