github.com/yukk001/go1.10.8@v0.0.0-20190813125351-6df2d3982e20/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  	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  }