/* sects.c -- Section Operations

This file is part of STAR, the Saturn Macro Assembler.

   STAR is not distributed by the Free Software Foundation. Do not ask
them for a copy or how to obtain new releases. Instead, send e-mail to
the address below. STAR is merely covered by the GNU General Public
License.

Please send your comments, ideas, and bug reports to
Jan Brittenson <bson@ai.mit.edu>

*/


/* Copyright (C) 1991 Jan Brittenson.

   STAR 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 1, or (at your option) any
later version.

   STAR 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 STAR; see the file COPYING. If not, to obtain a copy, write
to the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139,
USA, or send e-mail to bson@ai.mit.edu. */


#include <stdio.h>
#include "sects.h"
#include "star.h"
#include "symbols.h"


SECT
  *sc_list[256];		/* List of sections */

static SECT
  **sc_list_next;		/* Next slot */


/* These are local, but have to be accessible externally */
int
  half_byte,			/* Nibble is waiting to be output */
  half_value;			/* Nibble value */


/* Initialize */
void sc_init()
{
  bclear((char *) sc_list, sizeof sc_list);
  sc_list_next = sc_list;

  half_byte = FALSE;
}


/* Allocate new section */
static SECT *sc_alloc()
{
  SECT *stmp;
  extern char *malloc();


  if(!(stmp = (SECT *) malloc(sizeof(SECT))))
    fatal("Cannot allocate memory for section");

  bclear((char *) stmp, sizeof *stmp);

  return(stmp);
}


/* Create new section */
SECT *sc_new(symbol, type)
  SYM_NODE *symbol;
  int type;
{
  SECT *new_sect = sc_alloc();

  /* Set R/W and type bits */
  switch(type & SC_SECT)
    {
    case SC_PSECT:
    case SC_DSECT: new_sect->sc_bits = SC_W;
    case SC_LSECT:
    case SC_CSECT: new_sect->sc_bits |= SC_R;
    }

  new_sect->sc_bits |= type & SC_SECT;
  new_sect->sc_symbol = symbol;
  
  /* Add to list over sections */
  *sc_list_next++ = new_sect;

  return(new_sect);
}


/* Allocate new code segment */
static SECT_CSEG *sc_cseg_alloc()
{
  SECT_CSEG *new_cseg;
  extern char *malloc();


  if(!(new_cseg = (SECT_CSEG *) malloc(sizeof(SECT_CSEG))))
    fatal("Cannot allocate section code buffer");

  bclear((char *) new_cseg, sizeof *new_cseg);

  return(new_cseg);
}


/* Add code to sect, low-endian */
void sc_add_code(sc_sect, nnib, data)
  SECT *sc_sect;
  int nnib;
  unsigned long data;
{
  SECT_CSEG *cseg;
  extern pass;
  extern unsigned long kcrc;


  /* Adjust size */
  if(sc_sect)
    sc_sect->sc_size += nnib;

  /* Ignore if n/a */
  if(!sc_sect || pass != 3 || !nnib || ISASECT(sc_sect))
    return;

  /* Initialize cseg list if we don't have any */
  if(!(cseg = sc_sect->sc_code_tail))
    cseg = sc_sect->sc_code_head = sc_sect->sc_code_tail = sc_cseg_alloc();
      
  /* Add data */
  while(nnib-- > 0)
    {
      /* Segment is full - create new segment */
      if(cseg->sc_nbytes >= SC_SEGBYTES)
	{
	  cseg->sc_next = sc_sect->sc_code_tail = sc_cseg_alloc();
	  cseg = cseg->sc_next;
	}
	  
      /* Update Kermit CRC */
      kcrc = (((kcrc ^ (unsigned long) (data & 0xf)) & 0xf)
	      * (unsigned long) 0x1081) ^
		(unsigned long) (kcrc >> 4);

      /* Add data */
      cseg->sc_data[ cseg->sc_nbytes++ ] = data & 0xf;
      data >>= 4;
    }

  /* Update section statistics */
  sc_sect->sc_nbytes += nnib;
}


/* Relocate section */
void sc_reloc(sc_sect, new_loc)
  SECT *sc_sect;
  long new_loc;
{
  if(!sc_sect)
    return;

  sc_sect->sc_reloc = new_loc;
}


/* Flush code segment list onto file.
 * If the section is null, we adjust to an even size.
 */
void sc_flush(sc_sect, fp)
  SECT *sc_sect;
  FILE *fp;
{
  register SECT_CSEG *cseg;
  register half_flag = half_byte, nib = half_value, ndata;
  char iobuf[ SC_IOBUF_SIZE ], *ioptr;


  /* Loop and add to file */
  if(!half_flag)
    nib = 0;

  if(!sc_sect)
    {
      if(half_flag)
	fwrite(&half_value, 1, 1, fp);

      half_byte = FALSE;
      return;
    }

  ioptr = iobuf;

  for(cseg = sc_sect->sc_code_head; cseg; cseg = cseg->sc_next)
    for(ndata = 0; ndata < cseg->sc_nbytes; ndata++)

      if(half_flag)
	{
	  if(ioptr == iobuf + SC_IOBUF_SIZE)
	    {
	      fwrite(iobuf, sizeof iobuf, 1, fp);
	      ioptr = iobuf;
	    }
	  
	  *ioptr++ = nib | (cseg->sc_data[ ndata ] << 4);
	  half_flag = FALSE;
	}
      else
	{
	  nib = cseg->sc_data[ ndata ];
	  half_flag = TRUE;
	}

  /* Flush IO buffer */
  if(ioptr != iobuf)
    fwrite(iobuf, ioptr-iobuf, 1, fp);

  /* Stow away any remaining loose nibble */
  half_byte = half_flag;
  half_value = nib;
}


/* Flush all sections, but wait with final byte adjustment */
void sc_write_code(fp)
  FILE *fp;
{
  SECT **sp;

  half_byte = FALSE;
  for(sp = sc_list; *sp; sc_flush(*sp++, fp));
}


/* Declare end-of-pass.
 * At the end of each pass we compute the reloc addresses
 * for all sections. We also initialize them for the next
 * pass.
 */
void sc_new_pass()
{
  SECT **sp;
  long reloc;

  for(sp = sc_list, reloc = 0; *sp; sp++)
    {
      if(ISASECT(*sp))
	(*sp)->sc_reloc = (*sp)->sc_loc = 0;
      else
	{
	  (*sp)->sc_reloc = (*sp)->sc_loc = reloc;
	  reloc += (*sp)->sc_size;
	}
      (*sp)->sc_size = 0;
    }
}


/* Return total code size */
long sc_code_size()
{
  SECT **sp;
  long size;

  for(sp = sc_list, size = 0; *sp; sp++)
    if(!ISASECT(*sp))
      size += (*sp++)->sc_size;

  return(size);
}


/* Write sections info to list file */
void sc_list_sect(fp)
  FILE *fp;
{
  SECT **sp;
  int longest_len;
  char asect_fmt[64], general_fmt[64];
  extern long loc;
  extern SECT *cur_sect;
  extern void newpage();

  /* Update location of current section */
  cur_sect->sc_loc = loc;

  /* Start new page */
  newpage();

  /* Loop sections to find longest name */
  for(longest_len = 0, sp = sc_list; *sp; sp++)
    
    if(strlen((*sp)->sc_symbol->name) > longest_len)
      longest_len = strlen((*sp)->sc_symbol->name);

  /* Then create control strings */
  if(longest_len > 44)
    longest_len = 44;

  sprintf(asect_fmt, "%%-%us             %%05x         asect\n",
	  longest_len);
  
  sprintf(general_fmt, "%%-%us  %%s  %%05x  %%05x  %%05x  ",
	  longest_len);


  /* List as:
   *  name   perm reloc  size point
   */

  for(sp = sc_list; *sp; sp++)
    {
      if(ISASECT(*sp))
	fprintf(fp, asect_fmt,
		(*sp)->sc_symbol->name,
		(*sp)->sc_size);
      else
	{
	  fprintf(fp, general_fmt,
		  (*sp)->sc_symbol->name,
		  ((*sp)->sc_bits & SC_W ? "rw" : "ro"),
		  (*sp)->sc_reloc,
		  (*sp)->sc_size,
		  (*sp)->sc_loc);

	  switch((*sp)->sc_bits & SC_SECT)
	    {
	    case SC_PSECT: fputc('p', fp); break;
	    case SC_DSECT: fputc('d', fp); break;
	    case SC_LSECT: fputc('l', fp); break;
	    case SC_CSECT: fputc('c', fp); break;
	    }

	  fputs("sect\n", fp);
	}
    }
}
