package s3.services.simplejit;

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

import ovm.core.domain.Method;
import ovm.core.execution.NativeConstants;
import ovm.services.bytecode.InstructionSet;
import ovm.util.OVMError;
import s3.core.domain.S3ByteCode;
import s3.core.domain.S3Method;
import s3.core.domain.S3NativeCode;
import s3.services.simplejit.bytecode.SimpleJITAbstractInterpreter;
import s3.services.simplejit.bytecode.Translator;
import s3.services.simplejit.bytecode.Inliner;
import s3.services.simplejit.bytecode.Inliner2;
import s3.services.simplejit.ir.SimpleJITBC2IR;
import s3.services.simplejit.ir.SimpleJITIRCode;
import s3.services.simplejit.optimization.SimpleJITAllResultWriteFilter;
import s3.services.simplejit.optimization.SimpleJITDataFlowAnalyzer;
import s3.services.simplejit.optimization.SimpleJITInlinedResultWriteFilter;
import s3.services.simplejit.optimization.SimpleJITNoResultWriteFilter;
import s3.services.simplejit.optimization.SimpleJITPostInliner;
import s3.services.simplejit.optimization.SimpleJITPreInliner;
import s3.services.simplejit.optimization.SimpleJITRegisterAllocator;
import s3.services.simplejit.optimization.SimpleJITResultWriteFilter;

/**
 * The compiler driver.
 * @author Hiroshi Yamauchi
 **/
public abstract class SimpleJIT {

	public static final boolean dumpOvmIRSJ = false;
	public static PrintWriter img_ovmir_ascii_sj = null;
	static {
	try {
	    if (dumpOvmIRSJ)
		    img_ovmir_ascii_sj = new PrintWriter(new BufferedOutputStream(new FileOutputStream("img.ovmir.ascii.sj")));
	} catch (IOException e) { throw new RuntimeException(e); }
	}

    protected CompilerVMInterface compilerVMInterface;
    protected boolean debugPrint;
    protected boolean opt;
    protected CodeGenerator.Precomputed precomputed;
   
    protected SimpleJIT(
		 CompilerVMInterface compilerVMInterface,
		 boolean debugPrint,
		 boolean opt) {
	this.compilerVMInterface = compilerVMInterface;
	this.debugPrint = debugPrint;
	this.opt = opt;
	this.precomputed =
	    new CodeGenerator.Precomputed(compilerVMInterface);
    }

    public CodeGenerator.Precomputed getCodeGeneratorPrecomputed() {
        return precomputed;
    }
    
    /**
     * Compile a method
     * @param bp the blueprint of the class of the method
     * @param cf the bytecode fragment of the method
     * @param selector the selector of the method (may be null)
     **/
    public S3NativeCode compile(Method method) {
        if (method.getCode(S3ByteCode.KIND) == null) {
            throw new OVMError.Internal("[SimpleJIT] No S3ByteCode found in "
                    + method.getSelector());
        }
        if (method.getCode(S3ByteCode.KIND).getBytes() == null) {
            throw new OVMError.Internal(
                    "[SimpleJIT] No bytecode (byte[]) found in "
                            + method.getSelector());
        }

	new Inliner(method).inline();

        if (opt) {
            if (false) {
                SimpleJITIRCode sjircode = compile2IR(method);
                method.addCode(sjircode);
                return null;
            } else {
                if (true) {
                //if (method.getDeclaringType().getDomain().isExecutive()) {
                //if (method.toString().startsWith("Ltest/runtime/TestBasicForCompiler;")
                //        || method.toString().startsWith("Gtest/runtime/TestBasicForCompiler;")
                //        || method.toString().startsWith("Ltest/common/TestBase;")) {  
                    System.out.println("Translating " + method.toString());
                    
                    S3ByteCode translatedCode = Translator
                    .translate((S3ByteCode) method
                            .getCode(S3ByteCode.KIND));
                    method.addCode(translatedCode);
                    
		    //new Inliner2(method).inline();

                    if (dumpOvmIRSJ) {
                        translatedCode.dumpAscii(method.getDeclaringType()
                                .getDomain().toString(), img_ovmir_ascii_sj);
                        img_ovmir_ascii_sj.flush();
                    }
                    
                    if (NativeConstants.OVM_PPC && NativeConstants.OSX_BUILD) {
                        CodeGenerator codegen = makeCodeGenerator2((S3Method) method,
                                InstructionSet.SINGLETON, compilerVMInterface,
                                precomputed, debugPrint);
                        return codegen.compile();
                    } else if (NativeConstants.OVM_X86) {
                        CodeGenerator codegen = makeCodeGenerator((S3Method) method,
                                InstructionSet.SINGLETON, compilerVMInterface,
                                precomputed, debugPrint);
                        return codegen.compile();
                    } else {
                        throw new Error();
                    }
                } else {
                    return null;
                }
            }
        } else {
	    if (dumpOvmIRSJ) {
		((S3ByteCode)method.getCode(S3ByteCode.KIND))
		    .dumpAscii(method.getDeclaringType()
			       .getDomain().toString(), img_ovmir_ascii_sj);
		img_ovmir_ascii_sj.flush();
	    }

            CodeGenerator codegen = makeCodeGenerator((S3Method) method,
                    InstructionSet.SINGLETON, compilerVMInterface, precomputed,
                    debugPrint);
            return codegen.compile();
        }
    }

    protected abstract CodeGenerator makeCodeGenerator
	(S3Method method,
	 InstructionSet is,
	 CompilerVMInterface compilerVMInterface,
	 CodeGenerator.Precomputed precomputed,
	 boolean debugPrintOn);

    protected abstract CodeGenerator makeCodeGenerator2
    (S3Method method,
     InstructionSet is,
     CompilerVMInterface compilerVMInterface,
     CodeGenerator.Precomputed precomputed,
     boolean debugPrintOn);
    
    public abstract String getStackLayoutAsCFunction();


    /* The compile filter */
    private static final SimpleJITCompileFilter compileFilter =
	new SimpleJITAllCompileFilter(); // compile all methods
    //new SimpleJITTestModeCompileFilter(); // compile only TestAbstractInterpretation

    /* Inlining modes */
    private static final int NO_INLINING = 1;
    private static final int PREINLINE = 2;
    private static final int POSTINLINE = 3;
    private static final int DOUBLE_INLINE = 4;

    /* The inlining mode */
    private static final int inliner = NO_INLINING;

    /* The register allocation mode */
    private final static int allocator = 
	SimpleJITRegisterAllocator.ALLOCATOR_LINEAR_PACKING;
        //SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_PACKING;
	//SimpleJITRegisterAllocator.ALLOCATOR_EXACT_GRAPH_COLORING;
	//SimpleJITRegisterAllocator.ALLOCATOR_INCREMENTAL_GRAPH_COLORING;
	//SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_EXACT_GRAPH_COLORING;
	//SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_INCREMENTAL_GRAPH_COLORING;
	//SimpleJITRegisterAllocator.ALLOCATOR_NORMAL_GRAPH_COLORING;


    /* The result write filter */
    private static final SimpleJITResultWriteFilter resultWriteFilter;

    /* The result writer */
    private static SimpleJITRegisterAllocator.ResultWriter resultWriter;

    private static final boolean WRITE_RESULT = false;

    static {
	String result_filename = null;
	String allocatorName = null;
	String inlinerName = null;
	switch(inliner) {
	case NO_INLINING:
	    inlinerName = "NoInlining";
	    if (WRITE_RESULT)
		resultWriteFilter = new SimpleJITAllResultWriteFilter();
	    else
		resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    break;
	case PREINLINE:
	    inlinerName = "Preinlining";
	    if (WRITE_RESULT)
		resultWriteFilter = new SimpleJITInlinedResultWriteFilter();
	    else
		resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    break;
	case POSTINLINE:
	    inlinerName = "Postinlining";
	    if (WRITE_RESULT)
		resultWriteFilter = new SimpleJITInlinedResultWriteFilter();
	    else
		resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    break;
	case DOUBLE_INLINE:
	    inlinerName = "DoubleInlining";
	    resultWriteFilter = new SimpleJITNoResultWriteFilter();
	    break;
	default:
	    throw new Error();
	}
	switch(allocator) {
	case SimpleJITRegisterAllocator.ALLOCATOR_LINEAR_PACKING:
	    allocatorName = "LinearPacking";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_PACKING:
	    allocatorName = "GreedyPacking";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_EXACT_GRAPH_COLORING:
	    allocatorName = "ExactGraphColoring";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_INCREMENTAL_GRAPH_COLORING:
	    allocatorName = "IncrementalGraphColoring";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_INCREMENTAL_GRAPH_COLORING:
	    allocatorName = "GreedyIncrementalGraphColoring";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_GREEDY_EXACT_GRAPH_COLORING:
	    allocatorName = "GreedyExactGraphColoring";
	    break;
	case SimpleJITRegisterAllocator.ALLOCATOR_NORMAL_GRAPH_COLORING:
	    allocatorName = "NormalGraphColoring";
	    if (inliner != NO_INLINING && inliner != PREINLINE)
		throw new Error();
	    break;
	default:
	    throw new Error();
	}
	result_filename = "Result" + allocatorName + inlinerName + ".result";
	if (WRITE_RESULT)
	    resultWriter = new SimpleJITRegisterAllocator
		.ResultWriter(result_filename);
    }

    public SimpleJITIRCode compile2IR(Method method) {
	switch(inliner) {
	case NO_INLINING: {
	    SimpleJITAbstractInterpreter.Driver ai =
		new SimpleJITAbstractInterpreter.Driver();
	    SimpleJITBC2IR.Driver bc2ir =
		new SimpleJITBC2IR.Driver(ai);
	    SimpleJITDataFlowAnalyzer.Driver dfa =
		new SimpleJITDataFlowAnalyzer.Driver(bc2ir);
	    SimpleJITRegisterAllocator.Driver ra = 
		new SimpleJITRegisterAllocator.Driver
		(dfa, 
		 allocator, 
		 compileFilter, 
		 resultWriteFilter,
		 resultWriter);
	    SimpleJITIRCode sjm = 
		ra.run((S3Method)method, 
		       compilerVMInterface);
	    return sjm;
	}
	case POSTINLINE: {
	    SimpleJITAbstractInterpreter.Driver ai =
		new SimpleJITAbstractInterpreter.Driver();
	    SimpleJITBC2IR.Driver bc2ir =
		new SimpleJITBC2IR.Driver(ai);
	    SimpleJITDataFlowAnalyzer.Driver dfa =
		new SimpleJITDataFlowAnalyzer.Driver(bc2ir);
	    SimpleJITRegisterAllocator.Driver ra =
		new SimpleJITRegisterAllocator.Driver
		(dfa, allocator);
	    SimpleJITPostInliner.Driver postInliner =
		new SimpleJITPostInliner.Driver
		(ra, 
		 compileFilter,
		 resultWriteFilter,
		 resultWriter);
	    SimpleJITIRCode sjm = 
		postInliner.run((S3Method)method, 
				compilerVMInterface);
	    return sjm;
	}
	case PREINLINE: {
	    SimpleJITAbstractInterpreter.Driver ai =
		new SimpleJITAbstractInterpreter.Driver();
	    SimpleJITBC2IR.Driver bc2ir =
		new SimpleJITBC2IR.Driver(ai);
	    SimpleJITPreInliner.Driver preInliner =
		new SimpleJITPreInliner.Driver(bc2ir);
	    SimpleJITDataFlowAnalyzer.Driver dfa =
		new SimpleJITDataFlowAnalyzer.Driver(preInliner);
	    SimpleJITRegisterAllocator.Driver ra =
		new SimpleJITRegisterAllocator.Driver
		(dfa, 
		 allocator, 
		 compileFilter, 
		 resultWriteFilter,
		 resultWriter);
	    SimpleJITIRCode sjm = 
		ra.run((S3Method)method, 
		       compilerVMInterface);
	    return sjm;
	}
	case DOUBLE_INLINE: {
	    SimpleJITAbstractInterpreter.Driver ai =
		new SimpleJITAbstractInterpreter.Driver();
	    SimpleJITBC2IR.Driver bc2ir =
		new SimpleJITBC2IR.Driver(ai);
	    SimpleJITPreInliner.Driver preInliner =
		new SimpleJITPreInliner.Driver(bc2ir);
	    SimpleJITDataFlowAnalyzer.Driver dfa =
		new SimpleJITDataFlowAnalyzer.Driver(preInliner);
	    SimpleJITRegisterAllocator.Driver ra =
		new SimpleJITRegisterAllocator.Driver(dfa, allocator);
	    SimpleJITPostInliner.Driver postInliner =
		new SimpleJITPostInliner.Driver(ra, compileFilter);
	    SimpleJITIRCode sjm = 
		postInliner.run((S3Method)method, 
				compilerVMInterface);
	    return sjm;
	}
	default:
	    throw new Error();
	}
    }


} // end of SimpleJIT
