/*

This file gives Cryptol implementations for transferring between
the various bitvector domain representations and proofs of the
correctness of these operations.
*/

module bvdomain where

import arithdomain as A
import bitsdomain as B
import xordomain as X


// Precondition `x <= mask`.  Find the (arithmetically) smallest
//  `z` above `x` which is bitwise above `mask`.  In other words
// find the smallest `z` such that `x <= z` and `mask || z == z`.

bitwise_round_above : {n} (fin n, n >= 1) => [n] -> [n] -> [n]
bitwise_round_above x mask = (x && ~q) ^ (mask && q)
  where
  q = A::fillright_alt ((x || mask) ^ x)

bra_correct1 : {n} (fin n, n>=1) => [n] -> [n] -> Bit
bra_correct1 x mask = mask <= x ==> (x <= q /\ B::bitle mask q)
  where
  q = bitwise_round_above x mask

bra_correct2 : {n} (fin n, n>=1) => [n] -> [n] -> [n] -> Bit
bra_correct2 x mask q' = (x <= q' /\ B::bitle mask q') ==> q <= q'
  where
  q = bitwise_round_above x mask

property bra1 = bra_correct1`{64}
property bra2 = bra_correct2`{64}


// Precondition `lomask <= x <= himask` and `lomask || himask == himask`.
// Find the (arithmetically) smallest `z` above `x` which is bitwise between
// `lomask` and `himask`.  In otherwords, find the smallest `z` such that
//  `x <= z` and `lomask || z = z` and `z || himask == himask`.
bitwise_round_between : {n} (fin n, n >= 1) => [n] -> [n] -> [n] -> [n]
bitwise_round_between x lomask himask = if r == 0 then loup else final
  // Read these steps from the bottom up...
  where

  // Finally mask out the low bits and only set those requried by the lomask
  final = (upper && ~lowbits) || lomask

  // add the correcting bit and mask out any extraneous bits set in
  // the previous step
  upper = (z + highbit) && himask

  // set ourselves up so that when we add the high bit to correct,
  // the carry will ripple until it finds a bit position that we
  // are allowed to set.
  z = loup || ~himask

  // isolate just the highest incorrect bit
  highbit = rmask ^ lowbits

  // A mask for all the bits lower than the high bit of r
  lowbits = rmask >> 1

  // set all the bits to the right of the highest incorrect bit
  rmask = A::fillright_alt r

  // now compute all the bits that are set that are not allowed
  // to be set according to the himask
  r = loup && ~himask

  // first, round up to the lomask
  loup = bitwise_round_above x lomask


brb_correct1 : {n} (fin n, n>=1) => [n] -> [n] -> [n] -> Bit
brb_correct1 x lomask himask =
    (B::bitle lomask himask /\ lomask <= x /\ x <= himask) ==>
    (x <= q /\ B::bitle lomask q /\ B::bitle q himask)

  where
  q = bitwise_round_between x lomask himask

brb_correct2 : {n} (fin n, n>=1) => [n] -> [n] -> [n] -> [n] -> Bit
brb_correct2 x lomask himask q' = (x <= q' /\ B::bitle lomask q' /\ B::bitle q' himask) ==> q <= q'
  where
  q = bitwise_round_between x lomask himask

property brb1 = brb_correct1`{64}
property brb2 = brb_correct2`{64}

// Interesting fact about arithmetic domains: the low values of the two domains
// represent overlap candidates.  If neither low value is contained in the other domain,
// then they do not overlap.
arith_overlap_candidates : {n} (fin n, n >= 1) => A::Dom n -> A::Dom n -> [n] -> Bit
arith_overlap_candidates a b x =
  A::mem a x ==>
  A::mem b x ==>
  ((A::mem a b.lo /\ A::mem b b.lo) \/
   (A::mem a a.lo /\ A::mem b a.lo))

// Bitwise domains, if they overlap, must overlap in some specific points.  The bitwise
// union of the low bounds is one.
bitwise_overlap_candidates : {n} (fin n, n >= 1) => B::Dom n -> B::Dom n -> [n] -> Bit
bitwise_overlap_candidates a b x =
  B::mem a x ==>
  B::mem b x ==>
  (B::mem a witness /\ B::mem b witness)

 where
 witness = a.lomask || b.lomask

// If mixed domains have some common value, then they must definintely overlap at one
// of the following three listed candidate points.
mixed_overlap_candidates : {n} (fin n, n >= 1) => A::Dom n -> B::Dom n -> [n] -> Bit
mixed_overlap_candidates a b x =
  A::mem a x ==>
  B::mem b x ==>
  (A::mem a b.lomask /\ B::mem b b.lomask) \/
  (A::mem a b.himask /\ B::mem b b.himask) \/
  (A::mem a next     /\ B::mem b next)

 where
 next = bitwise_round_between a.lo b.lomask b.himask


// A mixed domain overlap test.  It relies on testing special candidate overlap values.
//
// If none of the overlap candidates are found in both domains, then the domains do not overlap.
// On the other hand, if any canadiate is in both domains, it is a constructive witness of
// overlap.
mixed_domain_overlap : {n} (fin n, n >= 1) => A::Dom n -> B::Dom n -> Bit
mixed_domain_overlap a b =
  A::mem a b.lomask \/ A::mem a b.himask \/ A::mem a (bitwise_round_between a.lo b.lomask b.himask)

// If mixed domains have a common element, the overlap test will be true.
correct_mixed_domain_overlap : {n} (fin n, n >= 1) => A::Dom n -> B::Dom n -> [n] -> Bit
correct_mixed_domain_overlap a b x =
  A::mem a x ==>
  B::mem b x ==>
  mixed_domain_overlap a b

// If the overlap test is true, then we can find some element they share in common,
// provided the bitwise domain is nonempty.
correct_mixed_domain_overlap_inv : {n} (fin n, n >= 1) => A::Dom n -> B::Dom n -> Bit
correct_mixed_domain_overlap_inv a b =
  B::nonempty b ==> mixed_domain_overlap a b ==> (A::mem a witness /\ B::mem b witness)

 where
 witness = if A::mem a b.lomask then b.lomask else
           if A::mem a b.himask then b.himask else
           bitwise_round_between a.lo b.lomask b.himask

property mx = correct_mixed_domain_overlap`{64}
property mx_inv = correct_mixed_domain_overlap_inv`{64}


// Operations that transfer between the domains

arithToBitDom : {n} (fin n, n >= 1) => A::Dom n -> B::Dom n
arithToBitDom a = { lomask = lo, himask = hi }
  where
  u  = A::unknowns a
  hi = a.lo || u
  lo = hi ^ u

bitToArithDom : {n} (fin n) => B::Dom n -> A::Dom n
bitToArithDom b = A::range b.lomask b.himask

bitToXorDom : {n} (fin n) => B::Dom n -> X::Dom n
bitToXorDom b = { val = b.himask, unknown = b.lomask ^ b.himask }

xorToBitDom : {n} (fin n) => X::Dom n -> B::Dom n
xorToBitDom x = { lomask = x.val ^ x.unknown, himask = x.val }

arithToXorDom : {n} (fin n, n >= 1) => A::Dom n -> X::Dom n
arithToXorDom a = { val = a.lo || u, unknown = u }
  where
  u = A::unknowns a

// A small collection of operations that start in one
// domain and end in the other

popcount : {n} (fin n, n>=1) => [n] -> [n]
popcount bs = sum [ zero#[b] | b <- bs ]

countLeadingZeros : {n} (fin n, n>=1) => [n] -> [n]
countLeadingZeros x = loop 0
 where
 loop n =
   if n >= length x then
     length x
   else
     if x@n then n else loop (n+1)

countTrailingZeros : {n} (fin n, n>=1) => [n] -> [n]
countTrailingZeros xs = countLeadingZeros (reverse xs)



popcnt : {n} (fin n, n>=1) => B::Dom n -> A::Dom n
popcnt b = A::range lo hi
  where
  lo = popcount b.lomask
  hi = popcount b.himask

clz : {n} (fin n, n>=1) => B::Dom n -> A::Dom n
clz b = A::range lo hi
 where
 lo = countLeadingZeros b.himask
 hi = countLeadingZeros b.lomask

ctz : {n} (fin n, n>=1) => B::Dom n -> A::Dom n
ctz b = A::range lo hi
 where
 lo = countTrailingZeros b.himask
 hi = countTrailingZeros b.lomask


//////////////////////////////////////////////////////////////
// Correctness properties

correct_arithToBitDom : {n} (fin n, n >= 1) => A::Dom n -> [n] -> Bit
correct_arithToBitDom a x =
  A::mem a x ==> B::mem (arithToBitDom a) x

correct_bitToArithDom : {n} (fin n) => B::Dom n -> [n] -> Bit
correct_bitToArithDom b x =
  B::mem b x ==> A::mem (bitToArithDom b) x

correct_bitToXorDom : {n} (fin n) => B::Dom n -> [n] -> Bit
correct_bitToXorDom b x =
  B::mem b x == X::mem (bitToXorDom b) x

correct_xorToBitDom : {n} (fin n) => X::Dom n -> [n] -> Bit
correct_xorToBitDom b x =
  X::mem b x == B::mem (xorToBitDom b) x

correct_arithToXorDom : {n} (fin n, n >= 1) => A::Dom n -> [n] -> Bit
correct_arithToXorDom a x =
  A::mem a x ==> X::mem (arithToXorDom a) x

property t1 = correct_arithToBitDom`{16}
property t2 = correct_bitToArithDom`{16}
property t3 = correct_bitToXorDom`{16}
property t4 = correct_xorToBitDom`{16}
property t5 = correct_arithToXorDom`{16}

correct_popcnt : {n} (fin n, n>=1) => B::Dom n -> [n] -> Bit
correct_popcnt a x =
  B::mem a x ==> A::mem (popcnt a) (popcount x)

correct_clz : {n} (fin n, n>=1) => B::Dom n -> [n] -> Bit
correct_clz a x =
  B::mem a x ==> A::mem (clz a) (countLeadingZeros x)

correct_ctz : {n} (fin n, n>=1) => B::Dom n -> [n] -> Bit
correct_ctz a x =
  B::mem a x ==> A::mem (ctz a) (countTrailingZeros x)

property w1 = correct_popcnt`{16}
property w2 = correct_clz`{16}
property w3 = correct_ctz`{16}

////////////////////////////////////////////////////////////////
// Proofs that the XOR domain is really just an alternate way
// to compute the same thing as the bitsdomain operations.
// For "band" this requires the input domains to be nonempty,
// which should be the case for all actual values of interest.

equiv_bxor : {n} (fin n) => B::Dom n -> B::Dom n -> Bit
equiv_bxor a b =
  B::bxor a b == xorToBitDom (X::bxor (bitToXorDom a) (bitToXorDom b))

equiv_band : {n} (fin n) => B::Dom n -> B::Dom n -> Bit
equiv_band a b =
  B::nonempty a /\ B::nonempty b ==>
  B::band a b == xorToBitDom (X::band (bitToXorDom a) (bitToXorDom b))

equiv_band_scalar : {n} (fin n) => B::Dom n -> [n] -> Bit
equiv_band_scalar a x =
  B::band a (B::singleton x) == xorToBitDom (X::band_scalar (bitToXorDom a) x)


property e1 = equiv_bxor`{16}
property e2 = equiv_band`{16}
property e3 = equiv_band_scalar`{16}
