/* * 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(); } } }