/* $VER: pasm output_abs.c V0.4 (05.07.97)
 *
 * 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.4 (05.07.97) phx
 *      Absolute output format supports correct relocations and
 *      aligments. The user will be warned, if external references
 *      are present.
 * v0.2 (25.03.97) phx
 *      Writes ELF object for 32-bit PowerPC big-endian. Either absolute
 *      or ELF output format may be selected. ELF is default for all
 *      currently supported platforms. PPCasm supports nine different
 *      relocation types (there are much more...).
 *      Compiles and works also under NetBSD/amiga (68k).
 *      Changed function declaration to 'new style' in all sources
 *      (to avoid problems with '...' for example).
 * v0.1 (11.03.97) phx
 *      First test version with all PowerPC instructions and most
 *      important directives. Only raw, absolute output.
 * v0.0 (04.03.97) phx
 *      File created.
 */


#define OUTPUT_ABS_C
#include "ppcasm.h"
#define BASEREG_OFFSET 0x7ffc  /* @@@ ? */


void output_absolute(struct GlobalVars *);
static void wr_err(struct GlobalVars *);



void output_absolute(struct GlobalVars *gv)
{
  struct Section *nextsec,*sec=(struct Section *)gv->sectionlist.first;
  struct Reloc *nextrel,*rel;
  struct XReference *xref;
  unsigned long offs = gv->absbase;
  FILE *fp;

  /* calculate section base addresses */
  while (nextsec = (struct Section *)sec->n.next) {
    if (!(sec->flags & SF_DISCARD)) {
      sec->index = (uint32)offs;
      offs += (sec->size + 3) & ~3;
    }
    sec = nextsec;
  }

  /* create output file */
  sec = (struct Section *)gv->sectionlist.first;
  if (fp = fopen(gv->dest_name,"w")) {
    while (nextsec = (struct Section *)sec->n.next) {
      if (!(sec->flags & SF_DISCARD) && sec->size) {

        /* try to resolve relocations */
        rel = (struct Reloc *)sec->reloclist.first;
        while (nextrel = (struct Reloc *)rel->n.next) {
          uint32 *p = (uint32 *)((char *)sec->contents + rel->offset);
          uint32 a = rel->relocsect->index + rel->addend;

          switch (rel->type) {
            case R_PPC_ADDR32:
              *p = ECVW(a);
              break;
            case R_PPC_ADDR16:
              *(uint16 *)p = ECVH((uint16)a);
              break;
            case R_PPC_ADDR16_LO:
              *(uint16 *)p = ECVH((uint16)(a & 0xffff));
              break;
            case R_PPC_ADDR16_HA:
              if (a & 0x8000)
                a += 0x10000;
            case R_PPC_ADDR16_HI:
              *(uint16 *)p = ECVH((uint16)(a >> 16));
              break;
            case R_PPC_ADDR24:
              *p = ECVW((uint32)((*(uint8 *)p & 0xfc) << 24) |
                        (a & 0x3fffffc) | (uint32)(*((uint8 *)p+3) & 3));
              break;
            case R_PPC_ADDR14:
            case R_PPC_ADDR14_BRTAKEN:
            case R_PPC_ADDR14_BRNTAKEN:
              *((uint16 *)p+1) = ECVH((uint16)(a & 0xfffc) |
                                      (uint16)(*((uint8 *)p+3) & 3));
              break;
            case R_PPC_REL24:
              *p = ECVW((uint32)((*(uint8 *)p & 0xfc) << 24) |
                        ((a - (sec->index + rel->offset)) & 0x3fffffc) |
                        (uint32)(*((uint8 *)p+3) & 3));
              break;
            case R_PPC_REL14:
            case R_PPC_REL14_BRTAKEN:
            case R_PPC_REL14_BRNTAKEN:
              *((uint16 *)p+1) = ECVH((uint16)((a - (sec->index + 
                                      rel->offset - 2)) & 0xfffc) |
                                      (uint16)(*((uint8 *)p+3) & 3));
              break;
            case R_PPC_REL32:
              *p = ECVW(a - (sec->index + rel->offset));
              break;
            case R_PPC_TOC16:
              if (gv->tocsect)
                a -= gv->tocsect->index + BASEREG_OFFSET;
              else
                a -= BASEREG_OFFSET;
              *(uint16 *)p = ECVH((uint16)a);
              break;
            default:
              error(53,elfrel_name[rel->type & ELFRELNAMMSK],rel->offset,
                    sec->name);  /* relocation not supported */
              break;
          }
          rel = nextrel;
        }

        /* any external references? (would be difficult here :) */
        while (xref = (struct XReference *)remhead(&sec->xreflist))
          error(55,xref->xsymbol->name);  /* no xrefs in absolute output */

        /* write section contents */
        if (sec->flags & SF_UNINITIALIZED) {  /* bss section */
          unsigned long s = (sec->size + 3) >> 2;
          while (s--) {
            if (!fwrite(gv->alignment_bytes,1,4,fp))
              wr_err(gv);
          }
        }
        else {
          if (!fwrite(sec->contents,1,sec->size,fp))
              wr_err(gv);
          if ((4-(sec->size&3))&3) {  /* aligment */
            if (!fwrite(gv->alignment_bytes,1,(4-(sec->size&3))&3,fp))
                wr_err(gv);
          }
        }
      }
      sec = nextsec;
    }
    fclose(fp);
  }
  else
    error(25,gv->dest_name);  /* unable to create output file */
}


static void wr_err(struct GlobalVars *gv)
{
  error(26,gv->dest_name);  /* write error */
}
