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

Module: Symbolic Execution

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

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

#include <assert.h>

#include <expr_util.h>

#include "goto_symex.h"

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

Function: goto_symext::symex_goto

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::symex_goto(statet &state)
{
  const goto_programt::instructiont &instruction=*state.source.pc;
  
  exprt old_guard(instruction.guard);
  replace_dynamic_allocation(state, old_guard);
  dereference(old_guard, state);

  exprt new_guard(old_guard);
  state.rename(new_guard);
  do_simplify(new_guard);
  
  if(new_guard.is_false())
  {
    // reset unwinding
    unwind_map[state.source]=0;

    // next instruction
    state.source.pc++;
    return; // nothing to do
  }
    
  target.location(state.guard, state.source);

  assert(!instruction.targets.empty());
  
  // we only do deterministic gotos for now
  if(instruction.targets.size()!=1)
    throw "no support for non-deterministic gotos";
    
  goto_programt::const_targett goto_target=
    instruction.targets.front();
    
  bool forward=
    state.source.pc->ordering<
    goto_target->ordering;
    
  if(!forward)
  {
    unsigned &unwind=unwind_map[state.source];
    unwind++;
    
    if(get_unwind(state.source, unwind))
    {
      loop_bound_exceeded(state, new_guard);

      // reset unwinding
      unwind_map[state.source]=0;
      
      // next instruction
      state.source.pc++;
      return;
    }      
  }
  
  if(!forward && new_guard.is_true())
  {
    state.source.pc=goto_target;
    return; // nothing else to do
  }
  
  goto_programt::const_targett new_state_pc, state_pc;
  
  if(forward)
  {
    new_state_pc=goto_target;
    state_pc=state.source.pc;
    state_pc++;
  }
  else
  {
    new_state_pc=state.source.pc;
    new_state_pc++;
    state_pc=goto_target;
  }
  
  // put on state-queue
  state_listt &state_list=top().state_map[new_state_pc];
  state_list.push_back(state);
  statet &new_state=state_list.back();
  
  state.source.pc=state_pc;
  new_state.source.pc=new_state_pc;
  
  // adjust guard
  if(new_guard.is_true())
  {
    state.guard.make_false();
  }
  else
  {
    // produce new guard symbol
    exprt guard_expr;
    if(new_guard.id()=="symbol" ||
       (new_guard.id()=="not" &&
        new_guard.operands().size()==1 &&
        new_guard.op0().id()=="symbol"))
      guard_expr=new_guard;
    else
    {
      guard_expr=exprt("symbol", typet("bool"));
      guard_expr.set("identifier", guard_identifier);
      exprt new_rhs(new_guard), rhs(old_guard);
      new_rhs.make_not();
      rhs.make_not();
      
      exprt new_lhs(guard_expr);
      state.assignment(new_lhs, new_rhs, ns, false, false);
      
      guardt guard;

      target.assignment(
        guard,
        new_lhs, guard_expr,
        new_rhs,
        state.source,
        symex_targett::HIDDEN);
      
      guard_expr.make_not();
      state.rename(guard_expr);
    }
    
    if(forward)
    {
      new_state.guard.add(guard_expr);
      guard_expr.make_not();
      state.guard.add(guard_expr);
    }
    else
    {
      state.guard.add(guard_expr);
      guard_expr.make_not();
      new_state.guard.add(guard_expr);
    }
  }
}

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

Function: goto_symext::symex_goto

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::symex_goto(statet &state, bool taken)
{
  const goto_programt::instructiont &instruction=*state.source.pc;
  
  exprt guard(instruction.guard);
  dereference(guard, state);
  state.rename(guard);
  
  if(!taken) guard.make_not();
  
  state.guard.guard_expr(guard);
  do_simplify(guard);

  target.assumption(state.guard, guard, state.source);  
}

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

Function: goto_symext::merge_gotos

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::merge_gotos(statet &state)
{
  // first, see if this is a target at all
  state_mapt::iterator state_map_it=
    top().state_map.find(state.source.pc);
  
  if(state_map_it!=top().state_map.end())
  {
    // we need to merge
    state_listt &state_list=state_map_it->second;

    for(state_listt::reverse_iterator list_it=state_list.rbegin();
        list_it!=state_list.rend();
        list_it++)
    {
      statet &goto_state=*list_it;
      
      // do SSA phi functions
      phi_function(goto_state, state);

      merge_value_sets(goto_state, state);

      // adjust guard
      state.guard|=goto_state.guard;
    }
    
    // clean up
    top().state_map.erase(state_map_it);
  }
}

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

Function: goto_symext::merge_value_sets

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::merge_value_sets(
  const statet &src,
  statet &dest)
{
  if(dest.guard.is_false())
  {
    dest.value_set=src.value_set;
    return;
  }
  
  dest.value_set.make_union(src.value_set);
}

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

Function: goto_symext::phi_function

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::phi_function(
  const statet &goto_state,
  statet &state)
{
  // go over all variables to see what changed
  std::set<irep_idt> variables;
  
  goto_state.level2.get_variables(variables);
  state.level2.get_variables(variables);
  
  for(std::set<irep_idt>::const_iterator
      it=variables.begin();
      it!=variables.end();
      it++)
  {
    if(goto_state.level2.current_number(*it)!=
       state.level2.current_number(*it))
    {
      if(*it==guard_identifier) continue;
      
      irep_idt original_identifier=
        state.get_original_name(*it);
        
      // changed!
      const symbolt &symbol=ns.lookup(original_identifier);

      typet type(symbol.type);
      // type may need renaming
      state.rename(type);
      
      guardt tmp_guard(goto_state.guard);
      exprt rhs;
      
      if(state.guard.is_false())
      {
        rhs=exprt("symbol", type);
        rhs.set("identifier", goto_state.current_name(symbol.name));
      }
      else
      {
        tmp_guard-=state.guard;
      
        rhs=exprt("if", type);
        rhs.operands().resize(3);
        tmp_guard.expr(rhs.op0());
        rhs.op1()=exprt("symbol", type);
        rhs.op2()=exprt("symbol", type);
        
        rhs.op1().set("identifier", goto_state.current_name(symbol.name));
        rhs.op2().set("identifier", state.current_name(symbol.name));
      }

      exprt lhs(symbol_expr(symbol));
      exprt new_lhs(lhs);
      
      state.assignment(new_lhs, rhs, ns, false, false);
      
      guardt true_guard;

      target.assignment(
        true_guard,
        new_lhs, lhs,
        rhs,
        state.source,
        symex_targett::HIDDEN);
    }
  }
}

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

Function: goto_symext::loop_bound_exceeded

  Inputs:

 Outputs:

 Purpose:

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

void goto_symext::loop_bound_exceeded(
  statet &state,
  const exprt &guard)
{
  const irep_idt &loop_id=state.source.pc->location.get("loop-id");
    
  exprt negated_cond(guard);
  negated_cond.make_not();
  
  if(options.get_bool_option("unwinding-assertions"))
  {
    // generate unwinding assertion

    claim(negated_cond,
          "unwinding assertion loop "+id2string(loop_id),
          state);
  }
  else
  {
    // generate unwinding assumption

    state.guard.guard_expr(negated_cond);
    target.assumption(state.guard, negated_cond, state.source);
  }
}

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

Function: goto_symext::get_unwind

  Inputs:

 Outputs:

 Purpose:

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

bool goto_symext::get_unwind(
  const symex_targett::sourcet &source,
  unsigned unwind)
{
  return false;
}
