Package org.openquark.cal.internal.machine.lecc

Source Code of org.openquark.cal.internal.machine.lecc.CodeGenerator

/*
* Copyright (c) 2007 BUSINESS OBJECTS SOFTWARE LIMITED
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
*     * Redistributions of source code must retain the above copyright notice,
*       this list of conditions and the following disclaimer.
*
*     * Redistributions in binary form must reproduce the above copyright
*       notice, this list of conditions and the following disclaimer in the
*       documentation and/or other materials provided with the distribution.
*
*     * Neither the name of Business Objects nor the names of its contributors
*       may be used to endorse or promote products derived from this software
*       without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
* POSSIBILITY OF SUCH DAMAGE.
*/


/*
* CodeGenerator.java
* Created: Jan 27, 2003 at 2:16:12 PM
* By: Raymond Cypher
*/
package org.openquark.cal.internal.machine.lecc;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.openquark.cal.compiler.CompilerMessage;
import org.openquark.cal.compiler.CompilerMessageLogger;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.MessageKind;
import org.openquark.cal.compiler.MessageLogger;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.ModuleTypeInfo;
import org.openquark.cal.compiler.Packager;
import org.openquark.cal.compiler.TypeConstructor;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.Expression.PackCons;
import org.openquark.cal.internal.machine.CodeGenerationException;
import org.openquark.cal.internal.machine.MachineFunctionImpl;
import org.openquark.cal.internal.machine.primitiveops.PrimOps;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;
import org.openquark.cal.internal.serialization.ModuleSerializationTags;
import org.openquark.cal.internal.serialization.RecordInputStream;
import org.openquark.cal.internal.serialization.RecordOutputStream;
import org.openquark.cal.internal.serialization.RecordInputStream.RecordHeaderInfo;
import org.openquark.cal.machine.AsynchronousFileWriter;
import org.openquark.cal.machine.MachineFunction;
import org.openquark.cal.machine.Module;
import org.openquark.cal.machine.ProgramResourceLocator;
import org.openquark.cal.machine.ProgramResourceRepository;
import org.openquark.cal.machine.StatusListener;
import org.openquark.cal.services.ResourcePath;
import org.openquark.util.NakedByteArrayOutputStream;


/**
* The CodeGenerator for the LECC 'machine' (compiler back-end)
* <p>
* Creation: Jan. 27, 2003
* @author Raymond Cypher
*/
final class CodeGenerator extends org.openquark.cal.machine.CodeGenerator  {

    /** The namespace for log messages from the LECC machine. */
    static final String MACHINE_LOGGER_NAMESPACE = "org.openquark.cal.internal.runtime.lecc";

    /** An instance of a Logger for LECC machine messages. */
    static final Logger MACHINE_LOGGER = Logger.getLogger(MACHINE_LOGGER_NAMESPACE);

    static {
        MACHINE_LOGGER.setLevel(Level.FINEST);
    }

    /** Indicates that any previously generated code in the target package should be deleted. */
    private final boolean forceCodeRegen;

    /** Indicates that the classes generated will be used immediately. */
    private final boolean forImmediateUse;

    /** The repository for program resources. */
    private final ProgramResourceRepository resourceRepository;

    private final Map<ModuleName, Long> changedModules;

    /** Used to write the class files generated by the lecc CodeGenerator on a separate thread. */
    private AsynchronousFileWriter classFileWriter;

    /**
     * (ModuleName->GeneratedCodeInfo) Map from module name to GeneratedCodeInfo, only for all modules which are generated with immediateUse flag.
     */
    private final Map<ModuleName, GeneratedCodeInfo> immediateUseModuleNameToGeneratedCodeInfoMap = new HashMap<ModuleName, GeneratedCodeInfo>();

    /** The object used for collecting code generation info.  May be null. */
    private final CodeGenerationStats codeGenerationStats = (System.getProperty(LECCMachineConfiguration.CODE_GENERATION_STATS_PROP) != null) ? new CodeGenerationStats() : null;

    /**
     * CodeGenerator constructor.
     * @param forceCodeRegen - indicates that all code should be regenerated
     * @param immediateUse - indicates that generated code should be loaded preemptively
     * @param adjunct - indicates that the code being generated is for an adjunct.
     * @param resourceRepository The repository for program resources.
     */
    CodeGenerator(boolean forceCodeRegen, boolean immediateUse, boolean adjunct, ProgramResourceRepository resourceRepository) {
        super(adjunct);
        this.forceCodeRegen = forceCodeRegen;
        this.forImmediateUse = immediateUse;
        this.changedModules = new HashMap<ModuleName, Long>();
        this.resourceRepository = resourceRepository;
    }

    /**
     * Build up a set of the names of all the modules upon which the given
     * ModuleTypeInfo depends, both directly and indirectly.
     * @param mti
     * @param moduleNames (Set of String) the set module names which will be populated.
     */
    private static void buildDependeeModuleNameSet (ModuleTypeInfo mti, Set<ModuleName> moduleNames) {
        for (int i = 0; i < mti.getNImportedModules(); ++i) {
            ModuleTypeInfo importedModule = mti.getNthImportedModule(i);

            // Check whether this module has already been traversed..
            if (moduleNames.add(importedModule.getModuleName())) {
                buildDependeeModuleNameSet (importedModule, moduleNames);
            }
        }
    }

    /**
     * Generate lecc code for all the supercombinators and data types in the program.
     * @param module
     * @param logger
     * @return CompilerMessage.Severity
     */
    @Override
    public CompilerMessage.Severity generateSCCode (Module module, CompilerMessageLogger logger) {
        if (module instanceof LECCModule) {
            return generateSCCode((LECCModule)module, logger);
        } else {
            throw new IllegalArgumentException("The module is not an instance LECCModule");
        }
    }

    /**
     * Generate lecc code for all the supercombinators and data types in the program.
     * @param module
     * @param logger
     * @return CompilerMessage.Severity
     */
    private CompilerMessage.Severity generateSCCode (LECCModule module, CompilerMessageLogger logger) {
        if (LECCMachineConfiguration.isLeccRuntimeStatic()) {
            return generateSCCodeStatic(module, logger);
        } else {
            return generateSCCodeDynamic(module, logger);
        }
    }

    /**
     * Generate lecc code for all the supercombinators and data types in the program.
     * This is the case for dynamically-generated bytecode
     *   ie. when the classloader asks for the byte array definition of a class, it is generated by the bytecode generator
     *       rather than retrieved from disk.
     *
     * @param module
     * @param logger
     * @return CompilerMessage.Severity
     */
    private CompilerMessage.Severity generateSCCodeDynamic(LECCModule module, CompilerMessageLogger logger) {

        /* ****************************************************************************************
         *  *** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***
         *
         *  *** If this method is changed, generateSCCodeStatic almost certainly needs to be changed.
         *
         *  *** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***
         * ****************************************************************************************/

        if (module == null) {
            throw new IllegalArgumentException("lecc.CodeGenerator.generateSCCode(): module is null");
        }

        CompilerMessageLogger generateLogger = new MessageLogger();

        //System.out.println ("Gencode: " + module.getName());

        // Top level try catch block.  All exceptions/errors should be caught and logged with the logger.

        try {
            // Since this module has been modified we need to tell the class loader to reset.
            // NOTE!  We must do this early so that nothing in the code generation process grabs
            // a reference to a class loader that is discarded at a later point.
            module.resetClassLoader(isForAdjunct());


            // Get module and info
            ModuleName moduleName = module.getName();

            informStatusListeners(StatusListener.SM_GENCODE, moduleName);

            // Retrieve the custom class loader that is stored in the Program object.
            CALClassLoader calLoader = module.getClassLoader();
            if (calLoader == null) {
                // This should never be null.
                throw (new CodeGenerationException ("Null class loader in LECCProgram."));
            }

            // Mark labels as adjuncts, and whether they will have code generated.
            for (final MachineFunction machineFunction : module.getFunctions()) {

                // Get the label.
                MachineFunctionImpl label = (MachineFunctionImpl)machineFunction;

                if (label.isCodeGenerated()) {
                    continue;
                }

                if (label.getAliasOf() != null || label.getLiteralValue() != null) {
                    label.setCodeGenerated(true);
                    //System.out.println("**** Skipping: " + label.getQualifiedName() + "  -> alias of: " + label.getCoreFunction().getAliasOf());
                    continue;
                }

                /*
                 * Mark adjuncts.
                 */
                if (isForAdjunct()) {
                    // Check to see if this is a primitive function.
                    // Functions marked as primitive can be one of two things:
                    // 1) a primitive function implemented as a machine specific operator
                    // 2) a primitive function for which a hard coded machine specific implementation is provided.
                    // If the function falls into category two we don't want to generate anything for it.
                    if (label.isPrimitiveFunction()) {
                        // Check to see if this is considered a primitiv op.
                        if (PrimOps.fetchInfo(label.getQualifiedName()) == null && !label.getName().equals("unsafeCoerce")) {
                            // don't need to generate code.
                            continue;
                        }
                    }

                    // The class to mark as an adjunct.
                    String fullClassName;

                    // This can either be a supercombinator or a data declaration
                    PackCons packCons = label.getExpressionForm().asPackCons();
                    if (packCons != null) {
                        // This is a data declaration
                        TypeConstructor typeCons = packCons.getDataConstructor().getTypeConstructor();

                        if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS && TypeExpr.isEnumType(typeCons)) {
                            //System.out.println ("**** skipping data type because it is enum: " + typeConsName);
                            continue;
                        }

                        fullClassName = CALToJavaNames.createFullClassNameFromType (typeCons, module);

                    } else {
                        // This is a supercombinator.
                        fullClassName = CALToJavaNames.createFullClassNameFromSC(label.getQualifiedName(), module);
                    }

                    // Mark the adjunct.
                    calLoader.markAdjunctClass(fullClassName);
                }

                // Clear the expression form, since we're finished with it.
                label.setCodeGenerated(true);
            }

            informStatusListeners(StatusListener.SM_GENCODE_DONE, module.getName());

            // Write out the compiled module definition if needed.
            if (!isForAdjunct() && !forImmediateUse) {
                writeCompiledModuleInfo (module, generateLogger);
            }

        } catch (Exception e) {
            try {
                if (generateLogger.getNErrors() > 0) {
                    //if an error occurred previously, we continue to compile the program to try to report additional
                    //meaningful compilation errors. However, this can produce spurious exceptions related to the fact
                    //that the program state does not satisfy preconditions because of the initial error(s). We don't
                    //report the spurious exception as an internal coding error.
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Fatal.UnableToRecoverFromCodeGenErrors(module.getName())));
                } else {
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Fatal.CodeGenerationAbortedDueToInternalCodingError(module.getName()), e));
                }
            } catch (CompilerMessage.AbortCompilation ace) {/* Ignore and continue. */}
        } catch (Error e) {
            try {
                generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAbortedWithException(module.getName(), e), null));
            } catch (CompilerMessage.AbortCompilation ace) {/* Ignore and continue. */}
        } finally {
            if (logger != null) {
                // Log messages to the passed-in logger.
                try {
                    logger.logMessages(generateLogger);
                } catch (CompilerMessage.AbortCompilation e) {
                    /* Ignore and continue. */
                }
            }
        }

        return generateLogger.getMaxSeverity();
    }

    /**
     * Generate lecc code for all the supercombinators and data types in the program.
     * This is the case for statically-generated bytecode
     *   ie. when the classloader asks for the byte array definition of a class, it is retrieved from disk,
     *       where it has been previously written by the bytecode generator.
     *
     * @param module
     * @param logger
     * @return CompilerMessage.Severity
     */
    private CompilerMessage.Severity generateSCCodeStatic (LECCModule module, CompilerMessageLogger logger) {

        /* ********************************************************************************
         *  *** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***
         *
         *  *** If this method is changed, generateSCCodeDynamic may also need to be changed.
         *
         *  *** WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING WARNING ***
         * ********************************************************************************/

        if (module == null) {
            throw new IllegalArgumentException("lecc.CodeGenerator.generateSCCode(): module is null");
        }
        ModuleName moduleName = module.getName();

        CompilerMessageLogger generateLogger = new MessageLogger();

        //System.out.println ("Gencode: " + moduleName);


        // Top level try catch block.  All exceptions/errors should be caught and logged with the logger.

        try {
            // Since this module has been modified we need to tell the class loader to reset.
            // NOTE!  We must do this early so that nothing in the code generation process grabs
            // a reference to a class loader that is discarded at a later point.
            module.resetClassLoader(isForAdjunct());


            GeneratedCodeInfo existingCodeInfo = null;
            GeneratedCodeInfo newCodeInfo = getNewCodeInfo(module);

            if (forceCodeRegen && !isForAdjunct() && !LECCMachineConfiguration.generateBytecode()) {
                // If we are generating source code we just do a clean sweep of the target directory.
                // If generating bytecode we will try to do a smarter cleanup after generating.
                try {
                    resourceRepository.delete(getModuleResourceFolder(module));
                    // TODO: actually handle this.
                } catch (IOException ioe) {
                    // There was a problem deleting one or more files.
                    // This used to be just ignored.
                    System.err.println("Problem deleting one or more files: " + ioe);
                }

            } else
            if (!isForAdjunct()){
                // Load the information about any existing generated code.
                if (forImmediateUse) {
                    existingCodeInfo = immediateUseModuleNameToGeneratedCodeInfoMap.get(moduleName);
                }

                if (existingCodeInfo == null) {
                    existingCodeInfo = getExistingCodeInfo(moduleName, resourceRepository);
                }
            }

            // Do we need to generate all sources.
            boolean generateAll = isForAdjunct() || needToGenerateAll (newCodeInfo, existingCodeInfo) || forceCodeRegen;

            // For the moment we assume that the timestamp is constant for all things in a given module.
            if (!isForAdjunct()) {
                long timeStamp = newCodeInfo.cal_source_timeStamp;
                if (existingCodeInfo == null || timeStamp != existingCodeInfo.cal_source_timeStamp) {
                    // The source for this module has been changed.  Add it to the changed module list.
                    changedModules.put (moduleName, new Long(timeStamp));
                    generateAll = true;

                } else {
                    // Check imported modules.
                    ModuleTypeInfo mti = module.getModuleTypeInfo();
                    for (int i = 0; i < mti.getNImportedModules(); ++i) {
                        ModuleTypeInfo imti = mti.getNthImportedModule(i);
                        Long l = changedModules.get(imti.getModuleName());
                        if (l != null && l.longValue() > timeStamp) {
                            generateAll = true;
                            break;
                        }
                    }
                }
            }

            // Get module and info
            informStatusListeners(StatusListener.SM_GENCODE, moduleName);

            JavaGenerator javaGenerator;
            if (LECCMachineConfiguration.generateBytecode()) {

                if (classFileWriter == null) {
                    classFileWriter = resourceRepository.getAsynchronousFileWriter();
                }

                try {
                    javaGenerator = new LECCJavaBytecodeGenerator(module, resourceRepository, isForAdjunct(), forImmediateUse, classFileWriter);

                } catch (IOException e) {
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAbortedWithException(moduleName, e)));
                    return CompilerMessage.Severity.ERROR;
                }


            } else {
                // Generate a list of package names for all the modules associated with this module.
                Set<ModuleName> dependeeModuleNameSet = new HashSet<ModuleName>();
                dependeeModuleNameSet.add(moduleName);

                buildDependeeModuleNameSet(module.getModuleTypeInfo(), dependeeModuleNameSet);

                try {
                    javaGenerator = new LECCJavaSourceGenerator(module, resourceRepository, dependeeModuleNameSet);

                } catch (IOException e) {
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAbortedWithException(moduleName, e)));
                    return CompilerMessage.Severity.ERROR;
                }
            }

            // Pass the status listeners to the java generator.
            javaGenerator.setStatusListeners(getStatusListeners());

            if (codeGenerationStats != null) {
                // Check if this is a module we want to generate stats for.
                String statsModuleNameString = System.getProperty("org.openquark.cal.machine.lecc.code_generation_stats");

                if (statsModuleNameString != null &&
                    (statsModuleNameString.equals("") || statsModuleNameString.equals(moduleName.toSourceText()) || statsModuleNameString.trim().toLowerCase().equals("all"))) {
                    // Set the object used to collect code generation info.
                    javaGenerator.setCodeGenerationStatsCollector(codeGenerationStats);
                    // Inform the stats collector that we're generating stats for new module.
                    codeGenerationStats.startNewModule(moduleName);
                }
            }

            // Retrieve the custom class loader that is stored in the Program object.
            CALClassLoader calLoader = module.getClassLoader();
            if (calLoader == null) {
                // This should never be null.
                throw (new CodeGenerationException ("Null class loader in LECCProgram."));
            }


            // Create a new Set to hold the TypeConstructor instances associated with this module
            Set<TypeConstructor> typeConsSet = new HashSet<TypeConstructor>();

            // Emit supercombinator symbols and collate data definitions
            // For every symbol, we generate its code, which determines what it is, and builds auxiliary entities
            for (final MachineFunction machineFunction : module.getFunctions()) {

                // Get the label.
                MachineFunctionImpl label = (MachineFunctionImpl)machineFunction;

                if (label.isCodeGenerated()) {
                    continue;
                }

                if (label.getAliasOf() != null || label.getLiteralValue() != null) {
                    label.setCodeGenerated(true);
                    //System.out.println("**** Skipping: " + label.getQualifiedName() + "  -> alias of: " + label.getCoreFunction().getAliasOf());
                    continue;
                }

                // Check to see if this is a primitive function.
                // Functions marked as primitive can be one of two things:
                // 1) a primitive function implemented as a machine specific operator
                // 2) a primitive function for which a hard coded machine specific implementation is provided.
                // If the function falls into category two we don't want to generate anything for it.
                if (label.isPrimitiveFunction()) {
                    // Check to see if this is considered a primitiv op.
                    if (PrimOps.fetchInfo(label.getQualifiedName()) == null && !label.getName().equals("unsafeCoerce")) {
                        // don't need to generate code.
                        continue;
                    }
                }

                // This can either be a supercombinator or a data declaration
                if (label.getExpressionForm().asPackCons() != null) {
                    // This is a data declaration
                    // Add the associated TypeConstructor to the set for later code generation.
                    DataConstructor dc = label.getExpressionForm().asPackCons().getDataConstructor();
                    TypeConstructor typeCons = dc.getTypeConstructor();
                    typeConsSet.add(typeCons);
                } else {
                    // This is a supercombinator.  Emit the definition if necessary.
                    try {
                        LECCModule.FunctionGroupInfo fgi = module.getFunctionGroupInfo(label);

                        javaGenerator.createFunction(fgi, generateAll, logger);
                        fgi.setCodeGenerated(true);
                    } catch (CodeGenerationException e) {
                        try {
                            // Note: The code generation could potentially have failed because a foreign type or a foreign function's corresponding Java entity
                            // could not be resolved. (In this case the CodeGenerationException would be wrapping an UnableToResolveForeignEntityException)

                            final Throwable cause = e.getCause();
                            if (cause instanceof UnableToResolveForeignEntityException) {
                                generateLogger.logMessage(((UnableToResolveForeignEntityException)cause).getCompilerMessage());
                            }

                            generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAborted(label.getQualifiedName().getQualifiedName()), e));

                        } catch (CompilerMessage.AbortCompilation e2) {/* Ignore and continue. */}

                        return CompilerMessage.Severity.ERROR;

                    } catch (CompilerMessage.AbortCompilation e) {
                        try {
                            generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAborted(label.getQualifiedName().getQualifiedName())));
                        } catch (CompilerMessage.AbortCompilation e2) {/* Ignore and continue. */}

                        return CompilerMessage.Severity.ERROR;
                    }

                    if (isForAdjunct()) {
                        String fullClassName = CALToJavaNames.createFullClassNameFromSC(label.getQualifiedName(), module);
                        calLoader.markAdjunctClass(fullClassName);
                    }
                }

                // Clear the expression form, since we're finished with it.
                label.setCodeGenerated(true);
            }


            // Emit any data definitions as necessary.
            for (final TypeConstructor typeCons : typeConsSet) {
                if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS && TypeExpr.isEnumType(typeCons)) {
                    //System.out.println ("**** skipping data type because it is enum: " + typeConsName);
                    continue;
                }

                if (isForAdjunct()) {
                    String className = CALToJavaNames.createFullClassNameFromType (typeCons, module);
                    calLoader.markAdjunctClass(className);
                }

                try {
                    javaGenerator.createTypeDefinition(typeCons, generateAll, logger);
                } catch (CodeGenerationException e) {
                    try {
                        // Note: The code generation could potentially have failed because a foreign type or a foreign function's corresponding Java entity
                        // could not be resolved. (In this case the CodeGenerationException would be wrapping an UnableToResolveForeignEntityException)

                        final Throwable cause = e.getCause();
                        if (cause instanceof UnableToResolveForeignEntityException) {
                            generateLogger.logMessage(((UnableToResolveForeignEntityException)cause).getCompilerMessage());
                        }

                        String className = CALToJavaNames.createFullClassNameFromType (typeCons, module);
                        generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAborted(className), e));

                    } catch (CompilerMessage.AbortCompilation e2) {/* Ignore and continue. */}

                    return CompilerMessage.Severity.ERROR;
                }
            }

            informStatusListeners(StatusListener.SM_GENCODE_DONE, moduleName);

            // Now compile the java sources for this module.
            try {
                javaGenerator.wrap();
            } catch (CodeGenerationException e) {
                try {
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.FailedToFinalizeJavaCode(moduleName), e));
                } catch (CompilerMessage.AbortCompilation e2) {/* Ignore and continue. */}

                return CompilerMessage.Severity.ERROR;
            }

            // Update the generated code info map.  Write out the generated code info if it's new.
            if (!isForAdjunct()) {
                if (forImmediateUse) {
                    immediateUseModuleNameToGeneratedCodeInfoMap.put(module.getName(), newCodeInfo);

                } else {
                    immediateUseModuleNameToGeneratedCodeInfoMap.remove(module.getName());
                    if (!newCodeInfo.equals(existingCodeInfo)) {

                        // Get the module folder.  Ensure that it exists.
                        ProgramResourceLocator.Folder moduleFolder = getModuleResourceFolder(module);
                        try {
                            resourceRepository.ensureFolderExists(moduleFolder);

                        } catch (IOException e) {
                            // the folder couldn't be created.
                            generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAbortedWithException(moduleName, e)));
                        }
                    }

                    if (forceCodeRegen && LECCMachineConfiguration.generateBytecode()) {
                        // Delete any extraneous files in the target directory.
                        deleteExtraneousCode(module, javaGenerator.getGeneratedClassFiles());
                    }

                    // write out the compiled module definition.
                    writeCompiledModuleInfo (module, generateLogger);
                }
            }

        } catch (Exception e) {
            try {
                if (generateLogger.getNErrors() > 0) {
                    //if an error occurred previously, we continue to compile the program to try to report additional
                    //meaningful compilation errors. However, this can produce spurious exceptions related to the fact
                    //that the program state does not satisfy preconditions because of the initial error(s). We don't
                    //report the spurious exception as an internal coding error.
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Fatal.UnableToRecoverFromCodeGenErrors(module.getName())));
                } else {
                    generateLogger.logMessage(new CompilerMessage(new MessageKind.Fatal.CodeGenerationAbortedDueToInternalCodingError(module.getName()), e));
                }
            } catch (CompilerMessage.AbortCompilation ace) {/* Ignore and continue. */}
        } catch (Error e) {
            try {
                generateLogger.logMessage(new CompilerMessage(new MessageKind.Error.CodeGenerationAbortedWithException(module.getName(), e), null));
            } catch (CompilerMessage.AbortCompilation e2) {/* Ignore and continue. */}

        } finally {
            // Log messages to the passed-in logger.
            if (logger != null) {
                try {
                    logger.logMessages(generateLogger);
                } catch (CompilerMessage.AbortCompilation e) {
                    /* Ignore and continue. */
                }
            }
        }

        return generateLogger.getMaxSeverity();
    }

    private void writeCompiledModuleInfo (Module module, CompilerMessageLogger logger) {

        // Get the module folder.  Create the folder if it doesn't exist.
        ProgramResourceLocator.Folder moduleFolder = getModuleResourceFolder(module);
        try {
            resourceRepository.ensureFolderExists(moduleFolder);
        } catch (IOException e) {
            logger.logMessage(new CompilerMessage(new MessageKind.Warning.DebugMessage("Failed saving compiled module info for " + module.getName()), e));
            return;
        }

        // Get the info file within that folder.
        ProgramResourceLocator.File compileModuleInfoFileLocator = moduleFolder.extendFile(module.getName() + "." + Module.COMPILED_MODULE_SUFFIX);

        // Put the module info into a byte array.
        NakedByteArrayOutputStream bos = new NakedByteArrayOutputStream(8192);
        RecordOutputStream ros = new RecordOutputStream(bos);

        Exception exception = null;
        try {
            module.write(ros);
        } catch (IOException saveException) {
            exception = saveException;
        } finally {
            try {
                ros.close();
            } catch (IOException ioe) {
                exception = ioe;
            }
        }

        if (exception != null) {
            logger.logMessage(new CompilerMessage(new MessageKind.Warning.DebugMessage("Failed saving compiled module info for " + module.getName()), exception));
        }

        // Create an input stream on the byte array, and use this to set the file contents.
        InputStream is = new ByteArrayInputStream(bos.getByteArray(), 0, bos.getCount());
        try {
            resourceRepository.setContents(compileModuleInfoFileLocator, is);
        } catch (IOException e) {
            logger.logMessage(new CompilerMessage(new MessageKind.Warning.DebugMessage("Failed saving compiled module info for " + module.getName()), e));
        }
    }

    /**
     * @param module a module
     * @return the folder in which resources for that module will be located.
     */
    private ProgramResourceLocator.Folder getModuleResourceFolder(Module module) {
        return CodeGenerator.getModuleResourceFolder(module.getName());
    }

    /**
     * Get the folder in which resources for a module should be located.
     * @param moduleName the name of a module.
     * @return the folder in which resources for that module should be located, with respect to the program resource repository.
     */
    static ProgramResourceLocator.Folder getModuleResourceFolder(ModuleName moduleName) {

        //todoEL is this the right place?
        ProgramResourceLocator.Folder moduleBaseFolder = new ProgramResourceLocator.Folder(moduleName, ResourcePath.EMPTY_PATH);
            return moduleBaseFolder;
    }

    private boolean needToGenerateAll (GeneratedCodeInfo newCodeInfo, GeneratedCodeInfo existingCodeInfo) {
        if (isForAdjunct()) {
            return true;
        }
        if (newCodeInfo != null &&
            existingCodeInfo != null) {

            // When the current runtime is static, any change in code generation options
            // requires a complete regeneration.
            if(LECCMachineConfiguration.isLeccRuntimeStatic()) {
                return existingCodeInfo.isCodeGenerationChanged(newCodeInfo);
            }

            // This method should only be called when the lecc runtime is static
            throw new IllegalStateException("needToGenerateAll should never be called when using the dynamic lecc runtime");
        }

        return false;
    }

    /**
     * Delete any extra files that no longer belong.
     * @param module
     * @param currentFileSet
     */
    private void deleteExtraneousCode (Module module, Set<String> currentFileSet) {

        // Get the module folder and file.
        ProgramResourceLocator.Folder moduleFolder = getModuleResourceFolder(module);

        if (resourceRepository.exists(moduleFolder)) {

            // Iterate over the module members.
            ProgramResourceLocator[] folderMembers = resourceRepository.getMembers(moduleFolder);
            if (folderMembers != null) {

                // Aggregate all resources to delete so that deletion happens in bulk.

                // (Set of ProgramResourceLocator)
                Set<ProgramResourceLocator> deleteSet = new HashSet<ProgramResourceLocator>();

                for (int i = 0; i < folderMembers.length; ++i) {
                    ProgramResourceLocator folderMember = folderMembers[i];
                    String name = folderMembers[i].getName();

                    if (!currentFileSet.contains(name)) {
                        deleteSet.add(folderMember);
                    }
                }

                try {
                    resourceRepository.delete(deleteSet.toArray(new ProgramResourceLocator[deleteSet.size()]));
                    // TODO: actually handle this.
                } catch (IOException ioe) {
                    // There was a problem deleting one or more files.
                    // This used to be just ignored.
                    System.err.println("Problem deleting one or more files: " + ioe);
                }
            }
        }
    }

    private static GeneratedCodeInfo getExistingCodeInfo (ModuleName moduleName, ProgramResourceRepository resourceRepository) {
        ProgramResourceLocator.Folder moduleFolder = CodeGenerator.getModuleResourceFolder(moduleName);
        ProgramResourceLocator.File compileModuleInfoFileLocator = moduleFolder.extendFile(moduleName + "." + Module.COMPILED_MODULE_SUFFIX);

        try {
            InputStream is = resourceRepository.getContents(compileModuleInfoFileLocator);
            if (is != null) {
                RecordInputStream ris = new RecordInputStream(is);

                try {
                    return LECCModule.readLeccSpecificSerializationInfo(ris);

                } finally {
                    try {
                        ris.close();
                    } catch (IOException e) {
                        // Ignore this particular exception
                    }
                }
            }
        } catch(IOException e) {
            // This could happen if, for example, the file doesn't exist

        }

        // If we couldn't successfully load the information from the compiled module info, then
        // return an object initialized with default values.
        return new GeneratedCodeInfo(
                0,
                false,
                false,
                false,
                false,
                false,
                LECCMachineConfiguration.isLeccRuntimeStatic(),
                false,
                Packager.getOptimizerLevel(),
                LECCMachineConfiguration.bytecodeSpaceOptimization(),
                LECCMachineConfiguration.sourcecodeSpaceOptimization(),
                -1);
    }

    /**
     * Build up the generated code info based on the current state.
     * i.e. the info for the code that is being generated now.
     * @param module the module for which to return code info, or null to return a default value.
     * @return the info about the code that is being generated currently.
     */
    static GeneratedCodeInfo getNewCodeInfo (Module module) {
        long timeStamp = -1;
        if (module != null) {
            Iterator<MachineFunction> functions = module.getFunctions().iterator();
            if (functions.hasNext()) {
                MachineFunction machineFunction = functions.next();
                timeStamp = machineFunction.getTimeStamp();
            } else {
                // Empty module.  Fall through.
            }
        }

        return new GeneratedCodeInfo (LECCMachineConfiguration.CODEGEN_VERSION,
                                      LECCMachineConfiguration.generateStatistics(),
                                      LECCMachineConfiguration.generateCallCounts(),
                                      LECCMachineConfiguration.generateDebugCode(),
                                      LECCMachineConfiguration.generateDirectPrimOpCalls(),
                                      LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS,
                                      LECCMachineConfiguration.isLeccRuntimeStatic(),
                                      LECCMachineConfiguration.nonInterruptibleRuntime(),
                                      Packager.getOptimizerLevel(),
                                      LECCMachineConfiguration.bytecodeSpaceOptimization(),
                                      LECCMachineConfiguration.sourcecodeSpaceOptimization(),
                                      timeStamp);
    }

    /**
     * Holds info about previously existing generated source/byte code.
     * @author RCypher
     */
    protected static class GeneratedCodeInfo extends org.openquark.cal.machine.GeneratedCodeInfo {

        private static final int leccGeneratedCodeInfoSerializationSchema = 1;

        /** Code generation scheme version. */
        private final int codeVersion;

        /** Does the generated code include runtime statistics. */
        private final boolean runtime_stats;

        /** Does the generated code include call counting. */
        private final boolean call_counts;

        /** Does the generated code include debug code. */
        private final boolean debug_code;

        /**
         * Does the generated code directly call primitive operators and foreign functions whenever possible
         * or does it indirect through fUnboxed calls.
         */
        private final boolean direct_primop_calls;

        /** The most recent time stamp for the CAL source. */
        private final long cal_source_timeStamp;

        /** Does the generated code ignore strictness annotations. */
        private final boolean ignoring_strictness_annotations;

        /** Are we using a static (ie, stored to disk) runtime */
        private final boolean static_runtime;

        /** Does this runtime check for external halt commands. */
        private final boolean noninterruptable_runtime;

        /** The optimizer level for the generated code. */
        private final int optimizer_level;

        /**
         * If true, the bytecode compiler rewrites its output to insert null assignments, which
         * optimizes for runtime memory usage. Otherwise, no rewriting is done.
         */
        private final boolean bytecode_space_optimization;

        /**
         * If true, the java generator uses RTValue.lastRef() to null out local variables
         * at the point of last reference.
         */
        private final boolean sourcecode_space_optimization;


        GeneratedCodeInfo (int codeVersion,
                           boolean runtime_stats,
                           boolean call_counts,
                           boolean debug_code,
                           boolean direct_primop_calls,
                           boolean ignoring_strictness_annotations,
                           boolean static_runtime,
                           boolean uninterruptable_runtime,
                           int optimizer_level,
                           boolean bytecode_space_optimization,
                           boolean sourcecode_space_optimization,
                           long cal_source_timeStamp) {

            this.codeVersion = codeVersion;
            this.runtime_stats = runtime_stats;
            this.call_counts = call_counts;
            this.debug_code = debug_code;
            this.direct_primop_calls = direct_primop_calls;
            this.ignoring_strictness_annotations = ignoring_strictness_annotations;
            this.static_runtime = static_runtime;
            this.noninterruptable_runtime = uninterruptable_runtime;
            this.optimizer_level = optimizer_level;
            this.bytecode_space_optimization = bytecode_space_optimization;
            this.sourcecode_space_optimization = sourcecode_space_optimization;
            this.cal_source_timeStamp = cal_source_timeStamp;
        }

        boolean isCodeGenerationChanged (GeneratedCodeInfo otherInfo) {
            return codeVersion != otherInfo.codeVersion ||
                   runtime_stats != otherInfo.runtime_stats ||
                   call_counts != otherInfo.call_counts ||
                   debug_code != otherInfo.debug_code ||
                   direct_primop_calls != otherInfo.direct_primop_calls ||
                   ignoring_strictness_annotations != otherInfo.ignoring_strictness_annotations ||
                   static_runtime != otherInfo.static_runtime ||
                   noninterruptable_runtime != otherInfo.noninterruptable_runtime ||
                   bytecode_space_optimization != otherInfo.bytecode_space_optimization ||
                   sourcecode_space_optimization != otherInfo.sourcecode_space_optimization;
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof GeneratedCodeInfo) {
                GeneratedCodeInfo otherCodeInfo = (GeneratedCodeInfo)obj;
                return !isCodeGenerationChanged(otherCodeInfo) && (otherCodeInfo.cal_source_timeStamp == cal_source_timeStamp);
            }
            return false;
        }


        /**
         * Serialize this instance to s
         * @param s RecordOutputStream to serialize to
         * @throws IOException
         */
        void write (RecordOutputStream s) throws IOException {
            s.startRecord(ModuleSerializationTags.LECC_GENERATED_CODE_INFO, leccGeneratedCodeInfoSerializationSchema);
            s.writeInt(codeVersion);
            s.writeBoolean(runtime_stats);
            s.writeBoolean(call_counts);
            s.writeBoolean(direct_primop_calls);
            s.writeBoolean(ignoring_strictness_annotations);
            s.writeBoolean(static_runtime);
            s.writeInt(optimizer_level);
            s.writeLong(cal_source_timeStamp);
            s.writeBoolean(debug_code);
            s.writeBoolean(noninterruptable_runtime);
            s.writeBoolean(bytecode_space_optimization);
            s.writeBoolean(sourcecode_space_optimization);
            s.endRecord();
        }

        /**
         * Load a serialized instance from s
         * @param s RecordOutputStream to deserialize from
         * @return GeneratedCodeInfo deserialized from s
         * @throws IOException
         */
        static GeneratedCodeInfo load(RecordInputStream s) throws IOException {
            RecordHeaderInfo rhi = s.findRecord(ModuleSerializationTags.LECC_GENERATED_CODE_INFO);
            if (rhi == null) {
                throw new IOException ("Unable to find generated module info record.");
            }
            return load(s, rhi.getSchema());
        }

        /**
         * Load a serialized instance from s
         * @param s RecordOutputStream to deserialize from
         * @param schema
         * @return GeneratedCodeInfo deserialized from s
         * @throws IOException
         */
        static GeneratedCodeInfo load(RecordInputStream s, short schema) throws IOException {
            if (schema > leccGeneratedCodeInfoSerializationSchema) {
                throw new IOException ("Loaded schema later than current schema in " + GeneratedCodeInfo.class.getName() + ".load()");
            }

            int codeVersion = s.readInt();
            boolean runtime_stats = s.readBoolean();
            boolean call_counts = s.readBoolean();
            boolean direct_primop_calls = s.readBoolean();
            boolean ignoring_strictness_annotations = s.readBoolean();
            boolean static_runtime = s.readBoolean();
            int optimizer_level = s.readInt();
            long cal_source_timeStamp = s.readLong();
            boolean debug_code = s.readBoolean();
            boolean uninterruptable_runtime = s.readBoolean();
            boolean bytecode_space_optimization = s.readBoolean();

            boolean sourcecode_space_optimization = false;
            if (!s.atEndOfRecord()) {
                sourcecode_space_optimization = s.readBoolean();
            }

            s.skipRestOfRecord();

            return new GeneratedCodeInfo(
                    codeVersion,
                    runtime_stats,
                    call_counts,
                    debug_code,
                    direct_primop_calls,
                    ignoring_strictness_annotations,
                    static_runtime,
                    uninterruptable_runtime,
                    optimizer_level,
                    bytecode_space_optimization,
                    sourcecode_space_optimization,
                    cal_source_timeStamp);
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean isCompatibleWithCurrentConfiguration() {
            return this.codeVersion == LECCMachineConfiguration.CODEGEN_VERSION &&
                this.optimizer_level >= Packager.getOptimizerLevel() &&         // can use optimized generated code with non-optimized current config
                this.runtime_stats == LECCMachineConfiguration.generateStatistics() &&
                this.call_counts == LECCMachineConfiguration.generateCallCounts() &&
                this.noninterruptable_runtime == LECCMachineConfiguration.nonInterruptibleRuntime() &&
                this.debug_code == LECCMachineConfiguration.generateDebugCode() &&
                this.direct_primop_calls == LECCMachineConfiguration.generateDirectPrimOpCalls() &&
                this.ignoring_strictness_annotations == LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS &&
                this.bytecode_space_optimization == LECCMachineConfiguration.bytecodeSpaceOptimization() &&
                this.sourcecode_space_optimization == LECCMachineConfiguration.sourcecodeSpaceOptimization();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public boolean needToRegenerate() {

            // When the current runtime is static, any change in code generation options requires a complete regeneration.
            if (LECCMachineConfiguration.isLeccRuntimeStatic()) {
                return this.static_runtime != true || !isCompatibleWithCurrentConfiguration();
            }

            // When the current runtime is dynamic, only a change in code generation version requires a complete regeneration.
            return this.codeVersion != LECCMachineConfiguration.CODEGEN_VERSION;
        }


        /**
         * @return the static_runtime
         */
        boolean isStaticRuntime() {
            return static_runtime;
        }
    }

    /* (non-Javadoc)
     * @see org.openquark.cal.runtime.CodeGenerator#finishedGeneratingCode()
     */
    @Override
    public void finishedGeneratingCode(CompilerMessageLogger logger) {
        if (classFileWriter != null) {
            //no more files to be added for writing...
            classFileWriter.stopAcceptingFiles();
            try {
                classFileWriter.waitForFilesToBeWritten(logger);
            } catch (InterruptedException ie) {
                //oh well, the files will just have to be written out later...
            }
        }

        if (codeGenerationStats != null) {
            // Dump the code generation stats to the console (i.e. System.out).
            codeGenerationStats.dumpCodeGenerationStatistics();
        }
    }
}
TOP

Related Classes of org.openquark.cal.internal.machine.lecc.CodeGenerator

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.