 bf27481efd
			
		
	
	
		bf27481efd
		
			
		
	
	
	
	
		
			
			full diff: https://github.com/dominikh/go-tools/compare/2019.2.3...2020.1.3 Also updates tests to accomodate updated rules: --- FAIL: TestSourcesFromTestdataWithIssuesDir/staticcheck.go (0.43s) linters_test.go:137: [run --disable-all --print-issued-lines=false --print-linter-name=false --out-format=line-number --max-same-issues=10 -Estaticcheck --no-config testdata/staticcheck.go] linters_test.go:33: Error Trace: linters_test.go:33 linters_test.go:138 linters_test.go:53 Error: Received unexpected error: staticcheck.go:11: no match for `self-assignment of x to x` vs ["SA4006: this value of `x` is never used"] in: staticcheck.go:11:2: SA4006: this value of `x` is never used unmatched errors staticcheck.go:11:2: SA4006: this value of `x` is never used Test: TestSourcesFromTestdataWithIssuesDir/staticcheck.go Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
		
			
				
	
	
		
			1064 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			1064 lines
		
	
	
		
			26 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| // Copyright 2013 The Go Authors. All rights reserved.
 | ||
| // Use of this source code is governed by a BSD-style
 | ||
| // license that can be found in the LICENSE file.
 | ||
| 
 | ||
| package ir
 | ||
| 
 | ||
| // This file defines the lifting pass which tries to "lift" Alloc
 | ||
| // cells (new/local variables) into SSA registers, replacing loads
 | ||
| // with the dominating stored value, eliminating loads and stores, and
 | ||
| // inserting φ- and σ-nodes as needed.
 | ||
| 
 | ||
| // Cited papers and resources:
 | ||
| //
 | ||
| // Ron Cytron et al. 1991. Efficiently computing SSA form...
 | ||
| // http://doi.acm.org/10.1145/115372.115320
 | ||
| //
 | ||
| // Cooper, Harvey, Kennedy.  2001.  A Simple, Fast Dominance Algorithm.
 | ||
| // Software Practice and Experience 2001, 4:1-10.
 | ||
| // http://www.hipersoft.rice.edu/grads/publications/dom14.pdf
 | ||
| //
 | ||
| // Daniel Berlin, llvmdev mailing list, 2012.
 | ||
| // http://lists.cs.uiuc.edu/pipermail/llvmdev/2012-January/046638.html
 | ||
| // (Be sure to expand the whole thread.)
 | ||
| //
 | ||
| // C. Scott Ananian. 1997. The static single information form.
 | ||
| //
 | ||
| // Jeremy Singer. 2006. Static program analysis based on virtual register renaming.
 | ||
| 
 | ||
| // TODO(adonovan): opt: there are many optimizations worth evaluating, and
 | ||
| // the conventional wisdom for SSA construction is that a simple
 | ||
| // algorithm well engineered often beats those of better asymptotic
 | ||
| // complexity on all but the most egregious inputs.
 | ||
| //
 | ||
| // Danny Berlin suggests that the Cooper et al. algorithm for
 | ||
| // computing the dominance frontier is superior to Cytron et al.
 | ||
| // Furthermore he recommends that rather than computing the DF for the
 | ||
| // whole function then renaming all alloc cells, it may be cheaper to
 | ||
| // compute the DF for each alloc cell separately and throw it away.
 | ||
| //
 | ||
| // Consider exploiting liveness information to avoid creating dead
 | ||
| // φ-nodes which we then immediately remove.
 | ||
| //
 | ||
| // Also see many other "TODO: opt" suggestions in the code.
 | ||
| 
 | ||
| import (
 | ||
| 	"fmt"
 | ||
| 	"go/types"
 | ||
| 	"os"
 | ||
| )
 | ||
| 
 | ||
| // If true, show diagnostic information at each step of lifting.
 | ||
| // Very verbose.
 | ||
| const debugLifting = false
 | ||
| 
 | ||
| // domFrontier maps each block to the set of blocks in its dominance
 | ||
| // frontier.  The outer slice is conceptually a map keyed by
 | ||
| // Block.Index.  The inner slice is conceptually a set, possibly
 | ||
| // containing duplicates.
 | ||
| //
 | ||
| // TODO(adonovan): opt: measure impact of dups; consider a packed bit
 | ||
| // representation, e.g. big.Int, and bitwise parallel operations for
 | ||
| // the union step in the Children loop.
 | ||
| //
 | ||
| // domFrontier's methods mutate the slice's elements but not its
 | ||
| // length, so their receivers needn't be pointers.
 | ||
| //
 | ||
| type domFrontier [][]*BasicBlock
 | ||
| 
 | ||
| func (df domFrontier) add(u, v *BasicBlock) {
 | ||
| 	df[u.Index] = append(df[u.Index], v)
 | ||
| }
 | ||
| 
 | ||
| // build builds the dominance frontier df for the dominator tree of
 | ||
| // fn, using the algorithm found in A Simple, Fast Dominance
 | ||
| // Algorithm, Figure 5.
 | ||
| //
 | ||
| // TODO(adonovan): opt: consider Berlin approach, computing pruned SSA
 | ||
| // by pruning the entire IDF computation, rather than merely pruning
 | ||
| // the DF -> IDF step.
 | ||
| func (df domFrontier) build(fn *Function) {
 | ||
| 	for _, b := range fn.Blocks {
 | ||
| 		if len(b.Preds) >= 2 {
 | ||
| 			for _, p := range b.Preds {
 | ||
| 				runner := p
 | ||
| 				for runner != b.dom.idom {
 | ||
| 					df.add(runner, b)
 | ||
| 					runner = runner.dom.idom
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func buildDomFrontier(fn *Function) domFrontier {
 | ||
| 	df := make(domFrontier, len(fn.Blocks))
 | ||
| 	df.build(fn)
 | ||
| 	return df
 | ||
| }
 | ||
| 
 | ||
| type postDomFrontier [][]*BasicBlock
 | ||
| 
 | ||
| func (rdf postDomFrontier) add(u, v *BasicBlock) {
 | ||
| 	rdf[u.Index] = append(rdf[u.Index], v)
 | ||
| }
 | ||
| 
 | ||
| func (rdf postDomFrontier) build(fn *Function) {
 | ||
| 	for _, b := range fn.Blocks {
 | ||
| 		if len(b.Succs) >= 2 {
 | ||
| 			for _, s := range b.Succs {
 | ||
| 				runner := s
 | ||
| 				for runner != b.pdom.idom {
 | ||
| 					rdf.add(runner, b)
 | ||
| 					runner = runner.pdom.idom
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func buildPostDomFrontier(fn *Function) postDomFrontier {
 | ||
| 	rdf := make(postDomFrontier, len(fn.Blocks))
 | ||
| 	rdf.build(fn)
 | ||
| 	return rdf
 | ||
| }
 | ||
| 
 | ||
| func removeInstr(refs []Instruction, instr Instruction) []Instruction {
 | ||
| 	i := 0
 | ||
| 	for _, ref := range refs {
 | ||
| 		if ref == instr {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		refs[i] = ref
 | ||
| 		i++
 | ||
| 	}
 | ||
| 	for j := i; j != len(refs); j++ {
 | ||
| 		refs[j] = nil // aid GC
 | ||
| 	}
 | ||
| 	return refs[:i]
 | ||
| }
 | ||
| 
 | ||
| func clearInstrs(instrs []Instruction) {
 | ||
| 	for i := range instrs {
 | ||
| 		instrs[i] = nil
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // lift replaces local and new Allocs accessed only with
 | ||
| // load/store by IR registers, inserting φ- and σ-nodes where necessary.
 | ||
| // The result is a program in pruned SSI form.
 | ||
| //
 | ||
| // Preconditions:
 | ||
| // - fn has no dead blocks (blockopt has run).
 | ||
| // - Def/use info (Operands and Referrers) is up-to-date.
 | ||
| // - The dominator tree is up-to-date.
 | ||
| //
 | ||
| func lift(fn *Function) {
 | ||
| 	// TODO(adonovan): opt: lots of little optimizations may be
 | ||
| 	// worthwhile here, especially if they cause us to avoid
 | ||
| 	// buildDomFrontier.  For example:
 | ||
| 	//
 | ||
| 	// - Alloc never loaded?  Eliminate.
 | ||
| 	// - Alloc never stored?  Replace all loads with a zero constant.
 | ||
| 	// - Alloc stored once?  Replace loads with dominating store;
 | ||
| 	//   don't forget that an Alloc is itself an effective store
 | ||
| 	//   of zero.
 | ||
| 	// - Alloc used only within a single block?
 | ||
| 	//   Use degenerate algorithm avoiding φ-nodes.
 | ||
| 	// - Consider synergy with scalar replacement of aggregates (SRA).
 | ||
| 	//   e.g. *(&x.f) where x is an Alloc.
 | ||
| 	//   Perhaps we'd get better results if we generated this as x.f
 | ||
| 	//   i.e. Field(x, .f) instead of Load(FieldIndex(x, .f)).
 | ||
| 	//   Unclear.
 | ||
| 	//
 | ||
| 	// But we will start with the simplest correct code.
 | ||
| 	var df domFrontier
 | ||
| 	var rdf postDomFrontier
 | ||
| 	var closure *closure
 | ||
| 	var newPhis newPhiMap
 | ||
| 	var newSigmas newSigmaMap
 | ||
| 
 | ||
| 	// During this pass we will replace some BasicBlock.Instrs
 | ||
| 	// (allocs, loads and stores) with nil, keeping a count in
 | ||
| 	// BasicBlock.gaps.  At the end we will reset Instrs to the
 | ||
| 	// concatenation of all non-dead newPhis and non-nil Instrs
 | ||
| 	// for the block, reusing the original array if space permits.
 | ||
| 
 | ||
| 	// While we're here, we also eliminate 'rundefers'
 | ||
| 	// instructions in functions that contain no 'defer'
 | ||
| 	// instructions.
 | ||
| 	usesDefer := false
 | ||
| 
 | ||
| 	// Determine which allocs we can lift and number them densely.
 | ||
| 	// The renaming phase uses this numbering for compact maps.
 | ||
| 	numAllocs := 0
 | ||
| 	for _, b := range fn.Blocks {
 | ||
| 		b.gaps = 0
 | ||
| 		b.rundefers = 0
 | ||
| 		for _, instr := range b.Instrs {
 | ||
| 			switch instr := instr.(type) {
 | ||
| 			case *Alloc:
 | ||
| 				if !liftable(instr) {
 | ||
| 					instr.index = -1
 | ||
| 					continue
 | ||
| 				}
 | ||
| 				index := -1
 | ||
| 				if numAllocs == 0 {
 | ||
| 					df = buildDomFrontier(fn)
 | ||
| 					rdf = buildPostDomFrontier(fn)
 | ||
| 					if len(fn.Blocks) > 2 {
 | ||
| 						closure = transitiveClosure(fn)
 | ||
| 					}
 | ||
| 					newPhis = make(newPhiMap, len(fn.Blocks))
 | ||
| 					newSigmas = make(newSigmaMap, len(fn.Blocks))
 | ||
| 
 | ||
| 					if debugLifting {
 | ||
| 						title := false
 | ||
| 						for i, blocks := range df {
 | ||
| 							if blocks != nil {
 | ||
| 								if !title {
 | ||
| 									fmt.Fprintf(os.Stderr, "Dominance frontier of %s:\n", fn)
 | ||
| 									title = true
 | ||
| 								}
 | ||
| 								fmt.Fprintf(os.Stderr, "\t%s: %s\n", fn.Blocks[i], blocks)
 | ||
| 							}
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 				liftAlloc(closure, df, rdf, instr, newPhis, newSigmas)
 | ||
| 				index = numAllocs
 | ||
| 				numAllocs++
 | ||
| 				instr.index = index
 | ||
| 			case *Defer:
 | ||
| 				usesDefer = true
 | ||
| 			case *RunDefers:
 | ||
| 				b.rundefers++
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	if numAllocs > 0 {
 | ||
| 		// renaming maps an alloc (keyed by index) to its replacement
 | ||
| 		// value.  Initially the renaming contains nil, signifying the
 | ||
| 		// zero constant of the appropriate type; we construct the
 | ||
| 		// Const lazily at most once on each path through the domtree.
 | ||
| 		// TODO(adonovan): opt: cache per-function not per subtree.
 | ||
| 		renaming := make([]Value, numAllocs)
 | ||
| 
 | ||
| 		// Renaming.
 | ||
| 		rename(fn.Blocks[0], renaming, newPhis, newSigmas)
 | ||
| 
 | ||
| 		simplifyPhis(newPhis)
 | ||
| 
 | ||
| 		// Eliminate dead φ- and σ-nodes.
 | ||
| 		markLiveNodes(fn.Blocks, newPhis, newSigmas)
 | ||
| 	}
 | ||
| 
 | ||
| 	// Prepend remaining live φ-nodes to each block and possibly kill rundefers.
 | ||
| 	for _, b := range fn.Blocks {
 | ||
| 		var head []Instruction
 | ||
| 		if numAllocs > 0 {
 | ||
| 			nps := newPhis[b.Index]
 | ||
| 			head = make([]Instruction, 0, len(nps))
 | ||
| 			for _, pred := range b.Preds {
 | ||
| 				nss := newSigmas[pred.Index]
 | ||
| 				idx := pred.succIndex(b)
 | ||
| 				for _, newSigma := range nss {
 | ||
| 					if sigma := newSigma.sigmas[idx]; sigma != nil && sigma.live {
 | ||
| 						head = append(head, sigma)
 | ||
| 
 | ||
| 						// we didn't populate referrers before, as most
 | ||
| 						// sigma nodes will be killed
 | ||
| 						if refs := sigma.X.Referrers(); refs != nil {
 | ||
| 							*refs = append(*refs, sigma)
 | ||
| 						}
 | ||
| 					} else if sigma != nil {
 | ||
| 						sigma.block = nil
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 			for _, np := range nps {
 | ||
| 				if np.phi.live {
 | ||
| 					head = append(head, np.phi)
 | ||
| 				} else {
 | ||
| 					for _, edge := range np.phi.Edges {
 | ||
| 						if refs := edge.Referrers(); refs != nil {
 | ||
| 							*refs = removeInstr(*refs, np.phi)
 | ||
| 						}
 | ||
| 					}
 | ||
| 					np.phi.block = nil
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		rundefersToKill := b.rundefers
 | ||
| 		if usesDefer {
 | ||
| 			rundefersToKill = 0
 | ||
| 		}
 | ||
| 
 | ||
| 		j := len(head)
 | ||
| 		if j+b.gaps+rundefersToKill == 0 {
 | ||
| 			continue // fast path: no new phis or gaps
 | ||
| 		}
 | ||
| 
 | ||
| 		// We could do straight copies instead of element-wise copies
 | ||
| 		// when both b.gaps and rundefersToKill are zero. However,
 | ||
| 		// that seems to only be the case ~1% of the time, which
 | ||
| 		// doesn't seem worth the extra branch.
 | ||
| 
 | ||
| 		// Remove dead instructions, add phis and sigmas
 | ||
| 		ns := len(b.Instrs) + j - b.gaps - rundefersToKill
 | ||
| 		if ns <= cap(b.Instrs) {
 | ||
| 			// b.Instrs has enough capacity to store all instructions
 | ||
| 
 | ||
| 			// OPT(dh): check cap vs the actually required space; if
 | ||
| 			// there is a big enough difference, it may be worth
 | ||
| 			// allocating a new slice, to avoid pinning memory.
 | ||
| 			dst := b.Instrs[:cap(b.Instrs)]
 | ||
| 			i := len(dst) - 1
 | ||
| 			for n := len(b.Instrs) - 1; n >= 0; n-- {
 | ||
| 				instr := dst[n]
 | ||
| 				if instr == nil {
 | ||
| 					continue
 | ||
| 				}
 | ||
| 				if !usesDefer {
 | ||
| 					if _, ok := instr.(*RunDefers); ok {
 | ||
| 						continue
 | ||
| 					}
 | ||
| 				}
 | ||
| 				dst[i] = instr
 | ||
| 				i--
 | ||
| 			}
 | ||
| 			off := i + 1 - len(head)
 | ||
| 			// aid GC
 | ||
| 			clearInstrs(dst[:off])
 | ||
| 			dst = dst[off:]
 | ||
| 			copy(dst, head)
 | ||
| 			b.Instrs = dst
 | ||
| 		} else {
 | ||
| 			// not enough space, so allocate a new slice and copy
 | ||
| 			// over.
 | ||
| 			dst := make([]Instruction, ns)
 | ||
| 			copy(dst, head)
 | ||
| 
 | ||
| 			for _, instr := range b.Instrs {
 | ||
| 				if instr == nil {
 | ||
| 					continue
 | ||
| 				}
 | ||
| 				if !usesDefer {
 | ||
| 					if _, ok := instr.(*RunDefers); ok {
 | ||
| 						continue
 | ||
| 					}
 | ||
| 				}
 | ||
| 				dst[j] = instr
 | ||
| 				j++
 | ||
| 			}
 | ||
| 			b.Instrs = dst
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// Remove any fn.Locals that were lifted.
 | ||
| 	j := 0
 | ||
| 	for _, l := range fn.Locals {
 | ||
| 		if l.index < 0 {
 | ||
| 			fn.Locals[j] = l
 | ||
| 			j++
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// Nil out fn.Locals[j:] to aid GC.
 | ||
| 	for i := j; i < len(fn.Locals); i++ {
 | ||
| 		fn.Locals[i] = nil
 | ||
| 	}
 | ||
| 	fn.Locals = fn.Locals[:j]
 | ||
| }
 | ||
| 
 | ||
| func hasDirectReferrer(instr Instruction) bool {
 | ||
| 	for _, instr := range *instr.Referrers() {
 | ||
| 		switch instr.(type) {
 | ||
| 		case *Phi, *Sigma:
 | ||
| 			// ignore
 | ||
| 		default:
 | ||
| 			return true
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return false
 | ||
| }
 | ||
| 
 | ||
| func markLiveNodes(blocks []*BasicBlock, newPhis newPhiMap, newSigmas newSigmaMap) {
 | ||
| 	// Phi and sigma nodes are considered live if a non-phi, non-sigma
 | ||
| 	// node uses them. Once we find a node that is live, we mark all
 | ||
| 	// of its operands as used, too.
 | ||
| 	for _, npList := range newPhis {
 | ||
| 		for _, np := range npList {
 | ||
| 			phi := np.phi
 | ||
| 			if !phi.live && hasDirectReferrer(phi) {
 | ||
| 				markLivePhi(phi)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	for _, npList := range newSigmas {
 | ||
| 		for _, np := range npList {
 | ||
| 			for _, sigma := range np.sigmas {
 | ||
| 				if sigma != nil && !sigma.live && hasDirectReferrer(sigma) {
 | ||
| 					markLiveSigma(sigma)
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// Existing φ-nodes due to && and || operators
 | ||
| 	// are all considered live (see Go issue 19622).
 | ||
| 	for _, b := range blocks {
 | ||
| 		for _, phi := range b.phis() {
 | ||
| 			markLivePhi(phi.(*Phi))
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func markLivePhi(phi *Phi) {
 | ||
| 	phi.live = true
 | ||
| 	for _, rand := range phi.Edges {
 | ||
| 		switch rand := rand.(type) {
 | ||
| 		case *Phi:
 | ||
| 			if !rand.live {
 | ||
| 				markLivePhi(rand)
 | ||
| 			}
 | ||
| 		case *Sigma:
 | ||
| 			if !rand.live {
 | ||
| 				markLiveSigma(rand)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func markLiveSigma(sigma *Sigma) {
 | ||
| 	sigma.live = true
 | ||
| 	switch rand := sigma.X.(type) {
 | ||
| 	case *Phi:
 | ||
| 		if !rand.live {
 | ||
| 			markLivePhi(rand)
 | ||
| 		}
 | ||
| 	case *Sigma:
 | ||
| 		if !rand.live {
 | ||
| 			markLiveSigma(rand)
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // simplifyPhis replaces trivial phis with non-phi alternatives. Phi
 | ||
| // nodes where all edges are identical, or consist of only the phi
 | ||
| // itself and one other value, may be replaced with the value.
 | ||
| func simplifyPhis(newPhis newPhiMap) {
 | ||
| 	// find all phis that are trivial and can be replaced with a
 | ||
| 	// non-phi value. run until we reach a fixpoint, because replacing
 | ||
| 	// a phi may make other phis trivial.
 | ||
| 	for changed := true; changed; {
 | ||
| 		changed = false
 | ||
| 		for _, npList := range newPhis {
 | ||
| 			for _, np := range npList {
 | ||
| 				if np.phi.live {
 | ||
| 					// we're reusing 'live' to mean 'dead' in the context of simplifyPhis
 | ||
| 					continue
 | ||
| 				}
 | ||
| 				if r, ok := isUselessPhi(np.phi); ok {
 | ||
| 					// useless phi, replace its uses with the
 | ||
| 					// replacement value. the dead phi pass will clean
 | ||
| 					// up the phi afterwards.
 | ||
| 					replaceAll(np.phi, r)
 | ||
| 					np.phi.live = true
 | ||
| 					changed = true
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, npList := range newPhis {
 | ||
| 		for _, np := range npList {
 | ||
| 			np.phi.live = false
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| type BlockSet struct {
 | ||
| 	idx    int
 | ||
| 	values []bool
 | ||
| 	count  int
 | ||
| }
 | ||
| 
 | ||
| func NewBlockSet(size int) *BlockSet {
 | ||
| 	return &BlockSet{values: make([]bool, size)}
 | ||
| }
 | ||
| 
 | ||
| func (s *BlockSet) Set(s2 *BlockSet) {
 | ||
| 	copy(s.values, s2.values)
 | ||
| 	s.count = 0
 | ||
| 	for _, v := range s.values {
 | ||
| 		if v {
 | ||
| 			s.count++
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func (s *BlockSet) Num() int {
 | ||
| 	return s.count
 | ||
| }
 | ||
| 
 | ||
| func (s *BlockSet) Has(b *BasicBlock) bool {
 | ||
| 	if b.Index >= len(s.values) {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	return s.values[b.Index]
 | ||
| }
 | ||
| 
 | ||
| // add adds b to the set and returns true if the set changed.
 | ||
| func (s *BlockSet) Add(b *BasicBlock) bool {
 | ||
| 	if s.values[b.Index] {
 | ||
| 		return false
 | ||
| 	}
 | ||
| 	s.count++
 | ||
| 	s.values[b.Index] = true
 | ||
| 	s.idx = b.Index
 | ||
| 
 | ||
| 	return true
 | ||
| }
 | ||
| 
 | ||
| func (s *BlockSet) Clear() {
 | ||
| 	for j := range s.values {
 | ||
| 		s.values[j] = false
 | ||
| 	}
 | ||
| 	s.count = 0
 | ||
| }
 | ||
| 
 | ||
| // take removes an arbitrary element from a set s and
 | ||
| // returns its index, or returns -1 if empty.
 | ||
| func (s *BlockSet) Take() int {
 | ||
| 	// [i, end]
 | ||
| 	for i := s.idx; i < len(s.values); i++ {
 | ||
| 		if s.values[i] {
 | ||
| 			s.values[i] = false
 | ||
| 			s.idx = i
 | ||
| 			s.count--
 | ||
| 			return i
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// [start, i)
 | ||
| 	for i := 0; i < s.idx; i++ {
 | ||
| 		if s.values[i] {
 | ||
| 			s.values[i] = false
 | ||
| 			s.idx = i
 | ||
| 			s.count--
 | ||
| 			return i
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return -1
 | ||
| }
 | ||
| 
 | ||
| type closure struct {
 | ||
| 	span       []uint32
 | ||
| 	reachables []interval
 | ||
| }
 | ||
| 
 | ||
| type interval uint32
 | ||
| 
 | ||
| const (
 | ||
| 	flagMask   = 1 << 31
 | ||
| 	numBits    = 20
 | ||
| 	lengthBits = 32 - numBits - 1
 | ||
| 	lengthMask = (1<<lengthBits - 1) << numBits
 | ||
| 	numMask    = 1<<numBits - 1
 | ||
| )
 | ||
| 
 | ||
| func (c closure) has(s, v *BasicBlock) bool {
 | ||
| 	idx := uint32(v.Index)
 | ||
| 	if idx == 1 || s.Dominates(v) {
 | ||
| 		return true
 | ||
| 	}
 | ||
| 	r := c.reachable(s.Index)
 | ||
| 	for i := 0; i < len(r); i++ {
 | ||
| 		inv := r[i]
 | ||
| 		var start, end uint32
 | ||
| 		if inv&flagMask == 0 {
 | ||
| 			// small interval
 | ||
| 			start = uint32(inv & numMask)
 | ||
| 			end = start + uint32(inv&lengthMask)>>numBits
 | ||
| 		} else {
 | ||
| 			// large interval
 | ||
| 			i++
 | ||
| 			start = uint32(inv & numMask)
 | ||
| 			end = uint32(r[i])
 | ||
| 		}
 | ||
| 		if idx >= start && idx <= end {
 | ||
| 			return true
 | ||
| 		}
 | ||
| 	}
 | ||
| 	return false
 | ||
| }
 | ||
| 
 | ||
| func (c closure) reachable(id int) []interval {
 | ||
| 	return c.reachables[c.span[id]:c.span[id+1]]
 | ||
| }
 | ||
| 
 | ||
| func (c closure) walk(current *BasicBlock, b *BasicBlock, visited []bool) {
 | ||
| 	visited[b.Index] = true
 | ||
| 	for _, succ := range b.Succs {
 | ||
| 		if visited[succ.Index] {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		visited[succ.Index] = true
 | ||
| 		c.walk(current, succ, visited)
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| func transitiveClosure(fn *Function) *closure {
 | ||
| 	reachable := make([]bool, len(fn.Blocks))
 | ||
| 	c := &closure{}
 | ||
| 	c.span = make([]uint32, len(fn.Blocks)+1)
 | ||
| 
 | ||
| 	addInterval := func(start, end uint32) {
 | ||
| 		if l := end - start; l <= 1<<lengthBits-1 {
 | ||
| 			n := interval(l<<numBits | start)
 | ||
| 			c.reachables = append(c.reachables, n)
 | ||
| 		} else {
 | ||
| 			n1 := interval(1<<31 | start)
 | ||
| 			n2 := interval(end)
 | ||
| 			c.reachables = append(c.reachables, n1, n2)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	for i, b := range fn.Blocks[1:] {
 | ||
| 		for i := range reachable {
 | ||
| 			reachable[i] = false
 | ||
| 		}
 | ||
| 
 | ||
| 		c.walk(b, b, reachable)
 | ||
| 		start := ^uint32(0)
 | ||
| 		for id, isReachable := range reachable {
 | ||
| 			if !isReachable {
 | ||
| 				if start != ^uint32(0) {
 | ||
| 					end := uint32(id) - 1
 | ||
| 					addInterval(start, end)
 | ||
| 					start = ^uint32(0)
 | ||
| 				}
 | ||
| 				continue
 | ||
| 			} else if start == ^uint32(0) {
 | ||
| 				start = uint32(id)
 | ||
| 			}
 | ||
| 		}
 | ||
| 		if start != ^uint32(0) {
 | ||
| 			addInterval(start, uint32(len(reachable))-1)
 | ||
| 		}
 | ||
| 
 | ||
| 		c.span[i+2] = uint32(len(c.reachables))
 | ||
| 	}
 | ||
| 
 | ||
| 	return c
 | ||
| }
 | ||
| 
 | ||
| // newPhi is a pair of a newly introduced φ-node and the lifted Alloc
 | ||
| // it replaces.
 | ||
| type newPhi struct {
 | ||
| 	phi   *Phi
 | ||
| 	alloc *Alloc
 | ||
| }
 | ||
| 
 | ||
| type newSigma struct {
 | ||
| 	alloc  *Alloc
 | ||
| 	sigmas []*Sigma
 | ||
| }
 | ||
| 
 | ||
| // newPhiMap records for each basic block, the set of newPhis that
 | ||
| // must be prepended to the block.
 | ||
| type newPhiMap [][]newPhi
 | ||
| type newSigmaMap [][]newSigma
 | ||
| 
 | ||
| func liftable(alloc *Alloc) bool {
 | ||
| 	// Don't lift aggregates into registers, because we don't have
 | ||
| 	// a way to express their zero-constants.
 | ||
| 	switch deref(alloc.Type()).Underlying().(type) {
 | ||
| 	case *types.Array, *types.Struct:
 | ||
| 		return false
 | ||
| 	}
 | ||
| 
 | ||
| 	fn := alloc.Parent()
 | ||
| 	// Don't lift named return values in functions that defer
 | ||
| 	// calls that may recover from panic.
 | ||
| 	if fn.hasDefer {
 | ||
| 		for _, nr := range fn.namedResults {
 | ||
| 			if nr == alloc {
 | ||
| 				return false
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	for _, instr := range *alloc.Referrers() {
 | ||
| 		switch instr := instr.(type) {
 | ||
| 		case *Store:
 | ||
| 			if instr.Val == alloc {
 | ||
| 				return false // address used as value
 | ||
| 			}
 | ||
| 			if instr.Addr != alloc {
 | ||
| 				panic("Alloc.Referrers is inconsistent")
 | ||
| 			}
 | ||
| 		case *Load:
 | ||
| 			if instr.X != alloc {
 | ||
| 				panic("Alloc.Referrers is inconsistent")
 | ||
| 			}
 | ||
| 
 | ||
| 		case *DebugRef:
 | ||
| 			// ok
 | ||
| 		default:
 | ||
| 			return false
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	return true
 | ||
| }
 | ||
| 
 | ||
| // liftAlloc determines whether alloc can be lifted into registers,
 | ||
| // and if so, it populates newPhis with all the φ-nodes it may require
 | ||
| // and returns true.
 | ||
| func liftAlloc(closure *closure, df domFrontier, rdf postDomFrontier, alloc *Alloc, newPhis newPhiMap, newSigmas newSigmaMap) {
 | ||
| 	fn := alloc.Parent()
 | ||
| 
 | ||
| 	defblocks := fn.blockset(0)
 | ||
| 	useblocks := fn.blockset(1)
 | ||
| 	Aphi := fn.blockset(2)
 | ||
| 	Asigma := fn.blockset(3)
 | ||
| 	W := fn.blockset(4)
 | ||
| 
 | ||
| 	// Compute defblocks, the set of blocks containing a
 | ||
| 	// definition of the alloc cell.
 | ||
| 	for _, instr := range *alloc.Referrers() {
 | ||
| 		// Bail out if we discover the alloc is not liftable;
 | ||
| 		// the only operations permitted to use the alloc are
 | ||
| 		// loads/stores into the cell, and DebugRef.
 | ||
| 		switch instr := instr.(type) {
 | ||
| 		case *Store:
 | ||
| 			defblocks.Add(instr.Block())
 | ||
| 		case *Load:
 | ||
| 			useblocks.Add(instr.Block())
 | ||
| 			for _, ref := range *instr.Referrers() {
 | ||
| 				useblocks.Add(ref.Block())
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 	// The Alloc itself counts as a (zero) definition of the cell.
 | ||
| 	defblocks.Add(alloc.Block())
 | ||
| 
 | ||
| 	if debugLifting {
 | ||
| 		fmt.Fprintln(os.Stderr, "\tlifting ", alloc, alloc.Name())
 | ||
| 	}
 | ||
| 
 | ||
| 	// Φ-insertion.
 | ||
| 	//
 | ||
| 	// What follows is the body of the main loop of the insert-φ
 | ||
| 	// function described by Cytron et al, but instead of using
 | ||
| 	// counter tricks, we just reset the 'hasAlready' and 'work'
 | ||
| 	// sets each iteration.  These are bitmaps so it's pretty cheap.
 | ||
| 
 | ||
| 	// Initialize W and work to defblocks.
 | ||
| 
 | ||
| 	for change := true; change; {
 | ||
| 		change = false
 | ||
| 		{
 | ||
| 			// Traverse iterated dominance frontier, inserting φ-nodes.
 | ||
| 			W.Set(defblocks)
 | ||
| 
 | ||
| 			for i := W.Take(); i != -1; i = W.Take() {
 | ||
| 				n := fn.Blocks[i]
 | ||
| 				for _, y := range df[n.Index] {
 | ||
| 					if Aphi.Add(y) {
 | ||
| 						if len(*alloc.Referrers()) == 0 {
 | ||
| 							continue
 | ||
| 						}
 | ||
| 						live := false
 | ||
| 						if closure == nil {
 | ||
| 							live = true
 | ||
| 						} else {
 | ||
| 							for _, ref := range *alloc.Referrers() {
 | ||
| 								if _, ok := ref.(*Load); ok {
 | ||
| 									if closure.has(y, ref.Block()) {
 | ||
| 										live = true
 | ||
| 										break
 | ||
| 									}
 | ||
| 								}
 | ||
| 							}
 | ||
| 						}
 | ||
| 						if !live {
 | ||
| 							continue
 | ||
| 						}
 | ||
| 
 | ||
| 						// Create φ-node.
 | ||
| 						// It will be prepended to v.Instrs later, if needed.
 | ||
| 						phi := &Phi{
 | ||
| 							Edges: make([]Value, len(y.Preds)),
 | ||
| 						}
 | ||
| 
 | ||
| 						phi.source = alloc.source
 | ||
| 						phi.setType(deref(alloc.Type()))
 | ||
| 						phi.block = y
 | ||
| 						if debugLifting {
 | ||
| 							fmt.Fprintf(os.Stderr, "\tplace %s = %s at block %s\n", phi.Name(), phi, y)
 | ||
| 						}
 | ||
| 						newPhis[y.Index] = append(newPhis[y.Index], newPhi{phi, alloc})
 | ||
| 
 | ||
| 						for _, p := range y.Preds {
 | ||
| 							useblocks.Add(p)
 | ||
| 						}
 | ||
| 						change = true
 | ||
| 						if defblocks.Add(y) {
 | ||
| 							W.Add(y)
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 
 | ||
| 		{
 | ||
| 			W.Set(useblocks)
 | ||
| 			for i := W.Take(); i != -1; i = W.Take() {
 | ||
| 				n := fn.Blocks[i]
 | ||
| 				for _, y := range rdf[n.Index] {
 | ||
| 					if Asigma.Add(y) {
 | ||
| 						sigmas := make([]*Sigma, 0, len(y.Succs))
 | ||
| 						anyLive := false
 | ||
| 						for _, succ := range y.Succs {
 | ||
| 							live := false
 | ||
| 							for _, ref := range *alloc.Referrers() {
 | ||
| 								if closure == nil || closure.has(succ, ref.Block()) {
 | ||
| 									live = true
 | ||
| 									anyLive = true
 | ||
| 									break
 | ||
| 								}
 | ||
| 							}
 | ||
| 							if live {
 | ||
| 								sigma := &Sigma{
 | ||
| 									From: y,
 | ||
| 									X:    alloc,
 | ||
| 								}
 | ||
| 								sigma.source = alloc.source
 | ||
| 								sigma.setType(deref(alloc.Type()))
 | ||
| 								sigma.block = succ
 | ||
| 								sigmas = append(sigmas, sigma)
 | ||
| 							} else {
 | ||
| 								sigmas = append(sigmas, nil)
 | ||
| 							}
 | ||
| 						}
 | ||
| 
 | ||
| 						if anyLive {
 | ||
| 							newSigmas[y.Index] = append(newSigmas[y.Index], newSigma{alloc, sigmas})
 | ||
| 							for _, s := range y.Succs {
 | ||
| 								defblocks.Add(s)
 | ||
| 							}
 | ||
| 							change = true
 | ||
| 							if useblocks.Add(y) {
 | ||
| 								W.Add(y)
 | ||
| 							}
 | ||
| 						}
 | ||
| 					}
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| }
 | ||
| 
 | ||
| // replaceAll replaces all intraprocedural uses of x with y,
 | ||
| // updating x.Referrers and y.Referrers.
 | ||
| // Precondition: x.Referrers() != nil, i.e. x must be local to some function.
 | ||
| //
 | ||
| func replaceAll(x, y Value) {
 | ||
| 	var rands []*Value
 | ||
| 	pxrefs := x.Referrers()
 | ||
| 	pyrefs := y.Referrers()
 | ||
| 	for _, instr := range *pxrefs {
 | ||
| 		rands = instr.Operands(rands[:0]) // recycle storage
 | ||
| 		for _, rand := range rands {
 | ||
| 			if *rand != nil {
 | ||
| 				if *rand == x {
 | ||
| 					*rand = y
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 		if pyrefs != nil {
 | ||
| 			*pyrefs = append(*pyrefs, instr) // dups ok
 | ||
| 		}
 | ||
| 	}
 | ||
| 	*pxrefs = nil // x is now unreferenced
 | ||
| }
 | ||
| 
 | ||
| // renamed returns the value to which alloc is being renamed,
 | ||
| // constructing it lazily if it's the implicit zero initialization.
 | ||
| //
 | ||
| func renamed(fn *Function, renaming []Value, alloc *Alloc) Value {
 | ||
| 	v := renaming[alloc.index]
 | ||
| 	if v == nil {
 | ||
| 		v = emitConst(fn, zeroConst(deref(alloc.Type())))
 | ||
| 		renaming[alloc.index] = v
 | ||
| 	}
 | ||
| 	return v
 | ||
| }
 | ||
| 
 | ||
| // rename implements the Cytron et al-based SSI renaming algorithm, a
 | ||
| // preorder traversal of the dominator tree replacing all loads of
 | ||
| // Alloc cells with the value stored to that cell by the dominating
 | ||
| // store instruction.
 | ||
| //
 | ||
| // renaming is a map from *Alloc (keyed by index number) to its
 | ||
| // dominating stored value; newPhis[x] is the set of new φ-nodes to be
 | ||
| // prepended to block x.
 | ||
| //
 | ||
| func rename(u *BasicBlock, renaming []Value, newPhis newPhiMap, newSigmas newSigmaMap) {
 | ||
| 	// Each φ-node becomes the new name for its associated Alloc.
 | ||
| 	for _, np := range newPhis[u.Index] {
 | ||
| 		phi := np.phi
 | ||
| 		alloc := np.alloc
 | ||
| 		renaming[alloc.index] = phi
 | ||
| 	}
 | ||
| 
 | ||
| 	// Rename loads and stores of allocs.
 | ||
| 	for i, instr := range u.Instrs {
 | ||
| 		switch instr := instr.(type) {
 | ||
| 		case *Alloc:
 | ||
| 			if instr.index >= 0 { // store of zero to Alloc cell
 | ||
| 				// Replace dominated loads by the zero value.
 | ||
| 				renaming[instr.index] = nil
 | ||
| 				if debugLifting {
 | ||
| 					fmt.Fprintf(os.Stderr, "\tkill alloc %s\n", instr)
 | ||
| 				}
 | ||
| 				// Delete the Alloc.
 | ||
| 				u.Instrs[i] = nil
 | ||
| 				u.gaps++
 | ||
| 			}
 | ||
| 
 | ||
| 		case *Store:
 | ||
| 			if alloc, ok := instr.Addr.(*Alloc); ok && alloc.index >= 0 { // store to Alloc cell
 | ||
| 				// Replace dominated loads by the stored value.
 | ||
| 				renaming[alloc.index] = instr.Val
 | ||
| 				if debugLifting {
 | ||
| 					fmt.Fprintf(os.Stderr, "\tkill store %s; new value: %s\n",
 | ||
| 						instr, instr.Val.Name())
 | ||
| 				}
 | ||
| 				if refs := instr.Addr.Referrers(); refs != nil {
 | ||
| 					*refs = removeInstr(*refs, instr)
 | ||
| 				}
 | ||
| 				if refs := instr.Val.Referrers(); refs != nil {
 | ||
| 					*refs = removeInstr(*refs, instr)
 | ||
| 				}
 | ||
| 				// Delete the Store.
 | ||
| 				u.Instrs[i] = nil
 | ||
| 				u.gaps++
 | ||
| 			}
 | ||
| 
 | ||
| 		case *Load:
 | ||
| 			if alloc, ok := instr.X.(*Alloc); ok && alloc.index >= 0 { // load of Alloc cell
 | ||
| 				// In theory, we wouldn't be able to replace loads
 | ||
| 				// directly, because a loaded value could be used in
 | ||
| 				// different branches, in which case it should be
 | ||
| 				// replaced with different sigma nodes. But we can't
 | ||
| 				// simply defer replacement, either, because then
 | ||
| 				// later stores might incorrectly affect this load.
 | ||
| 				//
 | ||
| 				// To avoid doing renaming on _all_ values (instead of
 | ||
| 				// just loads and stores like we're doing), we make
 | ||
| 				// sure during code generation that each load is only
 | ||
| 				// used in one block. For example, in constant switch
 | ||
| 				// statements, where the tag is only evaluated once,
 | ||
| 				// we store it in a temporary and load it for each
 | ||
| 				// comparison, so that we have individual loads to
 | ||
| 				// replace.
 | ||
| 				newval := renamed(u.Parent(), renaming, alloc)
 | ||
| 				if debugLifting {
 | ||
| 					fmt.Fprintf(os.Stderr, "\tupdate load %s = %s with %s\n",
 | ||
| 						instr.Name(), instr, newval)
 | ||
| 				}
 | ||
| 				replaceAll(instr, newval)
 | ||
| 				u.Instrs[i] = nil
 | ||
| 				u.gaps++
 | ||
| 			}
 | ||
| 
 | ||
| 		case *DebugRef:
 | ||
| 			if x, ok := instr.X.(*Alloc); ok && x.index >= 0 {
 | ||
| 				if instr.IsAddr {
 | ||
| 					instr.X = renamed(u.Parent(), renaming, x)
 | ||
| 					instr.IsAddr = false
 | ||
| 
 | ||
| 					// Add DebugRef to instr.X's referrers.
 | ||
| 					if refs := instr.X.Referrers(); refs != nil {
 | ||
| 						*refs = append(*refs, instr)
 | ||
| 					}
 | ||
| 				} else {
 | ||
| 					// A source expression denotes the address
 | ||
| 					// of an Alloc that was optimized away.
 | ||
| 					instr.X = nil
 | ||
| 
 | ||
| 					// Delete the DebugRef.
 | ||
| 					u.Instrs[i] = nil
 | ||
| 					u.gaps++
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// update all outgoing sigma nodes with the dominating store
 | ||
| 	for _, sigmas := range newSigmas[u.Index] {
 | ||
| 		for _, sigma := range sigmas.sigmas {
 | ||
| 			if sigma == nil {
 | ||
| 				continue
 | ||
| 			}
 | ||
| 			sigma.X = renamed(u.Parent(), renaming, sigmas.alloc)
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// For each φ-node in a CFG successor, rename the edge.
 | ||
| 	for succi, v := range u.Succs {
 | ||
| 		phis := newPhis[v.Index]
 | ||
| 		if len(phis) == 0 {
 | ||
| 			continue
 | ||
| 		}
 | ||
| 		i := v.predIndex(u)
 | ||
| 		for _, np := range phis {
 | ||
| 			phi := np.phi
 | ||
| 			alloc := np.alloc
 | ||
| 			// if there's a sigma node, use it, else use the dominating value
 | ||
| 			var newval Value
 | ||
| 			for _, sigmas := range newSigmas[u.Index] {
 | ||
| 				if sigmas.alloc == alloc && sigmas.sigmas[succi] != nil {
 | ||
| 					newval = sigmas.sigmas[succi]
 | ||
| 					break
 | ||
| 				}
 | ||
| 			}
 | ||
| 			if newval == nil {
 | ||
| 				newval = renamed(u.Parent(), renaming, alloc)
 | ||
| 			}
 | ||
| 			if debugLifting {
 | ||
| 				fmt.Fprintf(os.Stderr, "\tsetphi %s edge %s -> %s (#%d) (alloc=%s) := %s\n",
 | ||
| 					phi.Name(), u, v, i, alloc.Name(), newval.Name())
 | ||
| 			}
 | ||
| 			phi.Edges[i] = newval
 | ||
| 			if prefs := newval.Referrers(); prefs != nil {
 | ||
| 				*prefs = append(*prefs, phi)
 | ||
| 			}
 | ||
| 		}
 | ||
| 	}
 | ||
| 
 | ||
| 	// Continue depth-first recursion over domtree, pushing a
 | ||
| 	// fresh copy of the renaming map for each subtree.
 | ||
| 	r := make([]Value, len(renaming))
 | ||
| 	for _, v := range u.dom.children {
 | ||
| 		// XXX add debugging
 | ||
| 		copy(r, renaming)
 | ||
| 
 | ||
| 		// on entry to a block, the incoming sigma nodes become the new values for their alloc
 | ||
| 		if idx := u.succIndex(v); idx != -1 {
 | ||
| 			for _, sigma := range newSigmas[u.Index] {
 | ||
| 				if sigma.sigmas[idx] != nil {
 | ||
| 					r[sigma.alloc.index] = sigma.sigmas[idx]
 | ||
| 				}
 | ||
| 			}
 | ||
| 		}
 | ||
| 		rename(v, r, newPhis, newSigmas)
 | ||
| 	}
 | ||
| 
 | ||
| }
 |