Skip to content

Commit 3baf53a

Browse files
committed
cmd/compile: derive bounds on signed %N for N a power of 2
-N+1 <= x % N <= N-1 This is useful for cases like: func setBit(b []byte, i int) { b[i/8] |= 1<<(i%8) } The shift does not need protection against larger-than-7 cases. (It does still need protection against <0 cases.) Change-Id: Idf83101386af538548bfeb6e2928cea855610ce2 Reviewed-on: https://go-review.googlesource.com/c/go/+/672995 Reviewed-by: Jorropo <jorropo.pgm@gmail.com> Reviewed-by: David Chase <drchase@google.com> LUCI-TryBot-Result: Go LUCI <golang-scoped@luci-project-accounts.iam.gserviceaccount.com> Reviewed-by: Michael Knyszek <mknyszek@google.com>
1 parent 498899e commit 3baf53a

File tree

2 files changed

+128
-1
lines changed

2 files changed

+128
-1
lines changed

src/cmd/compile/internal/ssa/prove.go

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1757,7 +1757,9 @@ func (ft *factsTable) flowLimit(v *Value) bool {
17571757
case OpSub64, OpSub32, OpSub16, OpSub8:
17581758
a := ft.limits[v.Args[0].ID]
17591759
b := ft.limits[v.Args[1].ID]
1760-
return ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8))
1760+
sub := ft.newLimit(v, a.sub(b, uint(v.Type.Size())*8))
1761+
mod := ft.detectSignedMod(v)
1762+
return sub || mod
17611763
case OpNeg64, OpNeg32, OpNeg16, OpNeg8:
17621764
a := ft.limits[v.Args[0].ID]
17631765
bitsize := uint(v.Type.Size()) * 8
@@ -1913,6 +1915,122 @@ func (ft *factsTable) flowLimit(v *Value) bool {
19131915
return false
19141916
}
19151917

1918+
// See if we can get any facts because v is the result of signed mod by a constant.
1919+
// The mod operation has already been rewritten, so we have to try and reconstruct it.
1920+
// x % d
1921+
// is rewritten as
1922+
// x - (x / d) * d
1923+
// furthermore, the divide itself gets rewritten. If d is a power of 2 (d == 1<<k), we do
1924+
// (x / d) * d = ((x + adj) >> k) << k
1925+
// = (x + adj) & (-1<<k)
1926+
// with adj being an adjustment in case x is negative (see below).
1927+
// if d is not a power of 2, we do
1928+
// x / d = ... TODO ...
1929+
func (ft *factsTable) detectSignedMod(v *Value) bool {
1930+
if ft.detectSignedModByPowerOfTwo(v) {
1931+
return true
1932+
}
1933+
// TODO: non-powers-of-2
1934+
return false
1935+
}
1936+
func (ft *factsTable) detectSignedModByPowerOfTwo(v *Value) bool {
1937+
// We're looking for:
1938+
//
1939+
// x % d ==
1940+
// x - (x / d) * d
1941+
//
1942+
// which for d a power of 2, d == 1<<k, is done as
1943+
//
1944+
// x - ((x + (x>>(w-1))>>>(w-k)) & (-1<<k))
1945+
//
1946+
// w = bit width of x.
1947+
// (>> = signed shift, >>> = unsigned shift).
1948+
// See ./_gen/generic.rules, search for "Signed divide by power of 2".
1949+
1950+
var w int64
1951+
var addOp, andOp, constOp, sshiftOp, ushiftOp Op
1952+
switch v.Op {
1953+
case OpSub64:
1954+
w = 64
1955+
addOp = OpAdd64
1956+
andOp = OpAnd64
1957+
constOp = OpConst64
1958+
sshiftOp = OpRsh64x64
1959+
ushiftOp = OpRsh64Ux64
1960+
case OpSub32:
1961+
w = 32
1962+
addOp = OpAdd32
1963+
andOp = OpAnd32
1964+
constOp = OpConst32
1965+
sshiftOp = OpRsh32x64
1966+
ushiftOp = OpRsh32Ux64
1967+
case OpSub16:
1968+
w = 16
1969+
addOp = OpAdd16
1970+
andOp = OpAnd16
1971+
constOp = OpConst16
1972+
sshiftOp = OpRsh16x64
1973+
ushiftOp = OpRsh16Ux64
1974+
case OpSub8:
1975+
w = 8
1976+
addOp = OpAdd8
1977+
andOp = OpAnd8
1978+
constOp = OpConst8
1979+
sshiftOp = OpRsh8x64
1980+
ushiftOp = OpRsh8Ux64
1981+
default:
1982+
return false
1983+
}
1984+
1985+
x := v.Args[0]
1986+
and := v.Args[1]
1987+
if and.Op != andOp {
1988+
return false
1989+
}
1990+
var add, mask *Value
1991+
if and.Args[0].Op == addOp && and.Args[1].Op == constOp {
1992+
add = and.Args[0]
1993+
mask = and.Args[1]
1994+
} else if and.Args[1].Op == addOp && and.Args[0].Op == constOp {
1995+
add = and.Args[1]
1996+
mask = and.Args[0]
1997+
} else {
1998+
return false
1999+
}
2000+
var ushift *Value
2001+
if add.Args[0] == x {
2002+
ushift = add.Args[1]
2003+
} else if add.Args[1] == x {
2004+
ushift = add.Args[0]
2005+
} else {
2006+
return false
2007+
}
2008+
if ushift.Op != ushiftOp {
2009+
return false
2010+
}
2011+
if ushift.Args[1].Op != OpConst64 {
2012+
return false
2013+
}
2014+
k := w - ushift.Args[1].AuxInt // Now we know k!
2015+
d := int64(1) << k // divisor
2016+
sshift := ushift.Args[0]
2017+
if sshift.Op != sshiftOp {
2018+
return false
2019+
}
2020+
if sshift.Args[0] != x {
2021+
return false
2022+
}
2023+
if sshift.Args[1].Op != OpConst64 || sshift.Args[1].AuxInt != w-1 {
2024+
return false
2025+
}
2026+
if mask.AuxInt != -d {
2027+
return false
2028+
}
2029+
2030+
// All looks ok. x % d is at most +/- d-1.
2031+
return ft.signedMinMax(v, -d+1, d-1)
2032+
}
2033+
19162034
// getBranch returns the range restrictions added by p
19172035
// when reaching b. p is the immediate dominator of b.
19182036
func getBranch(sdom SparseTree, p *Block, b *Block) branch {

test/codegen/shift.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -656,3 +656,12 @@ func rsh64to8(v int64) int8 {
656656
}
657657
return x
658658
}
659+
660+
// We don't need to worry about shifting
661+
// more than the type size.
662+
// (There is still a negative shift test, but
663+
// no shift-too-big test.)
664+
func signedModShift(i int) int64 {
665+
// arm64:-"CMP",-"CSEL"
666+
return 1 << (i % 64)
667+
}

0 commit comments

Comments
 (0)