/* output_tos.c Atari TOS executable output driver for vasm */
/* (c) in 2009-2014 by Frank Wille */

#include "vasm.h"
#include "output_tos.h"
#if defined(VASM_CPU_M68K)
static char *copyright="vasm tos output module 0.9 (c) 2009-2014 Frank Wille";

static int tosflags,textbasedsyms;
static int max_relocs_per_atom;
static section *sections[3];
static utaddr secsize[3];
static utaddr secoffs[3];
static utaddr sdabase,lastoffs;

#define SECT_ALIGN 2  /* TOS sections have to be aligned to 16 bits */


static int get_sec_type(section *s)
/* scan section attributes for type, 0=text, 1=data, 2=bss */
{
  char *a = s->attr;

  while (*a) {
    switch (*a++) {
      case 'c':
        return _TEXT;
      case 'd':
        return _DATA;
      case 'u':
        return _BSS;
    }
  }
  output_error(3,s->attr);  /* section attributes not suppported */
  return 0;
}


static int tos_initwrite(section *sec,symbol *sym)
{
  int nsyms = 0;
  int i;

  /* find exactly one .text, .data and .bss section for a.out */
  sections[_TEXT] = sections[_DATA] = sections[_BSS] = NULL;
  secsize[_TEXT] = secsize[_DATA] = secsize[_BSS] = 0;

  for (; sec; sec=sec->next) {
    /* section size is assumed to be in in (sec->pc - sec->org), otherwise
       we would have to calculate it from the atoms and store it there */
    if ((sec->pc - sec->org) > 0 || (sec->flags & HAS_SYMBOLS)) {
      i = get_sec_type(sec);
      if (!sections[i]) {
        sections[i] = sec;
        secsize[i] = (get_sec_size(sec) + SECT_ALIGN - 1) /
                     SECT_ALIGN * SECT_ALIGN;
        sec->idx = i;  /* section index 0:text, 1:data, 2:bss */
      }
      else
        output_error(7,sec->name);
    }
  }

  max_relocs_per_atom = 1;
  secoffs[_TEXT] = 0;
  secoffs[_DATA] = secsize[_TEXT] + balign(secsize[_TEXT],SECT_ALIGN);
  secoffs[_BSS] = secoffs[_DATA] + secsize[_DATA] +
                  balign(secsize[_DATA],SECT_ALIGN);
  /* define small data base as .data+32768 @@@FIXME! */
  sdabase = secoffs[_DATA] + 0x8000;

  /* count symbols */
  for (; sym; sym=sym->next) {
    /* ignore symbols preceded by a '.' and internal symbols */
    if (*sym->name!='.' && *sym->name!=' ' && !(sym->flags&VASMINTERN)) {
      nsyms++;
      if (strlen(sym->name) > DRI_NAMELEN)
        nsyms++;  /* extra symbol for long name */
    }
    else {
      if (!strcmp(sym->name," TOSFLAGS")) {
        if (tosflags == 0)  /* not defined by command line? */
          tosflags = (int)get_sym_value(sym);
      }
      sym->flags |= VASMINTERN;
    }
  }
  return no_symbols ? 0 : nsyms;
}


static void tos_header(FILE *f,unsigned long tsize,unsigned long dsize,
                        unsigned long bsize,unsigned long ssize,
                        unsigned long flags)
{
  PH hdr;

  setval(1,hdr.ph_branch,2,0x601a);
  setval(1,hdr.ph_tlen,4,tsize);
  setval(1,hdr.ph_dlen,4,dsize);
  setval(1,hdr.ph_blen,4,bsize);
  setval(1,hdr.ph_slen,4,ssize);
  setval(1,hdr.ph_magic,4,0);
  setval(1,hdr.ph_flags,4,flags);
  setval(1,hdr.ph_abs,2,0);
  fwdata(f,&hdr,sizeof(PH));
}


static void checkdefined(symbol *sym)
{
  if (sym->type == IMPORT)
    output_error(6,sym->name);
}


static taddr tos_sym_value(symbol *sym,int textbased)
{
  taddr val = get_sym_value(sym);

  /* all sections form a contiguous block, so add section offset */
  if (textbased && sym->type==LABSYM && sym->sec!=NULL)
    val += secoffs[sym->sec->idx];

  return val;
}


static void write_reloc68k(atom *a,nreloc *nrel,taddr val)
{
  char *p;

  if (a->type == DATA)
    p = a->content.db->data + (nrel->offset>>3);
  else if (a->type == SPACE)
    p = (char *)a->content.sb->fill;  /* @@@ ignore offset completely? */
  else
    return;
  setbits(1,p,((nrel->offset&7)+nrel->size+7)&~7,nrel->offset&7,nrel->size,val);
}


static void do_relocs(taddr pc,atom *a)
/* Try to resolve all relocations in a DATA or SPACE atom.
   Very simple implementation which can only handle basic 68k relocs. */
{
  int rcnt = 0;
  section *sec;
  rlist *rl;

  if (a->type == DATA)
    rl = a->content.db->relocs;
  else if (a->type == SPACE)
    rl = a->content.sb->relocs;
  else
    rl = NULL;

  while (rl) {
    switch (rl->type) {
      case REL_SD:
        checkdefined(((nreloc *)rl->reloc)->sym);
        write_reloc68k(a,rl->reloc,
                       (tos_sym_value(((nreloc *)rl->reloc)->sym,1)
                        + nreloc_real_addend(rl->reloc)) - sdabase);
        break;
      case REL_PC:
        checkdefined(((nreloc *)rl->reloc)->sym);
        write_reloc68k(a,rl->reloc,
                       (tos_sym_value(((nreloc *)rl->reloc)->sym,1)
                        + nreloc_real_addend(rl->reloc)) -
                       (pc + (((nreloc *)rl->reloc)->offset>>3)));
        break;
      case REL_ABS:
        checkdefined(((nreloc *)rl->reloc)->sym);
        sec = ((nreloc *)rl->reloc)->sym->sec;
        write_reloc68k(a,rl->reloc,
                       secoffs[sec?sec->idx:0]+((nreloc *)rl->reloc)->addend);
        if (((nreloc *)rl->reloc)->size == 32)
          break;  /* only support 32-bit absolute */
      default:
        unsupp_reloc_error(rl);
        break;
    }
    rcnt++;
    if (a->type == SPACE)
      break;  /* all SPACE relocs are identical, one is enough */
    rl = rl->next;
  }

  if (rcnt > max_relocs_per_atom)
    max_relocs_per_atom = rcnt;
}


static void tos_writesection(FILE *f,section *sec,taddr sec_align)
{
  if (sec) {
    utaddr pc = secoffs[sec->idx];
    utaddr npc,i;
    taddr align;
    atom *a;

    for (a=sec->first; a; a=a->next) {
      align = a->align;
      npc = ((pc + align-1) / align) * align;
      for (i=pc; i<npc; i++)
        fw8(f,0);
      do_relocs(npc,a);
      if (a->type == DATA)
        fwdata(f,a->content.db->data,a->content.db->size);
      else if (a->type == SPACE)
        fwsblock(f,a->content.sb);
      pc = npc + atom_size(a,sec,npc);
    }
    fwalign(f,pc,sec_align);
  }
}


static void write_dri_sym(FILE *f,char *name,int type,taddr value)
{
  struct DRIsym stab;
  int longname = strlen(name) > DRI_NAMELEN;

  strncpy(stab.name,name,DRI_NAMELEN);
  setval(1,stab.type,sizeof(stab.type),longname?(type|STYP_LONGNAME):type);
  setval(1,stab.value,sizeof(stab.value),value);
  fwdata(f,&stab,sizeof(struct DRIsym));

  if (longname) {
    char rest_of_name[sizeof(struct DRIsym)];

    memset(rest_of_name,0,sizeof(struct DRIsym));
    strncpy(rest_of_name,name+DRI_NAMELEN,sizeof(struct DRIsym));
    fwdata(f,rest_of_name,sizeof(struct DRIsym));
  }
}


static void tos_symboltable(FILE *f,symbol *sym)
{
  static int labtype[] = { STYP_TEXT,STYP_DATA,STYP_BSS };
  int t;

  for (; sym; sym=sym->next) {
    if (!(sym->flags & VASMINTERN)) {
      if (sym->type == EXPRESSION)
        t = STYP_EQUATED | STYP_DEFINED;
      else if (sym->type == LABSYM)
        t = labtype[sym->sec->idx] | STYP_DEFINED;
      else if (sym->type != IMPORT)
        ierror(0);

      if (sym->flags & EXPORT)
        t |= STYP_GLOBAL;

      write_dri_sym(f,sym->name,t,tos_sym_value(sym,textbasedsyms));
    }
  }
}


static int offscmp(const void *offs1,const void *offs2)
{
  return *(int *)offs1 - *(int *)offs2;
}


static int tos_writerelocs(FILE *f,section *sec)
{
  int n = 0;
  int *sortoffs = mymalloc(max_relocs_per_atom*sizeof(int));

  if (sec) {
    utaddr pc = secoffs[sec->idx];
    utaddr npc;
    taddr align;
    atom *a;
    rlist *rl;

    for (a=sec->first; a; a=a->next) {
      int nrel=0;

      align = a->align;
      npc = ((pc + align-1) / align) * align;

      if (a->type == DATA)
        rl = a->content.db->relocs;
      else if (a->type == SPACE)
        rl = a->content.sb->relocs;
      else
        rl = NULL;

      while (rl) {
        if (rl->type==REL_ABS && ((nreloc *)rl->reloc)->size==32)
          sortoffs[nrel++] = ((nreloc *)rl->reloc)->offset;
        rl = rl->next;
      }

      if (nrel) {
        int i;

        /* first sort the atom's relocs */
        if (nrel > 1)
          qsort(sortoffs,nrel,sizeof(int),offscmp);

        /* write differences between them */
        n += nrel;
        for (i=0; i<nrel; i++) {
          utaddr newoffs = npc + (utaddr)(sortoffs[i]>>3);

          if (lastoffs) {
            /* determine 8bit difference to next relocation */
            taddr diff = newoffs - lastoffs;

            if (diff < 0)
              ierror(0);
            while (diff > 254) {
              fw8(f,1);
              diff -= 254;
            }
            fw8(f,(uint8_t)diff);
          }
          else  /* first entry is a 32 bits offset */
            fw32(f,newoffs,1);
          lastoffs = newoffs;
        }
      }
      pc = npc + atom_size(a,sec,npc);
    }
  }

  myfree(sortoffs);
  return n;
}


static void write_output(FILE *f,section *sec,symbol *sym)
{
  int nsyms = tos_initwrite(sec,sym);
  int nrelocs = 0;

  tos_header(f,secsize[_TEXT],secsize[_DATA],secsize[_BSS],
             nsyms*sizeof(struct DRIsym),tosflags);
  tos_writesection(f,sections[_TEXT],SECT_ALIGN);
  tos_writesection(f,sections[_DATA],SECT_ALIGN);
  if (nsyms)
    tos_symboltable(f,sym);
  nrelocs += tos_writerelocs(f,sections[_TEXT]);
  nrelocs += tos_writerelocs(f,sections[_DATA]);
  if (nrelocs)
    fw8(f,0);
  else
    fw32(f,0,1);
}


static int output_args(char *p)
{
  if (!strncmp(p,"-tos-flags=",5)) {
    tosflags = atoi(p+11);
    return 1;
  }
  else if (!strcmp(p,"-monst")) {
    textbasedsyms = 1;
    return 1;
  }
  return 0;
}


int init_output_tos(char **cp,void (**wo)(FILE *,section *,symbol *),
                    int (**oa)(char *))
{
  *cp = copyright;
  *wo = write_output;
  *oa = output_args;
  return 1;
}

#else

int init_output_tos(char **cp,void (**wo)(FILE *,section *,symbol *),
                    int (**oa)(char *))
{
  return 0;
}
#endif
