/*
 * See the dyninst/COPYRIGHT file for copyright information.
 * 
 * We provide the Paradyn Tools (below described as "Paradyn")
 * on an AS IS basis, and do not warrant its validity or performance.
 * We reserve the right to update, modify, or discontinue this
 * software at any time.  We shall have no obligation to supply such
 * updates or modifications or any other form of support to you.
 * 
 * By your use of Paradyn, you understand and agree that we (or any
 * other person or entity with proprietary rights in Paradyn) are
 * under no obligation to provide either maintenance services,
 * update services, notices of latent defects, or correction of
 * defects for Paradyn.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#define INSIDE_INSTRUCTION_API

#include "common/src/vgannotations.h"
#include "common/src/Types.h"

#include "Operation_impl.h"
#include "common/src/arch-x86.h"
#include "entryIDs.h"
#include "common/src/Singleton.h"
#include "Register.h"
#include <map>
#include <mutex>
#include "concurrent.h"
#include "common/src/singleton_object_pool.h"

using namespace NS_x86;
#include "BinaryFunction.h"
#include "Immediate.h"

namespace Dyninst
{
  namespace InstructionAPI
  {
    RegisterAST::Ptr makeRegFromID(MachRegister regID, unsigned int low, unsigned int high)
    {
      return make_shared(singleton_object_pool<RegisterAST>::construct(regID, low, high));
    }
    RegisterAST::Ptr makeRegFromID(MachRegister regID)
    {
        return make_shared(singleton_object_pool<RegisterAST>::construct(regID, 0, regID.size() * 8));
    }

    Operation_impl::Operation_impl(entryID id, std::string m, Architecture arch)
        : operationID(id), archDecodedFrom(arch), prefixID(prefix_none)
    {
        switch(archDecodedFrom)
        {
            case Arch_x86:
            case Arch_ppc32:
                addrWidth = u32;
                break;
            default:
                addrWidth = u64;
                break;
        }
        segPrefix = 0;
        isVectorInsn = false;
        mnemonic = m;
    }

    static bool getVectorizationInfo(ia32_entry* e)
    {
        for(int i = 0; i < 3; i++)
        {
            switch(e->operands[i].admet)
            {
                case am_V:
                case am_W:
                case am_P:
                case am_Q:
                case am_HK:
                case am_H:
                case am_X:
                case am_XH:
                case am_XU:
                case am_XV:
                case am_XW:
                case am_Y:
                case am_YH:
                case am_YU:
                case am_YV:
                case am_YW:
                case am_VK:
                case am_WK:

                    return true;
                default:
                    break;
            }
        }
        return false;
    }
    
    Operation_impl::Operation_impl(ia32_entry* e, ia32_prefixes* p, ia32_locations* l, Architecture arch) :
        archDecodedFrom(arch), prefixID(prefix_none)
    {
      segPrefix = 0;
      isVectorInsn = getVectorizationInfo(e);
      operationID = e->getID(l);
      // Defaults for no size prefix
      switch(archDecodedFrom)
      {
          case Arch_x86:
          case Arch_ppc32:
              addrWidth = u32;
              break;
          default:
              addrWidth = u64;
              break;
      }
      if(p && p->getCount())
      {
        if (p->getPrefix(0) == PREFIX_REP) prefixID = prefix_rep;
        if (p->getPrefix(0) == PREFIX_REPNZ) prefixID = prefix_repnz;
        segPrefix = p->getPrefix(1);
        if(p->getAddrSzPrefix())
        {
            addrWidth = u16;
        }
      }
    }

    Operation_impl::Operation_impl(const Operation_impl& o)
    {
      operationID = o.operationID;
      archDecodedFrom = o.archDecodedFrom;
      prefixID = o.prefixID;
      addrWidth = o.addrWidth;
      segPrefix = o.segPrefix;
      isVectorInsn = o.isVectorInsn;
      mnemonic = o.mnemonic;
    }
    const Operation_impl& Operation_impl::operator=(const Operation_impl& o)
    {
      operationID = o.operationID;
      archDecodedFrom = o.archDecodedFrom;
      prefixID = o.prefixID;
      addrWidth = o.addrWidth;
      segPrefix = o.segPrefix;
      isVectorInsn = o.isVectorInsn;
      mnemonic = o.mnemonic;
      return *this;
    }
    Operation_impl::Operation_impl()
    {
      operationID = e_No_Entry;
      archDecodedFrom = Arch_none;
      prefixID = prefix_none;
      addrWidth = u64;
      segPrefix = 0;
      isVectorInsn = false;
    }
    
    const Operation_impl::registerSet&  Operation_impl::implicitReads()
    {
      SetUpNonOperandData();
      
      return otherRead;
    }
    const Operation_impl::registerSet&  Operation_impl::implicitWrites()
    {
      SetUpNonOperandData();

      return otherWritten;
    }
    bool Operation_impl::isRead(Expression::Ptr candidate)
    {
     
	SetUpNonOperandData();
     
      for(registerSet::const_iterator r = otherRead.begin();
	  r != otherRead.end();
	  ++r)
      {
	if(*candidate == *(*r))
	{
	  return true;
	}
      }
      for(VCSet::const_iterator e = otherEffAddrsRead.begin();
	  e != otherEffAddrsRead.end();
	  ++e)
      {
	if(*candidate == *(*e))
	{
	  return true;
	}
      }
      return false;
    }
    const Operation_impl::VCSet& Operation_impl::getImplicitMemReads()
    {
      SetUpNonOperandData();
      return otherEffAddrsRead;
    }
    const Operation_impl::VCSet& Operation_impl::getImplicitMemWrites()
    {
      SetUpNonOperandData();
      return otherEffAddrsWritten;
    }

    bool Operation_impl::isWritten(Expression::Ptr candidate)
    {
     
	SetUpNonOperandData();
      
      for(registerSet::const_iterator r = otherWritten.begin();
	  r != otherWritten.end();
	  ++r)
      {
	if(*candidate == *(*r))
	{
	  return true;
	}
      }
      for(VCSet::const_iterator e = otherEffAddrsWritten.begin();
	  e != otherEffAddrsWritten.end();
	  ++e)
      {
	if(*candidate == *(*e))
	{
	  return true;
	}
      }
      return false;
    }
	  
    std::string Operation_impl::format() const
    {
        if(mnemonic != "")
        {
            return mnemonic;
        }
      dyn_hash_map<prefixEntryID, std::string>::const_iterator foundPrefix = prefixEntryNames_IAPI.find(prefixID);
      dyn_hash_map<entryID, std::string>::const_iterator found = entryNames_IAPI.find(operationID);
      std::string result;
      if(foundPrefix != prefixEntryNames_IAPI.end())
      {
        result += (foundPrefix->second + " ");
      }
      if(found != entryNames_IAPI.end())
      {
	result += found->second;
      }
      else
      {
        result += "[INVALID]";
      }
      return result;
    }

    entryID Operation_impl::getID() const
    {
      return operationID;
    }

    prefixEntryID Operation_impl::getPrefixID() const
    {
      return prefixID;
    }

    struct OperationMaps
    {
        typedef dyn_c_hash_map<entryID, Operation_impl::registerSet > reg_info_t;
        typedef dyn_c_hash_map<entryID, Operation_impl::VCSet > mem_info_t;
    public:
      OperationMaps(Architecture arch)
      {
          thePC.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getPC(arch))));
          pcAndSP.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getPC(arch))));
          pcAndSP.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getStackPointer(arch))));
          stackPointer.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getStackPointer(arch))));
          stackPointerAsExpr.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getStackPointer(arch))));
          framePointer.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getFramePointer(arch))));
          spAndBP.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getStackPointer(arch))));
          spAndBP.insert(RegisterAST::Ptr(new RegisterAST(MachRegister::getFramePointer(arch))));
          si.insert(RegisterAST::Ptr(new RegisterAST(arch == Arch_x86_64 ? x86_64::rsi : x86::esi)));
          di.insert(RegisterAST::Ptr(new RegisterAST(arch == Arch_x86_64 ? x86_64::rdi : x86::edi)));
          si_and_di.insert(RegisterAST::Ptr(new RegisterAST(arch == Arch_x86_64 ? x86_64::rsi : x86::esi)));
          si_and_di.insert(RegisterAST::Ptr(new RegisterAST(arch == Arch_x86_64 ? x86_64::rdi : x86::edi)));
	
          nonOperandRegisterReads.insert(make_pair(e_call, pcAndSP));
          nonOperandRegisterReads.insert(make_pair(e_ret_near, stackPointer));
          nonOperandRegisterReads.insert(make_pair(e_ret_far, stackPointer));
          nonOperandRegisterReads.insert(make_pair(e_leave, framePointer));
          nonOperandRegisterReads.insert(make_pair(e_enter, spAndBP));

          nonOperandRegisterWrites.insert(make_pair(e_call, pcAndSP));
          nonOperandRegisterWrites.insert(make_pair(e_ret_near, pcAndSP));
          nonOperandRegisterWrites.insert(make_pair(e_ret_far, pcAndSP));
          nonOperandRegisterWrites.insert(make_pair(e_leave, spAndBP));
          nonOperandRegisterWrites.insert(make_pair(e_enter, spAndBP));

          nonOperandRegisterWrites.insert(make_pair(e_loop, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_loope, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_loopn, thePC));

          nonOperandRegisterWrites.insert(make_pair(e_jb, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jb_jnaej_j, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jbe, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jcxz_jec, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jl, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jle, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jmp, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnb, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnb_jae_j, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnbe, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnl, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnle, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jno, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnp, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jns, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jnz, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jo, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jp, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_js, thePC));
          nonOperandRegisterWrites.insert(make_pair(e_jz, thePC));

          nonOperandMemoryReads.insert(make_pair(e_pop, stackPointerAsExpr));
          nonOperandMemoryReads.insert(make_pair(e_popa, stackPointerAsExpr));
          nonOperandMemoryReads.insert(make_pair(e_popad, stackPointerAsExpr));
          nonOperandMemoryWrites.insert(make_pair(e_push, stackPointerAsExpr));
          nonOperandMemoryWrites.insert(make_pair(e_pusha, stackPointerAsExpr));
          nonOperandMemoryWrites.insert(make_pair(e_pushad, stackPointerAsExpr));
          nonOperandMemoryWrites.insert(make_pair(e_call, stackPointerAsExpr));
          nonOperandMemoryReads.insert(make_pair(e_ret_near, stackPointerAsExpr));
          nonOperandMemoryReads.insert(make_pair(e_ret_far, stackPointerAsExpr));
          nonOperandMemoryReads.insert(make_pair(e_leave, stackPointerAsExpr));

          nonOperandRegisterWrites.insert(make_pair(e_cmpsb, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_cmpsd, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_cmpsw, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_movsb, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_movsd, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_movsw, si_and_di));
          nonOperandRegisterWrites.insert(make_pair(e_insb, di));
          nonOperandRegisterWrites.insert(make_pair(e_insd, di));
          nonOperandRegisterWrites.insert(make_pair(e_insw, di));
          nonOperandRegisterWrites.insert(make_pair(e_stosb, di));
          nonOperandRegisterWrites.insert(make_pair(e_stosd, di));
          nonOperandRegisterWrites.insert(make_pair(e_stosw, di));
          nonOperandRegisterWrites.insert(make_pair(e_scasb, di));
          nonOperandRegisterWrites.insert(make_pair(e_scasd, di));
          nonOperandRegisterWrites.insert(make_pair(e_scasw, di));
          nonOperandRegisterWrites.insert(make_pair(e_lodsb, di));
          nonOperandRegisterWrites.insert(make_pair(e_lodsd, di));
          nonOperandRegisterWrites.insert(make_pair(e_lodsw, di));
          nonOperandRegisterWrites.insert(make_pair(e_outsb, di));
          nonOperandRegisterWrites.insert(make_pair(e_outsd, di));
          nonOperandRegisterWrites.insert(make_pair(e_outsw, di));


      }
      Operation_impl::registerSet thePC;
      Operation_impl::registerSet pcAndSP;
      Operation_impl::registerSet stackPointer;
      Operation_impl::VCSet stackPointerAsExpr;
      Operation_impl::registerSet framePointer;
      Operation_impl::registerSet spAndBP;
      Operation_impl::registerSet si;
      Operation_impl::registerSet di;
      Operation_impl::registerSet si_and_di;

      reg_info_t nonOperandRegisterReads;
      reg_info_t nonOperandRegisterWrites;

      mem_info_t nonOperandMemoryReads;
      mem_info_t nonOperandMemoryWrites;
    };
    OperationMaps op_data_32(Arch_x86);
    OperationMaps op_data_64(Arch_x86_64);
    const OperationMaps& op_data(Architecture arch)
    {
        switch(arch)
        {
            case Arch_x86:
                return op_data_32;
            case Arch_x86_64:
                return op_data_64;
            default:
                return op_data_32;
        }
    }
    void Operation_impl::SetUpNonOperandData()
    {
        #if defined(arch_x86) || defined(arch_x86_64)
        std::call_once(data_initialized, [&]() {
        if (prefixID == prefix_rep || prefixID == prefix_repnz) 	{
            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::df : x86_64::df));
            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::ecx : x86_64::rcx));
            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::ecx : x86_64::rcx));
            if(prefixID == prefix_repnz)
            {
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::zf : x86_64::zf));
            }
        }
        switch(segPrefix)
        {
            case PREFIX_SEGCS:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::cs : x86_64::cs));
                break;
            case PREFIX_SEGDS:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::ds : x86_64::ds));
                break;
            case PREFIX_SEGES:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::es : x86_64::es));
                break;
            case PREFIX_SEGFS:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::fs : x86_64::fs));
                break;
            case PREFIX_SEGGS:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::gs : x86_64::gs));
                break;
            case PREFIX_SEGSS:
                otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::ss : x86_64::ss));
                break;
        }

            OperationMaps::reg_info_t::const_accessor a, b;
            if (op_data(archDecodedFrom).nonOperandRegisterReads.find(a, operationID)) {
                otherRead.insert(a->second.begin(), a->second.end());
            }
            if (op_data(archDecodedFrom).nonOperandRegisterWrites.find(b, operationID)) {
                otherWritten.insert(b->second.begin(), b->second.end());
            }
            OperationMaps::mem_info_t::const_accessor c, d;
            if (op_data(archDecodedFrom).nonOperandMemoryReads.find(c, operationID)) {
                otherEffAddrsRead.insert(c->second.begin(), c->second.end());
            }
            if (operationID == e_push) {
                BinaryFunction::funcT::Ptr adder(new BinaryFunction::addResult());
                // special case for push: we write at the new value of the SP.
                Result dummy(addrWidth, 0);
                Expression::Ptr push_addr(new BinaryFunction(
                        *(op_data(archDecodedFrom).stackPointerAsExpr.begin()),
                        Immediate::makeImmediate(Result(s8, -(dummy.size()))),
                        addrWidth,
                        adder));

                otherEffAddrsWritten.insert(push_addr);

            } else {
                if (op_data(archDecodedFrom).nonOperandMemoryWrites.find(d, operationID)) {
                    otherEffAddrsWritten.insert(d->second.begin(), d->second.end());
                }
            }

            dyn_hash_map<entryID, flagInfo>::const_iterator found = ia32_instruction::getFlagTable().find(operationID);
            if (found != ia32_instruction::getFlagTable().end()) {
                for (unsigned i = 0; i < found->second.readFlags.size(); i++) {
                    switch (found->second.readFlags[i]) {
                        case x86::icf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::cf : x86_64::cf));
                            break;
                        case x86::ipf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::pf : x86_64::pf));
                            break;
                        case x86::iaf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::af : x86_64::af));
                            break;
                        case x86::izf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::zf : x86_64::zf));
                            break;
                        case x86::isf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::sf : x86_64::sf));
                            break;
                        case x86::itf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::tf : x86_64::tf));
                            break;
                        case x86::idf:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::df : x86_64::df));
                            break;
                        case x86::iof:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::of : x86_64::of));
                            break;
                        case x86::int_:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::nt_ : x86_64::nt_));
                            break;
                        case x86::iif_:
                            otherRead.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::if_ : x86_64::if_));
                            break;
                        default:
                            assert(0);
                    }
                }

                for (unsigned j = 0; j < found->second.writtenFlags.size(); j++) {
                    switch (found->second.writtenFlags[j]) {
                        case x86::icf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::cf : x86_64::cf));
                            break;
                        case x86::ipf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::pf : x86_64::pf));
                            break;
                        case x86::iaf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::af : x86_64::af));
                            break;
                        case x86::izf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::zf : x86_64::zf));
                            break;
                        case x86::isf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::sf : x86_64::sf));
                            break;
                        case x86::itf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::tf : x86_64::tf));
                            break;
                        case x86::idf:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::df : x86_64::df));
                            break;
                        case x86::iof:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::of : x86_64::of));
                            break;
                        case x86::int_:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::nt_ : x86_64::nt_));
                            break;
                        case x86::iif_:
                            otherWritten.insert(makeRegFromID((archDecodedFrom == Arch_x86) ? x86::if_ : x86_64::if_));
                            break;
                        default:
                            fprintf(stderr, "ERROR: unhandled entry %s\n",
                                    found->second.writtenFlags[j].name().c_str());
                            assert(0);
                    }
                }
            }
        });
#endif
    return;
    }
  }

};
