package s3.services.memory.mostlyCopying;

import ovm.core.domain.DomainDirectory;
import ovm.core.domain.ObjectModel;
import ovm.core.domain.Oop;
import ovm.core.execution.Activation;
import ovm.core.execution.Context;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.execution.Native;
import ovm.core.execution.NativeConstants;
import ovm.core.execution.NativeInterface;
import ovm.core.services.io.BasicIO;
import ovm.core.services.memory.ExtentWalker;
import ovm.core.services.memory.NonPinningMemoryManager;
import ovm.core.services.memory.MovingGC;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.memory.VM_Word;
import ovm.services.bytecode.JVMConstants.Throwables;
import ovm.util.CommandLine;
import ovm.util.Mem;
import ovm.util.NumberRanges;
import ovm.util.OVMError;
import s3.util.PragmaNoInline;
import ovm.core.services.memory.VM_Area;
import ovm.core.services.memory.VM_Area.Destructor;
import s3.util.PragmaNoPollcheck;
import s3.util.PragmaAtomic;

/**
 * The basic Mostly Copying implementation.  This code attempts to
 * decouple object allocation and GC (which are provided by
 * <code>Manager</code> and its subclasses) from page allocation and
 * the global data structures used to represent the heap (which are
 * provided by {@link BlockData} and its implementations).
 *
 * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
 */
public class Manager extends NonPinningMemoryManager
    implements NativeConstants
{
    /* Number of extra blocks to hold in reserve for GC.  We need a
     * little extra space to do formatted IO.
     */
    static final int GC_WIGGLE_ROOM = 10;
    /*
     * Conservative collector parameters.  It really doesn't matter
     * whether gcc generates displaced pointers, as long as there is a
     * pointer to the object base.
     */
    static final boolean SKIP_UNALIGNED = true;
    static final boolean SKIP_OFF_BLOCK = true;

    /**
     * If true, zero out memory whether it is allocated for a new
     * object or a copy.  This results in a leaner path through
     * getHeapMem, but it also results in the to-space being
     * needlessly zeroed out.
     **/
    static final boolean ZERO_TO_SPACE = true;
    
    // Because MemoryManager is a boot-time-allocated singleton, J2c
    // be able to substitute these final fields.
    final int heapSize;

    final int blockSize;
    final int blockShift;	// 1 << blockShift == blockSize
    final int blockMask;	// == blockSize - 1

    final boolean dumpOOM;
    static final boolean DUMP_ON_GC = false;
    
    int gcThreshold;		// # blocks allocated before GC
    int allocated = 0;		// # of allocated blocks;
    int pinCount;
    int pinSize;
    int movedBytes = 0;
    boolean inGC = false;	// true during collection
    
    int pageSize;

    // I'm not sure how the compiler can take advantadge of this final
    // modifier, but it is final, and in fact must be build-time
    // allocated.
    final BlockData block;
    VM_Address heapBase;

    public static final class NativeHelpers implements NativeInterface {
	static native void abort();
	static native int getpagesize();
    }
    /**
     * Adapt The ExtentWalker interface to iterate over blocks of the
     * heap rather than address ranges.
     */
    public class BlockWalker {
	private ExtentWalker w;

	public BlockWalker(ExtentWalker w) { this. w = w; }

	public void walk(int idx, int sz) {
	    VM_Address base = heapBase.add(idx << blockShift);
	    VM_Address end = base.add(sz << blockShift);
	    w.walk(base, end);
	}

    }

    /**
     * Return the data structure used to represent the overall
     * structure of the heap.
     **/
    protected BlockData makeBlockData(int nb) {
	return new VecList(nb);
    }
    
    public Manager(String heapSize, String blockSize,
		   String dumpOom) {
	//System.err.println(heapSize + ", " + blockSize);
	this.heapSize = CommandLine.parseSize(heapSize);
	this.blockSize = CommandLine.parseSize(blockSize);
	this.blockMask = this.blockSize - 1;
        
        //System.err.println("HEAP SIZE = "+this.heapSize);
        
	blockOffset = this.blockSize; // first allocation grabs a block

	int _blockShift = -1;
	for (int i = 7; i < 20; i++)
	    if (this.blockSize == 1 << i) {
		_blockShift = i;
		break;
	    }
	if (_blockShift == -1)
	    throw new OVMError("bad block size " + blockSize);
	this.blockShift = _blockShift;
	gcThreshold = (this.heapSize >> (_blockShift + 1)) - 10;
	block = makeBlockData(this.heapSize >> _blockShift);
	dumpOOM = dumpOom != null && !dumpOom.equals("false");
    }

    public void boot() {
	pageSize = NativeHelpers.getpagesize();
	int req = (heapSize + pageSize - 1) & ~(pageSize - 1);
	heapBase = Native.mmap(null, req, PROT_READ|PROT_WRITE,
			       MAP_ANON|MAP_PRIVATE, -1, 0);
	if (heapBase.asInt() == -1)
	    NativeHelpers.abort();
	heapArea.setupDestructors(heapSize);
    }

    VM_Address getBlock(int size) {
	if (!inGC && allocated + size > gcThreshold) {
            // we'd prefer to call doGC but the gc sync policy is tied up
            // in garbageCollect. So we have some redundant synchronization
            // here
	    garbageCollect();
	    if (allocated + size > gcThreshold)
		return null;
	}
	int b = block.alloc(size);
	if (b == -1)
	    // FIXME: block.alloc() may fail due to fragmentation.  If
	    // we did not GC above, maybe it would be a good idea to
	    // GC here.
	    return null;
	allocated += size;
	return heapBase.add(b << blockShift);
    }

    boolean inFromSpace(VM_Address addr) {
	VM_Word off = addr.diff(heapBase);
	if (off.uLT(VM_Word.fromInt(heapSize))) {
	    // in the heap...
	    int bidx = off.asInt() >>> blockShift;
	    return block.inFromSpace(bidx);
	}
	return false;
    }

    /**
     * This class is responsible for copying objects reachable from
     * other objects and pinning objects that reachable from the
     * stack.
     *
     * @author <a href=mailto://baker29@cs.purdue.edu> Jason Baker </a>
     */
    protected class Updater extends ConservativeUpdater {
	    public boolean needsUpdate(MovingGC oop) {
		return inFromSpace(VM_Address.fromObject(oop));
	    }

	    public void checkConservativeRoot(VM_Word w) {
		if (SKIP_UNALIGNED) {
		    int low = w.asInt() & 0x3;
		    if (low != 0)
			return;
		}
		VM_Word offset = w.sub(heapBase.asInt());
		if (offset.uGE(VM_Word.fromInt(heapSize)))
		    return;
		int idx = offset.asInt() >>> blockShift;
		if (block.isContinued(idx)) {
		    if (SKIP_OFF_BLOCK)
			return;
		    else
			idx = block.getBase(idx);
		}
		if (block.inFromSpace(idx)) {
		    //BasicIO.err.print("found conservative pointer: 0x");
		    //BasicIO.err.println(Integer.toHexString(w.asInt()));
		    pinCount++;
		    pinSize += block.getSize(idx);
		    block.pin(idx);
		}
	    }

	    public Oop updateReference(MovingGC oop) {
		//Blueprint _bp = oop.getBlueprint();
		int sz = oop.getBlueprint().getVariableSize(oop);
		if (sz >= blockSize) {
		    movedBytes += sz;
		    VM_Word off = VM_Address.fromObject(oop).diff(heapBase);
		    int idx = off.asInt() >>> blockShift;
		    block.pin(idx);
		    return oop;
		} else {
		    VM_Address newLoc = getHeapMem(sz, false);
		    Mem.the().cpy(newLoc, VM_Address.fromObject(oop), sz);
		    oop.markAsForwarded(newLoc);
		    return newLoc.asOop();
		}
	    }
    }

    final ConservativeUpdater copier = makeCopier();

    protected ConservativeUpdater makeCopier() { return new Updater(); }

    /**
     * A wrapper for {@link #copier} that deals with heap blocks rather
     * than address ranges.
     **/
    final BlockWalker bcopier = new BlockWalker(copier); 

    /**
     * The first GC step: pin objects that may be referenced from the
     * stack.  These objects are marked as grey, and will be traversed
     * at a later stack in the GC.
     * @param curThreadSkip the first activation NOT to walk in this thread
     **/
    void markConservativeRoots(Activation curThreadSkip) {
	copier.walkThreadStacks(curThreadSkip);
	allocated += pinSize;
    }

    /**
     * The second GC step:  copy heap objects reachable from the
     * bootimage.
     **/
    void markPreciseRoots() {
	copier.walk(Native.getImageBaseAddress(),
		    Native.getImageEndAddress());
    }

    /** Base address of the block we are currently allocating from **/
    protected VM_Address blockPointer = null;
    /** offset within current block, or blockSize if blockPointer is null **/
    protected int blockOffset;

    int b2k(int b) { return (b << blockShift) >> 10; }

    protected void printStats() {
	if (pinCount > 0) {
	    BasicIO.out.print(" pinned " + b2k(pinSize) + "k in " +
				pinCount + " blocks");
	    pinCount = pinSize = 0;
	}
	if (movedBytes > 0) {
	    BasicIO.out.print(" logically copied " + (movedBytes>>10) + "k");
	    movedBytes = 0;
	}
    }

    /**
     * GC consists of three major steps.
     * <ol>
     * <li> {@link #markConservativeRoots},
     * <li> {@link #markPreciseRoots}, and
     * <li> {@link BlockData#walkGrey}
     * </ol>
     * Once all blocks of to-space have been traversed, we can reuse
     * from-space and continue allocating.
     **/
    protected void doGC() throws PragmaNoInline {
	inGC = true;
	VM_Area.DestructorWalker updater =
	    heapArea.new DestructorUpdater();
	if (DUMP_ON_GC) {
	    final ObjectCounter oc = new ObjectCounter();
	    block.walkWhite(new BlockWalker(oc));
	    BasicIO.out.println("GC triggered");
	    oc.dumpStats();
	}
	blockPointer = null;
	blockOffset = blockSize;
	int liveBefore = allocated;
	allocated = 0;
	block.startGC(true);
	BasicIO.out.print("[Garbage collecting...");
	long start = Native.getCurrentTime();
	
	Activation act = Context.getCurrentContext().getCurrentActivation();
	markConservativeRoots(act);
	markPreciseRoots();
	block.walkGrey(bcopier);
	for (int kind = 0; kind < Destructor.N_KINDS; kind++){
	    VM_Address lastBlock = blockPointer;
	    int lastOffset = blockOffset;
	    int lastAlloc = allocated;
	    heapArea.walkDestructors(kind, updater);
	    if (allocated != lastAlloc || lastOffset != blockOffset) {
		// scan newly grey objects
		if (lastBlock != null)
		    // On the previously scanned block
		    copier.walk(lastBlock.add(lastOffset),
				lastBlock.add(blockSize));
		// and freshy copied ones
		block.walkGrey(bcopier);
	    }
	}

	block.endGC();
	inGC = false;
	if (!ZERO_TO_SPACE && blockPointer != null)
	    Mem.the().zero(blockPointer.add(blockOffset),
			   blockSize - blockOffset);
	long end = Native.getCurrentTime();
	BasicIO.out.print("reclaimed " + b2k(liveBefore - allocated) +
			  "k in " + (end-start)/1000000 + "ms ("
			  + b2k(allocated) + "k live)");
	printStats();
	BasicIO.out.println("]");
    }
    
    private void exhausted() {
	    if (inGC)
		Native.abort();
	    if (dumpOOM) {
		inGC = true;
		final ObjectCounter oc = new ObjectCounter();
		block.walkWhite(new BlockWalker(oc));
		BasicIO.out.println("heap exhausted");
		oc.dumpStats();
	    }
	    throw outOfMemory();
    }

    private VM_Address getHeapMemSlow(int size, boolean shouldClear) {
	VM_Address mem;

	if (size > blockSize) {
	    int nBlocks = (size + blockSize - 1) >> blockShift;
	    mem = getBlock(nBlocks);
	    if (mem == null)
		exhausted();
	    else if (ZERO_TO_SPACE || !inGC) {
		int alloced = nBlocks << blockShift;
		int toClear = size + 4 < alloced ? size + 4 : alloced;
		Mem.the().zero(mem, toClear);
	    }
	    // We can't continue allocating out of this block and hope
	    // to use card marking.  We also can't keep allocating
	    // from the old block, since it is no longer last in the
	    // Cheney scan.  So, we discard the remainder of both
	    // blocks.
	    blockPointer = null;
	    blockOffset = blockSize;
	}
	else {
	    mem = getBlock(1);
	    if (mem == null)
		exhausted();
	    else if (ZERO_TO_SPACE || !inGC)
		Mem.the().zero(mem, blockSize);
	    blockPointer = mem;
	    blockOffset = size;
	}
	return mem;
    }

    /** Return a pointer to an uninitialized chunk of garbage collected
        memory of size sz.  This memory will be zero filled unless all
        / of ZERO_TO_SPACE, inGC, and shouldClear are false.  Setting
        ZERO_TO_SPACE to false slows down allocation significantly.
        
        As long as ZERO_TO_SPACE is true, gcc should inline this method
        everywhere.  Given that it includes two getfields, a setfield,
        a call, and a branch, I'm not sure that j2c will ever think
        about inlining on its own.
    */
    VM_Address getHeapMem(int size, boolean shouldClear) {
	VM_Address mem;
	
	if (blockOffset + size <= blockSize) {
	    mem = blockPointer.add(blockOffset);
	    blockOffset += size;
	}
	else
	    mem = getHeapMemSlow(size, shouldClear);

	if (!ZERO_TO_SPACE && inGC) {
	    // Inside GC, make sure that the word following the last
	    // object copied is null (this terminates scanning of the
	    // block), and explicitly zero out memory allocated by new
	    if (shouldClear)
		Mem.the().zero(mem, size);

	    if (blockOffset != blockSize)
		// small object, not at end
		blockPointer.add(blockOffset).setAddress(null);
	    else if (blockPointer == null && (size & blockMask) != 0) {
		// large object not filling space
		VM_Address justPast = mem.add(size);
		justPast.setAddress(null);
	    }
	}
	return mem;
    }

    /** {@inheritDoc}
     * <p>Provides atomicity by using {@link PragmaAtomic}.
     */
    protected VM_Address getMem(int size) throws PragmaAtomic {
	return getHeapMem(size, true);
    }

    /** Factory method to allow specialization of the heap area 
        by subclasses.
        NOTE: this is called prior to initialization of the subclass
        instance.
     */
    protected VM_Area makeHeapArea() {
        return new HeapArea();
    }

    protected class HeapArea extends VM_Area {
        public int size() { return gcThreshold << blockShift; }
        public int memoryConsumed() { return allocated << blockShift; }
        public VM_Address getBaseAddress() {
            return heapBase;
        }
        public VM_Address getMem(int size) {
            return getHeapMem(size, true);
        }
        public String toString()throws PragmaNoPollcheck { return "Heap Area"; }
        public boolean isLive(Oop oop) {
            return !inFromSpace(VM_Address.fromObject(oop));
        }
        
        public Oop revive(Destructor d) {
            if (inGC) {
                Oop doop = VM_Address.fromObject(d).asOop();
                copier.updateLoc(destructorRef.addressWithin(doop));
            }
            return super.revive(d);
        }
    }

    final protected VM_Area heapArea = makeHeapArea();

    public VM_Area getHeapArea() { return heapArea; }
    public boolean supportsDestructors() { return true; }
}

    
