github.com/hikaru7719/go@v0.0.0-20181025140707-c8b2ac68906a/src/cmd/compile/internal/ssa/branchelim.go (about)

     1  // Copyright 2017 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  // branchelim tries to eliminate branches by
     8  // generating CondSelect instructions.
     9  //
    10  // Search for basic blocks that look like
    11  //
    12  // bb0            bb0
    13  //  | \          /   \
    14  //  | bb1  or  bb1   bb2    <- trivial if/else blocks
    15  //  | /          \   /
    16  // bb2            bb3
    17  //
    18  // where the intermediate blocks are mostly empty (with no side-effects);
    19  // rewrite Phis in the postdominator as CondSelects.
    20  func branchelim(f *Func) {
    21  	// FIXME: add support for lowering CondSelects on more architectures
    22  	switch f.Config.arch {
    23  	case "arm64", "amd64":
    24  		// implemented
    25  	default:
    26  		return
    27  	}
    28  
    29  	change := true
    30  	for change {
    31  		change = false
    32  		for _, b := range f.Blocks {
    33  			change = elimIf(f, b) || elimIfElse(f, b) || change
    34  		}
    35  	}
    36  }
    37  
    38  func canCondSelect(v *Value, arch string) bool {
    39  	// For now, stick to simple scalars that fit in registers
    40  	switch {
    41  	case v.Type.Size() > v.Block.Func.Config.RegSize:
    42  		return false
    43  	case v.Type.IsPtrShaped():
    44  		return true
    45  	case v.Type.IsInteger():
    46  		if arch == "amd64" && v.Type.Size() < 2 {
    47  			// amd64 doesn't support CMOV with byte registers
    48  			return false
    49  		}
    50  		return true
    51  	default:
    52  		return false
    53  	}
    54  }
    55  
    56  func elimIf(f *Func, dom *Block) bool {
    57  	// See if dom is an If with one arm that
    58  	// is trivial and succeeded by the other
    59  	// successor of dom.
    60  	if dom.Kind != BlockIf || dom.Likely != BranchUnknown {
    61  		return false
    62  	}
    63  	var simple, post *Block
    64  	for i := range dom.Succs {
    65  		bb, other := dom.Succs[i].Block(), dom.Succs[i^1].Block()
    66  		if isLeafPlain(bb) && bb.Succs[0].Block() == other {
    67  			simple = bb
    68  			post = other
    69  			break
    70  		}
    71  	}
    72  	if simple == nil || len(post.Preds) != 2 || post == dom {
    73  		return false
    74  	}
    75  
    76  	// We've found our diamond CFG of blocks.
    77  	// Now decide if fusing 'simple' into dom+post
    78  	// looks profitable.
    79  
    80  	// Check that there are Phis, and that all of them
    81  	// can be safely rewritten to CondSelect.
    82  	hasphis := false
    83  	for _, v := range post.Values {
    84  		if v.Op == OpPhi {
    85  			hasphis = true
    86  			if !canCondSelect(v, f.Config.arch) {
    87  				return false
    88  			}
    89  		}
    90  	}
    91  	if !hasphis {
    92  		return false
    93  	}
    94  
    95  	// Pick some upper bound for the number of instructions
    96  	// we'd be willing to execute just to generate a dead
    97  	// argument to CondSelect. In the worst case, this is
    98  	// the number of useless instructions executed.
    99  	const maxfuseinsts = 2
   100  
   101  	if len(simple.Values) > maxfuseinsts || !allTrivial(simple) {
   102  		return false
   103  	}
   104  
   105  	// Replace Phi instructions in b with CondSelect instructions
   106  	swap := (post.Preds[0].Block() == dom) != (dom.Succs[0].Block() == post)
   107  	for _, v := range post.Values {
   108  		if v.Op != OpPhi {
   109  			continue
   110  		}
   111  		v.Op = OpCondSelect
   112  		if swap {
   113  			v.Args[0], v.Args[1] = v.Args[1], v.Args[0]
   114  		}
   115  		v.AddArg(dom.Control)
   116  	}
   117  
   118  	// Put all of the instructions into 'dom'
   119  	// and update the CFG appropriately.
   120  	dom.Kind = post.Kind
   121  	dom.SetControl(post.Control)
   122  	dom.Aux = post.Aux
   123  	dom.Succs = append(dom.Succs[:0], post.Succs...)
   124  	for i := range dom.Succs {
   125  		e := dom.Succs[i]
   126  		e.b.Preds[e.i].b = dom
   127  	}
   128  
   129  	for i := range simple.Values {
   130  		simple.Values[i].Block = dom
   131  	}
   132  	for i := range post.Values {
   133  		post.Values[i].Block = dom
   134  	}
   135  	dom.Values = append(dom.Values, simple.Values...)
   136  	dom.Values = append(dom.Values, post.Values...)
   137  
   138  	// Trash 'post' and 'simple'
   139  	clobberBlock(post)
   140  	clobberBlock(simple)
   141  
   142  	f.invalidateCFG()
   143  	return true
   144  }
   145  
   146  // is this a BlockPlain with one predecessor?
   147  func isLeafPlain(b *Block) bool {
   148  	return b.Kind == BlockPlain && len(b.Preds) == 1
   149  }
   150  
   151  func clobberBlock(b *Block) {
   152  	b.Values = nil
   153  	b.Preds = nil
   154  	b.Succs = nil
   155  	b.Aux = nil
   156  	b.SetControl(nil)
   157  	b.Likely = BranchUnknown
   158  	b.Kind = BlockInvalid
   159  }
   160  
   161  func elimIfElse(f *Func, b *Block) bool {
   162  	// See if 'b' ends in an if/else: it should
   163  	// have two successors, both of which are BlockPlain
   164  	// and succeeded by the same block.
   165  	if b.Kind != BlockIf || b.Likely != BranchUnknown {
   166  		return false
   167  	}
   168  	yes, no := b.Succs[0].Block(), b.Succs[1].Block()
   169  	if !isLeafPlain(yes) || len(yes.Values) > 1 || !allTrivial(yes) {
   170  		return false
   171  	}
   172  	if !isLeafPlain(no) || len(no.Values) > 1 || !allTrivial(no) {
   173  		return false
   174  	}
   175  	if b.Succs[0].Block().Succs[0].Block() != b.Succs[1].Block().Succs[0].Block() {
   176  		return false
   177  	}
   178  	// block that postdominates the if/else
   179  	post := b.Succs[0].Block().Succs[0].Block()
   180  	if len(post.Preds) != 2 || post == b {
   181  		return false
   182  	}
   183  	hasphis := false
   184  	for _, v := range post.Values {
   185  		if v.Op == OpPhi {
   186  			hasphis = true
   187  			if !canCondSelect(v, f.Config.arch) {
   188  				return false
   189  			}
   190  		}
   191  	}
   192  	if !hasphis {
   193  		return false
   194  	}
   195  
   196  	// Don't generate CondSelects if branch is cheaper.
   197  	if !shouldElimIfElse(no, yes, post, f.Config.arch) {
   198  		return false
   199  	}
   200  
   201  	// now we're committed: rewrite each Phi as a CondSelect
   202  	swap := post.Preds[0].Block() != b.Succs[0].Block()
   203  	for _, v := range post.Values {
   204  		if v.Op != OpPhi {
   205  			continue
   206  		}
   207  		v.Op = OpCondSelect
   208  		if swap {
   209  			v.Args[0], v.Args[1] = v.Args[1], v.Args[0]
   210  		}
   211  		v.AddArg(b.Control)
   212  	}
   213  
   214  	// Move the contents of all of these
   215  	// blocks into 'b' and update CFG edges accordingly
   216  	b.Kind = post.Kind
   217  	b.SetControl(post.Control)
   218  	b.Aux = post.Aux
   219  	b.Succs = append(b.Succs[:0], post.Succs...)
   220  	for i := range b.Succs {
   221  		e := b.Succs[i]
   222  		e.b.Preds[e.i].b = b
   223  	}
   224  	for i := range post.Values {
   225  		post.Values[i].Block = b
   226  	}
   227  	for i := range yes.Values {
   228  		yes.Values[i].Block = b
   229  	}
   230  	for i := range no.Values {
   231  		no.Values[i].Block = b
   232  	}
   233  	b.Values = append(b.Values, yes.Values...)
   234  	b.Values = append(b.Values, no.Values...)
   235  	b.Values = append(b.Values, post.Values...)
   236  
   237  	// trash post, yes, and no
   238  	clobberBlock(yes)
   239  	clobberBlock(no)
   240  	clobberBlock(post)
   241  
   242  	f.invalidateCFG()
   243  	return true
   244  }
   245  
   246  // shouldElimIfElse reports whether estimated cost of eliminating branch
   247  // is lower than threshold.
   248  func shouldElimIfElse(no, yes, post *Block, arch string) bool {
   249  	switch arch {
   250  	default:
   251  		return true
   252  	case "amd64":
   253  		const maxcost = 2
   254  		phi := 0
   255  		other := 0
   256  		for _, v := range post.Values {
   257  			if v.Op == OpPhi {
   258  				// Each phi results in CondSelect, which lowers into CMOV,
   259  				// CMOV has latency >1 on most CPUs.
   260  				phi++
   261  			}
   262  			for _, x := range v.Args {
   263  				if x.Block == no || x.Block == yes {
   264  					other++
   265  				}
   266  			}
   267  		}
   268  		cost := phi * 1
   269  		if phi > 1 {
   270  			// If we have more than 1 phi and some values in post have args
   271  			// in yes or no blocks, we may have to recalucalte condition, because
   272  			// those args may clobber flags. For now assume that all operations clobber flags.
   273  			cost += other * 1
   274  		}
   275  		return cost < maxcost
   276  	}
   277  }
   278  
   279  func allTrivial(b *Block) bool {
   280  	// don't fuse memory ops, Phi ops, divides (can panic),
   281  	// or anything else with side-effects
   282  	for _, v := range b.Values {
   283  		if v.Op == OpPhi || isDivMod(v.Op) || v.Type.IsMemory() ||
   284  			v.MemoryArg() != nil || opcodeTable[v.Op].hasSideEffects {
   285  			return false
   286  		}
   287  	}
   288  	return true
   289  }
   290  
   291  func isDivMod(op Op) bool {
   292  	switch op {
   293  	case OpDiv8, OpDiv8u, OpDiv16, OpDiv16u,
   294  		OpDiv32, OpDiv32u, OpDiv64, OpDiv64u, OpDiv128u,
   295  		OpDiv32F, OpDiv64F,
   296  		OpMod8, OpMod8u, OpMod16, OpMod16u,
   297  		OpMod32, OpMod32u, OpMod64, OpMod64u:
   298  		return true
   299  	default:
   300  		return false
   301  	}
   302  }