github.com/FenixAra/go@v0.0.0-20170127160404-96ea0918e670/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  	"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  }