/* $VER: pasm output_ehf.c V0.7 (02.01.98)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-98  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-98 by Volker Barthelmann.
 * pasm may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.7 (02.01.98) phx
 *      Sections after a section with HUNK_EXT were 4 bytes too large.
 *      Output format OFMT_ADOS support. The difference between ADOS and
 *      and EHF is, that ADOS uses HUNK_CODE instead HUNK_PPC_CODE and
 *      doesn't support any PPC-specific relocations and references types.
 *      This makes it possible to link simple PowerPC programs with an
 *      old linker, like BLink, SLink or PhxLnk.
 * v0.4 (02.07.97) phx
 *      File created.
 */


#define OUTPUT_EHF_C
#include "ppcasm.h"
#include "ehf.h"


struct XRefNode {
  struct node n;
  char *sym_name;
  uint8 ref_type;
  int noffsets;
  struct list xreflist;
};


static char *output_name;
static int nsecs = 0;
static bool exthunk;


void output_ehf(struct GlobalVars *);

static void reloc_hunk(FILE *,struct Section *,uint8,uint32);
static void unsupp_relocs(struct Section *);
static void ext_header(FILE *);
static void ext_refs(FILE *,struct GlobalVars *,struct Section *);
static void ext_defs(FILE *,struct GlobalVars *,uint32,uint8,uint32);
static uint32 word_strlen(char *);
static void write_name(FILE *,struct GlobalVars *,char *);
static void fwalign(FILE *,struct GlobalVars *,uint32);
static void fw(FILE *,void *,size_t);
static void fw4(FILE *,uint32);



void output_ehf(struct GlobalVars *gv)
{
  struct Section *nextsec,*sec=(struct Section *)gv->sectionlist.first;
  struct Symbol *symchain;
  uint32 index;
  bool ehfmode = gv->output==OFMT_EHF;
  FILE *fp;

  /* assign an index to each valid section */
  while (nextsec = (struct Section *)sec->n.next) {
    if (!(sec->flags & SF_DISCARD) && sec->size > 0)
      sec->index = nsecs++;
    sec = nextsec;
  }

  /* create output file */
  output_name = gv->dest_name;

  if (fp = fopen(output_name,"w")) {  /* create output file */
    fw4(fp,HUNK_UNIT);
    if (gv->ident) {
      fw4(fp,word_strlen(gv->ident));  /* unit name */
      write_name(fp,gv,gv->ident);
    }
    else
      fw4(fp,0);


    /* section loop */
    sec = (struct Section *)gv->sectionlist.first;
    while (nextsec = (struct Section *)sec->n.next) {
      exthunk = FALSE;
      if (!(sec->flags & SF_DISCARD) && sec->size > 0) {
        index = sec->index;

        /* section name */
        fw4(fp,HUNK_NAME);
        fw4(fp,word_strlen(sec->name));
        write_name(fp,gv,sec->name);

        /* section type and size */
        switch(sec->type) {
          case ST_CODE:
            ehfmode ? fw4(fp,HUNK_PPC_CODE) : fw4(fp,HUNK_CODE);
            break;
          case ST_DATA:
            fw4(fp,HUNK_DATA);
            break;
          case ST_UDATA:
            fw4(fp,HUNK_BSS);
            break;
          default:  /* section type not supported in EHF/ADOS */
            error(52,sec->name,(int)sec->type);
            fw4(fp,HUNK_DATA);  /* defaults to HUNK_DATA */
            break;
        }
        fw4(fp,(uint32)(sec->size+3)>>2);

        /* write section contents */
        if (!(sec->flags & SF_UNINITIALIZED)) {
          fw(fp,sec->contents,(uint32)sec->size);
          fwalign(fp,gv,(uint32)sec->size);
        }

        /* relocation hunks */
        reloc_hunk(fp,sec,R_PPC_ADDR32,HUNK_ABSRELOC32);
/* @@@  reloc_hunk(fp,sec,R_PPC_ADDR16,HUNK_ABSRELOC16); */
        if (ehfmode)
          reloc_hunk(fp,sec,R_PPC_REL24,HUNK_RELRELOC26);
        reloc_hunk(fp,sec,R_PPC_REL14,HUNK_RELRELOC16);
        reloc_hunk(fp,sec,R_PPC_REL14_BRTAKEN,HUNK_RELRELOC16);
        reloc_hunk(fp,sec,R_PPC_REL14_BRNTAKEN,HUNK_RELRELOC16);
        reloc_hunk(fp,sec,R_PPC_REL32,HUNK_RELRELOC32);
        reloc_hunk(fp,sec,R_PPC_TOC16,HUNK_DREL16);
        unsupp_relocs(sec);  /* print unsupported relocations */

        /* external references and global definitions */
        ext_refs(fp,gv,sec);
        ext_defs(fp,gv,index,SYM_RELOC,EXT_DEF);
        if (index == 0)  /* put absolute definitions in first HUNK_EXT */
          ext_defs(fp,gv,0xffff,SYM_ABS,EXT_ABS);
        if (exthunk)
          fw4(fp,0);  /* close HUNK_EXT block */

        /* symbol table */
        ext_defs(fp,gv,index,SYM_RELOC,EXT_SYMB);

        fw4(fp,HUNK_END);  /* end of this hunk */
      }
      sec = nextsec;
    }

    fclose(fp);
  }
  else
    error(25,output_name);  /* unable to create output file */
}


static void reloc_hunk(FILE *fp,struct Section *sec,uint8 r,uint32 ehfrel)
/* generate an EHF relocation hunk for a specific reloc type */
{
  struct Reloc *nextrel,*rel=(struct Reloc *)sec->reloclist.first;
  struct list **rlist=alloc(nsecs*sizeof(struct list *));
  int *rcnt=alloczero(nsecs*sizeof(int));  /* reloc cnt for all sections */
  bool hunk_required=FALSE;
  int i;

  for (i=0; i<nsecs; i++) {  /* empty reloc lists for each section */
    rlist[i] = alloc(sizeof(struct list));
    initlist(rlist[i]);
  }

  while (nextrel = (struct Reloc *)rel->n.next) {
    if (rel->type == r) {
      /* move reloc node of correct type into relocssect's rlist */
      remnode(&rel->n);
      addtail(rlist[rel->relocsect->index],&rel->n);
      rcnt[rel->relocsect->index]++;
      hunk_required = TRUE;
    }
    rel = nextrel;
  }

  if (hunk_required) {  /* there's at least one relocation */
    fw4(fp,ehfrel);  /* reloc hunk id */
    for (i=0; i<nsecs; i++) {
      if (rcnt[i]) {
        fw4(fp,(uint32)rcnt[i]);  /* number of relocations */
        fw4(fp,(uint32)i);  /* section index */

        /* store relocation offsets */
        while(rel = (struct Reloc *)remhead(rlist[i])) {
          fw4(fp,(uint32)rel->offset);
          free(rel);
        }
      }
    }
    fw4(fp,0);  /* no more relocation entries */
  }

  /* free dynamically allocated rlists and rcnt array */
  for (i=0; i<nsecs; free(rlist[i++]));
  free(rcnt);
}


static void unsupp_relocs(struct Section *sec)
{
  struct Reloc *nextrel,*rel=(struct Reloc *)sec->reloclist.first;

  while (nextrel = (struct Reloc *)rel->n.next) {
    error(53,elfrel_name[rel->type & ELFRELNAMMSK],rel->offset,sec->name);
    rel = nextrel;
  }
}


static void ext_header(FILE *fp)
{
  if (!exthunk) {
    exthunk = TRUE;
    fw4(fp,HUNK_EXT);
  }
}


static void ext_refs(FILE *fp,struct GlobalVars *gv,struct Section *sec)
{
  struct list xnodelist;  /* xrefs with same ref. type and symbol name */
  struct XRefNode *xn,*nextxn;
  struct XReference *xref;

  initlist(&xnodelist);
  while (xref = (struct XReference *)remhead(&sec->xreflist)) {
    char *name = xref->xsymbol->name;  /* name of xref'ed symbol */
    uint8 rtype = xref->type;

    /* ELF32 -> EHF reference type mapping */
    switch (rtype) {
      case R_PPC_ADDR32:
        rtype = EXT_ABSREF32;
        break;
      case R_PPC_REL14:
      case R_PPC_REL14_BRTAKEN:
      case R_PPC_REL14_BRNTAKEN:
      case R_PPC_ADDR16:
        rtype = EXT_RELREF16;
        break;
      case R_PPC_TOC16:
        rtype = EXT_DEXT16;  /* small data */
        break;
      case R_PPC_REL24:
        if (gv->output == OFMT_EHF) {
          rtype = EXT_RELREF26;
          break;
        }
      default:
        error(53,elfrel_name[rtype & ELFRELNAMMSK],xref->offset,sec->name);
        rtype = EXT_RELREF8;  /* @@@ to keep the loop running */
        break;
    }

    /* search appropriate XRefNode for referenced symbol and type */
    xn = (struct XRefNode *)xnodelist.first;
    while (nextxn = (struct XRefNode *)xn->n.next) {
      if (!strcmp(name,xn->sym_name) && rtype==xn->ref_type)
        break;
      xn = nextxn;
    }

    if (nextxn==NULL) {  /* we have to create a new XRefNode? */
      xn = alloc(sizeof(struct XRefNode));
      xn->sym_name = name;
      xn->ref_type = rtype;
      xn->noffsets = 0;
      initlist(&xn->xreflist);
      addtail(&xnodelist,&xn->n);
    }

    /* add new offset to xreflist for same ref. type and symbol name */
    addtail(&xn->xreflist,&xref->n);
    xn->noffsets++;
  }

  if (xnodelist.first->next) {  /* at least one reference in this section? */
    ext_header(fp);
    while (xn = (struct XRefNode *)remhead(&xnodelist)) {
      fw4(fp,((uint32)xn->ref_type << 24) | word_strlen(xn->sym_name));
      write_name(fp,gv,xn->sym_name);  /* symbol's name */
      fw4(fp,(uint32)xn->noffsets);  /* number of references */
      while (xref = (struct XReference *)remhead(&xn->xreflist)) {
        fw4(fp,(uint32)xref->offset);  /* offset */
        free(xref);
      }
      free(xn);
    }
  }
}


static void ext_defs(FILE *fp,struct GlobalVars *gv,uint32 idx,
                     uint8 stype,uint32 xdeftype)
{
  struct Symbol *sym;
  bool xdefs = FALSE;
  int i;

  for (i=0; i<SYMHTABSIZE; i++) {
    sym = gv->symbols[i];
    while (sym) {
      if (sym->type==stype &&
          (sym->info==SYMI_NOTYPE || sym->info==SYMI_FUNC)) {
        if (idx == ((stype==SYM_RELOC) ? sym->relsect->index : 0xffff)) {
          if (sym->bind > SYMB_LOCAL) {
            if (!xdefs) {
              xdefs = TRUE;
              if (xdeftype == EXT_SYMB)
                fw4(fp,HUNK_SYMBOL);
              else
                ext_header(fp);
            }

            /* generate xdef oder symbol table entry */
            fw4(fp,(xdeftype << 24) | word_strlen(sym->name));
            write_name(fp,gv,sym->name);  /* symbol's name */
            fw4(fp,sym->value);  /* ... and its value */
          }
        }
      }
      sym = sym->hash_chain;
    }
  }
  if (xdefs && xdeftype==EXT_SYMB)
    fw4(fp,0);  /* end of symbol table for this hunk */
}


static uint32 word_strlen(char *s)
/* calculate number of 32 bit words required for
   a string without terminator */
{
  uint32 l;

  if (l = (uint32)strlen(s))
    l = (l+3)>>2;
  return (l);
}


static void write_name(FILE *fp,struct GlobalVars *gv,char *name)
/* write a unit/section/symbol name word-aligned */
{
  size_t nl=strlen(name);

  fw(fp,name,nl);
  fwalign(fp,gv,(uint32)nl);
}


static void fwalign(FILE *fp,struct GlobalVars *gv,uint32 n)
{
  fw(fp,gv->alignment_bytes,(4-(n&3))&3);
}


static void fw(FILE *fp,void *buf,size_t len)
{
  if (len) {
    if (!fwrite(buf,1,len,fp)) {
      fclose(fp);
      error(26,output_name);  /* write error */
    }
  }
}


static void fw4(FILE *fp,uint32 w)
/* write a 32 bit word, automatic endian conversion */
{
  w = ECVW(w);
  if (!fwrite(&w,1,sizeof(uint32),fp)) {
    fclose(fp);
    error(26,output_name);  /* write error */
  }
}
