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

Module: Function Inlining

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

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

#include <assert.h>

#include <prefix.h>
#include <cprover_prefix.h>
#include <base_type.h>
#include <std_code.h>

#include "remove_skip.h"
#include "goto_inline.h"

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

Function: goto_inlinet::parameter_assignments

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::parameter_assignments(
  const code_typet &code_type,
  const exprt &call,
  goto_programt &dest)
{
  // iterates over the operands
  exprt::operandst::const_iterator it1=call.operands().begin();

  it1++; // skip function operand
  
  goto_programt::local_variablest local_variables;
  
  const code_typet::argumentst &arguments=code_type.arguments();
  
  // iterates over the types of the arguments
  for(code_typet::argumentst::const_iterator
      it2=arguments.begin();
      it2!=arguments.end();
      it2++)
  {
    // if you run out of actual arguments there was a mismatch
    if(it1==call.operands().end())
    {
      err_location(call);
      throw "function call: not enough arguments";
    }

    const exprt &argument=(const exprt &)*it2;

    // this is the type the n-th argument should be
    const typet &arg_type=argument.type();

    const irep_idt &identifier=argument.get("#identifier");

    if(identifier=="")
    {
      err_location(call);
      throw "no identifier for function argument";
    }

    local_variables.insert(identifier);
    
    // nil means "don't assign"
    if(it1->is_nil())
    {
    
    }
    else
    {
      // it should be the same exact type
      if(!base_type_eq(arg_type, it1->type(), ns))
      {
        err_location(call);

        err << "function call: argument `" << identifier
            << "' type mismatch: got "
            << from_type(ns, identifier, it1->type())
            << ", expected "
            << from_type(ns, identifier, arg_type);
        throw 0;
      }

      // this is the actual parameter
      exprt actual(*it1);

      // adds an assignment of the actual parameter to the formal parameter
      exprt assignment("code", typet("code"));
      assignment.set("statement", "assign");
      assignment.operands().resize(2);
      assignment.op0()=exprt("symbol", arg_type);
      assignment.op0().set("identifier", identifier);
      assignment.op1().swap(actual);

      dest.add_instruction();
      dest.instructions.back().location=call.find_location();
      dest.instructions.back().code.swap(assignment);
      dest.instructions.back().local_variables=local_variables;
    }

    it1++;
  }

  if(it1!=call.operands().end())
  {
    // may be an ellipsis
    if(code_type.has_ellipsis())
    {
      // it's ok
    }
    else
    {
      err_location(*it1);
      err << "function call: too many arguments (expected "
          << arguments.size() << ", got "
          << (call.operands().size()-1) << ")";
      throw 0;
    }
  }
}

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

Function: goto_inlinet::replace_return

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::replace_return(
  goto_programt &dest,
  const exprt &lhs,
  const exprt &constrain)
{
  for(goto_programt::instructionst::iterator
      it=dest.instructions.begin();
      it!=dest.instructions.end();
      it++)
  {
    if(it->is_return())
    {
      if(lhs.is_not_nil())
      {
        if(it->code.operands().size()!=1)
        {
          err_location(it->code);
          err << "return expects one operand!" << std::endl;
          warning();
	  continue;
        }
      
        goto_programt tmp;
        goto_programt::targett assignment=tmp.add_instruction(OTHER);
        
        assignment->code=exprt("code", typet("code"));
        assignment->code.set("statement", "assign");
        assignment->code.reserve_operands(2);
        assignment->code.copy_to_operands(lhs);
        assignment->code.move_to_operands(it->code.op0());
        assignment->location=it->location;
        assignment->local_variables=it->local_variables;
        
        if(constrain.is_not_nil() && !constrain.is_true())
        {
          codet constrain("bp_constrain");
          constrain.reserve_operands(2);
          constrain.move_to_operands(assignment->code);
          constrain.copy_to_operands(constrain);
        }
        
        dest.insert_swap(it, *assignment);
        it++;
      }
      else if(!it->code.operands().empty())
      {
        goto_programt tmp;
        goto_programt::targett expression=tmp.add_instruction(OTHER);
        
        expression->code=exprt("code", typet("code"));
        expression->code.set("statement", "expression");
        expression->code.move_to_operands(it->code.op0());
        expression->location=it->location;
        expression->local_variables=it->local_variables;
        
        dest.insert_swap(it, *expression);
        it++;
      }

      it->make_goto(--dest.instructions.end());
    }
  }
}

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

Function: goto_inlinet::expand_functioncall

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::expand_functioncall(
  goto_programt &dest,
  goto_programt::targett &target,
  const exprt &lhs,
  exprt &rhs,
  const exprt &constrain,
  bool full)
{
  // look it up
  if(rhs.operands().empty())
  {
    err_location(rhs);
    throw "functioncall expects at least one operand";
  }
  
  const exprt &function=rhs.op0();
  
  if(function.id()!="symbol")
  {
    err_location(function);
    throw "functioncall expects symbol as first operand";
  }
  
  const irep_idt &identifier=
    function.get("identifier");
  
  // see if we are already expanding it
  if(recursion_set.find(identifier)!=recursion_set.end())
  {
    if(!full)
    {
      target++;
      return; // simply ignore, it's ok
    }

    // it's really recursive. Uh. Buh. Give up.
    err_location(function);
    warning("recursion is ignored");
    target->make_skip();
    
    target++;
    return;
  }

  goto_functionst::function_mapt::const_iterator m_it=
    goto_functions.function_map.find(identifier);

  if(m_it==goto_functions.function_map.end())
  {
    err_location(function);
    err << "failed to find function `" << identifier
        << "'";
    throw 0;
  }
  
  const goto_functiont &f=m_it->second;

  // see if we need to inline this  
  if(!full)
  {
    if(!f.body_available ||
       !f.is_inlined())
    {
      target++;
      return;
    }
  }

  if(f.body_available)
  {
    recursion_sett::iterator recursion_it=
      recursion_set.insert(identifier).first;  
  
    goto_programt tmp2;
    tmp2.copy_from(f.body);
    
    assert(tmp2.instructions.back().is_end_function());
    
    replace_return(tmp2, lhs, constrain);
    
    goto_programt tmp;
    parameter_assignments(f.type, rhs, tmp);
    tmp.destructive_append(tmp2);
    
    // set local variables
    Forall_goto_program_instructions(it, tmp)
      it->local_variables.insert(target->local_variables.begin(),
                                 target->local_variables.end());

    if(f.type.get_bool("#hide"))
    {
      Forall_goto_program_instructions(it, tmp)
      {
        it->location.set_file(target->location.get_file());
        it->location.set_line(target->location.get_line());
        it->location.set_column(target->location.get_column());
        it->location.set_function(target->location.get_function());
      }
    }

    // do this recursively
    goto_inline_rec(tmp, full);

    // set up location instruction for function call  
    target->type=LOCATION;
    target->code.clear();
    
    goto_programt::targett next_target(target);
    next_target++;

    dest.instructions.splice(next_target, tmp.instructions);
    target=next_target;

    recursion_set.erase(recursion_it);
  }
  else
  {
    if(no_body_set.insert(identifier).second)
    {
      err_location(function);
      err << "no body for function `" << identifier
          << "'";
      warning();
    }

    goto_programt tmp;

    // evaluate function arguments -- they might have
    // pointer dereferencing
    for(unsigned i=1; i<rhs.operands().size(); i++)
    {
      goto_programt::targett t=tmp.add_instruction();
      t->make_other();
      t->location=target->location;
      t->local_variables=target->local_variables;
      t->code=codet("expression");
      t->code.copy_to_operands(rhs.operands()[i]);
    }
    
    // return value
    if(lhs.is_not_nil())
    {
      exprt rhs=exprt("sideeffect", lhs.type());
      rhs.set("statement", "nondet");
      rhs.location()=target->location;

      code_assignt code;
      code.copy_to_operands(lhs, rhs);
      code.location()=target->location;
    
      goto_programt::targett t=tmp.add_instruction();
      t->make_other();
      t->location=target->location;
      t->local_variables=target->local_variables;
      t->code.swap(code);
    }

    // now just kill call
    target->type=LOCATION;
    target->code.clear();
    target++;

    // insert tmp
    dest.instructions.splice(target, tmp.instructions);
  }
}

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

Function: goto_inlinet::goto_inline

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::goto_inline(goto_programt &dest)
{
  goto_inline_rec(dest, true);
  replace_return(dest, (const exprt &)nil_rep, (const exprt &)nil_rep);
}

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

Function: goto_inlinet::goto_inline_rec

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::goto_inline_rec(goto_programt &dest, bool full)
{
  bool changed=false;

  for(goto_programt::instructionst::iterator
      it=dest.instructions.begin();
      it!=dest.instructions.end();
      ) // no it++
  {
    if(it->is_other())
      inline_other(dest, it, changed, full);
    else
      it++;
  }

  if(changed)
  {
    remove_skip(dest);  
    dest.update();
  }
}

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

Function: goto_inlinet::inline_other

  Inputs:

 Outputs:

 Purpose:

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

void goto_inlinet::inline_other(
  goto_programt &dest,
  goto_programt::targett &it,
  bool &changed,
  bool full)
{
  if(it->code.get("statement")=="functioncall")
  {
    expand_functioncall(
      dest, it, (const exprt &)nil_rep, it->code,
      (const exprt &)nil_rep, full);
    // don't change it
    changed=true;
  }
  else if(it->code.get("statement")=="assign" &&
          it->code.operands().size()==2 &&
          it->code.op1().id()=="sideeffect" &&
          it->code.op1().get("statement")=="functioncall")
  {
    expand_functioncall(
      dest, it, it->code.op0(), it->code.op1(),
      (const exprt &)nil_rep, full);
    // don't change it
    changed=true;
  }
  else if(it->code.get("statement")=="bp_constrain" &&
          it->code.operands().size()==2 &&
          it->code.op0().operands().size()==2 &&
          it->code.op0().op1().get("statement")=="functioncall")
  {
    expand_functioncall(
      dest, it, it->code.op0().op0(), it->code.op0().op1(),
      it->code.op1(), full);

    // don't change it
    changed=true;
  }
  else
    it++;
}

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

Function: goto_inline

  Inputs:

 Outputs:

 Purpose:

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

void goto_inline(
  const goto_functionst &goto_functions,
  const namespacet &ns,
  goto_programt &dest,
  message_handlert &message_handler)
{
  goto_inlinet goto_inline(goto_functions, ns, message_handler);

  {
    // find main
    goto_functionst::function_mapt::const_iterator it=
      goto_functions.function_map.find("main");
      
    if(it==goto_functions.function_map.end())
    {
      dest.clear();
      return;
    }
    
    dest.copy_from(it->second.body);
  }

  try
  {
    goto_inline.goto_inline(dest);
  }

  catch(int)
  {
    goto_inline.error();
  }

  catch(const char *e)
  {
    goto_inline.error(e);
  }

  catch(const std::string &e)
  {
    goto_inline.error(e);
  }
  
  if(goto_inline.get_error_found())
    throw 0;
}

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

Function: goto_partial_inline

  Inputs:

 Outputs:

 Purpose:

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

void goto_partial_inline(
  goto_functionst &goto_functions,
  const namespacet &ns,
  message_handlert &message_handler)
{
  goto_inlinet goto_inline(goto_functions, ns, message_handler);

  try
  {
    for(goto_functionst::function_mapt::iterator
        it=goto_functions.function_map.begin();
        it!=goto_functions.function_map.end();
        it++)
      if(it->second.body_available)
        goto_inline.goto_inline_rec(it->second.body, false);
  }

  catch(int)
  {
    goto_inline.error();
  }

  catch(const char *e)
  {
    goto_inline.error(e);
  }

  catch(const std::string &e)
  {
    goto_inline.error(e);
  }

  if(goto_inline.get_error_found())
    throw 0;
}
