(* Known "bugs":  multiplying two int64s does not work (throws an exception in
   IRUtil.typereg_of_int because it tries to create a TypeReg_ term for a 
   128-bit quantity, which does not exist within the IR). However, this is not
   supported in the IR anyway, and typechecking ought to prevent this. *)
(* To do:  Binop(_,ULT|ULE|SLT|SLE,_) *)
open IR

exception BitwiseAINewInterfaceExn of string

let failwith s = raise (BitwiseAINewInterfaceExn(s))

(* A bit is either known to be zero or one, or it is unknown (half) *)
type abstract_bit =
| AbsZero
| AbsOne
| AbsHalf

(* Debugging functions *)
let string_of_abstract_bit = function
| AbsZero -> "0"
| AbsOne  -> "1"
| AbsHalf -> "1/2"

let string_of_abstract_bitvector arr = 
  let len = Array.length arr in
  let descr = Array.fold_left (fun acc a -> string_of_abstract_bit a^" "^acc) "" arr in
  "BV("^string_of_int len^"): "^descr

(* This data type mimics a variable in the main IR definition; it's either a
   bitvector term or a memory. *)
type absvalue = 
| AbsValue of abstract_bit array 
| AbsMem of (int64 -> abstract_bit array)

(* Convert an abstract bit into an integer, so we can index them into tables *)
let int_of_abstract_bit = function
| AbsZero -> 0
| AbsOne  -> 2
| AbsHalf -> 1

(* Create a bitvector of size IRUtil.bits s based on the contents of a 
   Const(i,s) *)
let abstract_bv_of_constant i s =
  let make_mask i = Int64.shift_left Int64.one i in
  let se = IRUtil.bits s in
  let arr = Array.make se (AbsZero) in
  for j = 0 to (se-1) do
    let store = 
      match (Int64.logand i (make_mask j) <> Int64.zero) with
      | true  -> AbsOne
      | false -> AbsZero
    in arr.(j) <- store 
  done;
  arr

(* Partially applied wrapper function for abstract bitwise binops.  Returns
   a function that expects two equally-sized abstract bitvectors. *)
let abstract_bitwise_op table = 
  ArrayUtil.map2 
    (AbsZero) 
    (fun x y -> table.(int_of_abstract_bit x).(int_of_abstract_bit y)) 

(* Abstract version of xor.  Works like you'd expect. *)
let abstract_xor_table = 

            (* 0 *)  (*1/2*)  (* 1 *)
(* 0 *)[|[| AbsZero; AbsHalf; AbsOne  |];
(*1/2*)  [| AbsHalf; AbsHalf; AbsHalf |];
(* 1 *)  [| AbsOne;  AbsHalf; AbsZero |]|]

let abstract_xor = abstract_bitwise_op abstract_xor_table

(* Abstract version of or.  Works like you'd expect. *)
let abstract_or_table = 

            (* 0 *)  (*1/2*)  (* 1 *)
(* 0 *)[|[| AbsZero; AbsHalf; AbsOne  |];
(*1/2*)  [| AbsHalf; AbsHalf; AbsOne  |];
(* 1 *)  [| AbsOne;  AbsOne ; AbsOne  |]|]

let abstract_or = abstract_bitwise_op abstract_or_table

(* Abstract version of and.  Works like you'd expect. *)
let abstract_and_table = 

            (* 0 *)  (*1/2*)  (* 1 *)
(* 0 *)[|[| AbsZero; AbsZero; AbsZero |];
(*1/2*)  [| AbsZero; AbsHalf; AbsHalf |];
(* 1 *)  [| AbsZero; AbsHalf; AbsOne  |]|]

let abstract_and = abstract_bitwise_op abstract_and_table

(* Abstract version of not.  Works like you'd expect. *)
let abstract_not_table =
   (* 0 *)  (*1/2*)  (* 1 *)
[| AbsOne ; AbsHalf; AbsZero |]

let abstract_not = Array.map (fun i -> abstract_not_table.(int_of_abstract_bit i))

(* Common function used in abstract shift operators. *)
let abstract_shift init f lhs_arr i =
  let len = Array.length lhs_arr in
  let newarr = Array.make len init in
  for j = 0 to ((len - i) - 1) do f newarr lhs_arr j i done;
  newarr

(* Abstract versions of shift left/right/arithmetic right. *)
let abstract_shl =
  let shl newarr lhs_arr j i =
    let len = Array.length lhs_arr in
    newarr.((len-1)-j) <- lhs_arr.((len-1) - (j+i))
  in abstract_shift (AbsZero) shl
 
let shr newarr lhs_arr j i = newarr.(j) <- lhs_arr.(i+j)
let abstract_shr = abstract_shift (AbsZero) shr
let abstract_sar lhs_arr i = abstract_shift (lhs_arr.(Array.length lhs_arr-1)) shr lhs_arr i

(* Abstract addition and subtraction.  Addition works with the notion of an 
   "abstract carry flag"; for example, if two corresponding bits are 0 and 1, 
   respectively, and the abstract carry flag is 1/2, then:
   if the abstract carry flag is 0, we get (1,0)
   if the abstract carry flag is 1, we get (0,1)
   Therefore both the result and the abstract carry flag are variable, so
   the return value is (1/2,1/2).  Apply this process sequentially over the
   entirety of the bitvectors. 
   
   Subtraction is defined as being the same thing as addition, with the RHS
   NOT-ted, and the initial carry flag being set to one instead of zero.  This
   comes from Digital Design:  Principles and Practices, Third Edition, by 
   John F. Wakerly.  Testing indicates that it works for non-half bits. *)
let abstract_add_or_sub initial_carry f_rhs lhs_arr rhs_arr =
  let abstract_add_bits ab1 ab2 abc =
    match ab1,ab2,abc with
    | AbsZero, AbsZero, AbsZero -> (AbsZero,AbsZero)
    | AbsZero, AbsZero, AbsOne  -> (AbsOne,AbsZero)
    | AbsZero, AbsZero, AbsHalf -> (AbsHalf,AbsZero)
    | AbsZero, AbsOne,  AbsZero -> (AbsOne,AbsZero)
    | AbsZero, AbsOne,  AbsOne  -> (AbsZero,AbsOne)
    | AbsZero, AbsOne,  AbsHalf -> (AbsHalf,AbsHalf)
    | AbsZero, AbsHalf, AbsZero -> (AbsHalf,AbsZero)
    | AbsZero, AbsHalf, AbsOne  -> (AbsHalf,AbsHalf)
    | AbsZero, AbsHalf, AbsHalf -> (AbsHalf,AbsHalf)
    | AbsOne,  AbsZero, AbsZero -> (AbsOne,AbsZero)
    | AbsOne,  AbsZero, AbsOne  -> (AbsZero,AbsOne)
    | AbsOne,  AbsZero, AbsHalf -> (AbsHalf,AbsHalf)
    | AbsOne,  AbsOne,  AbsZero -> (AbsZero,AbsOne)
    | AbsOne,  AbsOne,  AbsOne  -> (AbsOne,AbsOne)
    | AbsOne,  AbsOne,  AbsHalf -> (AbsHalf,AbsOne)
    | AbsOne,  AbsHalf, AbsZero -> (AbsHalf,AbsHalf)
    | AbsOne,  AbsHalf, AbsOne  -> (AbsHalf,AbsOne)
    | AbsOne,  AbsHalf, AbsHalf -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsZero, AbsZero -> (AbsHalf,AbsZero)
    | AbsHalf, AbsZero, AbsOne  -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsZero, AbsHalf -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsOne,  AbsZero -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsOne,  AbsOne  -> (AbsHalf,AbsOne)
    | AbsHalf, AbsOne,  AbsHalf -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsHalf, AbsZero -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsHalf, AbsOne  -> (AbsHalf,AbsHalf)
    | AbsHalf, AbsHalf, AbsHalf -> (AbsHalf,AbsHalf)
  in
  let rhs_arr = f_rhs rhs_arr in
  let lhs_size,rhs_size = Array.length lhs_arr,Array.length lhs_arr in
  if lhs_size <> rhs_size then failwith "typechecking prevents this jlkasglkj";
  
  let out_arr = Array.make lhs_size (AbsZero) in
  let rec aux j c =
    if j < lhs_size 
    then
     (let ar,ac = abstract_add_bits lhs_arr.(j) rhs_arr.(j) c in
      out_arr.(j) <- ar;
      aux (j+1) ac)
  in aux 0 initial_carry;
  out_arr

let abstract_add = abstract_add_or_sub (AbsZero) (fun x -> x)
let abstract_sub = abstract_add_or_sub (AbsOne)  abstract_not

(* Used in multiplication.  A multiplication can be expressed as a series
   of additions of shifted values.  If the corresponding bit in the RHS of
   the mul is a zero, we add a partial product of zeroes.  If the corresponding
   bit in the RHS is one, we add a partial product of the original LHS term
   widened to twice its size and shifted left by the weight of the one bit.
   If the bit is half, then we need to turn every one in the partial product
   into a half (since they will be either 1 or 0), and leave the zero bits 
   alone (since they will always be 0). *)
let replace_ones_with_halves lhs_arr =
  let len = Array.length lhs_arr in
  let newarr = Array.make len (AbsZero) in
  for j = 0 to (len-1) do 
    let old = lhs_arr.(j) in
    newarr.(j) <- match old with
    | AbsZero | AbsHalf -> old
    | AbsOne -> AbsHalf
  done;
  newarr

(* Shift a value into a larger quantity; equivalent to abstract interpreting
   Binop(Cast(Unsigned,size,lhs),Shl,i).  Used in multiplication. *)
let abstract_widening_shl size lhs_arr i =
  let len = Array.length lhs_arr in
  let newarr = Array.make size (AbsZero) in
  for j = 0 to len-1 do newarr.(j+i) <- lhs_arr.(j) done;
  newarr

let abstract_umul lhs_arr rhs_arr = 
  let lhs_size,rhs_size = Array.length lhs_arr,Array.length rhs_arr in
  if lhs_size<>rhs_size then failwith "typechecking prevents this jklasgjlk";
  let out_size = lhs_size * 2 in
  let rec aux j out_arr = 
    if j < lhs_size then
     (match rhs_arr.(j) with
      | AbsZero -> aux (j+1) out_arr
      | AbsOne  -> aux (j+1) (abstract_add out_arr (abstract_widening_shl out_size lhs_arr j))
      | AbsHalf -> aux (j+1) (abstract_add out_arr (replace_ones_with_halves (abstract_widening_shl out_size lhs_arr j))))
    else out_arr
  in aux 0 (Array.make out_size (AbsZero))

(* Abstract EQ/NE operators.  If two corresponding bits differ, then in
   the EQ case, the whole bitvectors can not match (so we return AbsZero), 
   and in the NE case, they definitely are not equal (so we return AbsOne). 
   If the bitvectors are non-half and identical, for EQ we return AbsOne, 
   and for NE we return AbsZero.  If any bits are half, and the first rule
   above does not apply, then we cannot make the determination and so we 
   return AbsHalf. *)
let abstract_equality_compare mismatch final a1 a2 =
  let lhs_size = Array.length a1 in
  let rhs_size = Array.length a2 in
  if lhs_size <> rhs_size then failwith ("typechecking prevents this njmaslgnjk "^string_of_int lhs_size^" "^string_of_int rhs_size);
  let not_constant = ref false in
  let rec aux j =
    if j < lhs_size 
    then
   (match a1.(j),a2.(j) with
    | AbsHalf,_ | _,AbsHalf -> not_constant := true; aux (j+1)
    | AbsOne,AbsZero | AbsZero,AbsOne -> mismatch
    | _ -> aux (j+1))
    else
      if !not_constant then AbsHalf else final
  in ([|aux 0|])
  
let abstract_eq = abstract_equality_compare (AbsZero) (AbsOne)
let abstract_ne = abstract_equality_compare (AbsOne)  (AbsZero)

(* Neg(x) is the same thing as (~x)+1, so we implement this operation in terms 
   of those operations. *)
let abstract_neg a = 
  let ares = abstract_not a in
  abstract_add ares (abstract_bv_of_constant 1L (IRUtil.typereg_of_int (Array.length ares)))

(* Make a bitvector of the proscribed size with all half bits. *)
let make_top s = Array.make (IRUtil.bits s) (AbsHalf)

(* Since the abstract interpreter passes around absvalue terms, it becomes
   cumbersome to continually pattern match over them, extract the innards of
   the AbsValues, perform operations, and then wrap the result in an AbsValue
   constructor again.  Therefore, we abstract this functionality out into this
   little function... *)
let value_wrapped_abstract_binop f v1 v2 = match (v1,v2) with
| AbsValue(v1), AbsValue(v2) -> AbsValue(f v1 v2)
| _,_ -> failwith "typechecking prevents this nmaSMFG" 

(* ... which we then partially apply abstract operators against. *)
let abstract_and  = value_wrapped_abstract_binop abstract_and
let abstract_xor  = value_wrapped_abstract_binop abstract_xor
let abstract_or   = value_wrapped_abstract_binop abstract_or
let abstract_add  = value_wrapped_abstract_binop abstract_add
let abstract_sub  = value_wrapped_abstract_binop abstract_sub
let abstract_umul = value_wrapped_abstract_binop abstract_umul
let abstract_eq   = value_wrapped_abstract_binop abstract_eq
let abstract_ne   = value_wrapped_abstract_binop abstract_ne

(* Same deal, but for shift operators instead of binary ones. *)
let value_wrapped_abstract_shift f v1 i = match v1 with
| AbsValue(v1) -> AbsValue(f v1 i)
| _ -> failwith "typechecking prevents this kljasdlgkj"

let abstract_shl = value_wrapped_abstract_shift abstract_shl
let abstract_shr = value_wrapped_abstract_shift abstract_shr
let abstract_sar = value_wrapped_abstract_shift abstract_sar

(* Same deal, except for unary operators. *)
let value_wrapped_abstract_unop f v1 = match v1 with
| AbsValue(v1) -> AbsValue(f v1)
| _ -> failwith "typechecking prevents this kljasjklf"

let abstract_not = value_wrapped_abstract_unop abstract_not
let abstract_neg = value_wrapped_abstract_unop abstract_neg

(* More sugar for respectively extracting abstract_bit arrays and memories 
   from absvalue terms. *)
let unwrap_abstract_value = function
| AbsValue(v1) -> v1
| _ -> failwith "typechecking prevents this lnmaslnkmg"

let unwrap_abstract_memory = function
| AbsMem(f) -> f
| _ -> failwith "typechecking prevents this jklDGJKL"

(* Take an abstract_bit array and turn it into a constant, if possible.  Return
   an option type with the constant inside of it, or None. *)
let make_constant_opt bv =
  let size = Array.length bv in
  let s = IRUtil.typereg_of_int size in
  let rec aux c j = 
    if j < size 
    then
      match c with 
      | None -> c
      | Some(c) ->
        let res =
         (let bit_opt = 
          match bv.(j) with
          | AbsOne  -> Some(1L)
          | AbsZero -> Some(0L)
          | AbsHalf -> None
          in
          match bit_opt with
          | Some(i) -> Some(Int64.logor c (Int64.shift_left i j))
          | None -> None)
        in aux res (j+1)
    else c
  in 
  match (aux (Some(Int64.zero)) 0) with
  | Some(c) -> Some(Const(c,s))
  | None -> None
   
(* We model memory as a function that takes a non-symbolic, int64 address and 
   returns the 8-bit contents of what's stored there.  Loads against addresses 
   that have not yet been written to return all half bits.  Also, there are two 
   convenience functions for performing loads and stores. *)
let default_abstract_memory = (fun _ -> make_top TypeReg_8)
let abstract_load v mem = mem v
let abstract_store v addr mem = (fun a -> if a = addr then v else mem a)

(* Given an IR var type (memory or variable), return the default abstract value
   (i.e., top for the given size, or an empty memory). *)
let default_abstract_value = function
| Mem(_,_,_) -> AbsMem(default_abstract_memory)
| Variable(_,s) -> AbsValue(make_top s)

(* Use this hashtable to store abstract variable bindings, i.e. v from
   Assign(v,_) instructions and Let(v,_,_) expressions.  Also, convenience
   functions for fetching and storing bindings. *)
let varctx = ref (Hashtbl.create 50)
let lookup_varctx v = try Hashtbl.find !varctx v with Not_found -> default_abstract_value v
let assign_varctx v e = Hashtbl.add !varctx v e

(* This is where all the action is.  This function takes an expression, and 
   returns a pair of (its abstract evaluation, its transformed concrete 
   representation).  As an example of the latter, suppose that as a result of
   the abstract interpretation, we discover that some subexpression is constant.
   We therefore replace the entire subexpression with that constant.  *)
let rec bitwise_abstract_interpret expr = match expr with
| Binop(e1,((And|Or|Xor|Add|Sub|Mul|EQ|NE) as o) ,e2) -> 
  let (a1,te1) = bitwise_abstract_interpret e1 in
  let (a2,te2) = bitwise_abstract_interpret e2 in
  let ares =
    match o with
    | And  -> abstract_and  a1 a2
    | Or   -> abstract_or   a1 a2
    | Xor  -> abstract_xor  a1 a2
    | Add  -> abstract_add  a1 a2
    | Sub  -> abstract_sub  a1 a2
    | Mul  -> abstract_umul a1 a2
    | EQ   -> abstract_eq   a1 a2
    | NE   -> abstract_ne   a1 a2
    | _    -> failwith "impossible"
  in 
  (* If the abstract result of the binop is a constant, replace the whole binop
     with the constant.  Otherwise, return a binop where the left and right 
     subtrees may be simplified. *)
  let cres = match (make_constant_opt (unwrap_abstract_value ares)) with
    | Some(c) -> c
    | None -> Binop(te1, o, te2)
  in (ares,cres)

(* Handle shifts with constant values.  Revisit this for non-constant values? *)
| Binop(e1,((Shl|Shr|Sar) as o),(Const(i,s) as c)) ->
  let (a1,te1) = bitwise_abstract_interpret e1 in
  let i = Int64.to_int i in
  let ares =
    match o with
    | Shl -> abstract_shl a1 i
    | Shr -> abstract_shr a1 i
    | Sar -> abstract_sar a1 i
    | _   -> failwith "impossible"
  in 
  (* Replace binop with constant, if applicable, or replace LHS of binop with
     a possibly transformed expression. *)
  let cres = match (make_constant_opt (unwrap_abstract_value ares)) with
  | Some(c) -> c
  | None -> Binop(te1, o, c)
  in (ares,cres)

(* Handle shifts with "partially constant" values:  e.g. consider
   mov cl, 5
   shr eax, cl
   This gets broken down in the IR to a partially constant assignment into e.g.
   ECX = (ECX & 0xFFFFFF00) | cast(Unsigned,TypeReg_32,const(TypeReg_32,5)), then
   EAX = Binop(EAX,Shr,cast(Low,TypeReg_8,ECX))
   Therefore, ECX itself is not necessarily constant, even though the bottom 8
   bits are.  By abstract interpreting the right-hand side of the binop, we 
   can propagate the 5 into the shift, which then allows it to be handled by 
   the above case.  This type of "partial constant propagation" is not limited
   to shifts, and is in general a major positive point for this abstract 
   interpretation over this IR. *)
| Binop(e1,((Shl|Shr|Sar) as o),e2) ->
  let (a2,te2) = bitwise_abstract_interpret e2 in
 (match te2 with
  | Const(_,_) -> bitwise_abstract_interpret (Binop(e1,o,te2))
  | _ -> (AbsValue(make_top (IRTypeCheck.type_of_integer_type (IRTypeCheck.typecheck_expr e1))),Binop(e1,o,te2)))

(* Do this better.  For now, we only do comparisons if they are constants. *)
| Binop(e1,((ULT|ULE|SLT|SLE) as o),e2) ->
  let (a1,te1) = bitwise_abstract_interpret e1 in
  let (a2,te2) = bitwise_abstract_interpret e2 in
  let cres = Binop(te1,o,te2) in
  let ares = match (te1,te2) with
  | Const(i1,s1),Const(i2,s2) -> 
    let c = IRLocalOpt.fold_expr_constants cres in
   (match c with
    | Const(i,s) -> AbsValue(abstract_bv_of_constant i s)
    | _ -> failwith "impossible uioqwaqwy")
  | _ -> AbsValue([|AbsHalf|])
  in (ares,cres)
  

(* Punt on all binops except the above.  Revisit for the other conditional 
   comparisons.  Should we do anything about shifts by non-constants, and/or 
   division/modulus? *)
| Binop(e1,o,e2) ->
  let _,te1 = bitwise_abstract_interpret e1 in
  let _,te2 = bitwise_abstract_interpret e2 in
  (AbsValue(make_top (IRTypeCheck.type_of_integer_type (IRTypeCheck.typecheck_expr expr))),Binop(te1,o,te2))

(* Handle all unary operations the same way as we handled binops above. *)
| Unop(o,e) -> 
  let (a,te) = bitwise_abstract_interpret e in
  let ares =
    match o with
    | Not -> abstract_not a
    | Neg -> abstract_neg a
  in 
  let cres = match (make_constant_opt (unwrap_abstract_value ares)) with
  | Some(c) -> c
  | None -> Unop(o,te)
  in (ares,cres)

(* Stores and loads are pretty hairy.  We ignore all loads and stores from 
   addresses that are not constant values. *)
| Store(m,a,_,s)
| Load(m,a,s) -> 
  (* Abstract interpret the address we load/store against. *)
  let aa,ta = bitwise_abstract_interpret a in
  (* Fetch the memory we load/store against. *)
  let am,tm = bitwise_abstract_interpret m in
  let am = unwrap_abstract_memory am in
  (* Was the address constant? *)
 (match ta with
  (* Address was constant; try to read from it *)
  | Const(ca,_) (*when List.mem X86ToIRUtil.vEsp (IRLocalOpt.extract_uses a)*) -> 
   (match expr with
    | Store(_,_,t,_) -> 
      let at,et = bitwise_abstract_interpret t in
      (* Create a list of (address,abstract_bit array) pairs. *)
      (* Could make this tail-recursive, but who cares *)
      let make_normalized_store_pairs a s =
        let s = (IRUtil.bits s) lsr 3 in
        if s = 0 then failwith "1-bit store";
        let at = unwrap_abstract_value at in
      (*IDA.msg "s: %d length: %d\n" s (Array.length at);*)
        let rec aux i =
          if i < s
          then (Int64.add a (Int64.of_int i), Array.sub at (i*8) 8)::(aux (i+1))
          else []
        in aux 0
      in
      let store_pairs = make_normalized_store_pairs ca s in
    (*List.iter 
        (fun (where,what) -> 
          IDA.msg "%s\n" ("Storing to "^Int64.to_string where^": "^string_of_abstract_bitvector what))
        store_pairs;*)
      
      (* Update the memory accordingly *)
      let mem = List.fold_left (fun f (addr,value) -> abstract_store value addr f) am store_pairs in

      (* Return the memory, and the transformed store expression (don't touch 
         the address component). *)
      (AbsMem(mem),Store(tm,a,et,s))

    | Load(_,_,_)    -> 
      let se = (IRUtil.bits s) lsr 3 in
      if se = 0 then failwith "1-bit load";
      let outval = Array.make (IRUtil.bits s) (AbsZero) in
      (* Load each byte out of memory and OR them together. *)
      let rec aux j =
        if j < se
        then 
          let loaded_value = abstract_load (Int64.add ca (Int64.of_int j)) am in
          for i = (j*8) to (((j+1)*8) - 1) do outval.(i) <- loaded_value.(i land 7) done;
          aux (j+1)
      in 
      (* Return the result and the transformed load expression. *)
      aux 0;
    (*IDA.msg "%s\n" ("Loading from "^Int64.to_string ca^"("^PpIR.ppTypereg s^"): "^string_of_abstract_bitvector outval);*)
      (AbsValue(outval),Load(tm,a,s))
    | _ -> failwith "impossible")
  (* Address wasn't constant; for a load, return top; for a store, return
     the existing memory.  This is not quite sound, but since we only
     track writes that occur in the vicnity of the current stack pointer,
     and we assume that stores to addresses not involving the stack pointer
     do not alias the stack, we can get away with it without sleeping too 
     poorly at night. 
     
     Cases to worry about:
     mov [esp+edi*4], x
     mov [esp+eax], x
     
     We could specifically check for these cases:  if the address expression
     involves esp, but does not evaluate to a constant, then we could return
     top memory.  This is still not sound, however:  consider the ebp register
     aliasing esp.
     *)
  | _ -> (match expr with
    | Load(_,_,_)    -> (AbsValue(make_top s),expr)
    | Store(_,_,_,_) -> (AbsMem(am),expr)
    | _ -> failwith "impossible"))

(* Lookup a variable in the variable context.  If we haven't seen it being 
   assigned yet, we return the default abstract value (top).  If it has been
   assigned already, and it is of variable type, try to replace it with a
   constant. *)
| Var(v) -> 
  let ares = lookup_varctx v in
  let cres = 
    match ares with
    | AbsValue(av) -> 
     (match (make_constant_opt av) with
      | Some(c) -> c
      | None -> expr)
    | _ -> expr
  in
  (ares,cres)

| Let(v,e1,e2) -> 
  (* Save the original value of v. *)
  let orig_v_binding = lookup_varctx v in
  (* Evaluate e1. *)
  let (a1,te1) = bitwise_abstract_interpret e1 in
  (* Update the value of v. *)
  Hashtbl.replace !varctx v a1;
  (* Evaluate e2 with the v binding above. *)
  let (a2,te2) = bitwise_abstract_interpret e2 in
  (* Replace the original value of v. *)
  Hashtbl.replace !varctx v orig_v_binding;
  (* Return the abstract evaluation and the transformed Let expression. *)
  (a2,Let(v,te1,te2))

(* Perform casting.  Very straightforward. *)
| Cast(k,s,e) -> 
  let src_array,te = bitwise_abstract_interpret e in
  let src_array = unwrap_abstract_value src_array in
  let size_of_dest = IRUtil.bits s in
  let size_of_src  = Array.length src_array in
  let partial_copy init start_in_src copy_len =
    let dest_arr = Array.make size_of_dest init in
    for j = 0 to (copy_len-1) do dest_arr.(j) <- src_array.(start_in_src + j) done;
    dest_arr
  in
  let ares =
    match k with
    (* For unsigned casts, fill the whole array with AbsZero terms, and then
       copy the source values into the lower portion of the destination. *)
    | Unsigned -> partial_copy (AbsZero)                              0 (Array.length src_array)
    (* For signed casts, fill the whole array with the topmost bit of the 
       source, and then copy the lower bits of the source. *)
    | Signed   -> partial_copy src_array.(Array.length src_array - 1) 0 (Array.length src_array)
    (* For low casts, just copy the low values. *)
    | Low      -> partial_copy (AbsZero)                              0 size_of_dest
    (* For high casts, just copy the high values. *)
    | High     -> partial_copy (AbsZero)   (size_of_src - size_of_dest) size_of_dest
  in
  (* Replace with a constant, if possible. *)
  let cres =
    match (make_constant_opt ares) with
    | Some(c) -> c
    | None -> Cast(k,s,te)
  in (AbsValue(ares),cres)

(* Turn a constant directly into an abstract bitvector. *)
| Const(i,s) -> (AbsValue(abstract_bv_of_constant i s),expr)
  
(* Abstract interpret the expressions, and replace them by their transformed
   values.  For all instruction types other than assigns, we throw away the 
   abstract result; for assigns, we update the variable context. *)
let bitwise_abstract_interpret_instr instr =
(*let _ = IDA.msg "Processing: %s\n" (PpIR.ppInstr instr) in*)
  match instr with
  | Assign(v,e)  -> let a,expr = bitwise_abstract_interpret e in assign_varctx v a; Assign(v,expr) 
  | Jmp(e)       -> let _,expr = bitwise_abstract_interpret e in Jmp(expr)
  (* Try to turn CJumps into Jmps, if the conditional expression evaluated to
     a constant. *)
  | CJmp(c,t,nt) -> let _,expr = bitwise_abstract_interpret c in 
                   (match expr with
                    | Const(i,TypeReg_1) when i = 1L -> Jmp(t)
                    | Const(i,TypeReg_1) when i = 0L -> Jmp(nt)
                    | Const(_,_) -> failwith "typechecking prevents this jklasjklf"
                    | _ -> CJmp(expr,t,nt))
  | Halt(e)      -> let _,expr = bitwise_abstract_interpret e in Halt(expr)
  | Assert(e)    -> let _,expr = bitwise_abstract_interpret e in Assert(expr)
  | Label(i)     -> instr
  | Comment(s)   -> instr
  | Special(i)   -> failwith ("Cannot abstract interpret special "^PpIR.ppInstr instr)

(* Abstract interpret an entire list of instructions. *)
let bitwise_abstract_interpret_instr_list ht instrlist =
  varctx := ht;
  List.map 
   (fun i -> 
    (*let abv = unwrap_abstract_value ((Hashtbl.find !varctx X86ToIRUtil.vEsp)) in
      let const = make_constant_opt abv in
      IDA.msg "ESP = %s" (string_of_abstract_bitvector abv);
     (match const with
      | Some(Const(i,_)) -> IDA.msg " (%08Lx)\n" i
      | None -> IDA.msg "\n");
      IDA.msg "%s\n" (PpIR.ppInstr i);*)
      bitwise_abstract_interpret_instr i) instrlist 
  
(* Test the abstract operations on random values n times.  Straightforward. *)
(* As of 5/30/2010 4:47:01 AM, all tests pass for substantial values of n (>100k), 
   although it is revealed that this is a slow process, undoubtedly because of all 
   the array creation and deletion.  If speed turns out to be a critical concern, 
   this whole module is going to need profiling and modification. *)
let test n =
  let ri64 () = Random.int64 Int64.max_int in
  let ri32 () = Random.int64 0x100000000L in
  let ri n    = Random.int n in
  let abv c s = abstract_bv_of_constant c s in
  let abv64 c = abv c TypeReg_64 in
  let abv32 c = abv c TypeReg_32 in
  let test_abstract_binop absbinop concbinop =
    let lhs,rhs = ri64(),ri64() in
    match (make_constant_opt (unwrap_abstract_value (absbinop (AbsValue(abv64 lhs)) (AbsValue(abv64 rhs))))) with
    | Some(Const(c,_)) when c = (concbinop lhs rhs) -> true
    | _ -> false
  in
  let test_abstract_unop absunop concunop =
    let lhs = ri64() in
    match (make_constant_opt (unwrap_abstract_value (absunop (AbsValue(abv64 lhs))))) with
    | Some(Const(c,_)) when c = (concunop lhs) -> true
    | _ -> false
  in
  let test_abstract_shift absshift concshift =
    let lhs,rhs = ri64(),ri 64 in
    match (make_constant_opt (unwrap_abstract_value (absshift (AbsValue(abv64 lhs)) rhs))) with
    | Some(Const(c,_)) when c = (concshift lhs rhs) -> true
    | _ -> false
  in
  let test_abstract_mul absmul concmul =
    let lhs,rhs = ri32(),ri32() in
    match (make_constant_opt (unwrap_abstract_value (absmul (AbsValue(abv32 lhs)) (AbsValue(abv32 rhs))))) with
    | Some(Const(c,_)) when c = (concmul lhs rhs) -> true
    | _ -> false
  in
  for i = 0 to (n-1) do
    if i mod 100 = 0 then print_endline ("Done "^string_of_int i^" tests");
    if not (test_abstract_binop abstract_add  Int64.add                ) then failwith "abstract add failed";
    if not (test_abstract_binop abstract_sub  Int64.sub                ) then failwith "abstract sub failed";
    if not (test_abstract_binop abstract_xor  Int64.logxor             ) then failwith "abstract xor failed";
    if not (test_abstract_binop abstract_and  Int64.logand             ) then failwith "abstract and failed";
    if not (test_abstract_binop abstract_or   Int64.logor              ) then failwith "abstract or failed" ;
    if not (test_abstract_unop  abstract_neg  Int64.neg                ) then failwith "abstract neg failed";
    if not (test_abstract_unop  abstract_not  Int64.lognot             ) then failwith "abstract not failed";
    if not (test_abstract_shift abstract_shl  Int64.shift_left         ) then failwith "abstract shl failed";
    if not (test_abstract_shift abstract_shr  Int64.shift_right_logical) then failwith "abstract shr failed";
    if not (test_abstract_shift abstract_sar  Int64.shift_right        ) then failwith "abstract sar failed";
    if not (test_abstract_mul   abstract_umul Int64.mul                ) then failwith "abstract umul failed";
  done