cmd/compile: add Lsh support to known bits

I've got a bit carried away with the exhaustive implementation.

I have one private real world code where adding support for
x << aConst to knownBits helps the compilation output.

However the « you can just think about it like a phi of all the shift
amounts » looked pretty easy to implement and it worked first try so
here it is.

Uniqued by LOC this adds 5 known bits hits when building the std.

Updates #78633

Change-Id: I3c4cafe9907004296129a07b2d13086660747a3d
Reviewed-on: https://go-review.googlesource.com/c/go/+/766042
Reviewed-by: Keith Randall <khr@golang.org>
LUCI-TryBot-Result: golang-scoped@luci-project-accounts.iam.gserviceaccount.com <golang-scoped@luci-project-accounts.iam.gserviceaccount.com>
Reviewed-by: Keith Randall <khr@google.com>
Auto-Submit: Jorropo <jorropo.pgm@gmail.com>
Reviewed-by: Carlos Amedee <carlos@golang.org>
This commit is contained in:
Jorropo 2026-04-12 20:16:14 +02:00 committed by Gopher Robot
parent 9c0a8a2b46
commit 977041b065
2 changed files with 113 additions and 0 deletions

View file

@ -117,6 +117,11 @@ func (kb *knownBitsState) fold(v *Value) (value, known int64) {
srcSize := v.Args[0].Type.Size() * 8
mask := int64(1<<srcSize - 1)
return x & mask, k | ^mask
case OpLsh8x8, OpLsh16x8, OpLsh32x8, OpLsh64x8,
OpLsh8x16, OpLsh16x16, OpLsh32x16, OpLsh64x16,
OpLsh8x32, OpLsh16x32, OpLsh32x32, OpLsh64x32,
OpLsh8x64, OpLsh16x64, OpLsh32x64, OpLsh64x64:
return kb.computeKnownBitsForLsh(v)
default:
return 0, 0
}
@ -219,3 +224,66 @@ func (kb *knownBitsState) isLiveOutEdge(b *Block, index uint) bool {
panic("unreachable; unknown block kind")
}
}
// computeKnownBitsForLsh computes the known bits for a left shift.
// Considering the following piece of code x = x << uint8(i)
// The algorithm is based on two observations:
//
// 1. computing a shift of a lattice by a constant (i) is easy:
// value, known = x<<i, xk<<i|(1<<i-1)
// each point in the lattice is shifted by the constant, all new shifted in bits are known zeros.
//
// 2. x = uint8(x) << i is equivalent to
//
// switch i {
// case 0: x0 = x << 0
// case 1: x1 = x << 1
// case 2: x2 = x << 2
// case 3: x3 = x << 3
// case 4: x4 = x << 4
// case 5: x5 = x << 5
// case 6: x6 = x << 6
// case 7: x7 = x << 7
// default: xd = x << 8
// }
// x = phi(x0, x1, x2, x3, x4, x5, x6, x7, xd)
//
// The algorithm below then models the phi in the equivalence above using same intersection algorithm phi uses.
// We also leverage known bits of the shift amount to remove "branches" in the switch that are proved to be impossible.
func (kb *knownBitsState) computeKnownBitsForLsh(v *Value) (value, known int64) {
xSize := v.Args[0].Type.Size() * 8
x, xk := kb.fold(v.Args[0])
y, yk := kb.fold(v.Args[1])
if uint64(y) >= uint64(xSize) {
return 0, -1
}
set := false
if v.AuxInt == 0 && uint64(^yk) >= uint64(xSize) {
// this implement the default case of the equivalent switch above.
// if the shift isn't bounded and there are unknown bits above the shift size we might completely stomp all bits.
value = 0
known = -1
set = true
}
yk &= xSize - 1
for i := range xSize {
if i&yk != y {
continue
}
a, k := x<<i, xk<<i|(1<<i-1)
if !set {
value, known = a, k
set = true
} else {
known &^= value ^ a
known &= k
}
if known == 0 {
break
}
}
return value & known, known
}

View file

@ -249,3 +249,48 @@ func unknownBitsSignExt(x int16) int32 {
x |= -0b010101010101010
return int32(x) & -1 << 12
}
func knownBitsLsh(x, y uint32) uint32 {
x |= 0b11110
x &^= 0b100000
y &= 2
// ???01111?
// ?01111???
// ---------
// ????11???
return (x << y) & 0b11000 // ERROR "known value of v[0-9]+ \(And32\): 24$"
}
func knownBitsLshZero(x, y uint64) uint64 {
x &^= 2
y &^= 2
y |= 128
return (x << y) & 8 // ERROR "known value of v[0-9]+ \(And64\): 0$" "known value of v[0-9]+ \(Lsh64x[0-9]+\): 0$"
}
func unknownBitsLshLeftSideMsb(x uint32, y uint32) uint32 {
x |= 0b11110
x &^= 0b100000
y &= 2
return (x << y) & 0b111000
}
func unknownBitsLshLeftSideLsb(x uint32, y uint32) uint32 {
x |= 0b11110
x &^= 0b100000
y &= 2
return (x << y) & 0b011100
}
func unknownBitsLshRightSide(x uint32, y uint32) uint32 {
x |= 0b11110
x &^= 0b100000
y &= 6
return (x << y) & 0b11000
}