Home | History | Annotate | Download | only in ssa
      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