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