/*! \file c-globals-tbl.c
 *
 * COPYRIGHT (c) 2019 The Fellowship of SML/NJ (http://www.smlnj.org)
 * All rights reserved.
 *
 * This file implements a registry of global C symbols that may be referenced
 * in the ML heap (e.g., references to C functions).
 */

#include "ml-base.h"
#include "tags.h"
#include "ml-values.h"
#include "c-globals-tbl.h"
#include <string.h>

#define MAKE_EXTERN(index)	MAKE_DESC(index, DTAG_extern)

#define HASH_STRING(name, res)	{				\
	const char	*__cp = (name);				\
	int	__hash = 0, __n;				\
	for (;  *__cp;  __cp++) {				\
	    __n = (128*__hash) + (unsigned)*__cp;		\
	    __hash = __n - (8388593 * (__n / 8388593));		\
	}							\
	(res) = __hash;						\
    }

typedef struct item {		/* An item in the Symbol/Addr tables */
    ml_val_t	addr;		    /* The address of the external reference */
    const char	*name;		    /* The name of the reference */
    int		stringHash;	    /* The hash sum of the name */
    struct item	*nextSymb;	    /* The next item the SymbolTable bucket */
    struct item	*nextAddr;	    /* The next item the AddrTable bucket */
} item_t;

typedef struct item_ref {	/* an item in an export table */
    item_t	*item;
    int		index;
    struct item_ref *next;
} item_ref_t;

struct export_table {		/* A table of C symbols mapping strings to items, */
				/* which is used when loading a heap image. */
    item_ref_t	**table;
    int		tableSize;
    int		numItems;
    item_t	**itemMap;	    /* A map from item #s to items */
    int		itemMapSize;
};

/* hash key to index */
#define STRHASH_INDEX(h, sz)	((h) & ((sz)-1))
#define ADDRHASH_INDEX(a, sz)	(((Word_t)(a) >> 3) & ((sz)-1))


PVT item_t	**SymbolTable = NIL(item_t **);	/* Maps names to items */
PVT item_t	**AddrTable = NIL(item_t **);	/* Maps addresses to items */
PVT int		TableSize = 0;		/* The size of the tables; always */
					/* power of 2. */
PVT int		NumSymbols = 0;		/* The number of entries in the tables */

/* local routines */
PVT void GrowTable (export_table_t *tbl);


/* RecordCSymbol:
 *
 * Enter a global C symbol into the tables.
 */
void RecordCSymbol (const char *name, ml_val_t addr)
{
    int			n, i, hash;
    item_t		*item, *p;

    ASSERT (((Word_t)addr & MAJOR_MASK) == 0);

    if (TableSize == NumSymbols) {
      /* double the table size */
	int	newTblSz = (TableSize ? 2*TableSize : 64);
	item_t	**newSTbl = NEW_VEC(item_t *, newTblSz);
	item_t	**newATbl = NEW_VEC(item_t *, newTblSz);

	memset ((char *)newSTbl, 0, sizeof(item_t *) * newTblSz);
	memset ((char *)newATbl, 0, sizeof(item_t *) * newTblSz);

	for (i = 0;  i < TableSize;  i++) {
	    for (p = SymbolTable[i];  p != NIL(item_t *); ) {
		item = p;
		p = p->nextSymb;
		n = STRHASH_INDEX(item->stringHash, newTblSz);
		item->nextSymb = newSTbl[n];
		newSTbl[n] = item;
	    }
	    for (p = AddrTable[i];  p != NIL(item_t *); ) {
		item = p;
		p = p->nextAddr;
		n = ADDRHASH_INDEX(item->addr, newTblSz);
		item->nextAddr = newATbl[n];
		newATbl[n] = item;
	    }
	}

	if (SymbolTable != NIL(item_t **)) {
	    FREE (SymbolTable);
	    FREE (AddrTable);
	}
	SymbolTable = newSTbl;
	AddrTable = newATbl;
	TableSize = newTblSz;
    }

  /* compute the string hash function */
    HASH_STRING(name, hash);

  /* Allocate the item */
    item = NEW_OBJ(item_t);
    item->name		= name;
    item->stringHash	= hash;
    item->addr		= addr;

  /* insert the item into the symbol table. */
    n = STRHASH_INDEX(hash, TableSize);
    for (p = SymbolTable[n];  p != NIL(item_t *);  p = p->nextSymb) {
	if ((p->stringHash == hash) && (strcmp(name, p->name) == 0)) {
	    if (p->addr != addr)
		Die ("global C symbol \"%s\" defined twice", name);
	    else {
		FREE (item);
		return;
	    }
	}
    }
    item->nextSymb	= SymbolTable[n];
    SymbolTable[n]	= item;

  /* insert the item into the addr table. */
    n = ADDRHASH_INDEX(addr, TableSize);
    for (p = AddrTable[n];  p != NIL(item_t *);  p = p->nextAddr) {
	if (p->addr == addr) {
	    if ((p->stringHash != hash) || (strcmp(name, p->name) != 0))
		Die ("address %#x defined twice: \"%s\" and \"%s\"",
		    addr, p->name, name);
	    else {
		FREE (item);
		return;
	    }
	}
    }
    item->nextAddr	= AddrTable[n];
    AddrTable[n]	= item;
    NumSymbols++;

} /* end of RecordCSymbol */

/* AddrToCSymbol:
 *
 * Return the name of the C symbol that labels the given address
 * (or NIL).
 */
const char *AddrToCSymbol (ml_val_t addr)
{
    item_t	*q;

  /* Find the symbol in the AddrTable */
    for (q = AddrTable[ADDRHASH_INDEX(addr, TableSize)];
	 q != NIL(item_t *);
	 q = q->nextAddr)
    {
	if (q->addr == addr)
	   return q->name;
    }

    return NIL(const char *);

} /* end of AddrToCSymbol */

/* NewExportTbl:
 */
export_table_t *NewExportTbl ()
{
    export_table_t	*tbl;

    tbl = NEW_OBJ(export_table_t);
    tbl->table		= NIL(item_ref_t **);
    tbl->tableSize	= 0;
    tbl->numItems	= 0;
    tbl->itemMap	= NIL(item_t **);
    tbl->itemMapSize	= 0;

    return tbl;

} /* end of NewExportTbl */

/* ExportCSymbol:
 *
 * Add an external address to an export table, returning its external reference
 * descriptor.
 */
ml_val_t ExportCSymbol (export_table_t *tbl, ml_val_t addr)
{
    Addr_t	a = PTR_MLtoADDR(addr);
    item_ref_t	*p;
    item_t	*q;
    int		h, index;

/*SayDebug("ExportCSymbol: addr = %#x, ", addr);*/

    if (tbl->numItems >= tbl->tableSize)
	GrowTable (tbl);

  /* First check to see if addr is already in tbl */
    h = ADDRHASH_INDEX(a, tbl->tableSize);
    for (p = tbl->table[h];  p != NIL(item_ref_t *);  p = p->next) {
	if (p->item->addr == addr) {
/*SayDebug("old name = \"%s\", index = %d\n", p->item->name, p->index);*/
	    return MAKE_EXTERN(p->index);
	}
    }

  /* Find the symbol in the AddrTable */
    for (q = AddrTable[ADDRHASH_INDEX(a, TableSize)];  q != NIL(item_t *);  q = q->nextAddr) {
	if (q->addr == addr)
	   break;
    }
    if (q == NIL(item_t *)) {
	Error("external address %#x not registered\n", addr);
	return ML_unit;
    }

  /* Insert the index into the address to index map. */
/*SayDebug("new name = \"%s\", index = %d\n", q->name, tbl->numItems);*/
    index		= tbl->numItems++;
    if (tbl->itemMapSize <= index) {
	int		newSz = ((tbl->itemMapSize == 0) ? 64 : 2*tbl->itemMapSize);
	item_t		**newMap = NEW_VEC(item_t *, newSz);
	int		i;

	for (i = 0;  i < tbl->itemMapSize;  i++)
	    newMap[i] = tbl->itemMap[i];
	if (tbl->itemMap != NIL(item_t **))
	    FREE (tbl->itemMap);
	tbl->itemMap = newMap;
	tbl->itemMapSize = newSz;
    }
    tbl->itemMap[index]	= q;

  /* Insert the address into the export table */
    p			= NEW_OBJ(item_ref_t);
    p->item		= q;
    p->index		= index;
    p->next		= tbl->table[h];
    tbl->table[h]	= p;

    return MAKE_EXTERN(index);

} /* end of ExportCSymbol */

/* AddrOfCSymbol:
 *
 * Given an external reference, return its address.
 */
ml_val_t AddrOfCSymbol (export_table_t *tbl, ml_val_t xref)
{
    int	index;

    index = GET_LEN(xref);

/*SayDebug("AddrOfCSymbol: %#x: %d --> %#x\n", xref, index, tbl->itemMap[index]->addr);*/
    if (index >= tbl->numItems) {
	Die ("bad external object index %d", index);
    }

    return tbl->itemMap[index]->addr;

} /* end of AddrOfCSymbol */

/* ExportedSymbols:
 */
void ExportedSymbols (export_table_t *tbl, int *numSymbs, export_item_t **symbs)
{
    int			i, n = tbl->numItems;
    item_t		**p;
    export_item_t	*ep;

    *numSymbs = n;
    *symbs  = ep = NEW_VEC(export_item_t, n);
    for (p = tbl->itemMap, i = 0;  i < n;  i++) {
	*ep = (*p)->name;
	p++; ep++;
    }

} /* end of ExportedSymbols */


/* FreeExportTbl:
 *
 * Free the storage used by a import/export table.
 */
void FreeExportTbl (export_table_t *tbl)
{
    int		i;
    item_ref_t	*p, *q;

    for (i = 0;  i <  tbl->tableSize;  i++) {
	for (p = tbl->table[i];  p != NIL(item_ref_t *); ) {
	    q = p->next;
	    FREE (p);
	    p = q;
	}
    }

    if (tbl->itemMap != NIL(item_t **))
	FREE (tbl->itemMap);

    FREE (tbl);

} /* end of FreeExportTbl */


/* ImportCSymbol:
 */
ml_val_t ImportCSymbol (const char *name)
{
    int		hash, index;
    item_t	*p;

    HASH_STRING(name, hash);

  /* insert the item into the symbol table. */
    index = STRHASH_INDEX(hash, TableSize);
    for (p = SymbolTable[index];  p != NIL(item_t *);  p = p->nextSymb) {
	if ((p->stringHash == hash) && (strcmp(name, p->name) == 0)) {
	    return (p->addr);
	}
    }

    return ML_unit;

} /* end of ImportCSymbol */


/* ExportTableSz:
 *
 * Return the number of bytes required to represent the strings in an exported
 * symbols table.
 */
Addr_t ExportTableSz (export_table_t *tbl)
{
    int		i;
    Addr_t	nbytes;

    for (nbytes = 0, i = 0;  i < tbl->numItems;  i++) {
	nbytes += (strlen(tbl->itemMap[i]->name) + 1);
    }
    nbytes = ROUNDUP(nbytes, WORD_SZB);

    return nbytes;

} /* end of ExportTableSz */


/* GrowTable:
 */
PVT void GrowTable (export_table_t *tbl)
{
    int		newTblSz = (tbl->tableSize ? 2 * tbl->tableSize : 32);
    item_ref_t	**newTbl = NEW_VEC(item_ref_t *, newTblSz);
    int		i, n;
    item_ref_t	*p, *q;

    memset ((char *)newTbl, 0, newTblSz * sizeof(item_ref_t *));

    for (i = 0;  i < tbl->tableSize;  i++) {
	for (p = tbl->table[i];  p != NIL(item_ref_t *); ) {
	    q = p;
	    p = p->next;
	    n = ADDRHASH_INDEX(q->item->addr, newTblSz);
	    q->next = newTbl[n];
	    newTbl[n] = q;
	}
    }

    if (tbl->table != NIL(item_ref_t **)) FREE (tbl->table);
    tbl->table		= newTbl;
    tbl->tableSize	= newTblSz;

} /* end of GrowTable */

