gitee.com/wgliang/goreporter@v0.0.0-20180902115603-df1b20f7c5d0/linters/simpler/ssa/lift.go (about)

     1  // Copyright 2013 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  // +build go1.5
     6  
     7  package ssa
     8  
     9  // This file defines the lifting pass which tries to "lift" Alloc
    10  // cells (new/local variables) into SSA registers, replacing loads
    11  // with the dominating stored value, eliminating loads and stores, and
    12  // inserting φ-nodes as needed.
    13  
    14  // Cited papers and resources:
    15  //
    16  // Ron Cytron et al. 1991. Efficiently computing SSA form...
    17  // http://doi.acm.org/10.1145/115372.115320
    18  //
    19  // Cooper, Harvey, Kennedy.  2001.  A Simple, Fast Dominance Algorithm.
    20  // Software Practice and Experience 2001, 4:1-10.
    21  // http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
    22  //
    23  // Daniel Berlin, llvmdev mailing list, 2012.
    24  // http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
    25  // (Be sure to expand the whole thread.)
    26  
    27  // TODO(adonovan): opt: there are many optimizations worth evaluating, and
    28  // the conventional wisdom for SSA construction is that a simple
    29  // algorithm well engineered often beats those of better asymptotic
    30  // complexity on all but the most egregious inputs.
    31  //
    32  // Danny Berlin suggests that the Cooper et al. algorithm for
    33  // computing the dominance frontier is superior to Cytron et al.
    34  // Furthermore he recommends that rather than computing the DF for the
    35  // whole function then renaming all alloc cells, it may be cheaper to
    36  // compute the DF for each alloc cell separately and throw it away.
    37  //
    38  // Consider exploiting liveness information to avoid creating dead
    39  // φ-nodes which we then immediately remove.
    40  //
    41  // Integrate lifting with scalar replacement of aggregates (SRA) since
    42  // the two are synergistic.
    43  //
    44  // Also see many other "TODO: opt" suggestions in the code.
    45  
    46  import (
    47  	"fmt"
    48  	"go/token"
    49  	"go/types"
    50  	"math/big"
    51  	"os"
    52  )
    53  
    54  // If true, perform sanity checking and show diagnostic information at
    55  // each step of lifting.  Very verbose.
    56  const debugLifting = false
    57  
    58  // domFrontier maps each block to the set of blocks in its dominance
    59  // frontier.  The outer slice is conceptually a map keyed by
    60  // Block.Index.  The inner slice is conceptually a set, possibly
    61  // containing duplicates.
    62  //
    63  // TODO(adonovan): opt: measure impact of dups; consider a packed bit
    64  // representation, e.g. big.Int, and bitwise parallel operations for
    65  // the union step in the Children loop.
    66  //
    67  // domFrontier's methods mutate the slice's elements but not its
    68  // length, so their receivers needn't be pointers.
    69  //
    70  type domFrontier [][]*BasicBlock
    71  
    72  func (df domFrontier) add(u, v *BasicBlock) {
    73  	p := &df[u.Index]
    74  	*p = append(*p, v)
    75  }
    76  
    77  // build builds the dominance frontier df for the dominator (sub)tree
    78  // rooted at u, using the Cytron et al. algorithm.
    79  //
    80  // TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
    81  // by pruning the entire IDF computation, rather than merely pruning
    82  // the DF -> IDF step.
    83  func (df domFrontier) build(u *BasicBlock) {
    84  	// Encounter each node u in postorder of dom tree.
    85  	for _, child := range u.dom.children {
    86  		df.build(child)
    87  	}
    88  	for _, vb := range u.Succs {
    89  		if v := vb.dom; v.idom != u {
    90  			df.add(u, vb)
    91  		}
    92  	}
    93  	for _, w := range u.dom.children {
    94  		for _, vb := range df[w.Index] {
    95  			// TODO(adonovan): opt: use word-parallel bitwise union.
    96  			if v := vb.dom; v.idom != u {
    97  				df.add(u, vb)
    98  			}
    99  		}
   100  	}
   101  }
   102  
   103  func buildDomFrontier(fn *Function) domFrontier {
   104  	df := make(domFrontier, len(fn.Blocks))
   105  	df.build(fn.Blocks[0])
   106  	if fn.Recover != nil {
   107  		df.build(fn.Recover)
   108  	}
   109  	return df
   110  }
   111  
   112  func RemoveInstr(refs []Instruction, instr Instruction) []Instruction {
   113  	return removeInstr(refs, instr)
   114  }
   115  
   116  func removeInstr(refs []Instruction, instr Instruction) []Instruction {
   117  	i := 0
   118  	for _, ref := range refs {
   119  		if ref == instr {
   120  			continue
   121  		}
   122  		refs[i] = ref
   123  		i++
   124  	}
   125  	for j := i; j != len(refs); j++ {
   126  		refs[j] = nil // aid GC
   127  	}
   128  	return refs[:i]
   129  }
   130  
   131  // lift attempts to replace local and new Allocs accessed only with
   132  // load/store by SSA registers, inserting φ-nodes where necessary.
   133  // The result is a program in classical pruned SSA form.
   134  //
   135  // Preconditions:
   136  // - fn has no dead blocks (blockopt has run).
   137  // - Def/use info (Operands and Referrers) is up-to-date.
   138  // - The dominator tree is up-to-date.
   139  //
   140  func lift(fn *Function) {
   141  	// TODO(adonovan): opt: lots of little optimizations may be
   142  	// worthwhile here, especially if they cause us to avoid
   143  	// buildDomFrontier.  For example:
   144  	//
   145  	// - Alloc never loaded?  Eliminate.
   146  	// - Alloc never stored?  Replace all loads with a zero constant.
   147  	// - Alloc stored once?  Replace loads with dominating store;
   148  	//   don't forget that an Alloc is itself an effective store
   149  	//   of zero.
   150  	// - Alloc used only within a single block?
   151  	//   Use degenerate algorithm avoiding φ-nodes.
   152  	// - Consider synergy with scalar replacement of aggregates (SRA).
   153  	//   e.g. *(&x.f) where x is an Alloc.
   154  	//   Perhaps we'd get better results if we generated this as x.f
   155  	//   i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
   156  	//   Unclear.
   157  	//
   158  	// But we will start with the simplest correct code.
   159  	df := buildDomFrontier(fn)
   160  
   161  	if debugLifting {
   162  		title := false
   163  		for i, blocks := range df {
   164  			if blocks != nil {
   165  				if !title {
   166  					fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
   167  					title = true
   168  				}
   169  				fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
   170  			}
   171  		}
   172  	}
   173  
   174  	newPhis := make(newPhiMap)
   175  
   176  	// During this pass we will replace some BasicBlock.Instrs
   177  	// (allocs, loads and stores) with nil, keeping a count in
   178  	// BasicBlock.gaps.  At the end we will reset Instrs to the
   179  	// concatenation of all non-dead newPhis and non-nil Instrs
   180  	// for the block, reusing the original array if space permits.
   181  
   182  	// While we're here, we also eliminate 'rundefers'
   183  	// instructions in functions that contain no 'defer'
   184  	// instructions.
   185  	usesDefer := false
   186  
   187  	// Determine which allocs we can lift and number them densely.
   188  	// The renaming phase uses this numbering for compact maps.
   189  	numAllocs := 0
   190  	for _, b := range fn.Blocks {
   191  		b.gaps = 0
   192  		b.rundefers = 0
   193  		for _, instr := range b.Instrs {
   194  			switch instr := instr.(type) {
   195  			case *Alloc:
   196  				index := -1
   197  				if liftAlloc(df, instr, newPhis) {
   198  					index = numAllocs
   199  					numAllocs++
   200  				}
   201  				instr.index = index
   202  			case *Defer:
   203  				usesDefer = true
   204  			case *RunDefers:
   205  				b.rundefers++
   206  			}
   207  		}
   208  	}
   209  
   210  	// renaming maps an alloc (keyed by index) to its replacement
   211  	// value.  Initially the renaming contains nil, signifying the
   212  	// zero constant of the appropriate type; we construct the
   213  	// Const lazily at most once on each path through the domtree.
   214  	// TODO(adonovan): opt: cache per-function not per subtree.
   215  	renaming := make([]Value, numAllocs)
   216  
   217  	// Renaming.
   218  	rename(fn.Blocks[0], renaming, newPhis)
   219  
   220  	// Eliminate dead new phis, then prepend the live ones to each block.
   221  	for _, b := range fn.Blocks {
   222  
   223  		// Compress the newPhis slice to eliminate unused phis.
   224  		// TODO(adonovan): opt: compute liveness to avoid
   225  		// placing phis in blocks for which the alloc cell is
   226  		// not live.
   227  		nps := newPhis[b]
   228  		j := 0
   229  		for _, np := range nps {
   230  			if !phiIsLive(np.phi) {
   231  				// discard it, first removing it from referrers
   232  				for _, newval := range np.phi.Edges {
   233  					if refs := newval.Referrers(); refs != nil {
   234  						*refs = removeInstr(*refs, np.phi)
   235  					}
   236  				}
   237  				continue
   238  			}
   239  			nps[j] = np
   240  			j++
   241  		}
   242  		nps = nps[:j]
   243  
   244  		rundefersToKill := b.rundefers
   245  		if usesDefer {
   246  			rundefersToKill = 0
   247  		}
   248  
   249  		if j+b.gaps+rundefersToKill == 0 {
   250  			continue // fast path: no new phis or gaps
   251  		}
   252  
   253  		// Compact nps + non-nil Instrs into a new slice.
   254  		// TODO(adonovan): opt: compact in situ if there is
   255  		// sufficient space or slack in the slice.
   256  		dst := make([]Instruction, len(b.Instrs)+j-b.gaps-rundefersToKill)
   257  		for i, np := range nps {
   258  			dst[i] = np.phi
   259  		}
   260  		for _, instr := range b.Instrs {
   261  			if instr == nil {
   262  				continue
   263  			}
   264  			if !usesDefer {
   265  				if _, ok := instr.(*RunDefers); ok {
   266  					continue
   267  				}
   268  			}
   269  			dst[j] = instr
   270  			j++
   271  		}
   272  		for i, np := range nps {
   273  			dst[i] = np.phi
   274  		}
   275  		b.Instrs = dst
   276  	}
   277  
   278  	// Remove any fn.Locals that were lifted.
   279  	j := 0
   280  	for _, l := range fn.Locals {
   281  		if l.index < 0 {
   282  			fn.Locals[j] = l
   283  			j++
   284  		}
   285  	}
   286  	// Nil out fn.Locals[j:] to aid GC.
   287  	for i := j; i < len(fn.Locals); i++ {
   288  		fn.Locals[i] = nil
   289  	}
   290  	fn.Locals = fn.Locals[:j]
   291  }
   292  
   293  func phiIsLive(phi *Phi) bool {
   294  	for _, instr := range *phi.Referrers() {
   295  		if instr == phi {
   296  			continue // self-refs don't count
   297  		}
   298  		if _, ok := instr.(*DebugRef); ok {
   299  			continue // debug refs don't count
   300  		}
   301  		return true
   302  	}
   303  	return false
   304  }
   305  
   306  type blockSet struct{ big.Int } // (inherit methods from Int)
   307  
   308  // add adds b to the set and returns true if the set changed.
   309  func (s *blockSet) add(b *BasicBlock) bool {
   310  	i := b.Index
   311  	if s.Bit(i) != 0 {
   312  		return false
   313  	}
   314  	s.SetBit(&s.Int, i, 1)
   315  	return true
   316  }
   317  
   318  // take removes an arbitrary element from a set s and
   319  // returns its index, or returns -1 if empty.
   320  func (s *blockSet) take() int {
   321  	l := s.BitLen()
   322  	for i := 0; i < l; i++ {
   323  		if s.Bit(i) == 1 {
   324  			s.SetBit(&s.Int, i, 0)
   325  			return i
   326  		}
   327  	}
   328  	return -1
   329  }
   330  
   331  // newPhi is a pair of a newly introduced φ-node and the lifted Alloc
   332  // it replaces.
   333  type newPhi struct {
   334  	phi   *Phi
   335  	alloc *Alloc
   336  }
   337  
   338  // newPhiMap records for each basic block, the set of newPhis that
   339  // must be prepended to the block.
   340  type newPhiMap map[*BasicBlock][]newPhi
   341  
   342  // liftAlloc determines whether alloc can be lifted into registers,
   343  // and if so, it populates newPhis with all the φ-nodes it may require
   344  // and returns true.
   345  //
   346  func liftAlloc(df domFrontier, alloc *Alloc, newPhis newPhiMap) bool {
   347  	// Don't lift aggregates into registers, because we don't have
   348  	// a way to express their zero-constants.
   349  	switch deref(alloc.Type()).Underlying().(type) {
   350  	case *types.Array, *types.Struct:
   351  		return false
   352  	}
   353  
   354  	// Don't lift named return values in functions that defer
   355  	// calls that may recover from panic.
   356  	if fn := alloc.Parent(); fn.Recover != nil {
   357  		for _, nr := range fn.namedResults {
   358  			if nr == alloc {
   359  				return false
   360  			}
   361  		}
   362  	}
   363  
   364  	// Compute defblocks, the set of blocks containing a
   365  	// definition of the alloc cell.
   366  	var defblocks blockSet
   367  	for _, instr := range *alloc.Referrers() {
   368  		// Bail out if we discover the alloc is not liftable;
   369  		// the only operations permitted to use the alloc are
   370  		// loads/stores into the cell, and DebugRef.
   371  		switch instr := instr.(type) {
   372  		case *Store:
   373  			if instr.Val == alloc {
   374  				return false // address used as value
   375  			}
   376  			if instr.Addr != alloc {
   377  				panic("Alloc.Referrers is inconsistent")
   378  			}
   379  			defblocks.add(instr.Block())
   380  		case *UnOp:
   381  			if instr.Op != token.MUL {
   382  				return false // not a load
   383  			}
   384  			if instr.X != alloc {
   385  				panic("Alloc.Referrers is inconsistent")
   386  			}
   387  		case *DebugRef:
   388  			// ok
   389  		default:
   390  			return false // some other instruction
   391  		}
   392  	}
   393  	// The Alloc itself counts as a (zero) definition of the cell.
   394  	defblocks.add(alloc.Block())
   395  
   396  	if debugLifting {
   397  		fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
   398  	}
   399  
   400  	fn := alloc.Parent()
   401  
   402  	// Φ-insertion.
   403  	//
   404  	// What follows is the body of the main loop of the insert-φ
   405  	// function described by Cytron et al, but instead of using
   406  	// counter tricks, we just reset the 'hasAlready' and 'work'
   407  	// sets each iteration.  These are bitmaps so it's pretty cheap.
   408  	//
   409  	// TODO(adonovan): opt: recycle slice storage for W,
   410  	// hasAlready, defBlocks across liftAlloc calls.
   411  	var hasAlready blockSet
   412  
   413  	// Initialize W and work to defblocks.
   414  	var work blockSet = defblocks // blocks seen
   415  	var W blockSet                // blocks to do
   416  	W.Set(&defblocks.Int)
   417  
   418  	// Traverse iterated dominance frontier, inserting φ-nodes.
   419  	for i := W.take(); i != -1; i = W.take() {
   420  		u := fn.Blocks[i]
   421  		for _, v := range df[u.Index] {
   422  			if hasAlready.add(v) {
   423  				// Create φ-node.
   424  				// It will be prepended to v.Instrs later, if needed.
   425  				phi := &Phi{
   426  					Edges:   make([]Value, len(v.Preds)),
   427  					Comment: alloc.Comment,
   428  				}
   429  				phi.pos = alloc.Pos()
   430  				phi.setType(deref(alloc.Type()))
   431  				phi.block = v
   432  				if debugLifting {
   433  					fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, v)
   434  				}
   435  				newPhis[v] = append(newPhis[v], newPhi{phi, alloc})
   436  
   437  				if work.add(v) {
   438  					W.add(v)
   439  				}
   440  			}
   441  		}
   442  	}
   443  
   444  	return true
   445  }
   446  
   447  func ReplaceAll(x, y Value) {
   448  	replaceAll(x, y)
   449  }
   450  
   451  // replaceAll replaces all intraprocedural uses of x with y,
   452  // updating x.Referrers and y.Referrers.
   453  // Precondition: x.Referrers() != nil, i.e. x must be local to some function.
   454  //
   455  func replaceAll(x, y Value) {
   456  	var rands []*Value
   457  	pxrefs := x.Referrers()
   458  	pyrefs := y.Referrers()
   459  	for _, instr := range *pxrefs {
   460  		rands = instr.Operands(rands[:0]) // recycle storage
   461  		for _, rand := range rands {
   462  			if *rand != nil {
   463  				if *rand == x {
   464  					*rand = y
   465  				}
   466  			}
   467  		}
   468  		if pyrefs != nil {
   469  			*pyrefs = append(*pyrefs, instr) // dups ok
   470  		}
   471  	}
   472  	*pxrefs = nil // x is now unreferenced
   473  }
   474  
   475  // renamed returns the value to which alloc is being renamed,
   476  // constructing it lazily if it's the implicit zero initialization.
   477  //
   478  func renamed(renaming []Value, alloc *Alloc) Value {
   479  	v := renaming[alloc.index]
   480  	if v == nil {
   481  		v = zeroConst(deref(alloc.Type()))
   482  		renaming[alloc.index] = v
   483  	}
   484  	return v
   485  }
   486  
   487  // rename implements the (Cytron et al) SSA renaming algorithm, a
   488  // preorder traversal of the dominator tree replacing all loads of
   489  // Alloc cells with the value stored to that cell by the dominating
   490  // store instruction.  For lifting, we need only consider loads,
   491  // stores and φ-nodes.
   492  //
   493  // renaming is a map from *Alloc (keyed by index number) to its
   494  // dominating stored value; newPhis[x] is the set of new φ-nodes to be
   495  // prepended to block x.
   496  //
   497  func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap) {
   498  	// Each φ-node becomes the new name for its associated Alloc.
   499  	for _, np := range newPhis[u] {
   500  		phi := np.phi
   501  		alloc := np.alloc
   502  		renaming[alloc.index] = phi
   503  	}
   504  
   505  	// Rename loads and stores of allocs.
   506  	for i, instr := range u.Instrs {
   507  		switch instr := instr.(type) {
   508  		case *Alloc:
   509  			if instr.index >= 0 { // store of zero to Alloc cell
   510  				// Replace dominated loads by the zero value.
   511  				renaming[instr.index] = nil
   512  				if debugLifting {
   513  					fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
   514  				}
   515  				// Delete the Alloc.
   516  				u.Instrs[i] = nil
   517  				u.gaps++
   518  			}
   519  
   520  		case *Store:
   521  			if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
   522  				// Replace dominated loads by the stored value.
   523  				renaming[alloc.index] = instr.Val
   524  				if debugLifting {
   525  					fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
   526  						instr, instr.Val.Name())
   527  				}
   528  				// Remove the store from the referrer list of the stored value.
   529  				if refs := instr.Val.Referrers(); refs != nil {
   530  					*refs = removeInstr(*refs, instr)
   531  				}
   532  				// Delete the Store.
   533  				u.Instrs[i] = nil
   534  				u.gaps++
   535  			}
   536  
   537  		case *UnOp:
   538  			if instr.Op == token.MUL {
   539  				if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
   540  					newval := renamed(renaming, alloc)
   541  					if debugLifting {
   542  						fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
   543  							instr.Name(), instr, newval.Name())
   544  					}
   545  					// Replace all references to
   546  					// the loaded value by the
   547  					// dominating stored value.
   548  					replaceAll(instr, newval)
   549  					// Delete the Load.
   550  					u.Instrs[i] = nil
   551  					u.gaps++
   552  				}
   553  			}
   554  
   555  		case *DebugRef:
   556  			if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // ref of Alloc cell
   557  				if instr.IsAddr {
   558  					instr.X = renamed(renaming, alloc)
   559  					instr.IsAddr = false
   560  
   561  					// Add DebugRef to instr.X's referrers.
   562  					if refs := instr.X.Referrers(); refs != nil {
   563  						*refs = append(*refs, instr)
   564  					}
   565  				} else {
   566  					// A source expression denotes the address
   567  					// of an Alloc that was optimized away.
   568  					instr.X = nil
   569  
   570  					// Delete the DebugRef.
   571  					u.Instrs[i] = nil
   572  					u.gaps++
   573  				}
   574  			}
   575  		}
   576  	}
   577  
   578  	// For each φ-node in a CFG successor, rename the edge.
   579  	for _, v := range u.Succs {
   580  		phis := newPhis[v]
   581  		if len(phis) == 0 {
   582  			continue
   583  		}
   584  		i := v.predIndex(u)
   585  		for _, np := range phis {
   586  			phi := np.phi
   587  			alloc := np.alloc
   588  			newval := renamed(renaming, alloc)
   589  			if debugLifting {
   590  				fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
   591  					phi.Name(), u, v, i, alloc.Name(), newval.Name())
   592  			}
   593  			phi.Edges[i] = newval
   594  			if prefs := newval.Referrers(); prefs != nil {
   595  				*prefs = append(*prefs, phi)
   596  			}
   597  		}
   598  	}
   599  
   600  	// Continue depth-first recursion over domtree, pushing a
   601  	// fresh copy of the renaming map for each subtree.
   602  	for _, v := range u.dom.children {
   603  		// TODO(adonovan): opt: avoid copy on final iteration; use destructive update.
   604  		r := make([]Value, len(renaming))
   605  		copy(r, renaming)
   606  		rename(v, r, newPhis)
   607  	}
   608  }