/******************************** -*- C -*- ****************************
 *
 *	Dynamic threaded code compiler.
 *
 *	$Revision: 1.7.5$
 *	$Date: 2000/05/28 16:56:52$
 *	$Author: pb$
 *
 ***********************************************************************/

/***********************************************************************
 *
 * Copyright 1988-92, 1994-95, 1999, 2000 Free Software Foundation, Inc.
 * Written by Paolo Bonzini.
 *
 * This file is part of GNU Smalltalk.
 *
 * GNU Smalltalk is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation; either version 2, or (at your option) any later 
 * version.
 * 
 * GNU Smalltalk is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 * more details.
 * 
 * You should have received a copy of the GNU General Public License along with
 * GNU Smalltalk; see the file COPYING.  If not, write to the Free Software
 * Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.  
 *
 ***********************************************************************/

/***************************************************************************/
/*                     ,------------------------------.                    */
/* -------------------: This file is *not* used yet!! :------------------- */
/*                    `------------------------------'                     */
/* It will be rewritten for 1.8 -- you better think that it does not exist */
/***************************************************************************/

#include "gst.h"
#include "alloc.h"
#include "byte.h"
#include "comp.h"
#include "dynamic.h"
#include "interp.h"
#include "memzero.h"
#include "oop.h"
#include "sysdep.h"
#include "obstack.h"

/* Define this if you want the translator to print what it is doing
 * with every bytecode it finds on his way.
 */
/* #define DEBUG_TRANSLATION */

/* Define this to forbid the creation of macro opcodes */
/* #define NO_INLINING */

#ifdef NO_INLINING
#define DONT_INLINE true
#endif
#ifndef NO_INLINING
#define DONT_INLINE false
#endif

/* Slightly oversized to avoid silly bugs... */
Opcode				opcodes[300];
mst_Boolean			opcodeTableInitialized = false;



#define HASH_TABLE_SIZE		(2*1024)
#define MAX_HASH_TABLE_SIZE	(HASH_TABLE_SIZE * 3 / 4)
#define MAX_MACRO_CACHE_SIZE	(MAX_HASH_TABLE_SIZE * 1024)

typedef struct OpcodeEntryStruct {
  struct OpcodeEntryStruct *next;
  unsigned long		    hashValue;
  unsigned int		    nBytes;
  long	 		    codeStart[1];
} OpcodeEntry;

typedef struct MethodEntryStruct {
  struct MethodEntryStruct *next;
  OOP			    methodOOP;
  InstructionType	    threadedStart[1];
} MethodEntry;

#define OPCODE_HEADER_SIZE (sizeof(OpcodeEntry) - sizeof(long))
#define METHOD_HEADER_SIZE (sizeof(MethodEntry) - sizeof(InstructionType))

/* Here is where the dynamically compiled stuff goes */
static OpcodeEntry 		*macroOpcodes[HASH_TABLE_SIZE];
static MethodEntry 		*methodsTable[HASH_TABLE_SIZE];

static struct obstack		macroOpcodesObstack;
static struct obstack		methodsTableObstack;

/* Status of the macroOpcodesObstack, kept here for faster updates */
static int			bytecodesCopied;
static unsigned long		macroHashValue;
static mst_Boolean		isMacro = false;

/* Status of the methodsTableObstack, kept here for faster updates */
static int			left;
static int			offsetToFill;
static Byte			*in;

/* This are used to check for the need to flush the cache */
static int			macroCount      = 0;
static int			methodCount     = 0;
static int			macroCacheSize  = 0;

static inline void		newOpcodeEntry(), finishOpcodeEntry(),
				newMethodEntry(), copyBytecode(),
				emitOpcode(), emitArguments(),
				updateHash(), compileToThreadedCode(),
				invalidateMacroOpcodeCache();

static inline InstructionType	*finishMethodEntry();

void
newOpcodeEntry()
{
  obstack_blank(&macroOpcodesObstack, OPCODE_HEADER_SIZE);
  offsetToFill = obstack_object_size (&methodsTableObstack);
  macroHashValue = 0;
  bytecodesCopied = 0;
  isMacro = true;
}

void
finishOpcodeEntry(dest)
     InstructionType	*dest;
{
  int 		nBytes;
  OpcodeEntry  *macro;
  OpcodeEntry  *otherMacro;
  
  nBytes = obstack_object_size(&macroOpcodesObstack);
  macro = obstack_finish(&macroOpcodesObstack);

#ifdef DEBUG_TRANSLATION
  printf("%p  end opcode", ((char *)macro) + nBytes);
#endif

  macro->hashValue = macroHashValue;
  macroHashValue %= HASH_TABLE_SIZE;

  macro->next = macroOpcodes[macroHashValue];
  macro->nBytes = nBytes;
  macroOpcodes[macroHashValue] = macro;

  for (otherMacro = macro->next; otherMacro; otherMacro = otherMacro->next) {
    if (!memcmp(&macro->hashValue, &otherMacro->hashValue, nBytes - SIZEOF_CHAR_P)) {
      macroOpcodes[macroHashValue] = macro->next;
      obstack_free(&macroOpcodesObstack, macro);
#ifdef DEBUG_TRANSLATION
      printf(" (recycled)");
#endif
      debug();
      break;
    }
  }

  if (!otherMacro) {
    macroCount++;
    macroCacheSize += nBytes;
    flushCode(&macro->codeStart, nBytes - OPCODE_HEADER_SIZE);
  } else {
    macro = otherMacro;
  }

  isMacro = false;
  *dest = (InstructionType) &macro->codeStart;
#ifdef DEBUG_TRANSLATION
  printf("\n");
#endif
}

void
newMethodEntry(methodOOP)
     OOP methodOOP;
{
  MethodEntry *newThread;

  obstack_blank(&methodsTableObstack, METHOD_HEADER_SIZE);
  newThread = (MethodEntry *) obstack_base(&methodsTableObstack);
  newThread->methodOOP = methodOOP;

  offsetToFill = METHOD_HEADER_SIZE;  
  left = obstack_room(&methodsTableObstack) / SIZEOF_CHAR_P;
}

InstructionType *
finishMethodEntry()
{
  MethodEntry  *method;
  unsigned int	hashEntry;

  method = obstack_finish(&methodsTableObstack);
  hashEntry = oopIndex(method->methodOOP) % HASH_TABLE_SIZE;

  method->next = methodsTable[hashEntry];
  methodsTable[hashEntry] = method;
  methodCount++;

  return method->threadedStart;
}


void
updateHash(op)
     Byte op;
{
  if (macroHashValue < 0) {
    op ^= 1;
  }
  macroHashValue <<= 1;
  macroHashValue ^= op;
}

void
emitOpcode(op)
     voidPtr op;
{
  bytecodesCopied++;
  if (left--) {
    obstack_ptr_grow_fast(&methodsTableObstack, op);
  } else {
    obstack_ptr_grow(&methodsTableObstack, op);
    left = obstack_room(&methodsTableObstack);
  }
}

void
emitArguments(nargs)
     int  nargs;
{
  while (nargs--) {
    register Byte operand = *in++;
    emitOpcode((void *) (unsigned long) operand);
  }
}

void
copyBytecode(op, nargs, endMacro)
     Byte op;
     int nargs;
     mst_Boolean endMacro;
{
  int size;

  if (!isMacro) {
    newOpcodeEntry();
  }

  size = endMacro ? opcodes[op].threadSize : opcodes[op].size;

  updateHash(op);
  obstack_grow (&macroOpcodesObstack, opcodes[op].start, size);

  if (endMacro) {
    char *base = (char *) obstack_base (&methodsTableObstack);
    finishOpcodeEntry((InstructionType *) (base + offsetToFill));
  }
}

void
compileToThreadedCode(byteCodes, size)
     Byte	*byteCodes;
     int	size;
{
  register Byte	*last;
  register int	thisOp, nargs;
  mst_Boolean	endMacro;
  char		*destination;

  destination = alloca((size + 1) * sizeof(char));
  makeDestinationTable(byteCodes, size, destination);
  destination[size] = true;

  in = byteCodes;
  last = in + size;
  nargs = 0;
  
  while (in < last) {
    thisOp = (int) *in++;		/* Grab a bytecode */
    destination += nargs + 1;		/* Keep it in sync with in */
    nargs = byteCodeSize(thisOp) - 1;
    endMacro = DONT_INLINE || opcodes[in[nargs]].protect || destination[nargs];

    if (opcodes[thisOp].split) {
      thisOp = opcodes[thisOp].split + (*in >> 6);
    }

#ifdef DEBUG_TRANSLATION
    if (opcodes[thisOp].protect || (!isMacro && endMacro)) {
      printf("%p   thread to ", opcodes[thisOp].start);
    } else {
      printf("%p  ", obstack_base(&macroOpcodesObstack) +
		  obstack_object_size(&macroOpcodesObstack));

      if (isMacro) {
	printf("append    ");
      } else {
	printf("copy      ");
      }
    }
    printByteCodeName (in - 1, in - 1 - byteCodes, nil);
    printf("\n");
#endif

    if (opcodes[thisOp].protect || (!isMacro && endMacro)) {
      emitOpcode(opcodes[thisOp].start);
    } else {
      copyBytecode(thisOp, nargs, endMacro);

      /* emitting the current opcode's offset eases debugging
       * a lot -- e.g. if gdb says `SIGSEGV at address 0x2a'
       * I know that wrong inlined code was generated for
       * opcode offset 0x2a (that is, 42).
       */
      emitOpcode((voidPtr) (in - 1 - byteCodes) );
    }
    emitArguments(nargs);
  }

#ifdef DEBUG_TRANSLATION
  printf("\n");
#endif
}


InstructionType *
getThreadedCode(methodOOP)
     OOP	methodOOP;
{
  register MethodEntry  *method, *prev;
  register unsigned int hashEntry;
  register unsigned int size;

  if (isNil(methodOOP)) {
    return (nil);
  }

  hashEntry = oopIndex(methodOOP) % HASH_TABLE_SIZE;
  if (method = methodsTable[hashEntry]) {
    if (method->methodOOP == methodOOP) {
      return method->threadedStart;
    }

    for (prev = method; method = method->next; prev = method) {
      if (method->methodOOP != methodOOP) {
	continue;
      }
      prev->next = method->next;
      method->next = methodsTable[hashEntry];
      methodsTable[hashEntry] = method;
      return method->threadedStart;
    }
  }

  if (methodCount > MAX_HASH_TABLE_SIZE) {
    invalidateThreadedCodeCache();
  }
  if (macroCacheSize > MAX_MACRO_CACHE_SIZE
      || macroCount > MAX_HASH_TABLE_SIZE) {
    invalidateMacroOpcodeCache();
  }

  size = numIndexableFields(methodOOP);
  newMethodEntry (methodOOP, size);
  compileToThreadedCode (getMethodByteCodes(methodOOP), size);
  return (finishMethodEntry (methodOOP));
}

void
invalidateMacroOpcodeCache()
{
  if (opcodeTableInitialized) {
    obstack_free (&macroOpcodesObstack, NULL);
  }
  obstack_init (&macroOpcodesObstack);
  memzero (macroOpcodes, sizeof(macroOpcodes));
  macroCount = macroCacheSize = 0;
}

void
invalidateThreadedCodeCache()
{
  fixupObjectPointers();

  if (!opcodeTableInitialized) {
    invalidateMacroOpcodeCache();
    interpret();
    opcodeTableInitialized = true;
  } else {
    obstack_free (&methodsTableObstack, NULL);
    /* Watch out! obstack_free with NULL as the second parameter
     * leaves the obstack uninitialized!! */
  }

  obstack_init (&methodsTableObstack);
  memzero (methodsTable, sizeof(methodsTable));
  methodCount = 0;
  restoreObjectPointers();
}
