// main.cc -- compiler initialisation and hooks
//
// Author: Ian.Piumarta@INRIA.Fr
//
// Last edited: 03/16/01 by marcus@ira.uka.de

#define	INTERNAL	// define to make version linkable with squeak binary

extern "C" {
#include "sqVirtualMachine.h"
};

#undef	DEBUG

#undef  PRINT_STATS
#undef	DO_PROFILING
#define PROFILE_RANGE	4*1024*1024

#include "PcMap.h"
#include "Memoizer.h"
#include "NativeMethod.h"
#include "NativeProcess.h"
#include "RelinkCache.h"
#include "Profiler.h"

#include "compile.h"
#include "optimise.h"
#include "hooks.h"
#include "machine.h"
#include "primitive.h"
#include "generate.h"
#include "cachebits.h"
#include "atcache.h"

#include "Frame.h"

#include "xmalloc.h"

#include <setjmp.h>

struct VirtualMachine *vm;

jmp_buf exit_env;

extern int withSpy;
extern void spyInitialise(void);
extern void spyRelease(void);
void spyStatistics(void);
void spyTranslation(Class *cls, oop selector);

static bool inSnapshot= false;

// for stats

#define DECAY	0.25

unsigned
  runOff= 0,		// current run time
  gcOn= 0, gcOff= 0;	// gc start/stop times
unsigned
  gcMajor= 0,		// number of full GCs so far
  gcMinor= 0,		// number of incremental GCs so far
  gcTime= 0,		// total ticks in GC
  genOn= 0,		// code generation start
  genTime= 1,		// code generation time
  genCount= 0,		// number of methods translated
  runTime= 0,		// total ticks in run
  gcLast= 0,		// most recent gc ticks
  runLast= 0;		// most recent gc ticks
unsigned
  siTotal= 0,		// total number of SI traps
  tfTotal= 0;		// total number of TF traps
unsigned
  icHits= 0,		// total number inline cache hits
  icMisses= 0,		// total number inline cache misses
  mcProbes= 0;		// number of times MC was probed
float
  gcNow= 0.0,		// instantaneous GC percentage
  gcDecay= 0.0,		// decaying GC percentage
  gcTotal= 0.0;		// total GC percentage

#ifdef __macintosh__

static inline unsigned gettime(void)
{
  return 0;
}

#else

# include <sys/time.h>
# include <sys/resource.h>
# include <sys/unistd.h>

static inline unsigned gettime(void)
{
  struct rusage usage;
  getrusage(RUSAGE_SELF, &usage);
  return usage.ru_utime.tv_sec * 1000 + usage.ru_utime.tv_usec / 1000;
}

#endif // !__macintosh__

void j_undefinedHook(void)
{
  fatal("undefinedHook\n");
}

#ifdef INTERNAL
int j_setInterpreter(struct VirtualMachine *proxy)
#else
int setInterpreter(struct VirtualMachine *proxy)
#endif
{
  vm= proxy;

  CompilerHook *hooks= vm->compilerHookVector();

  hooks[ 0]= (CompilerHook)j_undefinedHook;
  hooks[ 1]= (CompilerHook)j_translateMethodHook;
  hooks[ 2]= (CompilerHook)j_flushCacheHook;
  hooks[ 3]= (CompilerHook)j_preGCHook;
  hooks[ 4]= (CompilerHook)j_mapHook;
  hooks[ 5]= (CompilerHook)j_postGCHook;
  hooks[ 6]= (CompilerHook)j_processChangeHook;
  hooks[ 7]= (CompilerHook)j_preSnapshotHook;
  hooks[ 8]= (CompilerHook)j_postSnapshotHook;
  hooks[ 9]= (CompilerHook)j_markHook;
  hooks[10]= (CompilerHook)j_preStoreImageSegment;
  hooks[11]= (CompilerHook)j_postStoreImageSegment;
  hooks[12]= (CompilerHook)j_undefinedHook; 
  hooks[13]= (CompilerHook)j_undefinedHook; 
  hooks[14]= (CompilerHook)j_createActualMessageHook;
 
  return 1;
}


static bool initialised= false;
static bool gcLocked= false;

Cache		 *mapCache= 0;
Cache		 *codeCache= 0;
Cache		 *iclCache= 0;
ClassMemoizer    *classMemoizer= 0;
SelectorMemoizer *selectorMemoizer= 0;
MethodMemoizer   *methodMemoizer= 0;
NativeMemoizer   *nativeMemoizer= 0;


void initialise(void)
{
  assert(initialised == false);
  gcLocked= true;

# ifdef DO_PROFILING
  codeCache=	    new Cache(PROFILE_RANGE);
  Profiler::initialise((insn *)codeCache->reserve(1), PROFILE_RANGE);
# else
  codeCache=	    new Cache();
# endif
  mapCache=	    new Cache();
  iclCache=	    new Cache();
  nativeMemoizer=   new NativeMemoizer();
  classMemoizer=    new ClassMemoizer();
  methodMemoizer=   new MethodMemoizer();
  selectorMemoizer= new SelectorMemoizer();

  // clear all receiverMap slots to nil
  int nMx= 0;
  int nBx= 0;
  oop obj= Object::firstObject();
  while (obj != 0)
    {
      if (obj->isMethodContext())
	{
	  //obj->clearRootBit();
	  obj->asMethodContext()->receiverMap= nilObj;
	  ++nMx;
	}
#   ifdef USE_CLOSURE_CACHE
      if (obj->isBlockContext())
	{
	  if (obj->wordLength() < (1+6+ClosureCacheIndex))
	    fatal("BlockContext %p too small for closure cache (word length = %d)\n",
		  obj, obj->wordLength());
	  BlockContext *bx= obj->asBlockContext();
	  if ((bx->stackp->notNil()) && (bx->stackIndex() > ClosureCacheIndex))
	    {
	      printf("BlockContext %p has stackp ", obj);
	      bx->stackp->print();
	      printf("!\n");
	      fatal("cannot use closure cache");
	    }
	  bx->stack[ClosureCacheIndex]= 0;	// closure entry point
	}
#   endif // USE_CLOSURE_CACHE
      obj= obj->nextObject();
    }
  PRINTF(("initialised %d MethodContexts, %d BlockContexts\n", nMx, nBx));

  gcLocked= false;
  initialised= true;

  opt_initialise();
  gen_initialise();	// init code generator
  prim_initialise();	// init primitive table
  tramp_initialise();	// init trampolines
  Object::initialise();
  NativeMethod::initialise();
  NativeProcess::initialise();
  RelinkCache::initialise();

# ifdef DO_PROFILING
  Profiler::methodBase((insn *)codeCache->reserve(0));
# endif

  vm->setCompilerInitialized(true);
}

void release(void)
{
  assert(initialised == true);

  // deletion of the following releases all non-heap resources...
  if (selectorMemoizer != 0) { delete selectorMemoizer;  selectorMemoizer= 0; }
  if (methodMemoizer   != 0) { delete methodMemoizer;    methodMemoizer= 0; }
  if (classMemoizer    != 0) { delete classMemoizer;	 classMemoizer= 0; }
  if (nativeMemoizer   != 0) { delete nativeMemoizer;    nativeMemoizer= 0; }
  if (iclCache         != 0) { delete iclCache;          iclCache= 0; }
  if (mapCache         != 0) { delete mapCache;          mapCache= 0; }
  if (codeCache        != 0) { delete codeCache;	 codeCache= 0; }
  // ...and the next full GC will catch any other remnants

  initialised= false;

  gen_release();	// release code generator
  compile_release();	// release compiler

  NativeProcess::release();

  vm->setCompilerInitialized(false);
}


extern "C" {
  int fullGC(void);
  int incrementalGC(void);
  int loadInitialContext(void);
};

#define barrier() __asm__ __volatile__("": : :"memory")

void j_flushAll(void)
{
  printf("J3 restart\n");  fflush(stdout);
  assert(NativeProcess::activeProcess->process == Processor->activeProcess);
  assert(activeContext->isNil());
  assert(activeFrame != 0);
  assert(activeFrame->stackp == stackPointer);
  Context *cx= NativeProcess::stabiliseAll();
  Processor->activeProcess->suspendedContext= cx;
  release();
  loadInitialContext();
  assert(activeContext->notNil());
  {
    extern int allocationCount, endOfMemory, freeBlock;
    //    if ((endOfMemory - freeBlock) < 100000)
    //      fullGC();
    allocationCount= 0;	// MUST prevent GC during process load!
  }
  initialise();
  activeFrame= 0;
  activeContext= nilObj->asContext();
  NativeProcess::loadAndResumeInitialProcess(Processor->activeProcess);
}


// hooks


void j_translateMethodHook(void)
{
  // called post method cache lookup
  if (withSpy) genOn= gettime();
  gen_compile();
  if (withSpy) genTime+= gettime() - genOn;
  assert(newNativeMethod->compiledMethod() == newMethod);
  assert(newNativeMethod->selector() == messageSelector);
}


void j_flushCacheHook(Object *target)
{
  if (target->isCompiledMethod())
    {
      CompiledMethod *cMeth= target->asCompiledMethod();
      gen_flushMethod(cMeth);
    }
  else // if (target->isNil())
    {
      //if (gen_flushClass(target->asClass()))
	// more than half the methods have been flushed: restart from scratch
	tramp_callOnCStack((void *)j_flushAll);
    }
}


#ifndef NDEBUG
static oop *preGCsp= 0;
#endif


void j_preGCHook(int fullGCFlag)
{
  PRINTF(("%s GC\n", fullGCFlag ? "full" : "incremental"));

  //	static int nGCs= 0;
  //        printf("%4d %s GC\n", ++nGCs, fullGCFlag ? "full" : "incremental");

  assert(classMemoizer->okayFields());
  assert(methodMemoizer->okayFields());
  assert(selectorMemoizer->okayFields());
  //assert(Object::allObjectsOkay());

  if (gcLocked)
    fatal("inopportune GC during image initialisation");

  if (withSpy)
    {
      if (fullGCFlag) ++gcMajor; else ++gcMinor;
      spyStatistics();
      gcOn= gettime();
    }

  if (inSnapshot)
    {
      assert(activeFrame == 0);
      assert(activeContext->notNil());
    }
  else
    {
      assert(activeFrame != 0);
      activeContext= nilObj->asContext(); // prevent remap of IP/SP in mapInterpObjs
      NativeProcess::activeProcess->suspendedFrame= activeFrame;
#     ifndef NDEBUG
      NativeProcess::okayOops();
      preGCsp= stackPointer;
#     endif
    }
}


void j_markHook(void)
{
  PRINTF(("markHook\n"));
# if 0
  if (classMemoizer >= youngStart) classMemoizer->mark();
  if (methodMemoizer >= youngStart) methodMemoizer->mark();
  if (selectorMemoizer >= youngStart) selectorMemoizer->mark();
# else
  classMemoizer->mark();
  methodMemoizer->mark();
  selectorMemoizer->mark();
# endif

  prim_markArgs();

  if (inSnapshot)
    {
      assert(activeContext->notNil());
      activeContext->mark();
      // assert(NativeProcess::activeProcess->process == Processor->activeProcess);
      // NativeProcess::activeProcess->process->mark();		// splObj gets this
    }
  else
    {
      assert(activeContext->isNil());
      assert(NativeProcess::activeProcess->suspendedFrame == activeFrame);
      //NativeProcess::okayOops();
      NativeProcess::markProcesses();
    }
}


void j_mapHook(int memStart, int memEnd)
{
  PRINTF(("mapHook(%x, %x)\n", memStart, memEnd));
  classMemoizer=    (ClassMemoizer    *)classMemoizer->remap();
  methodMemoizer=   (MethodMemoizer   *)methodMemoizer->remap();
  selectorMemoizer= (SelectorMemoizer *)selectorMemoizer->remap();

  prim_mapArgs();

  if (inSnapshot)
    {
      assert(activeContext->notNil());
      stackPointer= (oop *)((int)stackPointer - (int)activeContext);
      activeContext= activeContext->remap()->asContext();
      stackPointer= (oop *)((int)stackPointer + (int)activeContext);
    }
  else
    {
      // the update of activeProc is needed because of #become: 
      NativeProcess::activeProcess->suspendedFrame= activeFrame;
      NativeProcess::remapProcesses();
    }

  // don't bother remapping: just zap the atcache entirely
  AtCacheLine::clearAll();
}


void j_postGCHook(void)
{
  if (withSpy)
    {
      gcOff= gettime();
      unsigned gcDelta= gcOff - gcOn;
      gcLast+= gcDelta;
      gcTime+= gcDelta;
    }

  PRINTF(("postGCHook\n"));

  assert(classMemoizer->okayFields());
  assert(methodMemoizer->okayFields());
  assert(selectorMemoizer->okayFields());
  //assert(Object::allObjectsOkay());

  assert(classMemoizer->okayMemoizer());
  assert(methodMemoizer->okayMemoizer());
  assert(selectorMemoizer->okayMemoizer());

  if (inSnapshot)
    {
      assert(activeContext->notNil());
      assert(activeFrame == 0);
    }
  else
    {
#     ifndef NDEBUG
      NativeProcess::okayOops();
      assert(preGCsp == stackPointer);
#     endif
    }

  // context registers may have changed
  //gen_reload();
}


void j_processChangeHook(Process *oldProc, Process *newProc)
{
  FPRINTF((stderr, "process change: %p -> %p\n", oldProc, newProc));
  assert(activeFrame != 0);
  NativeProcess::transferTo(oldProc, newProc);
  assert(("this cannot happen", false));	// NOT REACHED
}


void j_preSnapshotHook(void)
{
  PRINTF(("preSnapshotHook\n"));

  // About to snapshot: stabilise all Process stacks, saving topmost
  // stable Context of the active Process in activeContext.

  assert(NativeProcess::activeProcess->process == Processor->activeProcess);
  assert(activeContext->isNil());
  assert(activeFrame != 0);

  activeContext= NativeProcess::stabiliseAll();

  // primitiveSnapshot requires stackPointer to be correct w.r.t. activeContext
  stackPointer= activeContext->stackPointer();

  activeFrame= 0;

# ifndef NDEBUG
  // ensure that there are no PseudoContexts left in the image
  {
    oop obj= Object::firstObject();
    while (obj != 0)
      {
	if (obj->isPseudoContext())
	  {
	    printf("rogue PseudoContext found in pre-snapshot: ");
	    obj->print();
	    putchar('\n');
	  }
	obj->okayFields();
	obj= obj->nextObject();
      }
  }
# endif

  inSnapshot= true;
}


void j_postSnapshotHook(void)
{
  PRINTF(("postSnapshotHook\n"));

  // Returning from snapshot: reload the active Process stack from
  // activeContext and resume execution in it.

  assert(activeFrame == 0);
  assert(inSnapshot);
  assert(Processor->activeProcess->suspendedContext == activeContext);

  // primitiveSnapshot has modified stackPointer w.r.t. activeContext
  activeContext->stackPointer(stackPointer);
  activeContext= nilObj->asContext();
  inSnapshot= false;

  assert(Object::allObjectsOkay());

  tramp_callOnCStack((void *)NativeProcess::reloadAndResumeProcess,
		   	     (int)Processor->activeProcess);
}

void j_preStoreImageSegment(void)
{
  activeContext = NativeProcess::stabiliseAll();
  Processor->activeProcess->suspendedContext= activeContext;
  inSnapshot= true;
}

void j_postStoreImageSegment(void)
{
  inSnapshot= false;
  tramp_callOnCStack((void *)NativeProcess::reloadAndResumeProcess,
                     (int)Processor->activeProcess); 
}


void j_createActualMessageHook(Message *aMessage, Array *argArray)
{
  PRINTF(("create actual message: "));
  PRINT(activeFrame->stackValue(argumentCount));
  PRINTF((">>"));
  PRINTLN(messageSelector);

  for (int i= argumentCount - 1; i >= 0; --i)
    {
      argArray->atPut(i, activeFrame->pop());
    }
  activeFrame->push(aMessage);
}

unsigned icLast= 0;
unsigned icNow= 0;

void printStatsOn(char buf[][100])
{
  runOff= gettime();
  runLast+= runOff - runTime;
  runTime= runOff;

  if (runLast == 0) runLast = 1;
  gcNow= percent(gcLast, gcLast + runLast);
  gcDecay= (gcDecay * (1.0 - DECAY)) + (gcNow * DECAY);
  gcTotal= percent(gcTime, gcTime + runTime);

  size_t hBytes= 0, mBytes= 0;
  hBytes+= classMemoizer->printStatsOn(   buf[0] + sprintf(buf[0], "classes:   "));
  hBytes+= selectorMemoizer->printStatsOn(buf[1] + sprintf(buf[1], "selectors: "));
  hBytes+= methodMemoizer->printStatsOn(  buf[2] + sprintf(buf[2], "methods:   "));
  mBytes+= mapCache->printStatsOn(        buf[3] + sprintf(buf[3], "mapCache:  "));
  mBytes+= codeCache->printStatsOn(       buf[4] + sprintf(buf[4], "codeCache: "));
  mBytes+= iclCache->printStatsOn(        buf[5] + sprintf(buf[5], "iclCache:  "));
  sprintf(buf[6], "memory: %d heap, %d cached (%d+%d = %.1f bytes/bytecode)",
	  hBytes, mBytes, gen_hTotal, gen_nTotal, (float)gen_nTotal/(float)gen_vTotal);
  icNow= gettime();
  unsigned icElapsed= icNow - icLast;
  icLast= icNow;
  sprintf(buf[7], "GC: %d major, %d minor, %6.2f%% %6.2f%% %6.2f%% CPU;  i-cache: %6.2f%% %6.2f %6.2f",
	  gcMajor, gcMinor, gcNow, gcDecay, percent(gcTime, runTime),
	  percent(icHits, icHits + icMisses),
	  0.0/*flushTime / (float)CLOCKS_PER_SEC*/,
	  (float)icMisses / ((float)icElapsed / 1000.0));

# if 0
  printf("%8d %8d %6.2f\n",
	 (int)icMisses,
	 (int)icElapsed,
	 (float)icMisses / ((float)icElapsed / 1000.0));
# endif

  siTotal+= siTraps;
  tfTotal+= tfTraps;
  sprintf(buf[8], "traps: %d SI/sec (avg %d), %d TF (avg %d); trans: %d (%d/sec)",
	  (runLast == 0) ? 0 : (int)(((float)siTraps * 1000) / runLast),
	  (runTime == 0) ? 0 : (int)(((float)siTotal * 1000) / runTime),
	  (runLast == 0) ? 0 : (int)(((float)tfTraps * 1000) / runLast),
	  (runTime == 0) ? 0 : (int)(((float)tfTotal * 1000) / runTime),
	  genCount, (genCount * 1000) / genTime);

  siTraps= tfTraps= gcLast= runLast= 0;
  icMisses= mcProbes= 0;
  icHits= 1;
}


void j_exit(void)
{
  static bool exited= false;	// avoid recursive atexit processing
  if (!exited)
    {
      exited= true;
      longjmp(exit_env, 1);
    }
}


// this is called instead of the VM's bytecode interpret()er.

extern "C" {
  int j_interpret(void);
  void SqueakTerminate(void);
}

int j_interpret(void)
{
  if (PseudoContextCCI != (ClassPseudoContext->ccBits() >> 12))
    fatal("PseudoContext does not have expected compact class index");
  if (BlockContextCCI != (ClassBlockContext->ccBits() >> 12))
    fatal("BlockContext does not have expected compact class index");
  if (MethodContextCCI != (ClassMethodContext->ccBits() >> 12))
    fatal("MethodContext does not have expected compact class index");

# if 0
  {
    extern int allocationCount;
#   define report(name) \
    printf("%20s %p index %5d\n", #name, &name, (char *)&name - (char *)&allocationCount)
    report(allocationCount);
    report(falseObj);
    report(specialObjectsOop);
    report(stackPointer);
    report(successFlag);
    report(trueObj);
    report(youngStart);
#   undef report
  }
#endif

  extern int memory, endOfMemory;

  if (((memory | endOfMemory) & SuperedBit) != 0)
    fatal("memory too high to allow oops in inline caches");

  assert(Processor->activeProcess->suspendedContext == activeContext);

# ifdef INTERNAL
  j_setInterpreter(sqGetInterpreterProxy());
# endif

  assert(vm != 0);

  if (setjmp(exit_env))
    {
      // NOTE: this code is executed on the INITIAL C stack!
      // if (withSpy) spyRelease();
#     ifdef PRTINT_STATS
      char buf[9][100];
      printStatsOn(buf);
      printf("\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n",
	     buf[0], buf[1], buf[2], buf[3], buf[4], buf[5], buf[6], buf[7], buf[8]);
      printf("\nmorituri te salutant\n");
#     endif
      release();
#     ifdef __macintosh__
	  SqueakTerminate();
      fflush(stdout);
#     endif // __macintosh__
      exit(0);
    }

  initialise();
  if (withSpy) spyInitialise();
  atexit(j_exit);

  assert(activeFrame == 0);
  NativeProcess::loadAndResumeInitialProcess(Processor->activeProcess);

  return 0;
}










