package s3.services.memory.mostlyCopying;

import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;

import ovm.core.domain.Blueprint;
import ovm.core.domain.Domain;
import ovm.core.domain.DomainDirectory;
import ovm.core.domain.LinkageException;
import ovm.core.domain.ObjectModel;
import ovm.core.domain.Oop;
import ovm.core.domain.Type;
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.repository.JavaNames;
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 s3.util.PragmaTransformCallsiteIR.BCdead;

import s3.services.bootimage.Driver;

public class ImageBarrierManager extends Manager {
    static final class Nat implements NativeInterface {
	static native void mc_barrier_init(int imageEnd,
					   int[] continued,
					   int[] dirty);
    }

    final VM_Address imageBase;
    final int imageBaseIdx;
    VM_Word  lastImageBlock;
    // call DomainDirectory.getExecutiveDomain late to avoid an
    // initialization cycle
    /*final*/ Blueprint.Array intArray;

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

    int rootPages = 0;

    protected void markPreciseRoots() {
	// avoid walking the image header
	int idx = imageBaseIdx;
	if (block.getBit(block.dirty, idx)) {
	    int sz = block.getSize(idx);
	    copier.walk(Native.getImageBaseAddress(),
			imageBase.add(sz << blockShift));
	    idx += sz;
	}

	for (idx = block.ffs(block.dirty, idx);
	     VM_Word.fromInt(idx).uLT(lastImageBlock);
	     idx = block.ffs(block.dirty, idx)) {
	    if (block.isContinued(idx))
		idx = block.getBase(idx);
	    int sz = block.getSize(idx);
	    bcopier.walk(idx, sz);
	    rootPages += sz;
	    idx += sz;
	}

	// avoid walking the image trailer's magic word.
	if (idx > 0) {
	    copier.walk(heapBase.add(idx << blockShift),
			Native.getImageEndAddress());
	}
    }


    protected void printStats() {
	super.printStats();
	BasicIO.out.print(" ");
	BasicIO.out.print(b2k(rootPages));
	BasicIO.out.print("k of bootimage traversed");
	rootPages = 0;
    }

    private void initLate() throws BCdead {
	try {
	    Domain ed = DomainDirectory.getExecutiveDomain();
	    Type.Context stc = ed.getSystemTypeContext();
	    Type intArrayT = stc.typeFor(JavaNames.arr_int);
	    intArray = (Blueprint.Array) ed.blueprintFor(intArrayT);
	} catch (LinkageException e) {
	    throw e.unchecked();
	}
    }
	
    public ImageBarrierManager(String heapSize,
			       String blockSize,
			       String dumpOom,
			       String _immortalSize)
	throws BCdead
    {
	super(heapSize, "4k", dumpOom);
	int _heapBase = super.getImageBaseAddress();
	int immortalSize = CommandLine.parseSize(_immortalSize);
	int _imageBase = _heapBase + this.heapSize + immortalSize;
	// round up to a 32-block boundary
	_imageBase = _imageBase + (31<<blockShift) & (-1 << 5+blockShift);

	heapBase = VM_Address.fromInt(_heapBase);
	imageBase = VM_Address.fromInt(_imageBase);
	
	imageBaseIdx =  (_imageBase - _heapBase) >> blockShift;

	try {
	    params = new PrintWriter(new FileOutputStream
				     ("mc_barrier_params.h"),
				     true);
	    params.println("#define OVM_PAGE_SIZE " + this.blockSize);
	    params.println("#define HEAP_START 0x" +
			   Integer.toHexString(_heapBase));
	    params.println("#define IMAGE_CONTINUED_RANGES \\");

	    Driver.gen_ovm_c.println("#include <mc_barrier_params.h>");
	    Driver.gen_ovm_c.println("#include <mc_barrier.h>");
	    Driver.gen_ovm_c.flush();
	} catch (IOException e) {
	    throw new RuntimeException(e);
	}
    }

    transient boolean lastBig;
    transient PrintWriter params;

    public int getFixedHeapAddress() {
	return heapBase.asInt();
    }

    public int getImageBaseAddress() {
	return imageBase.asInt();
    }

    protected int allocateInImage(int firstFree, int size, Blueprint bp)
	throws BCdead
    {
	if (intArray == null)
	    initLate();
	if (!lastBig
	    && ((firstFree + size) >> blockShift) == (firstFree >> blockShift))
	    return firstFree;
	else if (size <= blockSize) {
	    lastBig = false;
	    return (firstFree + blockSize - 1) & ~(blockSize - 1);
	} else {
	    lastBig = true;
	    int ret = (firstFree + blockSize - 1) & ~(blockSize - 1);
	    params.println("\t{ 0x" + Integer.toHexString(ret) +
			     ", 0x" + Integer.toHexString(ret+size) + "}, \\");
	    return ret;
	}
    }

    public void boot() {
	VM_Address ret = Native.mmap(heapBase, imageBase.diff(heapBase).asInt(),
				     PROT_READ|PROT_WRITE,
				     MAP_FIXED|MAP_ANON|MAP_PRIVATE,
				     -1, 0);
	if (ret != heapBase)
	    NativeHelpers.abort();

	int heapLen = (heapSize + (31<<blockShift)) >> (blockShift + 5);
	int heapSize = (int) intArray.computeSizeFor(heapLen);
	int fullByteSize = Native.getImageEndAddress().diff(heapBase).asInt();
	int fullLen = ((fullByteSize + (31<<blockShift) + blockSize - 1)
		       >> (blockShift + 5));
	int fullSize = (int) intArray.computeSizeFor(fullLen);

	VM_Address p = Native.mmap(VM_Address.fromInt(0),
				   3*heapSize + 2*fullSize,
				   PROT_READ|PROT_WRITE,
				   MAP_ANON|MAP_PRIVATE,
				   -1, 0);
	intArray.stamp(p, heapLen);
	block.freeBlocks = (int[]) p.asAnyObject();
	p = p.add(heapSize);

	intArray.stamp(p, heapLen);
	block.whiteBlocks = (int[]) p.asAnyObject();
	block.freshBlocks = block.whiteBlocks;
	p = p.add(heapSize);

	intArray.stamp(p, heapLen);
	block.greyBlocks = (int[]) p.asAnyObject();
	p = p.add(heapSize);

	intArray.stamp(p, fullLen);
	block.dirty = (int[]) p.asAnyObject();
	p = p.add(fullSize);

	intArray.stamp(p, fullLen);
	block.cont = (int[]) p.asAnyObject();
	p = p.add(fullSize);

	block.initFreelist();

	int lastIdx = Native.getImageEndAddress().diff(heapBase).asInt();
	lastIdx >>= blockShift;
	if (block.isContinued(lastIdx))
	    lastIdx = block.getBase(lastIdx);
	lastImageBlock = VM_Word.fromInt(lastIdx);

 	Nat.mc_barrier_init(Native.getImageEndAddress().asInt(),
			    block.cont, block.dirty);
	Native.mprotect(imageBase,
			Native.getImageEndAddress().diff(imageBase).asInt(),
			PROT_READ);

	heapArea.setupDestructors(this.heapSize);
    }
}
