packages feed

what4-domains-0.1: src/What4/Domains/BV/Bitwise.hs

{-|
Module      : What4.Domains.BV.Bitwise
Copyright   : (c) Galois Inc, 2020
License     : BSD3
Maintainer  : huffman@galois.com

Provides a bitwise implementation of bitvector abstract domains.
-}

{-# LANGUAGE BangPatterns #-}
{-# LANGUAGE DataKinds #-}
{-# LANGUAGE GADTs #-}
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE TypeOperators #-}

module What4.Domains.BV.Bitwise
  ( Domain(..)
  , bitle
  , proper
  , bvdMask
  , member
  , pmember
  , size
  , asSingleton
  , nonempty
  , eq
  , slt
  , ult
  , domainsOverlap
  , bitbounds
  , ubounds
  , sbounds
  -- * Lattice operations
  , top
  , any
  , bottom
  , isBottom
  , join
  , union
  , meet
  , intersection
  , leq
  -- * Operations
  , singleton
  , range
  , interval
  , concat
  , select
  , zext
  , sext
  , testBit
  -- ** shifts and rotates
  , shl
  , lshr
  , ashr
  , rol
  , ror
  , shlAbstract
  , lshrAbstract
  , ashrAbstract
  , rolAbstract
  , rorAbstract
  , shlAbstractSpec
  , lshrAbstractSpec
  , ashrAbstractSpec
  , rolAbstractSpec
  , rorAbstractSpec
  -- ** arithmetic
  , add
  , sub
  , negate
  , scale
  , mul
  , mulPrecise
  , udiv
  , urem
  , sdiv
  , srem
  , udivPrecise
  , uremPrecise
  -- ** arithmetic (SMT-LIB div-by-zero semantics)
  , udivSmtlib
  , uremSmtlib
  , sdivSmtlib
  , sremSmtlib
  -- ** bitwise logical
  , and
  , or
  , xor
  , not

  -- * Correctness properties
  , genDomain
  , genElement
  , genPair
  , correct_any
  , correct_singleton
  , correct_overlap
  , correct_overlap_inv
  , correct_asSingleton
  , correct_union
  , correct_intersection
  , correct_join
  , correct_meet
  , precise_meet
  , correct_leq
  -- ** Lattice laws
  , join_commutative
  , join_idempotent
  , meet_commutative
  , meet_idempotent
  , join_top
  , join_bottom
  , meet_top
  , meet_bottom
  , leq_reflexive
  , leq_transitive
  , meet_lower_bound
  , join_upper_bound
  , join_monotone
  , meet_monotone
  , join_associative
  , meet_associative
  , join_absorb
  , meet_absorb
  , join_proper
  , meet_proper
  , correct_zero_ext
  , correct_sign_ext
  , correct_concat
  , correct_shrink
  , correct_trunc
  , correct_select
  , correct_shl
  , correct_lshr
  , correct_ashr
  , correct_rol
  , correct_ror
  , correct_shlAbstract
  , correct_lshrAbstract
  , correct_ashrAbstract
  , correct_rolAbstract
  , correct_rorAbstract
  , correct_equiv_shlAbstract
  , correct_equiv_lshrAbstract
  , correct_equiv_ashrAbstract
  , correct_equiv_rolAbstract
  , correct_equiv_rorAbstract
  , correct_eq
  , correct_ult
  , correct_slt
  , correct_ubounds
  , correct_sbounds
  , correct_add
  , correct_sub
  , correct_neg
  , correct_scale
  , correct_mul
  , correct_mulPrecise
  , correct_udiv
  , correct_urem
  , correct_sdiv
  , correct_srem
  , correct_udivPrecise
  , correct_uremPrecise
  , correct_udivSmtlib
  , correct_uremSmtlib
  , correct_sdivSmtlib
  , correct_sremSmtlib
  , correct_and
  , correct_or
  , correct_not
  , correct_xor
  , correct_testBit
  ) where

import           Data.Bits hiding (testBit, xor)
import qualified Data.Bits as Bits
import           Data.Parameterized.NatRepr
import           Numeric.Natural
import           GHC.TypeNats
import           What4.Domains.BV.Bitwise.Tnum (Tnum)
import qualified What4.Domains.BV.Bitwise.Tnum as Tnum
import           What4.Domains.Verification (Property, property, (==>), Gen, chooseInteger)

import qualified Prelude
import           Prelude hiding (any, concat, negate, and, or, not)

import qualified What4.Domains.Arithmetic as Arith

-- | A bitwise interval domain, defined via a
--   bitwise upper and lower bound.  The ordering
--   used here to construct the interval is the pointwise
--   ordering on bits.  In particular @x [= y iff x .|. y == y@,
--   and a value @x@ is in the set defined by the pair @(lo,hi)@
--   just when @lo [= x && x [= hi@.
data Domain (w :: Nat) =
  BVBitInterval !Integer !Integer !Integer
  -- ^ @BVDBitInterval mask lo hi@.
  --  @mask@ caches the value of @2^w - 1@
  deriving (Eq, Ord, Show)

-- | /O(w)/. Test if the domain satisfies its invariants.
proper :: NatRepr w -> Domain w -> Bool
proper w (BVBitInterval mask lo hi) =
  mask == maxUnsigned w &&
  bitle lo mask &&
  bitle hi mask &&
  bitle lo hi

-- | /O(w)/. Test if the given integer value is a member of the abstract domain.
member :: Domain w -> Integer -> Bool
member (BVBitInterval mask lo hi) x = bitle lo x' && bitle x' hi
  where x' = x .&. mask

-- | /O(w)/. Compute how many concrete elements are in the abstract domain.
size :: Domain w -> Integer
size d@(BVBitInterval _ lo hi)
  | bitle lo hi = Bits.bit (Bits.popCount (unknownBits d))
  | otherwise   = 0

bitle :: Integer -> Integer -> Bool
bitle x y = (x .|. y) == y

-- | /O(1)/. The set of bit positions whose values are not constant
-- throughout the domain — i.e.\ the tristate-number mask. Bits set here
-- vary; bits clear here are determined (and equal in @lo@ and @hi@).
unknownBits :: Domain w -> Integer
unknownBits (BVBitInterval _ lo hi) = lo `Bits.xor` hi
{-# INLINE unknownBits #-}

-- | /O(1)/. Return the bitvector mask value from this domain.
bvdMask :: Domain w -> Integer
bvdMask (BVBitInterval mask _ _) = mask

-- | Random generator for domain values.  We always generate
--   nonempty domain values.
genDomain :: NatRepr w -> Gen (Domain w)
genDomain w =
  do let mask = maxUnsigned w
     lo <- chooseInteger (0, mask)
     hi <- chooseInteger (0, mask)
     pure $! interval mask lo (lo .|. hi)

-- This generator goes to some pains to try
-- to generate a good statistical distribution
-- of the values in the domain.  It only chooses
-- random bits for the "unknown" values of
-- the domain, then stripes them out among
-- the unknown bit positions.
genElement :: Domain w -> Gen Integer
genElement d@(BVBitInterval _mask lo _) =
  do x <- chooseInteger (0, bit bs - 1)
     pure $ stripe lo x 0

 where
 u = unknownBits d
 bs = Bits.popCount u
 stripe val x i
   | x == 0 = val
   | Bits.testBit u i =
       let val' = if Bits.testBit x 0 then setBit val i else val in
       stripe val' (x `shiftR` 1) (i + 1)
   | otherwise = stripe val x (i + 1)

{- A faster generator, but I worry that it
   doesn't have very good statistical properties...

genElement :: Domain w -> Gen Integer
genElement (BVBitInterval mask lo hi) =
  do let u = Bits.xor lo hi
     x <- chooseInteger (0, mask)
     pure ((x .&. u) .|. lo)
-}

-- | Generate a random nonempty domain and an element
--   contained in that domain.
genPair :: NatRepr w -> Gen (Domain w, Integer)
genPair w =
  do a <- genDomain w
     x <- genElement a
     return (a,x)

-- | /O(1)/. Unsafe constructor for internal use.
interval :: Integer -> Integer -> Integer -> Domain w
interval mask lo hi = BVBitInterval mask lo hi

-- | /O(w)/. Construct a domain from bitwise lower and upper bounds.
range :: NatRepr w -> Integer -> Integer -> Domain w
range w lo hi = BVBitInterval (maxUnsigned w) lo' hi'
  where
  lo'  = lo .&. mask
  hi'  = hi .&. mask
  mask = maxUnsigned w

-- | /O(1)/. Bitwise lower and upper bounds.
bitbounds :: Domain w -> (Integer, Integer)
bitbounds (BVBitInterval _ lo hi) = (lo, hi)

-- | /O(w)/. Test if this domain contains a single value, and return it if so.
asSingleton :: Domain w -> Maybe Integer
asSingleton (BVBitInterval _ lo hi) = if lo == hi then Just lo else Nothing

-- | /O(w)/. Returns true iff there is at least one element
-- in this bitwise domain.
nonempty :: Domain w -> Bool
nonempty (BVBitInterval _mask lo hi) = bitle lo hi

------------------------------------------------------------------------
-- Lattice operations

-- | /O(1)/. Top element of the lattice: represents all bitvectors of width @w@.
top :: NatRepr w -> Domain w
top w = BVBitInterval mask 0 mask
  where
  mask = maxUnsigned w
{-# INLINE top #-}

-- | /O(w)/. Bitwise domain containing every bitvector value.
{-# DEPRECATED any "Use 'top' instead" #-}
any :: NatRepr w -> Domain w
any = top
{-# INLINE any #-}

-- | /O(1)/. Bottom element of the lattice: represents the empty set of bitvectors.
-- This is an improper domain whose membership predicate is unsatisfiable.
bottom :: NatRepr w -> Domain w
bottom w = BVBitInterval mask mask 0
  where
  mask = maxUnsigned w
{-# INLINE bottom #-}

-- | /O(1)/.
isBottom :: Domain w -> Bool
isBottom (BVBitInterval mask lo hi) = lo == mask && hi == 0

-- | /O(w)/. Lattice join: pointwise least upper bound on the bit-level @bitle@ ordering.
join :: Domain w -> Domain w -> Domain w
join (BVBitInterval mask alo ahi) (BVBitInterval _ blo bhi) =
  BVBitInterval mask (alo .&. blo) (ahi .|. bhi)

{-# DEPRECATED union "Use 'join' instead" #-}
union :: Domain w -> Domain w -> Domain w
union = join
{-# INLINE union #-}

-- | /O(w)/. Lattice meet: pointwise greatest lower bound on the bit-level @bitle@ ordering.
-- If both inputs are proper (or bottom), so is the result.
meet :: Domain w -> Domain w -> Domain w
meet (BVBitInterval mask alo ahi) (BVBitInterval _ blo bhi)
  | bitle lo hi = BVBitInterval mask lo hi
  | otherwise   = BVBitInterval mask mask 0  -- canonical bottom
  where
    lo = alo .|. blo
    hi = ahi .&. bhi

{-# DEPRECATED intersection "Use 'meet' instead" #-}
intersection :: Domain w -> Domain w -> Domain w
intersection = meet
{-# INLINE intersection #-}

-- | /O(w)/. Lattice ordering: @leq a b@ returns 'True' if every concrete value
-- represented by @a@ is also represented by @b@.
leq :: Domain w -> Domain w -> Bool
leq (BVBitInterval _ alo ahi) (BVBitInterval _ blo bhi) =
  bitle blo alo && bitle ahi bhi
{-# INLINE leq #-}

------------------------------------------------------------------------
-- Operations

-- | /O(w)/. Return a domain containing just the given value.
singleton :: NatRepr w -> Integer -> Domain w
singleton w x = BVBitInterval mask x' x'
  where
  x' = x .&. mask
  mask = maxUnsigned w

-- | /O(w)/. Returns true iff the domains have some value in common.
domainsOverlap :: Domain w -> Domain w -> Bool
domainsOverlap a b = nonempty (meet a b)

-- | /O(w)/. Decide equality of two domains: 'Just True' if both are the same
-- singleton, 'Just False' if they're disjoint, 'Nothing' otherwise.
eq :: Domain w -> Domain w -> Maybe Bool
eq a b
  | Just x <- asSingleton a
  , Just y <- asSingleton b
  = Just (x == y)

  | Prelude.not (domainsOverlap a b) = Just False
  | otherwise = Nothing

-- | /O(u + v)/. @concat a y@ returns a domain where each element in @a@ has
-- been concatenated with an element in @y@. The most-significant bits are
-- @a@, and the least significant bits are @y@.
concat :: NatRepr u -> Domain u -> NatRepr v -> Domain v -> Domain (u + v)
concat u (BVBitInterval _ alo ahi) v (BVBitInterval _ blo bhi) =
    BVBitInterval mask (cat alo blo) (cat ahi bhi)
  where
    cat i j = (i `shiftL` widthVal v) + j
    mask = maxUnsigned (addNat u v)

-- | /O(w)/. @shrink i a@ drops the @i@ least significant bits from @a@.
shrink ::
  NatRepr i ->
  Domain (i + n) -> Domain n
shrink i (BVBitInterval mask lo hi) = BVBitInterval (shr mask) (shr lo) (shr hi)
  where
  shr x = x `shiftR` widthVal i

-- | /O(w)/. @trunc n d@ selects the @n@ least significant bits from @d@.
trunc ::
  (n <= w) =>
  NatRepr n ->
  Domain w ->
  Domain n
trunc n (BVBitInterval _ lo hi) = range n lo hi

-- | /O(w)/. @select i n a@ selects @n@ bits starting from index @i@ from @a@.
select ::
  (1 <= n, i + n <= w) =>
  NatRepr i ->
  NatRepr n ->
  Domain w -> Domain n
select i n a = shrink i (trunc (addNat i n) a)

-- | /O(w)/. Zero-extend a domain to a larger width.
zext :: (1 <= w, w + 1 <= u) => Domain w -> NatRepr u -> Domain u
zext (BVBitInterval _ lo hi) u = range u lo hi

-- | /O(w)/. Sign-extend a domain to a larger width.
sext :: (1 <= w, w + 1 <= u) => NatRepr w -> Domain w -> NatRepr u -> Domain u
sext w (BVBitInterval _ lo hi) u = range u lo' hi'
  where
  lo' = toSigned w lo
  hi' = toSigned w hi

-- | /O(w)/. Test bit @i@ of every value in the domain: 'Just True' if it is
-- set in every member, 'Just False' if clear in every member, 'Nothing' if
-- it varies.
testBit :: Domain w -> Natural -> Maybe Bool
testBit (BVBitInterval _mask lo hi) i = if lob == hib then Just lob else Nothing
  where
  lob = Bits.testBit lo j
  hib = Bits.testBit hi j
  j = fromIntegral i

-- | /O(w)/. Shift left by a known amount.
shl :: NatRepr w -> Domain w -> Integer -> Domain w
shl w (BVBitInterval mask lo hi) y = BVBitInterval mask (shleft lo) (shleft hi)
  where
  y' = fromInteger (min y (intValue w))
  shleft x = (x `shiftL` y') .&. mask

-- | /O(w)/. Rotate left by a known amount.
rol :: NatRepr w -> Domain w -> Integer -> Domain w
rol w (BVBitInterval mask lo hi) y =
  BVBitInterval mask (Arith.rotateLeft w lo y) (Arith.rotateLeft w hi y)

-- | /O(w)/. Rotate right by a known amount.
ror :: NatRepr w -> Domain w -> Integer -> Domain w
ror w (BVBitInterval mask lo hi) y =
  BVBitInterval mask (Arith.rotateRight w lo y) (Arith.rotateRight w hi y)

-- | /O(w)/. Logical (zero-fill) shift right by a known amount.
lshr :: NatRepr w -> Domain w -> Integer -> Domain w
lshr w (BVBitInterval mask lo hi) y = BVBitInterval mask (shr lo) (shr hi)
  where
  y' = fromInteger (min y (intValue w))
  shr x = x `shiftR` y'

-- | /O(w)/. Arithmetic (sign-extending) shift right by a known amount.
ashr :: (1 <= w) => NatRepr w -> Domain w -> Integer -> Domain w
ashr w (BVBitInterval mask lo hi) y = BVBitInterval mask (shr lo) (shr hi)
  where
  y' = fromInteger (min y (intValue w))
  shr x = ((toSigned w x) `shiftR` y') .&. mask

-- | Conflict ("empty") domain: invariant @lo [= hi@ is violated.
-- Used as the meet identity when intersecting per-shift contributions.
conflict :: Integer -> Domain w
conflict mask = BVBitInterval mask mask 0

isConflict :: Domain w -> Bool
isConflict (BVBitInterval _ lo hi) = Prelude.not (bitle lo hi)

-- | Is this the fully unknown domain, @[0, mask]@?
isAny :: Domain w -> Bool
isAny (BVBitInterval mask lo hi) = lo == 0 && hi == mask

-- | Decompose @b@'s bounds into two bitmasks, @(zeros, ones)@:
--
-- * @zeros@ has a @1@ at every position where every member of @b@ has a @0@.
-- * @ones@ has a @1@ at every position where every member of @b@ has a @1@.
--
-- This is the same encoding LLVM's @KnownBits@ uses, and is paired with
-- 'memberMask' to check membership using bitwise operations alone.
knownZerosOnes :: Domain w -> (Integer, Integer)
knownZerosOnes (BVBitInterval mask lo hi) = (mask `Bits.xor` hi, lo)

-- | Equivalent to 'member' @b@ @s@, given @(zeros, ones) = knownZerosOnes b@.
-- Cheaper than 'member' (no @[lo, hi]@ ordering check) and lets the inner
-- loop hoist @(zeros, ones)@ outside the iteration.
memberMask :: Integer -> Integer -> Integer -> Bool
memberMask zeros ones s = (zeros .&. s) == 0 && (ones .|. s) == s

-- | Generic shift skeleton shared by 'shlAbstract', 'lshrAbstract', and
-- 'ashrAbstract'.
--
-- The idea: try every concrete shift amount @s@ that @b@ could be, apply
-- @op s@, and union the results. \"Union\" here means \"a result bit is
-- known to be 0 only if every per-shift result agrees it's 0, known to
-- be 1 only if every result agrees it's 1, otherwise unknown\".
--
-- Three optimizations make this fast:
--
-- * Don't iterate past the width. Every shift amount @>= w@ produces
--   the same result for a given @op@ (all zeros for @shl@/@lshr@, the
--   sign-extended pattern for @ashr@), so we iterate
--   @[bl, min bh w]@ and (if @bh > w@) collapse the rest into one
--   call @op w@.
-- * Skip impossible amounts. If @b@'s low bit is known to be 1, only
--   odd shift amounts are reachable; we use 'memberMask' to skip the
--   rest with a cheap pair of bitwise tests.
-- * Stop early. If the running union is already \"fully unknown\",
--   nothing more can be inferred.
--
-- Same iteration strategy as LLVM's @KnownBits::shl@, @KnownBits::lshr@,
-- and @KnownBits::ashr@.
{-# INLINE foldShifts #-}
foldShifts ::
  NatRepr w ->
  Domain w {- ^ shift-amount domain -} ->
  (Int -> Domain w) {- ^ per-shift transfer; @s@ ranges over @[0..w]@ -} ->
  Domain w
foldShifts w b op = collapse (go bl (conflict mask))
  where
  mask = bvdMask b
  wI = intValue w
  (bl, bh) = ubounds b
  (zeros, ones) = knownZerosOnes b
  iterEnd = min bh wI
  go !s !acc
    | isAny acc = acc
    | s <= iterEnd =
        if memberMask zeros ones s
          then go (s + 1) (union acc (op (fromInteger s)))
          else go (s + 1) acc
    | bh > wI =
        -- @b@'s high bound itself is a member of @b@ that exceeds @w@,
        -- so at least one shift amount falls in the saturated tail.
        union acc (op (fromInteger wI))
    | otherwise = acc

  collapse d
    | isConflict d = BVBitInterval mask 0 0
    | otherwise    = d

-- | /O(w²)/. Shift left by an amount drawn from the domain @b@. See
-- 'foldShifts' for the algorithm.
--
-- More precisely, /O(n · w)/ where @w@ is the bitvector width and
-- @n = min(bh − bl + 1, w + 1)@ is the number of candidate shift amounts
-- considered, with @bl@ and @bh@ the unsigned bounds of @b@.
shlAbstract :: NatRepr w -> Domain w -> Domain w -> Domain w
shlAbstract w a@(BVBitInterval mask aLo aHi) b
  -- Fast path: a fully unknown @a@ shifts in zeros at the bottom. Bits
  -- @[0..min bl w - 1]@ are forced to 0 because every concrete shift
  -- amount is at least @bl@ (and shift @>= w@ kills every bit).
  | isAny a =
      let k = fromInteger (min bl (intValue w))
          lowZeros = bit k - 1
      in BVBitInterval mask 0 (mask .&. complement lowZeros)
  | otherwise = foldShifts w b shiftBy
  where
  (bl, _) = ubounds b
  shiftBy s = BVBitInterval mask ((aLo `shiftL` s) .&. mask)
                                 ((aHi `shiftL` s) .&. mask)

-- | /O(w²)/. Logical (zero-fill) shift right by an amount drawn from
-- the domain @b@. See 'foldShifts' for the algorithm.
--
-- More precisely, /O(n · w)/ where @w@ is the bitvector width and
-- @n = min(bh − bl + 1, w + 1)@ is the number of candidate shift amounts
-- considered, with @bl@ and @bh@ the unsigned bounds of @b@.
lshrAbstract :: NatRepr w -> Domain w -> Domain w -> Domain w
lshrAbstract w a@(BVBitInterval mask aLo aHi) b
  -- Fast path: every shift @>= bl@ forces the top @min bl w@ bits of
  -- the result to 0.
  | isAny a =
      let k = fromInteger (min bl (intValue w))
          highMask = mask `shiftR` k
      in BVBitInterval mask 0 highMask
  | otherwise = foldShifts w b shiftBy
  where
  (bl, _) = ubounds b
  shiftBy s = BVBitInterval mask (aLo `shiftR` s) (aHi `shiftR` s)

-- | /O(w²)/. Arithmetic (sign-extending) shift right by an amount drawn
-- from the domain @b@. See 'foldShifts' for the algorithm.
--
-- More precisely, /O(n · w)/ where @w@ is the bitvector width and
-- @n = min(bh − bl + 1, w + 1)@ is the number of candidate shift amounts
-- considered, with @bl@ and @bh@ the unsigned bounds of @b@.
ashrAbstract :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
ashrAbstract w (BVBitInterval mask aLo aHi) b =
  foldShifts w b shiftBy
  where
  -- Sign-extending shift on the @lo@ and @hi@ bounds independently is
  -- sound: if every member of @a@ has a known-1 at position @i >= sign@,
  -- so does every member's @ashr s@; same for known-0.
  shiftBy s = BVBitInterval mask
                ((toSigned w aLo `shiftR` s) .&. mask)
                ((toSigned w aHi `shiftR` s) .&. mask)

-- | /O(w²)/. Rotate left by an amount drawn from the domain @b@. See
-- 'foldRotates' for the algorithm.
--
-- More precisely, /O(r · w)/ where @w@ is the bitvector width and @r@ is
-- the number of distinct residues mod @w@ that are reachable from @b@
-- (at most @w@).
rolAbstract :: NatRepr w -> Domain w -> Domain w -> Domain w
rolAbstract w (BVBitInterval mask aLo aHi) b = foldRotates w b rotBy fullDom
  where
  -- Fast path: if every residue in @[0, w-1]@ is reachable from @b@,
  -- every output bit could come from any input bit, so the answer is
  -- determined by @a@'s global structure alone.
  fullDom = fullCoverage mask aLo aHi
  rotBy s = BVBitInterval mask
              (Arith.rotateLeft w aLo (toInteger s))
              (Arith.rotateLeft w aHi (toInteger s))

-- | /O(w²)/. Rotate right by an amount drawn from the domain @b@.
-- Mirrors 'rolAbstract'.
--
-- More precisely, /O(r · w)/ where @w@ is the bitvector width and @r@ is
-- the number of distinct residues mod @w@ that are reachable from @b@
-- (at most @w@).
rorAbstract :: NatRepr w -> Domain w -> Domain w -> Domain w
rorAbstract w (BVBitInterval mask aLo aHi) b = foldRotates w b rotBy fullDom
  where
  fullDom = fullCoverage mask aLo aHi
  rotBy s = BVBitInterval mask
              (Arith.rotateRight w aLo (toInteger s))
              (Arith.rotateRight w aHi (toInteger s))

-- | Generic rotate skeleton shared by 'rolAbstract' and 'rorAbstract'.
--
-- Rotating by @s@ is the same as rotating by @s `mod` w@, so we only
-- ever care about @w@ distinct rotation amounts. The trick is figuring
-- out which residues mod @w@ some member of @b@ can produce, then
-- unioning @op r@ over those residues. Two cases:
--
-- * Power-of-two width (the common case): @s `mod` w@ is just the low
--   @log2 w@ bits of @s@. So the reachable residues are exactly the
--   values consistent with @b@'s known bits restricted to those low
--   bits, and we use the same @KnownBits@-style mask check as
--   'foldShifts' to skip residues no member of @b@ can produce. This
--   gives the smallest sound result.
--
-- * Non-power-of-two width: there's no clean correspondence between
--   @b@'s bits and residues mod @w@. We fall back to bounds: the
--   residues reachable from @[bl, bh]@ form a (possibly wrapping)
--   range in @[0, w-1]@, which we iterate without further skipping.
--   Sound, sometimes loose.
--
-- Iteration is always at most @w@ steps, never over the (possibly
-- enormous) integer range @[bl, bh]@.
{-# INLINE foldRotates #-}
foldRotates ::
  NatRepr w ->
  Domain w {- ^ rotate-amount domain -} ->
  (Int -> Domain w) {- ^ per-amount transfer; argument is residue mod @w@ -} ->
  Domain w {- ^ result when all residues are reachable -} ->
  Domain w
foldRotates w b op fullDom
  | Arith.isPow2Integer wI =
      let residueMask = wI - 1
          zerosLow = zeros .&. residueMask
          onesLow = ones .&. residueMask
          allResiduesReachable = zerosLow == 0 && onesLow == 0
          skip r = Prelude.not (memberMask zerosLow onesLow (toInteger r))
      in if allResiduesReachable
           then fullDom
           else iterRanges skip [(0, fromInteger wI - 1)] (conflict mask)
  | otherwise =
      case residueRanges of
        Nothing     -> fullDom
        Just ranges -> iterRanges (\_ -> False) ranges (conflict mask)
  where
  mask = bvdMask b
  wI = intValue w
  (bl, bh) = ubounds b
  (zeros, ones) = knownZerosOnes b

  -- Reduce @[bl, bh]@ mod @w@ to a list of residue ranges in @[0, w-1]@.
  -- @Nothing@ means every residue is reachable; otherwise the list has
  -- one or two ranges (two when the residue range wraps around @0@).
  residueRanges
    | bh - bl + 1 >= wI = Nothing
    | otherwise =
        let (ql, rl) = bl `divMod` wI
            (qh, rh) = bh `divMod` wI
        in if qh == ql
             then Just [(fromInteger rl, fromInteger rh)]
             else Just [(0, fromInteger rh), (fromInteger rl, fromInteger wI - 1)]

  iterRanges _ [] acc = acc
  iterRanges skip ((lo, hi) : rest) acc = iterRanges skip rest (iter skip lo hi acc)

  iter skip !s !hi !acc
    | isAny acc = acc
    | s > hi    = acc
    | skip s    = iter skip (s + 1) hi acc
    | otherwise = iter skip (s + 1) hi (union acc (op s))

-- | Declarative reference: union of @op s@ over every member @s@ of
-- @b@. /O(|b| · w / W)/, exponential in @w@, only suitable as a
-- correctness oracle, not for production.
foldShiftsSpec ::
  Integer  {- ^ mask -} ->
  Domain w {- ^ shift-amount domain -} ->
  (Integer -> Domain w) {- ^ per-amount transfer -} ->
  Domain w
foldShiftsSpec mask b op =
  Prelude.foldr (\s acc -> if member b s then union acc (op s) else acc)
                (conflict mask)
                [0 .. mask]

-- | Declarative reference variant of 'shlAbstract': for every member
-- @y@ of the shift-amount domain, compute the per-shift result and
-- union them all. Strictly slower; used to validate 'shlAbstract'.
shlAbstractSpec :: NatRepr w -> Domain w -> Domain w -> Domain w
shlAbstractSpec w a b = foldShiftsSpec (bvdMask a) b (\y -> shl w a y)

lshrAbstractSpec :: NatRepr w -> Domain w -> Domain w -> Domain w
lshrAbstractSpec w a b = foldShiftsSpec (bvdMask a) b (\y -> lshr w a y)

ashrAbstractSpec :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
ashrAbstractSpec w a b = foldShiftsSpec (bvdMask a) b (\y -> ashr w a y)

rolAbstractSpec :: NatRepr w -> Domain w -> Domain w -> Domain w
rolAbstractSpec w a b = foldShiftsSpec (bvdMask a) b (\y -> rol w a y)

rorAbstractSpec :: NatRepr w -> Domain w -> Domain w -> Domain w
rorAbstractSpec w a b = foldShiftsSpec (bvdMask a) b (\y -> ror w a y)

-- | The result of rotating @a@ by every position in @[0, w-1]@: each
-- output bit could come from any input bit, so the result is
-- determined by global properties of @a@. It's the all-zeros singleton
-- if @a = {0}@, the all-ones singleton if @a@ is the singleton mask,
-- and fully unknown otherwise.
fullCoverage :: Integer -> Integer -> Integer -> Domain w
fullCoverage mask aLo aHi = BVBitInterval mask outLo outHi
  where
  outHi = if aHi == 0 then 0 else mask
  outLo = if aLo == mask then mask else 0

-- | /O(w)/. Bitwise complement.
not :: Domain w -> Domain w
not (BVBitInterval mask alo ahi) =
  BVBitInterval mask (ahi `Bits.xor` mask) (alo `Bits.xor` mask)

-- | /O(w)/. Bitwise AND of two domains.
and :: Domain w -> Domain w -> Domain w
and (BVBitInterval mask alo ahi) (BVBitInterval _ blo bhi) =
  BVBitInterval mask (alo .&. blo) (ahi .&. bhi)

-- | /O(w)/. Bitwise OR of two domains.
or :: Domain w -> Domain w -> Domain w
or (BVBitInterval mask alo ahi) (BVBitInterval _ blo bhi) =
  BVBitInterval mask (alo .|. blo) (ahi .|. bhi)

-- | /O(w)/. Bitwise XOR of two domains.
xor :: Domain w -> Domain w -> Domain w
xor a@(BVBitInterval mask alo _) b@(BVBitInterval _ blo _) = BVBitInterval mask clo chi
  where
  c   = alo `Bits.xor` blo
  cu  = unknownBits a .|. unknownBits b
  chi = c  .|. cu
  clo = chi `Bits.xor` cu


---------------------------------------------------------------------------------------
-- Bounds and comparisons

-- | /O(1)/. Unsigned bounds for the domain. The low bit-pattern bound is
-- also the unsigned minimum, and the high bit-pattern bound is also the
-- unsigned maximum: setting unknown bits to 0 minimizes, setting them to
-- 1 maximizes.
ubounds :: Domain w -> (Integer, Integer)
ubounds = bitbounds

-- | /O(1)/. The mask with just the sign bit set: @bit (w - 1)@.
signBit :: (1 <= w) => NatRepr w -> Integer
signBit w = bit (widthVal w - 1)
{-# INLINE signBit #-}

-- | /O(w)/. Signed bounds for the domain.
sbounds :: (1 <= w) => NatRepr w -> Domain w -> (Integer, Integer)
sbounds w (BVBitInterval _ lo hi) = (toSigned w lo', toSigned w hi')
  where
  signbit = signBit w
  -- If the sign bit is known (lo and hi agree on it), the bit-pattern
  -- bounds are also the signed bounds. If the sign bit is unknown, the
  -- most-negative value sets the sign bit and clears all other unknowns,
  -- and the most-positive clears the sign bit and sets all other unknowns.
  (lo', hi')
    | (lo .&. signbit) == (hi .&. signbit) = (lo, hi)
    | otherwise = (lo .|. signbit, hi .&. complement signbit)

-- | /O(w)/. Check if all elements in one domain are unsigned-less-than all
-- elements in the other.
ult :: Domain w -> Domain w -> Maybe Bool
ult a b
  | ah < bl  = Just True
  | al >= bh = Just False
  | otherwise = Nothing
  where
  (al, ah) = ubounds a
  (bl, bh) = ubounds b

-- | /O(w)/. Check if all elements in one domain are signed-less-than all
-- elements in the other.
slt :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Maybe Bool
slt w a b
  | ah < bl  = Just True
  | al >= bh = Just False
  | otherwise = Nothing
  where
  (al, ah) = sbounds w a
  (bl, bh) = sbounds w b

---------------------------------------------------------------------------------------
-- Arithmetic

-- | Convert a domain into its tristate-number form.
toTnum :: Domain w -> Tnum
toTnum d@(BVBitInterval _ lo _) = Tnum.mk lo (unknownBits d)

-- | Convert a tristate-number back into a domain at the given @bvmask@.
fromTnum :: Integer -> Tnum -> Domain w
fromTnum mask t = BVBitInterval mask v (v .|. Tnum.tnumMask t)
  where v = Tnum.tnumValue t

-- | Internal helper: build a singleton domain when only the mask is known.
mkSingleton :: Integer -> Integer -> Domain w
mkSingleton mask x = BVBitInterval mask x' x'
  where x' = x .&. mask

-- | /O(w)/. Add two bitwise domains.
add :: Domain w -> Domain w -> Domain w
add a@(BVBitInterval mask _ _) b = fromTnum mask (Tnum.add mask (toTnum a) (toTnum b))

-- | /O(w)/. Two's complement negation: @negate a = not a + 1@.
negate :: Domain w -> Domain w
negate a = add (not a) (mkSingleton (bvdMask a) 1)

-- | /O(w)/. Subtract: @sub a b = add a (negate b)@.
sub :: Domain w -> Domain w -> Domain w
sub a b = add a (negate b)

-- | /O(w²)/. Multiply by a constant. Uses 'mulPrecise' since the
-- shift-and-add algorithm gives bit-level precision when one operand
-- is concrete.
scale :: Integer -> Domain w -> Domain w
scale k a = mulPrecise (mkSingleton (bvdMask a) k) a

-- | /O(w)/. Multiply two bitwise domains via interval and trailing-zero
-- analysis. Captures known leading bits (both 0s and 1s) derived from
-- @[aMin*bMin, aMax*bMax]@, plus known trailing zeros from the operands.
--
-- See 'Tnum.mul' for the algorithm. 'mulPrecise' is strictly more
-- precise; this is the cheaper alternative when middle-bit precision
-- doesn't matter.
mul :: Domain w -> Domain w -> Domain w
mul a@(BVBitInterval mask _ _) b =
  fromTnum mask (Tnum.mul mask (toTnum a) (toTnum b))

-- | /O(w²)/. Multiply two bitwise domains, combining the shift-and-add
-- tristate-number algorithm (BPF @tnum_mul@) with the interval and
-- trailing-zero analysis of 'mul'. Strictly at least as precise as 'mul'.
mulPrecise :: Domain w -> Domain w -> Domain w
mulPrecise a@(BVBitInterval mask _ _) b =
  intersection
    (fromTnum mask (Tnum.mulPrecise mask (toTnum a) (toTnum b)))
    (mul a b)

-- | /O(w)/. Unsigned division via interval analysis on the quotient bounds.
-- Assumes the divisor is nonzero.
--
-- Captures known leading bits (both 0s and 1s) derived from
-- @[aMin \`quot\` bMax, aMax \`quot\` bMin]@. When the divisor is a known
-- power of two, the result is exact (bit-level structure of the dividend
-- is preserved, e.g.\ @udiv (any w) (singleton w (2^k))@ has its top @k@
-- bits known zero). 'udivPrecise' is strictly more precise; this is the
-- cheaper alternative when middle-bit precision doesn't matter.
udiv :: Domain w -> Domain w -> Domain w
udiv a@(BVBitInterval mask _ _) b =
  fromTnum mask (Tnum.udiv mask (toTnum a) (toTnum b))

-- | /O(w)/. Unsigned remainder via leading-zero analysis. Assumes the divisor
-- is nonzero.
--
-- The result is bounded above by @min(aMax, bMax - 1)@; bits above that are
-- known zero. (The remainder's lower bound is trivially 0, so the same
-- interval-agreement analysis used in 'udiv' would not yield additional
-- leading bits here.) When the divisor is a known power of two,
-- @urem a (singleton w (2^k))@ is exactly the low @k@ bits of @a@.
urem :: Domain w -> Domain w -> Domain w
urem a@(BVBitInterval mask _ _) b =
  fromTnum mask (Tnum.urem mask (toTnum a) (toTnum b))

-- | /O(w²)/. Unsigned division combining abstract schoolbook long division
-- with the interval analysis of 'udiv'. Assumes the divisor is nonzero.
-- Strictly at least as precise as 'udiv'.
--
-- The result is the 'intersection' of 'udiv' (interval analysis on the
-- quotient bounds, plus an exact path for power-of-two divisors) and the
-- schoolbook result (which captures middle-bit structure that interval
-- analysis can't see, but joins through any undetermined comparison and so
-- loses on power-of-two divisors).
udivPrecise :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
udivPrecise w a b = intersection (fst (longDivision w a b)) (udiv a b)

-- | /O(w²)/. Unsigned remainder combining schoolbook long division with the
-- leading-zero analysis of 'urem'. Assumes the divisor is nonzero. Strictly
-- at least as precise as 'urem'.
uremPrecise :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
uremPrecise w a b = intersection (snd (longDivision w a b)) (urem a b)

-- | Abstract schoolbook long division: simultaneously computes the
-- quotient and remainder by walking the bits of the dividend from MSB to
-- LSB, maintaining a running partial remainder @r@ as a 'Domain'.
--
-- At each step, @r@ is shifted left and the next bit of the dividend is
-- shifted in. If @r >= b@ definitely, we subtract and set the corresponding
-- bit of the quotient. If @r < b@ definitely, we leave it. If the
-- comparison is undetermined, we union both possibilities into @r@ and
-- leave the quotient bit unknown.
longDivision :: forall w. (1 <= w) => NatRepr w -> Domain w -> Domain w -> (Domain w, Domain w)
longDivision w a b = go (widthVal w - 1) (singleton w 0) (singleton w 0)
  where
  -- Loop from bit (w-1) down to 0. @q@ accumulates the quotient,
  -- @r@ is the partial remainder.
  go :: Int -> Domain w -> Domain w -> (Domain w, Domain w)
  go i q r
    | i < 0     = (q, r)
    | otherwise =
        let r'        = injectBit r (testBit a (fromIntegral i))
            r'MinusB  = sub r' b
            (q'', r'')= case ult r' b of
              Just True  -> (q,                       r')
              Just False -> (setBitDom q i,           r'MinusB)
              Nothing    -> (unknownBitDom q i,       union r' r'MinusB)
        in go (i - 1) q'' r''

  -- Shift @r@ left by 1 and OR in a fresh low bit, whose value is
  -- determined by the @testBit@ result on the dividend.
  injectBit :: Domain w -> Maybe Bool -> Domain w
  injectBit r mb =
    let r1 = shl w r 1
        bit_dom = case mb of
          Just True  -> singleton w 1
          Just False -> singleton w 0
          Nothing    -> range w 0 1
    in or r1 bit_dom

  -- Set bit @i@ of a domain that is known to have bit @i@ = 0 going in
  -- (q starts at 0 and we only ever set bits, so this is safe).
  setBitDom :: Domain w -> Int -> Domain w
  setBitDom (BVBitInterval mask lo hi) i =
    BVBitInterval mask (Bits.setBit lo i) (Bits.setBit hi i)

  -- Mark bit @i@ of a domain as unknown.
  unknownBitDom :: Domain w -> Int -> Domain w
  unknownBitDom (BVBitInterval mask lo hi) i =
    BVBitInterval mask lo (Bits.setBit hi i)

-- | /O(w)/. Signed division (rounds toward zero). Assumes the divisor is
-- nonzero.
--
-- Implemented by splitting each operand on its sign bit into a non-negative
-- \"zero circle\" and a negative \"one circle\", applying 'udiv' to the
-- absolute values, fixing up the sign, and joining the resulting subcases.
sdiv :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
sdiv w = signedOp w udiv flipDiff
  where
  -- For sdiv, the result is negated iff the input signs differ.
  flipDiff sa sb d = if sa == sb then d else negate d

-- | /O(w)/. Signed remainder (sign of dividend). Assumes the divisor is
-- nonzero.
--
-- Implemented like 'sdiv', except the result takes the sign of the dividend
-- rather than the XOR of the input signs. Additionally, leading bits of the
-- result are refined using magnitude bounds: if the dividend is non-negative,
-- the result has leading zeros from both the dividend and divisor magnitude;
-- if negative and nonzero, it has leading ones similarly.
srem :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
srem w a b = meet base signMagnitudeBound
  where
  base = signedOp w urem flipByDividend a b
  flipByDividend SNeg _ d = negate d
  flipByDividend SNonneg _ d = d
  -- Sign/magnitude refinement (LLVM KnownBits::srem approach):
  -- (1) srem has the sign of the dividend (or is zero):
  --     x >= 0  ==>  x %$ y >= 0           (lemma_srem_nonneg_leading_zeros)
  --     x <  0  ==>  x %$ y <= 0           (lemma_srem_neg_sign)
  -- (2) |x %$ y| < |y|, so if |y| < 2^(w-k) then the result has at least
  --     k sign bits (lemma_srem_magnitude_bound). Similarly |x %$ y| <= |x|
  --     bounds the result by the dividend's magnitude.
  -- We take the max of both bounds to get the tightest leading-bit constraint.
  -- See @lemma_srem_*@ properties in bitsdomain.cry.
  mask = maxUnsigned w
  (alo, ahi) = bitbounds a
  (blo, bhi) = bitbounds b
  clzOf x = fromInteger (Arith.clz w x)
  -- countMinSignBits: minimum number of identical sign bits guaranteed in b.
  -- Non-negative: leading zeros come from hi (upper bound on set bits).
  -- Negative: leading ones come from lo (lower bound on set bits).
  bSignBits = case signOf w b of
    Just SNonneg -> clzOf bhi
    Just SNeg    -> clzOf (mask `Bits.xor` blo)
    Nothing      -> 1
  signMagnitudeBound = case signOf w a of
    Just SNonneg ->
      let leadZ = max (clzOf ahi) bSignBits
          hi' = mask `shiftR` leadZ
      in BVBitInterval mask 0 hi'
    Just SNeg
      | Prelude.not (member base 0) ->
          let leadO = clzOf (mask `Bits.xor` alo)
              leading = max leadO bSignBits
              lo' = complement (mask `shiftR` leading) .&. mask
          in BVBitInterval mask lo' mask
    _ -> BVBitInterval mask 0 mask

-- | Helper for signed div/rem: split each operand on its sign bit,
--   call the unsigned operation on the absolute values, fix up the
--   result's sign per the operation's rule, and union all subcases.
signedOp ::
  (1 <= w) =>
  NatRepr w ->
  (Domain w -> Domain w -> Domain w) {- ^ unsigned op on absolute values -} ->
  (Sign -> Sign -> Domain w -> Domain w) {- ^ result fix-up given signs -} ->
  Domain w -> Domain w ->
  Domain w
signedOp w uop fixup a b =
  Prelude.foldr1 union
    [ fixup sa sb (uop (absVal sa a') (absVal sb b'))
    | (sa, a') <- splitSign w a
    , (sb, b') <- splitSign w b
    ]
  where
  absVal SNonneg d = d
  absVal SNeg    d = negate d

data Sign = SNonneg | SNeg
  deriving Eq

-- | If the sign bit is known, return its value; otherwise 'Nothing'.
signOf :: (1 <= w) => NatRepr w -> Domain w -> Maybe Sign
signOf w d =
  case testBit d (fromIntegral (widthVal w - 1)) of
    Just True  -> Just SNeg
    Just False -> Just SNonneg
    Nothing    -> Nothing

-- | Split a domain on its sign bit, returning each restriction tagged with
--   its sign. If the sign bit is already known, returns a singleton list.
splitSign :: (1 <= w) => NatRepr w -> Domain w -> [(Sign, Domain w)]
splitSign w d@(BVBitInterval mask lo hi) =
  case signOf w d of
    Just s  -> [(s, d)]
    Nothing -> [ (SNonneg, BVBitInterval mask lo (hi `Bits.xor` signbit))
               , (SNeg,    BVBitInterval mask (lo .|. signbit) hi)
               ]
  where
  signbit = signBit w

-- | /O(w)/. Like 'udiv', but using the SMT-LIB @FixedSizeBitVectors@ theory's
-- div-by-zero semantics: @bvudiv s 0@ is the all-ones bitvector. See @Note
-- [SMT-LIB division]@ in "What4.Interface" for the design rationale.
udivSmtlib :: (1 <= w) => Domain w -> Domain w -> Domain w
udivSmtlib a b
  | Just 0 <- asSingleton b = mkSingleton mask mask
  | member b 0              = union (udiv a b) (mkSingleton mask mask)
  | otherwise               = udiv a b
  where
  mask = bvdMask a

-- | /O(w)/. Like 'urem', but using the SMT-LIB @FixedSizeBitVectors@ theory's
-- div-by-zero semantics: @bvurem s 0@ is the dividend itself (@s@). See @Note
-- [SMT-LIB division]@ in "What4.Interface" for the design rationale.
uremSmtlib :: (1 <= w) => Domain w -> Domain w -> Domain w
uremSmtlib a b
  | Just 0 <- asSingleton b = a
  | member b 0              = union (urem a b) a
  | otherwise               = urem a b

-- | /O(w)/. Like 'sdiv', but using the SMT-LIB QF_BV logic's div-by-zero
-- convention: @bvsdiv s 0@ is all-ones when @s@ is non-negative and @1@ when
-- @s@ is negative. See @Note [SMT-LIB division]@ in "What4.Interface" for the
-- design rationale.
sdivSmtlib :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
sdivSmtlib w a b
  | Just 0 <- asSingleton b = sdivByZero w a
  | member b 0              = union (sdiv w a b) (sdivByZero w a)
  | otherwise               = sdiv w a b

-- | The result of @bvsdiv s 0@ as a function of @s@'s sign: all-ones when @s >=
--   0@, @1@ when @s < 0@.
sdivByZero :: (1 <= w) => NatRepr w -> Domain w -> Domain w
sdivByZero w a =
  case signOf w a of
    Just SNonneg -> mkSingleton mask mask
    Just SNeg    -> mkSingleton mask 1
    Nothing      -> union (mkSingleton mask 1) (mkSingleton mask mask)
  where
  mask = bvdMask a

-- | /O(w)/. Like 'srem', but using the SMT-LIB QF_BV logic's div-by-zero
-- convention: @bvsrem s 0@ is the dividend itself (@s@). See @Note [SMT-LIB
-- division]@ in "What4.Interface" for the design rationale.
sremSmtlib :: (1 <= w) => NatRepr w -> Domain w -> Domain w -> Domain w
sremSmtlib w a b
  | Just 0 <- asSingleton b = a
  | member b 0              = union (srem w a b) a
  | otherwise               = srem w a b


---------------------------------------------------------------------------------------
-- Correctness properties

-- | Check that a domain is proper, and that
--   the given value is a member
pmember :: NatRepr n -> Domain n -> Integer -> Bool
pmember n a x = proper n a && member a x

correct_any :: (1 <= n) => NatRepr n -> Integer -> Property
correct_any n x = property (pmember n (any n) x)

correct_singleton :: (1 <= n) => NatRepr n -> Integer -> Integer -> Property
correct_singleton n x y = property (pmember n (singleton n x') y' == (x' == y'))
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_overlap :: Domain n -> Domain n -> Integer -> Property
correct_overlap a b x =
  member a x && member b x ==> domainsOverlap a b

-- | If 'domainsOverlap' returns 'True', then a shared witness exists
-- at the bitwise OR of the two low masks.
correct_overlap_inv :: Domain n -> Domain n -> Property
correct_overlap_inv a b =
  domainsOverlap a b ==> (member a witness && member b witness)
  where
    (alo, _) = bitbounds a
    (blo, _) = bitbounds b
    witness  = alo Bits..|. blo

correct_asSingleton :: (1 <= n) => NatRepr n -> Domain n -> Property
correct_asSingleton n a =
  case asSingleton a of
    Just x -> property (a == singleton n x)
    Nothing -> property True

correct_union :: (1 <= n) => NatRepr n -> Domain n -> Domain n -> Integer -> Property
correct_union n a b x =
  member a x || member b x ==> pmember n (union a b) x

correct_intersection :: (1 <= n) => Domain n -> Domain n -> Integer -> Property
correct_intersection a b x = -- NB, intersection might not be proper
  member a x && member b x ==> member (intersection a b) x

correct_join :: (1 <= n) => NatRepr n -> Domain n -> Domain n -> Integer -> Property
correct_join n a b x =
  member a x || member b x ==> pmember n (join a b) x

correct_meet :: (1 <= n) => Domain n -> Domain n -> Integer -> Property
correct_meet a b x =
  member a x && member b x ==> member (meet a b) x

-- | Precision of meet: if @x@ is a member of @meet a b@, then @x@ is
-- a member of both @a@ and @b@.
precise_meet :: (1 <= n) => Domain n -> Domain n -> Integer -> Property
precise_meet a b x =
  member (meet a b) x ==> (member a x && member b x)

correct_leq :: Domain n -> Domain n -> Integer -> Property
correct_leq a b x =
  (leq a b && member a x) ==> member b x

------------------------------------------------------------------------
-- Lattice law properties (semantic, i.e. same set of members)

join_commutative :: Domain n -> Domain n -> Integer -> Property
join_commutative a b x =
  property (member (join a b) x == member (join b a) x)

join_idempotent :: Domain n -> Integer -> Property
join_idempotent a x =
  property (member (join a a) x == member a x)

meet_commutative :: Domain n -> Domain n -> Integer -> Property
meet_commutative a b x =
  property (member (meet a b) x == member (meet b a) x)

meet_idempotent :: Domain n -> Integer -> Property
meet_idempotent a x =
  property (member (meet a a) x == member a x)

join_top :: NatRepr n -> Domain n -> Integer -> Property
join_top n a x =
  property (member (join a (top n)) x)

join_bottom :: NatRepr n -> Domain n -> Integer -> Property
join_bottom n a x =
  property (member (join a (bottom n)) x == member a x)

meet_top :: NatRepr n -> Domain n -> Integer -> Property
meet_top n a x =
  property (member (meet a (top n)) x == member a x)

meet_bottom :: NatRepr n -> Domain n -> Integer -> Property
meet_bottom n a x =
  property (Prelude.not (member (meet a (bottom n)) x))

leq_reflexive :: Domain n -> Property
leq_reflexive a = property (leq a a)

leq_transitive :: Domain n -> Domain n -> Domain n -> Property
leq_transitive a b c =
  (leq a b && leq b c) ==> leq a c

meet_lower_bound :: Domain n -> Domain n -> Property
meet_lower_bound a b = property (leq (meet a b) a)

join_upper_bound :: Domain n -> Domain n -> Property
join_upper_bound a b = property (leq a (join a b))

join_monotone :: Domain n -> Domain n -> Domain n -> Property
join_monotone a b c =
  leq a b ==> leq (join a c) (join b c)

meet_monotone :: Domain n -> Domain n -> Domain n -> Property
meet_monotone a b c =
  leq a b ==> leq (meet a c) (meet b c)

join_associative :: Domain n -> Domain n -> Domain n -> Integer -> Property
join_associative a b c x =
  property (member (join (join a b) c) x == member (join a (join b c)) x)

meet_associative :: Domain n -> Domain n -> Domain n -> Integer -> Property
meet_associative a b c x =
  property (member (meet (meet a b) c) x == member (meet a (meet b c)) x)

join_absorb :: Domain n -> Domain n -> Integer -> Property
join_absorb a b x =
  property (member (join a (meet a b)) x == member a x)

meet_absorb :: Domain n -> Domain n -> Integer -> Property
meet_absorb a b x =
  property (member (meet a (join a b)) x == member a x)

join_proper :: (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
join_proper n a b = property (proper n (join a b))

meet_proper :: (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
meet_proper n a b = property (proper n c || isBottom c)
  where c = meet a b

correct_zero_ext :: (1 <= w, w + 1 <= u) => NatRepr w -> Domain w -> NatRepr u -> Integer -> Property
correct_zero_ext w a u x = member a x' ==> pmember u (zext a u) x'
  where
  x' = toUnsigned w x

correct_sign_ext :: (1 <= w, w + 1 <= u) => NatRepr w -> Domain w -> NatRepr u -> Integer -> Property
correct_sign_ext w a u x = member a x' ==> pmember u (sext w a u) x'
  where
  x' = toSigned w x

correct_concat :: NatRepr m -> (Domain m,Integer) -> NatRepr n -> (Domain n,Integer) -> Property
correct_concat m (a,x) n (b,y) = member a x' ==> member b y' ==> pmember (addNat m n) (concat m a n b) z
  where
  x' = toUnsigned m x
  y' = toUnsigned n y
  z  = x' `shiftL` (widthVal n) .|. y'

correct_shrink :: NatRepr i -> NatRepr n -> (Domain (i + n), Integer) -> Property
correct_shrink i n (a,x) = member a x' ==> pmember n (shrink i a) (x' `shiftR` widthVal i)
  where
  x' = x .&. bvdMask a

correct_trunc :: (n <= w) => NatRepr n -> (Domain w, Integer) -> Property
correct_trunc n (a,x) = member a x' ==> pmember n (trunc n a) (toUnsigned n x')
  where
  x' = x .&. bvdMask a

correct_select :: (1 <= n, i + n <= w) =>
  NatRepr i -> NatRepr n -> (Domain w, Integer) -> Property
correct_select i n (a, x) = member a x ==> pmember n (select i n a) y
  where
  y = toUnsigned n ((x .&. bvdMask a) `shiftR` (widthVal i))

correct_eq :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_eq n (a,x) (b,y) =
  member a x ==> member b y ==>
    case eq a b of
      Just True  -> toUnsigned n x == toUnsigned n y
      Just False -> toUnsigned n x /= toUnsigned n y
      Nothing    -> True

correct_shl :: (1 <= n) => NatRepr n -> (Domain n,Integer) -> Integer -> Property
correct_shl n (a,x) y = member a x ==> pmember n (shl n a y) z
  where
  z = (toUnsigned n x) `shiftL` fromInteger (min (intValue n) y)

correct_lshr :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Integer -> Property
correct_lshr n (a,x) y = member a x ==> pmember n (lshr n a y) z
  where
  z = (toUnsigned n x) `shiftR` fromInteger (min (intValue n) y)

correct_ashr :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Integer -> Property
correct_ashr n (a,x) y = member a x ==> pmember n (ashr n a y) z
  where
  z = (toSigned n x) `shiftR` fromInteger (min (intValue n) y)

correct_rol :: (1 <= n) => NatRepr n -> (Domain n,Integer) -> Integer -> Property
correct_rol n (a,x) y = member a x ==> pmember n (rol n a y) (Arith.rotateLeft n x y)

correct_ror :: (1 <= n) => NatRepr n -> (Domain n,Integer) -> Integer -> Property
correct_ror n (a,x) y = member a x ==> pmember n (ror n a y) (Arith.rotateRight n x y)

correct_shlAbstract ::
  (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_shlAbstract n (a,x) (b,y) =
  member a x ==> member b y ==> pmember n (shlAbstract n a b) z
  where
  z = (toUnsigned n x) `shiftL` fromInteger (min (intValue n) (toUnsigned n y))

correct_lshrAbstract ::
  (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_lshrAbstract n (a,x) (b,y) =
  member a x ==> member b y ==> pmember n (lshrAbstract n a b) z
  where
  z = (toUnsigned n x) `shiftR` fromInteger (min (intValue n) (toUnsigned n y))

correct_ashrAbstract ::
  (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_ashrAbstract n (a,x) (b,y) =
  member a x ==> member b y ==> pmember n (ashrAbstract n a b) z
  where
  z = (toSigned n x) `shiftR` fromInteger (min (intValue n) (toUnsigned n y))

correct_rolAbstract ::
  (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_rolAbstract n (a,x) (b,y) =
  member a x ==> member b y ==>
    pmember n (rolAbstract n a b) (Arith.rotateLeft n x (toUnsigned n y))

correct_rorAbstract ::
  (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_rorAbstract n (a,x) (b,y) =
  member a x ==> member b y ==>
    pmember n (rorAbstract n a b) (Arith.rotateRight n x (toUnsigned n y))

-- | The optimized 'shlAbstract' produces the same domain as the
-- declarative 'shlAbstractSpec'. Together with 'correct_shlAbstract',
-- this proves 'shlAbstract' is point-wise optimal at this domain.
correct_equiv_shlAbstract ::
  (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
correct_equiv_shlAbstract n a b =
  property (shlAbstract n a b == shlAbstractSpec n a b)

correct_equiv_lshrAbstract ::
  (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
correct_equiv_lshrAbstract n a b =
  property (lshrAbstract n a b == lshrAbstractSpec n a b)

correct_equiv_ashrAbstract ::
  (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
correct_equiv_ashrAbstract n a b =
  property (ashrAbstract n a b == ashrAbstractSpec n a b)

correct_equiv_rolAbstract ::
  (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
correct_equiv_rolAbstract n a b =
  property (rolAbstract n a b == rolAbstractSpec n a b)

correct_equiv_rorAbstract ::
  (1 <= n) => NatRepr n -> Domain n -> Domain n -> Property
correct_equiv_rorAbstract n a b =
  property (rorAbstract n a b == rorAbstractSpec n a b)

correct_not :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Property
correct_not n (a,x) = member a x ==> pmember n (not a) (complement x)

correct_and :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_and n (a,x) (b,y) = member a x ==> member b y ==> pmember n (and a b) (x .&. y)

correct_or :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_or n (a,x) (b,y) = member a x ==> member b y ==> pmember n (or a b) (x .|. y)

correct_xor :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_xor n (a,x) (b,y) = member a x ==> member b y ==> pmember n (xor a b) (x `Bits.xor` y)

correct_testBit :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Natural -> Property
correct_testBit n (a,x) i =
  i < natValue n ==>
    case testBit a i of
      Just True  -> Bits.testBit x (fromIntegral i)
      Just False -> Prelude.not (Bits.testBit x (fromIntegral i))
      Nothing    -> True

correct_ubounds :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Property
correct_ubounds n (a,x) = member a x' ==> lo <= x' && x' <= hi
  where
  x' = toUnsigned n x
  (lo, hi) = ubounds a

correct_sbounds :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Property
correct_sbounds n (a,x) = member a x' ==> lo <= x' && x' <= hi
  where
  x' = toSigned n x
  (lo, hi) = sbounds n a

correct_ult :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_ult n (a,x) (b,y) =
  member a x ==> member b y ==>
    case ult a b of
      Just True  -> toUnsigned n x < toUnsigned n y
      Just False -> toUnsigned n x >= toUnsigned n y
      Nothing    -> True

correct_slt :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_slt n (a,x) (b,y) =
  member a x ==> member b y ==>
    case slt n a b of
      Just True  -> toSigned n x < toSigned n y
      Just False -> toSigned n x >= toSigned n y
      Nothing    -> True

correct_add :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_add n (a,x) (b,y) = member a x ==> member b y ==> pmember n (add a b) (x + y)

correct_sub :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_sub n (a,x) (b,y) = member a x ==> member b y ==> pmember n (sub a b) (x - y)

correct_neg :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> Property
correct_neg n (a,x) = member a x ==> pmember n (negate a) (Prelude.negate x)

correct_scale :: (1 <= n) => NatRepr n -> Integer -> (Domain n, Integer) -> Property
correct_scale n k (a,x) = member a x ==> pmember n (scale k' a) (k' * x)
  where
  k' = toSigned n k

correct_mul :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_mul n (a,x) (b,y) = member a x ==> member b y ==> pmember n (mul a b) (x * y)

correct_mulPrecise :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_mulPrecise n (a,x) (b,y) = member a x ==> member b y ==> pmember n (mulPrecise a b) (x * y)

correct_udiv :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_udiv n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (udiv a b) (x' `quot` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_urem :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_urem n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (urem a b) (x' `rem` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_sdiv :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_sdiv n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (sdiv n a b) (x' `quot` y')
  where
  x' = toSigned n x
  y' = toSigned n y

correct_srem :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_srem n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (srem n a b) (x' `rem` y')
  where
  x' = toSigned n x
  y' = toSigned n y

correct_udivPrecise :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_udivPrecise n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (udivPrecise n a b) (x' `quot` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_uremPrecise :: (1 <= n) => NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_uremPrecise n (a,x) (b,y) =
  member a x ==> member b y ==> y' /= 0 ==> pmember n (uremPrecise n a b) (x' `rem` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y



correct_udivSmtlib ::
  (1 <= n) =>
  NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_udivSmtlib n (a,x) (b,y) =
  member a x' ==> member b y' ==>
    pmember n (udivSmtlib a b)
      (if y' == 0 then maxUnsigned n else x' `quot` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_uremSmtlib ::
  (1 <= n) =>
  NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_uremSmtlib n (a,x) (b,y) =
  member a x' ==> member b y' ==>
    pmember n (uremSmtlib a b) (if y' == 0 then x' else x' `rem` y')
  where
  x' = toUnsigned n x
  y' = toUnsigned n y

correct_sdivSmtlib ::
  (1 <= n) =>
  NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_sdivSmtlib n (a,x) (b,y) =
  member a x ==> member b y ==>
    pmember n (sdivSmtlib n a b) result
  where
  x' = toSigned n x
  y' = toSigned n y
  result
    | y' /= 0   = x' `quot` y'
    | x' >= 0   = maxUnsigned n
    | otherwise = 1

correct_sremSmtlib ::
  (1 <= n) =>
  NatRepr n -> (Domain n, Integer) -> (Domain n, Integer) -> Property
correct_sremSmtlib n (a,x) (b,y) =
  member a x ==> member b y ==>
    pmember n (sremSmtlib n a b) (if y' == 0 then x' else x' `rem` y')
  where
  x' = toSigned n x
  y' = toSigned n y