// Copyright 2016 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package ssa import ( "fmt" "math" "math/bits" "cmd/compile/internal/ssa/ssaop" ) // FitsInBitsU reports whether x fits in b bits (unsigned). func FitsInBitsU(x uint64, b uint) bool { return x>>b == 0 } // InitLimit sets initial constant limit for v. This limit is based // only on the operation itself, not any of its input arguments. This // method is only used in two places, once when the prove pass startup // and the other when a new ssa value is created, both for init. (unlike // flowLimit, below, which computes additional constraints based on // ranges of opcode arguments). func InitLimit(v *Value) Limit { if v.Type.IsBoolean() { switch v.Op { case ssaop.OpConstBool: b := v.AuxInt return Limit{Min: b, Max: b, Umin: uint64(b), Umax: uint64(b)} default: return Limit{Min: 0, Max: 1, Umin: 0, Umax: 1} } } if v.Type.IsPtrShaped() { // These are the types that EqPtr/NeqPtr operate on, except uintptr. switch v.Op { case ssaop.OpConstNil: return Limit{Min: 0, Max: 0, Umin: 0, Umax: 0} case ssaop.OpAddr, ssaop.OpLocalAddr: // TODO: others? l := NoLimit() l.Umin = 1 return l default: return NoLimit() } } if !v.Type.IsInteger() { return NoLimit() } // Default limits based on type. lim := NoLimitForBitsize(uint(v.Type.Size()) * 8) // Tighter limits on some opcodes. switch v.Op { // constants case ssaop.OpConst64: lim = Limit{Min: v.AuxInt, Max: v.AuxInt, Umin: uint64(v.AuxInt), Umax: uint64(v.AuxInt)} case ssaop.OpConst32: lim = Limit{Min: v.AuxInt, Max: v.AuxInt, Umin: uint64(uint32(v.AuxInt)), Umax: uint64(uint32(v.AuxInt))} case ssaop.OpConst16: lim = Limit{Min: v.AuxInt, Max: v.AuxInt, Umin: uint64(uint16(v.AuxInt)), Umax: uint64(uint16(v.AuxInt))} case ssaop.OpConst8: lim = Limit{Min: v.AuxInt, Max: v.AuxInt, Umin: uint64(uint8(v.AuxInt)), Umax: uint64(uint8(v.AuxInt))} // extensions case ssaop.OpZeroExt8to64, ssaop.OpZeroExt8to32, ssaop.OpZeroExt8to16: lim = lim.SignedMinMax(0, 1<<8-1) lim = lim.UnsignedMax(1<<8 - 1) case ssaop.OpZeroExt16to64, ssaop.OpZeroExt16to32: lim = lim.SignedMinMax(0, 1<<16-1) lim = lim.UnsignedMax(1<<16 - 1) case ssaop.OpZeroExt32to64: lim = lim.SignedMinMax(0, 1<<32-1) lim = lim.UnsignedMax(1<<32 - 1) case ssaop.OpSignExt8to64, ssaop.OpSignExt8to32, ssaop.OpSignExt8to16: lim = lim.SignedMinMax(math.MinInt8, math.MaxInt8) case ssaop.OpSignExt16to64, ssaop.OpSignExt16to32: lim = lim.SignedMinMax(math.MinInt16, math.MaxInt16) case ssaop.OpSignExt32to64: lim = lim.SignedMinMax(math.MinInt32, math.MaxInt32) // math/bits intrinsics case ssaop.OpCtz64, ssaop.OpBitLen64, ssaop.OpPopCount64, ssaop.OpCtz32, ssaop.OpBitLen32, ssaop.OpPopCount32, ssaop.OpCtz16, ssaop.OpBitLen16, ssaop.OpPopCount16, ssaop.OpCtz8, ssaop.OpBitLen8, ssaop.OpPopCount8: lim = lim.UnsignedMax(uint64(v.Args[0].Type.Size() * 8)) // bool to uint8 conversion case ssaop.OpCvtBoolToUint8: lim = lim.UnsignedMax(1) // length operations case ssaop.OpSliceLen, ssaop.OpSliceCap: f := v.Block.Func elemSize := uint64(v.Args[0].Type.Elem().Size()) if elemSize > 0 { heapSize := uint64(1)<<(uint64(f.Config.PtrSize)*8) - 1 maximumElementsFittingInHeap := heapSize / elemSize lim = lim.UnsignedMax(maximumElementsFittingInHeap) } fallthrough case ssaop.OpStringLen: lim = lim.signedMin(0) } // signed <-> unsigned propagation if lim.Min >= 0 { lim = lim.UnsignedMinMax(uint64(lim.Min), uint64(lim.Max)) } if FitsInBitsU(lim.Umax, uint(8*v.Type.Size()-1)) { lim = lim.SignedMinMax(int64(lim.Umin), int64(lim.Umax)) } return lim } // a Limit records known upper and lower bounds for a value. // // If we have min>max or umin>umax, then this Limit is // called "unsatisfiable". When we encounter such a Limit, we // know that any code for which that Limit applies is unreachable. // We don't particularly care how unsatisfiable limits propagate, // including becoming satisfiable, because any optimization // decisions based on those limits only apply to unreachable code. type Limit struct { Min, Max int64 // min <= value <= max, signed Umin, Umax uint64 // umin <= value <= umax, unsigned // For booleans, we use 0==false, 1==true for both ranges // For pointers, we use 0,0,0,0 for nil and minInt64,maxInt64,1,maxUint64 for nonnil } func NoLimit() Limit { return NoLimitForBitsize(64) } // If x and y can add without overflow or underflow // (using b bits), SafeAdd returns x+y, true. // Otherwise, returns 0, false. func SafeAdd(x, y int64, b uint) (int64, bool) { s := x + y if x >= 0 && y >= 0 && s < 0 { return 0, false // 64-bit overflow } if x < 0 && y < 0 && s >= 0 { return 0, false // 64-bit underflow } if !fitsInBits(s, b) { return 0, false } return s, true } // same as safeAdd but for subtraction. func SafeSub(x, y int64, b uint) (int64, bool) { if y == math.MinInt64 { if x == math.MaxInt64 { return 0, false // 64-bit overflow } x++ y++ } return SafeAdd(x, -y, b) } // same as safeAddU but for subtraction. func SafeSubU(x, y uint64, b uint) (uint64, bool) { if x < y { return 0, false // 64-bit underflow } s := x - y if !FitsInBitsU(s, b) { return 0, false } return s, true } func ConvertIntWithBitsize[Target uint64 | int64, Source uint64 | int64](x Source, bitsize uint) Target { if Target(0)-1 < 0 { // Signed target: sign-extend the low bitsize bits. switch bitsize { case 64: return Target(int64(x)) case 32: return Target(int32(x)) case 16: return Target(int16(x)) case 8: return Target(int8(x)) } } else { // Unsigned target: zero-extend the low bitsize bits. switch bitsize { case 64: return Target(uint64(x)) case 32: return Target(uint32(x)) case 16: return Target(uint16(x)) case 8: return Target(uint8(x)) } } panic("unreachable") } // fitsInBits reports whether x fits in b bits (signed). func fitsInBits(x int64, b uint) bool { if b == 64 { return true } m := int64(-1) << (b - 1) M := -m - 1 return x >= m && x <= M } func NoLimitForBitsize(bitsize uint) Limit { return Limit{Min: -(1 << (bitsize - 1)), Max: 1<<(bitsize-1) - 1, Umin: 0, Umax: 1< 0 || l.Umin > 0 || l.Max < 0 } func (l Limit) MaybeZero() bool { return !l.nonzero() } func (l Limit) Nonnegative() bool { return l.Min >= 0 } func (l Limit) Unsat() bool { return l.Min > l.Max || l.Umin > l.Umax } // UnsignedFixedLeadingBits extracts the all the most significant fixed bits from the limit. // fixed and count are an other way to represent a limit, you can convert them to a limit as follows: // // umin = fixed // umax = fixed | (1<<(64-count) - 1) // // In order to be useful for bitmanip analysis fixed and count are a coarser tool than a limit: // 1. the varying section (umax-umin) is always one less than a power of two // 2. that section is naturally aligned inside the 64-bit space func (l Limit) UnsignedFixedLeadingBits() (fixed uint64, count uint) { varying := uint(bits.Len64(l.Umin ^ l.Umax)) count = uint(bits.LeadingZeros64(l.Umin ^ l.Umax)) fixed = l.Umin &^ (1<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 } // Similar to add, but computes the complement of the limit for bitsize b. func (l Limit) Com(b uint) Limit { switch b { case 64: return Limit{ Min: ^l.Max, Max: ^l.Min, Umin: ^l.Umax, Umax: ^l.Umin, } case 32: return Limit{ Min: int64(^int32(l.Max)), Max: int64(^int32(l.Min)), Umin: uint64(^uint32(l.Umax)), Umax: uint64(^uint32(l.Umin)), } case 16: return Limit{ Min: int64(^int16(l.Max)), Max: int64(^int16(l.Min)), Umin: uint64(^uint16(l.Umax)), Umax: uint64(^uint16(l.Umin)), } case 8: return Limit{ Min: int64(^int8(l.Max)), Max: int64(^int8(l.Min)), Umin: uint64(^uint8(l.Umax)), Umax: uint64(^uint8(l.Umin)), } default: panic("unreachable") } } // Similar to add, but computes the negation of the limit for bitsize b. func (l Limit) Neg(b uint) Limit { return l.Com(b).Add(Limit{Min: 1, Max: 1, Umin: 1, Umax: 1}, b) } // Similar to add, but computes the TrailingZeros of the limit for bitsize b. func (l Limit) Ctz(b uint) Limit { fixed, fixedCount := l.UnsignedFixedLeadingBits() if fixedCount == 64 { constResult := min(uint(bits.TrailingZeros64(fixed)), b) return Limit{Min: int64(constResult), Max: int64(constResult), Umin: uint64(constResult), Umax: uint64(constResult)} } varying := 64 - fixedCount if l.Umin&((1<