/* $VER: pasm output_abs.c V1.3c (08.02.00)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-2000  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-2000 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.
 *
 *
 * v1.3c  (08.02.00) phx
 *        Uses get_elfrel_name().
 * v1.2   (21.10.98) phx
 *        Replaced obsolete R_PPC_TOC16 by real R_PPC_SDAREL16.
 * v1.1d  (27.09.97) phx
 *        fopen() uses binary mode "b" for windoze/msdog compatibility.
 * 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,"wb")) {
    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_SDAREL16:
              if (gv->tocsect)
                a -= gv->tocsect->index + BASEREG_OFFSET;
              else
                a -= BASEREG_OFFSET;
              *(uint16 *)p = ECVH((uint16)a);
              break;
            default:
              error(53,get_elfrel_name(rel->type),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 */
}
