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