Source file src/cmd/compile/internal/ssacompile/fuse_comparisons.go

     1  // Copyright 2019 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 ssacompile
     6  
     7  import (
     8  	"cmd/compile/internal/ssa"
     9  	"cmd/compile/internal/ssa/block"
    10  	"cmd/compile/internal/ssa/ssaop"
    11  )
    12  
    13  // fuseIntInRange transforms integer range checks to remove the short-circuit operator. For example,
    14  // it would convert `if 1 <= x && x < 5 { ... }` into `if (1 <= x) & (x < 5) { ... }`. Rewrite rules
    15  // can then optimize these into unsigned range checks, `if unsigned(x-1) < 4 { ... }` in this case.
    16  func fuseIntInRange(b *ssa.Block) bool {
    17  	return fuseComparisons(b, canOptIntInRange)
    18  }
    19  
    20  // fuseNanCheck replaces the short-circuit operators between NaN checks and comparisons with
    21  // constants. For example, it would transform `if x != x || x > 1.0 { ... }` into
    22  // `if (x != x) | (x > 1.0) { ... }`. Rewrite rules can then merge the NaN check with the comparison,
    23  // in this case generating `if !(x <= 1.0) { ... }`.
    24  func fuseNanCheck(b *ssa.Block) bool {
    25  	return fuseComparisons(b, canOptNanCheck)
    26  }
    27  
    28  // fuseSingleBitDifference replaces the short-circuit operators between equality checks with
    29  // constants that only differ by a single bit. For example, it would convert
    30  // `if x == 4 || x == 6 { ... }` into `if (x == 4) | (x == 6) { ... }`. Rewrite rules can
    31  // then optimize these using a bitwise operation, in this case generating `if x|2 == 6 { ... }`.
    32  func fuseSingleBitDifference(b *ssa.Block) bool {
    33  	return fuseComparisons(b, canOptSingleBitDifference)
    34  }
    35  
    36  // fuseComparisons looks for control graphs that match this pattern:
    37  //
    38  //	p - predecessor
    39  //	|\
    40  //	| b - block
    41  //	|/ \
    42  //	s0 s1 - successors
    43  //
    44  // This pattern is typical for if statements such as `if x || y { ... }` and `if x && y { ... }`.
    45  //
    46  // If canOptControls returns true when passed the control values for p and b then fuseComparisons
    47  // will try to convert p into a plain block with only one successor (b) and modify b's control
    48  // value to include p's control value (effectively causing b to be speculatively executed).
    49  //
    50  // This transformation results in a control graph that will now look like this:
    51  //
    52  //	p
    53  //	 \
    54  //	  b
    55  //	 / \
    56  //	s0 s1
    57  //
    58  // Later passes will then fuse p and b.
    59  //
    60  // In other words `if x || y { ... }` will become `if x | y { ... }` and `if x && y { ... }` will
    61  // become `if x & y { ... }`. This is a useful transformation because we can then use rewrite
    62  // rules to optimize `x | y` and `x & y`.
    63  func fuseComparisons(b *ssa.Block, canOptControls func(a, b *ssa.Value, op ssaop.Op) bool) bool {
    64  	if len(b.Preds) != 1 {
    65  		return false
    66  	}
    67  	p := b.Preds[0].Block()
    68  	if b.Kind != block.BlockIf || p.Kind != block.BlockIf {
    69  		return false
    70  	}
    71  
    72  	// Don't merge control values if b is likely to be bypassed anyway.
    73  	if p.Likely == ssa.BranchLikely && p.Succs[0].Block() != b {
    74  		return false
    75  	}
    76  	if p.Likely == ssa.BranchUnlikely && p.Succs[1].Block() != b {
    77  		return false
    78  	}
    79  
    80  	// If the first (true) successors match then we have a disjunction (||).
    81  	// If the second (false) successors match then we have a conjunction (&&).
    82  	for i, op := range [2]ssaop.Op{ssaop.OpOrB, ssaop.OpAndB} {
    83  		if p.Succs[i].Block() != b.Succs[i].Block() {
    84  			continue
    85  		}
    86  
    87  		// Check if the control values can be usefully combined.
    88  		bc := b.Controls[0]
    89  		pc := p.Controls[0]
    90  		if !canOptControls(bc, pc, op) {
    91  			return false
    92  		}
    93  
    94  		// TODO(mundaym): should we also check the cost of executing b?
    95  		// Currently we might speculatively execute b even if b contains
    96  		// a lot of instructions. We could just check that len(b.Values)
    97  		// is lower than a fixed amount. Bear in mind however that the
    98  		// other optimization passes might yet reduce the cost of b
    99  		// significantly so we shouldn't be overly conservative.
   100  		if !canSpeculativelyExecute(b) {
   101  			return false
   102  		}
   103  
   104  		// if the destination block has any phis that are differentiated between
   105  		// the edge being removed and the other edge, removing it will change
   106  		// the behavior of the program
   107  		if hasDifferentiatedPhi(p.Succs[i], b.Succs[i]) {
   108  			continue
   109  		}
   110  
   111  		// Logically combine the control values for p and b.
   112  		v := b.NewValue0(bc.Pos, op, bc.Type)
   113  		v.AddArg(pc)
   114  		v.AddArg(bc)
   115  
   116  		// Set the combined control value as the control value for b.
   117  		b.SetControl(v)
   118  
   119  		// Modify p so that it jumps directly to b.
   120  		p.RemoveEdge(i)
   121  		p.Kind = block.BlockPlain
   122  		p.Likely = ssa.BranchUnknown
   123  		p.ResetControls()
   124  
   125  		return true
   126  	}
   127  
   128  	// TODO: could negate condition(s) to merge controls.
   129  	return false
   130  }
   131  
   132  func hasDifferentiatedPhi(x ssa.Edge, y ssa.Edge) bool {
   133  	b := x.Block()
   134  	if y.Block() != b {
   135  		panic("non matching edges")
   136  	}
   137  	xi := x.I
   138  	yi := y.I
   139  	for _, v := range b.Values {
   140  		if v.Op != ssaop.OpPhi {
   141  			continue
   142  		}
   143  		if v.Args[xi] != v.Args[yi] {
   144  			return true
   145  		}
   146  	}
   147  	return false
   148  }
   149  
   150  // getConstIntArgIndex returns the index of the first argument that is a
   151  // constant integer or -1 if no such argument exists.
   152  func getConstIntArgIndex(v *ssa.Value) int {
   153  	for i, a := range v.Args {
   154  		switch a.Op {
   155  		case ssaop.OpConst8, ssaop.OpConst16, ssaop.OpConst32, ssaop.OpConst64:
   156  			return i
   157  		}
   158  	}
   159  	return -1
   160  }
   161  
   162  // isSignedInequality reports whether op represents the inequality < or ≤
   163  // in the signed domain.
   164  func isSignedInequality(v *ssa.Value) bool {
   165  	switch v.Op {
   166  	case ssaop.OpLess64, ssaop.OpLess32, ssaop.OpLess16, ssaop.OpLess8,
   167  		ssaop.OpLeq64, ssaop.OpLeq32, ssaop.OpLeq16, ssaop.OpLeq8:
   168  		return true
   169  	}
   170  	return false
   171  }
   172  
   173  // isUnsignedInequality reports whether op represents the inequality < or ≤
   174  // in the unsigned domain, including "x != 0", which is equivalent to the
   175  // unsigned "0 < x".
   176  func isUnsignedInequality(v *ssa.Value) bool {
   177  	switch v.Op {
   178  	case ssaop.OpLess64U, ssaop.OpLess32U, ssaop.OpLess16U, ssaop.OpLess8U,
   179  		ssaop.OpLeq64U, ssaop.OpLeq32U, ssaop.OpLeq16U, ssaop.OpLeq8U:
   180  		return true
   181  	case ssaop.OpNeq64, ssaop.OpNeq32, ssaop.OpNeq16, ssaop.OpNeq8:
   182  		// "x != 0" is equivalent to the unsigned "0 < x", and is its
   183  		// canonical form; see "prefer equalities with zero" in generic.rules.
   184  		return ssa.IsConstZero(v.Args[0]) || ssa.IsConstZero(v.Args[1])
   185  	}
   186  	return false
   187  }
   188  
   189  func canOptIntInRange(x, y *ssa.Value, op ssaop.Op) bool {
   190  	// We need both inequalities to be either in the signed or unsigned domain.
   191  	// TODO(mundaym): it would also be good to merge when we have an Eq op that
   192  	// could be transformed into a Less/Leq. For example in the unsigned
   193  	// domain 'x == 0 || 3 < x' is equivalent to 'x <= 0 || 3 < x'
   194  	inequalityChecks := [...]func(*ssa.Value) bool{
   195  		isSignedInequality,
   196  		isUnsignedInequality,
   197  	}
   198  	for _, f := range inequalityChecks {
   199  		if !f(x) || !f(y) {
   200  			continue
   201  		}
   202  
   203  		// Check that both inequalities are comparisons with constants.
   204  		xi := getConstIntArgIndex(x)
   205  		if xi < 0 {
   206  			return false
   207  		}
   208  		yi := getConstIntArgIndex(y)
   209  		if yi < 0 {
   210  			return false
   211  		}
   212  
   213  		// Check that the non-constant arguments to the inequalities
   214  		// are the same.
   215  		return x.Args[xi^1] == y.Args[yi^1]
   216  	}
   217  	return false
   218  }
   219  
   220  // canOptNanCheck reports whether one of arguments is a NaN check and the other
   221  // is a comparison with a constant that can be combined together.
   222  //
   223  // Examples (c must be a constant):
   224  //
   225  //	v != v || v <  c => !(c <= v)
   226  //	v != v || v <= c => !(c <  v)
   227  //	v != v || c <  v => !(v <= c)
   228  //	v != v || c <= v => !(v <  c)
   229  func canOptNanCheck(x, y *ssa.Value, op ssaop.Op) bool {
   230  	if op != ssaop.OpOrB {
   231  		return false
   232  	}
   233  
   234  	for i := 0; i <= 1; i, x, y = i+1, y, x {
   235  		if len(x.Args) != 2 || x.Args[0] != x.Args[1] {
   236  			continue
   237  		}
   238  		v := x.Args[0]
   239  		switch x.Op {
   240  		case ssaop.OpNeq64F:
   241  			if y.Op != ssaop.OpLess64F && y.Op != ssaop.OpLeq64F {
   242  				return false
   243  			}
   244  			for j := 0; j <= 1; j++ {
   245  				a, b := y.Args[j], y.Args[j^1]
   246  				if a.Op != ssaop.OpConst64F {
   247  					continue
   248  				}
   249  				// Sign bit operations not affect NaN check results. This special case allows us
   250  				// to optimize statements like `if v != v || Abs(v) > c { ... }`.
   251  				if (b.Op == ssaop.OpAbs || b.Op == ssaop.OpNeg64F) && b.Args[0] == v {
   252  					return true
   253  				}
   254  				return b == v
   255  			}
   256  		case ssaop.OpNeq32F:
   257  			if y.Op != ssaop.OpLess32F && y.Op != ssaop.OpLeq32F {
   258  				return false
   259  			}
   260  			for j := 0; j <= 1; j++ {
   261  				a, b := y.Args[j], y.Args[j^1]
   262  				if a.Op != ssaop.OpConst32F {
   263  					continue
   264  				}
   265  				// Sign bit operations not affect NaN check results. This special case allows us
   266  				// to optimize statements like `if v != v || -v > c { ... }`.
   267  				if b.Op == ssaop.OpNeg32F && b.Args[0] == v {
   268  					return true
   269  				}
   270  				return b == v
   271  			}
   272  		}
   273  	}
   274  	return false
   275  }
   276  
   277  // canOptSingleBitDifference returns true if x op y matches either:
   278  //
   279  //	v == c || v == d
   280  //	v != c && v != d
   281  //
   282  // Where c and d are constant values that differ by a single bit.
   283  func canOptSingleBitDifference(x, y *ssa.Value, op ssaop.Op) bool {
   284  	if x.Op != y.Op {
   285  		return false
   286  	}
   287  	switch x.Op {
   288  	case ssaop.OpEq64, ssaop.OpEq32, ssaop.OpEq16, ssaop.OpEq8:
   289  		if op != ssaop.OpOrB {
   290  			return false
   291  		}
   292  	case ssaop.OpNeq64, ssaop.OpNeq32, ssaop.OpNeq16, ssaop.OpNeq8:
   293  		if op != ssaop.OpAndB {
   294  			return false
   295  		}
   296  	default:
   297  		return false
   298  	}
   299  
   300  	xi := getConstIntArgIndex(x)
   301  	if xi < 0 {
   302  		return false
   303  	}
   304  	yi := getConstIntArgIndex(y)
   305  	if yi < 0 {
   306  		return false
   307  	}
   308  	if x.Args[xi^1] != y.Args[yi^1] {
   309  		return false
   310  	}
   311  	return ssa.OneBit(x.Args[xi].AuxInt ^ y.Args[yi].AuxInt)
   312  }
   313  

View as plain text