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