github.com/gagliardetto/golang-go@v0.0.0-20201020153340-53909ea70814/cmd/compile/internal/ssa/loopbce.go (about)

     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  	"fmt"
     9  	"math"
    10  )
    11  
    12  type indVarFlags uint8
    13  
    14  const (
    15  	indVarMinExc indVarFlags = 1 << iota // minimum value is exclusive (default: inclusive)
    16  	indVarMaxInc                         // maximum value is inclusive (default: exclusive)
    17  )
    18  
    19  type indVar struct {
    20  	ind   *Value // induction variable
    21  	min   *Value // minimum value, inclusive/exclusive depends on flags
    22  	max   *Value // maximum value, inclusive/exclusive depends on flags
    23  	entry *Block // entry block in the loop.
    24  	flags indVarFlags
    25  	// Invariant: for all blocks strictly dominated by entry:
    26  	//	min <= ind <  max    [if flags == 0]
    27  	//	min <  ind <  max    [if flags == indVarMinExc]
    28  	//	min <= ind <= max    [if flags == indVarMaxInc]
    29  	//	min <  ind <= max    [if flags == indVarMinExc|indVarMaxInc]
    30  }
    31  
    32  // parseIndVar checks whether the SSA value passed as argument is a valid induction
    33  // variable, and, if so, extracts:
    34  //   * the minimum bound
    35  //   * the increment value
    36  //   * the "next" value (SSA value that is Phi'd into the induction variable every loop)
    37  // Currently, we detect induction variables that match (Phi min nxt),
    38  // with nxt being (Add inc ind).
    39  // If it can't parse the induction variable correctly, it returns (nil, nil, nil).
    40  func parseIndVar(ind *Value) (min, inc, nxt *Value) {
    41  	if ind.Op != OpPhi {
    42  		return
    43  	}
    44  
    45  	if n := ind.Args[0]; n.Op == OpAdd64 && (n.Args[0] == ind || n.Args[1] == ind) {
    46  		min, nxt = ind.Args[1], n
    47  	} else if n := ind.Args[1]; n.Op == OpAdd64 && (n.Args[0] == ind || n.Args[1] == ind) {
    48  		min, nxt = ind.Args[0], n
    49  	} else {
    50  		// Not a recognized induction variable.
    51  		return
    52  	}
    53  
    54  	if nxt.Args[0] == ind { // nxt = ind + inc
    55  		inc = nxt.Args[1]
    56  	} else if nxt.Args[1] == ind { // nxt = inc + ind
    57  		inc = nxt.Args[0]
    58  	} else {
    59  		panic("unreachable") // one of the cases must be true from the above.
    60  	}
    61  
    62  	return
    63  }
    64  
    65  // findIndVar finds induction variables in a function.
    66  //
    67  // Look for variables and blocks that satisfy the following
    68  //
    69  // loop:
    70  //   ind = (Phi min nxt),
    71  //   if ind < max
    72  //     then goto enter_loop
    73  //     else goto exit_loop
    74  //
    75  //   enter_loop:
    76  //	do something
    77  //      nxt = inc + ind
    78  //	goto loop
    79  //
    80  // exit_loop:
    81  //
    82  //
    83  // TODO: handle 32 bit operations
    84  func findIndVar(f *Func) []indVar {
    85  	var iv []indVar
    86  	sdom := f.Sdom()
    87  
    88  	for _, b := range f.Blocks {
    89  		if b.Kind != BlockIf || len(b.Preds) != 2 {
    90  			continue
    91  		}
    92  
    93  		var flags indVarFlags
    94  		var ind, max *Value // induction, and maximum
    95  
    96  		// Check thet the control if it either ind </<= max or max >/>= ind.
    97  		// TODO: Handle 32-bit comparisons.
    98  		// TODO: Handle unsigned comparisons?
    99  		c := b.Controls[0]
   100  		switch c.Op {
   101  		case OpLeq64:
   102  			flags |= indVarMaxInc
   103  			fallthrough
   104  		case OpLess64:
   105  			ind, max = c.Args[0], c.Args[1]
   106  		case OpGeq64:
   107  			flags |= indVarMaxInc
   108  			fallthrough
   109  		case OpGreater64:
   110  			ind, max = c.Args[1], c.Args[0]
   111  		default:
   112  			continue
   113  		}
   114  
   115  		// See if this is really an induction variable
   116  		less := true
   117  		min, inc, nxt := parseIndVar(ind)
   118  		if min == nil {
   119  			// We failed to parse the induction variable. Before punting, we want to check
   120  			// whether the control op was written with arguments in non-idiomatic order,
   121  			// so that we believe being "max" (the upper bound) is actually the induction
   122  			// variable itself. This would happen for code like:
   123  			//     for i := 0; len(n) > i; i++
   124  			min, inc, nxt = parseIndVar(max)
   125  			if min == nil {
   126  				// No recognied induction variable on either operand
   127  				continue
   128  			}
   129  
   130  			// Ok, the arguments were reversed. Swap them, and remember that we're
   131  			// looking at a ind >/>= loop (so the induction must be decrementing).
   132  			ind, max = max, ind
   133  			less = false
   134  		}
   135  
   136  		// Expect the increment to be a nonzero constant.
   137  		if inc.Op != OpConst64 {
   138  			continue
   139  		}
   140  		step := inc.AuxInt
   141  		if step == 0 {
   142  			continue
   143  		}
   144  
   145  		// Increment sign must match comparison direction.
   146  		// When incrementing, the termination comparison must be ind </<= max.
   147  		// When decrementing, the termination comparison must be ind >/>= max.
   148  		// See issue 26116.
   149  		if step > 0 && !less {
   150  			continue
   151  		}
   152  		if step < 0 && less {
   153  			continue
   154  		}
   155  
   156  		// If the increment is negative, swap min/max and their flags
   157  		if step < 0 {
   158  			min, max = max, min
   159  			oldf := flags
   160  			flags = indVarMaxInc
   161  			if oldf&indVarMaxInc == 0 {
   162  				flags |= indVarMinExc
   163  			}
   164  			step = -step
   165  		}
   166  
   167  		// Up to now we extracted the induction variable (ind),
   168  		// the increment delta (inc), the temporary sum (nxt),
   169  		// the mininum value (min) and the maximum value (max).
   170  		//
   171  		// We also know that ind has the form (Phi min nxt) where
   172  		// nxt is (Add inc nxt) which means: 1) inc dominates nxt
   173  		// and 2) there is a loop starting at inc and containing nxt.
   174  		//
   175  		// We need to prove that the induction variable is incremented
   176  		// only when it's smaller than the maximum value.
   177  		// Two conditions must happen listed below to accept ind
   178  		// as an induction variable.
   179  
   180  		// First condition: loop entry has a single predecessor, which
   181  		// is the header block.  This implies that b.Succs[0] is
   182  		// reached iff ind < max.
   183  		if len(b.Succs[0].b.Preds) != 1 {
   184  			// b.Succs[1] must exit the loop.
   185  			continue
   186  		}
   187  
   188  		// Second condition: b.Succs[0] dominates nxt so that
   189  		// nxt is computed when inc < max, meaning nxt <= max.
   190  		if !sdom.IsAncestorEq(b.Succs[0].b, nxt.Block) {
   191  			// inc+ind can only be reached through the branch that enters the loop.
   192  			continue
   193  		}
   194  
   195  		// We can only guarantee that the loop runs within limits of induction variable
   196  		// if (one of)
   197  		// (1) the increment is ±1
   198  		// (2) the limits are constants
   199  		// (3) loop is of the form k0 upto Known_not_negative-k inclusive, step <= k
   200  		// (4) loop is of the form k0 upto Known_not_negative-k exclusive, step <= k+1
   201  		// (5) loop is of the form Known_not_negative downto k0, minint+step < k0
   202  		if step > 1 {
   203  			ok := false
   204  			if min.Op == OpConst64 && max.Op == OpConst64 {
   205  				if max.AuxInt > min.AuxInt && max.AuxInt%step == min.AuxInt%step { // handle overflow
   206  					ok = true
   207  				}
   208  			}
   209  			// Handle induction variables of these forms.
   210  			// KNN is known-not-negative.
   211  			// SIGNED ARITHMETIC ONLY. (see switch on c above)
   212  			// Possibilities for KNN are len and cap; perhaps we can infer others.
   213  			// for i := 0; i <= KNN-k    ; i += k
   214  			// for i := 0; i <  KNN-(k-1); i += k
   215  			// Also handle decreasing.
   216  
   217  			// "Proof" copied from https://go-review.googlesource.com/c/go/+/104041/10/src/cmd/compile/internal/ssa/loopbce.go#164
   218  			//
   219  			//	In the case of
   220  			//	// PC is Positive Constant
   221  			//	L := len(A)-PC
   222  			//	for i := 0; i < L; i = i+PC
   223  			//
   224  			//	we know:
   225  			//
   226  			//	0 + PC does not over/underflow.
   227  			//	len(A)-PC does not over/underflow
   228  			//	maximum value for L is MaxInt-PC
   229  			//	i < L <= MaxInt-PC means i + PC < MaxInt hence no overflow.
   230  
   231  			// To match in SSA:
   232  			// if  (a) min.Op == OpConst64(k0)
   233  			// and (b) k0 >= MININT + step
   234  			// and (c) max.Op == OpSubtract(Op{StringLen,SliceLen,SliceCap}, k)
   235  			// or  (c) max.Op == OpAdd(Op{StringLen,SliceLen,SliceCap}, -k)
   236  			// or  (c) max.Op == Op{StringLen,SliceLen,SliceCap}
   237  			// and (d) if upto loop, require indVarMaxInc && step <= k or !indVarMaxInc && step-1 <= k
   238  
   239  			if min.Op == OpConst64 && min.AuxInt >= step+math.MinInt64 {
   240  				knn := max
   241  				k := int64(0)
   242  				var kArg *Value
   243  
   244  				switch max.Op {
   245  				case OpSub64:
   246  					knn = max.Args[0]
   247  					kArg = max.Args[1]
   248  
   249  				case OpAdd64:
   250  					knn = max.Args[0]
   251  					kArg = max.Args[1]
   252  					if knn.Op == OpConst64 {
   253  						knn, kArg = kArg, knn
   254  					}
   255  				}
   256  				switch knn.Op {
   257  				case OpSliceLen, OpStringLen, OpSliceCap:
   258  				default:
   259  					knn = nil
   260  				}
   261  
   262  				if kArg != nil && kArg.Op == OpConst64 {
   263  					k = kArg.AuxInt
   264  					if max.Op == OpAdd64 {
   265  						k = -k
   266  					}
   267  				}
   268  				if k >= 0 && knn != nil {
   269  					if inc.AuxInt > 0 { // increasing iteration
   270  						// The concern for the relation between step and k is to ensure that iv never exceeds knn
   271  						// i.e., iv < knn-(K-1) ==> iv + K <= knn; iv <= knn-K ==> iv +K < knn
   272  						if step <= k || flags&indVarMaxInc == 0 && step-1 == k {
   273  							ok = true
   274  						}
   275  					} else { // decreasing iteration
   276  						// Will be decrementing from max towards min; max is knn-k; will only attempt decrement if
   277  						// knn-k >[=] min; underflow is only a concern if min-step is not smaller than min.
   278  						// This all assumes signed integer arithmetic
   279  						// This is already assured by the test above: min.AuxInt >= step+math.MinInt64
   280  						ok = true
   281  					}
   282  				}
   283  			}
   284  
   285  			// TODO: other unrolling idioms
   286  			// for i := 0; i < KNN - KNN % k ; i += k
   287  			// for i := 0; i < KNN&^(k-1) ; i += k // k a power of 2
   288  			// for i := 0; i < KNN&(-k) ; i += k // k a power of 2
   289  
   290  			if !ok {
   291  				continue
   292  			}
   293  		}
   294  
   295  		if f.pass.debug >= 1 {
   296  			printIndVar(b, ind, min, max, step, flags)
   297  		}
   298  
   299  		iv = append(iv, indVar{
   300  			ind:   ind,
   301  			min:   min,
   302  			max:   max,
   303  			entry: b.Succs[0].b,
   304  			flags: flags,
   305  		})
   306  		b.Logf("found induction variable %v (inc = %v, min = %v, max = %v)\n", ind, inc, min, max)
   307  	}
   308  
   309  	return iv
   310  }
   311  
   312  func dropAdd64(v *Value) (*Value, int64) {
   313  	if v.Op == OpAdd64 && v.Args[0].Op == OpConst64 {
   314  		return v.Args[1], v.Args[0].AuxInt
   315  	}
   316  	if v.Op == OpAdd64 && v.Args[1].Op == OpConst64 {
   317  		return v.Args[0], v.Args[1].AuxInt
   318  	}
   319  	return v, 0
   320  }
   321  
   322  func printIndVar(b *Block, i, min, max *Value, inc int64, flags indVarFlags) {
   323  	mb1, mb2 := "[", "]"
   324  	if flags&indVarMinExc != 0 {
   325  		mb1 = "("
   326  	}
   327  	if flags&indVarMaxInc == 0 {
   328  		mb2 = ")"
   329  	}
   330  
   331  	mlim1, mlim2 := fmt.Sprint(min.AuxInt), fmt.Sprint(max.AuxInt)
   332  	if !min.isGenericIntConst() {
   333  		if b.Func.pass.debug >= 2 {
   334  			mlim1 = fmt.Sprint(min)
   335  		} else {
   336  			mlim1 = "?"
   337  		}
   338  	}
   339  	if !max.isGenericIntConst() {
   340  		if b.Func.pass.debug >= 2 {
   341  			mlim2 = fmt.Sprint(max)
   342  		} else {
   343  			mlim2 = "?"
   344  		}
   345  	}
   346  	extra := ""
   347  	if b.Func.pass.debug >= 2 {
   348  		extra = fmt.Sprintf(" (%s)", i)
   349  	}
   350  	b.Func.Warnl(b.Pos, "Induction variable: limits %v%v,%v%v, increment %d%s", mb1, mlim1, mlim2, mb2, inc, extra)
   351  }