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