/*******************************************************************\

Module:

Author: Daniel Kroening, kroening@cs.cmu.edu

\*******************************************************************/

#include <i2string.h>
#include <config.h>
#include <c_misc.h>
#include <arith_tools.h>
#include <pointer_offset_size.h>
#include <prefix.h>

#include "bv_pointers.h"

/*******************************************************************\

Function: bv_pointerst::convert_rest

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

literalt bv_pointerst::convert_rest(const exprt &expr)
{
  if(expr.type().id()!="bool")
    throw "bv_pointerst::convert_rest got non-boolean operand";

  const exprt::operandst &operands=expr.operands();

  if(expr.id()=="invalid-pointer")
  {
    if(operands.size()==1 &&
       is_ptr(operands[0].type()))
    {
      bvt bv;
      if(!convert_bv(operands[0], bv))
      {
        if(bv.size()!=0)
        {
          bvt invalid_bv, null_bv;
          encode(pointer_logic.get_invalid_object(), invalid_bv);
          encode(pointer_logic.get_null_object(),    null_bv);

          bvt equal_invalid_bv, equal_null_bv;
          equal_invalid_bv.resize(addr_bits);
          equal_null_bv.resize(addr_bits);

          for(unsigned i=0; i<addr_bits; i++)
          {
            equal_invalid_bv[i]=prop.lequal(bv[offset_bits+i],
                                            invalid_bv[offset_bits+i]);
            equal_null_bv[i]   =prop.lequal(bv[offset_bits+i],
                                            null_bv[offset_bits+i]);

          }

          literalt equal_invalid=prop.land(equal_invalid_bv);
          literalt equal_null=prop.land(equal_null_bv);

          return prop.lor(equal_invalid, equal_null);
        }
      }
    }
  }
  else if(expr.id()=="is_dynamic_object")
  {
    if(operands.size()==1 &&
       is_ptr(operands[0].type()))
    {
      bvt bv;
      if(!convert_bv(operands[0], bv))
      {
        bv.erase(bv.begin(), bv.begin()+offset_bits);

        // for now, allocate literal and then do later
        is_dynamic_objectt is_dynamic_object;
        is_dynamic_object.bv=bv;
        is_dynamic_object.l=prop.new_literal();
        
        is_dynamic_object_list.push_back(is_dynamic_object);
        return is_dynamic_object.l;
      }
    }
  }
  else if(expr.id()=="same-object")
  {
    if(operands.size()==2 &&
       is_ptr(operands[0].type()) &&
       is_ptr(operands[1].type()))
    {
      bvt bv0, bv1;
      if(!convert_bv(operands[0], bv0) &&
         !convert_bv(operands[1], bv1))
      {
        bvt equal_bv;
        equal_bv.resize(addr_bits);

        for(unsigned i=0; i<addr_bits; i++)
          equal_bv[i]=prop.lequal(bv0[offset_bits+i],
                                  bv1[offset_bits+i]);

        return prop.land(equal_bv);
      }
    }
  }
  else if(expr.id()=="<" || expr.id()=="<=" ||
          expr.id()==">" || expr.id()==">=")
  {
    if(operands.size()==2 &&
       is_ptr(operands[0].type()) &&
       is_ptr(operands[1].type()))
    {
      bvt bv0, bv1;

      if(!convert_bv(operands[0], bv0) &&
         !convert_bv(operands[1], bv1))
      {
        const std::string &rel=expr.id_string();

        if(rel=="<=" || rel=="<")
          return bv_utils.lt_or_le(rel=="<=", bv0, bv1, false);

        if(rel==">=" || rel==">")
          return bv_utils.lt_or_le(rel==">=", bv1, bv0, false);
                                              // swapped
      }
    }
  }

  return SUB::convert_rest(expr);
}

/*******************************************************************\

Function: bv_pointerst::bv_pointerst

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bv_pointerst::bv_pointerst(propt &_prop):boolbvt(_prop)
{
  addr_bits=8;
  offset_bits=config.ansi_c.pointer_width;
  bits=addr_bits+offset_bits;
}

/*******************************************************************\

Function: bv_pointerst::get_width

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/
  
bool bv_pointerst::get_width(const typet &type, unsigned &width) const
{
  if(is_ptr(type))
  {
    width=bits;
    return false;
  }

  return SUB::get_width(type, width);
}

/*******************************************************************\

Function: bv_pointerst::convert_address_of_rec

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool bv_pointerst::convert_address_of_rec(
  const exprt &expr,
  bvt &bv)
{
  if(expr.id()=="symbol")
  {
    add_addr(expr, bv);
    return false;
  }
  else if(expr.id()=="index")
  {
    if(expr.operands().size()!=2)
      throw "index takes two operands";

    const exprt &array=expr.op0();
    const exprt &index=expr.op1();
    
    // recursive call

    if(array.type().id()=="pointer")
    {
      if(convert_pointer_type(array, bv)) return true;
    }
    else
    {
      if(convert_address_of_rec(array, bv)) return true;
    }

    // get size
    mp_integer size;
    
    if(array.type().id()=="string-constant")
      size=1;
    else
      size=pointer_offset_size(array.type().subtype());
    
    if(size==0) return true;
    
    return increase_offset(bv, size, index);
  }
  else if(expr.id()=="member")
  {
    if(expr.operands().size()!=1)
      throw "member takes one operand";

    const exprt &struct_op=expr.op0();

    // recursive call
    if(convert_address_of_rec(struct_op, bv)) return true;

    const irept::subt &components=
      struct_op.type().find("components").get_sub();
    
    const irep_idt &component_name=expr.get("component_name");
    
    bool found=false;
    
    mp_integer offset=1; // for the struct itself

    forall_irep(it, components)
    {
      if(component_name==it->get("name")) { found=true; break; }
      const typet &subtype=(typet &)it->find("type");
      mp_integer sub_size=pointer_offset_size(subtype);
      if(sub_size==0) return true;
      offset+=sub_size;
    }

    if(!found) return true;

    // add offset
    increase_offset(bv, offset);
    
    return false;
  }
  else if(expr.id()=="constant" ||
          expr.id()=="string-constant")
  { // constant
    add_addr(expr, bv);
    return false;
  }
  
  return true;
}

/*******************************************************************\

Function: bv_pointerst::convert_pointer_type

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool bv_pointerst::convert_pointer_type(const exprt &expr, bvt &bv)
{
  if(!is_ptr(expr.type()))
    throw "convert_pointer_type got non-pointer type";

  bv.resize(bits);

  if(expr.id()=="symbol")
  {
    const irep_idt &identifier=expr.get("identifier");
    const typet &type=expr.type();

    for(unsigned i=0; i<bits; i++)
      bv[i]=get_literal(identifier, i, type);

    return false;
  }
  else if(expr.id()=="nondet_symbol")
  {
    for(unsigned i=0; i<bits; i++)
      bv[i]=prop.new_literal();

    return false;
  }
  else if(expr.id()=="typecast")
  {
    if(expr.operands().size()!=1)
      throw "typecast takes one operand";

    const exprt &op=expr.op0();

    if(op.type().id()=="pointer")
      return convert_pointer_type(op, bv);
    else
    {
      // produce an invalid pointer
      encode(pointer_logic.get_invalid_object(), bv);

      // set offset to nondet
      for(unsigned i=0; i<offset_bits; i++)
        bv[i]=prop.new_literal();

      return false;
    }
  }
  else if(expr.id()=="if")
  {
    return SUB::convert_if(expr, bv);
  }
  else if(expr.id()=="index")
  {
    return SUB::convert_index(expr, bv);
  }
  else if(expr.id()=="member")
  {
    return SUB::convert_member(expr, bv);
  }
  else if(expr.id()=="address_of" ||
          expr.id()=="implicit_address_of" ||
          expr.id()=="reference_to")
  {
    if(expr.operands().size()!=1)
      throw expr.id_string()+" takes one operand";
      
    return convert_address_of_rec(expr.op0(), bv);
  }
  else if(expr.id()=="constant")
  {
    if(expr.get("value")!="NULL")
      throw "found non-NULL pointer constant";

    encode(pointer_logic.get_null_object(), bv);

    return false;
  }
  else if(expr.id()=="+")
  {
    if(expr.operands().size()==0)
      throw "operator "+expr.id_string()+" takes at least one operand";

    bool found=false;
    mp_integer size;

    forall_operands(it, expr)
    {
      if(it->type().id()=="pointer")
      {
        if(found)
          throw "found two pointers in sum";

        found=true;

        if(convert_bv(*it, bv))
          return true;
          
        size=pointer_offset_size(it->type().subtype());
      }
    }

    if(!found)
      throw "found no pointer in pointer type sum";

    bvt sum;
    sum.resize(offset_bits);

    for(unsigned i=0; i<offset_bits; i++)
      sum[i]=prop.constant(false);

    forall_operands(it, expr)
    {
      bvt op;

      if(it->type().id()=="pointer") continue;

      if(it->type().id()!="unsignedbv" &&
         it->type().id()!="signedbv")
        return true;

      if(convert_bv(*it, op)) return true;

      if(op.size()>offset_bits || op.size()==0)
        throw "unexpected pointer arithmetic operand width";

      bv_utils.extend(op, offset_bits, it->type().id()=="signedbv");
      bv_utils.adder(sum, op, false);
    }    

    increase_offset(bv, size, sum);

    return false;
  }
  else if(expr.id()=="-")
  {
    if(expr.operands().size()!=2)
      throw "operator "+expr.id_string()+" takes two operands";

    if(expr.op0().type().id()!="pointer")
      throw "found no pointer in pointer type in difference";

    bvt bv0, bv1;

    if(convert_bv(expr.op0(), bv0))
      return true;

    if(convert_bv(expr.op1(), bv1))
      return true;

    bvt sum;
    sum.resize(offset_bits);

    for(unsigned i=0; i<offset_bits; i++)
      sum[i]=bv0[i];

    if(expr.op1().type().id()!="unsignedbv" &&
       expr.op1().type().id()!="signedbv")
      return true;

    if(bv1.size()>offset_bits || bv1.size()==0)
      throw "unexpected pointer arithmetic operand width";
 
    bv_utils.extend(bv1, offset_bits, expr.op1().type().id()=="signedbv");

    bv_utils.adder(sum, bv1, true); // subtract

    for(unsigned i=0; i<bv.size(); i++)
      bv[i]=i<offset_bits?sum[i]:bv0[i];

    return false;
  }

  return true;
}

/*******************************************************************\

Function: bv_pointerst::convert_bitvector

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool bv_pointerst::convert_bitvector(const exprt &expr, bvt &bv)
{
  if(is_ptr(expr.type()))
    return convert_pointer_type(expr, bv);

  if(expr.id()=="-" && expr.operands().size()==2 &&
     expr.op0().type().id()=="pointer" &&
     expr.op1().type().id()=="pointer")
  {
    bvt op0, op1;

    if(convert_bv(expr.op0(), op0)) return true;
    if(convert_bv(expr.op1(), op1)) return true;

    unsigned width;
    if(get_width(expr.type(), width))
      return true;

    if(width>offset_bits)
      throw "no sign extension on pointer differences";

    op0.resize(width);
    op1.resize(width);

    bv=op0;

    bv_utils.adder(op0, op1, true);

    return false;
  }
  else if(expr.id()=="pointer_offset" &&
          expr.operands().size()==1 &&
          is_ptr(expr.op0().type()))
  {
    bvt op0;

    if(convert_bv(expr.op0(), op0)) return true;

    unsigned width;
    if(get_width(expr.type(), width))
      return true;

    if(width>offset_bits)
      throw "no sign extension on pointer differences";

    op0.resize(width);

    bv=op0;

    return false;
  }
  else if(expr.id()=="pointer_object" &&
          expr.operands().size()==1 &&
          is_ptr(expr.op0().type()))
  {
    bvt op0;

    if(convert_bv(expr.op0(), op0)) return true;

    unsigned width;
    if(get_width(expr.type(), width))
      return true;
      
    // erase offset bits
    
    op0.erase(op0.begin()+0, op0.begin()+offset_bits);

    bv_utils.extend(op0, width, false);

    bv=op0;

    return false;
  }
  else if(expr.id()=="typecast" &&
          expr.operands().size()==1 &&
          expr.op0().type().id()=="pointer")
  {
    bvt op0;
  
    if(convert_pointer_type(expr.op0(), op0))
      return true;
  
    // squeeze it in!

    unsigned width;
    if(get_width(expr.type(), width))
      return true;

    bv.resize(width);
    
    // too small?
    if(width<addr_bits) return true;

    for(unsigned i=0; i<width; i++)
    {
      if(i>=width-addr_bits)
        bv[i]=op0[i-(width-addr_bits)+offset_bits];
      else if(i>=offset_bits)
        bv[i]=prop.constant(false);
      else
        bv[i]=op0[i];
    }

    return false;
  }

  return SUB::convert_bitvector(expr, bv);
}

/*******************************************************************\

Function: bv_pointerst::bv_get_rec

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::bv_get_rec(
  const bvt &bv,
  const std::vector<bool> &unknown,
  unsigned offset,
  const typet &type,
  exprt &dest) const
{
  if(!is_ptr(type))
  {
    SUB::bv_get_rec(bv, unknown, offset, type, dest);
    return;
  }

  std::string value_addr, value_offset;

  for(unsigned i=0; i<bits; i++)
  {
    char ch;
    unsigned bit_nr=i+offset;

    if(unknown[bit_nr])
      ch='0';
    else
      switch(prop.l_get(bv[bit_nr]).get_value())
      {
       case tvt::TV_FALSE: ch='0'; break;
       case tvt::TV_TRUE:  ch='1'; break;
       case tvt::TV_UNKNOWN: ch='0'; break;
       default: assert(false);
      }

    if(i<offset_bits)
      value_offset=ch+value_offset;
    else
      value_addr=ch+value_addr;
  }

  pointer_logict::pointert pointer;
  pointer.object=integer2long(binary2integer(value_addr, false));
  pointer.offset=binary2integer(value_offset, true);
  
  pointer_logic.pointer_expr(pointer, type, dest);
}

/*******************************************************************\

Function: bv_pointerst::encode

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::encode(unsigned addr, bvt &bv)
{
  bv.resize(bits);

  // set offset to zero
  for(unsigned i=0; i<offset_bits; i++)
    bv[i]=prop.constant(false);

  // set variable part
  for(unsigned i=0; i<addr_bits; i++)
    bv[offset_bits+i]=prop.constant(addr&(1<<i));
}

/*******************************************************************\

Function: bv_pointerst::increase_offset

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::increase_offset(bvt &bv, const mp_integer &x)
{
  mp_integer tmp(x);
  bvt bv2;
  
  bv2.resize(bits);

  // set offset to zero
  for(unsigned i=0; i<offset_bits; i++)
  {
    bv2[i]=prop.constant((tmp%2)!=0);
    tmp/=2;
  }

  // set variable part
  for(unsigned i=0; i<addr_bits; i++)
    bv2[offset_bits+i]=prop.constant(false);
    
  bv_utils.adder(bv, bv2, false);
}

/*******************************************************************\

Function: bv_pointerst::increase_offset

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

bool bv_pointerst::increase_offset(
  bvt &bv,
  const mp_integer &x,
  const exprt &index)
{
  bvt bv_index;
  if(convert_bv(index, bv_index)) return true;
  
  bv_utils.extend(bv_index, offset_bits, index.type().id()=="signedbv");

  increase_offset(bv, x, bv_index);
  
  return false;
}

/*******************************************************************\

Function: bv_pointerst::increase_offset

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::increase_offset(
  bvt &bv,
  const mp_integer &x,
  const bvt &index)
{
  bvt bv_index(index);

  // add object part
  bv_index.resize(bits);
  
  // set object part
  for(unsigned i=0; i<addr_bits; i++)
    bv_index[offset_bits+i]=prop.constant(false);

  if(x!=1)
  {
    mp_integer tmp(x);
    bvt bv_x;
    
    bv_x.resize(bits);

    // set offset to zero
    for(unsigned i=0; i<offset_bits; i++)
    {
      bv_x[i]=prop.constant((tmp%2)!=0);
      tmp/=2;
    }

    // set variable part
    for(unsigned i=0; i<addr_bits; i++)
      bv_x[offset_bits+i]=prop.constant(false);
      
    bv_utils.multiplier(bv_index, bv_x, false);    
  }

  bv_utils.adder(bv, bv_index, false);
}

/*******************************************************************\

Function: bv_pointerst::add_addr

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::add_addr(const exprt &expr, bvt &bv)
{
  unsigned a=pointer_logic.add_object(expr);

  if(a==(unsigned(1)>>addr_bits))
    throw "too many variables";

  encode(a, bv);
}

/*******************************************************************\

Function: bv_pointerst::do_is_dynamic_object

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::do_is_dynamic_object(
  const is_dynamic_objectt &is_dynamic_object)
{
  const pointer_logict::object_mapt &object_map=
    pointer_logic.get_object_map();
    
  for(pointer_logict::object_mapt::const_iterator
      it=object_map.begin();
      it!=object_map.end();
      it++)
  {
    const exprt &expr=it->first;
    
    bool is_dynamic=
      expr.type().get_bool("#dynamic") ||
      (expr.id()=="symbol" &&
       has_prefix(expr.get_string("identifier"), "symex_dynamic::"));
    
    // only compare object part
    bvt bv;
    encode(it->second, bv);
    
    bv.erase(bv.begin(), bv.begin()+offset_bits);
    
    literalt l1=bv_utils.equal(bv, is_dynamic_object.bv);
    literalt l2=is_dynamic_object.l;
    
    if(!is_dynamic) l2=prop.lnot(l2);
    
    prop.l_set_to(prop.limplies(l1, l2), true);
  }
}

/*******************************************************************\

Function: bv_pointerst::post_process

  Inputs:

 Outputs:

 Purpose:

\*******************************************************************/

void bv_pointerst::post_process()
{
  for(is_dynamic_object_listt::const_iterator
      it=is_dynamic_object_list.begin();
      it!=is_dynamic_object_list.end();
      it++)
    do_is_dynamic_object(*it);
  
  is_dynamic_object_list.clear();
  
  SUB::post_process();
}
