mirror of https://github.com/golang/go.git
cmd/compile: enhance induction variable detection for unrolled loops
Would suggest extending capabilities (32-bit, unsigned, etc) in separate CLs because prove bugs are so mystifying. This implements the suggestion in this comment https://go-review.googlesource.com/c/go/+/104041/10/src/cmd/compile/internal/ssa/loopbce.go#164 for inferring properly bounded iteration for loops of the form for i := K0; i < KNN-(K-1); i += K for i := K0; i <= KNN-K; i += K Where KNN is "known non negative" (i.e., len or cap) and K is also not negative. Because i <= KNN-K, i+K <= KNN and no overflow occurs. Also handles decreasing case (K1 > 0) for i := KNN; i >= K0; i -= K1 which works when MININT+K1 < K0 (i.e. MININT < K0-K1, no overflow) Signed only, also only 64 bit for now. Change-Id: I5da6015aba2f781ec76c4ad59c9c48d952325fdc Reviewed-on: https://go-review.googlesource.com/c/go/+/136375 Run-TryBot: David Chase <drchase@google.com> TryBot-Result: Gobot Gobot <gobot@golang.org> Reviewed-by: Alexandru Moșoi <alexandru@mosoi.ro>
This commit is contained in:
parent
c90f6dd496
commit
d8f60eea64
|
|
@ -4,7 +4,10 @@
|
||||||
|
|
||||||
package ssa
|
package ssa
|
||||||
|
|
||||||
import "fmt"
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math"
|
||||||
|
)
|
||||||
|
|
||||||
type indVarFlags uint8
|
type indVarFlags uint8
|
||||||
|
|
||||||
|
|
@ -59,6 +62,7 @@ func findIndVar(f *Func) []indVar {
|
||||||
|
|
||||||
// Check thet the control if it either ind </<= max or max >/>= ind.
|
// Check thet the control if it either ind </<= max or max >/>= ind.
|
||||||
// TODO: Handle 32-bit comparisons.
|
// TODO: Handle 32-bit comparisons.
|
||||||
|
// TODO: Handle unsigned comparisons?
|
||||||
switch b.Control.Op {
|
switch b.Control.Op {
|
||||||
case OpLeq64:
|
case OpLeq64:
|
||||||
flags |= indVarMaxInc
|
flags |= indVarMaxInc
|
||||||
|
|
@ -165,15 +169,101 @@ func findIndVar(f *Func) []indVar {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// We can only guarantee that the loops runs within limits of induction variable
|
// We can only guarantee that the loop runs within limits of induction variable
|
||||||
// if the increment is ±1 or when the limits are constants.
|
// if (one of)
|
||||||
if step != 1 {
|
// (1) the increment is ±1
|
||||||
|
// (2) the limits are constants
|
||||||
|
// (3) loop is of the form k0 upto Known_not_negative-k inclusive, step <= k
|
||||||
|
// (4) loop is of the form k0 upto Known_not_negative-k exclusive, step <= k+1
|
||||||
|
// (5) loop is of the form Known_not_negative downto k0, minint+step < k0
|
||||||
|
if step > 1 {
|
||||||
ok := false
|
ok := false
|
||||||
if min.Op == OpConst64 && max.Op == OpConst64 {
|
if min.Op == OpConst64 && max.Op == OpConst64 {
|
||||||
if max.AuxInt > min.AuxInt && max.AuxInt%step == min.AuxInt%step { // handle overflow
|
if max.AuxInt > min.AuxInt && max.AuxInt%step == min.AuxInt%step { // handle overflow
|
||||||
ok = true
|
ok = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Handle induction variables of these forms.
|
||||||
|
// KNN is known-not-negative.
|
||||||
|
// SIGNED ARITHMETIC ONLY. (see switch on b.Control.Op above)
|
||||||
|
// Possibilitis for KNN are len and cap; perhaps we can infer others.
|
||||||
|
// for i := 0; i <= KNN-k ; i += k
|
||||||
|
// for i := 0; i < KNN-(k-1); i += k
|
||||||
|
// Also handle decreasing.
|
||||||
|
|
||||||
|
// "Proof" copied from https://go-review.googlesource.com/c/go/+/104041/10/src/cmd/compile/internal/ssa/loopbce.go#164
|
||||||
|
//
|
||||||
|
// In the case of
|
||||||
|
// // PC is Positive Constant
|
||||||
|
// L := len(A)-PC
|
||||||
|
// for i := 0; i < L; i = i+PC
|
||||||
|
//
|
||||||
|
// we know:
|
||||||
|
//
|
||||||
|
// 0 + PC does not over/underflow.
|
||||||
|
// len(A)-PC does not over/underflow
|
||||||
|
// maximum value for L is MaxInt-PC
|
||||||
|
// i < L <= MaxInt-PC means i + PC < MaxInt hence no overflow.
|
||||||
|
|
||||||
|
// To match in SSA:
|
||||||
|
// if (a) min.Op == OpConst64(k0)
|
||||||
|
// and (b) k0 >= MININT + step
|
||||||
|
// and (c) max.Op == OpSubtract(Op{StringLen,SliceLen,SliceCap}, k)
|
||||||
|
// or (c) max.Op == OpAdd(Op{StringLen,SliceLen,SliceCap}, -k)
|
||||||
|
// or (c) max.Op == Op{StringLen,SliceLen,SliceCap}
|
||||||
|
// and (d) if upto loop, require indVarMaxInc && step <= k or !indVarMaxInc && step-1 <= k
|
||||||
|
|
||||||
|
if min.Op == OpConst64 && min.AuxInt >= step+math.MinInt64 {
|
||||||
|
knn := max
|
||||||
|
k := int64(0)
|
||||||
|
var kArg *Value
|
||||||
|
|
||||||
|
switch max.Op {
|
||||||
|
case OpSub64:
|
||||||
|
knn = max.Args[0]
|
||||||
|
kArg = max.Args[1]
|
||||||
|
|
||||||
|
case OpAdd64:
|
||||||
|
knn = max.Args[0]
|
||||||
|
kArg = max.Args[1]
|
||||||
|
if knn.Op == OpConst64 {
|
||||||
|
knn, kArg = kArg, knn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch knn.Op {
|
||||||
|
case OpSliceLen, OpStringLen, OpSliceCap:
|
||||||
|
default:
|
||||||
|
knn = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if kArg != nil && kArg.Op == OpConst64 {
|
||||||
|
k = kArg.AuxInt
|
||||||
|
if max.Op == OpAdd64 {
|
||||||
|
k = -k
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if k >= 0 && knn != nil {
|
||||||
|
if inc.AuxInt > 0 { // increasing iteration
|
||||||
|
// The concern for the relation between step and k is to ensure that iv never exceeds knn
|
||||||
|
// i.e., iv < knn-(K-1) ==> iv + K <= knn; iv <= knn-K ==> iv +K < knn
|
||||||
|
if step <= k || flags&indVarMaxInc == 0 && step-1 == k {
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
} else { // decreasing iteration
|
||||||
|
// Will be decrementing from max towards min; max is knn-k; will only attempt decrement if
|
||||||
|
// knn-k >[=] min; underflow is only a concern if min-step is not smaller than min.
|
||||||
|
// This all assumes signed integer arithmetic
|
||||||
|
// This is already assured by the test above: min.AuxInt >= step+math.MinInt64
|
||||||
|
ok = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: other unrolling idioms
|
||||||
|
// for i := 0; i < KNN - KNN % k ; i += k
|
||||||
|
// for i := 0; i < KNN&^(k-1) ; i += k // k a power of 2
|
||||||
|
// for i := 0; i < KNN&(-k) ; i += k // k a power of 2
|
||||||
|
|
||||||
if !ok {
|
if !ok {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
|
||||||
117
test/prove.go
117
test/prove.go
|
|
@ -726,6 +726,123 @@ func signHint2(b []byte, n int) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Induction variable in unrolled loop.
|
||||||
|
func unrollUpExcl(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = 0; i < len(a)-1; i += 2 { // ERROR "Induction variable: limits \[0,\?\), increment 2$"
|
||||||
|
x += a[i] // ERROR "Proved IsInBounds$"
|
||||||
|
x += a[i+1]
|
||||||
|
}
|
||||||
|
if i == len(a)-1 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Induction variable in unrolled loop.
|
||||||
|
func unrollUpIncl(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = 0; i <= len(a)-2; i += 2 { // ERROR "Induction variable: limits \[0,\?\], increment 2$"
|
||||||
|
x += a[i]
|
||||||
|
x += a[i+1]
|
||||||
|
}
|
||||||
|
if i == len(a)-1 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Induction variable in unrolled loop.
|
||||||
|
func unrollDownExcl0(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = len(a) - 1; i > 0; i -= 2 { // ERROR "Induction variable: limits \(0,\?\], increment 2$"
|
||||||
|
x += a[i] // ERROR "Proved IsInBounds$"
|
||||||
|
x += a[i-1] // ERROR "Proved IsInBounds$"
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Induction variable in unrolled loop.
|
||||||
|
func unrollDownExcl1(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = len(a) - 1; i >= 1; i -= 2 { // ERROR "Induction variable: limits \[1,\?\], increment 2$"
|
||||||
|
x += a[i] // ERROR "Proved IsInBounds$"
|
||||||
|
x += a[i-1] // ERROR "Proved IsInBounds$"
|
||||||
|
}
|
||||||
|
if i == 0 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Induction variable in unrolled loop.
|
||||||
|
func unrollDownInclStep(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = len(a); i >= 2; i -= 2 { // ERROR "Induction variable: limits \[2,\?\], increment 2$"
|
||||||
|
x += a[i-1] // ERROR "Proved IsInBounds$"
|
||||||
|
x += a[i-2]
|
||||||
|
}
|
||||||
|
if i == 1 {
|
||||||
|
x += a[i-1]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an induction variable (step too large)
|
||||||
|
func unrollExclStepTooLarge(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = 0; i < len(a)-1; i += 3 {
|
||||||
|
x += a[i]
|
||||||
|
x += a[i+1]
|
||||||
|
}
|
||||||
|
if i == len(a)-1 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an induction variable (step too large)
|
||||||
|
func unrollInclStepTooLarge(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = 0; i <= len(a)-2; i += 3 {
|
||||||
|
x += a[i]
|
||||||
|
x += a[i+1]
|
||||||
|
}
|
||||||
|
if i == len(a)-1 {
|
||||||
|
x += a[i]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an induction variable (min too small, iterating down)
|
||||||
|
func unrollDecMin(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = len(a); i >= math.MinInt64; i -= 2 {
|
||||||
|
x += a[i-1]
|
||||||
|
x += a[i-2]
|
||||||
|
}
|
||||||
|
if i == 1 { // ERROR "Disproved Eq64$"
|
||||||
|
x += a[i-1]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
|
// Not an induction variable (min too small, iterating up -- perhaps could allow, but why bother?)
|
||||||
|
func unrollIncMin(a []int) int {
|
||||||
|
var i, x int
|
||||||
|
for i = len(a); i >= math.MinInt64; i += 2 {
|
||||||
|
x += a[i-1]
|
||||||
|
x += a[i-2]
|
||||||
|
}
|
||||||
|
if i == 1 { // ERROR "Disproved Eq64$"
|
||||||
|
x += a[i-1]
|
||||||
|
}
|
||||||
|
return x
|
||||||
|
}
|
||||||
|
|
||||||
//go:noinline
|
//go:noinline
|
||||||
func useInt(a int) {
|
func useInt(a int) {
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue