package s3.services.memory.mostlyCopying;

import ovm.core.Executive;
import ovm.core.OVMBase;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.JavaDomain;
import ovm.core.domain.RealtimeJavaDomain;
import ovm.core.domain.Domain;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
import ovm.core.domain.Blueprint;
import ovm.core.domain.Method;
import ovm.core.domain.WildcardException;
import ovm.core.repository.TypeName;
import ovm.core.repository.JavaNames;
import ovm.core.repository.TypeCodes;
import ovm.core.repository.Selector;
import ovm.core.execution.Context;
import ovm.core.execution.InvocationMessage;
import ovm.core.execution.ReturnMessage;
import ovm.core.execution.CoreServicesAccess;
import ovm.core.execution.Native;
import ovm.core.execution.Context;
import ovm.core.services.threads.OVMThreadContext;
import ovm.core.services.threads.OVMThread;
import ovm.services.realtime.NoHeapRealtimeOVMThread;
import ovm.core.services.io.BasicIO;
import ovm.core.services.memory.MemoryPolicy;
import ovm.core.services.memory.ScopedMemoryContext;
import ovm.core.services.memory.VM_Address;
import ovm.core.services.memory.VM_Area;
import ovm.core.services.memory.PragmaNoBarriers;
import ovm.services.memory.FinalizableArea;
import ovm.core.services.memory.VM_Word;
import ovm.services.bytecode.JVMConstants.Throwables;
import ovm.services.memory.fifo.VM_FIFOArea;
import ovm.util.CommandLine;
import ovm.util.Mem;
import ovm.util.NumberRanges;
import ovm.util.OVMError;
import s3.util.PragmaNoPollcheck;
import s3.util.PragmaAtomic;
import s3.util.PragmaInline;
import s3.core.domain.S3Domain;

/**
 * Region suporting memory manager that takes regions from within the
 * same area as the heap. This manager no longer supports the RTSJ
 * configuration and has been replaced by SplitRegionManager.
 */
public class RegionManager extends Manager {

    /** A quick &amp; easy way to access the alignment. */
    static final int ALIGN = VM_Address.widthInBytes();
    
    /**
     * If false, allocate memory the first time a region is entered
     * and free it when the region is reset.  Otherwise, allocate
     * memory when a region is allocated and free it when it becomes
     * unreachable.
     * <p>
     * NOTE: since we don't detect when a region become
     * unreachable, regions are never implicitly destroyed.  When this
     * variable is set, regions aren't explicitly destroyed in reset()
     * either.  This represents a memory leak.
     **/
    static final boolean RETAIN_REGION_MEMORY = true;

    /**
     * If true, emit checks in read/write barriers.
     **/
    final boolean DO_CHECKS;


    /** Indicates whether a load check must occur. This is set whenever we
        context switch to a no-heap thread and cleared otherwise
    */
    boolean doLoadCheck = false;

    /** Suppresses load checks during debugging code and when dumping on OOM.
        A non-zero value suppresses the check.
     */
    int suppressLoadChecks = 0;



    /*
     * Number of blocks not allocated to regions.  The ratio heap
     * blocks to allocated blocks determines when a GC takes place.
     */
    int heapBlocks;

    /*
     * heapSize >> blockShift.  Useful for checking ranges after
     * shifting 
     */
    VM_Word totalHeapBlocks;

    /*
     * heapBase >>> blockShift.  Useful for checking ranges after
     * shifting.
     */
    int heapBaseIndex;

    /*
     * This shadows the our inherited blocks field to expose the
     * allocRoot and freeRoot methods
     */
    /*final*/ RegionList block;

    /*
     * Immortal and heap memory are distinct.  We have to statically
     * partition memory into heap and immortal regions to avoid GC for
     * immortal allocation.  We might as well partition the address
     * space too, and treat immortal just like the bootimage.
     */
    VM_Address immortalBase;
    int immortalSize;
    int immortalOffset = 0;
    
    public RegionManager(String heapSize,
			 String blockSize,
			 String immortalSize,
			 String disableChecks,
			 String dumpOom)
    {
	super(heapSize, blockSize, dumpOom);
	this.immortalSize = CommandLine.parseSize(immortalSize);
	heapBlocks = this.heapSize >> blockShift;
	totalHeapBlocks = VM_Word.fromInt(heapBlocks);
	block.claimHeap();
	DO_CHECKS = (disableChecks == null || disableChecks.equals("false"));
        // ovm.core.services.io.BasicIO.out.println("$$$$ Region checks enabled: " + DO_CHECKS);
    }

    protected BlockData makeBlockData(int nb) {
	block = new RegionList(nb);
	return block;
    }


    public void boot() {
	super.boot();
	heapBaseIndex = heapBase.asInt() >>> blockShift;
	immortalSize = (immortalSize + pageSize - 1) & ~(pageSize - 1);
	immortalBase = Native.mmap(null, immortalSize,
				   PROT_READ|PROT_WRITE,
				   MAP_ANON|MAP_PRIVATE, -1, 0);
	if (immortalBase.asInt() == -1)
	    NativeHelpers.abort();
    }
    
    /**
     * A region-aware extension of the block bitvectors.  In this
     * implementation, memory for the heap and scopes is allocated
     * from the same pool of blocks.  This class is responsible for
     * tracking which blocks are part of a scoped memory area.  It is
     * also responsible for walking the roots in scoped and immortal
     * memory before scanning the grey blocks of the heap.
     **/
    class RegionList extends VecList {
	int[] rootBlocks;
	Area[] rootOwners;  // entries may be scoped

	RegionList(int nb) {
	    super(nb);
	    rootBlocks = new int[(nb + 31)/32];
	    rootOwners = new Area[nb];
	}

	// run after instance init for outer class are finished
	void claimHeap() {
	    for (int i = 0; i < rootOwners.length; i++)
		rootOwners[i] = heapArea;
	}

	public int allocRoot(Area owner, int sz) 
            throws ovm.core.services.memory.PragmaNoBarriers {
	    int idx = alloc(rootBlocks, sz);
	    if (idx != -1)
		for (int i = idx + sz; i --> idx; )
		    rootOwners[i] = owner;
	    return idx;
	}

	public boolean inFromSpace(int idx) {
	    // Continued blocks of a scope contain object references,
	    // whereas continued blocks of the heap only contain the
	    // tail end of an array.  We must tweak the assertion used
	    // in the non-region VecList
	    OVMassert(!getBit(cont, idx) || rootOwners[idx] != null);
	    return getBit(whiteBlocks, idx);
	}

	public void freeRoot(Area owner, int idx) {
	    for (int sz = getSize(idx); sz --> 0; idx++) {
		OVMassert(rootOwners[idx] == owner);
		setBit(freeBlocks, idx);
		clearBit(rootBlocks, idx);
		clearBit(cont, idx);
		rootOwners[idx] = heapArea;
	    }
	}
        
        // can only be used to switch ownership of the beginning of a slice
        public void switchRoot(Area oldOwner,
                               Area newOwner,
                               int low,
                               int high) {
            for (int i=low;i<high;++i) {
                OVMassert(rootOwners[i] == oldOwner,
                          "The owner of one of the blocks specified to switchRoot() is "+
                          "not equal to the expected oldOwner.");
                rootOwners[i] = newOwner;
            }
            
            if (low>0 && rootOwners[low-1]==newOwner) {
                setBit(cont, low);
            }
            
            if (rootOwners[high]==oldOwner) {
                clearBit(cont, high);
            }
        }

	/**
	 * If this block index is part of a scoped/explicitly managed
	 * region, return the VM_Area.  If it is part of the heap or
	 * free, return null
	 *
	 * @param idx the block index in question
	 *
	 * @return a possibly null VM_Area
	 *
	 */
	public Area owner(int idx) throws ovm.core.services.memory.PragmaNoBarriers {
	    return rootOwners[idx];
	}
	
	public void walkGrey(Manager.BlockWalker w) {
	    /* First, update roots reachable from immortal space and
	     * temporary regions
	     */
	    copier.walk(immortalBase, immortalBase.add(immortalSize));
	    for (int i = ffs(rootBlocks, 0); i != -1; i = ffs(rootBlocks, i)) {
		int sz = getSize(i);
		w.walk(i, sz);
		i += sz;
	    }
	    /* Then proceed over the grey objects in the heap */
	    super.walkGrey(w);
	}

	public void endGC() {
	    for (int i = 0; i < whiteBlocks.length - 1; i++) {
		OVMassert((whiteBlocks[i] & freeBlocks[i]) == 0);
		cont[i] &= ~whiteBlocks[i];
		freeBlocks[i] |= whiteBlocks[i];
		whiteBlocks[i] = ~(freeBlocks[i] | rootBlocks[i]);
	    }
	    freshBlocks = whiteBlocks;
	}
    }

    /*
    static abstract class Reference {
	VM_Address addr;
	Reference next;

	public Reference(Oop oop) {
	    Allocator a = (Allocator) allocatorFor(VM_Address.fromObject oop);
	    a.addRefrence(this);
	}
	
	public final boolean revive() {
	    return ((Allocator) allocatorFor(addr)).revive(addr);
	}

	public final Oop get() {
	  return addr.asOop();
	}

	public abstract void unreachable();
    }
    */
    
    public Error throwOOM() {
        CoreServicesAccess csa = DomainDirectory.getExecutiveDomain()
                                                .getCoreServicesAccess();
        csa.generateThrowable(Throwables.OUT_OF_MEMORY_ERROR, 0);
        // Return statement unreachable
        return null;
    }

    static abstract class Area extends VM_Area {
	public abstract VM_Address getMem(int size);
	int prange;
	int crange;
	boolean unchecked;

	public void setRange(short min, short max) throws s3.util.PragmaNoPollcheck {
            //Native.print_string("setRange(");
            //Native.print_hex_int(min);
            //Native.print_string(", ");
            //Native.print_hex_int(max);
            //Native.print_string(")\n");
            
	    prange = (min << 16)|max|MSK;
	    crange = (min << 16)|max;
	}

	public void reset() {
	    throw new OVMError.Unimplemented();
	}

	public void reset(int newOffset) {
	    throw new OVMError.Unimplemented();
	}

	public void destroy() {
	    throw new OVMError.Unimplemented();
	}

	public void enter() { }

	public Area(Oop mirror) {
	    super(mirror);
	    setRange((short) 0, Short.MAX_VALUE);
	}
	
// 	public void addReference(Reference r) {
// 	    r.next = refs;
// 	    refs = r;
// 	}
    }

    /*
     * Go through standard path for allocating heap memory
     */
    final Area heapArea = new Area(null) {
	    public VM_Address getMem(int size) {
                return getHeapMem(size, true);
	    }

	    public int size() { return gcThreshold << blockShift; }
	    public int memoryConsumed() { return allocated << blockShift; }

            public String toString()throws PragmaNoPollcheck { return "Heap Area"; }
	    public VM_Address getBaseAddress() {
		return heapBase;
	    }
	};

    /*
     * Allocate immortal memory.  We never have to zero it, since it
     * is never reused.
     */
    final Area immortalArea = new Area(null) {
	    public VM_Address getMem(int size) {
		if (size + immortalOffset < immortalSize) {
		    VM_Address ret = immortalBase.add(immortalOffset);
		    immortalOffset += size;
		    return ret;
		} else {
		    if (dumpOOM) {
			VM_Area r = setCurrentArea(heapArea);
			try {
			    BasicIO.out.println("immortal space exhausted");
			    dumpExtent(immortalBase,
				       immortalBase.add(immortalOffset));
			} finally { setCurrentArea(r); }
		    }
		    throw throwOOM();
		}
	    }

	    public int size() {	return immortalSize; }
	    public int memoryConsumed() { return immortalOffset; }
            public String toString() throws PragmaNoPollcheck { 
                return "Immortal Area"; 
            }
	    public VM_Address getBaseAddress() {
		return immortalBase;
	    }
	};


    private VM_Address allocateForArea(Area a,int rsize) throws PragmaAtomic {

        int nb = rsize >> blockShift;
        // Determine whether GC is needed and/or possible once the
        // nb-many blocks are removed from the heap space
        int newBlocks = heapBlocks - nb;
        int newThreshold = newBlocks / 2 - GC_WIGGLE_ROOM;
        
        //BasicIO.out.println("allocated = "+allocated+
        //                    "; gcThreshold = "+gcThreshold);
        
        if (allocated >= newThreshold) {
            // We COULD try to garbage collect here, and check
            // whether reachable objects exceed our new GC
            // threshold, but GC may not be allowed at this point.
            if (areaOf(VM_Address.fromObject(this).asOop()) == heapArea) {
                garbageCollect();
                newBlocks = heapBlocks - nb;
                newThreshold = newBlocks / 2 - GC_WIGGLE_ROOM;
            }
            if (allocated >= newThreshold)
                throwOOM();
        }
        int b = block.allocRoot(a, nb);
        if (b == -1)	// may fail due to fragmentation
            throwOOM();
        heapBlocks = newBlocks;
        gcThreshold = newThreshold;
        
        VM_Address base = heapBase.add(b << blockShift);
        
        base.setAddress(VM_Address.fromObject(null));
        
        return base;
    }
    
    class FIFOArea implements VM_FIFOArea {
        VM_Address base;
        int off; // offset in blocks from heapBase to base
        int rsize;
        int numBlocks;
        
        Oop mirror;
        
        int[] sliceSize;
        
        Error myThrowOOM() {
            if (dumpOOM) {
                Native.print_string("dead.available = ");
                Native.print_int(dead.available());
                Native.print_string("\npast.available = ");
                Native.print_int(past.available());
                Native.print_string("\nfuture.available = ");
                Native.print_int(future.available());
                Native.print_string("\n");
            }
            return throwOOM();
        }
        
        class Slice extends Area {
            // if low > high, then the area occupies the region
            // [0,low) U [high,numBlocks)
            
            private int low;
            private int high;
            private boolean empty;  // if low==high then empty resolves the ambiguity
            
            Slice(Oop mirror,int low,int size) {
                super(mirror);
                reset(low,size);
                setRange((short) 1, (short) (Short.MAX_VALUE - 1));
            }
            
            void reset(int l,int size) {
                low=l;
                high=(low+size)%numBlocks;
                empty=size==0;
            }
            
            int getLow() throws PragmaNoPollcheck {
                return low;
            }
            
            int getHigh() throws PragmaNoPollcheck {
                return high;
            }
            
            void incLow(Slice other,int offset) throws PragmaNoPollcheck {
                OVMBase.OVMassert(other.high==low,
                                  "The high pointer of the other Slice is not equal to "+
                                  "the low pointer of this Slice.");
                
                int newLow=low+offset;
                if (newLow>=numBlocks) {
                    newLow-=numBlocks;
                    block.switchRoot(this,other,off+low,off+numBlocks);
                    block.switchRoot(this,other,off,off+newLow);
                } else {
                    block.switchRoot(this,other,off+low,off+newLow);
                }

                other.high=low=newLow;
                
                if (offset>0) {
                    other.empty=false;
                    if (low==high) {
                        empty=true;
                    }
                }
            }
            
            void incHigh(Slice other,int offset) throws PragmaNoPollcheck {
                other.incLow(this,offset);
            }
            
            int available() throws PragmaNoPollcheck {
                if (empty) {
                    return 0;
                }
                if (high>low) {
                    return high-low;
                } else {
                    return numBlocks-low+high;
                }
            }
            
            public int size() {
                return available()<<blockShift;
            }
            
            public int memoryConsumed() {
                return size();
            }
            
	    public VM_Address getMem(int size) {
                throw Executive.panic(
                    "RegionManager$FIFOArea$Slice.getMem() called.");
	    }
	    public VM_Address getBaseAddress() {
                throw Executive.panic(
                    "RegionManager$FIFOArea$Slice.getBaseAddress() called.");
	    }
        }
        
        class FutureSlice extends Slice {
            int offset;
            
            private void updateOffset() throws PragmaNoPollcheck {
                offset=getLow()<<blockShift;
            }
            
            FutureSlice(Oop mirror,int low,int size) {
                super(mirror,low,size);
                updateOffset();
            }
            
            void incLow(Slice other,int offset) throws PragmaNoPollcheck {
                super.incLow(other,offset);
                updateOffset();
            }
            
            public VM_Address getMem(int size) {
                //Native.print_string("in Future.getMem(");
                //Native.print_int(size);
                //Native.print_string(")\n");
                
                VM_Address ret;
                
                // determine how much is left in this block.  note that if
                // offset&(blockSize-1) == 0 then we don't have anything
                // left in this block
                int leftInBlock=(blockSize-offset)&(blockSize-1);
                
                // determine how much is going to be allocated in other
                // blocks
                int inOtherBlocks=size-leftInBlock;
                
                if (inOtherBlocks <= 0) {
                    // fast path - we can allocate everything in this block.
                } else {
                    int newBlocks;
                    
                    // figure out if we have to skip back to the beginning of
                    // the ring to get size contiguous bytes
                    if (size > rsize-offset) {
                        // waste some blocks
                        newBlocks=numBlocks-getLow()+(size+blockSize-1)>>>blockShift;
                        offset=0;
                    } else {
                        newBlocks=(inOtherBlocks+blockSize-1)>>>blockShift;
                    }
                    
                    if (dead.available() < newBlocks) {
                        myThrowOOM();
                    }
                    
                    future.incHigh(dead,newBlocks);
                }
                
                OVMBase.OVMassert(offset+size<=rsize,
                                  "holy crap, we overran the memory area!");
                
                ret=base.add(offset);

                /*Native.print_string("returning ");
                Native.print_ptr(ret);
                Native.print_string(" ");
                Native.print_int(offset);
                Native.print_string(":");
                Native.print_int(offset+size);
                Native.print_string("\n");*/
                
                offset+=size;
                
                // FIXME: we have an almost perfect opportunity here to do block-based
                // zeroing.  should implement the logic to do that at some point.
                Mem.the().zero(ret,size+
                    // zero the word of the next object, but only if that next
                    // word is not in the next block.
                    ((offset&(blockSize-1))==0?0:ALIGN));
                
                //Native.print_string("success in Future.getMem()\n");
                
                return ret;
            }
        }
        
        Slice dead,past,future;
        
        FIFOArea(Oop mirror,
                 Oop pastMirror,
                 Oop futureMirror,
                 int size) {
            this.mirror=mirror;
            this.rsize=size=(size+blockSize-1)&~(blockSize-1);
            this.numBlocks=size>>>blockShift;
            this.dead=new Slice(null,0,numBlocks);
            this.past=new Slice(pastMirror,0,0);
            this.future=new FutureSlice(futureMirror,0,0);
            this.base=allocateForArea(dead,rsize);
            this.off=this.base.diff(heapBase).asInt()>>>blockShift;
            this.sliceSize=new int[numBlocks];
        }
        
        public int maxNumSlices() {
            return numBlocks;
        }
        
        public void enterForDequeue() throws PragmaNoPollcheck {
        }
        
        public void dequeue() throws PragmaNoPollcheck {
            past.incLow(dead,sliceSize[past.getLow()]);
        }
        
        public void enterForEnqueue() throws PragmaNoPollcheck {
            //Native.print_string("in enterForEnqueue()\n");
            if (future.available()!=0) {
                throw Executive.panic(
                    "enterForEnqueue() called when future is already in use.");
            }
            if (dead.available()==0) {
                myThrowOOM();
            }
            //Native.print_string("success in enterForEnqueue()\n");
            // high is incremented in future.getMem().
        }
        
        public void enqueue() throws PragmaNoPollcheck {
            //Native.print_string("in enqueue()\n");
            if (future.available()==0) {
                // waste a block to make our book-keeping easier.  this shouldn't
                // affect our performance since empty slices are really a bizarre
                // case!
                future.incHigh(dead,1);
            }
            sliceSize[future.getLow()]=future.available();
            future.incLow(past,future.available());
            //Native.print_string("success in enqueue()\n");
        }
        
        public int getAvailable() {
            return dead.size();
        }
        
        public VM_Area getFuture() {
            return future;
        }
        
        public VM_Area getPast() {
            return past;
        }
        
        public void reset() {
            dead.reset(0,numBlocks);
            past.reset(0,0);
            future.reset(0,0);
        }
        
        public Oop getMirror() {
            return mirror;
        }
        
        public int getSize() {
            return rsize;
        }
    }

    class TransientArea extends Area {
        VM_Address base;
        int rsize;
	int offset = 0;

	TransientArea(Oop mirror, boolean unchecked, int size) {
	    super(mirror);
	    this.unchecked = unchecked;
	    rsize = size = (size + blockSize - 1) & ~(blockSize - 1);
	    setRange((short) 1, (short) (Short.MAX_VALUE - 1));
	    if (RETAIN_REGION_MEMORY)
                base=allocateForArea(this,rsize);
//             Native.print_string("Created ");
//             Native.print_string(id);
//             Native.print_string("\n");
	}

	TransientArea(Oop mirror, int size) {
	    this(mirror, false, size);
	}

	TransientArea(boolean unchecked, int size) {
	    this(null, unchecked, size);
	}

        private final String id = "Transient Area@" + hashCode();
        public String toString() throws PragmaNoPollcheck { return id; }

	public void enter() {
	    if (!RETAIN_REGION_MEMORY && base == null)
                base=allocateForArea(this,rsize);
	}


	public VM_Address getMem(int size) {
	    if (offset + size > rsize) {
		if (dumpOOM) {
		    VM_Area r = setCurrentArea(heapArea);
                    suppressLoadChecks++;
                    try {
                        
			BasicIO.out.print(this + ": transient area exhausted ");
			BasicIO.out.print("(" + offset + " of " + rsize
					  + " used) ");
			Oop mirror = getMirror();
			int mnum = (mirror == null
				    ? 0
				    : mirror.getHash());
			BasicIO.out.println("mirror @" +
					    Integer.toHexString(mnum));

                        new Throwable().printStackTrace();

			dumpExtent(base, base.add(offset));
                        //new Error().printStackTrace();
		    }
		    finally { setCurrentArea(r); suppressLoadChecks--; }
		}
		throwOOM();
	    }

//             Native.print_string("Allocation from ");
//             Native.print_string(id);
//             Native.print_string("\n");
	    VM_Address ret = base.add(offset);
	    offset += size;

            // we zero here to get more predictable behavior.
            // note: must zero out the word following this object.  but
            // the first word of this object is already zeroed.  hence
            // this weird expression.
	    //
	    // Note: it may appear tempting to zero out a scope on
	    // per-block basis (as we do with the heap).  However,
	    // this will not work with the scratchpad, because we
	    // constantly discard the top few scratchpad objects, and
	    // allocate over them.
            Mem.the().zero(ret.add(ALIGN),offset==rsize?size-ALIGN:size);
	    
            return ret;
	}

	public VM_Address getBaseAddress() { return base; }

	public int memoryConsumed() { return offset; }

	public void reset() {
	    if (RETAIN_REGION_MEMORY) {
                // no! this is slow!
		//Mem.the().zero(base, offset);
	    } else {
                // Could zero as a testing aid. A dangling reference will 
                // blow up quicker if we actually zero the memory
		destroy();
            }
	    offset = 0;
	}

	public void reset(int newOffset) {
	    OVMBase.OVMassert(0 <= newOffset && newOffset <= offset);
	    Mem.the().zero(base.add(newOffset), offset - newOffset);
	    offset = newOffset;
	}

	public int size() { return rsize; }

	public void destroy() {
	    if (base != null) {
		VM_Word off = base.asWord().sub(heapBase.asWord());
		block.freeRoot(this, off.asInt() >>> blockShift);
		base = null;
		rsize = 0;
		int nb = rsize >> blockShift;
		heapBlocks += nb;
		gcThreshold = (heapBlocks / 2) - GC_WIGGLE_ROOM;
	    }
	}
    }



    class ScopedArea extends TransientArea implements FinalizableArea {

        VM_Address firstFinalizer = null;

	ScopedArea(Oop mirror, boolean unchecked, int size) {
	    super(mirror, unchecked,size);
	}

	ScopedArea(Oop mirror, int size) {
	    this(mirror, false, size);
	}

	ScopedArea(boolean unchecked, int size) {
	    this(null, unchecked, size);
	}

        private final String id = "Scoped Area@" + hashCode();
        public String toString() throws PragmaNoPollcheck { return id; }

        // be careful turning this on as you can easily enter a loop due to
        // the ED objects constructed by the debug code. The loop stops when
        // we run out of memory in this scope.
        static final boolean DEBUG_FINALIZERS = false;

        // pre-allocated debug strings for when exceptions occurs
        static final String ED_EXCEPTION = "ED exception from finalize():";
        static final String UD_EXCEPTION = "Warning: Exception in finalize(): ";
        static final String OOME = "Warning: OutOfMemoryError in finalize()";
//         static final String DONE_TRUE = "runFinalizers returning true";
//         static final String DONE_FALSE = "runFinalizers returning false";


        /**
         * Invokes the finalization methods for all user-domain objects 
         * currently allocated in this memory area.
         */
        public boolean runFinalizers() {
            if (firstFinalizer == null) 
                return false;

            VM_Address next = firstFinalizer;
            firstFinalizer = null;

            VM_Address end = base.add(offset);

            // i is only used for debugging
            for (int i = 1; next != end; i++) {
                int current = offset; // watch for unexpected allocations
                Oop oop = next.asOop();
                Blueprint bp = oop.getBlueprint();
                next = next.add(bp.getVariableSize(oop));

		JavaDomain d = (JavaDomain) bp.getDomain();
		Method finalize = ((S3Domain) d).findFinalizer(oop);
		if (finalize == null)
		    continue;
		
                // we need to avoid allocation of temporaries when performing
                // the reflective invocation. We can pre-allocate a 
                // ReturnMessage, but InvocationMessages are currently bound to
                // a particular Method at construction. We could modify IM
                // to allow a rebinding of a method with the same selector.
                // Or we can use the scratchpad.
                // For now we use the scratchpad but it might be better to
                // avoid allocation completely.

                Object r1 = MemoryPolicy.the().enterScratchPadArea();
                try {
		    // Every occurrence of `new InvocationMessage'
		    // outside of ovm.core.domain.ReflectiveMethod is
		    // potentially unsafe.  We have to trust the the
		    // domain got this method right.  That's OK,
		    // because this code is completely wrong anyway
                    InvocationMessage imsg = new InvocationMessage(finalize);
                    ReturnMessage rmsg = new ReturnMessage(TypeCodes.VOID);
                    VM_Area r2 = setCurrentArea(this);
                    try {
                        rmsg = imsg.invoke(oop, rmsg);
			OVMBase.OVMassert(rmsg.getExecutiveThrowable() == null);
			Oop ut = rmsg.getUserThrowable();
			if (ut != null)
			    BasicIO.out.println("RegionManager: exception in finalizer");
                    } finally {
                        setCurrentArea(r2);
                    }
                } finally {
                    MemoryPolicy.the().leave(r1);
                }

            }
            return firstFinalizer != null;
        }

	
	public VM_Address getMem(int size) {
            VM_Address ret = super.getMem(size);
            if (firstFinalizer == null)
                firstFinalizer = ret;
            return ret;
	}

	public void reset() {
            // uncomment for testing
            //Mem.the().zero(base, offset);
            super.reset();
            firstFinalizer = null;
	}

    }



    public VM_Address getMem(int size) throws PragmaAtomic {
	VM_Area a = (VM_Area) ScopedMemoryContext.getMemoryContext();
	return a.getMem(size);
    }

    public Object makeMemoryContext() {
	return heapArea;
    }
    public VM_Area getHeapArea() {
	return heapArea;
    }
    public VM_Area getImmortalArea() {
	return immortalArea;
    }
    public VM_Area getCurrentArea() {
	return (VM_Area) ScopedMemoryContext.getMemoryContext();
    }
    public VM_Area setCurrentArea(VM_Area area) {
	VM_Area ret = (VM_Area) ScopedMemoryContext.getMemoryContext();
	ScopedMemoryContext.setMemoryContext(area);
	((Area) area).enter();
	return ret;
    }

    public VM_Area makeExplicitArea(int size) {
	return new TransientArea(true, size);
    }

    public VM_Area makeScopedArea(Oop mirror, int size) {
	return new ScopedArea(mirror, size);
    }
    
    public VM_FIFOArea makeFIFOArea(Oop mirror,
                                    Oop pastMirror,
                                    Oop futureMirror,
                                    int size) {
        return new FIFOArea(mirror, pastMirror, futureMirror, size);
    }

    public VM_Area areaOf(Oop mem) {
	VM_Word off = VM_Address.fromObject(mem).diff(heapBase);
	if (!off.uLT(VM_Word.fromInt(heapSize)))
	    return immortalArea;
	int idx = off.asInt() >>> blockShift;
	return block.owner(idx);
    }

    protected void doGC() {
	// GC actually allocates some strings for its messages, be
	// sure not to allocate them in a scoped area
	VM_Area r = setCurrentArea(heapArea);
	try { super.doGC(); }
	finally { setCurrentArea(r); }
    }
	    
    public boolean needsWriteBarrier() {
        return DO_CHECKS;
    }

    public boolean needsReadBarrier() {
        return DO_CHECKS;
    }

    /**
     * Slow path for heap store checks.  Takes addresses that have
     * already been shifted by blockShift and do not fall on the same
     * block.
     * @param csa used to single error
     * @param sb  block pointed from (source)
     * @param tb  block pointed to (target)
     */
    // NOTE: If a user-domain exception is thrown due to a store check failure
    // then it can't be seen by this ED code. So no catch blocks or finally
    // clauses permitted around the part that can throw
    void storeCheckSlow(CoreServicesAccess csa, int sb, int tb) 
        throws s3.util.PragmaNoPollcheck, 
               ovm.core.services.memory.PragmaNoBarriers {
	VM_Word tidx = VM_Word.fromInt(tb - heapBaseIndex);
	if (tidx.uLT(totalHeapBlocks)) {
	    // target !immortal
	    Area ta = block.owner(tidx.asInt());
	    VM_Word sidx = VM_Word.fromInt(sb - heapBaseIndex);
	    if (sidx.uLT(totalHeapBlocks)) {
		// both !immortal
		Area sa = block.owner(sidx.asInt());
		// sa must be the same as, or a strict descendent of, ta
		if (sa != ta &&
		    ((ta.prange - sa.crange) & VM_Area.MSK) != VM_Area.RES &&
		    !sa.unchecked) {
                    // extended debugging info
                    if (!true) {
                        VM_Area scratchpad = 
                            ScopedMemoryContext.getScratchPadArea();
                        if (ta == heapArea)
                            Native.print_string("ta is heap\n");
                        else if (ta == immortalArea)
                            Native.print_string("ta is immortal \n");
                        else if (ta == scratchpad)
                            Native.print_string("ta is current scratchpad\n");
                        else
                            Native.print_string("ta is unknown scope \n");
                        if (sa == heapArea)
                            Native.print_string("sa is heap\n");
                        else if (sa == immortalArea)
                            Native.print_string("sa is immortal \n");
                        else if (sa == scratchpad)
                            Native.print_string("sa is scratchpad\n");
                        else
                            Native.print_string("sa is unknown scope \n");
                        Native.print_string("heapBaseIndex = ");
                        Native.print_int(heapBaseIndex);
                        Native.print_string("\n");
                        Native.print_string("sb = ");
                        Native.print_int(sb);
                        Native.print_string("\n");
                        Native.print_string("tb = ");
                        Native.print_int(tb);
                        Native.print_string("\n");
                        Native.print_string("sidx = ");
                        Native.print_int(sidx.asInt());
                        Native.print_string("\n");
                        Native.print_string("tidx = ");
                        Native.print_int(tidx.asInt());
                        Native.print_string("\n");
                        Native.print_string("range check failure: ");
                        Native.print_hex_int(ta.prange);
                        Native.print_string(" - ");
                        Native.print_hex_int(sa.crange);
                        Native.print_string("\n");
                        Native.print_string("want to throw ILLEGAL_ASSIGNMENT_ERROR due to range check\n");
                    }

                    RealtimeJavaDomain dom = 
                        (RealtimeJavaDomain) csa.getDomain();
                    dom.storeBarrierFailed();

                }
	    } else {
		// source immortal, target must be heap
		if (ta != heapArea) {
                    if (!true) {
                        Native.print_string("want to throw ILLEGAL_ASSIGNMENT_ERROR due to heap check\n");
                    }
                    RealtimeJavaDomain dom = 
                        (RealtimeJavaDomain) csa.getDomain();
                    dom.storeBarrierFailed();
                }
	    }
	}
	// immortal target can be stored anywhere
    }

    /**
     * Perform region store check, and raise the appropriate exception
     * when a store is bogus.
     *
     * @param csa used to signal error
     * @param src object being updated
     * @param tgt new field/element value
     **/
    void storeCheck(CoreServicesAccess csa, Oop src, int offset, Oop tgt) 
        throws s3.util.PragmaNoPollcheck, 
               ovm.core.services.memory.PragmaNoBarriers {
	int sb = VM_Address.fromObject(src).asInt() >>> blockShift;
	int tb = VM_Address.fromObject(tgt).asInt() >>> blockShift;
	if (sb != tb)
	    storeCheckSlow(csa, sb, tb);
	VM_Address addr = VM_Address.fromObject(src).add(offset);
	addr.setAddress(VM_Address.fromObject(tgt));
    }

    
    /**
     * Called immediately after a context switch to a new thread. We use this
     * hook to determine if the new thread requires load checks to be performed
     * and update the flag accordingly
     */
    public void observeContextSwitch(Object context) {
        OVMThread thread = null;
        if (context instanceof OVMThreadContext) {
            thread = ((OVMThreadContext) context).getThread();
        }
        else if (context instanceof OVMThread) {
            thread = (OVMThread) context;
        }
        doLoadCheck = (thread instanceof NoHeapRealtimeOVMThread);

        // debug tracing
        if (false) {
            if (doLoadCheck) {
                ovm.core.execution.Native.print_string("\nTurning on heap checks\n");
            }
            else {
                ovm.core.execution.Native.print_string("\nTurning off heap checks\n");
            }
        }
    }

    
    static final boolean DEBUG_HEAP_CHECKS = false;

    int dumpOn = 2;


    // NOTE: If a user-domain exception is thrown due to a load check failure
    // then it can't be seen by this ED code. So no catch blocks or finally
    // clauses permitted around the part that can throw - unless catching 
    // a wildcardException
    public Oop loadCheck(CoreServicesAccess csa, Oop src) 
        throws PragmaNoPollcheck, PragmaNoBarriers  {

//         Native.print_string("One read barrier for you: ");
//         Native.print_hex_int(VM_Address.fromObject(src).asInt());

        // check if we've disabled load checks - this prevents infinite
        // recursion when doing debug output and dumping on out-of-memory
        if (suppressLoadChecks > 0)
            return src;
        
        int sb = VM_Address.fromObject(src).asInt();
        if (sb==0) {
            //            Native.print_string(" - Null");
        } else {
            sb=sb >>> blockShift;
            VM_Word sidx = VM_Word.fromInt(sb - heapBaseIndex);
            if (sidx.uLT(totalHeapBlocks)) {
                Area sa = block.owner(sidx.asInt());
                if (sa == heapArea) {
                    // heap
                    if (DEBUG_HEAP_CHECKS) {
                        VM_Area current = getCurrentArea();
                        setCurrentArea(getImmortalArea());
                        suppressLoadChecks++;
                        try {
                            Native.print_string(" - Heap:");
                            Native.print_string(src.getBlueprint().getType().getUnrefinedName().toString());
                            Native.print_string("\n");
                            if (--dumpOn == 0) {
                                StackTraceElement[] stack = new Throwable().getStackTrace();
                                for (int i = 0; i < stack.length; i++) {
                                Native.print_string(stack[i].toString());
                                Native.print_string("\n");
                                }
                            }
                        }
                        finally {
                            setCurrentArea(current);
                            suppressLoadChecks--;
                        }
                    }
                    RealtimeJavaDomain dom = 
                        (RealtimeJavaDomain) csa.getDomain();
                    dom.readBarrierFailed();
                } else {
                    // scope
                    //                    Native.print_string(" - Scope");
                }
            } else {
                // immortal
                //                Native.print_string(" - Immortal");
            }
        }
        
        return src;
        
    }

    
    public void putFieldBarrier(CoreServicesAccess csa,
				Oop src, int offset, Oop tgt) 
        throws PragmaNoPollcheck, PragmaNoBarriers, PragmaInline {
	storeCheck(csa, src, offset, tgt);
    }

    public void aastoreBarrier(CoreServicesAccess csa,
                               Oop src, int offset, Oop tgt) 
        throws PragmaNoPollcheck, PragmaNoBarriers, PragmaInline {
	storeCheck(csa, src, offset, tgt);
    }

    public void readBarrier(CoreServicesAccess csa, Oop src) 
        throws PragmaNoPollcheck, PragmaNoBarriers, PragmaInline {
        if (doLoadCheck)
            loadCheck(csa, src);
    }
}
