/*
 * Created on Apr 9, 2003
 *
 */
package ovm.core.services.memory;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Domain;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.execution.Context;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.execution.NativeInterface;
import ovm.core.services.io.BasicIO;
import ovm.core.services.threads.OVMThread;
import ovm.core.stitcher.InvisibleStitcher;
import ovm.services.memory.fifo.VM_FIFOArea;
import ovm.services.bytecode.JVMConstants.Throwables;
import ovm.util.Iterator;
import ovm.util.OVMError;
import s3.util.PragmaTransformCallsiteIR.BCdead;
import s3.util.PragmaNoInline;
import ovm.core.execution.RuntimeExports;

/**
 * The abstract memory management interface. 
 * This class provides a broad API that 
 * supports all the different kinds of memory regions we may want: heap,
 * immortal, explicit, scope, and all features like destructors and GC. It is
 * up to each specific memory manager implementation to either support or
 * not support a given feature.
 * <p>All allocation via <tt>new</tt> is forwarded to the memory manager's
 * allocation methods ({@link #allocate allocate} and 
 * {@link #allocateArray allocateArray}) by the
 * CSA.
 * The default implementation for the allocation routines (and clone) are based
 * on the abstract {@link #getMem} method. If these methods are not
 * overridden then it is presumed that <tt>getMem</tt> takes responsibility for
 * protecting the memory manager against unsafe concurrent access. Otherwise
 * the overridden allocation methods take on this responsibility themselves.
 * The CSA does not provide any exclusion/atomicity guarantees.
 *
 * <p><b>Note:</b> Much of the documentation of this class is lacking.
 *
 * @author jv, cf, jb
 *
 */
public abstract class MemoryManager {
    public static final class GCNative implements NativeInterface {
	/**
	 * An engine-specific primitive operation.  Force the calling
	 * method to save all call-preserved registers.  This method
	 * may have an empty implementation in engines that do not
	 * perform register allocation.
	 */
	public static native void SAVE_REGISTERS();
    }

    public MemoryManager() {
	RuntimeExports.defineVMProperty("org.ovmj.supportsGC",
					supportsGC());
	RuntimeExports.defineVMProperty("org.ovmj.supportsFinalizers",
					supportsDestructors());
    }

    /**
     * Return the base address for the Ovm bootimage
     * (<code>img</code>).  MemoryManager implementations may override
     * this method to get finer control over global memory layout and
     * the layout of the boot image.
     * <p>
     * FIXME: This should return VM_Address rather than int.
     */
    public int getImageBaseAddress() {
	// historically, this value has been 0x60000000, but on ppc
	// linux, that value is a bit large for MemoryManagers that
	// wish to map the heap immediately after the bootimage.
	return 0x50000000;
    }

    /**
     * Return the virtual address at which the heap MUST be mapped, or
     * 0 if no particular address is required.  This value is passed
     * to the makefiles, which attempt to reserve the -heap-size chunk
     * of space starting at this address.  Reserving this space is
     * non-portable, and is currently done only under linux/elf
     * systems.  The memory manager should manually allocate space at
     * this address with mmap(MAP_FIXED).
     */
    public int getFixedHeapAddress() {
	return 0;
    }
    /**
     * Allocate space for an object within the bootimage.
     * 
     * @param end       the first address after the current bootimage contents
     * @param size      the object's size
     * @param bp        the object's type
     *
     * @return the object's address
     *
     * If this function returns a value &gt;= end, Ovm's notion of the
     * bootimage end is adjusted appropriately.
     * <p>
     * The default implementation of this method lays out the
     * bootimage as a contiguous sequence of objects without any
     * special alignment constraints and without segragating objects
     * by type.
     */
    protected int allocateInImage(int end, int size, Blueprint bp)
	throws BCdead
    {
	return end;
    }
    // public static final Object gcCriticalRegionLock = new Object();

    /**
     * Return true if pinning this object has no unpleasent side
     * effects, or false if pinning this object amounts to pinning
     * the entire heap (ie, blocking GC). 
     * @see #pin
     * @see #unpin
     **/
    public abstract boolean canPinEfficiently(Oop oop);
    /**
     * Return true if pinning this object has no unpleasent side
     * effects, or false if pinning this object amounts to pinning
     * the entire heap (ie, blocking GC). 
     * @see #pin
     * @see #unpin
     **/
    public final boolean canPinEfficiently(Object o) {
	return canPinEfficiently(VM_Address.fromObject(o).asOop());
    }

    /**
     * Prevent the garbage collector from moving this object, because
     * non-moveable references exist (for example in variables of
     * type VM_Address or in in-progress system calls such as
     * aio_read).
     * <p>
     * <b>NOTE:</b> GC may or may not be possible while this object
     * an pinned.  To determine whether pinning a particular object
     * locks out the GC, call 
     * @see #canPinEfficiently
     **/
    public abstract void pin(Oop oop);
    /**
     * Prevent the garbage collector from moving this object, because
     * non-moveable references exist (for example in variables of
     * type VM_Address or in in-progress system calls such as
     * aio_read).
     * <p>
     * <b>NOTE:</b> GC may or may not be possible while this object
     * an pinned.  To determine whether pinning a particular object
     * locks out the GC, call 
     * @see #canPinEfficiently
     **/
    public final void pin(Object o) {
	pin(VM_Address.fromObject(o).asOop());
    }

    /**
     * Allow the garbage collector to move an object that has been
     * previously pinned.
     **/
    public abstract void unpin(Oop oop);
    /**
     * Allow the garbage collector to move an object that has been
     * previously pinned.
     **/
    public final void unpin(Object o) {
	unpin(VM_Address.fromObject(o).asOop());
    }

    /**
     * Return or throw an OutOfMemoryError without performing any
     * allocation.
     **/
    protected Error outOfMemory() {
	CoreServicesAccess csa 
	    = DomainDirectory.getExecutiveDomain().getCoreServicesAccess();
	csa.generateThrowable(Throwables.OUT_OF_MEMORY_ERROR, 0);
	return null;
    }

    /**
     * Free block if supported by the configuartion.
     * FIXME perhaps we should have a size parameter, unless we want to force
     * any MM that supports freeing to do its own block-size bookkeeping, which
     * seems like it would duplicate information in the {@link ovm.core.domain.ObjectModel}
     * (which we are striving to keep the allocator a layer below).
     **/
    public void free(VM_Address adr) {
        throw new OVMError.Unimplemented();
    }

    /**
     * Runtime initialization of the memory manager. Must be called (once)
     * in advance of any code that might do allocation.
     * <p>This is essentially the first thing executed at OVM startup and so
     * it can't rely on any other executive domain services.
     */
    public abstract void boot();

    /**
     * Informs the memory manager that the OVM has completed booting. 
     **/
    public void fullyBootedVM() {
    }

    /**
     * @return true if this MemoryManager walks stacks precisely
     * <p>
     * The prefered way to walk the stack is through the
     * invisibly-stitched LocalReferenceIterator, and all execution
     * engines that currently support precise stack walking provide a
     * concrete implementation of this class.
     * <b>FIXME</b> Should
     * {@link s3.core.services.memory.LocalReferenceIterator}
     * be pulled into ovm-land, and if so, where should
     * {@link s3.core.services.interpreter.InterpreterLocalReferenceIterator}
     * live?  Maybe s3.core.execution?
     * <p>
     * <b>FIXME #2</b> Conservative collectors also need support from
     * the execution engine.  In particular, the engine must define the
     * C functions given in
     * {@link s3.services.memory.mostlyCopying.ConservativeUpdater.NativeHelpers}.
     * What is to become of
     * {@link s3.services.memory.mostlyCopying.ExtentUpdater} and
     * {@link s3.services.memory.mostlyCopying.ConservativeUpdater}?  I
     * think they are of general use, and maybe should be pulled up
     * into ovm.core.services.memory as well, but Phil isn't using
     * them (probably because they are a rewrite of Phil's
     * SimpleSemiSpace-specific, or S4, code).
     * <p>
     * All three of these classes are coded against ovm.core.** and
     * ovm.util, which suggests to me that they belong here.
     */
    public boolean isPrecise() { return false; }

    /**
     * Return the configured MemoryManager. Callsites of this method are
     * rewritten to load-constant of the (singleton) memory manager, courtesy
     * of the {@link ovm.core.stitcher.InvisibleStitcher}.
     * @return Singleton instance of the configured MemoryManager implementation
     * @throws PragmaStitchSingleton <em>this is a pragma</em>
     **/
    public static MemoryManager the()
        throws InvisibleStitcher.PragmaStitchSingleton {

        return (MemoryManager) InvisibleStitcher.singletonFor(
            MemoryManager.class.getName());
    }

    /**
     * Allocate a scalar.
     * Convenience version implemented in terms of
     * {@link #getMem getMem}, for memory managers that provide such
     * a raw interface and do not distinguish between allocating scalars and
     * arrays. 
     * If that is not possible, the memory manager should override this
     * method and supply a throws-unsupported version of getMem.
     * @param bp To stamp the allocation; override to do type-specific 
     * allocation
     * @return stamped, zeroed, allocated memory
     */
    public Oop allocate( Blueprint.Scalar bp) {
        return bp.stamp(getMem(bp.getFixedSize()));
    }

    /**
     * Allocate an array.
     * Convenience version implemented in terms of
     * {@link #getMem getMem}, for memory managers that provide such
     * a raw interface and do not distinguish between allocating scalars and
     * arrays.
     * If that is not possible, the memory manager should override this
     * method and supply a throws-unsupported version of getMem.
     * @param bp To stamp the allocation; override to do type-specific 
     * allocation
     * @param len number of elements 
     * @return stamped, zeroed, allocated memory
     */
    public Oop allocateArray(Blueprint.Array bp, int len) {
	long size = bp.computeSizeFor(len);
	if (size < Integer.MAX_VALUE)
	    return bp.stamp(getMem((int) size), len);
	else
	    throw outOfMemory();
    }

    public Oop clone(Oop oop) {
        Blueprint bp = oop.getBlueprint();
        return bp.clone(oop, getMem(bp.getVariableSize(oop)));
    }

    public boolean needsWriteBarrier() {
 	return false;
    }

    public boolean needsReadBarrier() {
 	return false;
    }

    public void putFieldBarrier(CoreServicesAccess csa,
      Oop src, int offset, Oop tgt) throws s3.util.PragmaNoPollcheck {
     throw new OVMError.Unimplemented("write barrier enabled but not defined!");
    }
    
    public void aastoreBarrier(CoreServicesAccess csa,
      Oop src, int offset, Oop tgt) throws s3.util.PragmaNoPollcheck {
     throw new OVMError.Unimplemented("write barrier enabled but not defined!");
    }
    
    public void readBarrier(CoreServicesAccess csa,
      Oop src) throws s3.util.PragmaNoPollcheck {
     throw new OVMError.Unimplemented("read barrier enabled but not defined!");
    }

    /*
     * Some region support.  The default implemenation of these hooks
     * ignores regions.
     */
    
    /**
     * This hook is called immediately after a context switch occurs.
     * When switching to a new-born thread, it is called before any
     * memory is allocated in that thread
     *
     * @param context an object representing the new execution context.
     * This will either be the actual 
     * {@link ovm.core.execution.Context Context} object, or the
     * {@link ovm.core.services.threads.OVMThread} object, depending on the caller.
     */
    public void observeContextSwitch(Object context) {
    }

    /**
     * Create the memory context assoicated with a newly created
     * ovm.core.execution.Context object, it can be retrieved with
     * ScopedMemoryContext.getMemoryContext(), and may be updated by
     * Context.setMemoryContext()
     */
    public Object makeMemoryContext() {
	return null;
    }

    /**
     * Make the given area current for this thread, and return
     * the area that once was current.  This function does not
     * enforce the RTSJ single parent rule.
     */
    public VM_Area setCurrentArea(VM_Area a) {
	return null;
    }

    public abstract VM_Area getHeapArea();

    public VM_Area getImmortalArea() {
	return null;
    }

    public VM_Area getCurrentArea() {
	return getHeapArea();
    }

    public VM_Area areaOf(Oop oop) {
	return getHeapArea();
    }

    /**
     * Create a memory area that is not subject to scope checks.
     *
     * <p>If the memory area can't be allocated this implementation returns
     * null, but throwing <tt>OutOfMemoryError</tt> is also permitted.
     * 
     * @param size the size of the area. The actual size of the area may be
     * larger due to rounding/alignment or other imposed allocator constraints
     *
     * @return a new memory area
     *
     **/
    public VM_Area makeExplicitArea(int size) {
	return null;
    }
    
    /**
     * Create an area.  The type of this object is completely
     * opaque so that both JMTk and native region implementations can
     * use it.
     *
     * <p>If the memory area can't be allocated an implementation may return
     * null, or throw <tt>OutOfMemoryError</tt>.
     *
     * <p>This implementation always throws <tt>OVMError.Unimplemented</tt>
     * as scoped memory is not supported by default.
     *
     * @param mirror an arbitrary reference associated with this area 
     * @param size the size of the area. The actual size of the area may be
     * larger due to rounding/alignment or other imposed allocator constraints
     *
     */
    public VM_Area makeScopedArea(Oop mirror, int size) {
	throw new OVMError.Unimplemented
	    ("this allocator does not support scoped memory");
    }
    
    /**
     * Create a FIFO area.
     */
    // this is Filip's experimental stuff
    public VM_FIFOArea makeFIFOArea(Oop mirror,
                                    Oop pastMirror,
                                    Oop futureMirror,
                                    int size) {
	throw new OVMError.Unimplemented
	    ("this allocator does not support FIFO memory");
    }

    /**
     * Allocate exactly size-many bytes of memory from the current
     * allocation context.  This is primary interface between Ovm and
     * a MemoryManager implementation, and not quite perfect.  size
     * should really be a VM_Word rather than an int, and we should
     * pass in the Blueprint being allocated, so that the memory
     * manager can respond more cleverly (for instance by giving
     * arrays or arrays of primitives special handling).
     **/
    protected abstract VM_Address getMem(int size);

    // A reasonable default, since we have 3 Managers that support GC,
    // but only one that doesn't
    public boolean supportsGC() { return true; }

    // FIXME: more managers need to support destructors, and the ones
    // that don't need to be rewritten to ignore addDestructor calls.
    public boolean supportsDestructors() { return false; }

    // If you override garbageCollect with a no-op, be sure to
    // override supportsGC as well.
    public abstract void garbageCollect() throws PragmaNoInline;

    public class ObjectCounter extends ExtentWalker {
	int ntc = DomainDirectory.maxContextID() + 1;
	int[][] counts = new int[ntc][];
	int[][] bytes =  new int[ntc][];
	Blueprint[][] rev = new Blueprint[ntc][];

	public ObjectCounter() {
	    for (int i = 0; i < ntc; i++) {
		Type.Context ctx = DomainDirectory.getContext(i);
		if (ctx == null)
		    continue;
		int nbp = ctx.getBlueprintCount();
		counts[i] = new int[nbp + 1];
		bytes[i]  = new int[nbp + 1];
		rev[i] = new Blueprint[nbp];
	    }
	

	    for (Iterator dit = DomainDirectory.domains(); dit.hasNext(); ) {
		for (Iterator bit = ((Domain) dit.next()).getBlueprintIterator();
		     bit.hasNext();
		     ) {
		    Blueprint bp = (Blueprint) bit.next();
		    rev[bp.getCID()][bp.getUID()] = bp;
		}
	    }
	}

	public void walk(VM_Address ptr, VM_Address end) {
	    while (ptr != end) {
		Oop oop = ptr.asOop();
		Blueprint bp =  oop.getBlueprint();
		if (bp == null)
		    return;
		int sz = bp.getVariableSize(oop);

		int i = bp.getCID();
		int j = bp.isSharedState() ? counts[i].length - 1 : bp.getUID();
		counts[i][j]++;
		bytes[i][j] += sz;
		ptr = ptr.add(sz);
	    }
	}

	public void dumpStats() {
	    BasicIO.out.println("OBJS\tBYTES\tBLUEPRINT");
	    for (int i = 0; i < ntc; i++) {
		if (counts[i] == null)
		    continue;
		for (int j = 0; j < counts[i].length - 1; j++)
		    if (counts[i][j] != 0)
			BasicIO.out.println(counts[i][j] + "\t" +
					    bytes[i][j] + "\t" +
					    rev[i][j]);
		int nbp = counts[i].length - 1;
		if (counts[i][nbp] != 0)
		    BasicIO.out.println(counts[i][nbp] + "\t" +
					bytes[i][nbp] + "\t" +
					DomainDirectory.getContext(i) + 
					" shared state");
	    }
	}
    }
    
    public void dumpExtent(VM_Address start, VM_Address end) {
	ObjectCounter oc = new ObjectCounter();
	oc.walk(start, end);
	oc.dumpStats();
    }
    
}
