/*****************************************************************************/
/*                                                                           */
/*				   segment.c				     */
/*                                                                           */
/*                   Segments for the ca65 macroassembler                    */
/*                                                                           */
/*                                                                           */
/*                                                                           */
/* (C) 1998-2007 Ullrich von Bassewitz                                       */
/*               Roemerstrasse 52                                            */
/*               D-70794 Filderstadt                                         */
/* EMail:        uz@cc65.org                                                 */
/*                                                                           */
/*                                                                           */
/* This software is provided 'as-is', without any expressed or implied       */
/* warranty.  In no event will the authors be held liable for any damages    */
/* arising from the use of this software.                                    */
/*                                                                           */
/* Permission is granted to anyone to use this software for any purpose,     */
/* including commercial applications, and to alter it and redistribute it    */
/* freely, subject to the following restrictions:                            */
/*                                                                           */
/* 1. The origin of this software must not be misrepresented; you must not   */
/*    claim that you wrote the original software. If you use this software   */
/*    in a product, an acknowledgment in the product documentation would be  */
/*    appreciated but is not required.                                       */
/* 2. Altered source versions must be plainly marked as such, and must not   */
/*    be misrepresented as being the original software.                      */
/* 3. This notice may not be removed or altered from any source              */
/*    distribution.                                                          */
/*                                                                           */
/*****************************************************************************/



#include <string.h>
#include <errno.h>

/* common */
#include "addrsize.h"
#include "mmodel.h"
#include "segnames.h"
#include "xmalloc.h"

/* cc65 */
#include "error.h"
#include "fragment.h"
#include "global.h"
#include "lineinfo.h"
#include "listing.h"
#include "objcode.h"
#include "objfile.h"
#include "segment.h"
#include "spool.h"
#include "studyexpr.h"
#include "symtab.h"



/*****************************************************************************/
/*     	      	    		     Data				     */
/*****************************************************************************/



/* If OrgPerSeg is false, all segments share the RelocMode flag and a PC
 * used when in absolute mode. OrgPerSeg may be set by .feature org_per_seg
 */
static int              RelocMode = 1;
static unsigned long    AbsPC	  = 0;		/* PC if in absolute mode */

/* Segment initializer macro */
#define SEG(segdef, num, prev)      \
    { prev, 0, 0, 0, num, 0, 1, 0, 0, segdef }

/* Definitions for predefined segments */
SegDef NullSegDef     = STATIC_SEGDEF_INITIALIZER (SEGNAME_NULL,     ADDR_SIZE_ABS);
SegDef ZeropageSegDef = STATIC_SEGDEF_INITIALIZER (SEGNAME_ZEROPAGE, ADDR_SIZE_ZP);
SegDef DataSegDef     = STATIC_SEGDEF_INITIALIZER (SEGNAME_DATA,     ADDR_SIZE_ABS);
SegDef BssSegDef      = STATIC_SEGDEF_INITIALIZER (SEGNAME_BSS,      ADDR_SIZE_ABS);
SegDef RODataSegDef   = STATIC_SEGDEF_INITIALIZER (SEGNAME_RODATA,   ADDR_SIZE_ABS);
SegDef CodeSegDef     = STATIC_SEGDEF_INITIALIZER (SEGNAME_CODE,     ADDR_SIZE_ABS);

/* Predefined segments */
static Segment NullSeg     = SEG (&NullSegDef,     5, NULL);
static Segment ZeropageSeg = SEG (&ZeropageSegDef, 4, &NullSeg);
static Segment DataSeg     = SEG (&DataSegDef,     3, &ZeropageSeg);
static Segment BssSeg      = SEG (&BssSegDef,      2, &DataSeg);
static Segment RODataSeg   = SEG (&RODataSegDef,   1, &BssSeg);
static Segment CodeSeg     = SEG (&CodeSegDef,     0, &RODataSeg);

/* Number of segments */
static unsigned SegmentCount = 6;

/* List of all segments */
Segment* SegmentList = &CodeSeg;
static Segment* SegmentLast = &NullSeg;

/* Currently active segment */
Segment* ActiveSeg = &CodeSeg;



/*****************************************************************************/
/*     	      	      	   	     Code				     */
/*****************************************************************************/



static Segment* NewSegment (const char* Name, unsigned char AddrSize)
/* Create a new segment, insert it into the global list and return it */
{
    Segment* S;

    /* Check for too many segments */
    if (SegmentCount >= 256) {
     	Fatal ("Too many segments");
    }

    /* Check the segment name for invalid names */
    if (!ValidSegName (Name)) {
     	Error ("Illegal segment name: `%s'", Name);
    }

    /* Create a new segment */
    S = xmalloc (sizeof (*S));

    /* Initialize it */
    S->List      = 0;
    S->Root      = 0;
    S->Last      = 0;
    S->FragCount = 0;
    S->Num       = SegmentCount++;
    S->Align     = 0;
    S->RelocMode = 1;
    S->PC        = 0;
    S->AbsPC     = 0;
    S->Def       = NewSegDef (Name, AddrSize);

    /* Insert it into the segment list */
    SegmentLast->List = S;
    SegmentLast = S;

    /* And return it... */
    return S;
}



Fragment* GenFragment (unsigned char Type, unsigned short Len)
/* Generate a new fragment, add it to the current segment and return it. */
{
    /* Create the new fragment */
    Fragment* F = NewFragment (Type, Len);

    /* Insert the fragment into the current segment */
    if (ActiveSeg->Root) {
    	ActiveSeg->Last->Next = F;
    	ActiveSeg->Last = F;
    } else {
    	ActiveSeg->Root = ActiveSeg->Last = F;
    }
    ++ActiveSeg->FragCount;

    /* Add this fragment to the current listing line */
    if (LineCur) {
	if (LineCur->FragList == 0) {
	    LineCur->FragList = F;
	} else {
       	    LineCur->FragLast->LineList = F;
     	}
     	LineCur->FragLast = F;
    }

    /* Increment the program counter */
    ActiveSeg->PC += F->Len;
    if (OrgPerSeg) {
        /* Relocatable mode is switched per segment */
        if (!ActiveSeg->RelocMode) {
            ActiveSeg->AbsPC += F->Len;
        }
    } else {
        /* Relocatable mode is switched globally */
        if (!RelocMode) {
            AbsPC += F->Len;
        }
    }

    /* Return the fragment */
    return F;
}



void UseSeg (const SegDef* D)
/* Use the segment with the given name */
{
    Segment* Seg = SegmentList;
    while (Seg) {
       	if (strcmp (Seg->Def->Name, D->Name) == 0) {
     	    /* We found this segment. Check if the type is identical */
	    if (D->AddrSize != ADDR_SIZE_DEFAULT &&
                Seg->Def->AddrSize != D->AddrSize) {
		Error ("Segment attribute mismatch");
		/* Use the new attribute to avoid errors */
	        Seg->Def->AddrSize = D->AddrSize;
       	    }
       	    ActiveSeg = Seg;
     	    return;
     	}
     	/* Check next segment */
     	Seg = Seg->List;
    }

    /* Segment is not in list, create a new one */
    if (D->AddrSize == ADDR_SIZE_DEFAULT) {
        Seg = NewSegment (D->Name, ADDR_SIZE_ABS);
    } else {
        Seg = NewSegment (D->Name, D->AddrSize);
    }
    ActiveSeg = Seg;
}



unsigned long GetPC (void)
/* Get the program counter of the current segment */
{
    if (OrgPerSeg) {
        /* Relocatable mode is switched per segment */
        return ActiveSeg->RelocMode? ActiveSeg->PC : ActiveSeg->AbsPC;
    } else {
        /* Relocatable mode is switched globally */
        return RelocMode? ActiveSeg->PC : AbsPC;
    }
}



void EnterAbsoluteMode (unsigned long PC)
/* Enter absolute (non relocatable mode). Depending on the OrgPerSeg flag,
 * this will either switch the mode globally or for the current segment.
 */
{
    if (OrgPerSeg) {
        /* Relocatable mode is switched per segment */
        ActiveSeg->RelocMode = 0;
        ActiveSeg->AbsPC = PC;
    } else {
        /* Relocatable mode is switched globally */
        RelocMode = 0;
        AbsPC = PC;
    }
}



int GetRelocMode (void)
/* Return true if we're currently in relocatable mode */
{
    if (OrgPerSeg) {
        /* Relocatable mode is switched per segment */
        return ActiveSeg->RelocMode;
    } else {
        /* Relocatable mode is switched globally */
        return RelocMode;
    }
}



void EnterRelocMode (void)
/* Enter relocatable mode. Depending on the OrgPerSeg flag, this will either
 * switch the mode globally or for the current segment.
 */
{
    if (OrgPerSeg) {
        /* Relocatable mode is switched per segment */
        ActiveSeg->RelocMode = 1;
    } else {
        /* Relocatable mode is switched globally */
        RelocMode = 1;
    }
}



void SegAlign (unsigned Power, int Val)
/* Align the PC segment to 2^Power. If Val is -1, emit fill fragments (the
 * actual fill value will be determined by the linker), otherwise use the
 * given value.
 */
{
    unsigned char Data [4];
    unsigned long Align = (1UL << Power) - 1;
    unsigned long NewPC = (ActiveSeg->PC + Align) & ~Align;
    unsigned long Count = NewPC - ActiveSeg->PC;

    if (Val != -1) {
	/* User defined fill value */
	memset (Data, Val, sizeof (Data));
	while (Count) {
	    if (Count > sizeof (Data)) {
	    	EmitData (Data, sizeof (Data));
	    	Count -= sizeof (Data);
	    } else {
	    	EmitData (Data, Count);
	    	Count = 0;
	    }
	}
    } else {
	/* Linker defined fill value */
	EmitFill (Count);
    }

    /* Remember the alignment in the header */
    if (ActiveSeg->Align < Power) {
     	ActiveSeg->Align = Power;
    }
}



unsigned char GetSegAddrSize (unsigned SegNum)
/* Return the address size of the segment with the given number */
{
    /* Search for the segment */
    Segment* S = SegmentList;
    while (S && SegNum) {
    	--SegNum;
    	S = S->List;
    }

    /* Did we find it? */
    if (S == 0) {
    	FAIL ("Invalid segment number");
    }

    /* Return the address size */
    return S->Def->AddrSize;
}



void SegCheck (void)
/* Check the segments for range and other errors */
{
    Segment* S = SegmentList;
    while (S) {
     	Fragment* F = S->Root;
     	while (F) {
       	    if (F->Type == FRAG_EXPR || F->Type == FRAG_SEXPR) {

                /* We have an expression, study it */
                ExprDesc ED;
                ED_Init (&ED);
                StudyExpr (F->V.Expr, &ED);

                /* Try to simplify it before looking further */
                F->V.Expr = SimplifyExpr (F->V.Expr, &ED);

                /* Check if the expression is constant */
                if (ED_IsConst (&ED)) {

       	       	    /* The expression is constant. Check for range errors. */
     	       	    int Abs = (F->Type != FRAG_SEXPR);
                    long Val = ED.Val;
     	       	    unsigned I;

     	       	    if (F->Len == 1) {
     	       	   	if (Abs) {
     	       	   	    /* Absolute value */
     	       	   	    if (Val > 255) {
     	       	       	     	PError (&F->Pos, "Range error (%ld not in [0..255])", Val);
     	       	   	    }
     	       	   	} else {
     	     	   	    /* PC relative value */
     	     	   	    if (Val < -128 || Val > 127) {
     	       	       	     	PError (&F->Pos, "Range error (%ld not in [-128..127])", Val);
     	     	   	    }
     	     	   	}
     	       	    } else if (F->Len == 2) {
     	     	    	if (Abs) {
     	     	   	    /* Absolute value */
     	     	   	    if (Val > 65535) {
     	       	       	     	PError (&F->Pos, "Range error (%ld not in [0..65535])", Val);
     	     	   	    }
     	     	   	} else {
     	     	   	    /* PC relative value */
     	     	   	    if (Val < -32768 || Val > 32767) {
     	       	       	     	PError (&F->Pos, "Range error (%ld not in [-32768..32767])", Val);
     	       		    }
     	     		}
     	     	    }

                    /* We don't need the expression tree any longer */
                    FreeExpr (F->V.Expr);

     	     	    /* Convert the fragment into a literal fragment */
     	     	    for (I = 0; I < F->Len; ++I) {
     	     	       	F->V.Data [I] = Val & 0xFF;
     	     	       	Val >>= 8;
     	     	    }
     	     	    F->Type = FRAG_LITERAL;

     	     	} else if (ED.AddrSize != ADDR_SIZE_DEFAULT) {

     	     	    /* We cannot evaluate the expression now, leave the job for
     	     	     * the linker. However, we can check if the address size
                     * matches the fragment size, and we will do so.
     		     */
                    if ((F->Len == 1 && ED.AddrSize > ADDR_SIZE_ZP)  ||
                        (F->Len == 2 && ED.AddrSize > ADDR_SIZE_ABS) ||
                        (F->Len == 3 && ED.AddrSize > ADDR_SIZE_FAR)) {
     	       	        PError (&F->Pos, "Range error");
     	     	    }
     		}

                /* Release memory allocated for the expression decriptor */
                ED_Done (&ED);
     	    }
     	    F = F->Next;
     	}
     	S = S->List;
    }
}



void SegDump (void)
/* Dump the contents of all segments */
{
    unsigned X = 0;
    Segment* S = SegmentList;
    printf ("\n");
    while (S) {
	unsigned I;
	Fragment* F;
	int State = -1;
       	printf ("New segment: %s", S->Def->Name);
	F = S->Root;
	while (F) {
    	    if (F->Type == FRAG_LITERAL) {
	       	if (State != 0) {
	       	    printf ("\n  Literal:");
		    X = 15;
	       	    State = 0;
		}
		for (I = 0; I < F->Len; ++I) {
		    printf (" %02X", F->V.Data [I]);
		    X += 3;
		}
     	    } else if (F->Type == FRAG_EXPR || F->Type == FRAG_SEXPR) {
       	       	State = 1;
	      	printf ("\n  Expression (%u): ", F->Len);
		DumpExpr (F->V.Expr, SymResolve);
	    } else if (F->Type == FRAG_FILL) {
		State = 1;
		printf ("\n  Fill bytes (%u)", F->Len);
	    } else {
		Internal ("Unknown fragment type: %u", F->Type);
	    }
       	    if (X > 65) {
	       	State = -1;
	    }
	    F = F->Next;
	}
	printf ("\n  End PC = $%04X\n", (unsigned)(S->PC & 0xFFFF));
	S = S->List;
    }
    printf ("\n");
}



static void WriteOneSeg (Segment* Seg)
/* Write one segment to the object file */
{
    Fragment* Frag;
    unsigned LineInfoIndex;
    unsigned long DataSize;
    unsigned long EndPos;

    /* Remember the file position, then write a dummy for the size of the
     * following data
     */
    unsigned long SizePos = ObjGetFilePos ();
    ObjWrite32 (0);

    /* Write the segment data */
    ObjWriteVar (GetStringId (Seg->Def->Name)); /* Name of the segment */
    ObjWrite32 (Seg->PC);                       /* Size */
    ObjWrite8 (Seg->Align);                     /* Segment alignment */
    ObjWrite8 (Seg->Def->AddrSize);             /* Address size of the segment */
    ObjWriteVar (Seg->FragCount);               /* Number of fragments */

    /* Now walk through the fragment list for this segment and write the
     * fragments.
     */
    Frag = Seg->Root;
    while (Frag) {

    	/* Write data depending on the type */
       	switch (Frag->Type) {

    	    case FRAG_LITERAL:
    	    	ObjWrite8 (FRAG_LITERAL);
    	    	ObjWriteVar (Frag->Len);
    	        ObjWriteData (Frag->V.Data, Frag->Len);
    	    	break;

    	    case FRAG_EXPR:
    	    	switch (Frag->Len) {
    	    	    case 1:   ObjWrite8 (FRAG_EXPR8);   break;
    	    	    case 2:   ObjWrite8 (FRAG_EXPR16);	break;
    	    	    case 3:   ObjWrite8 (FRAG_EXPR24);	break;
    	    	    case 4:   ObjWrite8 (FRAG_EXPR32);	break;
    	    	    default:  Internal ("Invalid fragment size: %u", Frag->Len);
     	    	}
    	    	WriteExpr (Frag->V.Expr);
    	    	break;

    	    case FRAG_SEXPR:
    	    	switch (Frag->Len) {
    	    	    case 1:   ObjWrite8 (FRAG_SEXPR8);  break;
    	    	    case 2:   ObjWrite8 (FRAG_SEXPR16);	break;
    	    	    case 3:   ObjWrite8 (FRAG_SEXPR24);	break;
    	    	    case 4:   ObjWrite8 (FRAG_SEXPR32);	break;
    	    	    default:  Internal ("Invalid fragment size: %u", Frag->Len);
    	    	}
    	    	WriteExpr (Frag->V.Expr);
    	    	break;

	    case FRAG_FILL:
	    	ObjWrite8 (FRAG_FILL);
       	       	ObjWriteVar (Frag->Len);
	    	break;

    	    default:
    	    	Internal ("Invalid fragment type: %u", Frag->Type);

    	}

       	/* Write the file position of this fragment */
	ObjWritePos (&Frag->Pos);

	/* Write extra line info for this fragment. Zero is considered
	 * "no line info", so add one to the value.
	 */
	LineInfoIndex = Frag->LI? Frag->LI->Index + 1 : 0;
	ObjWriteVar (LineInfoIndex);

	/* Next fragment */
	Frag = Frag->Next;
    }

    /* Calculate the size of the data, seek back and write it */
    EndPos = ObjGetFilePos ();          /* Remember where we are */
    DataSize = EndPos - SizePos - 4;    /* Don't count size itself */
    ObjSetFilePos (SizePos);            /* Seek back to the size */
    ObjWrite32 (DataSize);              /* Write the size */
    ObjSetFilePos (EndPos);             /* Seek back to the end */
}



void InitSegments (void)
/* Initialize segments */
{
    /* Initialize segment sizes. The segment definitions do already contain
     * the correct values for the default case (near), so we must only change
     * things that should be different.
     */
    switch (MemoryModel) {

        case MMODEL_NEAR:
            break;

        case MMODEL_FAR:
            CodeSegDef.AddrSize = ADDR_SIZE_FAR;
            break;

        case MMODEL_HUGE:
            CodeSegDef.AddrSize   = ADDR_SIZE_FAR;
            DataSegDef.AddrSize   = ADDR_SIZE_FAR;
            BssSegDef.AddrSize    = ADDR_SIZE_FAR;
            RODataSegDef.AddrSize = ADDR_SIZE_FAR;
            break;

        default:
            Internal ("Invalid memory model: %d", MemoryModel);
    }
}



void WriteSegments (void)
/* Write the segment data to the object file */
{
    Segment* Seg;

    /* Tell the object file module that we're about to start the seg list */
    ObjStartSegments ();

    /* First thing is segment count */
    ObjWriteVar (SegmentCount);

    /* Now walk through all segments and write them to the object file */
    Seg = SegmentList;
    while (Seg) {
	/* Write one segment */
	WriteOneSeg (Seg);
	/* Next segment */
      	Seg = Seg->List;
    }

    /* Done writing segments */
    ObjEndSegments ();
}



