/* exp_tios.c: Routines to export to a TIOS file

   Copyright (C) 2002-2003 Sebastian Reichelt

   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, 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 "exp_tios.h"

#ifdef TIOS_SUPPORT

#include <string.h>

#include "../manip.h"
#include "../special.h"
#include "../formats/tios.h"

// Get the file size needed to export the program into a TIOS file.
// Returns 0 on failure.
// Call this function once to receive necessary error messages.
SIZE GetTIOSFileSize (const PROGRAM *Program)
{
	// Get a pointer to the main section.
	SECTION *MainSection = Program->MainSection;
	
	if (!MainSection)
	{
		Error (NULL, "No main section.");
		return 0;
	}
	
	{
		// Start with the size of the section.
		// Add 2 for the two null bytes at the beginning of the reloc table.
		SIZE Size = MainSection->Size + 2;
		
		if (!(IsEmpty (MainSection->Relocs)))
		{
			RELOC *Reloc;
			
			// Add the size needed for the relocs.
			for_each (Reloc, MainSection->Relocs)
			{
				// If this can be resolved to a calculator-dependent value, ignore the
				// reloc for now.
				if (IsPlainCalcBuiltin (Reloc))
					continue;
				
				// We can only emit 4-byte absolute relocs.
				if (Reloc->Relative || (Reloc->Size != 4))
				{
					Error (GetFileName (MainSection, Reloc->Location), "Cannot emit %ld byte %s reloc to `%s'.", (long) Reloc->Size, Reloc->Relative ? "relative" : "absolute", Reloc->Target.SymbolName);
					Size = 0;
				}
				
				if (Size)
					// A reloc takes 4 bytes: 2 bytes for the location and 2 bytes for the target offset.
					Size += 4;
			}
		}
		
		return Size;
	}
}

// Export the internal data structures into a TIOS file.
BOOLEAN ExportTIOSFile (const PROGRAM *Program, EXP_FILE *File, SIZE FileSize, ProgramCalcs DestCalc)
{
	// A simple macro to make the code more readable.
#define FailWithError(Err...) ({ Error (Err); return FALSE; })
	
	const char *SectionFileName = NULL;
	OFFSET DataStart = 0;
	
	COUNT EmittedRelocCount = 0;
	
	// Get a pointer to the main section.
	SECTION *MainSection = Program->MainSection;
	
	if (!MainSection)
		FailWithError (NULL, "No main section.");
	
	SectionFileName = MainSection->FileName;
	
	// In nostub mode, execution simply starts at the beginning of
	// the first section. But ideally, startup code should always be
	// marked as such. When merging sections, the destination startup
	// number is kept, so in the end, the complete code is marked as
	// a startup section (which makes sense; it is startable).
	if (!(MainSection->StartupNumber || (Program->Type == PT_NOSTUB)))
		FailWithError (SectionFileName, "No startup section(s) defined.");
	
	if (!(MainSection->Data))
		FailWithError (SectionFileName, "No section contents.");
	
	// Write out the section contents first.
	DataStart = ExportTell (File);
	ExportWrite (File, MainSection->Data, MainSection->Size, 1);
	
	// Write out two zero bytes as a separator.
	ExportWriteTI2 (File, 0);
	
	if (!(IsEmpty (MainSection->Relocs)))
	{
		RELOC *Reloc;
		
		// Write out the relocation table.
		for (Reloc = GetLast (MainSection->Relocs); Reloc; Reloc = GetPrev (Reloc))
		{
			// Get the current file name for error messages.
			const char *CurFileName = GetFileName (MainSection, Reloc->Location);
			
			// If this can be resolved to a calculator-dependent value, write the
			// value into the section data.
			if (EmitCalcBuiltinValue (Reloc, DestCalc, File, FileSize, DataStart))
				continue;
			
			// We can only emit relocs with a target symbol in the same section.
			if (!(Reloc->Target.Symbol))
				FailWithError (CurFileName, "Unresolved reference to `%s'.", Reloc->Target.SymbolName);
			if (Reloc->Target.Symbol->Parent != MainSection)
				FailWithError (CurFileName, "Cannot emit reloc to `%s' in different section.", Reloc->Target.SymbolName);
			
			// We can only emit 4-byte absolute relocs.
			if (Reloc->Relative || (Reloc->Size != 4))
				FailWithError (CurFileName, "Cannot emit %ld byte %s reloc to `%s'.", (long) Reloc->Size, Reloc->Relative ? "relative" : "absolute", Reloc->Target.SymbolName);
			
			if (((I2) Reloc->Location) != Reloc->Location)
				FailWithError (CurFileName, "Cannot emit reloc outside of limited program range to `%s'.", Reloc->Target.SymbolName);
			
			{
				OFFSET TargetLocation = GetLocationOffset (MainSection, &(Reloc->Target)) + Reloc->FixedOffset;
				
				if (((I2) TargetLocation) != TargetLocation)
					FailWithError (CurFileName, "Cannot emit reloc to `%s' (Offset %ld; Location 0x%lX) outside of limited program range.", Reloc->Target.SymbolName, (long) (Reloc->Target.Offset + Reloc->FixedOffset), (long) (TargetLocation));
				
				// Everything seems to be correct.
				ExportWriteTI2 (File, TargetLocation);
				ExportWriteTI2 (File, Reloc->Location);
				
				// Increase the statistics.
				EmittedRelocCount++;
			}
		}
	}
	
	if ((!(IsEmpty (MainSection->ROMCalls))) && (!(MainSection->ROMCalls.Handled)))
		FailWithError (SectionFileName, "ROM calls are not supported in this mode.");
	
	if ((!(IsEmpty (MainSection->RAMCalls))) && (!(MainSection->RAMCalls.Handled)))
		FailWithError (SectionFileName, "RAM calls are not supported in this mode.");
	
	if ((!(IsEmpty (MainSection->LibCalls))) && (!(MainSection->LibCalls.Handled)))
		FailWithError (SectionFileName, "Library calls are not supported in this mode.");
	
	if (Program->OptimizeInfo->NativeRelocCount < EmittedRelocCount)
		Program->OptimizeInfo->NativeRelocCount = EmittedRelocCount;
	
	return TRUE;
	
#undef FailWithError
}

#ifdef PUCRUNCH_SUPPORT
#include "pucrunch.h"

// Export the internal data structures into a packed TIOS file.
BOOLEAN ExportPackedTIOSFile (const PROGRAM *Program, EXP_FILE *File, SIZE FileSize, ProgramCalcs DestCalc)
{
	// Some macros to make the code more readable.
#define FailWithError(Err...) ({ Error (Err); return FALSE; })
#define BufferWrite(P,L,C) (memcpy (Buffer + CurPos, P, (L) * (C)), CurPos += (L) * (C))
#define BufferWriteTI2(D) ({ WriteTI2 (*((TI2 *) (Buffer + CurPos)), D); CurPos += 2; })
	
	const char *SectionFileName = NULL;
	OFFSET DataStart = 0;
	
	COUNT EmittedRelocCount = 0;
	
	// Calculate the size needed for the two size bytes and the
	// tag.
	SIZE VarFileSize = 2 + FileSize + 1;
	I1 Buffer[VarFileSize];
	OFFSET CurPos = 0;
	
	// Write out the 2 size bytes.
	BufferWriteTI2 (VarFileSize - 2);
	
	// Get a pointer to the main section.
	SECTION *MainSection = Program->MainSection;
	
	if (!MainSection)
		FailWithError (NULL, "No main section.");
	
	SectionFileName = MainSection->FileName;
	
	// In nostub mode, execution simply starts at the beginning of
	// the first section. But ideally, startup code should always be
	// marked as such. When merging sections, the destination startup
	// number is kept, so in the end, the complete code is marked as
	// a startup section (which makes sense; it is startable).
	if (!(MainSection->StartupNumber || (Program->Type == PT_NOSTUB)))
		FailWithError (SectionFileName, "No startup section(s) defined.");
	
	if (!(MainSection->Data))
		FailWithError (SectionFileName, "No section contents.");
	
	// Write out the section contents first.
	DataStart = CurPos;
	BufferWrite (MainSection->Data, MainSection->Size, 1);
	
	// Write out two zero bytes as a separator.
	BufferWriteTI2 (0);
	
	if (!(IsEmpty (MainSection->Relocs)))
	{
		RELOC *Reloc;
		
		// Write out the relocation table.
		for (Reloc = GetLast (MainSection->Relocs); Reloc; Reloc = GetPrev (Reloc))
		{
			// Get the current file name for error messages.
			const char *CurFileName = GetFileName (MainSection, Reloc->Location);
			
			// If this can be resolved to a calculator-dependent value, write the
			// value into the section data.
			if (EmitCalcBuiltinValueBuf (Reloc, DestCalc, Buffer, FileSize, DataStart))
				continue;
			
			// We can only emit relocs with a target symbol in the same section.
			if (!(Reloc->Target.Symbol))
				FailWithError (CurFileName, "Unresolved reference to `%s'.", Reloc->Target.SymbolName);
			if (Reloc->Target.Symbol->Parent != MainSection)
				FailWithError (CurFileName, "Cannot emit reloc to `%s' in different section.", Reloc->Target.SymbolName);
			
			// We can only emit 4-byte absolute relocs.
			if (Reloc->Relative || (Reloc->Size != 4))
				FailWithError (CurFileName, "Cannot emit %ld byte %s reloc to `%s'.", (long) Reloc->Size, Reloc->Relative ? "relative" : "absolute", Reloc->Target.SymbolName);
			
			if (((I2) Reloc->Location) != Reloc->Location)
				FailWithError (CurFileName, "Cannot emit reloc outside of limited program range to `%s'.", Reloc->Target.SymbolName);
			
			{
				OFFSET TargetLocation = GetLocationOffset (MainSection, &(Reloc->Target)) + Reloc->FixedOffset;
				
				if (((I2) TargetLocation) != TargetLocation)
					FailWithError (CurFileName, "Cannot emit reloc to `%s' (Offset %ld; Location 0x%lX) outside of limited program range.", Reloc->Target.SymbolName, (long) (Reloc->Target.Offset + Reloc->FixedOffset), (long) (TargetLocation));
				
				// Everything seems to be correct.
				BufferWriteTI2 (TargetLocation);
				BufferWriteTI2 (Reloc->Location);
				
				// Increase the statistics.
				EmittedRelocCount++;
			}
		}
	}
	
	if ((!(IsEmpty (MainSection->ROMCalls))) && (!(MainSection->ROMCalls.Handled)))
		FailWithError (SectionFileName, "ROM calls are not supported in this mode.");
	
	if ((!(IsEmpty (MainSection->RAMCalls))) && (!(MainSection->RAMCalls.Handled)))
		FailWithError (SectionFileName, "RAM calls are not supported in this mode.");
	
	if ((!(IsEmpty (MainSection->LibCalls))) && (!(MainSection->LibCalls.Handled)))
		FailWithError (SectionFileName, "Library calls are not supported in this mode.");
	
	// Write out the final ASM tag.
	Buffer[CurPos] = TIOS_TAG_ASM;
	
	if (Program->OptimizeInfo->NativeRelocCount < EmittedRelocCount)
		Program->OptimizeInfo->NativeRelocCount = EmittedRelocCount;
	
	// Now compress the buffer and write it out to the file.
	return !TTPack(0, VarFileSize, Buffer, File);
	
#undef FailWithError
}

#endif /* PUCRUNCH_SUPPORT */

#endif /* TIOS_SUPPORT */
