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