476 lines
10 KiB
Go
476 lines
10 KiB
Go
package vrp
|
|
|
|
import (
|
|
"fmt"
|
|
"go/token"
|
|
"go/types"
|
|
"math/big"
|
|
|
|
"honnef.co/go/tools/ssa"
|
|
)
|
|
|
|
type Zs []Z
|
|
|
|
func (zs Zs) Len() int {
|
|
return len(zs)
|
|
}
|
|
|
|
func (zs Zs) Less(i int, j int) bool {
|
|
return zs[i].Cmp(zs[j]) == -1
|
|
}
|
|
|
|
func (zs Zs) Swap(i int, j int) {
|
|
zs[i], zs[j] = zs[j], zs[i]
|
|
}
|
|
|
|
type Z struct {
|
|
infinity int8
|
|
integer *big.Int
|
|
}
|
|
|
|
func NewZ(n int64) Z {
|
|
return NewBigZ(big.NewInt(n))
|
|
}
|
|
|
|
func NewBigZ(n *big.Int) Z {
|
|
return Z{integer: n}
|
|
}
|
|
|
|
func (z1 Z) Infinite() bool {
|
|
return z1.infinity != 0
|
|
}
|
|
|
|
func (z1 Z) Add(z2 Z) Z {
|
|
if z2.Sign() == -1 {
|
|
return z1.Sub(z2.Negate())
|
|
}
|
|
if z1 == NInfinity {
|
|
return NInfinity
|
|
}
|
|
if z1 == PInfinity {
|
|
return PInfinity
|
|
}
|
|
if z2 == PInfinity {
|
|
return PInfinity
|
|
}
|
|
|
|
if !z1.Infinite() && !z2.Infinite() {
|
|
n := &big.Int{}
|
|
n.Add(z1.integer, z2.integer)
|
|
return NewBigZ(n)
|
|
}
|
|
|
|
panic(fmt.Sprintf("%s + %s is not defined", z1, z2))
|
|
}
|
|
|
|
func (z1 Z) Sub(z2 Z) Z {
|
|
if z2.Sign() == -1 {
|
|
return z1.Add(z2.Negate())
|
|
}
|
|
if !z1.Infinite() && !z2.Infinite() {
|
|
n := &big.Int{}
|
|
n.Sub(z1.integer, z2.integer)
|
|
return NewBigZ(n)
|
|
}
|
|
|
|
if z1 != PInfinity && z2 == PInfinity {
|
|
return NInfinity
|
|
}
|
|
if z1.Infinite() && !z2.Infinite() {
|
|
return Z{infinity: z1.infinity}
|
|
}
|
|
if z1 == PInfinity && z2 == PInfinity {
|
|
return PInfinity
|
|
}
|
|
panic(fmt.Sprintf("%s - %s is not defined", z1, z2))
|
|
}
|
|
|
|
func (z1 Z) Mul(z2 Z) Z {
|
|
if (z1.integer != nil && z1.integer.Sign() == 0) ||
|
|
(z2.integer != nil && z2.integer.Sign() == 0) {
|
|
return NewBigZ(&big.Int{})
|
|
}
|
|
|
|
if z1.infinity != 0 || z2.infinity != 0 {
|
|
return Z{infinity: int8(z1.Sign() * z2.Sign())}
|
|
}
|
|
|
|
n := &big.Int{}
|
|
n.Mul(z1.integer, z2.integer)
|
|
return NewBigZ(n)
|
|
}
|
|
|
|
func (z1 Z) Negate() Z {
|
|
if z1.infinity == 1 {
|
|
return NInfinity
|
|
}
|
|
if z1.infinity == -1 {
|
|
return PInfinity
|
|
}
|
|
n := &big.Int{}
|
|
n.Neg(z1.integer)
|
|
return NewBigZ(n)
|
|
}
|
|
|
|
func (z1 Z) Sign() int {
|
|
if z1.infinity != 0 {
|
|
return int(z1.infinity)
|
|
}
|
|
return z1.integer.Sign()
|
|
}
|
|
|
|
func (z1 Z) String() string {
|
|
if z1 == NInfinity {
|
|
return "-∞"
|
|
}
|
|
if z1 == PInfinity {
|
|
return "∞"
|
|
}
|
|
return fmt.Sprintf("%d", z1.integer)
|
|
}
|
|
|
|
func (z1 Z) Cmp(z2 Z) int {
|
|
if z1.infinity == z2.infinity && z1.infinity != 0 {
|
|
return 0
|
|
}
|
|
if z1 == PInfinity {
|
|
return 1
|
|
}
|
|
if z1 == NInfinity {
|
|
return -1
|
|
}
|
|
if z2 == NInfinity {
|
|
return 1
|
|
}
|
|
if z2 == PInfinity {
|
|
return -1
|
|
}
|
|
return z1.integer.Cmp(z2.integer)
|
|
}
|
|
|
|
func MaxZ(zs ...Z) Z {
|
|
if len(zs) == 0 {
|
|
panic("Max called with no arguments")
|
|
}
|
|
if len(zs) == 1 {
|
|
return zs[0]
|
|
}
|
|
ret := zs[0]
|
|
for _, z := range zs[1:] {
|
|
if z.Cmp(ret) == 1 {
|
|
ret = z
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
func MinZ(zs ...Z) Z {
|
|
if len(zs) == 0 {
|
|
panic("Min called with no arguments")
|
|
}
|
|
if len(zs) == 1 {
|
|
return zs[0]
|
|
}
|
|
ret := zs[0]
|
|
for _, z := range zs[1:] {
|
|
if z.Cmp(ret) == -1 {
|
|
ret = z
|
|
}
|
|
}
|
|
return ret
|
|
}
|
|
|
|
var NInfinity = Z{infinity: -1}
|
|
var PInfinity = Z{infinity: 1}
|
|
var EmptyIntInterval = IntInterval{true, PInfinity, NInfinity}
|
|
|
|
func InfinityFor(v ssa.Value) IntInterval {
|
|
if b, ok := v.Type().Underlying().(*types.Basic); ok {
|
|
if (b.Info() & types.IsUnsigned) != 0 {
|
|
return NewIntInterval(NewZ(0), PInfinity)
|
|
}
|
|
}
|
|
return NewIntInterval(NInfinity, PInfinity)
|
|
}
|
|
|
|
type IntInterval struct {
|
|
known bool
|
|
Lower Z
|
|
Upper Z
|
|
}
|
|
|
|
func NewIntInterval(l, u Z) IntInterval {
|
|
if u.Cmp(l) == -1 {
|
|
return EmptyIntInterval
|
|
}
|
|
return IntInterval{known: true, Lower: l, Upper: u}
|
|
}
|
|
|
|
func (i IntInterval) IsKnown() bool {
|
|
return i.known
|
|
}
|
|
|
|
func (i IntInterval) Empty() bool {
|
|
return i.Lower == PInfinity && i.Upper == NInfinity
|
|
}
|
|
|
|
func (i IntInterval) IsMaxRange() bool {
|
|
return i.Lower == NInfinity && i.Upper == PInfinity
|
|
}
|
|
|
|
func (i1 IntInterval) Intersection(i2 IntInterval) IntInterval {
|
|
if !i1.IsKnown() {
|
|
return i2
|
|
}
|
|
if !i2.IsKnown() {
|
|
return i1
|
|
}
|
|
if i1.Empty() || i2.Empty() {
|
|
return EmptyIntInterval
|
|
}
|
|
i3 := NewIntInterval(MaxZ(i1.Lower, i2.Lower), MinZ(i1.Upper, i2.Upper))
|
|
if i3.Lower.Cmp(i3.Upper) == 1 {
|
|
return EmptyIntInterval
|
|
}
|
|
return i3
|
|
}
|
|
|
|
func (i1 IntInterval) Union(other Range) Range {
|
|
i2, ok := other.(IntInterval)
|
|
if !ok {
|
|
i2 = EmptyIntInterval
|
|
}
|
|
if i1.Empty() || !i1.IsKnown() {
|
|
return i2
|
|
}
|
|
if i2.Empty() || !i2.IsKnown() {
|
|
return i1
|
|
}
|
|
return NewIntInterval(MinZ(i1.Lower, i2.Lower), MaxZ(i1.Upper, i2.Upper))
|
|
}
|
|
|
|
func (i1 IntInterval) Add(i2 IntInterval) IntInterval {
|
|
if i1.Empty() || i2.Empty() {
|
|
return EmptyIntInterval
|
|
}
|
|
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
|
|
return NewIntInterval(l1.Add(l2), u1.Add(u2))
|
|
}
|
|
|
|
func (i1 IntInterval) Sub(i2 IntInterval) IntInterval {
|
|
if i1.Empty() || i2.Empty() {
|
|
return EmptyIntInterval
|
|
}
|
|
l1, u1, l2, u2 := i1.Lower, i1.Upper, i2.Lower, i2.Upper
|
|
return NewIntInterval(l1.Sub(u2), u1.Sub(l2))
|
|
}
|
|
|
|
func (i1 IntInterval) Mul(i2 IntInterval) IntInterval {
|
|
if i1.Empty() || i2.Empty() {
|
|
return EmptyIntInterval
|
|
}
|
|
x1, x2 := i1.Lower, i1.Upper
|
|
y1, y2 := i2.Lower, i2.Upper
|
|
return NewIntInterval(
|
|
MinZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
|
|
MaxZ(x1.Mul(y1), x1.Mul(y2), x2.Mul(y1), x2.Mul(y2)),
|
|
)
|
|
}
|
|
|
|
func (i1 IntInterval) String() string {
|
|
if !i1.IsKnown() {
|
|
return "[⊥, ⊥]"
|
|
}
|
|
if i1.Empty() {
|
|
return "{}"
|
|
}
|
|
return fmt.Sprintf("[%s, %s]", i1.Lower, i1.Upper)
|
|
}
|
|
|
|
type IntArithmeticConstraint struct {
|
|
aConstraint
|
|
A ssa.Value
|
|
B ssa.Value
|
|
Op token.Token
|
|
Fn func(IntInterval, IntInterval) IntInterval
|
|
}
|
|
|
|
type IntAddConstraint struct{ *IntArithmeticConstraint }
|
|
type IntSubConstraint struct{ *IntArithmeticConstraint }
|
|
type IntMulConstraint struct{ *IntArithmeticConstraint }
|
|
|
|
type IntConversionConstraint struct {
|
|
aConstraint
|
|
X ssa.Value
|
|
}
|
|
|
|
type IntIntersectionConstraint struct {
|
|
aConstraint
|
|
ranges Ranges
|
|
A ssa.Value
|
|
B ssa.Value
|
|
Op token.Token
|
|
I IntInterval
|
|
resolved bool
|
|
}
|
|
|
|
type IntIntervalConstraint struct {
|
|
aConstraint
|
|
I IntInterval
|
|
}
|
|
|
|
func NewIntArithmeticConstraint(a, b, y ssa.Value, op token.Token, fn func(IntInterval, IntInterval) IntInterval) *IntArithmeticConstraint {
|
|
return &IntArithmeticConstraint{NewConstraint(y), a, b, op, fn}
|
|
}
|
|
func NewIntAddConstraint(a, b, y ssa.Value) Constraint {
|
|
return &IntAddConstraint{NewIntArithmeticConstraint(a, b, y, token.ADD, IntInterval.Add)}
|
|
}
|
|
func NewIntSubConstraint(a, b, y ssa.Value) Constraint {
|
|
return &IntSubConstraint{NewIntArithmeticConstraint(a, b, y, token.SUB, IntInterval.Sub)}
|
|
}
|
|
func NewIntMulConstraint(a, b, y ssa.Value) Constraint {
|
|
return &IntMulConstraint{NewIntArithmeticConstraint(a, b, y, token.MUL, IntInterval.Mul)}
|
|
}
|
|
func NewIntConversionConstraint(x, y ssa.Value) Constraint {
|
|
return &IntConversionConstraint{NewConstraint(y), x}
|
|
}
|
|
func NewIntIntersectionConstraint(a, b ssa.Value, op token.Token, ranges Ranges, y ssa.Value) Constraint {
|
|
return &IntIntersectionConstraint{
|
|
aConstraint: NewConstraint(y),
|
|
ranges: ranges,
|
|
A: a,
|
|
B: b,
|
|
Op: op,
|
|
}
|
|
}
|
|
func NewIntIntervalConstraint(i IntInterval, y ssa.Value) Constraint {
|
|
return &IntIntervalConstraint{NewConstraint(y), i}
|
|
}
|
|
|
|
func (c *IntArithmeticConstraint) Operands() []ssa.Value { return []ssa.Value{c.A, c.B} }
|
|
func (c *IntConversionConstraint) Operands() []ssa.Value { return []ssa.Value{c.X} }
|
|
func (c *IntIntersectionConstraint) Operands() []ssa.Value { return []ssa.Value{c.A} }
|
|
func (s *IntIntervalConstraint) Operands() []ssa.Value { return nil }
|
|
|
|
func (c *IntArithmeticConstraint) String() string {
|
|
return fmt.Sprintf("%s = %s %s %s", c.Y().Name(), c.A.Name(), c.Op, c.B.Name())
|
|
}
|
|
func (c *IntConversionConstraint) String() string {
|
|
return fmt.Sprintf("%s = %s(%s)", c.Y().Name(), c.Y().Type(), c.X.Name())
|
|
}
|
|
func (c *IntIntersectionConstraint) String() string {
|
|
return fmt.Sprintf("%s = %s %s %s (%t branch)", c.Y().Name(), c.A.Name(), c.Op, c.B.Name(), c.Y().(*ssa.Sigma).Branch)
|
|
}
|
|
func (c *IntIntervalConstraint) String() string { return fmt.Sprintf("%s = %s", c.Y().Name(), c.I) }
|
|
|
|
func (c *IntArithmeticConstraint) Eval(g *Graph) Range {
|
|
i1, i2 := g.Range(c.A).(IntInterval), g.Range(c.B).(IntInterval)
|
|
if !i1.IsKnown() || !i2.IsKnown() {
|
|
return IntInterval{}
|
|
}
|
|
return c.Fn(i1, i2)
|
|
}
|
|
func (c *IntConversionConstraint) Eval(g *Graph) Range {
|
|
s := &types.StdSizes{
|
|
// XXX is it okay to assume the largest word size, or do we
|
|
// need to be platform specific?
|
|
WordSize: 8,
|
|
MaxAlign: 1,
|
|
}
|
|
fromI := g.Range(c.X).(IntInterval)
|
|
toI := g.Range(c.Y()).(IntInterval)
|
|
fromT := c.X.Type().Underlying().(*types.Basic)
|
|
toT := c.Y().Type().Underlying().(*types.Basic)
|
|
fromB := s.Sizeof(c.X.Type())
|
|
toB := s.Sizeof(c.Y().Type())
|
|
|
|
if !fromI.IsKnown() {
|
|
return toI
|
|
}
|
|
if !toI.IsKnown() {
|
|
return fromI
|
|
}
|
|
|
|
// uint<N> -> sint/uint<M>, M > N: [max(0, l1), min(2**N-1, u2)]
|
|
if (fromT.Info()&types.IsUnsigned != 0) &&
|
|
toB > fromB {
|
|
|
|
n := big.NewInt(1)
|
|
n.Lsh(n, uint(fromB*8))
|
|
n.Sub(n, big.NewInt(1))
|
|
return NewIntInterval(
|
|
MaxZ(NewZ(0), fromI.Lower),
|
|
MinZ(NewBigZ(n), toI.Upper),
|
|
)
|
|
}
|
|
|
|
// sint<N> -> sint<M>, M > N; [max(-∞, l1), min(2**N-1, u2)]
|
|
if (fromT.Info()&types.IsUnsigned == 0) &&
|
|
(toT.Info()&types.IsUnsigned == 0) &&
|
|
toB > fromB {
|
|
|
|
n := big.NewInt(1)
|
|
n.Lsh(n, uint(fromB*8))
|
|
n.Sub(n, big.NewInt(1))
|
|
return NewIntInterval(
|
|
MaxZ(NInfinity, fromI.Lower),
|
|
MinZ(NewBigZ(n), toI.Upper),
|
|
)
|
|
}
|
|
|
|
return fromI
|
|
}
|
|
func (c *IntIntersectionConstraint) Eval(g *Graph) Range {
|
|
xi := g.Range(c.A).(IntInterval)
|
|
if !xi.IsKnown() {
|
|
return c.I
|
|
}
|
|
return xi.Intersection(c.I)
|
|
}
|
|
func (c *IntIntervalConstraint) Eval(*Graph) Range { return c.I }
|
|
|
|
func (c *IntIntersectionConstraint) Futures() []ssa.Value {
|
|
return []ssa.Value{c.B}
|
|
}
|
|
|
|
func (c *IntIntersectionConstraint) Resolve() {
|
|
r, ok := c.ranges[c.B].(IntInterval)
|
|
if !ok {
|
|
c.I = InfinityFor(c.Y())
|
|
return
|
|
}
|
|
|
|
switch c.Op {
|
|
case token.EQL:
|
|
c.I = r
|
|
case token.GTR:
|
|
c.I = NewIntInterval(r.Lower.Add(NewZ(1)), PInfinity)
|
|
case token.GEQ:
|
|
c.I = NewIntInterval(r.Lower, PInfinity)
|
|
case token.LSS:
|
|
// TODO(dh): do we need 0 instead of NInfinity for uints?
|
|
c.I = NewIntInterval(NInfinity, r.Upper.Sub(NewZ(1)))
|
|
case token.LEQ:
|
|
c.I = NewIntInterval(NInfinity, r.Upper)
|
|
case token.NEQ:
|
|
c.I = InfinityFor(c.Y())
|
|
default:
|
|
panic("unsupported op " + c.Op.String())
|
|
}
|
|
}
|
|
|
|
func (c *IntIntersectionConstraint) IsKnown() bool {
|
|
return c.I.IsKnown()
|
|
}
|
|
|
|
func (c *IntIntersectionConstraint) MarkUnresolved() {
|
|
c.resolved = false
|
|
}
|
|
|
|
func (c *IntIntersectionConstraint) MarkResolved() {
|
|
c.resolved = true
|
|
}
|
|
|
|
func (c *IntIntersectionConstraint) IsResolved() bool {
|
|
return c.resolved
|
|
}
|