package s3.services.simplejit.optimization;

import ovm.core.OVMBase;
import ovm.core.repository.ExceptionHandler;
import ovm.core.repository.TypeCodes;
import ovm.core.repository.TypeName;
import ovm.core.repository.Binder;
import ovm.core.repository.RepositoryString;
import ovm.core.domain.Type;
import ovm.core.domain.ConstantResolvedStaticMethodref;
import ovm.core.domain.ConstantResolvedInstanceMethodref;
import ovm.core.domain.LinkageException;
import ovm.core.domain.MemberResolver;
import ovm.services.bytecode.JVMConstants;
import s3.core.domain.S3Constants;
import s3.core.domain.S3ConstantsBuilder;
import s3.core.domain.S3Method;
import s3.core.domain.S3ByteCode;
import s3.services.simplejit.SimpleJITCompileFilter;
import s3.services.simplejit.SimpleJITAllCompileFilter;
import s3.services.simplejit.ir.*;
import s3.services.simplejit.util.*;
import s3.services.simplejit.ir.SimpleJITInstruction.*;
import s3.services.simplejit.ir.SimpleJITOperand.*;
import s3.services.simplejit.CompilerVMInterface;
import s3.services.simplejit.SimpleJITLogger;
import ovm.util.IdentityHashMap;
import ovm.util.Iterator;
import ovm.util.Set;
import java.util.Date;

public class SimpleJITPostInliner {

    public static class Driver {

	private final SimpleJITRegisterAllocator.Driver ra_driver;
	private final SimpleJITCompileFilter compileFilter;
	private final SimpleJITResultWriteFilter resultWriteFilter;
	private final SimpleJITRegisterAllocator.ResultWriter resultWriter;

	public Driver(SimpleJITRegisterAllocator.Driver ra_driver) {
	    this.ra_driver = ra_driver;
	    this.compileFilter = new SimpleJITAllCompileFilter();
	    this.resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    this.resultWriter = null;
	}

	public Driver(SimpleJITRegisterAllocator.Driver ra_driver,
		      SimpleJITCompileFilter compileFilter) {
	    this.ra_driver = ra_driver;
	    this.compileFilter = compileFilter;
	    this.resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    this.resultWriter = null;
	}

	public Driver(SimpleJITRegisterAllocator.Driver ra_driver,
		      SimpleJITResultWriteFilter resultWriteFilter,
		      SimpleJITRegisterAllocator.ResultWriter resultWriter) {
	    this.ra_driver = ra_driver;
	    this.compileFilter = new SimpleJITAllCompileFilter();
	    this.resultWriteFilter = resultWriteFilter;
	    this.resultWriter = resultWriter;
	}

	public Driver(SimpleJITRegisterAllocator.Driver ra_driver,
		      SimpleJITCompileFilter compileFilter,
		      SimpleJITResultWriteFilter resultWriteFilter,
		      SimpleJITRegisterAllocator.ResultWriter resultWriter) {
	    this.ra_driver = ra_driver;
	    this.compileFilter = compileFilter;
	    this.resultWriteFilter = resultWriteFilter;
	    this.resultWriter = resultWriter;
	}

	public SimpleJITIRCode run
	    (S3Method method,
	     CompilerVMInterface compilerVMInterface) {

	    if (! compileFilter.shouldCompile(method))
		return null;

	    SimpleJITIRCode sjMethod = null;
	    sjMethod = ra_driver.run(method,
				     compilerVMInterface);
	    if (sjMethod == null)
		return null;

	    runInliner(sjMethod,
		       compilerVMInterface);

	    return sjMethod;
	}	    
	private void runInliner
	    (SimpleJITIRCode sjMethod,
	     CompilerVMInterface compilerVMInterface) {
	    SimpleJITPostInliner inliner = 
		new SimpleJITPostInliner(sjMethod, 
					 ra_driver, 
					 compilerVMInterface,
					 resultWriteFilter,
					 resultWriter);
	    sjMethod.optimizationTime = 0L;
	    inliner.run();
	    inliner.print();
	}
    }

    /** This option enables logging in this abstract intepreter, if
     * this is turned on, the abstract interpretation gets way
     * slow. */
    public static final boolean LOG_ENABLED = false;

    /** If this option is enabled, full log messages will be logged,
     * whereas if it is disabled, only certain amount of the latest
     * log messages will be logged. This option is meaningful only
     * when LOG_ENABLED == true */
    private static final boolean FULL_LOG = true;

    /** This option specifies the size of the log message buffer when
     * fullLog == false */
    private static final int LOG_SIZE = 8 * 8192;

    private SimpleJITBasicBlock.Set blockSet;

    private SimpleJITLogger logger;

    private SimpleJITIRCode sjMethod;

    private SimpleJITRegisterAllocator.Driver ra_driver;
    private CompilerVMInterface compilerVMInterface;

    private SimpleJITResultWriteFilter resultWriteFilter;
    private SimpleJITRegisterAllocator.ResultWriter resultWriter;

    private long inlineTime = 0L;
    private Date startTime;
    private Date endTime;

    public SimpleJITPostInliner(SimpleJITIRCode sjMethod,
				SimpleJITRegisterAllocator.Driver ra_driver,
				CompilerVMInterface compilerVMInterface,
				SimpleJITResultWriteFilter resultWriteFilter,
				SimpleJITRegisterAllocator.ResultWriter resultWriter) {
	this.sjMethod = sjMethod;
	this.blockSet = sjMethod.getBlockSet();
	this.ra_driver = ra_driver;
	this.compilerVMInterface = compilerVMInterface;
	this.logger = 
	    FULL_LOG 
	    ? new SimpleJITLogger("SJPostInliner", System.err)
	    : new SimpleJITLogger("SJPostInliner", LOG_SIZE);
	this.resultWriteFilter = resultWriteFilter;
	this.resultWriter = resultWriter;
    }

    private void log(String message) {
	if (LOG_ENABLED)
	    logger.addLog(message);
    }

    private void __assert__(boolean shouldBeTrue) {
	OVMBase.OVMassert(shouldBeTrue);
    }

    private void run() {
	if (LOG_ENABLED) {
	    log("[PostInliner " 
		+ sjMethod.getS3Method() + "]");
	}

	startTime = new Date();
	inlineStaticCalls();
	inlineSpecialCalls();
	endTime = new Date();
	inlineTime += endTime.getTime() - startTime.getTime();

	sjMethod.optimizationTime += inlineTime;

	if (resultWriteFilter.shouldWrite(sjMethod))
	    writeResults();
    }

    private SimpleJITIdentitySet mapReg(IdentityHashMap regMap,
					SimpleJITIdentitySet set) {
	SimpleJITIdentitySet result = new SimpleJITIdentitySet();
	for(Iterator it = set.iterator();
	    it.hasNext(); ) {
	    Object o = it.next();
	    Object v = regMap.get(o);
	    if (v == null)
		throw new Error();
	    result.add(v);
	}
	return result;
    }

    private void inlineStaticCalls() {
	S3Method method = sjMethod.getS3Method();
	S3ByteCode bytecode = (S3ByteCode)method.getCode(S3ByteCode.KIND);
	ExceptionHandler[] handlers = bytecode.getExceptionHandlers();
	if (handlers.length > 0) {
	    if (LOG_ENABLED)
		log("Not try inlining in " + method 
		    + " because it has exception handlers.");
	    return;
	}
	IdentityHashMap regMap = initRegMap(sjMethod);
	S3Constants cp = sjMethod.getConstantPool();
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	SimpleJITBasicBlock.List blockList = blockSet.getBlockList();
	workset.addAll(blockList.toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (! (inst instanceof Call))
		    continue;
		Call call = (Call)inst;
		if (call.getOperator() != Operators.INVOKESTATIC)
		    continue;
		// Now it is a static call

		// Get the target Method and compile it
		ConstantResolvedStaticMethodref smi = null;
		try {
		    smi = cp.resolveStaticMethod(call.getCPIndex());
		} catch (LinkageException e) {
		    if (LOG_ENABLED)
			log("Linkage error : " + call.getSelector());
		    continue;
		}
		S3Method inlinee_method = (S3Method)smi.getMethod();
		inlinee_method = findTargetStaticMethod(inlinee_method);
		if (inlinee_method == null
		    || method == inlinee_method /* no self-recursive */)
		    continue;

		endTime = new Date();
		inlineTime += endTime.getTime() - startTime.getTime();
		SimpleJITIRCode inlinee_ircode =
		    ra_driver.run(inlinee_method, compilerVMInterface);
		startTime = new Date();

		mergeRegMap(regMap, inlinee_ircode);

		SimpleJITBasicBlock.Set inlinee_blockSet =
		    inlinee_ircode.getBlockSet();
		SimpleJITBasicBlock.List inlinee_blockList =
		    inlinee_blockSet.getBlockList();

		// split the block into three
		SimpleJITBasicBlock[] block123 =
		    new SimpleJITBasicBlock[3];
		boolean wasSplit = b.splitAtInstruction(inst, block123);

		// merge CP
		cp = mergeConstantPool(method.getDeclaringType(),
				       cp, 
				       inlinee_blockSet,
				       inlinee_ircode.getConstantPool());

		// Invariant at this point: block123[1] has only one
		// instruction which is the Call instruction

		// Replace block123[1] with the target method's blocks
		block123[1].replaceWith(inlinee_blockSet);

		// Merge register tables and update registers
		SimpleJITIdentitySet inlinerLiveVRegsAtCallSite =
		    new SimpleJITIdentitySet();
		inlinerLiveVRegsAtCallSite.union(mapReg(regMap, call.getLiveInUses()));
//		inlinerLiveVRegsAtCallSite.union(mapReg(regMap, call.getLiveOutUses()));

		// Replace GetArgs and Returns with Moves
		connectArgsAndReturnStatic(sjMethod, 
					   b.getLoopDepth(),
					   call, 
					   inlinee_ircode,
					   inlinee_blockSet);

		blockList.addUnlessContained(block123[0]);
		blockList.addUnlessContained(block123[1]);
		blockList.addUnlessContained(block123[2]);

		blockList.addAll(inlinee_blockList);
		blockList.remove(block123[1]);

		mergeVirtualRegisterTables(sjMethod,
					   b.getLoopDepth(),
					   inlinerLiveVRegsAtCallSite,
					   inlinee_ircode,
					   regMap);

		if (LOG_ENABLED)
		    log("PostInlined " + call.getSelector()
			+ " in " + method);

		// Record that an inlining happened
		sjMethod.setInliningFlag();
		sjMethod.addInlinedMethod(inlinee_ircode);

		if (wasSplit) {
		    // We don't want to go though block123[1]
		    if (block123[2] != block123[1])
			workset.add(block123[2]);
		    break; // block structure changed
		}
	    }
	}
	sjMethod.setConstantPool(cp);
	updateRegisters(sjMethod, regMap);
    }

    private void inlineSpecialCalls() {
	S3Method method = sjMethod.getS3Method();
	S3ByteCode bytecode = (S3ByteCode)method.getCode(S3ByteCode.KIND);
	ExceptionHandler[] handlers = bytecode.getExceptionHandlers();
	if (handlers.length > 0) {
	    if (LOG_ENABLED)
		log("Not try inlining in " + method 
		    + " because it has exception handlers.");
	    return;
	}
	IdentityHashMap regMap = initRegMap(sjMethod);
	S3Constants cp = sjMethod.getConstantPool();
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	SimpleJITBasicBlock.List blockList = blockSet.getBlockList();
	workset.addAll(blockList.toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (! (inst instanceof Call))
		    continue;
		Call call = (Call)inst;
		if (call.getOperator() != Operators.INVOKESPECIAL)
		    continue;

		// Get the target Method and compile it
		ConstantResolvedInstanceMethodref imi = null;
		try {
		    imi = cp.resolveInstanceMethod(call.getCPIndex());
		} catch (LinkageException e) {
		    if (LOG_ENABLED)
			log("Linkage error : " + call.getSelector());
		    continue;
		}
		if (! imi.isNonVirtual) // only deal with 'special' calls
		    continue;
		S3Method inlinee_method = (S3Method)imi.getMethod();
		if (inlinee_method == null
		    || method == inlinee_method /* no self-recursive */)
		    continue;
		if (inlinee_method.getCode(S3ByteCode.KIND) == null)
		    throw new Error("Found special method without bytecode");

		endTime = new Date();
		inlineTime += endTime.getTime() - startTime.getTime();
		SimpleJITIRCode inlinee_ircode =
		    ra_driver.run(inlinee_method, compilerVMInterface);
		startTime = new Date();

		mergeRegMap(regMap, inlinee_ircode);

		SimpleJITBasicBlock.Set inlinee_blockSet =
		    inlinee_ircode.getBlockSet();
		SimpleJITBasicBlock.List inlinee_blockList =
		    inlinee_blockSet.getBlockList();

		// split the block into three
		SimpleJITBasicBlock[] block123 =
		    new SimpleJITBasicBlock[3];
		boolean wasSplit = b.splitAtInstruction(inst, block123);

		// merge CP
		cp = mergeConstantPool(method.getDeclaringType(),
				       cp, 
				       inlinee_blockSet,
				       inlinee_ircode.getConstantPool());

		// Invariant at this point: block123[1] has only one
		// instruction which is the Call instruction

		// Replace block123[1] with the target method's blocks
		block123[1].replaceWith(inlinee_blockSet);

		// Merge register tables and update registers
		SimpleJITIdentitySet inlinerLiveVRegsAtCallSite =
		    new SimpleJITIdentitySet();
		inlinerLiveVRegsAtCallSite.union(mapReg(regMap, call.getLiveInUses()));
//		inlinerLiveVRegsAtCallSite.union(mapReg(regMap, call.getLiveOutUses()));

		// Replace GetArgs and Returns with Moves
		connectArgsAndReturnInstance(sjMethod,
					     b.getLoopDepth(),
					     call, 
					     inlinee_blockSet);

		blockList.addUnlessContained(block123[0]);
		blockList.addUnlessContained(block123[1]);
		blockList.addUnlessContained(block123[2]);

		blockList.addAll(inlinee_blockList);
		blockList.remove(block123[1]);

		mergeVirtualRegisterTables(sjMethod,
					   b.getLoopDepth(),
					   inlinerLiveVRegsAtCallSite,
					   inlinee_ircode,
					   regMap);

		if (LOG_ENABLED)
		    log("PostInlined " + call.getSelector()
			+ " in " + method);

		// Record that an inlining happened
		sjMethod.setInliningFlag();
		sjMethod.addInlinedMethod(inlinee_ircode);

		if (wasSplit) {
		    // We don't want to go though block123[1]
		    if (block123[2] != block123[1])
			workset.add(block123[2]);
		    break; // block structure changed
		}
	    }
	}
	sjMethod.setConstantPool(cp);
	updateRegisters(sjMethod, regMap);
    }

    private S3Method findTargetStaticMethod(S3Method method) {
	try {
	    MemberResolver resolver = 
		method.getDeclaringType().getDomain().getMemberResolver();
	    S3Method target =
		(S3Method)resolver
		.resolveStaticMethod(method.getDeclaringType(),
				     method.getSelector().getUnboundSelector(),
				     null);
	    if (LOG_ENABLED)
		log("findTargetStaticMethod : " + method + " -> " + target);
	    if (target.getMode().isNative())
		return null;
	    else
		return target;
	} catch (Throwable t) {
	    throw new Error
		("The inliner can't find the target of " + method + " : " + t);
	}
    }

    private S3Constants mergeConstantPool
	(Type.Compound type,
	 S3Constants inliner_cp,
	 SimpleJITBasicBlock.Set inlinee_blockSet,
	 S3Constants inlinee_cp) {

	S3ConstantsBuilder s3cb = new S3ConstantsBuilder(type, inliner_cp);

	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(inlinee_blockSet.getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (inst.isConstantPoolReferer()) {
		    ConstantPoolReferer cpr = inst.asConstantPoolReferer();
		    int cpindex = cpr.getCPIndex();
		    if (cpindex < 0)
			continue;
		    byte tag = inlinee_cp.getTagAt(cpindex);
		    int newCPIndex;
		    switch(tag) {
		    case JVMConstants.CONSTANT_Class:
		    case JVMConstants.CONSTANT_ResolvedClass:
			newCPIndex = s3cb.addClass(inlinee_cp.getClassAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_Fieldref:
		    case JVMConstants.CONSTANT_ResolvedStaticField:
		    case JVMConstants.CONSTANT_ResolvedInstanceField:
			newCPIndex = 
			    s3cb.addFieldref(inlinee_cp.getFieldrefAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_Methodref:
		    case JVMConstants.CONSTANT_ResolvedStaticMethod:
		    case JVMConstants.CONSTANT_ResolvedInstanceMethod:
			newCPIndex = 
			    s3cb.addMethodref(inlinee_cp.getMethodrefAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_InterfaceMethodref:
		    case JVMConstants.CONSTANT_ResolvedInterfaceMethod:
			newCPIndex = s3cb.addInterfaceMethodref
			    (inlinee_cp.getMethodrefAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_String:
			newCPIndex = s3cb.addUnresolvedString
			    ((RepositoryString)inlinee_cp.getConstantAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_Reference:
			newCPIndex = s3cb.addResolvedConstant
			    (inlinee_cp.getConstantAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_Binder:
			newCPIndex = s3cb.addUnresolvedBinder
			    ((Binder)inlinee_cp.getConstantAt(cpindex));
			break;
		    case JVMConstants.CONSTANT_SharedState:
			newCPIndex = s3cb.addUnresolvedSharedState
			    ((TypeName.Gemeinsam)inlinee_cp.getConstantAt(cpindex));
			break;
		    case 0: // empty
			newCPIndex = 0;
			break;
		    default:
			throw new Error("Unexpected tag " + tag);
		    }
		    cpr.setCPIndex(newCPIndex);
		}
	    }
	}
	return s3cb.build();
    }

    private void mergeVirtualRegisterTables
	(SimpleJITIRCode inliner,
	 int loopDepth,
	 SimpleJITIdentitySet inlinerLiveVRegsAtCallSite,
	 SimpleJITIRCode inlinee,
	 IdentityHashMap regMap) {

	VirtualRegisterFactory vrf = new VirtualRegisterFactory();

	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.INT,
					  regMap);
	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.REFERENCE,
					  regMap);
	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.LONG,
					  regMap);
	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.FLOAT,
					  regMap);
	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.DOUBLE,
					  regMap);
	mergeVirtualRegisterTables_helper(inliner,
					  loopDepth,
					  inlinerLiveVRegsAtCallSite,
					  inlinee,
					  vrf,
					  TypeCodes.RETURNADDRESS,
					  regMap);
    }

    private void mergeVirtualRegisterTables_helper
	(SimpleJITIRCode inliner,
	 int loopDepth,
	 SimpleJITIdentitySet inlinerLiveVRegsAtCallSite,
	 SimpleJITIRCode inlinee,
	 VirtualRegisterFactory vrf,
	 char typeCode,
	 IdentityHashMap regMap) {
	VirtualRegisterMergerMap[] mergerMap = 
	    mergeVirtualRegisterTables
	    (inliner,
	     loopDepth,
	     inlinerLiveVRegsAtCallSite,
	     inlinee,
	     vrf,
	     typeCode);
	printMergerMap(mergerMap);
	updateRegisterTable(inliner, typeCode, mergerMap);

	IdentityHashMap newRegMap = mergerMap2HashMap(mergerMap);
	mergeRegMap(regMap, newRegMap);

	//updateRegisters(inliner, mergerMap2HashMap(mergerMap));
    }

    private IdentityHashMap initRegMap(SimpleJITIRCode sjMethod) {
	IdentityHashMap regMap = new IdentityHashMap();
	SimpleJITVirtualRegisterOperand[] vregsI = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.INT);
	for(int i = 0; i < vregsI.length; i++) {
	    regMap.put(vregsI[i], vregsI[i]);
	}
	SimpleJITVirtualRegisterOperand[] vregsA = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.REFERENCE);
	for(int i = 0; i < vregsA.length; i++) {
	    regMap.put(vregsA[i], vregsA[i]);
	}
	SimpleJITVirtualRegisterOperand[] vregsJ = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.LONG);
	for(int i = 0; i < vregsJ.length; i++) {
	    regMap.put(vregsJ[i], vregsJ[i]);
	}
	SimpleJITVirtualRegisterOperand[] vregsF = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.FLOAT);
	for(int i = 0; i < vregsF.length; i++) {
	    regMap.put(vregsF[i], vregsF[i]);
	}
	SimpleJITVirtualRegisterOperand[] vregsD = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.DOUBLE);
	for(int i = 0; i < vregsD.length; i++) {
	    regMap.put(vregsD[i], vregsD[i]);
	}
	SimpleJITVirtualRegisterOperand[] vregsU = 
	    sjMethod.getVirtualRegisterTable(TypeCodes.RETURNADDRESS);
	for(int i = 0; i < vregsU.length; i++) {
	    regMap.put(vregsU[i], vregsU[i]);
	}
	return regMap;
    }

    private void mergeRegMap(IdentityHashMap old, IdentityHashMap change) {
	Set oldKeySet = old.keySet();
	for(Iterator it = oldKeySet.iterator();
	    it.hasNext(); ) {
	    Object oldKey = it.next();
	    Object oldValue = old.get(oldKey);
	    if (change.containsKey(oldValue)) {
		old.put(oldKey, change.get(oldValue));
	    }
	}
    }

    private void mergeRegMap(IdentityHashMap old, SimpleJITIRCode sjMethod) {
	IdentityHashMap newMap = initRegMap(sjMethod);
	Set newMapKeySet = newMap.keySet();
	for(Iterator it = newMapKeySet.iterator();
	    it.hasNext(); ) {
	    Object newKey = it.next();
	    old.put(newKey, newMap.get(newKey));
	}
    }

    private void updateRegisterTable(SimpleJITIRCode ircode,
				     char typeCode,
				     VirtualRegisterMergerMap[] mergerMap) {
	int validLength = 0;
	for(int i = 0; i < mergerMap.length; i++) {
	    if (mergerMap[i].inliner == null
		&& mergerMap[i].inlinee == null) {
		break;
	    }
	    validLength++;
	}
	SimpleJITVirtualRegisterOperand[] newTable =
	    new SimpleJITVirtualRegisterOperand[validLength];
	for(int i = 0; i < newTable.length; i++) {
	    newTable[i] = mergerMap[i].combined;
	}
	ircode.setVirtualRegisterTable(typeCode, newTable);	
    }
				     
    /**
     * Replace the virtual registers in the method body with the
     * virtual registers for the combined method.
     */
    private void updateRegisters(SimpleJITIRCode inliner,
				 IdentityHashMap regMap) {
	if (regMap.isEmpty())
	    return;
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(inliner.getBlockSet().getBlockList().toArray());
	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
    //	    log(b + "reachingIn : " + b.getReachingInDefs().toString());
    //	    log(b + "reachingOut : " + b.getReachingOutDefs().toString());

	    updateFlowInfoOfBlock(b, regMap);

	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];

		updateFlowInfoOfInstruction(inst, regMap);

		if (inst.isUser()) {
		    SimpleJITOperand[] uses = 
			inst.asUser().getUseOperands();
		    for(int j = 0; j < uses.length; j++) {
			if (! (uses[j]
			       instanceof SimpleJITVirtualRegisterOperand))
			    continue;
			SimpleJITVirtualRegisterOperand old_vreg =
			    (SimpleJITVirtualRegisterOperand)uses[j];
			SimpleJITVirtualRegisterOperand new_vreg =
			    (SimpleJITVirtualRegisterOperand)
			    regMap.get(old_vreg);
			if (new_vreg != null)
			    uses[j] = new_vreg;
		    }
		    inst.asUser().setUseOperands(uses);
		}
		if (inst.isDefiner()) {
		    SimpleJITOperand[] defs = 
			inst.asDefiner().getDefOperands();
		    for(int j = 0; j < defs.length; j++) {
			if (! (defs[j]
			       instanceof SimpleJITVirtualRegisterOperand))
			    continue;
			SimpleJITVirtualRegisterOperand old_vreg =
			    (SimpleJITVirtualRegisterOperand)defs[j];
			SimpleJITVirtualRegisterOperand new_vreg =
			    (SimpleJITVirtualRegisterOperand)
			    regMap.get(old_vreg);
			if (new_vreg != null)
			    defs[j] = new_vreg;
		    }
		    inst.asDefiner().setDefOperands(defs);
		}
	    }
	}
    }

    private void updateFlowInfoOfBlock(SimpleJITBasicBlock b, 
				       IdentityHashMap regMap) {
	SimpleJITIdentitySet reachingInDefs =
	    b.getReachingInDefs();
	SimpleJITIdentitySet reachingOutDefs =
	    b.getReachingOutDefs();
	SimpleJITIdentitySet liveInUses =
	    b.getLiveInUses();
	SimpleJITIdentitySet liveOutUses =
	    b.getLiveOutUses();

	for(Iterator it = reachingInDefs.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		reachingInDefs.remove(oldreg);
		reachingInDefs.add(newreg);
	    }
	}
	for(Iterator it = reachingOutDefs.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		reachingOutDefs.remove(oldreg);
		reachingOutDefs.add(newreg);
	    }
	}
	for(Iterator it = liveInUses.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		liveInUses.remove(oldreg);
		liveInUses.add(newreg);
	    }
	}
	for(Iterator it = liveOutUses.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		liveOutUses.remove(oldreg);
		liveOutUses.add(newreg);
	    }
	}

	b.setReachingInDefs(reachingInDefs);
	b.setReachingOutDefs(reachingOutDefs);
	b.setLiveInUses(liveInUses);
	b.setLiveOutUses(liveOutUses);
    }

    private void updateFlowInfoOfInstruction(SimpleJITInstruction inst,
					     IdentityHashMap regMap) {
	SimpleJITIdentitySet reachingInDefs =
	    inst.getReachingInDefs();
	SimpleJITIdentitySet reachingOutDefs =
	    inst.getReachingOutDefs();
	SimpleJITIdentitySet liveInUses =
	    inst.getLiveInUses();
	SimpleJITIdentitySet liveOutUses =
	    inst.getLiveOutUses();
	for(Iterator it = reachingInDefs.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		reachingInDefs.remove(oldreg);
		reachingInDefs.add(newreg);
	    }
	}
	for(Iterator it = reachingOutDefs.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		reachingOutDefs.remove(oldreg);
		reachingOutDefs.add(newreg);
	    }
	}
	for(Iterator it = liveInUses.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		liveInUses.remove(oldreg);
		liveInUses.add(newreg);
	    }
	}
	for(Iterator it = liveOutUses.copy().iterator();
	    it.hasNext(); ) {
	    SimpleJITVirtualRegisterOperand oldreg =
		(SimpleJITVirtualRegisterOperand)it.next();
	    SimpleJITVirtualRegisterOperand newreg =
		(SimpleJITVirtualRegisterOperand)regMap.get(oldreg);
	    if (newreg != null) {
		liveOutUses.remove(oldreg);
		liveOutUses.add(newreg);
	    }
	}
	inst.setReachingInDefs(reachingInDefs);
	inst.setReachingOutDefs(reachingOutDefs);
	inst.setLiveInUses(liveInUses);
	inst.setLiveOutUses(liveOutUses);
    }

    private IdentityHashMap 
	mergerMap2HashMap(VirtualRegisterMergerMap[] mergerMap) {
	// Hash map for fast lookup
	IdentityHashMap regMap = new IdentityHashMap();
	for(int i = 0; i < mergerMap.length; i++) {
	    SimpleJITVirtualRegisterOperand combined_vreg = mergerMap[i].combined;
	    SimpleJITVirtualRegisterOperand inliner_vreg = mergerMap[i].inliner;
	    SimpleJITVirtualRegisterOperand inlinee_vreg = mergerMap[i].inlinee;
	    if (inliner_vreg != null)
		regMap.put(inliner_vreg, combined_vreg);
	    if (inlinee_vreg != null)
		regMap.put(inlinee_vreg, combined_vreg);
	}

	return regMap;
    }

    /**
     * Merge the two virtual register tables
     */
    private VirtualRegisterMergerMap[] mergeVirtualRegisterTables
	(SimpleJITIRCode inliner, 
	 int loopDepth,
	 SimpleJITIdentitySet inlinerLiveVRegsAtCallSite,
	 SimpleJITIRCode inlinee,
	 VirtualRegisterFactory vrf,
	 char typeCode) {

	SimpleJITVirtualRegisterOperand[] inlinerVRegTable = 
	    inliner.getVirtualRegisterTable(typeCode);
	SimpleJITVirtualRegisterOperand[] inlineeVRegTable = 
	    inlinee.getVirtualRegisterTable(typeCode);
	VirtualRegisterMergerMap[] mergerMap =
	    new VirtualRegisterMergerMap
	    [inlinerVRegTable.length 
	     + inlineeVRegTable.length];
	for(int i = 0; i < mergerMap.length; i++) {
	    mergerMap[i] = new VirtualRegisterMergerMap();
	    mergerMap[i].combined = vrf.create(typeCode, i);
	}
	for(int i = 0; i < inlinerVRegTable.length; i++) {
	    mergerMap[i].inliner = inlinerVRegTable[i];
	}
	int eep = 0; // inlinee pointer
	for(int i = 0; i < mergerMap.length; i++) {
	    if (eep == inlineeVRegTable.length)
		break;
	    SimpleJITVirtualRegisterOperand inlineeVReg =
		inlineeVRegTable[eep];
	    SimpleJITVirtualRegisterOperand inlinerVReg =
		mergerMap[i].inliner;
	    if (inlinerVReg == null
		|| (inlinerVReg != null
		    && ! inlinerLiveVRegsAtCallSite.contains(inlinerVReg))) {
		mergerMap[i].inlinee = inlineeVReg;
		eep++;
	    }
	}

	// Compute the wrc and interferers of the combined regs
	IdentityHashMap regMap = mergerMap2HashMap(mergerMap);

	for(int i = 0; i < mergerMap.length; i++) {
	    SimpleJITVirtualRegisterOperand combinedReg =
		mergerMap[i].combined;
	    SimpleJITVirtualRegisterOperand inlinerReg =
		mergerMap[i].inliner;
	    SimpleJITVirtualRegisterOperand inlineeReg =
		mergerMap[i].inlinee;
	    int wrc = 0;
	    if (inlinerReg != null)
		wrc += inlinerReg.getWeightedRefCount();
	    if (inlineeReg != null)
		wrc += power(10, loopDepth) * inlineeReg.getWeightedRefCount();
	    combinedReg.setWeightedRefCount(wrc);

	    // Compute the interferers of the combined reg
	    SimpleJITIdentitySet combinedInterferers = 
		new SimpleJITIdentitySet();
	    if (inlinerReg != null) {
		combinedInterferers.union(inlinerReg.getInterferers());
	    }
	    if (inlineeReg != null) {
		combinedInterferers.union(inlineeReg.getInterferers());
	    }
	    if (combinedInterferers.empty())
		continue;
	    SimpleJITIdentitySet newCombinedInterferers = 
		new SimpleJITIdentitySet();
	    for(Iterator it = combinedInterferers.iterator();
		it.hasNext(); ) {
		SimpleJITVirtualRegisterOperand v =
		    (SimpleJITVirtualRegisterOperand)it.next();
		SimpleJITVirtualRegisterOperand mapped =
		    (SimpleJITVirtualRegisterOperand)regMap.get(v);
		if (mapped != null)
		    newCombinedInterferers.add(mapped);
	    }
	    combinedReg.setInterferers(newCombinedInterferers);
	}

	sortVirtualRegisterMergerMaps(mergerMap);
	mergerMap = reverseVirtualRegisterMergerMaps(mergerMap);
	for(int i = 0; i < mergerMap.length; i++) {
	    int found_index = mergerMap[i].combined.getIndex();
	    if (found_index != i)
		vrf.swapVirtualRegisterIndices
		    (typeCode, found_index, i);
	}

	return mergerMap;
    }

    private static int power(int a, int b) {
	int p = 1;
	for(int i = 0; i < b; i++) {
	    p *= a;
	}
	return p;
    }

    private VirtualRegisterMergerMap[] reverseVirtualRegisterMergerMaps
	(VirtualRegisterMergerMap[] maps) {
	VirtualRegisterMergerMap[] reversed =
	    new VirtualRegisterMergerMap[maps.length];
	for(int i = 0; i < maps.length; i++)
	    reversed[i] = maps[maps.length - 1 - i];
	return reversed;
    }

    private void sortVirtualRegisterMergerMaps
	(VirtualRegisterMergerMap[] maps) {
	sortVirtualRegisterMergerMaps(maps, 0, maps.length - 1);
    }

    // Quick sort
    private void sortVirtualRegisterMergerMaps
	(VirtualRegisterMergerMap[] maps, 
	 int low0,
	 int high0) {
	int low = low0;
	int high = high0;
	if (low >= high)
	    return;
	else if (low == high - 1) {
	    if (maps[low].combined.getWeightedRefCount()
		> maps[high].combined.getWeightedRefCount()) {
		VirtualRegisterMergerMap w = maps[low];
		maps[low] = maps[high];
		maps[high] = w;
	    }
	    return;
	}
	VirtualRegisterMergerMap pivot = maps[(low + high) / 2];
	maps[(low + high) / 2] = maps[high];
	maps[high] = pivot;
	while (low < high) {
	    while (maps[low].combined.getWeightedRefCount() <= 
		   pivot.combined.getWeightedRefCount()
		   && low < high) {
		low++;
	    }
	    while (pivot.combined.getWeightedRefCount() <= 
		   maps[high].combined.getWeightedRefCount()
		   && low < high) {
		high--;
	    }
	    if (low < high) {
		VirtualRegisterMergerMap w = maps[low];
		maps[low] = maps[high];
		maps[high] = w;
	    }
	}

	maps[high0] = maps[high];
	maps[high] = pivot;
	sortVirtualRegisterMergerMaps(maps, low0, low - 1);
	sortVirtualRegisterMergerMaps(maps, high + 1, high0);
    }

    private class VirtualRegisterMergerMap {
	SimpleJITVirtualRegisterOperand combined;
	SimpleJITVirtualRegisterOperand inliner;
	SimpleJITVirtualRegisterOperand inlinee;
    }

    /**
     * For a static call inlining, replace GetArgs and Return with
     * moves. Discard GetArg0 (shst).
     * FIXME: synchronized static call
     */
    private void connectArgsAndReturnStatic
	(SimpleJITIRCode inliner,
	 int loopDepth,
	 Call call,
	 SimpleJITIRCode inlinee,
	 SimpleJITBasicBlock.Set inlinee_blockSet) {
	boolean isVoidCall = (call instanceof CallVoid);
	SimpleJITOperand result = 
	    isVoidCall 
	    ? null
	    : ((CallNonVoid)call).getResult();
	SimpleJITOperand[] actualArgs = call.getArgs();
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(inlinee_blockSet.getBlockList().toArray());

	// Adjust register counts
// 	if (result != null) {
// 	    inliner.adjustVirtualRegisterTable
// 		((SimpleJITVirtualRegisterOperand)result,
// 		 -1 * power(10, loopDepth));
// 	}
// 	for(int i = 0; i < actualArgs.length; i++) {
// 	    if (actualArgs[i] instanceof SimpleJITVirtualRegisterOperand)
// 		inliner.adjustVirtualRegisterTable
// 		    ((SimpleJITVirtualRegisterOperand)actualArgs[i],
// 		     -1 * power(10, loopDepth));
// 	}

	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (inst instanceof GetArg) {
		    GetArg ga = (GetArg)inst;
		    int index = ga.getIndex();
		    if (index == 0) {
			b.removeInstruction(inst);
// 			inlinee.adjustVirtualRegisterTable
// 			    ((SimpleJITVirtualRegisterOperand)
// 			     ga.getDestination(),
// 			     -1 * power(10, b.getLoopDepth()));
		    } else {
			b.replaceInstruction
			    (ga, 
			     Move.create(ga.getType(),
					 ga.getDestination(),
					 actualArgs[index - 1],
					 null));

// 			// Adjust register counts
// 			if (actualArgs[index - 1] 
// 			    instanceof SimpleJITVirtualRegisterOperand)
// 			    inliner.adjustVirtualRegisterTable
// 				((SimpleJITVirtualRegisterOperand)
// 				 actualArgs[index - 1],
// 				 1 * power(10, loopDepth));
		    }
		} else if (inst instanceof ReturnValue) {
		    if (isVoidCall)
			throw new Error();
		    ReturnValue rv = (ReturnValue)inst;
		    b.replaceInstruction
			(rv,
			 Move.create(rv.getType(),
				     result,
				     rv.getReturnValueOperand(),
				     null));
		    b.setFlowEndBlock(false);

// 		    // Adjust register counts
// 		    inliner.adjustVirtualRegisterTable
// 			((SimpleJITVirtualRegisterOperand)result,
// 			 1 * power(10, loopDepth));
		} else if (inst instanceof Return) {
		    if (! isVoidCall)
			throw new Error();
		    b.removeInstruction(inst);
		    b.setFlowEndBlock(false);
		}
	    }
	}
    }

    /**
     * For an instance call inlining, replace GetArgs and Return with
     * moves.
     * FIXME: synchronized call
     */
    private void connectArgsAndReturnInstance
	(SimpleJITIRCode inliner,
	 int loopDepth,
	 Call call,
	 SimpleJITBasicBlock.Set inlinee_blockSet) {
	boolean isVoidCall = (call instanceof CallVoid);
	SimpleJITOperand result = 
	    isVoidCall 
	    ? null
	    : ((CallNonVoid)call).getResult();
	SimpleJITOperand[] actualArgs = call.getArgs();
	SimpleJITIdentitySet workset = new SimpleJITIdentitySet();
	workset.addAll(inlinee_blockSet.getBlockList().toArray());

	// Adjust register counts
// 	if (result != null) {
// 	    inliner.adjustVirtualRegisterTable
// 		((SimpleJITVirtualRegisterOperand)result,
// 		 -1 * power(10, loopDepth));
// 	}
// 	for(int i = 0; i < actualArgs.length; i++) {
// 	    if (actualArgs[i] instanceof SimpleJITVirtualRegisterOperand)
// 		inliner.adjustVirtualRegisterTable
// 		    ((SimpleJITVirtualRegisterOperand)actualArgs[i],
// 		     -1 * power(10, loopDepth));
// 	}

	while (! workset.empty()) {
	    SimpleJITBasicBlock b = 
		(SimpleJITBasicBlock)workset.remove();
	    SimpleJITInstruction[] insts = b.getInstructions();
	    for(int i = 0; i < insts.length; i++) {
		SimpleJITInstruction inst = insts[i];
		if (inst instanceof GetArg) {
		    GetArg ga = (GetArg)inst;
		    int index = ga.getIndex();
		    b.replaceInstruction
			(ga, 
			 Move.create(ga.getType(),
				     ga.getDestination(),
				     actualArgs[index],
				     null));

// 		    // Adjust register counts
// 		    if (actualArgs[index]
// 			instanceof SimpleJITVirtualRegisterOperand)
// 			inliner.adjustVirtualRegisterTable
// 			    ((SimpleJITVirtualRegisterOperand)
// 			     actualArgs[index],
// 			     1 * power(10, loopDepth));
		} else if (inst instanceof ReturnValue) {
		    if (isVoidCall)
			throw new Error();
		    ReturnValue rv = (ReturnValue)inst;
		    b.replaceInstruction
			(rv,
			 Move.create(rv.getType(),
				     result,
				     rv.getReturnValueOperand(),
				     null));
		    b.setFlowEndBlock(false);

// 		    // Adjust register counts
// 		    inliner.adjustVirtualRegisterTable
// 			((SimpleJITVirtualRegisterOperand)result,
// 			 1 * power(10, loopDepth));
		} else if (inst instanceof Return) {
		    if (! isVoidCall)
			throw new Error();
		    b.removeInstruction(inst);
		    b.setFlowEndBlock(false);
		}
	    }
	}
    }

    private void print() {
	if (LOG_ENABLED) {
	    printBlockSet();
	    printVirtualRegisterTable();
	}
    }

    private void writeResults() {
	String output = "";
	S3Method m = sjMethod.getS3Method();
	String mname = m.getSelector().toString();
	String tcname = m.getDeclaringType().getContext().toString();
	output += tcname + "\n" + mname + "\n";
	output = writeResults_helper(TypeCodes.INT, output);
	output = writeResults_helper(TypeCodes.REFERENCE, output);
	output = writeResults_helper(TypeCodes.LONG, output);
	output = writeResults_helper(TypeCodes.FLOAT, output);
	output = writeResults_helper(TypeCodes.DOUBLE, output);
	output = writeResults_helper(TypeCodes.RETURNADDRESS, output);
	output += "\n" + sjMethod.optimizationTime + "\n";
	resultWriter.println(output);
    }

    private String writeResults_helper(char typeCode, String output) {
	output += typeCode + "\n";
	SimpleJITVirtualRegisterOperand[] vregTable =
	    sjMethod.getVirtualRegisterTable(typeCode);
	output += vregTable.length + "\n";
	for(int i = 0; i < vregTable.length; i++) {
	    SimpleJITVirtualRegisterOperand v = vregTable[i];
	    output += "\t" + v.getWeightedRefCount() + "\n";
	}
	return output;
    }

    private void printBlockSet() {
	String b = blockSet.toString();
	log(b);
    }

    public void printVirtualRegisterTable() {
	printVirtualRegisterTable_helper(TypeCodes.INT);
	printVirtualRegisterTable_helper(TypeCodes.REFERENCE);
	printVirtualRegisterTable_helper(TypeCodes.LONG);
	printVirtualRegisterTable_helper(TypeCodes.FLOAT);
	printVirtualRegisterTable_helper(TypeCodes.DOUBLE);
	printVirtualRegisterTable_helper(TypeCodes.RETURNADDRESS);
    }

    private void printVirtualRegisterTable_helper(char typeCode) {
	String r = "Virtual register list " + typeCode + " :\n";
	SimpleJITVirtualRegisterOperand[] vregTable =
	    sjMethod.getVirtualRegisterTable(typeCode);
	for(int i = 0; i < vregTable.length; i++) {
	    SimpleJITVirtualRegisterOperand v = vregTable[i];
	    r += v.toString() + " : wrc = " 
		+ v.getWeightedRefCount() + " : "
		+ v.getInterferersAsString() + "\n";
	}
	log(r);
    }

    private void printMergerMap(VirtualRegisterMergerMap[] mergerMap) {
	String s = "\n\tCombined\tInliner\tInlinee\n";
	for(int i = 0; i < mergerMap.length; i++) {
	    s += "\t" + mergerMap[i].combined + "(" 
		+ mergerMap[i].combined.getWeightedRefCount() + ")"
		+ "\t" + mergerMap[i].inliner
		+ "\t" + mergerMap[i].inlinee + "\n";
	}
	log(s);
    }
}
