/* output_vasm.c vobj format output driver for vasm */
/* (c) in 2002-2018 by Volker Barthelmann */

#include "vasm.h"

#ifdef OUTVOBJ
static char *copyright="vasm vobj output module 0.9b (c) 2002-2018 Volker Barthelmann";

/*
  Format (WILL CHANGE!):

  .byte 0x56,0x4f,0x42,0x4a
  .byte flags
    1: BIGENDIAN
    2: LITTLENDIAN
  .number bitsperbyte
  .number bytespertaddr
  .string cpu
  .number nsections [1-based]
  .number nsymbols [1-based]
  
nsymbols
  .string name
  .number type
  .number flags
  .number secindex
  .number val
  .number size

nsections
  .string name
  .string attr
  .number flags
  .number align
  .number size
  .number nrelocs
  .number databytes
  .byte[databytes]

nrelocs [standard|special]
standard
   .number type
   .number byteoffset
   .number bitoffset
   .number size
   .number mask
   .number addend
   .number symbolindex | 0 (sectionbase)

special
    .number type
    .number size
    .byte[size]

.number:[taddr]
    .byte 0--127 [0--127]
    .byte 128-255 [x-0x80 bytes little-endian]

*/

static void write_number(FILE *f,taddr val)
{
  int i;
  if(val>=0&&val<=127){
    fw8(f,val);
    return;
  }
  fw8(f,0x80+bytespertaddr);
  for(i=bytespertaddr;i>0;i--){
    fw8(f,val&0xff);
    val>>=8;
  }
}

static int size_number(taddr val)
{
  if(val>=0&&val<=127)
    return 1;
  else
    return bytespertaddr+1;
}

static void write_string(FILE *f,char *p)
{
  while(p&&*p){
    fw8(f,*p);
    p++;
  }
  fw8(f,0);
}

static int sym_valid(symbol *symp)
{
  if(*symp->name==' ')
    return 0;  /* ignore internal/temporary symbols */
  if(symp->flags & VASMINTERN)
    return 0;  /* ignore vasm-internal symbols */
  return 1;
}

static int count_relocs(rlist *rl)
{
  int nrelocs;

  for(nrelocs=0; rl; rl=rl->next) {
    if(rl->type>=FIRST_STANDARD_RELOC && rl->type<=LAST_STANDARD_RELOC) {
      nreloc *r = (nreloc *)rl->reloc;
      if (r->sym->type==IMPORT&&!sym_valid(r->sym))
        unsupp_reloc_error(rl);
      else
        nrelocs++;
    }
    else
      unsupp_reloc_error(rl);
  }
  return nrelocs;
}

static void get_section_sizes(section *sec,taddr *rsize,taddr *rdata,taddr *rnrelocs)
{
  taddr data=0,nrelocs=0;
  atom *p;
  rlist *rl;
  int i;

  sec->pc=0;
  for(p=sec->first;p;p=p->next){
    sec->pc=pcalign(p,sec->pc);
    sec->pc+=atom_size(p,sec,sec->pc);
    if(p->type==DATA){
      data=sec->pc;
      nrelocs+=count_relocs(p->content.db->relocs);
    }
    else if(p->type==SPACE){
      if(p->content.sb->relocs){
        nrelocs+=count_relocs(p->content.sb->relocs);
        data=sec->pc;
      }else{
        for(i=0;i<p->content.sb->size;i++)
          if(p->content.sb->fill[i]!=0)
            data=sec->pc;
      }
    }
  }
  *rdata=data;
  *rsize=sec->pc;
  *rnrelocs=nrelocs;
}

static void write_data(FILE *f,section *sec,taddr data)
{
  atom *p;
  sec->pc=0;
  for(p=sec->first;p;p=p->next){
    if(sec->pc>=data)
      return;
    sec->pc=fwpcalign(f,p,sec,sec->pc);
    sec->pc+=atom_size(p,sec,sec->pc);
    if(p->type==DATA)
      fwdata(f,p->content.db->data,p->content.db->size);
    else if(p->type==SPACE)
      fwsblock(f,p->content.sb);
  }
}

static void write_rlist(FILE *f,section *sec,rlist *rl)
{
  int idx;
  for(;rl;rl=rl->next){
    if(rl->type>=FIRST_STANDARD_RELOC&&rl->type<=LAST_STANDARD_RELOC){
      nreloc *rel=rl->reloc;
      if(!(idx=rel->sym->idx)){
        if(rel->sym->type==IMPORT&&!sym_valid(rel->sym))
          continue;
        idx=rel->sym->sec->idx;  /* symbol does not exist, use section-symbol */
      }
      write_number(f,rl->type);
      write_number(f,sec->pc+rel->byteoffset);
      write_number(f,rel->bitoffset);
      write_number(f,rel->size);
      write_number(f,rel->mask);
      write_number(f,rel->addend);
      write_number(f,idx);
    }
  }
}

static void write_relocs(FILE *f,section *sec)
{
  atom *p;
  rlist *rl;
  sec->pc=0;
  for(p=sec->first;p;p=p->next){
    sec->pc=pcalign(p,sec->pc);
    if(p->type==DATA)
      write_rlist(f,sec,p->content.db->relocs);
    else if(p->type==SPACE)
      write_rlist(f,sec,p->content.sb->relocs);
    sec->pc+=atom_size(p,sec,sec->pc);
  }
}

static void write_output(FILE *f,section *sec,symbol *sym)
{
  int nsyms,nsecs;
  section *secp;
  symbol *symp,*first,*last;
  taddr size,data,nrelocs;

  /* assign section index, make section symbols */
  for(nsecs=1,secp=sec,first=sym,last=NULL;secp;secp=secp->next){
    secp->idx=nsecs++;  /* symp->idx will become equal to secp->idx */
    symp=mymalloc(sizeof(*symp));
    symp->name=secp->name;
    symp->type=LABSYM;
    symp->flags=TYPE_SECTION;
    symp->sec=secp;
    symp->pc=0;
    symp->expr=symp->size=NULL;
    symp->align=0;
    symp->next=sym;
    if(last!=NULL)
      last->next=symp;
    else
      first=symp;
    last=symp;
  }
  /* assign symbol index to all symbols */
  for(nsyms=1,symp=first;symp;symp=symp->next){
    if(sym_valid(symp))
      symp->idx=nsyms++;
    else
      symp->idx=0;  /* use section-symbol, when needed */
  }

  fw32(f,0x564f424a,1); /* "VOBJ" */
  if(BIGENDIAN)
    fw8(f,1);
  else if(LITTLEENDIAN)
    fw8(f,2);
  else
    ierror(0);
  write_number(f,bitsperbyte);
  write_number(f,bytespertaddr);
  write_string(f,cpuname);
  write_number(f,nsecs-1);
  write_number(f,nsyms-1);

  for(symp=first;symp;symp=symp->next){
    if(!sym_valid(symp))
      continue;
    write_string(f,symp->name);
    write_number(f,symp->type);
    write_number(f,symp->flags);
    write_number(f,symp->sec?symp->sec->idx:0);
    write_number(f,get_sym_value(symp));
    write_number(f,get_sym_size(symp));
  }

  for(secp=sec;secp;secp=secp->next){
    write_string(f,secp->name);
    write_string(f,secp->attr);
    write_number(f,secp->flags);
    write_number(f,secp->align);
    get_section_sizes(secp,&size,&data,&nrelocs);
    write_number(f,size);
    write_number(f,nrelocs);
    write_number(f,data);
    write_data(f,secp,data);
    write_relocs(f,secp);
  }
}

static int output_args(char *p)
{
  return 0;
}

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

#else

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