//===--------- VirtualMachine.h - Registering a VM ------------------------===//
//
//                            The VMKit project
//
// This file is distributed under the University of Pierre et Marie Curie 
// License. See LICENSE.TXT for details.
//
//===----------------------------------------------------------------------===//
//
// Ultimately, this would be like a generic way of defining a VM. But we're not
// quite there yet.
//
//===----------------------------------------------------------------------===//

#ifndef MVM_VIRTUALMACHINE_H
#define MVM_VIRTUALMACHINE_H

#include "mvm/Allocator.h"
#include "mvm/MethodInfo.h"
#include "mvm/Threads/CollectionRV.h"
#include "mvm/Threads/Cond.h"
#include "mvm/Threads/Locks.h"
#include "mvm/GC/GC.h"

#include <cassert>
#include <map>

namespace j3 {
  class JavaCompiler;
  class JnjvmClassLoader;
}

class gc;

namespace mvm {

class FunctionMap {
public:
  /// Functions - Map of applicative methods to function pointers. This map is
  /// used when walking the stack so that VMKit knows which applicative method
  /// is executing on the stack.
  ///
  std::map<void*, MethodInfo*> Functions;

  /// FunctionMapLock - Spin lock to protect the Functions map.
  ///
  mvm::SpinLock FunctionMapLock;
};

/// StartEndFunctionMap - This map is for functions for which we have
/// a start and end address.
///
class StartEndFunctionMap : public FunctionMap {
public:
  /// addMethodInFunctionMap - A new method pointer in the function map.
  ///
  void addMethodInfo(MethodInfo* meth, void* start, void* end) {
    FunctionMapLock.acquire();
    Functions.insert(std::make_pair(start, meth));
    Functions.insert(std::make_pair(end, meth));
    FunctionMapLock.release();
  }
  
  /// IPToMethodInfo - Map an instruction pointer to the MethodInfo.
  ///
  MethodInfo* IPToMethodInfo(void* ip) {
    FunctionMapLock.acquire();
    std::map<void*, MethodInfo*>::iterator I = Functions.upper_bound(ip);
    MethodInfo* res = 0;
    if (I != Functions.end() && I != Functions.begin()) {
      res = I->second;
      if ((--I)->second != res) res = 0;
    }
    FunctionMapLock.release();
    return res;
  }
};

/// StartFunctionMap - This map is for static functions where getting an end
/// address is cumbersome.
///
class StartFunctionMap : public FunctionMap {
public: 
  /// addMethodInFunctionMap - A new method pointer in the function map.
  ///
  void addMethodInfo(MethodInfo* meth, void* addr) {
    FunctionMapLock.acquire();
    Functions.insert(std::make_pair((void*)addr, meth));
    FunctionMapLock.release();
  }
  
  /// IPToMethodInfo - Map an instruction pointer to the MethodInfo.
  ///
  MethodInfo* IPToMethodInfo(void* ip);
 
};

class SharedStartFunctionMap : public StartFunctionMap {
public: 
  BumpPtrAllocator* StaticAllocator;
  bool initialized;
  SharedStartFunctionMap() {
    initialized = false;
  }

  void initialize();
};



// Same values than JikesRVM
#define INITIAL_QUEUE_SIZE 256
#define GROW_FACTOR 2

class CompilationUnit;
class VirtualMachine;

class ReferenceQueue {
private:
  gc** References;
  uint32 QueueLength;
  uint32 CurrentIndex;
  mvm::SpinLock QueueLock;
  uint8_t semantics;

  gc* processReference(gc*, VirtualMachine*);
public:

  static const uint8_t WEAK = 1;
  static const uint8_t SOFT = 2;
  static const uint8_t PHANTOM = 3;



  ReferenceQueue(uint8_t s) {
    References = new gc*[INITIAL_QUEUE_SIZE];
    QueueLength = INITIAL_QUEUE_SIZE;
    CurrentIndex = 0;
    semantics = s;
  }

  ~ReferenceQueue() {
    delete[] References;
  }
 
  void addReference(gc* ref) {
    QueueLock.acquire();
    if (CurrentIndex >= QueueLength) {
      uint32 newLength = QueueLength * GROW_FACTOR;
      gc** newQueue = new gc*[newLength];
      if (!newQueue) {
        fprintf(stderr, "I don't know how to handle reference overflow yet!\n");
        abort();
      }
      for (uint32 i = 0; i < QueueLength; ++i) newQueue[i] = References[i];
      delete[] References;
      References = newQueue;
      QueueLength = newLength;
    }
    References[CurrentIndex++] = ref;
    QueueLock.release();
  }
  
  void acquire() {
    QueueLock.acquire();
  }

  void release() {
    QueueLock.release();
  }

  void scan(VirtualMachine* vm);
};


/// VirtualMachine - This class is the root of virtual machine classes. It
/// defines what a VM should be.
///
class VirtualMachine : public mvm::PermanentObject {
  friend class ReferenceQueue;

protected:

  VirtualMachine(mvm::BumpPtrAllocator &Alloc) :
		allocator(Alloc),
    WeakReferencesQueue(ReferenceQueue::WEAK),
    SoftReferencesQueue(ReferenceQueue::SOFT), 
    PhantomReferencesQueue(ReferenceQueue::PHANTOM) {
#ifdef SERVICE
    memoryLimit = ~0;
    executionLimit = ~0;
    GCLimit = ~0;
    threadLimit = ~0;
    parent = this;
    status = 1;
    _since_last_collection = 4*1024*1024;
#endif
    FinalizationQueue = new gc*[INITIAL_QUEUE_SIZE];
    QueueLength = INITIAL_QUEUE_SIZE;
    CurrentIndex = 0;

    ToBeFinalized = new gc*[INITIAL_QUEUE_SIZE];
    ToBeFinalizedLength = INITIAL_QUEUE_SIZE;
    CurrentFinalizedIndex = 0;
    
    ToEnqueue = new gc*[INITIAL_QUEUE_SIZE];
    ToEnqueueLength = INITIAL_QUEUE_SIZE;
    ToEnqueueIndex = 0;
    
    mainThread = 0;
    NumberOfThreads = 0;
    if (!SharedStaticFunctions.initialized) SharedStaticFunctions.initialize();
  }
public:

  /// allocator - Bump pointer allocator to allocate permanent memory
  /// related to this VM.
  ///
  mvm::BumpPtrAllocator& allocator;

  /// mainThread - The main thread of this VM.
  ///
  mvm::Thread* mainThread;

  /// NumberOfThreads - The number of threads that currently run under this VM.
  ///
  uint32_t NumberOfThreads;

  /// ThreadLock - Lock to create or destroy a new thread.
  ///
  mvm::SpinLock ThreadLock;
  
  /// setMainThread - Set the main thread of this VM.
  ///
  void setMainThread(mvm::Thread* th) { mainThread = th; }
  
  /// getMainThread - Get the main thread of this VM.
  ///
  mvm::Thread* getMainThread() const { return mainThread; }

  /// addThread - Add a new thread to the list of threads.
  ///
  void addThread(mvm::Thread* th) {
    ThreadLock.lock();
    NumberOfThreads++;
    if (th != mainThread) {
      if (mainThread) th->append(mainThread);
      else mainThread = th;
    }
    ThreadLock.unlock();
  }
  
  /// removeThread - Remove the thread from the list of threads.
  ///
  void removeThread(mvm::Thread* th) {
    ThreadLock.lock();
    NumberOfThreads--;
    if (mainThread == th) mainThread = (Thread*)th->next();
    th->remove();
    if (!NumberOfThreads) mainThread = 0;
    ThreadLock.unlock();
  }


  virtual void tracer();

  virtual ~VirtualMachine() {
    if (scanner) delete scanner;
    delete[] FinalizationQueue;
    delete[] ToBeFinalized;
    delete[] ToEnqueue;
  }

  /// runApplication - Run an application. The application name is in
  /// the arguments, hence it is the virtual machine's job to parse them.
  virtual void runApplication(int argc, char** argv) = 0;
  
  /// waitForExit - Wait until the virtual machine stops its execution.
  virtual void waitForExit() = 0;

  static j3::JnjvmClassLoader* initialiseJVM(j3::JavaCompiler* C,
                                                bool dlLoad = true);
  static VirtualMachine* createJVM(j3::JnjvmClassLoader* C = 0);
  
  static CompilationUnit* initialiseCLIVM();
  static VirtualMachine* createCLIVM(CompilationUnit* C = 0);

private:
  /// WeakReferencesQueue - The queue of weak references.
  ///
  ReferenceQueue WeakReferencesQueue;

  /// SoftReferencesQueue - The queue of soft references.
  ///
  ReferenceQueue SoftReferencesQueue;

  /// PhantomReferencesQueue - The queue of phantom references.
  ///
  ReferenceQueue PhantomReferencesQueue;

  
  /// FinalizationQueueLock - A lock to protect access to the queue.
  ///
  mvm::SpinLock FinalizationQueueLock;

  /// finalizationQueue - A list of allocated objets that contain a finalize
  /// method.
  ///
  gc** FinalizationQueue;

  /// CurrentIndex - Current index in the queue of finalizable objects.
  ///
  uint32 CurrentIndex;

  /// QueueLength - Current length of the queue of finalizable objects.
  ///
  uint32 QueueLength;

  /// growFinalizationQueue - Grow the queue of finalizable objects.
  ///
  void growFinalizationQueue();
  
  /// ToBeFinalized - List of objects that are scheduled to be finalized.
  ///
  gc** ToBeFinalized;
  
  /// ToBeFinalizedLength - Current length of the queue of objects scheduled
  /// for finalization.
  ///
  uint32 ToBeFinalizedLength;

  /// CurrentFinalizedIndex - The current index in the ToBeFinalized queue
  /// that will be sceduled for finalization.
  ///
  uint32 CurrentFinalizedIndex;
  
  /// growToBeFinalizedQueue - Grow the queue of the to-be finalized objects.
  ///
  void growToBeFinalizedQueue();
  
  /// finalizationCond - Condition variable to wake up finalization threads.
  ///
  mvm::Cond FinalizationCond;

  /// finalizationLock - Lock for the condition variable.
  ///
  mvm::LockNormal FinalizationLock;
  
  gc** ToEnqueue;
  uint32 ToEnqueueLength;
  uint32 ToEnqueueIndex;
  
  /// ToEnqueueLock - A lock to protect access to the queue.
  ///
  mvm::LockNormal EnqueueLock;
  mvm::Cond EnqueueCond;
  mvm::SpinLock ToEnqueueLock;
  
  void addToEnqueue(gc* obj) {
    if (ToEnqueueIndex >= ToEnqueueLength) {
      uint32 newLength = ToEnqueueLength * GROW_FACTOR;
      gc** newQueue = new gc*[newLength];
      if (!newQueue) {
        fprintf(stderr, "I don't know how to handle reference overflow yet!\n");
        abort();
      }
      for (uint32 i = 0; i < QueueLength; ++i) newQueue[i] = ToEnqueue[i];
      delete[] ToEnqueue;
      ToEnqueue = newQueue;
      ToEnqueueLength = newLength;
    }
    ToEnqueue[ToEnqueueIndex++] = obj;
  }

protected:
  /// invokeFinalizer - Invoke the finalizer of the object. This may involve
  /// changing the environment, e.g. going to native to Java.
  ///
  virtual void invokeFinalizer(gc*) {}


public:
  /// finalizerStart - The start function of a finalizer. Will poll the
  /// finalizationQueue.
  ///
  static void finalizerStart(mvm::Thread*);
  
  /// enqueueStart - The start function of a thread for references. Will poll
  /// ToEnqueue.
  ///
  static void enqueueStart(mvm::Thread*);

  /// addFinalizationCandidate - Add an object to the queue of objects with
  /// a finalization method.
  ///
  void addFinalizationCandidate(gc*);
  
  /// scanFinalizationQueue - Scan objets with a finalized method and schedule
  /// them for finalization if they are not live.
  ///
  void scanFinalizationQueue();

  /// wakeUpFinalizers - Wake the finalizers.
  ///
  void wakeUpFinalizers() { FinalizationCond.broadcast(); }
  
  /// wakeUpEnqueue - Wake the threads for enqueueing.
  ///
  void wakeUpEnqueue() { EnqueueCond.broadcast(); }

  virtual void startCollection() {
    FinalizationQueueLock.acquire();
    ToEnqueueLock.acquire();
    SoftReferencesQueue.acquire();
    WeakReferencesQueue.acquire();
    PhantomReferencesQueue.acquire();
  }
  
  virtual void endCollection() {
    FinalizationQueueLock.release();
    ToEnqueueLock.release();
    SoftReferencesQueue.release();
    WeakReferencesQueue.release();
    PhantomReferencesQueue.release();
  }
  
  /// scanWeakReferencesQueue - Scan all weak references. Called by the GC
  /// before scanning the finalization queue.
  /// 
  void scanWeakReferencesQueue() {
    WeakReferencesQueue.scan(this);
  }
  
  /// scanSoftReferencesQueue - Scan all soft references. Called by the GC
  /// before scanning the finalization queue.
  ///
  void scanSoftReferencesQueue() {
    SoftReferencesQueue.scan(this);
  }
  
  /// scanPhantomReferencesQueue - Scan all phantom references. Called by the GC
  /// after the finalization queue.
  ///
  void scanPhantomReferencesQueue() {
    PhantomReferencesQueue.scan(this);
  }
  
  /// addWeakReference - Add a weak reference to the queue.
  ///
  void addWeakReference(gc* ref) {
    WeakReferencesQueue.addReference(ref);
  }
  
  /// addSoftReference - Add a weak reference to the queue.
  ///
  void addSoftReference(gc* ref) {
    SoftReferencesQueue.addReference(ref);
  }
  
  /// addPhantomReference - Add a weak reference to the queue.
  ///
  void addPhantomReference(gc* ref) {
    PhantomReferencesQueue.addReference(ref);
  }

  /// clearReferent - Clear the referent in a reference. Should be overriden
  /// by the VM.
  ///
  virtual void clearReferent(gc*) {}

  /// getReferent - Get the referent of the reference. Should be overriden
  /// by the VM.
  //
  virtual gc** getReferentPtr(gc*) { return 0; }
  
  /// setReferent - Set the referent of the reference. Should be overriden
  /// by the VM.
  virtual void setReferent(gc* reference, gc* referent) { }

  /// enqueueReference - Calls the enqueue method. Should be overriden
  /// by the VM.
  ///
  virtual bool enqueueReference(gc*) { return false; }


public:

  /// scanner - Scanner of threads' stacks.
  ///
  mvm::StackScanner* scanner;

  mvm::StackScanner* getScanner() {
    return scanner;
  }

  /// rendezvous - The rendezvous implementation for garbage collection.
  ///
  CollectionRV rendezvous;


  StartEndFunctionMap RuntimeFunctions;
  static StartEndFunctionMap SharedRuntimeFunctions;
  StartFunctionMap StaticFunctions;
  static SharedStartFunctionMap SharedStaticFunctions;

  MethodInfo* IPToMethodInfo(void* ip);

#ifdef ISOLATE
  size_t IsolateID;
#endif

#ifdef SERVICE
  uint64_t memoryUsed;
  uint64_t gcTriggered;
  uint64_t executionTime;
  uint64_t numThreads;
  CompilationUnit* CU;
  virtual void stopService() {}

  uint64_t memoryLimit;
  uint64_t executionLimit;
  uint64_t threadLimit;
  uint64_t GCLimit;

  int _since_last_collection;
  VirtualMachine* parent;
  uint32 status; 
#endif

};


} // end namespace mvm
#endif // MVM_VIRTUALMACHINE_H
