/* Look for undefined references that have the form of
   Symbol references, and generate the actual Symbols.
   Copyright (C) 1990 Per Bothner.
   Code based on collect (by Michael Tiemann) for g++,
   (which is Copyright (C) 1987 Free Software Foundation, Inc),
   (and should at some pointer be merged with collect - and GNU ld).
   COFF Changes by Dirk Grunwald (grunwald@flute.cs.uiuc.edu)

This file is part of the Q programming system.

Q is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY.  No author or distributor
accepts responsibility to anyone for the consequences of using it
or for whether it serves any particular purpose or works at all,
unless he says so in writing.  Refer to the Q General Public
License for full details.

Everyone is granted permission to copy, modify and redistribute
Q, but only under the conditions described in the
Q General Public License.   A copy of this license is
supposed to have been given to you along with Q so you
can know your rights and responsibilities.  It should be in a
file named COPYING.  Among other things, the copyright notice
and this notice must be preserved on all copies.  */


/* This file contains all the code needed for the program `collect'.

   `collect' is run on all object files that are processed
   by GNU C++, to create a list of all the file-level
   initialization and destruction that need to be performed.
   It generates an assembly file which holds the tables
   which are walked by the global init and delete routines.
   The format of the tables are an integer length,
   followed by the list of function pointers for the
   routines to be called.

   Constructors are called in the order they are laid out
   in the table.  Destructors are called in the reverse order
   of the way they lie in the table.  */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "config.h"
#include "sys-params.h"
int target_flags;	/* satisfy dependency in config.h */
#include <types.h>
#include <sys/types.h>
#include <sys/stat.h>

#include <a.out.h>
#include <ar.h>

#ifdef UMAX
#include <sgs.h>
#endif

#define START_LABEL "__START_SYM"
#define END_LABEL "__END_SYM"

/*
 *	Define various output routines in terms on ASM_INT_OP,
 *	which should be defined in config.h -- if it's not, add it
 *	as the marker used to allocate an int on your architecture.
 */
#if 0 /*def i386*/
#define ASM_INT_OP	ASM_LONG
#define NO_UNDERSCORES 1
#endif
#ifndef ASM_INT_OP
#define ASM_INT_OP	".long"
#endif
#ifndef TEXT_SECTION_ASM_OP
#define TEXT_SECTION_ASM_OP ".text"
#endif
#ifndef DATA_SECTION_ASM_OP
#define DATA_SECTION_ASM_OP ".data"
#endif
#ifndef ASM_FILE_START
#define ASM_FILE_START
#endif
#ifndef ASM_GLOBALIZE_LABEL
#define ASM_GLOBALIZE_LABEL(FILE,NAME)  \
  do { fputs(".globl ", FILE); assemble_name (FILE, NAME); fputs("\n", FILE);}\
    while (0)
#endif
#ifndef ASM_OUTPUT_LABEL
#define ASM_OUTPUT_LABEL(FILE,NAME)     \
  do { assemble_name (FILE, NAME); fputs (":\n", FILE); } while (0)
#endif
#ifndef ASM_OUTPUT_LABELREF
#define ASM_OUTPUT_LABELREF(FILE,NAME)  \
  assemble_name (FILE, NAME)
#endif

#ifdef mips
#define NO_UNDERSCORES 1
#undef EXTENDED_COFF
#define EXTENDED_COFF
#ifndef SDATA_SECTION_ASM_OP
#undef ASM_INT_OP
#define ASM_INT_OP ".word"
#endif
/*
 * provide stubs for various things in tm-mips.h
 */
int inside_function = 0;
int num_source_filenames = 0;
void print_options() { return; };
void data_section() { return; };
FILE *asm_out_text_file, *asm_out_data_file;
void pfatal_with_name(char *str)
{
  fprintf(stderr,"collect: %s\n", str);
  exit(1);
}
void pic_label_is_global () {return;};
#endif

#ifndef ASM_OUTPUT_INT_CONST
#define ASM_OUTPUT_INT_CONST(FILE,VALUE)	\
  fprintf(FILE,"\t%s %d\n", ASM_INT_OP, VALUE)
#endif

#ifndef ASM_OUTPUT_LABELREF_AS_INT
#define ASM_OUTPUT_LABELREF_AS_INT(FILE,NAME)	\
  (fprintf(FILE,"\t%s ", ASM_INT_OP), \
   ASM_OUTPUT_LABELREF(FILE,NAME), \
   fprintf(FILE,"\n"))
#endif

/* Same as ASM_OUTPUT_LABELREF_AS_INT but never prefix '_'. */
#ifndef ASM_OUTPUT_RAW_LABELREF
#define ASM_OUTPUT_RAW_LABELREF(FILE,NAME)	\
  fprintf(FILE,"\t%s %s\n", ASM_INT_OP, NAME)
#endif

#ifndef ASM_OUTPUT_PTR_INT_SUM
#define ASM_OUTPUT_PTR_INT_SUM(FILE,NAME,VALUE)	\
  (fprintf(FILE,"\t%s", ASM_INT_OP), \
   ASM_OUTPUT_LABELREF(FILE,NAME), \
   fprintf(FILE,"+%d\n", VALUE))
#endif

#ifndef ASM_OUTPUT_SOURCE_FILENAME
#define ASM_OUTPUT_SOURCE_FILENAME(FILE,NAME)\
  fprintf (FILE, "\t.file\t\"%s\"\n", NAME);
#endif

extern void * xmalloc (int size);

enum error_code { OK, BAD_MAGIC, NO_NAMELIST,
		  FOPEN_ERROR, FREAD_ERROR, FWRITE_ERROR,
		  RANDOM_ERROR, };

enum error_code process (FILE *fp, char *filename);
enum error_code process_a  (FILE *fp);
enum error_code process_o (FILE *fp, struct exec *header, int size);

void assemble_name (FILE *file, char *name);

/* Files for holding the assembly code for table of constructor
   function pointer addresses and list of destructor
   function pointer addresses.  */
static FILE *outfile;

/* Default outfile name, or take name from argv with -o option.  */
static char *outfile_name = "a.out";

/* For compatibility with toplev.c and tm-sun386.h  */
char *dump_base_name;
int optimize = 0;
char *language_string = "GNU C++";
char *main_input_filename;

typedef struct symbol_elem {
    struct symbol_elem *next;
    char *name;
    int len;
    int hash;
} symbol_elem;

#define HASH_SIZE 256

struct symbol_elem * symbol_tab[HASH_SIZE] = {0};

int main (int argc, char **argv)
{
  int i, nerrs = 0;
  enum error_code code;
  FILE *fp;

  if (argc > 2 && !strcmp (argv[1], "-o"))
    {
      outfile_name = argv[2];
      i = 3;
    }
  else
    i = 1;

  if ((outfile = fopen (outfile_name, "w")) == NULL)
    {
      perror ("collect");
      exit (-1);
    }

  dump_base_name = main_input_filename = outfile_name;
  ASM_FILE_START(outfile);
  fprintf (outfile, "%s\n", DATA_SECTION_ASM_OP);
  ASM_GLOBALIZE_LABEL (outfile, START_LABEL);
  ASM_OUTPUT_LABEL (outfile, START_LABEL);

  for (; i < argc; i++)
    {
      char buf[80];

      /* This is a library, skip it.  */
      if (argv[i][0] == '-' && argv[i][1] == 'l')
	continue;

      if ((fp = fopen (argv[i], "r")) == NULL)
	{
	  sprintf (buf, "collect `%s'", argv[i]);
	  perror (buf);
	  exit (-1);
	}

      switch (code = process (fp, argv[i]))
	{
	case OK:
	  break;

	case BAD_MAGIC:
	  fprintf (stderr, "file `%s' has a bad magic number for collect\n",
		   argv[i]);
	  exit (-1);

	case NO_NAMELIST:
	  fprintf (stderr, "file `%s' has a no namelist for collect\n",
		   argv[i]);
	  exit (-1);

	case RANDOM_ERROR:
	  fprintf (stderr, "random error while processing file `%s':\n",
		   argv[i]);
	  perror ("collect");
	  exit (-1);

	case FOPEN_ERROR:
	  fprintf (stderr, "fopen(3S) error while processing file `%s' in collect\n",
		   argv[i]);
	  exit (-1);

	case FREAD_ERROR:
	  fprintf (stderr, "fread(3S) error while processing file `%s' in collect\n",
		   argv[i]);
	  exit (-1);

	case FWRITE_ERROR:
	  fprintf (stderr, "fwrite(3S) error while processing file `%s' in collect\n",
		   argv[i]);
	  exit (-1);

	default:
	  abort ();
	}

      fclose (fp);

    }

  fprintf (outfile, "%s\n", DATA_SECTION_ASM_OP);
  ASM_GLOBALIZE_LABEL (outfile, END_LABEL);
  ASM_OUTPUT_LABEL (outfile, END_LABEL);
  fclose (outfile);
  return 0;
}

#define SymPrefixLength 4
#define StrPrefixLength 4

enum SymbolKind {
    StringKind,
    KeywordKind, /* Symbol in KEYWORD package */
    CLispKind, /* Symbol in COMMON-LISP package */
    SchemeKind, /* Symbol in SCHEME package */
    BuiltinKind, /* Symbol in BUILTIN package */
    SymbolKind, /* General symbol in name package. */
};

void check_symbol(char *p)
{
    int p_len;
    symbol_elem *symbol_ptr;
    unsigned int hash;
    enum SymbolKind kind;
    if (p[0] != '_' || p[1] != '$')
	return;
    if (p[2] == 'K' && p[3] == 'W') /* "_$KW" */
	kind = KeywordKind;
    else if (p[2] == 'c' && p[3] == 'l') /* "_$cl" */
	kind = CLispKind;
    else if (p[2] == 's' && p[3] == 'c') /* "_$sc" */
	kind = SchemeKind;
    else if (p[2] == 'S' && p[3] == 'Y') /* "_$SY" */
	kind = SymbolKind;
    else if (p[2] == 'Q' && p[3] == 'B') /* "_$SY" */
	kind = BuiltinKind;
    else if (p[2] == 'S' && p[3] == 'T') /* "_$ST" */
	kind = StringKind;
    else
	return;
    p_len = strlen(p);
    hash = hash_string(p, p_len);
    for (symbol_ptr = symbol_tab[hash % HASH_SIZE];
	 ; symbol_ptr = symbol_ptr->next)
	if (symbol_ptr == NULL) {
	    int i;
	    char *str_buf;
	    char *pack_buf;
	    char *name_start;
	    int align = sizeof(int);
	    symbol_ptr = (symbol_elem*)xmalloc(sizeof(symbol_elem));
	    symbol_ptr->next = symbol_tab[hash % HASH_SIZE];
	    symbol_tab[hash % HASH_SIZE] = symbol_ptr;
	    symbol_ptr->hash = hash;
	    symbol_ptr->len = p_len;
	    symbol_ptr->name = (char*)xmalloc(p_len+1);
	    strcpy(symbol_ptr->name, p);
	    switch (kind) {
		char *sym_buf;
		int sym_len;
	      case KeywordKind:
	      case CLispKind:
	      case SchemeKind:
	      case BuiltinKind:
	      case SymbolKind:
		str_buf = (char*)xmalloc(strlen(p)+1);
		name_start = p+SymPrefixLength;
		if (kind == SymbolKind) {
		    char *package_name_start = name_start;
		    int package_name_length;
		    /* Look for two consecutive '$'s. */
		    while (name_start[0] != '$' || name_start[1] != '$')
			if (name_start[0] == '\0') return;
			else name_start++;
		    package_name_length = name_start - package_name_start;
		    name_start += 2; /* Skip "$$". */
		    pack_buf = (char*)xmalloc(package_name_length+5);
		    sprintf(pack_buf, "_$pk%.*s",
			    package_name_length,
			    package_name_start);
		}
		sprintf(str_buf, "_$ST%s", name_start);
		fprintf (outfile, "%s\n", DATA_SECTION_ASM_OP);
#ifndef NO_UNDERSCORES
		p++;
#endif
		ASM_GLOBALIZE_LABEL (outfile, p);
		ASM_OUTPUT_LABEL (outfile, p);
		ASM_OUTPUT_LABELREF_AS_INT(outfile, "_vt$Symbol");
		 /*Property list*/
		ASM_OUTPUT_LABELREF_AS_INT(outfile, "NilSymbol");
#ifdef C_SYM
	        fprintf(outfile,"\t%s %s+%d\n", ASM_INT_OP,
			str_buf, sizeof(StringC));
#else
		ASM_OUTPUT_RAW_LABELREF(outfile, str_buf);
#endif
		switch (kind) {
		  case KeywordKind:
		    ASM_OUTPUT_RAW_LABELREF(outfile, "_$pkKEYWORD");
		    break;
		  case CLispKind:
		    ASM_OUTPUT_RAW_LABELREF(outfile, "_$pkCOMMON$_$LISP");
		    break;
		  case SchemeKind:
		    ASM_OUTPUT_RAW_LABELREF(outfile, "_$pkSCHEME");
		    break;
		  case BuiltinKind:
		    ASM_OUTPUT_RAW_LABELREF(outfile, "_$pkBUILTIN");
		    break;
		  case SymbolKind:
		    ASM_OUTPUT_RAW_LABELREF(outfile, pack_buf);
		}
		ASM_OUTPUT_INT_CONST(outfile, 0); /* Value cell */
#ifdef C_SYM
{
 char*	tmp_buf = (char*)xmalloc(p_len);
#ifndef NO_UNDERSCORES
		p--;
#endif
 int slen = decode_label_to_string(p+StrPrefixLength, tmp_buf);
#ifdef LITTLE_ENDIAN
		ASM_OUTPUT_INT_CONST(outfile, slen<<8); /* flags */
#else
		ASM_OUTPUT_INT_CONST(outfile, slen); /* flags */
#endif
}
#else
		ASM_OUTPUT_INT_CONST(outfile, 0); /* flags */
#endif
		check_symbol(str_buf);
		free(str_buf);
		break;
	      case StringKind:
		sym_buf = (char*)xmalloc(p_len);
		sym_len = decode_label_to_string(p+StrPrefixLength, sym_buf);
		fprintf (outfile, "%s\n", TEXT_SECTION_ASM_OP);
#ifndef NO_UNDERSCORES
		p++;
#endif
		ASM_GLOBALIZE_LABEL (outfile, p);
		ASM_OUTPUT_LABEL(outfile, p);
		ASM_OUTPUT_LABELREF_AS_INT(outfile, "_vt$StringC");
		ASM_OUTPUT_INT_CONST(outfile, sym_len);
#ifdef ASM_OUTPUT_ASCII
		ASM_OUTPUT_ASCII(outfile, sym_buf, sym_len);
#else
		fprintf(outfile, "\t.ascii\t\"");
		for (i = 0; i < sym_len; i++) {
		    int c = (unsigned char)sym_buf[i];
		    if (c >= ' ' && c < 127 && c != '\"' && c != '\\')
			putc(c, outfile);
		    else
			fprintf(outfile, "\\%03o", c);
		}
		fprintf(outfile, "\"\n");
#endif
		i = align - (unsigned)sym_len % align; /* Forces 1 NUL byte. */
#ifdef ASM_OUTPUT_SKIP
	    ASM_OUTPUT_SKIP(outfile, i);
#else
		fprintf(outfile, "\t.space\t%d\n", i);
#endif
		break;
	    } /* End switch */
	    break;
	} else if (symbol_ptr->hash == hash
		   && symbol_ptr->len == p_len
		   && !memcmp(symbol_ptr->name, p, p_len)) {
	    break;
	}
}



/*****************************************************************************
 *	
 *		COFF & EXTENDED COFF
 *	
 ****************************************************************************/
#if defined(COFF) || defined(EXTENDED_COFF)

#include <ldfcn.h>

#if defined(EXTENDED_COFF)
#   define GCC_SYMBOLS(X) (SYMHEADER(X).isymMax+SYMHEADER(X).iextMax)
#   define GCC_SYMENT SYMR
#   define GCC_OK_SYMBOL(X) ((X).st == stProc &&  (X).sc == scText)
#   define GCC_SYMINC(X) (1)
#else
#   define GCC_SYMBOLS(X) (HEADER(ldptr).f_nsyms)
#   define GCC_SYMENT SYMENT
#ifndef UNUSUAL_COFF_DEFINITION
#   define GCC_OK_SYMBOL(X) (!(((X).n_type & N_TMASK) != (DT_NON << N_BTSHFT)))
#else
#   define GCC_OK_SYMBOL(X) ((X).n_scnum == 1 && (X).n_sclass == C_EXT)
#endif
#   define GCC_SYMINC(X) ((X).n_numaux+1)
#endif

enum error_code
  process (FILE *fp, char *filename)
{
  LDFILE *ldptr = NULL;
  do {
    if ((ldptr = ldopen(filename, ldptr)) != NULL ) {
      
      if (!ISCOFF( HEADER(ldptr).f_magic ) ) {
	return BAD_MAGIC;
      }
      else {
	
	int symbols = GCC_SYMBOLS(ldptr);
	int symindex;
	
	for (symindex = 0;
	     symindex < symbols;
	     ) {
	  
	  GCC_SYMENT symbol;
	  
	  char *symbol_name;
	  extern char *ldgetname();
	  int returnCode;
	  
	  returnCode = ldtbread(ldptr, symindex, &symbol);
	  
	  if ( returnCode <= 0 ) {
	    break;
	  }
	  
	  symindex += GCC_SYMINC(symbol);
	  
	  check_symbol (ldgetname(ldptr, &symbol));
	}
      }
    }
    else {
      return( RANDOM_ERROR );
    }
  } while ( ldclose(ldptr) == FAILURE ) ;
  return ( OK );
}

/****** taken from sdbout.c ******/


/* Tell the assembler the source file name.
   On systems that use SDB, this is done whether or not -g,
   so it is called by ASM_FILE_START.

   ASM_FILE is the assembler code output file,
   INPUT_NAME is the name of the main input file.  */

/* void */
sdbout_filename (FILE *asm_file, char *input_name)
{
  int len = strlen (input_name);
  char *na = input_name + len;

  /* NA gets INPUT_NAME sans directory names.  */
  while (na > input_name)
    {
      if (na[-1] == '/')
	break;
      na--;
    }

  ASM_OUTPUT_SOURCE_FILENAME (asm_file, na);
}

#else

/*****************************************************************************
 *	
 *		BSD SYMBOL TABLES
 *	
 *****************************************************************************/

/* Figure out the type of file we need to process.
   Currently, only .o and .a formats are acceptable.  */
enum error_code
process (FILE *fp, char *filename)
{
  struct stat file_stat;
  union
    {
      char ar_form[SARMAG];
      struct exec a_out_form;
    } header;
  int size;

  if (fstat (fileno(fp), &file_stat))
    return RANDOM_ERROR;

  size = file_stat.st_size;

  if (fread (header.ar_form, SARMAG, 1, fp) < 1)
    return RANDOM_ERROR;

  if (strncmp (ARMAG, header.ar_form, SARMAG))
    {
      fseek (fp, 0, 0);
      if (fread (&header.a_out_form, sizeof (struct exec), 1, fp) < 1)
	return RANDOM_ERROR;

      if (N_BADMAG (header.a_out_form))
	return BAD_MAGIC;

      return process_o (fp, &header.a_out_form, size);
    }
  return process_a (fp);
}

enum error_code
process_o (FILE *fp, struct exec *header, int size)
{
  int symoff, symend;
#ifndef hp9000s300
  struct nlist *nelem, *nelems, *nend;
  char *strtab;
#else
  struct nlist_ *nelem, *nelems, *nend;
#endif /* hp9000s300 */

  if (N_BADMAG (*header))
    return BAD_MAGIC;

#ifndef hp9000s300
  symoff = N_SYMOFF (*header);
  symend = N_STROFF (*header);
#else
  symoff = LESYM_OFFSET (*header);
  symend = DNTT_OFFSET (*header);
#endif /* hp9000s300 */
  if (symoff == symend)
    return NO_NAMELIST;
  fseek (fp, symoff - sizeof (struct exec), 1);
#ifndef hp9000s300
  nelems = (struct nlist *)alloca (symend - symoff);
#else
  nelems = (struct nlist_ *)alloca (symend - symoff);
#endif /* hp9000s300 */
  if (fread (nelems, sizeof (char), symend - symoff, fp) < symend - symoff)
    return FREAD_ERROR;

#ifndef hp9000s300
  strtab = (char *)alloca ((char *)size - (char *)symend);
  if (fread (strtab, sizeof (char), (char *)size - (char *)symend, fp)
      < ((char *)size - (char *)symend) * sizeof (char))
    return FREAD_ERROR;

  nend = (struct nlist *)((char *)nelems + symend - symoff);
  for (nelem = nelems; nelem < nend; nelem++)
#else
  nend = (struct nlist_ *)((char *)nelems + symend - symoff);
  for (nelem = nelems; nelem < nend; )
#endif /* hp9000s300 */
    {
#ifndef hp9000s300
      int strindex = nelem->n_un.n_strx;
      if (strindex)
#else
      int symlen = nelem->n_length;
      char p[255];
      memcpy(p, (char *) (++nelem), symlen);
      p[symlen]='\0';
     
      /* printf("'%s'\n",p);   */
      nelem   = (struct nlist_ *)((char *)nelem + symlen);
      
      if (symlen)
#endif /* hp9000s300 */

	{
#ifndef hp9000s300
	  char *p = strtab+strindex;
#endif /* hp9000s300 */

	  if (nelem->n_type == N_UNDF|N_EXT)
	      check_symbol(p);
      }
    }
  return OK;
}

enum error_code
process_a (FILE *fp)
{
  struct ar_hdr header;
  struct exec exec_header;
  int size;
  enum error_code code;

  while (! feof (fp))
    {
      char c;
#ifdef hp9000s300
      int curpos;
#endif /* hp9000s300 */

      if (fread (&header, sizeof (struct ar_hdr), 1, fp) < 1)
	return RANDOM_ERROR;

      size = atoi (header.ar_size);

#ifndef hp9000s300
      if (fread (&exec_header, sizeof (struct exec), 1, fp) < 1)
	return RANDOM_ERROR;
#else
      curpos = ftell(fp);
      /* if the name starts with /, it's an index file */
      if (header.ar_name[0] != '/') {
      
        if (fread (&exec_header, sizeof (struct exec), 1, fp) < 1)
	  return RANDOM_ERROR;
#endif /* hp9000s300 */

      code = process_o (fp, &exec_header, size);
      if (code != OK)
	return code;
#ifdef hp9000s300
      } 
      
      if (fseek(fp,(long) curpos + size,0))
	return RANDOM_ERROR;
#endif /* hp9000s300 */
      if ((c = getc (fp)) == '\n')
	;
      else
	ungetc (c, fp);
      c = getc (fp);
      if (c != EOF)
	ungetc (c, fp);
    }
  return OK;
}
#endif

/* Output to FILE a reference to the assembler name of a C-level name NAME.
   If NAME starts with a *, the rest of NAME is output verbatim.
   Otherwise NAME is transformed in an implementation-defined way
   (usually by the addition of an underscore).
   Many macros in the tm file are defined to call this function.

   Swiped from `varasm.c'  */

void
assemble_name (FILE *file, char *name)
{
  if (name[0] == '*')
    fputs (&name[1], file);
  else
#ifdef NO_UNDERSCORES
    fprintf (file, "%s", name);
#else
    fprintf (file, "_%s", name);
#endif
}

void *
xmalloc (int size)
{
  void* result = malloc (size);
  if (! result)
    {
      fprintf (stderr, "Virtual memory exhausted\n");
      exit (-1);
    }
  return result;
}
