1
0
mirror of https://github.com/golang/go synced 2024-11-26 03:07:57 -07:00

cmd/compile: propagate constant ranges through multiplies and shifts

Fixes #40704
Fixes #66826

Change-Id: Ia9c356e29b2ed6f2e3bc6e5eb9304cd4dccb4263
Reviewed-on: https://go-review.googlesource.com/c/go/+/599256
Reviewed-by: Michael Knyszek <mknyszek@google.com>
LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: David Chase <drchase@google.com>
This commit is contained in:
khr@golang.org 2024-06-27 15:53:24 -07:00 committed by Keith Randall
parent 3b96eebcbd
commit a4a130f6d0
2 changed files with 81 additions and 1 deletions

View File

@ -8,6 +8,7 @@ import (
"cmd/internal/src" "cmd/internal/src"
"fmt" "fmt"
"math" "math"
"math/bits"
) )
type branch int type branch int
@ -311,6 +312,40 @@ func (l limit) sub(l2 limit, b uint) limit {
return r return r
} }
// same as add but for multiplication.
func (l limit) mul(l2 limit, b uint) limit {
r := noLimit
umaxhi, umaxlo := bits.Mul64(l.umax, l2.umax)
if umaxhi == 0 && fitsInBitsU(umaxlo, b) {
r.umax = umaxlo
r.umin = l.umin * l2.umin
// Note: if the code containing this multiply is
// unreachable, then we may have umin>umax, and this
// multiply may overflow. But that's ok for
// unreachable code. If this code is reachable, we
// know umin<=umax, so this multiply will not overflow
// because the max multiply didn't.
}
// Signed is harder, so don't bother. The only useful
// case is when we know both multiplicands are nonnegative,
// but that case is handled above because we would have then
// previously propagated signed info to the unsigned domain,
// and will propagate it back after the multiply.
return r
}
// Similar to add, but compute 1 << l if it fits without overflow in b bits.
func (l limit) exp2(b uint) limit {
r := noLimit
if l.umax < uint64(b) {
r.umin = 1 << l.umin
r.umax = 1 << l.umax
// Same as above in mul, signed<->unsigned propagation
// will handle the signed case for us.
}
return r
}
var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64} var noLimit = limit{math.MinInt64, math.MaxInt64, 0, math.MaxUint64}
// a limitFact is a limit known for a particular value. // a limitFact is a limit known for a particular value.
@ -1548,6 +1583,39 @@ func (ft *factsTable) flowLimit(v *Value) bool {
a := ft.limits[v.Args[0].ID] a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID] b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.sub(b, 8)) return ft.newLimit(v, a.sub(b, 8))
case OpMul64:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b, 64))
case OpMul32:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b, 32))
case OpMul16:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b, 16))
case OpMul8:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b, 8))
case OpLsh64x64, OpLsh64x32, OpLsh64x16, OpLsh64x8:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b.exp2(64), 64))
case OpLsh32x64, OpLsh32x32, OpLsh32x16, OpLsh32x8:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b.exp2(32), 32))
case OpLsh16x64, OpLsh16x32, OpLsh16x16, OpLsh16x8:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b.exp2(16), 16))
case OpLsh8x64, OpLsh8x32, OpLsh8x16, OpLsh8x8:
a := ft.limits[v.Args[0].ID]
b := ft.limits[v.Args[1].ID]
return ft.newLimit(v, a.mul(b.exp2(8), 8))
case OpPhi: case OpPhi:
// Compute the union of all the input phis. // Compute the union of all the input phis.
// Often this will convey no information, because the block // Often this will convey no information, because the block
@ -1834,7 +1902,7 @@ func simplifyBlock(sdom SparseTree, ft *factsTable, b *Block) {
OpRsh64x8, OpRsh64x16, OpRsh64x32, OpRsh64x64: OpRsh64x8, OpRsh64x16, OpRsh64x32, OpRsh64x64:
// Check whether, for a >> b, we know that a is non-negative // Check whether, for a >> b, we know that a is non-negative
// and b is all of a's bits except the MSB. If so, a is shifted to zero. // and b is all of a's bits except the MSB. If so, a is shifted to zero.
bits := 8 * v.Type.Size() bits := 8 * v.Args[0].Type.Size()
if v.Args[1].isGenericIntConst() && v.Args[1].AuxInt >= bits-1 && ft.isNonNegative(v.Args[0]) { if v.Args[1].isGenericIntConst() && v.Args[1].AuxInt >= bits-1 && ft.isNonNegative(v.Args[0]) {
if b.Func.pass.debug > 0 { if b.Func.pass.debug > 0 {
b.Func.Warnl(v.Pos, "Proved %v shifts to zero", v.Op) b.Func.Warnl(v.Pos, "Proved %v shifts to zero", v.Op)

View File

@ -1147,6 +1147,18 @@ func inequalityPropagation(a [1]int, i, j uint) int {
return 0 return 0
} }
func issue66826a(a [21]byte) {
for i := 0; i <= 10; i++ { // ERROR "Induction variable: limits \[0,10\], increment 1$"
_ = a[2*i] // ERROR "Proved IsInBounds"
}
}
func issue66826b(a [31]byte, i int) {
if i < 0 || i > 10 {
return
}
_ = a[3*i] // ERROR "Proved IsInBounds"
}
//go:noinline //go:noinline
func useInt(a int) { func useInt(a int) {
} }