Source file src/cmd/compile/internal/ssa/loopbce.go

     1  // Copyright 2018 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  import (
     8  	"cmd/compile/internal/base"
     9  	"cmd/compile/internal/types"
    10  	"fmt"
    11  )
    12  
    13  type indVarFlags uint8
    14  
    15  const (
    16  	indVarMinExc indVarFlags = 1 << iota // minimum value is exclusive (default: inclusive)
    17  	indVarMaxInc                         // maximum value is inclusive (default: exclusive)
    18  )
    19  
    20  type indVar struct {
    21  	ind   *Value // induction variable
    22  	nxt   *Value // the incremented variable
    23  	min   *Value // minimum value, inclusive/exclusive depends on flags
    24  	max   *Value // maximum value, inclusive/exclusive depends on flags
    25  	entry *Block // the block where the edge from the succeeded comparison of the induction variable goes to, means when the bound check has passed.
    26  	step  int64
    27  	flags indVarFlags
    28  	// Invariant: for all blocks dominated by entry:
    29  	//	min <= ind <  max    [if flags == 0]
    30  	//	min <  ind <  max    [if flags == indVarMinExc]
    31  	//	min <= ind <= max    [if flags == indVarMaxInc]
    32  	//	min <  ind <= max    [if flags == indVarMinExc|indVarMaxInc]
    33  }
    34  
    35  // parseIndVar checks whether the SSA value passed as argument is a valid induction
    36  // variable, and, if so, extracts:
    37  //   - the minimum bound
    38  //   - the increment value
    39  //   - the "next" value (SSA value that is Phi'd into the induction variable every loop)
    40  //   - the header's edge returning from the body
    41  //
    42  // Currently, we detect induction variables that match (Phi min nxt),
    43  // with nxt being (Add inc ind).
    44  // If it can't parse the induction variable correctly, it returns (nil, nil, nil).
    45  func parseIndVar(ind *Value) (min, inc, nxt *Value, loopReturn Edge) {
    46  	if ind.Op != OpPhi {
    47  		return
    48  	}
    49  
    50  	if n := ind.Args[0]; (n.Op == OpAdd64 || n.Op == OpAdd32 || n.Op == OpAdd16 || n.Op == OpAdd8) && (n.Args[0] == ind || n.Args[1] == ind) {
    51  		min, nxt, loopReturn = ind.Args[1], n, ind.Block.Preds[0]
    52  	} else if n := ind.Args[1]; (n.Op == OpAdd64 || n.Op == OpAdd32 || n.Op == OpAdd16 || n.Op == OpAdd8) && (n.Args[0] == ind || n.Args[1] == ind) {
    53  		min, nxt, loopReturn = ind.Args[0], n, ind.Block.Preds[1]
    54  	} else {
    55  		// Not a recognized induction variable.
    56  		return
    57  	}
    58  
    59  	if nxt.Args[0] == ind { // nxt = ind + inc
    60  		inc = nxt.Args[1]
    61  	} else if nxt.Args[1] == ind { // nxt = inc + ind
    62  		inc = nxt.Args[0]
    63  	} else {
    64  		panic("unreachable") // one of the cases must be true from the above.
    65  	}
    66  
    67  	return
    68  }
    69  
    70  // findIndVar finds induction variables in a function.
    71  //
    72  // Look for variables and blocks that satisfy the following
    73  //
    74  //	 loop:
    75  //	   ind = (Phi min nxt),
    76  //	   if ind < max
    77  //	     then goto enter_loop
    78  //	     else goto exit_loop
    79  //
    80  //	   enter_loop:
    81  //		do something
    82  //	      nxt = inc + ind
    83  //		goto loop
    84  //
    85  //	 exit_loop:
    86  //
    87  // We may have more than one induction variables, the loop in the go
    88  // source code may looks like this:
    89  //
    90  //	for i >= 0 && j >= 0 {
    91  //		// use i and j
    92  //		i--
    93  //		j--
    94  //	}
    95  //
    96  // So, also look for variables and blocks that satisfy the following
    97  //
    98  //	loop:
    99  //	  i = (Phi maxi nxti)
   100  //	  j = (Phi maxj nxtj)
   101  //	  if i >= mini
   102  //	    then goto check_j
   103  //	    else goto exit_loop
   104  //
   105  //	check_j:
   106  //	  if j >= minj
   107  //	    then goto enter_loop
   108  //	    else goto exit_loop
   109  //
   110  //	enter_loop:
   111  //	  do something
   112  //	  nxti = i - di
   113  //	  nxtj = j - dj
   114  //	  goto loop
   115  //
   116  //	exit_loop:
   117  func findIndVar(f *Func) []indVar {
   118  	var iv []indVar
   119  	sdom := f.Sdom()
   120  
   121  nextblock:
   122  	for _, b := range f.Blocks {
   123  		if b.Kind != BlockIf {
   124  			continue
   125  		}
   126  		c := b.Controls[0]
   127  		for idx := range 2 {
   128  			// Check that the control if it either ind </<= limit or limit </<= ind.
   129  			// TODO: Handle unsigned comparisons?
   130  			inclusive := false
   131  			switch c.Op {
   132  			case OpLeq64, OpLeq32, OpLeq16, OpLeq8:
   133  				inclusive = true
   134  			case OpLess64, OpLess32, OpLess16, OpLess8:
   135  			default:
   136  				continue nextblock
   137  			}
   138  
   139  			less := idx == 0
   140  			// induction variable, ending value
   141  			ind, limit := c.Args[idx], c.Args[1-idx]
   142  			// starting value, increment value, next value, loop return edge
   143  			init, inc, nxt, loopReturn := parseIndVar(ind)
   144  			if init == nil {
   145  				continue // this is not an induction variable
   146  			}
   147  
   148  			// This is ind.Block.Preds, not b.Preds. That's a restriction on the loop header,
   149  			// not the comparison block.
   150  			if len(ind.Block.Preds) != 2 {
   151  				continue
   152  			}
   153  
   154  			// Expect the increment to be a nonzero constant.
   155  			if !inc.isGenericIntConst() {
   156  				continue
   157  			}
   158  			step := inc.AuxInt
   159  			if step == 0 {
   160  				continue
   161  			}
   162  
   163  			// startBody is the edge that eventually returns to the loop header.
   164  			var startBody Edge
   165  			switch {
   166  			case sdom.IsAncestorEq(b.Succs[0].b, loopReturn.b):
   167  				startBody = b.Succs[0]
   168  			case sdom.IsAncestorEq(b.Succs[1].b, loopReturn.b):
   169  				// if x { goto exit } else { goto entry } is identical to if !x { goto entry } else { goto exit }
   170  				startBody = b.Succs[1]
   171  				less = !less
   172  				inclusive = !inclusive
   173  			default:
   174  				continue
   175  			}
   176  
   177  			// Increment sign must match comparison direction.
   178  			// When incrementing, the termination comparison must be ind </<= limit.
   179  			// When decrementing, the termination comparison must be ind >/>= limit.
   180  			// See issue 26116.
   181  			if step > 0 && !less {
   182  				continue
   183  			}
   184  			if step < 0 && less {
   185  				continue
   186  			}
   187  
   188  			// Up to now we extracted the induction variable (ind),
   189  			// the increment delta (inc), the temporary sum (nxt),
   190  			// the initial value (init) and the limiting value (limit).
   191  			//
   192  			// We also know that ind has the form (Phi init nxt) where
   193  			// nxt is (Add inc nxt) which means: 1) inc dominates nxt
   194  			// and 2) there is a loop starting at inc and containing nxt.
   195  			//
   196  			// We need to prove that the induction variable is incremented
   197  			// only when it's smaller than the limiting value.
   198  			// Two conditions must happen listed below to accept ind
   199  			// as an induction variable.
   200  
   201  			// First condition: the entry block has a single predecessor.
   202  			// The entry now means the in-loop edge where the induction variable
   203  			// comparison succeeded. Its predecessor is not necessarily the header
   204  			// block. This implies that b.Succs[0] is reached iff ind < limit.
   205  			if len(startBody.b.Preds) != 1 {
   206  				// the other successor must exit the loop.
   207  				continue
   208  			}
   209  
   210  			// Second condition: startBody.b dominates nxt so that
   211  			// nxt is computed when inc < limit.
   212  			if !sdom.IsAncestorEq(startBody.b, nxt.Block) {
   213  				// inc+ind can only be reached through the branch that confirmed the
   214  				// induction variable is in bounds.
   215  				continue
   216  			}
   217  
   218  			// Check for overflow/underflow. We need to make sure that inc never causes
   219  			// the induction variable to wrap around.
   220  			// We use a function wrapper here for easy return true / return false / keep going logic.
   221  			// This function returns true if the increment will never overflow/underflow.
   222  			ok := func() bool {
   223  				if step > 0 {
   224  					if limit.isGenericIntConst() {
   225  						// Figure out the actual largest value.
   226  						v := limit.AuxInt
   227  						if !inclusive {
   228  							if v == minSignedValue(limit.Type) {
   229  								return false // < minint is never satisfiable.
   230  							}
   231  							v--
   232  						}
   233  						if init.isGenericIntConst() {
   234  							// Use stride to compute a better lower limit.
   235  							if init.AuxInt > v {
   236  								return false
   237  							}
   238  							v = addU(init.AuxInt, diff(v, init.AuxInt)/uint64(step)*uint64(step))
   239  						}
   240  						if addWillOverflow(v, step) {
   241  							return false
   242  						}
   243  						if inclusive && v != limit.AuxInt || !inclusive && v+1 != limit.AuxInt {
   244  							// We know a better limit than the programmer did. Use our limit instead.
   245  							limit = f.constVal(limit.Op, limit.Type, v, true)
   246  							inclusive = true
   247  						}
   248  						return true
   249  					}
   250  					if step == 1 && !inclusive {
   251  						// Can't overflow because maxint is never a possible value.
   252  						return true
   253  					}
   254  					// If the limit is not a constant, check to see if it is a
   255  					// negative offset from a known non-negative value.
   256  					knn, k := findKNN(limit)
   257  					if knn == nil || k < 0 {
   258  						return false
   259  					}
   260  					// limit == (something nonnegative) - k. That subtraction can't underflow, so
   261  					// we can trust it.
   262  					if inclusive {
   263  						// ind <= knn - k cannot overflow if step is at most k
   264  						return step <= k
   265  					}
   266  					// ind < knn - k cannot overflow if step is at most k+1
   267  					return step <= k+1 && k != maxSignedValue(limit.Type)
   268  
   269  					// TODO: other unrolling idioms
   270  					// for i := 0; i < KNN - KNN % k ; i += k
   271  					// for i := 0; i < KNN&^(k-1) ; i += k // k a power of 2
   272  					// for i := 0; i < KNN&(-k) ; i += k // k a power of 2
   273  				} else { // step < 0
   274  					if limit.Op == OpConst64 {
   275  						// Figure out the actual smallest value.
   276  						v := limit.AuxInt
   277  						if !inclusive {
   278  							if v == maxSignedValue(limit.Type) {
   279  								return false // > maxint is never satisfiable.
   280  							}
   281  							v++
   282  						}
   283  						if init.isGenericIntConst() {
   284  							// Use stride to compute a better lower limit.
   285  							if init.AuxInt < v {
   286  								return false
   287  							}
   288  							v = subU(init.AuxInt, diff(init.AuxInt, v)/uint64(-step)*uint64(-step))
   289  						}
   290  						if subWillUnderflow(v, -step) {
   291  							return false
   292  						}
   293  						if inclusive && v != limit.AuxInt || !inclusive && v-1 != limit.AuxInt {
   294  							// We know a better limit than the programmer did. Use our limit instead.
   295  							limit = f.constVal(limit.Op, limit.Type, v, true)
   296  							inclusive = true
   297  						}
   298  						return true
   299  					}
   300  					if step == -1 && !inclusive {
   301  						// Can't underflow because minint is never a possible value.
   302  						return true
   303  					}
   304  				}
   305  				return false
   306  			}
   307  
   308  			if ok() {
   309  				flags := indVarFlags(0)
   310  				var min, max *Value
   311  				if step > 0 {
   312  					min = init
   313  					max = limit
   314  					if inclusive {
   315  						flags |= indVarMaxInc
   316  					}
   317  				} else {
   318  					min = limit
   319  					max = init
   320  					flags |= indVarMaxInc
   321  					if !inclusive {
   322  						flags |= indVarMinExc
   323  					}
   324  					step = -step
   325  				}
   326  				if f.pass.debug >= 1 {
   327  					printIndVar(b, ind, min, max, step, flags)
   328  				}
   329  
   330  				iv = append(iv, indVar{
   331  					ind: ind,
   332  					nxt: nxt,
   333  					min: min,
   334  					max: max,
   335  					// This is startBody.b, where startBody is the edge from the comparison for the
   336  					// induction variable, not necessarily the in-loop edge from the loop header.
   337  					// Induction variable bounds are not valid in the loop before this edge.
   338  					entry: startBody.b,
   339  					step:  step,
   340  					flags: flags,
   341  				})
   342  				b.Logf("found induction variable %v (inc = %v, min = %v, max = %v)\n", ind, inc, min, max)
   343  			}
   344  		}
   345  	}
   346  
   347  	return iv
   348  }
   349  
   350  // addWillOverflow reports whether x+y would result in a value more than maxint.
   351  func addWillOverflow(x, y int64) bool {
   352  	return x+y < x
   353  }
   354  
   355  // subWillUnderflow reports whether x-y would result in a value less than minint.
   356  func subWillUnderflow(x, y int64) bool {
   357  	return x-y > x
   358  }
   359  
   360  // diff returns x-y as a uint64. Requires x>=y.
   361  func diff(x, y int64) uint64 {
   362  	if x < y {
   363  		base.Fatalf("diff %d - %d underflowed", x, y)
   364  	}
   365  	return uint64(x - y)
   366  }
   367  
   368  // addU returns x+y. Requires that x+y does not overflow an int64.
   369  func addU(x int64, y uint64) int64 {
   370  	if y >= 1<<63 {
   371  		if x >= 0 {
   372  			base.Fatalf("addU overflowed %d + %d", x, y)
   373  		}
   374  		x += 1<<63 - 1
   375  		x += 1
   376  		y -= 1 << 63
   377  	}
   378  	if addWillOverflow(x, int64(y)) {
   379  		base.Fatalf("addU overflowed %d + %d", x, y)
   380  	}
   381  	return x + int64(y)
   382  }
   383  
   384  // subU returns x-y. Requires that x-y does not underflow an int64.
   385  func subU(x int64, y uint64) int64 {
   386  	if y >= 1<<63 {
   387  		if x < 0 {
   388  			base.Fatalf("subU underflowed %d - %d", x, y)
   389  		}
   390  		x -= 1<<63 - 1
   391  		x -= 1
   392  		y -= 1 << 63
   393  	}
   394  	if subWillUnderflow(x, int64(y)) {
   395  		base.Fatalf("subU underflowed %d - %d", x, y)
   396  	}
   397  	return x - int64(y)
   398  }
   399  
   400  // if v is known to be x - c, where x is known to be nonnegative and c is a
   401  // constant, return x, c. Otherwise return nil, 0.
   402  func findKNN(v *Value) (*Value, int64) {
   403  	var x, y *Value
   404  	x = v
   405  	switch v.Op {
   406  	case OpSub64, OpSub32, OpSub16, OpSub8:
   407  		x = v.Args[0]
   408  		y = v.Args[1]
   409  
   410  	case OpAdd64, OpAdd32, OpAdd16, OpAdd8:
   411  		x = v.Args[0]
   412  		y = v.Args[1]
   413  		if x.isGenericIntConst() {
   414  			x, y = y, x
   415  		}
   416  	}
   417  	switch x.Op {
   418  	case OpSliceLen, OpStringLen, OpSliceCap:
   419  	default:
   420  		return nil, 0
   421  	}
   422  	if y == nil {
   423  		return x, 0
   424  	}
   425  	if !y.isGenericIntConst() {
   426  		return nil, 0
   427  	}
   428  	if v.Op == OpAdd64 || v.Op == OpAdd32 || v.Op == OpAdd16 || v.Op == OpAdd8 {
   429  		return x, -y.AuxInt
   430  	}
   431  	return x, y.AuxInt
   432  }
   433  
   434  func printIndVar(b *Block, i, min, max *Value, inc int64, flags indVarFlags) {
   435  	mb1, mb2 := "[", "]"
   436  	if flags&indVarMinExc != 0 {
   437  		mb1 = "("
   438  	}
   439  	if flags&indVarMaxInc == 0 {
   440  		mb2 = ")"
   441  	}
   442  
   443  	mlim1, mlim2 := fmt.Sprint(min.AuxInt), fmt.Sprint(max.AuxInt)
   444  	if !min.isGenericIntConst() {
   445  		if b.Func.pass.debug >= 2 {
   446  			mlim1 = fmt.Sprint(min)
   447  		} else {
   448  			mlim1 = "?"
   449  		}
   450  	}
   451  	if !max.isGenericIntConst() {
   452  		if b.Func.pass.debug >= 2 {
   453  			mlim2 = fmt.Sprint(max)
   454  		} else {
   455  			mlim2 = "?"
   456  		}
   457  	}
   458  	extra := ""
   459  	if b.Func.pass.debug >= 2 {
   460  		extra = fmt.Sprintf(" (%s)", i)
   461  	}
   462  	b.Func.Warnl(b.Pos, "Induction variable: limits %v%v,%v%v, increment %d%s", mb1, mlim1, mlim2, mb2, inc, extra)
   463  }
   464  
   465  func minSignedValue(t *types.Type) int64 {
   466  	return -1 << (t.Size()*8 - 1)
   467  }
   468  
   469  func maxSignedValue(t *types.Type) int64 {
   470  	return 1<<((t.Size()*8)-1) - 1
   471  }
   472  

View as plain text