package s3.services.memory.mostlyCopying;

import ovm.core.OVMBase;

public class BlockList extends GenericFreeList implements BlockData {
    /*
     * We have 30 bits to play with for allocated blocks.  Let's see
     * what we might need:
     * head/continued
     * old/new
     * from/to
     * dirty
     * on-stack
     * explicit pin count/next block/block index
     * 
     * For multi-block allocations, we may want to record the delta
     * back to the starting block.
     *
     *
     *                      +-+-+-----------+-----------+
     *  top sentinel        |0|0|    tail   |   head    |  [-1]
     *                      +-+-+-----------+-----------+ 
     *                                    ....
     *           /--------  +-+-+-----------+-----------+
     *           |          |1|1|   prev    |   next    |  [j]
     *           |          +-+-+-----------+-----------+
     *           |          |1|1|           |   size    |  [j+1]
     *        free multi    +-+-+-----------+-----------+
     *        unit block    |1|1|          ...          |  ...
     *           |          +-+-+-----------+-----------+
     *           |          |1|1|           |   size    |
     *           >--------  +-+-+-----------+-----------+
     *  single free unit    |1|0|   prev    |   next    |
     *           >--------  +-+-+-----------+-----------+
     *  single used unit    |0|0|1|                     |
     *           >--------  +-+-+-----------------------+
     *           |          |0|1|1|   ...   |           |
     *           |          +-+-+-----------+-----------+
     *           |          |0|1|0|... delta|   size    |
     *        used multi    +-+-+-----------+-----------+
     *        unit block    |0|1|0|... delta|    ...    |
     *           |          +-+-+-----------+-----------+
     *           |          |0|1|0|... delta|   size    |
     *           \--------  +-+-+-----------+-----------+
     *                                    ....
     *                      +-+-+-----------------------+
     *  bottom sentinel     |0|0|                       |  [N]
     *                      +-+-+-----------------------+ 
     */

    /*
     * The head bit is set for all valid blocks, and clear for all
     * interior blocks.  The first block on the freelist will also
     * have this bit set, so we have consider both getFree() and head
     * when deciding whether this is a possible base address.
     */
    static final int HEAD_ = (BLOCK_INFO_MASK+1) >>> 1;

    // Probably old and dirty should be maintained in seperate byte
    // arrays to improve memory locality and cache effects.  For a
    // block size of 512, the block array is 1.5% of the heap size (1M
    // for a 64M heap), so we don't want to scan the entire block
    // array on every minor gc.
    static final int DIRTY = HEAD_ >> 1;
    static final int OLDER_GENERATION =  DIRTY >> 1;
    
    static final int IN_TO_SPACE = OLDER_GENERATION >> 1;

    // note: TO_SPACE and ON_STACK are mutually exclusive
    static final int ON_STACK = IN_TO_SPACE >> 1;

    /*
     * The index area has several uses, depending on the state of the
     * flags above:
     * HEAD && !TO_SPACE: explicit pin count (someday)
     * HEAD && TO_SPACE:  index of next grey block
     * !HEAD:             index of first block in allocation
     */
    static final int FLAG_BITS = (32 - BLOCK_INFO_BITS) + 5;
    static final int INDEX_MASK = ON_STACK - 1; // 26 bits

    boolean inGC = false;
    
    final int size;
    int firstGrey = -1;
    int lastGrey = -1;

    public BlockList(int size) {
	super(size);
	this.size = size;
	if (size >= INDEX_MASK)
	    throw new Error("heap size (" + size + ") " +
			    "too large for grey list (max" +
			    (INDEX_MASK - 1) + ")");
	System.err.println(FLAG_BITS + " flag bits starting below "
			   + ON_STACK);
	int unit = alloc(size);
	free(unit);
    }
    
    public int alloc(int sz) {
	int ret = super.alloc(sz);
	if (ret != -1) {
	    setBlockInfo(ret, HEAD_);
	    if (inGC)
		addGrey(ret);
	    for (int i = 1; i < sz; i++)
		// clear free bits
		setLoEntry(ret+i, i);
		// setBlockInfo(ret + i, i);
	}
	return ret;
    }

    public int free(int unit) {
	int sz = getSize(unit);
	super.free(unit);
	// There are any number of crazy ways in which the free bit
	// may fail to be set
	for (int i = 0; i < sz; i++)
	    setLoEntry(unit+i, getLoEntry(unit+i) | FREE_MASK);
	return sz;
    }

    void addGrey(int b) {
	OVMBase.OVMassert(getBlockInfo(b) == HEAD_);
	setBlockInfo(b, HEAD_ | IN_TO_SPACE | INDEX_MASK);
	if (lastGrey == -1)
	    firstGrey = b;
	else
	    setIndex(lastGrey, b);
	lastGrey = b;
    }

    int nextGrey() {
	int ret = firstGrey;

	if (firstGrey != -1)
	    firstGrey = (getIndex(ret) << FLAG_BITS) >> FLAG_BITS ;
	if (firstGrey == -1)
	    lastGrey = -1;
	return ret;
    }

    public void startGC(boolean full) { inGC = true; }
    
    public void endGC() {
	inGC = false;
	int unit = 0;
	while (unit < size) {
	    if (getFree(unit))
		/* Here's a scenario, we last walked block 2 in the
		 * following sequence: FFWFFW Before freeing 2,
		 * getSize() returned 1.  After freeing 2, Block 3 is
		 * marked as free, but it has size 5 instead of 2.
		 */
		unit++;
	    else {
		int sz = getSize(unit);
		int info = getBlockInfo(unit);
		if ((info & (ON_STACK|IN_TO_SPACE)) == 0) {
		    OVMassert(info == HEAD_);
		    free(unit);
		}
		else // FIXME for generations, explicit pinning, ...
		    setBlockInfo(unit, HEAD_);
		unit += sz;
	    }
	}
    }

    public void walkGrey(Manager.BlockWalker b) {
	for (int idx = nextGrey(); idx != -1; idx = nextGrey()) 
	    b.walk(idx, getSize(idx));
    }
    public void walkFree(Manager.BlockWalker b) {
	for (int idx = getNext(-1); idx != -1; idx = getNext(idx))
	    b.walk(idx, getSize(idx));
    }
    
    public void walkWhite(Manager.BlockWalker b) {
	for (int i = 1; i <= size; i++) {
	    if (!getFree(i)) {
		int sz = getSize(i);
		b.walk(i, sz);
		i += sz - 1;
	    }
	}
    }

    public void pin(int unit) {
	addGrey(unit);
	//addGrey marks unit as in IN_TO_SPACE, who cares whether it is
	//also ON_STACK?
	//int old = getBlockInfo(unit);
	//setBlockInfo(unit, old | ON_STACK);
    }

    boolean isLive(int unit) { return !getFree(unit); }

    /*
     * Given that unit is the base of an allocated block, return true
     * if the block is in the from space.
     */
    public boolean inFromSpace(int unit) {
	return (!getFree(unit)
		&& (getBlockInfo(unit) & (IN_TO_SPACE|ON_STACK)) == 0);
    }

    public boolean isContinued(int unit) {
	// return false for free blocks, because getBase won't work.
	// return false for HEAD blocks because they aren't
	return !(getFree(unit) || (getBlockInfo(unit) & HEAD_) != 0);
    }
    public int getBase(int unit) {
	OVMassert(isContinued(unit));
	return getIndex(unit);
    }

    protected int getIndex(int unit) {
	return getBlockInfo(unit) & INDEX_MASK;
    }
    protected void setIndex(int unit, int val) {
	setBlockInfo(unit, (getBlockInfo(unit) & ~INDEX_MASK) + val);
    }
}
