/* $VER: vlink support.c V0.5e (05.10.98)
 *
 * This file is part of vlink, a portable linker for multiple
 * object formats.
 * Copyright (c) 1997-99  Frank Wille
 *
 * vlink is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-99 by Volker Barthelmann.
 * vlink 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.5e (05.10.98) phx
 *       insertnode() is now insertbehind(). New functions insertbefore()
 *       and addhead().
 * v0.5d (22.08.98) phx
 *       Faster allocation routines using large memory chunks. They must
 *       be activated by #define FASTALLOC.
 * v0.3  (12.04.98) phx
 *       Replaced l2bh(),l2bw(),read16(),read32(),write16(),write32()
 *       by swap16(),swap32(),read16sw(),read32sw(),write16sw(),write32sw().
 *       fwrite32() is now called fwrite32be().
 * v0.2  (07.03.98) phx
 *       insertnode().
 * v0.1  (27.02.98) phx
 *       First version that seems to link AmigaOS ADOS and EHF
 *       objects and libraries. Many common features, like linking
 *       sections together which have relative references, are
 *       still missing. Also, PowerPC-ELF32 support is about to come.
 * v0.0  (04.08.97) phx
 *       File created. Project started on a beautiful summer-day
 *       at the North Sea beach of Cuxhaven. :)
 *       This file is based on support.c v0.4 of the portable
 *       PowerPC assembler "pasm".
 */


#define SUPPORT_C
#include "vlink.h"


void *alloc(size_t);
void *alloczero(size_t);
char *allocstring(char *);
void *alloc_hashtable(size_t);
void initlist(struct list *);
void insertbefore(struct node *,struct node *);
void insertbehind(struct node *,struct node *);
void addhead(struct list *,struct node *);
void addtail(struct list *,struct node *);
struct node *remhead(struct list *);
struct node *remnode(struct node *);
char *mapfile(char *);
char *base_name(char *);
char *check_name(char *);
int checkrange(uint32,int,bool);
uint16 swap16(uint16);
uint32 swap32(uint32);
uint16 read16sw(uint8 *);
uint32 read32sw(uint8 *);
void write16sw(uint8 *,uint16);
void write32sw(uint8 *,uint32);
void fwritex(FILE *,void *,size_t);
void fwrite32be(FILE *,uint32);
void fwrite_align(FILE *,uint32,uint32);
unsigned long elf_hash(unsigned char *);
unsigned long align(unsigned long,unsigned long);
int shiftval(uint32);

static size_t filesize(FILE *,char *);

static char *unnamed_txt = "unnamed";


#ifdef FASTALLOC
static struct MemChunk *new_chunk(void);
void init_mem(void);

#define CHUNKSIZE (0x40000-sizeof(void *)-sizeof(void *)-8)

struct MemChunk {             /* Large memory chunks for faster allocation */
  struct MemChunk *next;
  unsigned char *ptr;         /* pointer to available memory in this chunk */
  uint32 free;                /* number of bytes free */
#ifndef ALIGNOFF4
  uint32 align;               /* only for 64-bit alignment */
#endif
  unsigned char mchunk[CHUNKSIZE];
};

static struct MemChunk *mem;  /* first memory chunk for alloc() */



static struct MemChunk *new_chunk()
{
  struct MemChunk *m;

  if (!(m = (struct MemChunk *)malloc(sizeof(struct MemChunk))))
    error(1);  /* out of memory */
  m->next = NULL;
  m->ptr = m->mchunk;
  m->free = CHUNKSIZE;
  return (m);
}


void init_mem()
{
  mem = new_chunk();
}
#endif


void *alloc(size_t size)
/* allocate memory and print error message if not enough available */
{
#ifdef FASTALLOC  /* faster, but needs more memory */
  struct MemChunk *m=mem,*n;
  uint32 bytes;
  void *p;

  if (!(bytes = (uint32)(size+7) & ~7))  /* return 64-bit aligned memory */
    bytes = 8;
  if (bytes > CHUNKSIZE) {
    /* get an own block, for so much memory */
    if (!(p = malloc((size_t)bytes)))
      error(1);  /* out of memory */
    return (p);
  }
  for (;;) {
    if (bytes <= m->free) {
      p = (void *)m->ptr;
      m->free -= bytes;
      m->ptr += bytes;
      return (p);
    }
    if (!(n = m->next))
      n = m->next = new_chunk();
    m = n;
  }

#else
  void *p;

  if (!size)
    size = 1;
  if (!(p = malloc(size)))
    error(1);  /* out of memory */
  return (p);
#endif
}


void *alloczero(size_t size)
/* same as alloc() but zeroes the allocated memory */
{
  void *p = alloc(size);

  memset(p,0,size);
  return (p);
}


char *allocstring(char *s)
/* allocate space for a single string */
/* @@@ this should be improved by some kind of string buffer */
{
  char *p = alloc(strlen(s)+1);

  strcpy(p,s);
  return (p);
}


void *alloc_hashtable(size_t entries)
{
  return (alloczero(entries * sizeof(void *)));
}


void initlist(struct list *l)
/* initializes a list structure */
{
  l->first = (struct node *)&l->dummy;
  l->dummy = NULL;
  l->last = (struct node *)&l->first;
}


void insertbefore(struct node *n,struct node *sn)
/* insert node n directly before node sn */
/* sn must be a real node - no dummy nodes allowed! */
{
  struct node *pn = sn->pred;

  n->next = sn;
  n->pred = pn;
  pn->next = sn->pred = n;
}


void insertbehind(struct node *pn,struct node *n)
/* insert node n directly behind node pn */
/* pn must be a real node - no dummy nodes allowed! */
{
  struct node *sn = pn->next;

  n->next = sn;
  n->pred = pn;
  pn->next = sn->pred = n;
}


void addhead(struct list *l,struct node *n)
/* add node as first element of list */
{
  struct node *fn = l->first;

  n->pred = fn->pred;
  fn->pred = n;
  n->next = fn;
  l->first = n;
}


void addtail(struct list *l,struct node *n)
/* add node as last element of list */
{
  struct node *ln = l->last;

  n->next = ln->next;
  ln->next = n;
  n->pred = ln;
  l->last = n;
}


struct node *remhead(struct list *l)
/* remove first node in list and return a pointer to it */
{
  struct node *n = l->first;

  if (n->next) {
    l->first = n->next;
    n->next->pred = n->pred;
    return (n);
  }
  return (NULL);
}


struct node *remnode(struct node *n)
/* remove a node from a list */
{
  n->next->pred = n->pred;
  n->pred->next = n->next;
  return (n);
}


char *mapfile(char *name)
/* map a complete file into memory and return its address */
/* the file's length is returned in *(p-sizeof(size_t)) */
{
  FILE *fp;
  char *p=NULL;
  size_t fsiz;

  if (fp = fopen(name,"r")) {
    fsiz = filesize(fp,name);
    p = alloc(fsiz+sizeof(size_t));
    *(size_t *)p = fsiz;  /* store file size before the text starts */
    p += sizeof(size_t);
    if (fread(p,1,fsiz,fp) != fsiz) {
      fclose(fp);
      error(7,name);  /* read error */
    }
    fclose(fp);
  }
  return (p);
}


static size_t filesize(FILE *fp,char *name)
{
  /* somebody knows a better way to determine file size in ANSI C? */
  long oldpos,size;

  if ((oldpos = ftell(fp)) >= 0)
    if (fseek(fp,0,SEEK_END) >= 0)
      if ((size = ftell(fp)) >= 0)
        if (fseek(fp,oldpos,SEEK_SET) >= 0)
          return ((size_t)size);
  fclose(fp);
  error(5,name);  /* read error - doesn't return */
}


char *base_name(char *s)
/* returns last part of a path - the file name itself */
{
  char c;
  int l = strlen(s);

  while (l--) {
    c = s[l];
    if (c== '/' || c==':')
      return (&s[l+1]);
  }
  return (s);
}


char *check_name(char *name)
/* returns "unnamed", if name is a NULL-pointer */
{
  if (name)
    return (name);
  return (unnamed_txt);
}


int checkrange(uint32 val,int size,bool sign)
/* Checks if an integer value is in range, size=3 means 26-bit (B-instr.) */
/* If the check fails, the number of bits is returned (8, 16, 26) */
{
  if (sign) {
    int32 sval = (int32)val;
    switch (size) {
      case 1:
        if (sval>0x7f || sval<-0x80)
          return (8);
        break;
      case 2:
        if (sval>0x7fff || sval<-0x8000)
          return (16);
        break;
      case 3:
        if (sval>0x1ffffff || sval<-0x2000000)
          return (26);
        break;
    }
  }
  else {
    switch (size) {
      case 1:
        if (val>0xff)
          return (8);
        break;
      case 2:
        if (val>0xffff)
          return (16);
        break;
      case 3:
        if (val>0x3ffffff)
          return (26);
        break;
    }
  }
  return (0);
}


uint16 swap16(uint16 x)
/* 16-bit endian conversion */
{
  return ((x&0xff)<<8 | (x&0xff00)>>8);
}


uint32 swap32(uint32 x)
/* 32-bit endian conversion */
{
  return ((x&0xff)<<24 | (x&0xff00)<<8 |
          (x&0xff0000)>>8 | (x&0xff000000)>>24);
}


uint16 read16sw(uint8 *p)
/* read 16 bit word with endian conversion */
{
  return (((uint16)*p)<<8 | ((uint16)*(p+1)));
}


uint32 read32sw(uint8 *p)
/* read 32 bit word with endian conversion */
{
  return (((uint32)*p)<<24 | ((uint32)*(p+1))<<16 |
          ((uint32)*(p+2))<<8 | ((uint32)*(p+3)));
}


void write16sw(uint8 *p,uint16 x)
/* write 16 bit word with endian conversion */
{
  *p++ = (uint8)((x>>8)&0xff);
  *p = (uint8)(x&0xff);
}


void write32sw(uint8 *p,uint32 x)
/* write 32 bit word with endian conversion */
{
  *p++ = (uint8)((x>>24)&0xff);
  *p++ = (uint8)((x>>16)&0xff);
  *p++ = (uint8)((x>>8)&0xff);
  *p = (uint8)(x&0xff);
}


void fwritex(FILE *fp,void *buf,size_t len)
/* write a buffer of len bytes, with check for len=0 and write error */
{
  if (len) {
    if (!fwrite(buf,1,len,fp)) {
      fclose(fp);
      error(31,gvars.dest_name);  /* write error */
    }
  }
}


void fwrite32be(FILE *fp,uint32 w)
/* write a big endian 32 bit word */
{
  uint32 be = ECVW(w);

  if (!fwrite(&be,1,sizeof(uint32),fp)) {
    fclose(fp);
    error(31,gvars.dest_name);  /* write error */
  }
}


void fwrite_align(FILE *fp,uint32 a,uint32 n)
/* writes as many zero bytes as required for alignment a (a bits */
/* must be zero) with current file offset n */
{
  static uint8 alignment_bytes[MAX_FWALIGN];

  a = 1<<a;
  if ((n = (a-(n&(a-1))&(a-1))) > MAX_FWALIGN)
    ierror("fwrite_align(): Alignment > %d required",MAX_FWALIGN);
  fwritex(fp,alignment_bytes,n);
}


unsigned long elf_hash(unsigned char *name)
/* calculate a hash code as used in ELF objects */
{
  unsigned long h=0,g;

  while (*name) {
    h = (h << 4) + *name++;
    if (g = h & 0xf0000000)
      h ^= g >> 24;
    h &= ~g;
  }
  return (h);
}


unsigned long align(unsigned long addr,unsigned long alignment)
/* return number of bytes required to achieve alignment */
{
  unsigned long a = (1<<alignment) - 1;

  return (((addr+a)&~a) - addr);
}


int shiftval(uint32 x)
/* returns number of 0-bits before the first 1-bit - something like */
/* an integer-log2() function - returns 0 on x=0 */
{
  int i;

  if (x == 0)
    return (0);
  for (i=0; i<32; i++) {
    if (x & 1)
      break;
    x >>= 1;
  }
  return (i);
}
