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

View as plain text