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

Module: Program Transformation

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

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

#include <assert.h>

#include <i2string.h>
#include <replace_expr.h>
#include <expr_util.h>
#include <location.h>

#include <pointer-analysis/value_propagation.h>
#include <pointer-analysis/goto_program_dereference.h>

#include "remove_skip.h"
#include "goto_convert_functions.h"

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

Function: goto_convert_functionst::remove_function_pointers

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convert_functionst::remove_function_pointers()
{
  // first see if there is something to do
  if(!have_function_pointers())
    return false;

  // get value sets
  value_propagationt value_propagation(ns);

  value_propagation(functions);
  
  bool did_something=false;

  // remove function pointers
  for(goto_functionst::function_mapt::iterator f_it=
      functions.function_map.begin();
      f_it!=functions.function_map.end();
      f_it++)
    if(remove_function_pointers(value_propagation, f_it->second.body))
      did_something=true;
      
  return did_something;
}

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

Function: goto_convert_functionst::have_function_pointers

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convert_functionst::have_function_pointers()
{
  for(goto_functionst::function_mapt::iterator f_it=
      functions.function_map.begin();
      f_it!=functions.function_map.end();
      f_it++)
    if(have_function_pointers(f_it->second.body))
      return true;

  return false;
}

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

Function: goto_convert_functionst::have_function_pointers

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convert_functionst::have_function_pointers(
  const goto_programt &goto_program)
{
  for(goto_programt::const_targett
      target=goto_program.instructions.begin();
      target!=goto_program.instructions.end();
      target++)
    if(target->is_other())
    {
      const exprt &code=target->code;
    
      if((code.get("statement")=="function_pointer_call"))
        return true;
    }

  return false;
}

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

Function: goto_convert_functionst::remove_function_pointers

  Inputs:

 Outputs:

 Purpose:

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

bool goto_convert_functionst::remove_function_pointers(
  const value_propagationt &value_propagation,
  goto_programt &goto_program)
{
  bool did_something=false;

  for(goto_programt::targett target=goto_program.instructions.begin();
      target!=goto_program.instructions.end();
      target++)
    if(target->is_other())
    {
      exprt &code=target->code;
    
      if((code.get("statement")=="function_pointer_call"))
      {
        remove_function_pointer(value_propagation, goto_program, target); 
        did_something=true;
      }
    }

  if(did_something)
  {
    remove_skip(goto_program);
    goto_program.update();
  }
    
  return did_something;
}

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

Function: goto_convert_functionst::remove_function_pointer

  Inputs:

 Outputs:

 Purpose:

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

void goto_convert_functionst::remove_function_pointer(
  const value_propagationt &value_propagation,
  goto_programt &goto_program,
  goto_programt::targett target)
{
  exprt &code=target->code;

  assert(code.operands().size()==2);

  exprt &lhs=code.op0();
  exprt &rhs=code.op1();
  
  assert(rhs.operands().size()>=1);
  
  exprt &function=rhs.op0();

  // dereference function
  dereference(target, function, ns, value_propagation);

  // could now be a case split and so on
  goto_programt new_code;
  do_functioncall(lhs, rhs, new_code);
  finish_gotos();
  
  goto_programt::targett next_target=target;
  next_target++;
  
  // fix local variables
  Forall_goto_program_instructions(it, new_code)
    it->local_variables.insert(target->local_variables.begin(),
                               target->local_variables.end());
  
  goto_program.instructions.splice(next_target, new_code.instructions);

  target->make_skip();
}
