/*

File: source_program.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 "source_program_protos.h"
#include "memory_protos.h"
#include "bank_protos.h"
#include "address_set_protos.h"
#include "general_protos.h"
#include "address_protos.h"
#include "machine_protos.h"
#include "instruction_set_protos.h"
#include "reverse_graph_protos.h"
#include "segment_protos.h"
#include "config_protos.h"


/* Function: CreateSourceProgram
 * =============================
 */

SourceProgram CreateSourceProgram(TEXT *name,UWORD year,TEXT *owner,
   Machine machine,InstructionSet instructions)
{
   SourceProgram src_prog=Malloc(sizeof(SourceProgram_imp));

   src_prog->name=name;
   src_prog->year=year;
   src_prog->owner=owner;
   src_prog->machine=machine;
   src_prog->segment_count=0;
   src_prog->closure=CreateSet();
   src_prog->revisit_agenda=CreateSet();
   src_prog->rev_graph=CreateReverseGraph();
   src_prog->instructions=instructions;

   return src_prog;
}


/* Function: ReadSourceProgram
 * ===========================
 */

SourceProgram ReadSourceProgram(TEXT *file_name)
{
   Config config=ReadConfig(file_name);
   TEXT *name=GetStringConfigOption(config,"Name:");
   UWORD year; /*=GetNumericConfigOption(config,"Year:");*/
   TEXT *owner; /*=GetStringConfigOption(config,"Owner:");*/
   Machine machine=ReadMachine(GetStringConfigOption(config,"Machine:"));
   InstructionSet instructions=
      ReadInstructionSet(GetStringSetConfigOption(config,"Instructions:"));
   SourceProgram src_prog=CreateSourceProgram(name,year,owner,machine,
      instructions);
   StringSet segment_names=GetStringSetConfigOption(config,"Segments:");

   while(!IsEmptySet(segment_names))
      PutSegmentInSourceProgram(src_prog,
         ReadSegment(TakeFromSet(segment_names)));

   KillConfig(config);

   return src_prog;
}


/* Function: PutSegmentInSourceProgram
 * ===================================
 */

VOID PutSegmentInSourceProgram(SourceProgram src_prog,Segment segment)
{
   src_prog->segments[src_prog->segment_count++]=segment;

   return;
}


/* Function: GetEntryPoints
 * ========================
 */

AddressSet GetEntryPoints(SourceProgram src_prog)
{
   AddressSet entry_points=CreateSet();
   Set bank_set=GetSourceProgramBanks(src_prog);
   Bank bank;

   ULONG entry_vector=GetMachineEntryVector(src_prog->machine);

   while((bank=TakeFromSet(bank_set))!=NULL)
      if(OffsetIsInBank(bank,entry_vector))
         PutInSet(entry_points,CreateAddress(src_prog,GetBankNumber(bank),
            GetLittleEndianUWord(GetBankContents(bank,entry_vector))&
            GetSourceProgramAddressMask(src_prog)));

   KillSet(bank_set);

   return entry_points;
}


/* Function: GetInterruptPoints
 * ============================
 */

AddressSet GetInterruptPoints(SourceProgram src_prog)
{
   AddressSet interrupt_points=CreateSet();
   Set bank_set=GetSourceProgramBanks(src_prog),temp_bank_set_1,
      temp_bank_set_2;
   Bank bank,offset_bank;
   LONGBITS address_mask=GetSourceProgramAddressMask(src_prog);
   ULONG interrupt_vector=GetMachineInterruptVector(src_prog->machine);
   Segment vector_segment;
   ULONG interrupt_offset;
   UWORD i;

   /* Find out which segment the interrupt vector is in */

   for(i=0;(i<MAX_SEG_COUNT)&&!OffsetIsInSegment(src_prog->segments[i],
      interrupt_vector);i++);
   vector_segment=src_prog->segments[i];

   /* Only look for interrupt points if there is a segment containing the
    * interrupt vector. */

   if(i<MAX_SEG_COUNT)
   {

      /* Find out which banks the interrupt vector is in */

      temp_bank_set_1=DupSet(bank_set);

      while((bank=TakeFromSet(temp_bank_set_1))!=NULL)
      {
         if(OffsetIsInBank(bank,interrupt_vector))
         {

            /* Get the interrupt offset held at this bank's interrupt vector */

            interrupt_offset=GetLittleEndianUWord(GetBankContents(
               bank,interrupt_vector))&address_mask;

            /* If the offset is in the same segment as the vector, then they
             * must be in the same bank. */

            if(OffsetIsInSegment(vector_segment,interrupt_offset))
            {
               if(OffsetIsInBank(bank,interrupt_offset))
                  PutInSet(interrupt_points,CreateAddress(src_prog,
                     GetBankNumber(bank),interrupt_offset));
            }

            /* Otherwise, search for all banks containing the current interrupt
             * offset. */

            else
            {
               temp_bank_set_2=DupSet(bank_set);

               while((offset_bank=TakeFromSet(temp_bank_set_2))!=NULL)
                  if(OffsetIsInBank(offset_bank,interrupt_offset))
                     PutInSet(interrupt_points,CreateAddress(src_prog,
                        GetBankNumber(offset_bank),interrupt_offset));

               KillSet(temp_bank_set_2);
            }
         }
      }

      KillSet(temp_bank_set_1);
   }

   KillSet(bank_set);

   return interrupt_points;
}


/* Function: FindInstructions
 * ==========================
 */

VOID FindInstructions(SourceProgram src_prog)
{
   AddressSet visited,unexplored,subsequent;
   Address address,next_address;
   BOOL found_new_addresses=TRUE;

   /* Initialise search sets with program's entry points */

   unexplored=GetEntryPoints(src_prog);
   visited=DupSet(unexplored);

   /* Keep looking for new addresses until no more are being found */

   while(found_new_addresses)
   {

      /* Explore all currently known addresses */

      found_new_addresses=FALSE;
      while(!IsEmptySet(unexplored))
      {
         address=TakeFromSet(unexplored);
         subsequent=GetSubsequent(address,src_prog);

         /* Add new addresses to search sets and record whether any new
          * addresses have been found. */

         while(!IsEmptySet(subsequent))
         {
            next_address=TakeFromSet(subsequent);
            if(!IsInSet(visited,next_address))
            {
               found_new_addresses=TRUE;
               PutInSet(visited,next_address);
               PutInSet(unexplored,next_address);
            }
         }
         KillSet(subsequent);
      }

      /* Kill the empty "unexplored" set and replace it with the known
       * revisitable addresses */

      KillSet(unexplored);
      unexplored=DupSet(GetSourceProgramRevisitAgenda(src_prog));
   }

   return;
}


/* Function: DisassembleSourceProgram
 * ==================================
 * Disassembles all known source program code.
 */

VOID DisassembleSourceProgram(SourceProgram src_prog)
{
   AddressSet address_set=DupSet(GetSourceProgramClosure(src_prog));

   while(!IsEmptySet(address_set))
      DisassembleAddress(TakeFromSet(address_set),src_prog);

   KillSet(address_set);

   return;
}


/* Function: TranslateSourceProgram
 * ================================
 */

VOID TranslateSourceProgram(SourceProgram src_prog)
{
   Address address;
   AddressSet closure=DupSet(GetSourceProgramClosure(src_prog));

   while((address=TakeFromSet(closure))!=NULL)
   {
      SetUpCriticalFlags(address,src_prog);
      TranslateAddress(address,src_prog);
   }

   return;
}


/* Function: GetSourceProgramName
 * ==============================
 */

TEXT *GetSourceProgramName(SourceProgram src_prog)
{
   return src_prog->name;
}


/* Function: GetSourceProgramMachine
 * =================================
 */

Machine GetSourceProgramMachine(SourceProgram src_prog)
{
   return src_prog->machine;
}


/* Function: GetSourceProgramClosure
 * =================================
 */

AddressSet GetSourceProgramClosure(SourceProgram src_prog)
{
   return src_prog->closure;
}


/* Function: GetSourceProgramRevisitAgenda
 * =======================================
 * Returns a source program's set of revisitable addresses.
 */

AddressSet GetSourceProgramRevisitAgenda(SourceProgram src_prog)
{
   return src_prog->revisit_agenda;
}


/* Function: GetSourceProgramReverseGraph
 * ======================================
 */

ReverseGraph GetSourceProgramReverseGraph(SourceProgram src_prog)
{
   return src_prog->rev_graph;
}


/* Function: GetSourceProgramInstruction
 * =====================================
 */

Instruction GetSourceProgramInstruction(SourceProgram src_prog,
   Address address)
{
   Instruction instruction=GetMatchingInstruction(src_prog->instructions,
      address,src_prog);

   if(instruction==NULL)
      instruction=GetMachineInstruction(src_prog->machine,address,src_prog);

   return instruction;
}


/* Function: GetSourceProgramBank
 * ==============================
 */

Bank GetSourceProgramBank(SourceProgram src_prog,ULONG offset,
   UWORD bank_no)
{
   ULONG i;
   BOOL found=FALSE;

   for(i=0;(i<src_prog->segment_count)&&!found;i++)
      found=OffsetIsInSegment(src_prog->segments[i],offset);

   if(found)
      return GetSegmentBank(src_prog->segments[i-1],bank_no);
   else
      return NULL;
}


/* Function: GetSourceProgramBanks
 * ===============================
 */

Set GetSourceProgramBanks(SourceProgram src_prog)
{
   Set set=CreateSet();
   ULONG i,j;
   Segment segment;
   Bank bank;

   for(i=0;i<src_prog->segment_count;i++)
   {
      segment=src_prog->segments[i];
      for(j=0;(bank=GetSegmentBank(segment,j))!=NULL;j++)
         PutInSet(set,bank);
   }

   return set;
}


/* Function: GetSourceProgramAddressMask
 * =====================================
 */

LONGBITS GetSourceProgramAddressMask(SourceProgram src_prog)
{
   return GetMachineAddressMask(src_prog->machine);
}


/* Function: KillSourceProgram
 * ===========================
 */

VOID KillSourceProgram(SourceProgram src_prog)
{
   ULONG i;

   for(i=0;i<src_prog->segment_count;i++)
      KillSegment(src_prog->segments[i]);
   Free(src_prog,sizeof(SourceProgram_imp));

   return;
}


