/*

File: address.c		(code_location.c)
Author: Neil Cafferkey
Copyright (C) 1999-2001 Neil Cafferkey

This program is free software; you can redistribute it and/or
modify it under the terms of the GNU General Public License
as published by the Free Software Foundation; either version 2
of the License, or (at your option) any later version.

This program 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 General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place - Suite 330, Boston,
MA 02111-1307, USA.

*/

#include "instruction_type.h"
#include "address_protos.h"
#include "address_set_protos.h"
#include "memory_protos.h"
#include "source_program_protos.h"
#include "instruction_protos.h"
#include "reverse_graph_protos.h"
#include "general_protos.h"
#include "string_protos.h"
#include "bank_protos.h"

#include <stdio.h>
#include <assert.h>


/* Function: CreateAddress
 * =======================
 * Creates an Address, but replaces it with any existing equivalent address.
 */

Address CreateAddress(SourceProgram src_prog,UWORD bank_no,ULONG offset)
{
   Bank bank=GetSourceProgramBank(src_prog,offset,bank_no);

   assert(bank!=NULL);

   Address address=GetBankAddress(bank,offset);

   if(address==NULL)
   {
      address=Malloc(sizeof(Address_imp));
      address->bank_no=bank_no;
      address->offset=offset;
      address->saved_flags=0;
      address->instruction=NULL;
      address->disassembly=NULL;
      address->translation=NULL;

      PutAddressInBank(bank,address);

      assert(GetBankAddress(bank,offset)==address);

      PutInSet(GetSourceProgramClosure(src_prog),address);
   }

   return address;
}


/* Function: SetAddressInstruction
 * ===============================
 */

VOID SetAddressInstruction(Address address,Instruction instruction)
{
   address->instruction=instruction;

   return;
}


/* Function: GetAddressInstruction
 * ===============================
 */

Instruction GetAddressInstruction(Address address)
{
   return address->instruction;
}


/* Function: SetSavedFlags
 * =======================
 */

VOID SetSavedFlags(Address address,LONGBITS new_flags)
{
   address->saved_flags|=new_flags;

   return;
}


/* Function: GetSavedFlags
 * =======================
 */

LONGBITS GetSavedFlags(Address address)
{
   return address->saved_flags;
}


/* Function: GetAddressContents
 * ============================
 */

UBYTE *GetAddressContents(Address address,SourceProgram src_prog)
{
   return GetBankContents(GetSourceProgramBank(src_prog,address->offset,
      address->bank_no),address->offset);
}


/* Function: GetAddressDisassembly
 * ===============================
 */

TEXT *GetAddressDisassembly(Address address)
{
   return address->disassembly;
}


/* Function: GetAddressTranslation
 * ===============================
 */

TEXT *GetAddressTranslation(Address address)
{
   return address->translation;
}


/* Function: GetAddressOffset
 * ==========================
 */

ULONG GetAddressOffset(Address address)
{
   return address->offset;
}


/* Function: GetAddressLength
 * ==========================
 */

UBYTE GetAddressLength(Address address)
{
   return GetInstructionLength(address->instruction);
}


/* Function: GetSubsequent
 * =======================
 * Returns addresses that can directly follow the input address. Also
 * incorporates this information into the source program's reverse graph.
 */

AddressSet GetSubsequent(Address address,SourceProgram src_prog)
{
   AddressSet subsequent=CreateSet(),temp_set;
   ULONG instruction_type,temp_offset;
   Instruction instruction;
   UBYTE *address_data=GetAddressContents(address,src_prog),length;
   LONGBITS address_mask=GetSourceProgramAddressMask(src_prog);
   ULONG interrupt_vector=GetMachineInterruptVector(
      GetSourceProgramMachine(src_prog));
   Set bank_set;
   Bank temp_bank;
   Address temp_address;

   /* Find an instruction that matches the data at the input address */

   instruction=GetSourceProgramInstruction(src_prog,address);

   if(instruction==NULL)
   {
      printf("Unmatched instruction. Exiting...\n");
#ifndef NDEBUG
      ShowAddress(address);
#endif
      NoMoreMem();
      exit(20);
   }

   SetAddressInstruction(address,instruction);

   instruction_type=GetInstructionType(instruction);

   length=GetInstructionLength(instruction);

   /* Get no-branch subsequent addresses */

   switch(instruction_type)
   {
   case CONTINUE_1:
      PutInSet(subsequent,GetRelativeAddress(address,1,src_prog));
      break;

   case CONTINUE_2:
   case BRANCH_2:
   case BREAK:
   case NO_OPERAND:
      PutInSet(subsequent,GetRelativeAddress(address,length,src_prog));
      break;

   case CONTINUE_3:
      PutInSet(subsequent,GetRelativeAddress(address,3,src_prog));
      break;
   }

   /* Get branch subsequent addresses */

   switch(instruction_type)
   {

   case JUMP:
   case JSR_3:
      PutInSet(subsequent,CreateAddress(src_prog,address->bank_no,
         GetLittleEndianUWord(address_data+1)&address_mask));
      break;

   case JUMP_MEMORY_INDIRECT:
      printf("Warning: indirect jump at $%04x\n",address->offset);
      break;

   case BRANCH_2:
      PutInSet(subsequent,GetRelativeAddress(address,
         (BYTE)*(address_data+1)+2,src_prog));
      break;

   case BREAK:
      bank_set=GetSourceProgramBanks(src_prog);

      while((temp_bank=TakeFromSet(bank_set))!=NULL)
         if(OffsetIsInBank(temp_bank,interrupt_vector))
         {
            temp_offset=GetLittleEndianUWord(GetBankContents(temp_bank,
               interrupt_vector))&address_mask;
            assert(temp_offset<0x2000);
            temp_address=CreateAddress(src_prog,GetBankNumber(temp_bank),
               temp_offset);
            PutInSet(subsequent,temp_address);
         }
      KillSet(bank_set);

      break;

   case RTS:
      FindLastJSR(src_prog,subsequent,address);
      PutInSet(GetSourceProgramRevisitAgenda(src_prog),address);
      break;
   }

   /* Get bank-switch subsequent addresses */

   switch(instruction_type)
   {
   case BANK_0_SWITCH_3:
      PutInSet(subsequent,CreateAddress(src_prog,0,address->offset+3));
      break;
   case BANK_1_SWITCH_3:
      PutInSet(subsequent,CreateAddress(src_prog,1,address->offset+3));
      break;
   }

   /* Update program's reverse graph */

   temp_set=DupSet(subsequent);

   while(!IsEmptySet(temp_set))
      ReverseConnect(GetSourceProgramReverseGraph(src_prog),
         TakeFromSet(temp_set),address);

   KillSet(temp_set);

   return subsequent;
}


/* Function: DisassembleAddress
 * ============================
 */

VOID DisassembleAddress(Address address,SourceProgram src_prog)
{
   static TEXT string[500];

   TEXT *template=GetDisassemblyTemplate(address->instruction);
   UBYTE *data=GetAddressContents(address,src_prog);

   switch(GetInstructionType(address->instruction))
   {
   case CONTINUE_2:
      sprintf(string,template,*(data+1));
      break;
   case CONTINUE_3:
   case JSR_3:
      sprintf(string,template,GetLittleEndianUWord(data+1));
      break;
   case JUMP:
   case JUMP_MEMORY_INDIRECT:
   case BANK_0_SWITCH_3:
   case BANK_1_SWITCH_3:
      sprintf(string,template,GetLittleEndianUWord(data+1));
      strcat(string,"\n");
      break;
   case BRANCH_2:
      sprintf(string,template,address->offset+2+(BYTE)*(data+1));
      break;
   case RTS:
   case RTI:
      sprintf(string,"%s\n",template);
      break;
   default:
      strcpy(string,template);
   }

   address->disassembly=(String)strcpy(CreateString(strlen(string)),string);

   return;
}


/* Function: TranslateAddress
 * ==========================
 */

VOID TranslateAddress(Address address,SourceProgram src_prog)
{
   static TEXT string[500];

   TEXT *template=GetTranslationTemplate(address->instruction);
   UBYTE *data=GetAddressContents(address,src_prog);
   LONGBITS mask=GetSourceProgramAddressMask(src_prog);
   ULONG dest;

   switch(GetInstructionType(address->instruction))
   {
   case CONTINUE_2:
      sprintf(string,template,*(data+1));
      break;
   case CONTINUE_3:
   case JSR_3:
      sprintf(string,template,GetLittleEndianUWord(data+1)&mask);
      break;
   case JUMP:
   case JUMP_MEMORY_INDIRECT:
   case BANK_0_SWITCH_3:
   case BANK_1_SWITCH_3:
      sprintf(string,template,GetLittleEndianUWord(data+1)&mask);
      strcat(string,"\n");
      break;
   case BRANCH_2:
      dest=address->offset+2+(BYTE)*(data+1);
      sprintf(string,template,dest,
         (((((address->offset+2)&0xff)+(BYTE)*(data+1))>>8)==0)?0:1);
      break;
   case RTS:
   case RTI:
      sprintf(string,"%s\n",template);
      break;
   default:
      strcpy(string,template);
   }

   address->translation=(String)strcpy(CreateString(strlen(string)),string);

   return;
}


/* Function: IsSameAddress
 * =======================
 */

BOOL IsSameAddress(Address a1,Address a2)
{
   return (a1->bank_no==a2->bank_no)&&(a1->offset==a2->offset);
}


/* Function: FindLastFlags
 * =======================
 */

VOID FindLastFlags(LONGBITS critical_flags,ReverseGraph rev_graph,
   AddressSet visited,Address address)
{
   Address temp_address;
   AddressSet rev_adj=GetReverseAdjacent(rev_graph,address);
   LONGBITS matched_flags;

   while((temp_address=TakeFromSet(rev_adj))!=NULL)
   {
      if(!IsInSet(visited,temp_address))
      {
         PutInSet(visited,temp_address);
         matched_flags=GetModifiedFlags(temp_address->instruction)
            &critical_flags;
         SetSavedFlags(temp_address,matched_flags);
         if(matched_flags!=critical_flags)
            FindLastFlags(critical_flags^matched_flags,rev_graph,visited,
               temp_address);
      }
   }

   KillSet(rev_adj);

   return;
}


/* Function: SetUpCriticalFlags
 * ============================
 * Ensures that an address's critical flags are saved the last time they are
 * modified before the address's instruction is executed.
 */

VOID SetUpCriticalFlags(Address address,SourceProgram src_prog)
{
   AddressSet visited;

   if(GetCriticalFlags(address->instruction)!=0)
   {
      visited=CreateSet();
      FindLastFlags(GetCriticalFlags(address->instruction),
         GetSourceProgramReverseGraph(src_prog),visited,address);
      KillSet(visited);
   }

   return;
}


/* Function: FindLastJSR
 * =====================
 */

VOID FindLastJSR(SourceProgram src_prog,AddressSet subsequent,
   Address address)
{
   Address temp_address;
   AddressSet unexplored=CreateSet(),visited=CreateSet(),rev_adj;
   UWORD instruction_type;
   ReverseGraph rev_graph=GetSourceProgramReverseGraph(src_prog);

   PutInSet(unexplored,address);

   while(!IsEmptySet(unexplored))
   {
      address=TakeFromSet(unexplored);
      rev_adj=GetReverseAdjacent(rev_graph,address);

      while(!IsEmptySet(rev_adj))
      {
         temp_address=TakeFromSet(rev_adj);

         if(!IsInSet(visited,temp_address))
         {
            instruction_type=GetInstructionType(
               GetAddressInstruction(temp_address));

            if(instruction_type==RTS)
            {
               PutInSet(unexplored,GetRelativeAddress(address,-3,
                  src_prog));
            }

            else if(instruction_type==JSR_3)
            {
               PutInSet(subsequent,GetRelativeAddress(temp_address,3,
                  src_prog));
               PutInSet(visited,temp_address);
            }

            else
            {
               PutInSet(unexplored,temp_address);
               PutInSet(visited,temp_address);
            }
         }
      }

      KillSet(rev_adj);
   }

   KillSet(unexplored);
   KillSet(visited);

   return;
}


/* GetRelativeAddress
 * ==================
 */

Address GetRelativeAddress(Address address,LONG displacement,
                           SourceProgram src_prog)
{
   return CreateAddress(src_prog,address->bank_no,
      address->offset+displacement);
}


/* Function: KillAddress
 * =====================
 */

VOID KillAddress(Address address)
{
   Free(address,sizeof(Address_imp));

   return;
}


#ifndef NDEBUG

/* Function: ShowAddress
 * =====================
 */

VOID ShowAddress(Address address)
{
   printf("Address %u:",address);
   printf("\tbank_no=%u",address->bank_no);
   printf("\toffset=$%x\n",address->offset);

   return;
}

#endif


