/* $VER: pasm pass.c V0.8 (14.02.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.8 (14.02.98) phx
 *      Alignment list for each section. This fixes the problems
 *      with optimizations.
 * v0.7 (02.01.98) phx
 *      Allow more than two assembler passes, as required for
 *      optimizations.
 * v0.6 (26.10.97) phx
 *      Bug in conditional assembly fixed.
 *      @object and @function symbols are always defined before
 *      pass 1 is executed.
 * v0.5 (12.10.97) phx
 *      Support for user symbol definitions by -D option.
 *      The opcode field is automatically converted to lower case,
 *      so upper case directives and instructions are also allowed.
 * v0.3 (10.04.97) phx
 *      Some vbcc-specific changes.
 * 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 (15.02.97) phx
 *      File created.
 */


#define PASS_C
#include "ppcasm.h"


void exec_pass1(struct GlobalVars *);
void pass1(struct GlobalVars *,struct SourceText *,
           struct MacroParams *,struct SourceThread *);
struct SourceText *include_source(struct GlobalVars *,char *);
void exec_pass2(struct GlobalVars *);
void pass2(struct GlobalVars *,struct SourceText *,
           struct MacroParams *,struct SourceThread *);
struct SourceText *get_source(struct GlobalVars *);

static char *insert_macro_params(struct GlobalVars *,struct ParsedLine *,
                                 char *,struct MacroParams *);
static char *readline(struct GlobalVars *,struct ParsedLine *,char *);
static char *getlabel(struct GlobalVars *,char *);
static struct SourceText *add_source(struct GlobalVars *,char *,char *,long);
static void prepare_sections(struct GlobalVars *);



void exec_pass1(struct GlobalVars *gv)
{
  struct Symbol *sym;
  char asmname[20];
  char **p,*xmnemobuf,*usrdefbuf;
  size_t xmsize=0,udsize=0;
  struct UserDefine *nextudn;
  struct UserDefine *udn = (struct UserDefine *)gv->userdeflist.first;
  struct Section dummySec;

  sprintf(asmname,PNAME "_V%d.%02d",VERSION,REVISION);
  memset(&dummySec,0,sizeof(struct Section));
  gv->csect = &dummySec;  /* to avoid SEGVs during start up */
  sym = add_symbol(gv,asmname,SYM_ABS,0);
  sym->bind = SYMB_LOCAL;
  gv->lcsym = add_symbol(gv,"$",SYM_RELOC,0);
  gv->nargsym = add_symbol(gv,"$NARG",SYM_ABS,0);
  add_symbol(gv,"@object",SYM_ABS,1);
  add_symbol(gv,"@function",SYM_ABS,2);
  gv->ifcond[0] = TRUE;

  pass1(gv,add_source(gv,"<standard sections>",stdsects,
        strlen(stdsects)),NULL,NULL);

  if (!gv->noregsymbols)
    pass1(gv,add_source(gv,"<standard definitions>",stdsets,
          strlen(stdsets)),NULL,NULL);

  while (nextudn = (struct UserDefine *)udn->n.next) {
    udsize += strlen(udn->line);
    udn = nextudn;
  }
  if (udsize) {
    usrdefbuf = alloc(udsize+1);
    *usrdefbuf = 0;
    while (udn = (struct UserDefine *)remhead(&gv->userdeflist)) {
      strcat(usrdefbuf,udn->line);
      free(udn->line);
      free(udn);
    }
    gv->usrdefs = TRUE;
    pass1(gv,add_source(gv,"<user definitions>",usrdefbuf,udsize),
          NULL,NULL);
  }

  if (!gv->noextmnemo) {
    p = xmnemos;
    while (*p) {
      xmsize += strlen(*p);
      p++;
    }
    xmnemobuf = alloc(xmsize+1);
    *xmnemobuf = 0;
    p = xmnemos;
    while (*p) {
      strcat(xmnemobuf,*p);
      p++;
    }
    pass1(gv,add_source(gv,"<extended mnemonics>",xmnemobuf,xmsize),
          NULL,NULL);
  }

  pass1(gv,include_source(gv,gv->source_name),NULL,NULL);
  if (gv->vc) {
    activate_section(gv,(struct Section *)gv->sectionlist.first);
    alignment(gv,2);
    pcadd(gv,4);
  }
}


void pass1(struct GlobalVars *gv,struct SourceText *srctxt,
           struct MacroParams *macro,struct SourceThread *prev_st)
/* Assembler Pass 1 */
{
  struct SourceThread st;
  unsigned long nlines = srctxt->nlines;
  char *lp,c;
  struct ParsedLine *pl = srctxt->plin;

  /* init SourceThread structure */
  st.prev = prev_st;
  st.macro = macro;
  st.csource = srctxt;
  st.srcptr = srctxt->text;  /* current source pointer */
  st.line = 1;
  st.macskip = NULL;
  gv->cthread = &st;  /* set current source thread */

  while (nlines--) {
    gv->absline++;
    pl->lineptr = st.lineptr = st.srcptr;

    /* get next line of source text */
    if (macro && gv->ifcond[gv->iflevel]) {  /* insert macro parameters? */
      st.srcptr = insert_macro_params(gv,pl,st.srcptr,macro);
    }
    else {
      st.srcptr = readline(gv,pl,st.srcptr);
    }
    lp = gv->linebuf;

    /* evaluate label field */
    lp = getlabel(gv,lp);
    if (*gv->strbuf)
      if (!st.macskip && gv->ifcond[gv->iflevel])
        add_symbol(gv,gv->strbuf,SYM_RELOC,gv->csect->pc);
    lp = skipspaces(lp);

    /* evaluate opcode field */
    lp = getsymbol(gv,lp);
    if (*gv->strbuf) {
      lower_case(gv->strbuf);  /* convert opcode to lower case */

      if (st.macskip) {  /* macros */
        if (!strcmp(gv->strbuf,".endm")) {
          st.macskip->nlines = gv->absline - st.macskip->nlines;
          add_macro(gv,st.macskip);
          st.macskip = NULL;
        }
      }

      else if (!gv->ifcond[gv->iflevel]) {  /* conditional assembly */
        if (!strncmp(gv->strbuf,".if",3))
          gv->ifignore++;
        else if (!strcmp(gv->strbuf,".else")) {
          if (gv->ifignore == 0)
            gv->ifcond[gv->iflevel] = TRUE;
        }
        else if (!strcmp(gv->strbuf,".endif")) {
          if (gv->ifignore)
            gv->ifignore--;
          else
            gv->iflevel--;
        }
      }

      else {  /* search opcode */
        c = *lp;  /* branch hint given? */
        if (c=='+') {
          lp++;
          pl->branch_hint = 1;
        }
        else if (c=='-') {
          lp++;
          pl->branch_hint = -1;
        }
        search_opcode(gv,pl,gv->strbuf,skipspaces(lp));
      }
    }

    if (!(pl->flags&PLF_NONEWLINE))
      st.line++;
    ++pl;
  }
  gv->cthread = prev_st;
}


static char *insert_macro_params(struct GlobalVars *gv,struct ParsedLine *pl,
                                 char *s,struct MacroParams *mp)
{
  char **par = mp->param;
  char c,callidbuf[16];
  char *pp,*d=gv->linebuf;
  int n;

  do {
    if ((c=*s++) == '\\') {
      if (*s>='0' && *s<='9') {  /* macro parameter? */
        n = (int)(*s++ - '0');
        if (pp = par[n]) {
          while (*d++ = *pp++);  /* insert parameter */
          d--;
          continue;
        }
        else {
          error(9,n);  /* reference to undefined macro parameter n */
          continue;
        }
      }
      else if (*s=='@') {  /* insert macro call id */
        s++;
        pp = callidbuf;
        sprintf(pp,"$%d",(int)mp->call_id);
        while (*d++ = *pp++);
        d--;
        continue;
      }
    }
    *d++ = c;
  }
  while (c!=0 && c!=1);
  if (c==1) {
    *(--d) = 0;
    pl->flags |= PLF_NONEWLINE;
  }
  return (s);
}


static char *readline(struct GlobalVars *gv,struct ParsedLine *pl,char *s)
{
  char c,*d = gv->linebuf;

  do {
    c = *s++;
    *d++ = c;
  }
  while (c!=0 && c!=1);
  if (c==1) {
    *(--d) = 0;
    pl->flags |= PLF_NONEWLINE;
  }
  return (s);
}


static char *getlabel(struct GlobalVars *gv,char *s)
/* read label to gv->strbuf, s points to the beginning of a line */
{
  char *s_old = s;

  s = getsymbol(gv,s);  /* read label to buffer */
  if ((s != s_old) && (*s == ':'))  /* colon indicates a valid label */
    return (++s);
  *gv->strbuf = 0;
  return (s_old);
}


struct SourceText *include_source(struct GlobalVars *gv,char *name)
/* called when encountering an .include directive */
{
  char *s = mapfile(gv,name);
  long len = *(size_t *)(s - sizeof(size_t));  /* mapfile stores size here */

  return (add_source(gv,name,s,len));
}


static struct SourceText *add_source(struct GlobalVars *gv,char *name,
                                     char *s,  /* source text pointer */
                                     long len) /* text length in bytes */
/* create a new SourceText node and add it to the source list */
{
  struct SourceText *stxt = alloc(sizeof(struct SourceText));
  bool comm=FALSE;
  char quote=0;

  stxt->name = name;
  stxt->text = s;
  stxt->nlines = 0;

  /* replace '\n' and ';' by \0 and determine number of lines */
  for (; len>0; len--,s++) {
    switch (*s) {
      case '\n':
        *s = 0;
        stxt->nlines++;
        comm = FALSE;  /* \n ends a comment */
        break;
      case '\r':  /* ignore CRs */
        *s = 0;
        break;
      case ';':   /* ';' allows multiple statements per line */
        if (!comm && !quote) {
          if (*(s+1)=='\n' || *(s+1)=='\r')
            *s = ' ';
          else {
            *s = 1;
            stxt->nlines++;
          }
        }
        break;
      case '#':   /* comment: ignore ';' in rest of line */
        comm = TRUE;
        break;
      case 0x22:  /* ignore ';' in strings too */
        if (quote==0x22)
          quote = 0;
        else if (!quote)
          quote = 0x22;
        break;
      case 0x27:
        if (quote==0x27)
          quote = 0;
        else if (!quote)
          quote = 0x27;
        break;
    }
  }

  stxt->plin = alloczero(stxt->nlines * sizeof(struct ParsedLine));
  addtail(&gv->sourcelist,&stxt->n);
  return (stxt);
}


static void prepare_sections(struct GlobalVars *gv)
/* prepare sections for receiving code in pass 2 */
{
  struct Section *nexts,*sec=(struct Section *)gv->sectionlist.first;

  while (nexts = (struct Section *)sec->n.next) {
    sec->size = sec->pc;
    sec->pc = 0;
    sec->current_align = sec->first_align;
    if (!(sec->flags & SF_UNINITIALIZED))
      if (sec->size)
        sec->data = sec->contents = alloc((size_t)sec->size);
    sec = nexts;
  }
}


void exec_pass2(struct GlobalVars *gv)
{
  gv->absline = 0;
  gv->pass++;
  prepare_sections(gv);
  gv->srctxtp = (struct SourceText *)gv->sourcelist.first;

  pass2(gv,get_source(gv),NULL,NULL);  /* <standard sections> */
  if (!gv->noregsymbols)
    pass2(gv,get_source(gv),NULL,NULL);  /* <standard definitions> */
  if (gv->usrdefs)
    pass2(gv,get_source(gv),NULL,NULL);  /* <user definitions> */
  if (!gv->noextmnemo)
    pass2(gv,get_source(gv),NULL,NULL);  /* <extended mnemonics> */

  pass2(gv,get_source(gv),NULL,NULL); 
  if (gv->vc) {
    activate_section(gv,(struct Section *)gv->sectionlist.first);
    alignment(gv,2);
    store_word(gv,0x76626363);
  }
}


void pass2(struct GlobalVars *gv,struct SourceText *srctxt,
           struct MacroParams *macro,struct SourceThread *prev_st)
/* Assembler Pass 2 */
{
  struct SourceThread st;
  unsigned long nlines = srctxt->nlines;
  struct ParsedLine *pl = srctxt->plin;
  struct ParsedLine *p;
  uint32 oldnarg;

  /* init SourceThread structure */
  st.prev = prev_st;
  st.macro = macro;
  st.csource = srctxt;
  st.line = 1;
  gv->cthread = &st;  /* set current source thread */

  while (nlines--) {
    gv->absline++;
    st.lineptr = pl->lineptr;
    p = pl;

    do {
      /* evaluate opcode field */
      switch (p->type) {

        case OT_INSTRUCTION:
          instr(gv,p);
          break;

        case OT_DIRECTIVE:
          (((struct Directive *)p->opcode)->dfunct)(gv,p);
          break;

        case OT_MACRO:
          oldnarg = gv->nargsym->value;
          gv->nargsym->value = (uint32)p->narg;
          pass2(gv,get_source(gv),(struct MacroParams *)gv,gv->cthread);
          /* the MacroParams pointer is set to gv, because we */
          /* only need a non-zero pointer here... */
          gv->nargsym->value = oldnarg;
          break;

        case OT_SECTION:
          activate_section(gv,(struct Section *)p->opcode);
          break;
      }
    }
    while (p = p->next);

    if (!(pl->flags&PLF_NONEWLINE))
      st.line++;
    ++pl;
  }
  gv->cthread = prev_st;
}


struct SourceText *get_source(struct GlobalVars *gv)
{
  struct SourceText *st = gv->srctxtp;

  gv->srctxtp = (struct SourceText *)st->n.next;
  return (st);
}
