/*
* 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.
*/
/*
* SCJavaDefn.java
* Created: Feb 2, 2003
* By: Raymond Cypher
*/
package org.openquark.cal.internal.machine.lecc;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
import java.util.TreeSet;
import org.openquark.cal.compiler.DataConstructor;
import org.openquark.cal.compiler.Expression;
import org.openquark.cal.compiler.ExpressionAnalyzer;
import org.openquark.cal.compiler.FieldName;
import org.openquark.cal.compiler.ForeignFunctionInfo;
import org.openquark.cal.compiler.ForeignTypeInfo;
import org.openquark.cal.compiler.ModuleName;
import org.openquark.cal.compiler.QualifiedName;
import org.openquark.cal.compiler.StringEncoder;
import org.openquark.cal.compiler.TypeConsApp;
import org.openquark.cal.compiler.TypeConstructor;
import org.openquark.cal.compiler.TypeExpr;
import org.openquark.cal.compiler.TypeVar;
import org.openquark.cal.compiler.UnableToResolveForeignEntityException;
import org.openquark.cal.compiler.Expression.DataConsSelection;
import org.openquark.cal.compiler.Expression.FieldValueData;
import org.openquark.cal.compiler.Expression.Switch.SwitchAlt;
import org.openquark.cal.internal.javamodel.JavaClassRep;
import org.openquark.cal.internal.javamodel.JavaExceptionHandler;
import org.openquark.cal.internal.javamodel.JavaExpression;
import org.openquark.cal.internal.javamodel.JavaFieldDeclaration;
import org.openquark.cal.internal.javamodel.JavaModelCopier;
import org.openquark.cal.internal.javamodel.JavaModelTraverser;
import org.openquark.cal.internal.javamodel.JavaOperator;
import org.openquark.cal.internal.javamodel.JavaStatement;
import org.openquark.cal.internal.javamodel.JavaTypeName;
import org.openquark.cal.internal.javamodel.JavaExpression.ArrayCreationExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.Assignment;
import org.openquark.cal.internal.javamodel.JavaExpression.CastExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.ClassInstanceCreationExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.JavaField;
import org.openquark.cal.internal.javamodel.JavaExpression.LiteralWrapper;
import org.openquark.cal.internal.javamodel.JavaExpression.LocalName;
import org.openquark.cal.internal.javamodel.JavaExpression.LocalVariable;
import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation;
import org.openquark.cal.internal.javamodel.JavaExpression.MethodVariable;
import org.openquark.cal.internal.javamodel.JavaExpression.Nameable;
import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression;
import org.openquark.cal.internal.javamodel.JavaExpression.MethodInvocation.InvocationType;
import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression.Binary;
import org.openquark.cal.internal.javamodel.JavaExpression.OperatorExpression.Ternary;
import org.openquark.cal.internal.javamodel.JavaStatement.AssertStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.Block;
import org.openquark.cal.internal.javamodel.JavaStatement.ExpressionStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.IfThenElseStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.LineComment;
import org.openquark.cal.internal.javamodel.JavaStatement.LocalVariableDeclaration;
import org.openquark.cal.internal.javamodel.JavaStatement.ReturnStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.ThrowStatement;
import org.openquark.cal.internal.javamodel.JavaStatement.SwitchStatement.IntCaseGroup;
import org.openquark.cal.internal.machine.BasicOpTuple;
import org.openquark.cal.internal.machine.CodeGenerationException;
import org.openquark.cal.internal.machine.CondTuple;
import org.openquark.cal.internal.machine.ConstructorOpTuple;
import org.openquark.cal.internal.machine.lecc.JavaDefinitionBuilder.SCDefinitionBuilder.LECCLiftedLetVarMachineFunction;
import org.openquark.cal.internal.machine.primitiveops.PrimOps;
import org.openquark.cal.internal.module.Cal.Core.CAL_Prelude_internal;
import org.openquark.cal.internal.runtime.lecc.LECCMachineConfiguration;
import org.openquark.cal.internal.runtime.lecc.RTData;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Boolean;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Byte;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Char;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Double;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Float;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Int;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Integer;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Long;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_Short;
import org.openquark.cal.internal.runtime.lecc.RTData.CAL_String;
import org.openquark.cal.machine.MachineFunction;
import org.openquark.cal.module.Cal.Core.CAL_Prelude;
import org.openquark.cal.runtime.CalValue;
import org.openquark.cal.services.Assert;
import org.openquark.cal.util.ArrayStack;
import org.openquark.util.General;
/**
* An SCJavaDefn represents a supercombinator definition which can have either
* Java source or bytecode emitted.
* @author RCypher
*/
final class SCJavaDefn {
private static final String LITERAL_PREFIX = "$L";
static final String TAIL_RECURSION_LOOP_LABEL = "TRLoop";
static final QualifiedName PRELUDE_SEQ = CAL_Prelude.Functions.seq;
//
// zero-argument methods
//
// RTValue: public RTValue prevArg();
static final MethodInfo PREVARG = new MethodInfo("prevArg", JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
// RTValue: public RTValue getArgValue();
static final MethodInfo GETARGVALUE = new MethodInfo("getArgValue", JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
// RTValue: public RTValue evaluate() throws CALExecutorException;
static final MethodInfo EVALUATE = new MethodInfo("evaluate", JavaTypeNames.RTEXECUTION_CONTEXT, JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
// RTVAlue: public int getOrdinal();
private static final MethodInfo GETORDINALVALUE = new MethodInfo("getOrdinalValue", JavaTypeName.INT, MethodInvocation.InvocationType.VIRTUAL);
//
// single argument methods
//
// RTFunction: public RTValue apply(RTValue argument, RTValue arg2, ...);
private static final MethodInfo APPLY = new MethodInfo("apply", JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
// RTValue: public abstract RTValue setResult (RTValue result) throws RTValue.Exception;
private static final MethodInfo SETRESULT = new MethodInfo("setResult", JavaTypeNames.RTVALUE, JavaTypeName.VOID, InvocationType.VIRTUAL);
/** (Class->Class) CAL Literal class to RTKernel handler class.
* Since this map is only mutated by code in the static initialization block
* it isn't synchronized. If, however, the map is ever mutated by code outside
* the static initialization block it will need to be synchronized.*/
private static final Map<Class<?>, Class<?>> literalMap = new HashMap<Class<?>, Class<?>>();
static {
literalMap.put(Integer.class, CAL_Int.class);
literalMap.put(Double.class, CAL_Double.class);
literalMap.put(Boolean.class, CAL_Boolean.class);
literalMap.put(Byte.class, CAL_Byte.class);
literalMap.put(Short.class, CAL_Short.class);
literalMap.put(Float.class, CAL_Float.class);
literalMap.put(Long.class, CAL_Long.class);
}
/** The expression for the SC body */
private final Expression body;
/** The generated code for the body */
private Block bodyCode = null;
private Block unboxedBodyCode = null;
/** List of ExceptionBlock */
private List<JavaExceptionHandler> exceptionInfo = new ArrayList<JavaExceptionHandler>();
/** The DataConstructor iff this is a constructor, not an SC. */
private final DataConstructor dataConstructor;
/** The module containing the entity this SCJavaDefn object is associated with. */
private final LECCModule module;
private String instanceName;
/** Name of the RTExecutionContext variable in the generated code. NOTE: if this name is changed
* CodeGenerator.CODEGEN_VERSION must be incremented as well.
*/
static final String EXECUTION_CONTEXT_NAME = "$ec";
static final MethodVariable EXECUTION_CONTEXT_VAR = new MethodVariable (SCJavaDefn.EXECUTION_CONTEXT_NAME);
/** The level of nested case statements. */
private int nestedCaseLevel = 0;
/** Flag indicating that the SC associated with this SCJavaDefn has strict arguments */
private boolean hasStrictArguments;
/** boolean flags indicating the strictness of the SC arguments. */
private final boolean argumentStrictness[];
/** The types of the function arguments. Some or all members of this array may be null. */
private final TypeExpr argumentTypes[];
/** The result type of the function. */
private final TypeExpr resultType;
/** The number of arguments for this function */
private final int nArguments;
/** The names of the arguments for this function.
* These are the CAL names produced by the compiler. */
private final String argumentNames[];
/** The Java names for the arguments to this function.
* These are the names that will be used in the generated
* Java code.
*/
private final String javaArgumentNames[];
/** The name of the containing module. */
private final ModuleName currentModuleName;
/** The qualified name of the SC. */
private final String functionName;
private final QualifiedName qualifiedName;
/** True if this is a tail recursive function. */
private boolean isTailRecursive = false;
/** Set of String. Names of strongly connected components. */
private final Set<String> connectedComponents;
/** Flag indicating that this function is foreign. */
private final boolean isForeign;
/** Flag indicating that this function is primitive. */
private final boolean isPrimitive;
private final JavaTypeName thisTypeName;
/** Object to collect code generation info. May be null.*/
private final CodeGenerationStats codeGenerationStats;
/**
* Values shared across multiple SC definitions. Referenced SCs, literal values, etc.
*/
private final SharedValues sharedValues;
private final Map<ReturnStatement, Set<VarInfo>> returnStatementToLocalVars =
new HashMap<ReturnStatement, Set<VarInfo>>();
/**
* Create an SCJavaDefn from a MachineFunction
* @param label
* @param body
* @param module
* @param codeGenerationStats - object to collect code generation info. May be null.
* @param sharedValues
* @throws CodeGenerationException
*/
SCJavaDefn(MachineFunction label,
Expression body,
LECCModule module,
CodeGenerationStats codeGenerationStats,
SharedValues sharedValues) throws CodeGenerationException {
if (label == null || module == null || body == null) {
throw new IllegalArgumentException ("Unable to create SCJavaDefn due to null argument.");
}
if (LECCMachineConfiguration.isLeccRuntimeStatic() && label.isCodeGenerated()) {
throw new CodeGenerationException ("Code already generated for: " + label.getQualifiedName());
}
//this.scar = scar;
this.body = body;
this.module = module;
this.functionName = label.getName();
// R is a pack constructor?
Expression.PackCons packCons = body.asPackCons();
dataConstructor = (packCons != null) ? packCons.getDataConstructor() : null;
this.nArguments = label.getArity();
this.argumentNames = new String[this.nArguments];
System.arraycopy(label.getParameterNames(), 0, this.argumentNames , 0, this.argumentNames.length);
this.javaArgumentNames = new String[this.nArguments];
if (LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS) {
this.argumentStrictness = new boolean [nArguments];
Arrays.fill (this.argumentStrictness, false);
this.hasStrictArguments = false;
} else {
this.argumentStrictness = label.getParameterStrictness();
for (int i = 0; i < argumentStrictness.length; ++i) {
if (argumentStrictness[i]) {
this.hasStrictArguments = true;
break;
}
}
}
this.argumentTypes = label.getParameterTypes();
this.resultType = label.getResultType();
this.currentModuleName = module.getName();
this.isTailRecursive = label.isTailRecursive();
this.connectedComponents = label.getStronglyConnectedComponents();
this.isPrimitive = label.isPrimitiveFunction();
this.isForeign = label.isForeignFunction();
this.thisTypeName = CALToJavaNames.createTypeNameFromSC(QualifiedName.make(getModuleName(), getFunctionName()), module);
this.qualifiedName = QualifiedName.make(getModuleName(), getFunctionName());
this.codeGenerationStats = codeGenerationStats;
this.sharedValues = sharedValues;
}
/**
* @return - true if this SC has strict arguments.
*/
boolean hasStrictArguments() {
return hasStrictArguments;
}
/**
* @return - true if this SC has strict arguments of an unboxable type.
*/
boolean hasStrictUnboxableArguments () throws CodeGenerationException {
if (!hasStrictArguments()) {
return false;
}
for (int i = 0; i < argumentTypes.length; ++i) {
if (SCJavaDefn.canTypeBeUnboxed(argumentTypes[i]) && argumentStrictness[i]) {
return true;
}
}
return false;
}
/**
* @param mf
* @return - true if this SC has strict arguments of an unboxable type.
*/
private boolean hasStrictUnboxableArguments (MachineFunction mf) throws CodeGenerationException {
TypeExpr paramTypes[] = mf.getParameterTypes();
boolean paramStrictness[] = mf.getParameterStrictness();
for (int i = 0, n = paramTypes.length; i < n; ++i) {
if (SCJavaDefn.canTypeBeUnboxed(paramTypes[i]) && paramStrictness[i]) {
return true;
}
}
return false;
}
/**
* @param n
* @return - true if the nth argument is strict.
*/
boolean isArgStrict(int n) {
return argumentStrictness[n];
}
/**
* @param name
* @return true if the named argument is strict.
*/
private boolean isArgStrict (String name) {
for (int i = 0; i < argumentNames.length; ++i) {
if (argumentNames[i].equals(name)) {
return isArgStrict(i);
}
}
return false;
}
/**
* @param i
* @return return the Java name for the ith argument to this function.
*/
String getJavaArgumentName(int i) {
return javaArgumentNames[i];
}
/**
* @return boolean array indicating the strictness of the function arguments.
*/
boolean[] getArgumentStrictness () {
return argumentStrictness;
}
/**
* @param n
* @return boolean indicating the strictness of the nth argument.
*/
boolean getArgumentStrictness (int n) {
return argumentStrictness[n];
}
/**
* Build up a list of KernelLiteral object corresponding to all
* referenced literals in the function body.
* @throws CodeGenerationException
*/
private void buildLiteralsList () throws CodeGenerationException {
List<Expression.Literal> allLiterals = body.getLiterals();
for (final Expression.Literal literal : allLiterals) {
Object literalObject = literal.getLiteral();
// Force creation of the literal value.
sharedValues.addKernelLiteral(literalObject, thisTypeName);
}
}
/**
* The SC scheme deals with entire supercombinators.
* This version generates a function which returns boxed values.
* @return StatementBlock the resulting code contribution (SC body)
* @throws CodeGenerationException
*/
Block genS_SC_Boxed() throws CodeGenerationException {
genS_SC();
return bodyCode;
}
/**
* The SC scheme deals with entire supercombinators.
* This version generates a function which returns unboxed values.
* @return StatementBlock the resulting code contribution (SC body), may be null.
* @throws CodeGenerationException
*/
Block genS_SC_Unboxed() throws CodeGenerationException {
if (!SCJavaDefn.canTypeBeUnboxed(resultType)) {
return null;
}
if (unboxedBodyCode == null) {
// First we need to generate the body code for the boxed case.
genS_SC();
// Now try to do a copy/transformation of the boxed body code.
// This may generate a null pointer exception.
UnboxedReturnCopier copier = new UnboxedReturnCopier(SCJavaDefn.typeExprToTypeName(resultType));
try {
unboxedBodyCode = (JavaStatement.Block)bodyCode.accept(copier, null);
} catch (UnboxingTransformationError e) {
// Unable to transform function body to directly return an unboxed value.
// Fall back on calling fNS.
Block body = new Block();
JavaExpression argValues[] = new JavaExpression[getArity() + 1];
JavaTypeName argTypes[] = new JavaTypeName[argValues.length];
argValues[argValues.length-1] = SCJavaDefn.EXECUTION_CONTEXT_VAR;
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
argTypes[argTypes.length-1] = JavaTypeNames.RTEXECUTION_CONTEXT;
for (int i = 0, n = getArity(); i < n; ++i) {
argValues[i] = new JavaExpression.MethodVariable(getJavaArgumentName(i));
if (isArgStrict(i) && SCJavaDefn.canTypeBeUnboxed(getArgumentType(i))) {
argTypes[i] = typeExprToTypeName(getArgumentType(i));
}
}
LECCModule.FunctionGroupInfo fgi = module.getFunctionGroupInfo(getQualifiedName());
String functionName = fgi.getFnNamePrefix(getFunctionName()) + "f";
if (getArity() > 0) {
functionName = functionName + getArity() + "S";
}
MethodInvocation fNS =
new MethodInvocation.Instance(
null,
functionName,
argValues,
argTypes,
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
LocalVariable result = new LocalVariable("$result", JavaTypeNames.RTVALUE);
LocalVariableDeclaration resultDecl = new LocalVariableDeclaration(result, fNS);
body.addStatement(resultDecl);
for (int i = 0, n = getArity(); i < n; ++i) {
if (!isArgStrict(i) || !SCJavaDefn.canTypeBeUnboxed(getArgumentType(i))) {
// Null out the argument value.
Assignment nullOut =
new Assignment((JavaExpression.Nameable)argValues[i], LiteralWrapper.NULL);
body.addStatement(new ExpressionStatement(nullOut));
}
}
MethodInvocation eval = createInvocation(result, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
JavaExpression unbox = SCJavaDefn.unboxValue(SCJavaDefn.typeExprToTypeName(resultType), eval);
body.addStatement(new ReturnStatement(unbox));
unboxedBodyCode = body;
}
}
return unboxedBodyCode;
}
/**
* @return the JavaTypeName corresponding to the result type of this function.
*/
JavaTypeName getResultType () throws CodeGenerationException {
return SCJavaDefn.typeExprToTypeName(resultType);
}
TypeExpr getResultTypeExpr () {
return resultType;
}
/**
* The SC scheme deals with entire supercombinators.
* @throws CodeGenerationException
*/
private void genS_SC() throws CodeGenerationException {
if (bodyCode == null) {
// Set up the literal values.
buildLiteralsList ();
if (codeGenerationStats != null) {
codeGenerationStats.incrementSCArity(nArguments);
}
bodyCode = new Block(exceptionInfo);
// Generate the root (SC) variable scope
VariableContext variableContext = new VariableContext();
// Add the arguments to the variable scope and deal with strictness, boxing, etc.
for (int i = 0; i < nArguments; ++i) {
// Add each of the arguments to the variable scope.
VarInfo vi = variableContext.addArgument(QualifiedName.make(currentModuleName, argumentNames[i]), isArgStrict(i), getArgumentTypeName(i), getArgumentType(i));
this.javaArgumentNames[i] = vi.getJavaName();
}
// Generate the body contribution
JavaStatement bodyStatement = genS_R(body, variableContext);
// Emit let variables
bodyCode.addStatement(variableContext.popJavaScope());
// Emit the body
bodyCode.addStatement(new LineComment("Top level supercombinator logic"));
bodyCode.addStatement(bodyStatement);
if (LECCMachineConfiguration.sourcecodeSpaceOptimization()) {
bodyCode = (JavaStatement.Block)releaseVars(bodyCode, null);
}
}
}
Block genS_LetVarDef(Scheme scheme, LECCLiftedLetVarMachineFunction mf) throws CodeGenerationException {
if (bodyCode == null) {
// Set up the literal values.
buildLiteralsList ();
if (codeGenerationStats != null) {
codeGenerationStats.incrementSCArity(nArguments);
}
Expression methodBody = body;
// If the body of this 'function' is an application of Prelude.eager' we want
// to strip it off and treat this as strict.
Expression eagerStripped = stripEager(methodBody);
if (eagerStripped != methodBody) {
methodBody = eagerStripped;
if (scheme == Scheme.C_SCHEME) {
scheme = Scheme.E_SCHEME;
}
}
bodyCode = new Block(exceptionInfo);
// Generate the root (SC) variable scope
VariableContext variableContext = new VariableContext();
// Add the arguments to the variable scope and deal with strictness, boxing, etc.
for (int i = 0; i < nArguments; ++i) {
// Add each of the arguments to the variable scope.
VarInfo vi = variableContext.addArgument(QualifiedName.make(currentModuleName, argumentNames[i]), isArgStrict(i), getArgumentTypeName(i), getArgumentType(i));
this.javaArgumentNames[i] = vi.getJavaName();
}
if (canIgnoreLaziness(body, variableContext)) {
mf.setIgnoreLaziness(true);
}
Expression expressions[] = flattenTopLevelSeq(methodBody);
if (expressions != null && (scheme == Scheme.E_SCHEME || scheme == Scheme.UNBOX_INTERNAL_SCHEME)) {
for (int i = 0, n = expressions.length - 1; i < n; ++i) {
JavaStatement js = makeStrictStatementFromSeqArg(expressions[i], variableContext);
bodyCode.addStatement(js);
}
methodBody = expressions[expressions.length - 1];
}
ExpressionContextPair ecp;
if (scheme == Scheme.E_SCHEME) {
ecp = genS_E(methodBody, variableContext);
} else
if (scheme == Scheme.C_SCHEME) {
ecp = genS_C(methodBody, variableContext);
} else
if (scheme == Scheme.UNBOX_INTERNAL_SCHEME) {
ecp = generateUnboxedArgument(typeExprToTypeName(resultType), methodBody, variableContext);
} else {
throw new CodeGenerationException("Invalid compilation scheme for let variable definition.");
}
// Emit let variables
bodyCode.addStatement(variableContext.popJavaScope());
bodyCode.addStatement(ecp.getContextBlock());
if (ecp.getJavaExpression() != null) {
bodyCode.addStatement(new JavaStatement.ReturnStatement(ecp.getJavaExpression()));
}
if (LECCMachineConfiguration.sourcecodeSpaceOptimization()) {
bodyCode = (JavaStatement.Block)releaseVars(bodyCode, null);
}
}
return bodyCode;
}
/**
* This method attempts to generate code to produce an unboxed value
* by calling the fUnboxednS method of a supercombinator.
* @param e
* @param unboxedType
* @param scheme
* @param variableContext
* @return The generated code if successful, null otherwise.
* @throws CodeGenerationException
*/
private ExpressionContextPair buildUnboxedDirectCall (
Expression e,
JavaTypeName unboxedType,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
if (!LECCMachineConfiguration.DIRECT_UNBOXED_RETURNS) {
return null;
}
// Is e a call to a let variable definition function?
ExpressionContextPair letVarECP = buildLetVarDefCall(e, Scheme.UNBOX_INTERNAL_SCHEME, variableContext);
if (letVarECP != null) {
return letVarECP;
}
// We have to be dealing with an application.
if (e.asAppl() == null) {
return null;
}
// Break the application chain down into its components.
Expression[] chain = appChain (e.asAppl());
if (chain[0].asVar() == null) {
return null;
}
// If the function is a supercombinator we can potentially optimize the
// representation.
Expression.Var var = (Expression.Var)chain[0];
if (var.getDataConstructor() != null) {
return null;
}
if (variableContext.isLocalVariable(var.getName())) {
return null;
}
// Try to collect information about the called function.
// This is used to decide how to encode the function call.
MachineFunction mf = module.getFunction(var.getName());
if (mf == null) {
return null;
}
// Only functions with an unboxable return type implement an
// fUnboxed method.
if (!SCJavaDefn.canTypeBeUnboxed(mf.getResultType())) {
return null;
}
// We only generate fUnboxed methods under the same conditions we generate the fNL and fNS methods.
boolean generateFnMethods = mf.getArity() > 0
&& (mf.getArity() <= LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH
|| mf.isTailRecursive() || hasStrictUnboxableArguments(mf));
if (!generateFnMethods) {
return null;
}
// It must be a fully saturated application.
if (mf.getArity() != chain.length -1 || mf.getArity() == 0) {
return null;
}
// We don't 'unbox' our internal type.
JavaTypeName calledReturnType = SCJavaDefn.typeExprToTypeName(mf.getResultType());
if (calledReturnType.equals(JavaTypeNames.RTVALUE) ||
calledReturnType.equals(JavaTypeName.CAL_VALUE)) {
return null;
}
// If the primitiveness of the called function result type is not the same
// as the primitiveness of the desired unboxed type we can't do a direct call.
verifyUnboxType(unboxedType, calledReturnType);
// Get information about the arguments of the called function.
boolean[] calledArgStrictness = mf.getParameterStrictness();
TypeExpr[] calledArgTypes = mf.getParameterTypes();
int calledArity = mf.getArity();
if (!canCallDirectlyOnJavaStack (chain[0].asVar(), calledArgTypes, scheme)) {
return null;
}
// First generate the java expressions for the argument values.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 0; i < chain.length; ++i) {
if (i > 0 && calledArgStrictness[i-1]) {
if (SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i-1])) {
ecp[i] = generateUnboxedArgument(typeExprToTypeName(calledArgTypes[i-1]), chain[i], variableContext);
} else {
ecp[i] = genS_E(chain[i], variableContext);
}
} else {
ecp[i] = genS_C(chain[i], variableContext);
}
}
// Consolidate the context of the arguments.
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
// Get the supercombinator we're calling.
JavaExpression root = ecp[0].getJavaExpression();
if (this.codeGenerationStats != null) {
if (var.getForeignFunctionInfo() != null) {
this.codeGenerationStats.incrementDirectForeignCalls();
} else {
this.codeGenerationStats.incrementDirectSCCalls();
}
}
// Optimize Prelude.not to use the '!' java operator.
if (var.getName().equals(CAL_Prelude.Functions.not)) {
JavaExpression not = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, ecp[1].getJavaExpression());
return new ExpressionContextPair (not, newContext);
}
JavaExpression[] args = new JavaExpression[calledArity + 1];
for (int i = 0; i < calledArity; ++i) {
args[i] = ecp[i + 1].getJavaExpression();
}
args[calledArity] = EXECUTION_CONTEXT_VAR;
// Get the supercombinator class and do a direct application of the fn function.
// arg types are all RTValue except when strict
JavaTypeName[] argTypes = new JavaTypeName[args.length];
for (int i = 0; i < calledArgTypes.length; ++i) {
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
argTypes[i] = SCJavaDefn.typeExprToTypeName(calledArgTypes[i]);
} else {
argTypes[i] = JavaTypeNames.RTVALUE;
}
}
argTypes[calledArity] = JavaTypeNames.RTEXECUTION_CONTEXT;
root = expressionVarToJavaDef(var, Scheme.E_SCHEME, variableContext);
// Get the method name to call.
LECCModule.FunctionGroupInfo fgi = module.getFunctionGroupInfo(var.getName());
String functionName = fgi.getFnNamePrefix(var.getName().getUnqualifiedName()) + "fUnboxed";
if (calledArity > 0) {
functionName = functionName + calledArity + "S";
}
root = new MethodInvocation.Instance(root, functionName, args, argTypes, calledReturnType, InvocationType.VIRTUAL);
return new ExpressionContextPair(root, newContext);
}
/**
* This method determines if an expression can be compiled strictly
* even though it is in a lazy context.
* @param e
* @param variableContext
* @return whether the expression can be compiled strictly.
* @throws CodeGenerationException
*/
private boolean canIgnoreLaziness (Expression e, VariableContext variableContext) throws CodeGenerationException {
if (!LECCMachineConfiguration.OPTIMIZE_LAZY_SUPERCOMBINATORS) {
return false;
}
// Basically we return true for anthing that is already at WHNF.
if (e.asLiteral() != null) {
return true;
}
// If a var is a non-zero arity SC, a DC, or is already evaluated we can shortcut it.
if (e.asVar() != null) {
Expression.Var var = e.asVar();
if (var.getDataConstructor() != null) {
return true;
}
if (variableContext.isVarPreEvaluated(var.getName())) {
return true;
}
if (!variableContext.isLocalVariable(var.getName())) {
// This is an SC
MachineFunction mf = module.getFunction(var.getName());
if (mf != null && mf.getArity() > 0) {
return true;
}
}
}
// We can shortcut a basic op if it is marked as eager
// arguments can all be shortcut.
// Also if the op is Prelude.eager we can shortcut.
BasicOpTuple basicOpExpressions = BasicOpTuple.isBasicOp(e);
if (basicOpExpressions != null) {
if (basicOpExpressions.getPrimitiveOp() == PrimOps.PRIMOP_EAGER) {
return true;
}
QualifiedName opName = basicOpExpressions.getName();
MachineFunction mf = module.getFunction(opName);
if (mf != null && mf.canFunctionBeEagerlyEvaluated()) {
int nArgs = basicOpExpressions.getNArguments();
int nWHNFArgs = 0;
for (int i = 0; i < nArgs; ++i) {
Expression eArg = basicOpExpressions.getArgument(i);
if (canIgnoreLaziness(eArg, variableContext)) {
nWHNFArgs++;
}
}
if (nArgs == nWHNFArgs) {
String unqualifiedOpName = opName.getUnqualifiedName();
if (opName.getModuleName().equals(CAL_Prelude.MODULE_NAME) &&
(unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideLong.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderLong.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideInt.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderInt.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideShort.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderShort.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideByte.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderByte.getUnqualifiedName()))) {
// Check that the second argument is a non zero literal.
Expression arg = basicOpExpressions.getArgument(1);
if (arg.asLiteral() != null) {
if (unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideLong.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderLong.getUnqualifiedName())) {
Long l = (Long)arg.asLiteral().getLiteral();
return l.longValue() != 0;
} else if (unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideInt.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderInt.getUnqualifiedName())) {
Integer i = (Integer)arg.asLiteral().getLiteral();
return i.intValue() != 0;
} else if (unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideShort.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderShort.getUnqualifiedName())) {
Short shortValue = (Short)arg.asLiteral().getLiteral();
return shortValue.shortValue() != 0;
} else if (unqualifiedOpName.equals(CAL_Prelude_internal.Functions.divideByte.getUnqualifiedName()) ||
unqualifiedOpName.equals(CAL_Prelude_internal.Functions.remainderByte.getUnqualifiedName())) {
Byte byteValue = (Byte)arg.asLiteral().getLiteral();
return byteValue.byteValue() != 0;
} else {
throw new IllegalStateException();
}
} else {
return false;
}
} else {
return true;
}
} else {
return false;
}
}
}
basicOpExpressions = BasicOpTuple.isAndOr (e);
if (basicOpExpressions != null) {
// Code a basic operation
int nArgs = basicOpExpressions.getNArguments ();
int nWHNFArgs = 0;
for (int i = 0; i < nArgs; ++i) {
Expression eArg = basicOpExpressions.getArgument(i);
if (canIgnoreLaziness(eArg, variableContext)) {
nWHNFArgs++;
}
}
if (nArgs == nWHNFArgs) {
return true;
}
}
// If e is a fully saturated application of a function tagged for optimization and
// all the arguments can be shortcutted we can shortcut the application.
if (e.asAppl() != null) {
Expression[] chain = appChain(e.asAppl());
if (chain[0].asVar() != null) {
// Get the supercombinator on the left end of the chain.
Expression.Var scVar = chain[0].asVar();
if (scVar != null) {
// Check if this supercombinator is one we should try to optimize.
MachineFunction mf = module.getFunction(scVar.getName());
if (mf != null && mf.canFunctionBeEagerlyEvaluated()) {
// Now determine the arity of the SC.
int calledArity = mf.getArity();
// Check to see if we can shortcut all the arguments.
if (chain.length - 1 == calledArity) {
int nWHNFArgs = 0;
for (int i = 0; i < calledArity; ++i) {
if (canIgnoreLaziness(chain[i+1], variableContext)) {
nWHNFArgs++;
}
}
if (nWHNFArgs == calledArity) {
return true;
}
}
}
}
}
}
// Is e an application of a zero arity constructor.
if (ConstructorOpTuple.isConstructorOp(e, true) != null) {
ConstructorOpTuple constructorOpExpressions = ConstructorOpTuple.isConstructorOp(e, false);
DataConstructor dc = constructorOpExpressions.getDataConstructor ();
if (dc.getArity() == 0){
return true;
}
}
// Is e a DataConsFieldSelection where the data constructor expression can be shortcutted and
// the field is strict.
if (e.asDataConsSelection() != null) {
Expression.DataConsSelection dcs = (Expression.DataConsSelection)e;
if (dcs.getDataConstructor().isArgStrict(dcs.getFieldIndex()) && canIgnoreLaziness(dcs.getDCValueExpr(), variableContext)) {
return true;
}
}
// 'if a then b else c' where a, b, and c can all be shortcut.
CondTuple conditionExpressions = CondTuple.isCondOp(e);
if (conditionExpressions != null) {
Expression condExpr = conditionExpressions.getConditionExpression();
Expression thenExpr = conditionExpressions.getThenExpression();
Expression elseExpr = conditionExpressions.getElseExpression();
if (canIgnoreLaziness(condExpr, variableContext) &&
canIgnoreLaziness(thenExpr, variableContext) &&
canIgnoreLaziness(elseExpr, variableContext)) {
return true;
}
}
// We can compile a record extension strictly if the base record is null or
// laziness can be ignored for the base record. This is safe because while
// strict compilation generates code that creates a new record object none
// of the fields will be compiled differently, thus preserving laziness.
if (e.asRecordExtension() != null) {
Expression.RecordExtension re = (Expression.RecordExtension)e;
return re.getBaseRecordExpr() == null || canIgnoreLaziness(re.getBaseRecordExpr(), variableContext);
}
// We can compile a record update strictly if we can ignore laziness for the base
// record. This is safe because while
// strict compilation generates code that creates a new record object none
// of the fields will be compiled differently, thus preserving laziness.
if (e.asRecordUpdate() != null) {
Expression.RecordUpdate ru = (Expression.RecordUpdate)e;
return canIgnoreLaziness(ru.getBaseRecordExpr(), variableContext);
}
///////////////////
// Note: we can't ignore laziness for a record selection/case even if
// laziness can be ignored for the base record expression since strict
// compilation would force the evaluation of the field value and change
// laziness.
// However there is a less general optimization that can be applied in this
// situations. See generateLazyRecordSelection().
return false;
}
/**
* The C scheme generates code to construct an instance of the given expression.
* @param e expression of regular kind
* @param variableContext
* @return the resulting code contribution
* @throws CodeGenerationException
*/
private ExpressionContextPair genS_C(Expression e, VariableContext variableContext) throws CodeGenerationException {
if (canIgnoreLaziness(e, variableContext)) {
return genS_E(e, variableContext);
}
// e is a literal?
Expression.Literal lit = e.asLiteral();
if (lit != null) {
return new ExpressionContextPair(getKernelLiteral(lit.getLiteral()).getBoxedReference());
}
// Is e a call to a let variable definition function?
ExpressionContextPair letVarECP = buildLetVarDefCall(e, Scheme.C_SCHEME, variableContext);
if (letVarECP != null) {
return letVarECP;
}
// There is one primitive operation that gets special treatment in the lazy compilation context.
// This is Prelude.eager. Even in a lazy context eager is evaluated immediately.
BasicOpTuple bop = BasicOpTuple.isBasicOp(e);
if (bop != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementLazyPrimOps (bop.getName());
}
if (bop.getPrimitiveOp() == PrimOps.PRIMOP_EAGER) {
if (LECCMachineConfiguration.generateDirectPrimOpCalls()) {
return generatePrimitiveOp(e, true, Scheme.C_SCHEME, variableContext);
} else {
//we don't directly call primitive operators when doing function tracing.
//This will have the effect of ensuring that they get traced when called.
return genS_E(e, variableContext);
}
}
}
// Is e an application of a saturated constructor?
ConstructorOpTuple constructorOpTuple = ConstructorOpTuple.isConstructorOp(e, false);
if (constructorOpTuple != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementFullySaturatedDCInLazyContext(constructorOpTuple.getDataConstructor().getName());
}
return generateDataConstructor(constructorOpTuple, Scheme.C_SCHEME, variableContext);
}
// e is a variable?
if (e.asVar() != null) {
Expression.Var var = e.asVar();
JavaExpression varExpression = expressionVarToJavaDef(var, Scheme.C_SCHEME, variableContext);
return new ExpressionContextPair (varExpression);
}
// With changes to the compiler to lift inner case statements to their own function we
// should never encounter a case at this level.
// e is a case/switch?
if (e.asSwitch() != null) {
throw new CodeGenerationException ("Encountered a case statement at an inner level. schemeC.");
}
// Similarly, we should never encounter a data constructor field selection.
if (e.asDataConsSelection() != null) {
return generateDCFieldSelection(e.asDataConsSelection(), Scheme.C_SCHEME, variableContext);
//throw new CodeGenerationException ("Encountered a data constructor field selection at an inner level. schemeC.");
}
// e is a let var.
if (e.asLetNonRec() != null) {
return generateLetNonRec(e.asLetNonRec(), Scheme.C_SCHEME, null, variableContext);
}
if (e.asLetRec() != null) {
return generateLetRec(e.asLetRec(), Scheme.C_SCHEME, variableContext);
}
// e is an application?
Expression.Appl appl = e.asAppl();
if (appl != null) {
ExpressionContextPair ecp = buildApplicationChain (appl, Scheme.C_SCHEME, variableContext);
if (ecp == null) {
// This is a general application
ExpressionContextPair target = genS_C(appl.getE1(), variableContext);
ExpressionContextPair arg = genS_C(appl.getE2(), variableContext);
// Add the contexts for the target and the arg.
Block newContextBlock = target.getContextBlock();
newContextBlock.addStatement(arg.getContextBlock());
ecp = new ExpressionContextPair(createInvocation(target.getJavaExpression(), APPLY, arg.getJavaExpression()), newContextBlock);
}
return ecp;
}
//e is a record update
Expression.RecordUpdate recordUpdate = e.asRecordUpdate();
if (recordUpdate != null) {
return generateLazyRecordUpdate(recordUpdate, variableContext);
}
// e is a record extension
Expression.RecordExtension recordExtension = e.asRecordExtension();
if (recordExtension != null) {
return generateLazyRecordExtension(recordExtension, variableContext);
}
// e is a record selection
Expression.RecordSelection recordSelection = e.asRecordSelection();
if (recordSelection != null) {
return generateLazyRecordSelection(recordSelection, variableContext);
}
// e is an error info
Expression.ErrorInfo errorInfo = e.asErrorInfo();
if (errorInfo != null){
return new ExpressionContextPair(createMakeKernelOpaqueInvocation(getErrorInfo(errorInfo)));
}
// e is a record case
Expression.RecordCase recordCase = e.asRecordCase();
if (recordCase != null) {
throw new CodeGenerationException ("Encountered a record-case statement at an inner level. schemeC.");
}
throw new CodeGenerationException ("Unhandled expression of type " + e.getClass().getName() + " encountered in C scheme.");
}
/**
* The E scheme generates code to evaluate the given expression.
* @param e expression of regular kind
* @param variableContext
* @return the resulting code contribution
* @throws CodeGenerationException
*/
private ExpressionContextPair genS_E(Expression e, VariableContext variableContext) throws CodeGenerationException {
// Strip off an application of Prelude.eager and compile the argument strictly.
e = stripEager(e);
// R is a literal?
Expression.Literal lit = e.asLiteral();
if (lit != null) {
return new ExpressionContextPair(getKernelLiteral(lit.getLiteral()).getBoxedReference());
}
// Is e a call to a let variable definition function?
ExpressionContextPair letVarECP = buildLetVarDefCall(e, Scheme.E_SCHEME, variableContext);
if (letVarECP != null) {
return letVarECP;
}
// Is e a primitive operation (arithmetic, comparative etc.)?
{
BasicOpTuple bop = BasicOpTuple.isBasicOp(e);
if (!LECCMachineConfiguration.generateDirectPrimOpCalls()) {
// When we are generating call chains we want to force all primitive operations
// to be done as a function call to the generated function.
//When we have function tracing enabled, we want to force all primitive operations to be
//done as function calls. This will have the effect of ensuring that they get traced when called.
if (bop != null && !bop.getName().equals(getQualifiedName())) {
bop = null;
}
}
if (bop != null) {
return generatePrimitiveOp(e, true, Scheme.E_SCHEME, variableContext);
}
}
if (isNot(e)) {
return generateNot (e.asAppl(), true, variableContext);
}
// Is e an application of 'and' or 'or'? Test only - don't get tuple
if (BasicOpTuple.isAndOr(e) != null) {
return generateAndOr(e, true, variableContext);
}
// Is e an application of a saturated constructor?
ConstructorOpTuple constructorOpTuple = ConstructorOpTuple.isConstructorOp(e, false);
if (constructorOpTuple != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementFullySaturatedDCInStrictContext(constructorOpTuple.getDataConstructor().getName());
}
return generateDataConstructor(constructorOpTuple, Scheme.E_SCHEME, variableContext);
}
// e is a variable?
if (e.asVar() != null) {
Expression.Var var = e.asVar();
JavaExpression varExpression = expressionVarToJavaDef(var, Scheme.E_SCHEME, variableContext);
if (variableContext.isLocalVariable(var.getName())) {
return new ExpressionContextPair (varExpression );
} else {
// this is either a data constructor or an SC.
if (var.getDataConstructor() != null || (var.getForeignFunctionInfo() != null && SCJavaDefn.getNArguments(var.getForeignFunctionInfo()) > 0)) {
return new ExpressionContextPair(varExpression);
}
MachineFunction varFunction = module.getFunction(var.getName());
if (varFunction != null && varFunction.getArity() > 0) {
// This is an non-CAF SC and can't be resolved any further.
return new ExpressionContextPair(varExpression);
} else {
// We are in a strict context so we want to evaluate the variable
return new ExpressionContextPair(createInvocation(varExpression, EVALUATE, EXECUTION_CONTEXT_VAR));
}
}
}
// Is e a conditional op (if <cond expr> <then expr> <else expr>)?
CondTuple conditionExpressions = CondTuple.isCondOp(e);
if (conditionExpressions != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementOptimizedIfThenElse();
}
// This is a conditional op. The conditionExpressions tuple holds (kCond, kThen, kElse) expressions
// Generate the code for kThen and kElse, as arguments to a new I_Cond instruction
Expression condExpression = conditionExpressions.getConditionExpression();
ExpressionContextPair pair;
pair = generateUnboxedArgument(JavaTypeName.BOOLEAN, condExpression, variableContext);
Block contextBlock = pair.getContextBlock();
JavaExpression condJavaExpression = pair.getJavaExpression();
ExpressionContextPair thenPart = genS_E (conditionExpressions.getThenExpression(), variableContext);
ExpressionContextPair elsePart = genS_E (conditionExpressions.getElseExpression(), variableContext);
// Note: both contexts are evaluated..
contextBlock.addStatement(thenPart.getContextBlock());
contextBlock.addStatement(elsePart.getContextBlock());
OperatorExpression ternaryExpression = new OperatorExpression.Ternary(condJavaExpression, new JavaExpression.CastExpression(JavaTypeNames.RTVALUE, thenPart.getJavaExpression()), elsePart.getJavaExpression());
return new ExpressionContextPair(ternaryExpression, contextBlock);
}
// With the changes to the compiler to lift inner cases into their own function
// we should never encounte a case at this level.
// Expression is a case/switch?
if (e.asSwitch() != null) {
throw new CodeGenerationException ("Encountered a case statement that wasn't at top level in schemeE.");
}
// Similarly, we should never encounter a data constructor field selection.
if (e.asDataConsSelection() != null) {
return generateDCFieldSelection(e.asDataConsSelection(), Scheme.E_SCHEME, variableContext);
//throw new CodeGenerationException ("Encountered a data constructor field selection at an inner level. schemeE.");
}
// R is a let var.
if (e.asLetNonRec() != null) {
return generateLetNonRec (e.asLetNonRec(), Scheme.E_SCHEME, null, variableContext);
}
if (e.asLetRec() != null) {
return generateLetRec (e.asLetRec(), Scheme.E_SCHEME, variableContext);
}
// R is an application?
Expression.Appl appl = e.asAppl();
if (appl != null) {
ExpressionContextPair ecp = buildApplicationChain (appl, Scheme.E_SCHEME, variableContext);
if (ecp == null) {
// This is a general application
ExpressionContextPair target = genS_C(appl.getE1(), variableContext);
ExpressionContextPair arg = genS_C(appl.getE2(), variableContext);
Block newContextBlock = target.getContextBlock();
newContextBlock.addStatement(arg.getContextBlock());
MethodInvocation applyMI = createInvocation(target.getJavaExpression(), APPLY, arg.getJavaExpression());
MethodInvocation evaluateMI = createInvocation(applyMI, EVALUATE, EXECUTION_CONTEXT_VAR);//, ExecutionContextClassName));
ecp = new ExpressionContextPair(evaluateMI, newContextBlock);
}
return ecp;
}
//e is a record update
Expression.RecordUpdate recordUpdate = e.asRecordUpdate();
if (recordUpdate != null) {
return generateStrictRecordUpdate(recordUpdate, variableContext);
}
// e is a record extension
Expression.RecordExtension recordExtension = e.asRecordExtension();
if (recordExtension != null) {
return generateStrictRecordExtension(recordExtension, variableContext);
}
// e is a record selection
Expression.RecordSelection recordSelection = e.asRecordSelection();
if (recordSelection != null) {
return generateStrictRecordSelection(recordSelection, true, variableContext);
}
// e is an error info
Expression.ErrorInfo errorInfo = e.asErrorInfo();
if (errorInfo != null){
return new ExpressionContextPair(createMakeKernelOpaqueInvocation(getErrorInfo(errorInfo)));
}
// e is a record case
Expression.RecordCase recordCase = e.asRecordCase();
if (recordCase != null) {
throw new CodeGenerationException ("Encountered a record-case statement at an inner level. schemeE.");
}
return new ExpressionContextPair(LiteralWrapper.make("Can't handle this expression."));
}
/**
* Helper method for generating return statements.
* This is the method that should be used to build any return
* statements for the main function body.
* Generate a return statement from an ExpressionContextPair.
* This will simply be the context block from the pair, with an additional
* statement at the end "return"ing the pair's expression.
* Note: This method correctly deals with setting the result value
* before returning when dealing with a CAF.
*
* @param pair the ExpressionContextPair
* @param context - current variable context. May be null.
* @return the resulting java statement
*/
private JavaStatement generateReturn(ExpressionContextPair pair, VariableContext context) {
Block contextBlock = pair.getContextBlock();
JavaExpression javaExpression = pair.getJavaExpression();
if (javaExpression != null) {
ReturnStatement ret = new ReturnStatement(javaExpression);
if (context != null) {
Set<VarInfo> vars = new HashSet<VarInfo>();
Map<QualifiedName, VarInfo> scope = context.getCurrentScope();
for (final Map.Entry<QualifiedName, VarInfo> entry : scope.entrySet()) {
vars.add(entry.getValue());
}
returnStatementToLocalVars.put(ret, vars);
}
contextBlock.addStatement(ret);
}
return contextBlock;
}
/**
* Helper method for generating return statements.
* This method should be used for generating any return statements in
* the main function body.
* Generate a return statement from a java expression. This method deals
* with setting the result before returning when dealing with a CAF.
* @param javaExpression
* @param context - current variable context. May be null.
* @return the return statement
*/
private JavaStatement generateReturn(JavaExpression javaExpression, VariableContext context) {
ReturnStatement ret = new ReturnStatement(javaExpression);
if (context != null) {
Set<VarInfo> vars = new HashSet<VarInfo>();
Map<QualifiedName, VarInfo> scope = context.getCurrentScope();
for (final Map.Entry<QualifiedName, VarInfo> entry : scope.entrySet()) {
vars.add(entry.getValue());
}
returnStatementToLocalVars.put(ret, vars);
}
return ret;
}
/**
* If e is an application of Prelude.eager strip
* of eager and return the argument.
* @param e
* @return the argument to Prelude.eager
*/
private Expression stripEager (Expression e) throws CodeGenerationException {
BasicOpTuple bot = BasicOpTuple.isBasicOp(e);
while (bot != null && bot.getPrimitiveOp() == PrimOps.PRIMOP_EAGER) {
e = bot.getArgument(0);
bot = BasicOpTuple.isBasicOp(e);
}
return e;
}
private ExpressionContextPair buildLetVarDefCall(Expression e, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
// e has to be a fully saturated call to a letvar def function.
if (e.asVar() == null && e.asAppl() == null) {
return null;
}
Expression.Var eVar = e.asVar();
Expression[] chain = new Expression[]{eVar};
int nArgs = 0;
if (eVar == null) {
chain = appChain(e.asAppl());
eVar = chain[0].asVar();
nArgs = chain.length - 1;
}
if (eVar == null) {
return null;
}
MachineFunction mf = module.getFunction(eVar.getName());
if (mf == null ||
!(mf instanceof LECCLiftedLetVarMachineFunction) ||
mf.getArity() != nArgs) {
return null;
}
if (scheme == Scheme.UNBOX_INTERNAL_SCHEME && !canTypeBeUnboxed(mf.getResultType())) {
return null;
}
boolean calledArgStrictness[] = mf.getParameterStrictness();
TypeExpr calledArgTypes[] = mf.getParameterTypes();
// First generate the java expressions for the argument CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length-1];
for (int i = 1; i < chain.length; ++i) {
if (calledArgStrictness[i-1]) {
if (SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i-1])) {
ecp[i-1] = generateUnboxedArgument(typeExprToTypeName(calledArgTypes[i-1]), chain[i], variableContext);
} else {
ecp[i-1] = genS_E(chain[i], variableContext);
}
} else {
ecp[i-1] = genS_C(chain[i], variableContext);
}
}
Block newContext;
if (ecp.length > 0) {
newContext = ecp[0].getContextBlock();
for (int i = 1; i < ecp.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
} else {
newContext = new Block();
}
// This is a fully saturated supercombinator application
// for which we can generate a direct call instead of building a suspension.
int calledArity = mf.getArity();
JavaExpression args[] = new JavaExpression[ecp.length + 1];
for (int i = 0; i < calledArity; ++i) {
args[i] = ecp[i].getJavaExpression();
}
args[calledArity] = EXECUTION_CONTEXT_VAR;
JavaTypeName[] argTypes = new JavaTypeName[args.length];
for (int i = 0; i < calledArgTypes.length; ++i) {
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
argTypes[i] = SCJavaDefn.typeExprToTypeName(calledArgTypes[i]);
} else {
argTypes[i] = JavaTypeNames.RTVALUE;
}
}
argTypes[calledArity] = JavaTypeNames.RTEXECUTION_CONTEXT;
JavaTypeName returnType = JavaTypeNames.RTVALUE;
// Get the method name to call.
String functionName = CALToJavaNames.makeLetVarDefFunctionJavaName(mf.getQualifiedName(), module);
if (scheme == Scheme.E_SCHEME) {
functionName = functionName + "_Strict";
} else
if (scheme == Scheme.C_SCHEME) {
functionName = functionName + "_Lazy";
} else
if (scheme == Scheme.UNBOX_INTERNAL_SCHEME) {
functionName = functionName + "_Unboxed";
returnType = SCJavaDefn.typeExprToTypeName(mf.getResultType());
}
JavaExpression root = new MethodInvocation.Static(thisTypeName, functionName, args, argTypes, returnType);
return new ExpressionContextPair(root, newContext);
}
/**
* The R scheme generates code to apply a supercombinator to its arguments.
* @param e expression of regular kind
* @param variableContext
* @return Statement the resulting code contribution. This will either be a return statement, or a statement for which
* all possible code paths lead to a return statement.
* @throws CodeGenerationException
*/
private JavaStatement genS_R(Expression e, VariableContext variableContext) throws CodeGenerationException {
// e is a pack constructor?
if (e.asPackCons() != null) {
//This is an error! We should never be at this point with a data constructor expression.
throw new CodeGenerationException ("PackCons expression encountered in R scheme: " + e.asPackCons().getDataConstructor().getName());
}
// R is a literal?
Expression.Literal lit = e.asLiteral();
if (lit != null) {
return generateReturn (getKernelLiteral(lit.getLiteral()).getBoxedReference(), variableContext);
}
// Is e a call to a let variable definition function?
ExpressionContextPair letVarECP = buildLetVarDefCall(e, Scheme.C_SCHEME, variableContext);
if (letVarECP != null) {
return generateReturn(letVarECP, variableContext);
}
// Is e a primitive operation (arithmetic, comparative etc.)?
{
BasicOpTuple bop = BasicOpTuple.isBasicOp(e);
if (!LECCMachineConfiguration.generateDirectPrimOpCalls()) {
// When we are generating call chains we want to force all primitive operations
// to be done as a function call to the generated function.
//When we have function tracing enabled, we want to force all primitive operations to be
//done as function calls. This will have the effect of ensuring that they get traced when called.
if (bop != null && !bop.getName().equals(getQualifiedName())) {
bop = null;
}
}
if (bop != null) {
ExpressionContextPair ecPair = generatePrimitiveOp(e, true, Scheme.R_SCHEME, variableContext);
return generateReturn (ecPair, variableContext);
}
}
// We can potentially optimize an application of not to use the Java ! operator.
// Because we are directly evaluating and circumventing laziness it is only safe
// to do this if there is no chance of a directly/indirectly recursive call.
if (isNot(e) && !ExpressionAnalyzer.dependsOnStronglyConnectedComponent(e, connectedComponents, getModuleName())) {
ExpressionContextPair ecPair = generateNot (e.asAppl(), true, variableContext);
return generateReturn(ecPair, variableContext);
}
// Is e an application of 'and' or 'or'? Test only - don't get tuple
if (BasicOpTuple.isAndOr(e) != null) {
ExpressionContextPair ecPair = generateAndOr(e, true, variableContext);
return generateReturn(ecPair, variableContext);
}
// Is e an application of a saturated constructor?
ConstructorOpTuple constructorOpTuple = ConstructorOpTuple.isConstructorOp(e, false);
if (constructorOpTuple != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementFullySaturatedDCInTopLevelContext(constructorOpTuple.getDataConstructor().getName());
}
ExpressionContextPair ecPair = generateDataConstructor(constructorOpTuple, Scheme.R_SCHEME, variableContext);
return generateReturn(ecPair, variableContext);
}
// Is e a tail recursive call?
if (e.asTailRecursiveCall() != null) {
// We don't need to call generateReturn() at this point since a this
// expression will generate a loop continuation, not a 'return'.
return buildTailRecursiveLoopCall(e.asTailRecursiveCall(), variableContext);
}
// R is a variable?
if (e.asVar() != null) {
Expression.Var var = e.asVar();
JavaExpression varExpression = expressionVarToJavaDef(var, Scheme.R_SCHEME, variableContext);
// If the variable is already evaluated we want to call getValue(), which will follow any
// indirection chains and return the final value. However, if we are simply returning
// a strict unboxed function argument this isn't necessary.
if (variableContext.isLocalVariable(var.getName()) &&
variableContext.isVarPreEvaluated(var.getName()) &&
(!variableContext.isFunctionArgument(var.getName()) || !isArgUnboxable(var.getName()))) {
// Variable is already evaluated so simply do a 'getValue()' on it.
varExpression = new MethodInvocation.Instance(varExpression, "getValue", JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
}
return generateReturn(varExpression, variableContext);
}
// Is e a conditional op (if <cond expr> <then expr> <else expr>)?
// Since this is the top level compilation scheme we can generate an actual 'if' statement.
CondTuple conditionExpressions = CondTuple.isCondOp(e);
if (conditionExpressions != null) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementOptimizedIfThenElse();
}
// This is a conditional op. The conditionExpressions tuple holds (kCond, kThen, kElse) expressions
// Generate the code for kThen and kElse, as arguments to a new I_Cond instruction
Expression condExpression = conditionExpressions.getConditionExpression();
ExpressionContextPair pair = generateUnboxedArgument(JavaTypeName.BOOLEAN, condExpression, variableContext);
// Then
variableContext.pushJavaScope();
JavaStatement thenPart = genS_R (conditionExpressions.getThenExpression(), variableContext);
Block thenBlock = variableContext.popJavaScope();
thenBlock.addStatement(thenPart);
// Else
variableContext.pushJavaScope();
JavaStatement elsePart = genS_R (conditionExpressions.getElseExpression(), variableContext);
Block elseBlock = variableContext.popJavaScope();
elseBlock.addStatement(elsePart);
JavaExpression conditionExpression = pair.getJavaExpression();
JavaStatement ite = new IfThenElseStatement(conditionExpression, thenBlock, elseBlock);
Block contextBlock = pair.getContextBlock();
contextBlock.addStatement(ite);
// We don't need to call generateReturn() at this point. Any return statements
// will have been generated in the sub-parts of the if-then-else.
return contextBlock;
}
// Expression is a case/switch?
Expression.Switch eswitch = e.asSwitch();
if (eswitch != null) {
// We don't need to call generateReturn() at this point. Any return statements
// will have been generated in the sub-parts of the switch.
return generateSwitch (eswitch, variableContext);
}
// Expression is a data constructor field selection?
Expression.DataConsSelection eDCSelection = e.asDataConsSelection();
if (eDCSelection != null) {
return generateReturn(generateDCFieldSelection(eDCSelection, Scheme.R_SCHEME, variableContext), variableContext);
}
// e is a let var.
if (e.asLetNonRec() != null) {
return generateReturn (generateLetNonRec(e.asLetNonRec(), Scheme.R_SCHEME, null, variableContext), variableContext);
}
if (e.asLetRec() != null) {
return generateReturn(generateLetRec(e.asLetRec(), Scheme.R_SCHEME, variableContext), variableContext);
}
// e is an application?
Expression.Appl appl = e.asAppl();
if (appl != null) {
JavaStatement topLevelSeq = buildTopLevelSeq (appl, false, variableContext);
if (topLevelSeq != null) {
return topLevelSeq;
}
ExpressionContextPair ecp = buildApplicationChain (appl, Scheme.R_SCHEME, variableContext);
if (ecp != null) {
return generateReturn(ecp, variableContext);
} else {
// This is a general application
ExpressionContextPair target;
if (appl.getE1().asDataConsSelection() != null) {
target = generateDCFieldSelection(appl.getE1().asDataConsSelection(), Scheme.E_SCHEME, variableContext);
} else {
target = genS_C(appl.getE1(), variableContext);
}
ExpressionContextPair arg = genS_C(appl.getE2(), variableContext);
JavaStatement.Block returnBlock = target.getContextBlock();
returnBlock.addStatement(arg.getContextBlock());
MethodInvocation mi = createInvocation(target.getJavaExpression(), APPLY, arg.getJavaExpression());
return generateReturn (new ExpressionContextPair(mi, returnBlock), variableContext);
}
}
//e is a record update
Expression.RecordUpdate recordUpdate = e.asRecordUpdate();
if (recordUpdate != null) {
ExpressionContextPair ecPair = generateStrictRecordUpdate(recordUpdate, variableContext);
return generateReturn(ecPair, variableContext);
}
// e is a record extension
Expression.RecordExtension recordExtension = e.asRecordExtension();
if (recordExtension != null) {
ExpressionContextPair ecPair = generateStrictRecordExtension(recordExtension, variableContext);
return generateReturn(ecPair, variableContext);
}
// e is a record selection
Expression.RecordSelection recordSelection = e.asRecordSelection();
if (recordSelection != null) {
return generateReturn(generateStrictRecordSelection(recordSelection, true, variableContext), variableContext);
}
// e is a record case
Expression.RecordCase recordCase = e.asRecordCase();
if (recordCase != null) {
return generateRecordCase (recordCase, variableContext);
}
// e is a cast
Expression.Cast cast = e.asCast();
if (cast != null) {
Expression.Var varToCast = cast.getVarToCast();
if (!variableContext.isFunctionArgument(varToCast.getName())) {
throw new CodeGenerationException("Expression.Cast is applied to a non-argument variable.");
}
if (!isArgStrict(varToCast.getName()) || !isArgUnboxable(varToCast.getName())) {
throw new CodeGenerationException("Expression.Cast is applied to a non-strict or non-primitive argument.");
}
JavaTypeName castType = JavaTypeName.make (getCastType(cast));
JavaExpression returnVal = boxExpression(castType, new JavaExpression.CastExpression(castType, variableContext.getUnboxedReference(varToCast.getName())));
return generateReturn(returnVal, variableContext);
}
throw new CodeGenerationException("Unrecognized expression: " + e);
}
/**
* Generate java code corresponding to a data constructor field selection.
* @param dcSelection the expression representing the data constructor field selection.
* @param scheme
* @param variableContext
* @return The expression/context
* @throws CodeGenerationException
*/
private ExpressionContextPair generateDCFieldSelection(DataConsSelection dcSelection, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
if (codeGenerationStats != null) {
if (scheme == Scheme.E_SCHEME) {
codeGenerationStats.incrementStrictDCFieldSelectionCount();
} else if (scheme == Scheme.R_SCHEME) {
codeGenerationStats.incrementTopLevelDCFieldSelectionCount();
} else if (scheme == Scheme.C_SCHEME) {
codeGenerationStats.incrementLazyDCFieldSelectionCount();
}
}
if (scheme == Scheme.E_SCHEME) {
ExpressionContextPair ecp = genS_E (dcSelection.getDCValueExpr(), variableContext);
JavaExpression target = ecp.getJavaExpression();
boolean doCast = true;
if (target instanceof JavaExpression.ClassInstanceCreationExpression) {
doCast = false;
}
if (doCast) {
target = new JavaExpression.CastExpression(JavaTypeNames.RTCONS, target);
}
MethodInvocation mi =
new MethodInvocation.Instance (target,
"getFieldByIndex",
new JavaExpression[]{LiteralWrapper.make(new Integer(dcSelection.getDataConstructor().getOrdinal())),
LiteralWrapper.make(new Integer(dcSelection.getFieldIndex())),
getErrorInfo (dcSelection.getErrorInfo())},
new JavaTypeName[]{JavaTypeName.INT,
JavaTypeName.INT,
JavaTypeName.ERRORINFO},
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return new ExpressionContextPair (createInvocation(mi, EVALUATE, EXECUTION_CONTEXT_VAR), ecp.getContextBlock());
}
if (scheme == Scheme.R_SCHEME) {
ExpressionContextPair ecp = genS_E (dcSelection.getDCValueExpr(), variableContext);
JavaExpression target = ecp.getJavaExpression();
if (!(target instanceof JavaExpression.ClassInstanceCreationExpression)) {
target = new JavaExpression.CastExpression(JavaTypeNames.RTCONS, target);
}
MethodInvocation mi =
new MethodInvocation.Instance (target,
"getFieldByIndex",
new JavaExpression[]{LiteralWrapper.make(new Integer(dcSelection.getDataConstructor().getOrdinal())),
LiteralWrapper.make(new Integer(dcSelection.getFieldIndex())),
getErrorInfo (dcSelection.getErrorInfo())},
new JavaTypeName[]{JavaTypeName.INT,
JavaTypeName.INT,
JavaTypeName.ERRORINFO},
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return new ExpressionContextPair (mi, ecp.getContextBlock());
}
// This is a DC field selection expression in a lazy scheme. Do a lazy compilation of the
// DC expression and then pass the resulting graph as an argument to an RTDataConsFieldSelection
// sub class.
ExpressionContextPair ecp = genS_C (dcSelection.getDCValueExpr(), variableContext);
JavaTypeName fieldSelectionClassTypeName =
CALToJavaNames.createFieldSelectionClassTypeNameFromDC(dcSelection.getDataConstructor(), module);
JavaExpression cice =
new ClassInstanceCreationExpression (fieldSelectionClassTypeName,
new JavaExpression[]{ecp.getJavaExpression(),
LiteralWrapper.make(new Integer(dcSelection.getDataConstructor().getOrdinal())),
LiteralWrapper.make(new Integer(dcSelection.getFieldIndex())),
getErrorInfo (dcSelection.getErrorInfo())},
new JavaTypeName[]{JavaTypeNames.RTVALUE,
JavaTypeName.INT,
JavaTypeName.INT,
JavaTypeName.ERRORINFO});
return new ExpressionContextPair (cice, ecp.getContextBlock());
}
/**
* Generates code for the record update operator, such as
* {r | #1 := "abc", #2 = 10.0}
* in a lazy context. What this means is that r is not actually evaluated, but a special RTRecordUpdate node is created
* that can be evaluated later if needed to an actual updated record value.
*
* @param recordUpdateExpr
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateLazyRecordUpdate(Expression.RecordUpdate recordUpdateExpr, VariableContext variableContext) throws CodeGenerationException {
Expression baseRecordExpr = recordUpdateExpr.getBaseRecordExpr();
ExpressionContextPair baseRecordExprContextPair = genS_C(baseRecordExpr, variableContext);
JavaExpression javaBaseRecordExpr = baseRecordExprContextPair.getJavaExpression();
//holds the variable declarations introduced in subexpressions of the record update.
//for example, in the expression {e | field1 := e1, field2 := e2}, these could come from e, e1 or e2.
Block recordUpdateBlock = new Block();
recordUpdateBlock.addStatement(baseRecordExprContextPair.getContextBlock());
Expression.FieldValueData updateFieldsData = recordUpdateExpr.getUpdateFieldsData();
final int nOrdinalFields = updateFieldsData.getNOrdinalFields();
final int nTextualFields = updateFieldsData.getNTextualFields();
if (nOrdinalFields > 0 ) {
final boolean hasTupleOrdinalPart = updateFieldsData.hasTupleOrdinalPart();
if (nTextualFields > 0) {
if (hasTupleOrdinalPart) {
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_UPDATE,
"makeTupleMixedRecordUpdate",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalValuesArray(updateFieldsData, recordUpdateBlock, variableContext),
createTextualNamesArray(updateFieldsData.getTextualNames()),
createTextualValuesArray(updateFieldsData, recordUpdateBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_UPDATE),
recordUpdateBlock);
}
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_UPDATE,
"makeMixedRecordUpdate",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalNamesArray(updateFieldsData.getOrdinalNames()),
createOrdinalValuesArray(updateFieldsData, recordUpdateBlock, variableContext),
createTextualNamesArray(updateFieldsData.getTextualNames()),
createTextualValuesArray(updateFieldsData, recordUpdateBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_UPDATE),
recordUpdateBlock);
}
if (hasTupleOrdinalPart) {
return new ExpressionContextPair(new MethodInvocation.Static(
JavaTypeNames.RTRECORD_UPDATE,
"makeTupleRecordUpdate",
new JavaExpression[] {javaBaseRecordExpr, createOrdinalValuesArray(updateFieldsData, recordUpdateBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_UPDATE),
recordUpdateBlock);
}
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_UPDATE,
"makeOrdinalRecordUpdate",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalNamesArray(updateFieldsData.getOrdinalNames()),
createOrdinalValuesArray(updateFieldsData, recordUpdateBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_UPDATE),
recordUpdateBlock);
}
if (nTextualFields > 0) {
return new ExpressionContextPair(new MethodInvocation.Static(
JavaTypeNames.RTRECORD_UPDATE,
"makeTextualRecordUpdate",
new JavaExpression[] {
javaBaseRecordExpr,
createTextualNamesArray(updateFieldsData.getTextualNames()),
createTextualValuesArray(updateFieldsData, recordUpdateBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_UPDATE),
recordUpdateBlock);
}
//the empty record update is not allowed (should be caught by static checks).
throw new CodeGenerationException("The empty record update is not allowed.");
}
/**
* Generates code for record update in a strict context. For example,
* {r | field1 := 20.0, field2 := "abc"}.
* The main difference between the strict and lazy case is that in the strict case r must be
* reduced to know what the shape of the record is.
*
* @param recordUpdateExpr
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateStrictRecordUpdate(Expression.RecordUpdate recordUpdateExpr, VariableContext variableContext) throws CodeGenerationException {
Expression baseRecordExpr = recordUpdateExpr.getBaseRecordExpr();
//holds the variable declarations introduced in subexpressions of the record update expression.
//for example, in the expression {e | field1 := e1, field2 := e2}, these could come from e, e1 or e2.
Block recordUpdateBlock = new Block();
JavaExpression invocationTarget;
{
ExpressionContextPair baseRecordExprContextPair = genS_E(baseRecordExpr, variableContext);
JavaExpression javaBaseRecordExpr = baseRecordExprContextPair.getJavaExpression();
recordUpdateBlock.addStatement(baseRecordExprContextPair.getContextBlock());
//the compiler ensures that evaluating baseRecordExpr will result in a RTRecordValue.
invocationTarget = new CastExpression(JavaTypeNames.RTRECORD_VALUE, javaBaseRecordExpr);
}
//the expression {e | field1 := e1, field2 := e2, field3 := e3} is encoded as
//{{{e | field1 := e1} | field2 := e2} | field3 := e3}
// We want to copy the base record for the first update. Subsequent updates can just
// mutate the copy.
// The copy is created by a call to:
// 1) updateOrdinalField - if only ordinal fields are being updated
// 2) updateTextualField - if only textual fields are being updated
// 3) updateMixedOrdinalField - if both ordinal and textual fields are being updated
FieldValueData fieldValueData = recordUpdateExpr.getUpdateFieldsData();
SortedMap<FieldName, Expression> updateFieldValuesMap = recordUpdateExpr.getUpdateFieldValuesMap();
int fieldN = 0;
for (final Map.Entry<FieldName, Expression> entry : updateFieldValuesMap.entrySet()) {
FieldName fieldName = entry.getKey();
Expression updateExpr = entry.getValue();
//the actual updated values are not strictly evaluated, so we use the C scheme.
ExpressionContextPair updateExprContextPair = genS_C(updateExpr, variableContext);
JavaExpression javaUpdateExpr = updateExprContextPair.getJavaExpression();
recordUpdateBlock.addStatement(updateExprContextPair.getContextBlock());
if (fieldName instanceof FieldName.Ordinal) {
//we need to copy the base record only for the first update. Subsequent updates can just mutate the base.
String updateMethodName;
if (fieldN == 0) {
// If there are only ordinal field updates we call updateOrdinalField to generate
// the record copy. Otherwise we call updateMixedOrdinalField, so that it is
// safe to modify the textual fields of the copy.
if (fieldValueData.getNTextualFields() == 0) {
updateMethodName = "updateOrdinalField";
} else {
updateMethodName = "updateMixedOrdinalField";
}
} else {
updateMethodName = "mutateOrdinalField";
}
int ordinal = ((FieldName.Ordinal)fieldName).getOrdinal();
invocationTarget =
new MethodInvocation.Instance(
invocationTarget,
updateMethodName,
new JavaExpression[] {
LiteralWrapper.make(Integer.valueOf(ordinal)),
javaUpdateExpr},
new JavaTypeName[] {JavaTypeName.INT, JavaTypeNames.RTVALUE},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else if (fieldName instanceof FieldName.Textual) {
//we need to copy the base record only for the first update. Subsequent updates can just mutate the base.
String updateMethodName;
if (fieldN == 0) {
updateMethodName = "updateTextualField";
} else {
updateMethodName = "mutateTextualField";
}
String textualFieldName = fieldName.getCalSourceForm();
invocationTarget =
new MethodInvocation.Instance(
invocationTarget,
updateMethodName,
new JavaExpression[] {
LiteralWrapper.make(textualFieldName),
javaUpdateExpr},
new JavaTypeName[] {JavaTypeName.STRING, JavaTypeNames.RTVALUE},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
throw new IllegalStateException();
}
++fieldN;
}
return new ExpressionContextPair (invocationTarget, recordUpdateBlock);
}
/**
* Generates code for record extensions in a strict context.
* These include non record-polymorphic records such as
* {field1 = 10.0, field2 = "abc"}
* and record-polymorphic expressions such as
* {r | field1 = 20.0}.
* The main difference between the strict and lazy case is that in the strict case r must be
* reduced to know what the shape of the record is.
*
* @param recordExtensionExpr
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateStrictRecordExtension(Expression.RecordExtension recordExtensionExpr, VariableContext variableContext) throws CodeGenerationException {
Expression baseRecordExpr = recordExtensionExpr.getBaseRecordExpr();
//holds the variable declarations introduced in subexpressions of the record extension.
//for example, in the expression {e | field1 = e1, field2 = e2}, these could come from e, e1 or e2.
Block recordExtensionBlock = new Block();
JavaExpression invocationTarget;
if (baseRecordExpr != null) {
ExpressionContextPair baseRecordExprContextPair = genS_E(baseRecordExpr, variableContext);
JavaExpression javaBaseRecordExpr = baseRecordExprContextPair.getJavaExpression();
recordExtensionBlock.addStatement(baseRecordExprContextPair.getContextBlock());
//the compiler ensures that evaluating baseRecordExpr will result in a RTRecordValue.
invocationTarget = new CastExpression(JavaTypeNames.RTRECORD_VALUE, javaBaseRecordExpr);
//invocationType = InvocationType.VIRTUAL;
} else {
invocationTarget = null;
}
Expression.FieldValueData extensionFieldsData = recordExtensionExpr.getExtensionFieldsData();
final int nOrdinalFields = extensionFieldsData.getNOrdinalFields();
final int nTextualFields = extensionFieldsData.getNTextualFields();
if (nOrdinalFields > 0 ) {
final boolean hasTupleOrdinalPart = extensionFieldsData.hasTupleOrdinalPart();
if (nTextualFields > 0) {
if (hasTupleOrdinalPart) {
//in the non-extension case, create code for:
//RTRecordValue.makeTupleMixedRecord(new RTValue[] {ordinalValues1, ..., ordinalValuesN},
// new String[] {textualFieldName1, ..., textualFieldNameM},
// new RTValue[] {textualFieldValue1, ..., textualFieldValueM}
MethodInvocation mi;
if (baseRecordExpr != null) {
mi = new MethodInvocation.Instance(
invocationTarget,
makeRecordCreationName("makeTupleMixedRecord", baseRecordExpr),
new JavaExpression[] {
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
//static invocation
mi = new MethodInvocation.Static(
JavaTypeNames.RTRECORD_VALUE,
makeRecordCreationName("makeTupleMixedRecord", null),
new JavaExpression[] {
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE);
}
return new ExpressionContextPair (mi, recordExtensionBlock);
}
MethodInvocation mi;
if (baseRecordExpr != null) {
mi = new MethodInvocation.Instance(
invocationTarget,
makeRecordCreationName("makeMixedRecord", baseRecordExpr),
new JavaExpression[] {
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
mi = new MethodInvocation.Static(
JavaTypeNames.RTRECORD_VALUE,
makeRecordCreationName("makeMixedRecord", null),
new JavaExpression[] {
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE);
}
return new ExpressionContextPair (mi, recordExtensionBlock);
}
if (hasTupleOrdinalPart) {
MethodInvocation mi;
if (baseRecordExpr != null) {
mi = new MethodInvocation.Instance(
invocationTarget,
makeRecordCreationName("makeTupleRecord", baseRecordExpr),
new JavaExpression[] {createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
mi = new MethodInvocation.Static(
JavaTypeNames.RTRECORD_VALUE,
makeRecordCreationName("makeTupleRecord", null),
new JavaExpression[] {createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE);
}
return new ExpressionContextPair(mi, recordExtensionBlock);
}
MethodInvocation mi;
if (baseRecordExpr != null) {
mi = new MethodInvocation.Instance(
invocationTarget,
makeRecordCreationName("makeOrdinalRecord", baseRecordExpr),
new JavaExpression[] {
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
mi = new MethodInvocation.Static(
JavaTypeNames.RTRECORD_VALUE,
makeRecordCreationName("makeOrdinalRecord", null),
new JavaExpression[] {
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE);
}
return new ExpressionContextPair (mi, recordExtensionBlock);
}
if (nTextualFields > 0) {
//create code for:
//RTRecordValue.makeTextualRecord(new String[] {textualFieldName1, ..., textualFieldNameN},
// new RTValue[] {valueExpr1, ...., valueExprN})
//or
//baseRecordExpr.makeTextualRecordExtension(new String[] {textualFieldName1, ..., textualFieldNameN},
// new RTValue[] {valueExpr1, ...., valueExprN})
MethodInvocation mi;
if (baseRecordExpr != null) {
mi = new MethodInvocation.Instance(
invocationTarget,
makeRecordCreationName("makeTextualRecord", baseRecordExpr),
new JavaExpression[] {createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
mi = new MethodInvocation.Static(
JavaTypeNames.RTRECORD_VALUE,
makeRecordCreationName("makeTextualRecord", null),
new JavaExpression[] {createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_VALUE);
}
return new ExpressionContextPair(mi, recordExtensionBlock);
}
if (baseRecordExpr != null) {
//the empty record extension {e |}
return new ExpressionContextPair(invocationTarget, recordExtensionBlock);
}
//empty record: RTRecordValue.EMPTY_RECORD
return new ExpressionContextPair(new JavaField.Static(JavaTypeNames.RTRECORD_VALUE, "EMPTY_RECORD", JavaTypeNames.RTRECORD_VALUE),
recordExtensionBlock);
}
private static String makeRecordCreationName(String baseName, Expression baseRecordExpr) {
if (baseRecordExpr == null) {
return baseName;
}
return baseName + "Extension";
}
/**
* @param ordinalNames
* @return creates the expression "new int[] {ordinal1, ..., ordinalN}".
*/
private static ArrayCreationExpression createOrdinalNamesArray(int[] ordinalNames) {
int nOrdinalFields = ordinalNames.length;
JavaExpression[] javaTextualNames = new JavaExpression[nOrdinalFields];
for (int i = 0; i < nOrdinalFields; ++i) {
javaTextualNames[i] = LiteralWrapper.make(Integer.valueOf(ordinalNames[i]));
}
return new ArrayCreationExpression(JavaTypeName.INT, javaTextualNames);
}
/**
* @param textualNames
* @return creates the expression "new String[] {textualFieldName1, ..., textualFieldNameN}".
*/
private static ArrayCreationExpression createTextualNamesArray(String[] textualNames) {
int nTextualFields = textualNames.length;
JavaExpression[] javaTextualNames = new JavaExpression[nTextualFields];
for (int i = 0; i < nTextualFields; ++i) {
javaTextualNames[i] = LiteralWrapper.make(textualNames[i]);
}
return new ArrayCreationExpression(JavaTypeName.STRING, javaTextualNames);
}
private ArrayCreationExpression createOrdinalValuesArray(Expression.FieldValueData fieldValueData, Block recordModificationBlock, VariableContext variableContext) throws CodeGenerationException {
//create the expression:
//new RTValue[] {valueExpr1, ...., valueExprN}
int nOrdinalFields = fieldValueData.getNOrdinalFields();
Expression[] ordinalValues = fieldValueData.getOrdinalValues();
JavaExpression[] javaOrdinalValues = new JavaExpression[nOrdinalFields];
for (int i = 0; i < nOrdinalFields; ++i) {
ExpressionContextPair valueExprContextPair = genS_C(ordinalValues[i], variableContext);
javaOrdinalValues[i] = valueExprContextPair.getJavaExpression();
recordModificationBlock.addStatement(valueExprContextPair.getContextBlock());
}
return new ArrayCreationExpression(JavaTypeNames.RTVALUE, javaOrdinalValues);
}
private ArrayCreationExpression createTextualValuesArray(Expression.FieldValueData fieldValueData, Block recordModificationBlock, VariableContext variableContext) throws CodeGenerationException {
//create the expression:
//new RTValue[] {valueExpr1, ...., valueExprN}
int nTextualFields = fieldValueData.getNTextualFields();
Expression[] textualValues = fieldValueData.getTextualValues();
JavaExpression[] javaTextualValues = new JavaExpression[nTextualFields];
for (int i = 0; i < nTextualFields; ++i) {
ExpressionContextPair valueExprContextPair = genS_C(textualValues[i], variableContext);
javaTextualValues[i] = valueExprContextPair.getJavaExpression();
recordModificationBlock.addStatement(valueExprContextPair.getContextBlock());
}
return new ArrayCreationExpression(JavaTypeNames.RTVALUE, javaTextualValues);
}
/**
* Generates code for record extensions in a lazy context. These include
* non record-polymorphic records such as {field1 = 10.0, field2 = "abc"}
* and record-polymorphic expressions such as {r | field1 = 20.0}. The main
* difference between the strict and lazy case is that in the strict case r
* must be reduced to know what the shape of the record is.
*
* @param recordExtensionExpr
* @param variableContext
* @return Expression.RecordExtension
* @throws CodeGenerationException
*/
private ExpressionContextPair generateLazyRecordExtension(Expression.RecordExtension recordExtensionExpr, VariableContext variableContext) throws CodeGenerationException {
Expression baseRecordExpr = recordExtensionExpr.getBaseRecordExpr();
if (baseRecordExpr == null) {
//this is a non record-polymorphic record extension so it is already in reduced form.
return generateStrictRecordExtension(recordExtensionExpr, variableContext);
}
ExpressionContextPair baseRecordExprContextPair = genS_C(baseRecordExpr, variableContext);
JavaExpression javaBaseRecordExpr = baseRecordExprContextPair.getJavaExpression();
//holds the variable declarations introduced in subexpressions of the record extension.
//for example, in the expression {e | field1 = e1, field2 = e2}, these could come from e, e1 or e2.
Block recordExtensionBlock = new Block();
recordExtensionBlock.addStatement(baseRecordExprContextPair.getContextBlock());
Expression.FieldValueData extensionFieldsData = recordExtensionExpr.getExtensionFieldsData();
final int nOrdinalFields = extensionFieldsData.getNOrdinalFields();
final int nTextualFields = extensionFieldsData.getNTextualFields();
if (nOrdinalFields > 0 ) {
final boolean hasTupleOrdinalPart = extensionFieldsData.hasTupleOrdinalPart();
if (nTextualFields > 0) {
if (hasTupleOrdinalPart) {
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_EXTENSION,
"makeTupleMixedRecordExtension",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_EXTENSION),
recordExtensionBlock);
}
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_EXTENSION,
"makeMixedRecordExtension",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext),
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_EXTENSION),
recordExtensionBlock);
}
if (hasTupleOrdinalPart) {
return new ExpressionContextPair(new MethodInvocation.Static(
JavaTypeNames.RTRECORD_EXTENSION,
"makeTupleRecordExtension",
new JavaExpression[] {javaBaseRecordExpr, createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_EXTENSION),
recordExtensionBlock);
}
return new ExpressionContextPair (new MethodInvocation.Static(
JavaTypeNames.RTRECORD_EXTENSION,
"makeOrdinalRecordExtension",
new JavaExpression[] {
javaBaseRecordExpr,
createOrdinalNamesArray(extensionFieldsData.getOrdinalNames()),
createOrdinalValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.INT_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_EXTENSION),
recordExtensionBlock);
}
if (nTextualFields > 0) {
return new ExpressionContextPair(new MethodInvocation.Static(
JavaTypeNames.RTRECORD_EXTENSION,
"makeTextualRecordExtension",
new JavaExpression[] {
javaBaseRecordExpr,
createTextualNamesArray(extensionFieldsData.getTextualNames()),
createTextualValuesArray(extensionFieldsData, recordExtensionBlock, variableContext)},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.STRING_ARRAY, JavaTypeNames.RTVALUE_ARRAY},
JavaTypeNames.RTRECORD_EXTENSION),
recordExtensionBlock);
}
//the empty record extension {e |}
return new ExpressionContextPair(javaBaseRecordExpr, recordExtensionBlock);
}
/**
* Generates code for record selection i.e. recordExpr.fieldName in a strict context.
*
* @param recordSelectionExpr - the record selection construct
* @param evaluateField - indicates that the selected field value should be evaluated to WHNF
* @param variableContext
* @return Expression.RecordSelection
* @throws CodeGenerationException
*/
private ExpressionContextPair generateStrictRecordSelection(
Expression.RecordSelection recordSelectionExpr,
boolean evaluateField,
VariableContext variableContext) throws CodeGenerationException {
//for the CAL code recordExpr.fieldName we generate (roughly)
//for ordinal fields
//((RTRecordValue) (codeForRecordExpr.evaluate($ec))).getOrdinalFieldValue(ordinal)
//for textual fields
//((RTRecordValue) (codeForRecordExpr.evaluate($ec))).getTextualFieldValue(textualFieldName)
Expression recordExpr = recordSelectionExpr.getRecordExpr();
FieldName fieldName = recordSelectionExpr.getFieldName();
ExpressionContextPair recordExprContextPair = genS_E(recordExpr, variableContext);
JavaExpression javaRecordExpr = recordExprContextPair.getJavaExpression();
Block recordSelectionBlock = new Block();
recordSelectionBlock.addStatement(recordExprContextPair.getContextBlock());
//the compiler ensures that evaluating recordExpr will result in a RTRecordValue.
javaRecordExpr = new CastExpression(JavaTypeNames.RTRECORD_VALUE, javaRecordExpr);
MethodInvocation getValueInvocation;
if (fieldName instanceof FieldName.Textual) {
getValueInvocation = new MethodInvocation.Instance(javaRecordExpr, "getTextualFieldValue",
LiteralWrapper.make(fieldName.getCalSourceForm()), JavaTypeName.STRING,
JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
} else {
int ordinal = ((FieldName.Ordinal)fieldName).getOrdinal();
getValueInvocation = new MethodInvocation.Instance(javaRecordExpr, "getOrdinalFieldValue",
LiteralWrapper.make(Integer.valueOf(ordinal)), JavaTypeName.INT,
JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
}
if (evaluateField) {
getValueInvocation = createInvocation (getValueInvocation, EVALUATE, EXECUTION_CONTEXT_VAR);
}
return new ExpressionContextPair(getValueInvocation, recordSelectionBlock);
}
/**
* Generates code for record selection i.e. recordExpr.fieldName in a lazy context.
* What this essentially means is that we don't want to force the evaluation of recordExpr
* to get a record.
*
* @param recordSelectionExpr
* @param variableContext
* @return Expression.RecordSelection
* @throws CodeGenerationException
*/
private ExpressionContextPair generateLazyRecordSelection(Expression.RecordSelection recordSelectionExpr, VariableContext variableContext) throws CodeGenerationException {
//for the CAL code recordExpr.fieldName we generate (roughly)
//for textual field names
//new RTRecordSelection.Textual(codeForRecordExpr, textualFieldName);
//for ordinal field names
//new RTRecordSelection.Ordinal(codeForRecordExpr, ordinal);
Expression recordExpr = recordSelectionExpr.getRecordExpr();
// If we can ignore laziness for the record expression then we can generate code
// that directly extracts the field value, but doesn't force the field value to
// WHNF.
if (canIgnoreLaziness(recordExpr, variableContext)) {
return generateStrictRecordSelection(recordSelectionExpr, false, variableContext);
}
FieldName fieldName = recordSelectionExpr.getFieldName();
ExpressionContextPair recordExprContextPair = genS_C(recordExpr, variableContext);
JavaExpression javaRecordExpr = recordExprContextPair.getJavaExpression();
Block recordSelectionBlock = new Block();
recordSelectionBlock.addStatement(recordExprContextPair.getContextBlock());
JavaExpression createLazyRecordSelection;
if (fieldName instanceof FieldName.Textual) {
createLazyRecordSelection = new ClassInstanceCreationExpression(JavaTypeNames.RTRECORD_SELECTION_TEXTUAL_FIELD,
new JavaExpression[] {javaRecordExpr, LiteralWrapper.make(fieldName.getCalSourceForm())},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.STRING});
} else {
int ordinal = ((FieldName.Ordinal)fieldName).getOrdinal();
createLazyRecordSelection = new ClassInstanceCreationExpression(JavaTypeNames.RTRECORD_SELECTION_ORDINAL_FIELD,
new JavaExpression[] {javaRecordExpr, LiteralWrapper.make(Integer.valueOf(ordinal))},
new JavaTypeName[] {JavaTypeNames.RTVALUE, JavaTypeName.INT});
}
return new ExpressionContextPair(createLazyRecordSelection, recordSelectionBlock);
}
/**
* Generates code for record case.
* The prototypical record case is:
* case conditionExpr of {r | field1 = x1, field2 = x2, ...} -> resultExpr;
* r may be null in the case of a non record-polymorphic pattern match
*
* @param recordCaseExpr
* @param variableContext
* @return JavaStatement
* @throws CodeGenerationException
*/
private JavaStatement generateRecordCase(Expression.RecordCase recordCaseExpr, VariableContext variableContext) throws CodeGenerationException {
//generate code for the record case condition expression
//this will look something like:
//$recordCase = (RTRecordValue)javaConditionExpr.evaluate()
// Increment the nested case level. This is used to disambiguate the name of the variable created
// to hold the record value of the case expression.
nestedCaseLevel++;
Expression conditionExpr = recordCaseExpr.getConditionExpr();
ExpressionContextPair conditionExprContextPair = genS_E(conditionExpr, variableContext);
Block recordCaseBlock = new Block();
JavaExpression javaConditionExpr = conditionExprContextPair.getJavaExpression();
recordCaseBlock.addStatement(conditionExprContextPair.getContextBlock());
//the compiler ensures that evaluating conditionExpr will result in a RTRecordValue.
javaConditionExpr = new CastExpression(JavaTypeNames.RTRECORD_VALUE, javaConditionExpr);
LocalVariable conditionVar = new LocalVariable("$recordCase" + nestedCaseLevel, JavaTypeNames.RTRECORD_VALUE);
LocalVariableDeclaration conditionVarDeclaration = new LocalVariableDeclaration(conditionVar, javaConditionExpr);
recordCaseBlock.addStatement(conditionVarDeclaration);
//now encode the extraction of the pattern bound variables from the condition record expr.
// Also need to push a let variable block. This is separate from the variable scope because the two
// do not always coincide. The let variable block is popped by calling i_VariableScope.genS_Vars().
variableContext.pushJavaScope();
//FieldName -> String
SortedMap<FieldName, String> fieldBindingVarMap = recordCaseExpr.getFieldBindingVarMap();
String baseRecordPatternVarName = recordCaseExpr.getBaseRecordPatternVarName();
if (baseRecordPatternVarName != null &&
!baseRecordPatternVarName.equals(Expression.RecordCase.WILDCARD_VAR)) {
QualifiedName qn = QualifiedName.make(currentModuleName, baseRecordPatternVarName);
VarInfo.RecordField varInfo = variableContext.addRecordField(qn, null);
String javaBaseRecordPatternVarName = varInfo.getJavaName();
LocalName lazyRef = new LocalName(varInfo.getJavaName(), JavaTypeNames.RTVALUE);
varInfo.updateLazyReference(lazyRef);
varInfo.updateStrictReference(SCJavaDefn.createInvocation(lazyRef, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR));
//generate the Java code:
//(in the case of both ordinal and textual field names
//javaBaseRecordPatternVarName = conditionVar.makeMixedRecordRetraction(new int[] {ordinalFieldName1, ..., ordinalFieldNameN},
// new String[] {textualFieldName1, ..., textualFieldNameN}
LocalVariable baseRecordPatternVar = new LocalVariable(javaBaseRecordPatternVarName, JavaTypeNames.RTRECORD_VALUE);
JavaExpression javaExtractBaseRecordExpr;
Expression.RecordCase.FieldData fieldData = recordCaseExpr.getBindingFieldsData();
//todoBI there could be some more optimizations here to handle the cases
//a. where the ordinal names are in tuple form, then only the tuple size needs to be passed.
//b. where only a single ordinal or single textual field is being retracted, then we don't
// need to form the array.
//Note however, that if the record pattern var is not used in the expression on the right hand side
//of the ->, then it will not be created (earlier analysis replaces it by a _), so in fact this
//code is not called that often anyways.
int[] retractedOrdinalFields = fieldData.getOrdinalNames();
String[] retractedTextualFields = fieldData.getTextualNames();
if (retractedOrdinalFields.length > 0) {
if (retractedTextualFields.length > 0) {
if (fieldData.hasTupleOrdinalPart()) {
javaExtractBaseRecordExpr = new MethodInvocation.Instance(
conditionVar,
"makeTupleMixedRecordRetraction",
new JavaExpression[] {
LiteralWrapper.make(Integer.valueOf(retractedOrdinalFields.length)),
createTextualNamesArray(retractedTextualFields)},
new JavaTypeName[] {JavaTypeName.INT, JavaTypeName.STRING_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
javaExtractBaseRecordExpr = new MethodInvocation.Instance(
conditionVar,
"makeMixedRecordRetraction",
new JavaExpression[] {
createOrdinalNamesArray(retractedOrdinalFields),
createTextualNamesArray(retractedTextualFields)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY, JavaTypeName.STRING_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
}
} else {
if (fieldData.hasTupleOrdinalPart()) {
javaExtractBaseRecordExpr = new MethodInvocation.Instance(
conditionVar,
"makeTupleRecordRetraction",
new JavaExpression[] {
LiteralWrapper.make(Integer.valueOf(retractedOrdinalFields.length))},
new JavaTypeName[] {JavaTypeName.INT},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
javaExtractBaseRecordExpr = new MethodInvocation.Instance(
conditionVar,
"makeOrdinalRecordRetraction",
new JavaExpression[] {
createOrdinalNamesArray(retractedOrdinalFields)},
new JavaTypeName[] {JavaTypeName.INT_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
}
}
} else if (retractedTextualFields.length > 0) {
javaExtractBaseRecordExpr = new MethodInvocation.Instance(
conditionVar,
"makeTextualRecordRetraction",
new JavaExpression[] {
createTextualNamesArray(retractedTextualFields)},
new JavaTypeName[] {JavaTypeName.STRING_ARRAY},
JavaTypeNames.RTRECORD_VALUE,
InvocationType.VIRTUAL);
} else {
javaExtractBaseRecordExpr = conditionVar;
}
LocalVariableDeclaration extractBaseRecordDeclaration =
new LocalVariableDeclaration(baseRecordPatternVar, javaExtractBaseRecordExpr);
recordCaseBlock.addStatement(extractBaseRecordDeclaration);
}
for (final Map.Entry<FieldName, String> entry : fieldBindingVarMap.entrySet()) {
FieldName fieldName = entry.getKey();
String bindingVarName = entry.getValue();
//ignore anonymous pattern variables. These are guaranteed not to be used
//by the result expression, and so don't need to be extracted from the condition record.
if (!bindingVarName.equals(Expression.RecordCase.WILDCARD_VAR)) {
QualifiedName qn = QualifiedName.make(currentModuleName, bindingVarName);
VarInfo.RecordField varInfo = variableContext.addRecordField(qn, null);
String javaBindingVarName = varInfo.getJavaName();
LocalName lazyRef = new LocalName(varInfo.getJavaName(), JavaTypeNames.RTVALUE);
varInfo.updateLazyReference(lazyRef);
varInfo.updateStrictReference(SCJavaDefn.createInvocation(lazyRef, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR));
LocalVariable bindingVar = new LocalVariable(javaBindingVarName, JavaTypeNames.RTVALUE);
JavaExpression javaExtractValueExpr;
if (fieldName instanceof FieldName.Textual) {
//javaBindingVarName = $recordCase.getTextualFieldValue(fieldName);
javaExtractValueExpr = new MethodInvocation.Instance(conditionVar, "getTextualFieldValue",
LiteralWrapper.make(fieldName.getCalSourceForm()), JavaTypeName.STRING,
JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
} else {
int ordinal = ((FieldName.Ordinal)fieldName).getOrdinal();
javaExtractValueExpr = new MethodInvocation.Instance(conditionVar, "getOrdinalFieldValue",
LiteralWrapper.make(Integer.valueOf(ordinal)), JavaTypeName.INT,
JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
}
LocalVariableDeclaration extractValueDeclaration = new LocalVariableDeclaration(bindingVar, javaExtractValueExpr);
recordCaseBlock.addStatement(extractValueDeclaration);
}
}
//encode the result expression in the context of the extended variable scope.
Expression resultExpr = recordCaseExpr.getResultExpr();
JavaStatement resultJavaStatement = genS_R(resultExpr, variableContext);
// Generate any let variables in this block and add them to the recordCaseBlock.
recordCaseBlock.addStatement(variableContext.popJavaScope());
// Add the body of the record case.
recordCaseBlock.addStatement(resultJavaStatement);
return recordCaseBlock;
}
private JavaField getReferencedDC (JavaTypeName type, DataConstructor dc) {
if ((dc == null) || (type == null)) {
throw new NullPointerException ("Invalid argument to getReferencedDC()");
}
for (final ReferencedDCInfo rfi : sharedValues.getReferencedDCs()) {
if (rfi.getGeneratedClassTypeName().equals(type)) {
DataConstructor odc = rfi.getDC();
if (dc == odc) {
return rfi.getJField();
//don't need to copy, JavaField is immutable!
//return new JavaField (field);
}
}
}
String baseFieldName = "i_" + dc.getName().getUnqualifiedName();
String fieldName = baseFieldName;
boolean conflict = true;
int appi = 2;
while (conflict) {
conflict = false;
if (this.instanceName != null && fieldName.equals(this.instanceName)) {
conflict = true;
fieldName = fieldName + appi;
appi++;
} else {
for (final ReferencedDCInfo rfi : sharedValues.getReferencedDCs()) {
JavaField field = rfi.getJField();
if (field.getFieldName().equals(fieldName)) {
conflict = true;
fieldName = baseFieldName + appi;
appi++;
break;
}
}
}
}
JavaTypeName fieldType = type;
JavaField field;
field =new JavaField.Static(thisTypeName, fieldName, fieldType);
sharedValues.add (new ReferencedDCInfo(field, type, dc));
return field;
}
/**
* Get a literal variable to use in place of a CAL literal.
* @param object the literal object from the compiler
* @return JavaField a field that will represent the literal.
* These will be emitted on call to genS_LiteralDefs.
* @throws CodeGenerationException
*/
private KernelLiteral getKernelLiteral(Object object) throws CodeGenerationException {
KernelLiteral kl = sharedValues.getKernelLiteral(object);
if (kl == null) {
throw new CodeGenerationException ("Reference to unknown literal value: " + object.toString());
}
return kl;
}
/**
* Determine if this SCJavaDefn is actually a data constructor
* @return boolean true if this is a constructor (otherwise this is a supercombinator)
*/
boolean isDataConstructor() {
return (dataConstructor != null);
}
/**
* Get the arity of this SC
* @return int the arity
*/
int getArity() {
return nArguments;
}
/**
* Obtain the compiler DataConstructor object if this is a data constructor
* @return DataConstructor the data constructor object. Will be null if this is a supercombinator
*/
DataConstructor getDataConstructor() {
return dataConstructor;
}
/**
* Attemp to create a java expression that 'unboxes' the given value into one of the
* built in types or an opaque type.
* @param unboxedType - the desired type of the unboxed value. Null if the type doesn't matter.
* @param value
* @return the unboxing expression or null if the unboxed class doesn't correspond to a CAL primitive
*/
static JavaExpression unboxValue (JavaTypeName unboxedType, JavaExpression value) {
assert (value != null) : ("Attempt to unbox null expression.");
if (unboxedType == null) {
// We don't care about the actual type.
return value;
}
if (unboxedType.equals(JavaTypeNames.RTVALUE)) {
return value;
} else if (unboxedType.equals(JavaTypeName.CHAR)) {
return createVirtualInvocation(value, "getCharValue", JavaTypeName.CHAR);
} else if (unboxedType.equals(JavaTypeName.BOOLEAN)) {
return createVirtualInvocation(value, "getBooleanValue", JavaTypeName.BOOLEAN);
} else if (unboxedType.equals(JavaTypeName.INT)) {
return SCJavaDefn.createInvocation(value, SCJavaDefn.GETORDINALVALUE);
} else if (unboxedType.equals(JavaTypeName.DOUBLE)) {
return createVirtualInvocation(value, "getDoubleValue", JavaTypeName.DOUBLE);
} else if (unboxedType.equals(JavaTypeName.BYTE)) {
return createVirtualInvocation(value, "getByteValue", JavaTypeName.BYTE);
} else if (unboxedType.equals(JavaTypeName.SHORT)) {
return createVirtualInvocation(value, "getShortValue", JavaTypeName.SHORT);
} else if (unboxedType.equals(JavaTypeName.FLOAT)) {
return createVirtualInvocation(value, "getFloatValue", JavaTypeName.FLOAT);
} else if (unboxedType.equals(JavaTypeName.LONG)) {
return createVirtualInvocation(value, "getLongValue", JavaTypeName.LONG);
} else if (unboxedType.equals(JavaTypeName.STRING)) {
return createVirtualInvocation(value, "getStringValue", JavaTypeName.STRING);
} else if (unboxedType.equals(JavaTypeName.BIG_INTEGER)) {
return createVirtualInvocation(value, "getIntegerValue", JavaTypeName.BIG_INTEGER);
} else if (unboxedType.equals(JavaTypeName.CAL_VALUE)) {
// Casting to CalValue here is unnecessary as RTValue is derived from CalValue
return value;
} else {
// ((typeName)(argDef.getOpaqueValue()))
JavaExpression arg = createVirtualInvocation(value, "getOpaqueValue", JavaTypeName.OBJECT);
if (unboxedType.equals(JavaTypeName.OBJECT)) {
// getOpaqueValue() returns an Object no need to cast.
return arg;
} else {
return new CastExpression(unboxedType, arg); // cast to argClass
}
}
}
/**
* Unmarshal an argument from java to CAL. This is used to convert the return value from a foreign function
* call to one of the RTValue derived classes.
*
* @param argClass The return type of the Java call.
* @param definition Def the definition/expression of the Java type.
* @return JavaExpression the expression to get the CAL type.
*/
private ExpressionContextPair returnTypeToCal(Class<?> argClass, JavaExpression definition) {
// Variables from which to construct the ExpressionContextPair
JavaExpression javaExpression = null;
Block context = new Block();
if (argClass == void.class) {
// hopefully definition will be a method invocation, rather than object creation or a field..
context.addStatement(new ExpressionStatement(definition));
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
javaExpression = createMakeKernelIntInvocation(LiteralWrapper.make (Integer.valueOf(0)));
} else {
JavaTypeName calUnitClassName = JavaTypeName.make(CALToJavaNames.createFullPackageName(CALToJavaNames.createPackageNameFromModule(CAL_Prelude.MODULE_NAME)) + ".TYPE_Unit$CAL_Unit", false);
javaExpression = new MethodInvocation.Static(calUnitClassName, "make", EXECUTION_CONTEXT_VAR, JavaTypeNames.RTEXECUTION_CONTEXT, JavaTypeNames.RTFUNCTION);
}
} else
if (argClass == byte.class) {
javaExpression = createMakeKernelByteInvocation(definition);
} else
if (argClass == short.class) {
javaExpression = createMakeKernelShortInvocation(definition);
} else
if (argClass == long.class) {
javaExpression = createMakeKernelLongInvocation(definition);
} else
if (argClass == float.class) {
javaExpression = createMakeKernelFloatInvocation(definition);
} else
if (argClass == char.class) {
javaExpression = createMakeKernelCharInvocation(definition);
} else
if (argClass == boolean.class) {
javaExpression = createMakeKernelBooleanInvocation(definition);
} else
if (argClass == int.class) {
javaExpression = createMakeKernelIntInvocation(definition);
} else
if (argClass == double.class) {
javaExpression = createMakeKernelDoubleInvocation(definition);
} else
if (argClass == java.lang.String.class) {
javaExpression = createMakeKernelStringInvocation(definition);
} else
if (argClass == java.math.BigInteger.class) {
javaExpression = createMakeKernelIntegerInvocation(definition);
} else
if (isCalValueClass(argClass)) {
javaExpression = new CastExpression (JavaTypeNames.RTVALUE, definition);
} else
{
javaExpression = createMakeKernelOpaqueInvocation(definition);
}
return new ExpressionContextPair(javaExpression, context);
}
/**
* Generate the java model for a literal list in CAL code. (i.e. [a, b, c, d]).
* @param constructorOpExpressions
* @param scheme
* @param variableContext
* @return the ExpressionContextPair describing the java equivalent of the literal list.
* @throws CodeGenerationException
*/
private ExpressionContextPair compileLiteralList (ConstructorOpTuple constructorOpExpressions, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
// Because of the recursive nature of the List data structur we handle literal lists as a special case.
DataConstructor dc = constructorOpExpressions.getDataConstructor ();
QualifiedName qn = dc.getName();
if (!qn.equals(CAL_Prelude.DataConstructors.Cons)) {
return null;
}
JavaTypeName dcTypeName = CALToJavaNames.createTypeNameFromDC(dc, module);
int nArgs = 2;
boolean addEC = LECCMachineConfiguration.passExecContextToDataConstructors();
if (addEC) {
nArgs++;
}
JavaExpression constructorArgs[] = null;
JavaExpression lastConstructorArgs[] = null;
JavaTypeName constructorArgTypes[] = new JavaTypeName[nArgs];
Arrays.fill(constructorArgTypes, JavaTypeNames.RTVALUE);
if (addEC) {
constructorArgTypes[dc.getArity()] = JavaTypeNames.RTEXECUTION_CONTEXT;
}
JavaExpression listCreation = null;
Block contextBlock = null;
boolean moreListElements = true;
do {
moreListElements = false;
ExpressionContextPair leftArg = genS_C(constructorOpExpressions.getArgument(0), variableContext);
if (contextBlock == null) {
contextBlock = leftArg.getContextBlock();
} else {
contextBlock.addStatement(leftArg.getContextBlock());
}
constructorArgs = new JavaExpression [nArgs];
if (addEC) {
constructorArgs[dc.getArity()] = EXECUTION_CONTEXT_VAR;
}
constructorArgs[0] = leftArg.getJavaExpression();
ClassInstanceCreationExpression cc =
new ClassInstanceCreationExpression (dcTypeName, constructorArgs, constructorArgTypes);
if (listCreation == null) {
listCreation = cc;
}
if (lastConstructorArgs != null) {
lastConstructorArgs[1] = cc;
}
lastConstructorArgs = constructorArgs;
// The second argument will always be a data constructor, either cons or Nil.
ConstructorOpTuple constructorOpExpressions2 = ConstructorOpTuple.isConstructorOp(constructorOpExpressions.getArgument(1), false);
if (constructorOpExpressions2 != null && constructorOpExpressions2.getDataConstructor().getName().equals(CAL_Prelude.DataConstructors.Cons)) {
constructorOpExpressions = constructorOpExpressions2;
moreListElements = true;
}
} while (moreListElements);
ExpressionContextPair terminator = genS_C(constructorOpExpressions.getArgument(1), variableContext);
contextBlock.addStatement(terminator.getContextBlock());
lastConstructorArgs[1] = terminator.getJavaExpression();
return new ExpressionContextPair (listCreation, contextBlock);
}
/**
* Generate java code corresponding to a data constructor.
* @param constructorOpExpressions
* @param scheme
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateDataConstructor(ConstructorOpTuple constructorOpExpressions, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
// If this is a literal list (i.e. a saturated application of Prelude.Cons) then we can
// handle it in a non-recursive fashion.
ExpressionContextPair list = compileLiteralList(constructorOpExpressions, scheme, variableContext);
if (list != null) {
return list;
}
DataConstructor dc = constructorOpExpressions.getDataConstructor ();
// If we are dealing with a data constructor for the CAL type boolean we want to optimize
// by substituting and instance of the literal RTKernel.CAL_Boolean.
if (isTrueOrFalseDataCons(dc)) {
LiteralWrapper boolWrapper = LiteralWrapper.make(Boolean.valueOf(isTrueDataCons(dc)));
return new ExpressionContextPair(createMakeKernelBooleanInvocation(boolWrapper));
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (isEnumDataType(dc)) {
// We should never see a DataConstructor instance for an enumeration type.
throw new CodeGenerationException("Encountered enumeration data constructor: " + dc.getName().getQualifiedName());
}
}
int arity = dc.getArity();
JavaTypeName dcTypeName = CALToJavaNames.createTypeNameFromDC(dc, module);
Block argContext = new Block();
boolean[] fieldStrictness = new boolean [dc.getArity()];
boolean dcHasStrictFields = false;
if (LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS) {
Arrays.fill(fieldStrictness, false);
} else {
for (int i = 0; i < dc.getArity(); ++i) {
fieldStrictness[i] = dc.isArgStrict(i);
if (fieldStrictness[i]) {
dcHasStrictFields = true;
}
}
}
// If there are no strict arguments we can simply create an instance of the DC class.
// The simplest way to do this is to treat this DC application as if it were in a strict context.
if (!dcHasStrictFields) {
scheme = Scheme.E_SCHEME;
} else {
// If all strict arguments are already evaluated, or we consider them safe to evaluate (i.e. cheap and
// with no side effects) we can treat this as strict.
boolean allOK = true;
for (int i = 0; i < dc.getArity(); ++i) {
if (dc.getArgStrictness()[i] && !canIgnoreLaziness(constructorOpExpressions.getArgument(i), variableContext)) {
allOK = false;
break;
}
}
if (allOK) {
scheme = Scheme.E_SCHEME;
}
}
if (arity < 0) {
throw new CodeGenerationException("Invalid constructor operator arity: " + arity);
} else
if (arity == 0) {
JavaExpression dcInstance;
JavaTypeName typeName = CALToJavaNames.createTypeNameFromDC(dc, module);
if (LECCMachineConfiguration.generateStatistics()) {
// If this is a TagDC we want to pass the ordinal as an argument
if (isTagDC(dc, getModule())) {
dcInstance = new ClassInstanceCreationExpression(typeName, JavaExpression.LiteralWrapper.make(Integer.valueOf(dc.getOrdinal())), JavaTypeName.INT);
} else {
dcInstance = new ClassInstanceCreationExpression(typeName, SCJavaDefn.EXECUTION_CONTEXT_VAR, JavaTypeNames.RTEXECUTION_CONTEXT);
}
} else {
dcInstance = getReferencedDC(typeName, dc);
}
return new ExpressionContextPair(dcInstance, argContext);
} else{
if (scheme == Scheme.C_SCHEME) {
// This is a fully saturated DC application in a lazy context.
// If there are no strict fields in the data constructor we can
// simply create an instance of it. Otherwise create an
// appropriate application node.
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[dc.getArity()];
for (int i = 0; i < dc.getArity(); ++i) {
ecp[i] = genS_C(constructorOpExpressions.getArgument(i), variableContext);
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < dc.getArity(); ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
JavaExpression dcExpr = expressionVarToJavaDef(constructorOpExpressions.getDataConstructorExpression(), scheme, variableContext);
if (dc.getArity() <= LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH) {
JavaExpression nodeArgs[] = new JavaExpression[dc.getArity() + 1];
JavaTypeName nodeArgTypes[] = new JavaTypeName[dc.getArity() + 1];
nodeArgs[0] = dcExpr;
nodeArgTypes[0] = JavaTypeNames.RTSUPERCOMBINATOR;
for (int i = 0; i < dc.getArity(); ++i) {
nodeArgs[i+1] = ecp[i].getJavaExpression();
nodeArgTypes[i+1] = JavaTypeNames.RTVALUE;
}
JavaTypeName appNodeType = getTypeNameForApplicationNode(dc.getArity(), false);
return new ExpressionContextPair(new JavaExpression.ClassInstanceCreationExpression(appNodeType, nodeArgs, nodeArgTypes), newContext);
} else {
JavaExpression target = dcExpr;
for (int i = 0; i < dc.getArity(); ++i) {
target = createInvocation(target, APPLY, ecp[i].getJavaExpression());
}
return new ExpressionContextPair (target, newContext);
}
} else {
// This is a fully saturated DC application in a strict context. Create a
// new DC class instance.
// First generate the java expressions for the members.
ExpressionContextPair[] ecp = new ExpressionContextPair[dc.getArity()];
TypeExpr[] fieldTypes = SCJavaDefn.getFieldTypesForDC(dc);
for (int i = 0; i < dc.getArity(); ++i) {
if (fieldStrictness[i]) {
if (SCJavaDefn.canTypeBeUnboxed(fieldTypes[i])) {
ecp[i] = generateUnboxedArgument(typeExprToTypeName(fieldTypes[i]), constructorOpExpressions.getArgument(i), variableContext);
} else {
ecp[i] = genS_E(constructorOpExpressions.getArgument(i), variableContext);
}
} else {
ecp[i] = genS_C(constructorOpExpressions.getArgument(i), variableContext);
}
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < dc.getArity(); ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
int nArgs = dc.getArity();
boolean addEC = LECCMachineConfiguration.passExecContextToDataConstructors();
if (addEC) {
nArgs++;
}
JavaExpression[] constructorArgs = new JavaExpression [nArgs];
JavaTypeName[] constructorArgTypes = new JavaTypeName [nArgs];
for (int i = 0; i < dc.getArity(); ++i) {
constructorArgs[i] = ecp[i].getJavaExpression();
constructorArgTypes[i] = JavaTypeNames.RTVALUE;
if (fieldStrictness[i] && SCJavaDefn.canTypeBeUnboxed(fieldTypes[i])) {
constructorArgTypes[i] = SCJavaDefn.typeExprToTypeName(fieldTypes[i]);
}
}
if (addEC) {
constructorArgs[dc.getArity()] = EXECUTION_CONTEXT_VAR;
constructorArgTypes[dc.getArity()] = JavaTypeNames.RTEXECUTION_CONTEXT;
}
ClassInstanceCreationExpression cc = new ClassInstanceCreationExpression (dcTypeName, constructorArgs, constructorArgTypes);
return new ExpressionContextPair (cc, newContext);
}
}
}
/**
* Generate the source code for a call to a foreign function in a strict context.
* @param e the expression which is an application of a foreign function.
* @param boxResult
* @param scheme
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateForeignCall(Expression e, boolean boxResult, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
if (codeGenerationStats != null) {
codeGenerationStats.incrementDirectForeignCalls();
}
// Unpack the basic op into subexpressions
final BasicOpTuple basicOpExpressions = BasicOpTuple.isBasicOp(e);
final ForeignFunctionInfo foreignFunctionInfo = basicOpExpressions.getForeignFunctionInfo();
final ForeignFunctionInfo.JavaKind kind = foreignFunctionInfo.getJavaKind();
JavaExpression returnExpression = null;
Block returnContext = new Block();
if (kind.isMethod()) {
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
final Method method = (Method)SCJavaDefn.getJavaProxy(invocationInfo);
if (method.getReturnType() == void.class && (scheme != Scheme.R_SCHEME)) {
ExpressionContextPair ecp = genS_C (e, variableContext);
JavaExpression expr = ecp.getJavaExpression();
JavaStatement block = ecp.getContextBlock();
JavaExpression.MethodInvocation eval = createInvocation(expr, EVALUATE, EXECUTION_CONTEXT_VAR);
return new ExpressionContextPair(eval, block);
}
int nJavaArgs = SCJavaDefn.getNArguments(foreignFunctionInfo);
final Class<?> invocationClass = SCJavaDefn.getInvocationClass(invocationInfo);
final JavaTypeName invocationClassName = JavaTypeName.make(invocationClass);
String methodName = method.getName();
int startArg = 0;
Class<?>[] exceptions = method.getExceptionTypes();
for (int i = 0; i < exceptions.length; ++i) {
if (!exceptionHandled(exceptions [i])) {
addExceptionHandler(exceptions[i]);
}
}
if (!LECCMachineConfiguration.generateDirectPrimOpCalls()) {
if (!exceptionHandled(Throwable.class)) {
addExceptionHandler(Throwable.class);
}
}
JavaExpression target;
InvocationType invocationType; // static, virtual, or interface. Can't be special (ie. private method).
if (kind.isStatic()){
target = null;
invocationType = InvocationType.STATIC;
} else {
ExpressionContextPair pair = generateUnboxedForeignFunctionArgument(invocationClassName, basicOpExpressions.getArgument(0), variableContext);
target = pair.getJavaExpression();
invocationType = invocationClass.isInterface() ? InvocationType.INTERFACE : InvocationType.VIRTUAL;
returnContext.addStatement(pair.getContextBlock());
startArg = 1;
}
Class<?>[] argTypes = method.getParameterTypes();
JavaExpression[] args = new JavaExpression[argTypes.length];
JavaTypeName[] argTypeNames = new JavaTypeName[argTypes.length];
for (int i = startArg, j = 0; i < nJavaArgs; ++i, ++j) {
int index = i - startArg;
final JavaTypeName argTypeName = JavaTypeName.make(argTypes[index]);
ExpressionContextPair pair = generateUnboxedForeignFunctionArgument(argTypeName, basicOpExpressions.getArgument(i), variableContext);
args[index] = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
argTypeNames[index] = argTypeName;
}
JavaTypeName returnType = JavaTypeName.make(method.getReturnType());
if (kind.isStatic()) {
returnExpression = new MethodInvocation.Static(invocationClassName, methodName, args, argTypeNames, returnType);
} else {
returnExpression = new MethodInvocation.Instance(target, methodName, invocationClassName, args, argTypeNames, returnType, invocationType);
}
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal(method.getReturnType(), returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isField()) {
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
final Field field = (Field)SCJavaDefn.getJavaProxy(invocationInfo);
JavaTypeName fieldType = JavaTypeName.make(field.getType());
String fieldName = field.getName();
final JavaTypeName invocationClassName = JavaTypeName.make(SCJavaDefn.getInvocationClass(invocationInfo));
if (kind.isStatic()) {
returnExpression = new JavaField.Static(invocationClassName, fieldName, fieldType);
} else {
ExpressionContextPair pair = generateUnboxedForeignFunctionArgument(invocationClassName, basicOpExpressions.getArgument(0), variableContext);
JavaExpression instance = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
returnExpression = new JavaField.Instance(instance, fieldName, fieldType);
}
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal (field.getType(), returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isConstructor()) {
final ForeignFunctionInfo.Invocation invocationInfo = (ForeignFunctionInfo.Invocation)foreignFunctionInfo;
Constructor<?> constructor = (Constructor<?>)SCJavaDefn.getJavaProxy(invocationInfo);
int nJavaArgs = SCJavaDefn.getNArguments(foreignFunctionInfo);
Class<?> clazz = constructor.getDeclaringClass();
Class<?>[] argTypes = constructor.getParameterTypes();
Class<?>[] exceptions = constructor.getExceptionTypes();
for (int i = 0; i < exceptions.length; ++i) {
if (!exceptionHandled(exceptions [i])) {
addExceptionHandler(exceptions[i]);
}
}
JavaExpression[] args = new JavaExpression[argTypes.length];
JavaTypeName[] argTypeNames = new JavaTypeName[argTypes.length];
for (int i = 0; i < nJavaArgs; i++) {
final JavaTypeName argTypeName = JavaTypeName.make(argTypes[i]);
ExpressionContextPair pair = generateUnboxedForeignFunctionArgument(argTypeName, basicOpExpressions.getArgument(i), variableContext);
args[i] = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
argTypeNames[i] = argTypeName;
}
returnExpression = new ClassInstanceCreationExpression(JavaTypeName.make(clazz), args, argTypeNames);
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal(constructor.getDeclaringClass(), returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isCast()) {
final Class<?> argType = SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, 0);
final Class<?> resultType = SCJavaDefn.getJavaReturnType(foreignFunctionInfo);
final ExpressionContextPair argExprPair = generateUnboxedForeignFunctionArgument(JavaTypeName.make(argType), basicOpExpressions.getArgument(0), variableContext);
final JavaExpression argExpr = argExprPair.getJavaExpression();
returnContext.addStatement(argExprPair.getContextBlock());
if (kind == ForeignFunctionInfo.JavaKind.IDENTITY_CAST ||
kind == ForeignFunctionInfo.JavaKind.WIDENING_REFERENCE_CAST) {
//it is important to do nothing for a widening reference cast (except for evaluating)
//this is because at the JavaTypeName level, where the inheritance hierarchy is not available, it is not possible for
//the bytecode generator to determine if this is truly a widening reference cast i.e. a no-op. Hence we must make
//this optimization at this point.
returnExpression = argExpr;
} else if (kind == ForeignFunctionInfo.JavaKind.NARROWING_PRIMITIVE_CAST ||
kind == ForeignFunctionInfo.JavaKind.WIDENING_PRIMITIVE_CAST ||
kind == ForeignFunctionInfo.JavaKind.NARROWING_REFERENCE_CAST) {
returnExpression = new JavaExpression.CastExpression(JavaTypeName.make(resultType), argExpr);
} else {
throw new CodeGenerationException("Unrecognized foreign function cast kind: " + kind);
}
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal(resultType, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isInstanceOf()) {
final Class<?> argType = SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, 0);
final ForeignFunctionInfo.InstanceOf instanceOfInfo = (ForeignFunctionInfo.InstanceOf)foreignFunctionInfo;
final Class<?> instanceOfType = SCJavaDefn.getInstanceOfType(instanceOfInfo);
final ExpressionContextPair argExprPair = generateUnboxedForeignFunctionArgument(JavaTypeName.make(argType), basicOpExpressions.getArgument(0), variableContext);
final JavaExpression argExpr = argExprPair.getJavaExpression();
returnContext.addStatement(argExprPair.getContextBlock());
returnExpression = new JavaExpression.InstanceOf(argExpr, JavaTypeName.make(instanceOfType));
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal(boolean.class, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isClassLiteral()) {
final ForeignFunctionInfo.ClassLiteral classLiteralInfo = (ForeignFunctionInfo.ClassLiteral)foreignFunctionInfo;
final Class<?> referentType = SCJavaDefn.getReferentType(classLiteralInfo);
returnExpression = new JavaExpression.ClassLiteral(JavaTypeName.make(referentType));
if (boxResult) {
ExpressionContextPair pair = returnTypeToCal(Class.class, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isNullLiteral()) {
returnExpression = JavaExpression.LiteralWrapper.NULL;
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(SCJavaDefn.getJavaReturnType(foreignFunctionInfo), returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind.isNullCheck()) {
final ForeignFunctionInfo.NullCheck nullCheckInfo = (ForeignFunctionInfo.NullCheck)foreignFunctionInfo;
final Class<?> argType = SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, 0);
final ExpressionContextPair argExprPair = generateUnboxedForeignFunctionArgument(JavaTypeName.make(argType), basicOpExpressions.getArgument(0), variableContext);
final JavaExpression argExpr = argExprPair.getJavaExpression();
returnContext.addStatement(argExprPair.getContextBlock());
final JavaOperator javaOp = nullCheckInfo.checkIsNull() ? JavaOperator.EQUALS_OBJECT : JavaOperator.NOT_EQUALS_OBJECT;
returnExpression = new JavaExpression.OperatorExpression.Binary(javaOp, argExpr, LiteralWrapper.NULL);
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(boolean.class, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind == ForeignFunctionInfo.JavaKind.NEW_ARRAY) {
//e.g. newString3Array :: Int -> Int -> JString3Array; (for new String[d1][d2][])
//note, this may be less than the dimension of the array e.g. for new String[10][7][] this is 2.
final int nJavaArgs = SCJavaDefn.getNArguments(foreignFunctionInfo);
final Class<?> newArrayType = SCJavaDefn.getJavaReturnType(foreignFunctionInfo);
final JavaExpression[] args = new JavaExpression[nJavaArgs];
final JavaTypeName[] argTypeNames = new JavaTypeName[nJavaArgs];
for (int i = 0; i < nJavaArgs; i++) {
final ExpressionContextPair pair = generateUnboxedForeignFunctionArgument(JavaTypeName.INT, basicOpExpressions.getArgument(i), variableContext);
args[i] = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
argTypeNames[i] = JavaTypeName.INT;
}
returnExpression = new ClassInstanceCreationExpression(JavaTypeName.make(newArrayType), args, argTypeNames);
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(newArrayType, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind == ForeignFunctionInfo.JavaKind.LENGTH_ARRAY) {
final Class<?> argType = SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, 0);
final ExpressionContextPair argExprPair = generateUnboxedForeignFunctionArgument(JavaTypeName.make(argType), basicOpExpressions.getArgument(0), variableContext);
final JavaExpression argExpr = argExprPair.getJavaExpression();
returnContext.addStatement(argExprPair.getContextBlock());
returnExpression = new JavaExpression.ArrayLength(argExpr);
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(int.class, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind == ForeignFunctionInfo.JavaKind.SUBSCRIPT_ARRAY) {
//e.g. subscriptString3Array :: String3Array -> Int -> Int -> StringArray;
//for subscripting using 2 indices a 3-dimensional String array to get a 1-dimensional String array
//note, this may be less than the dimension of the array e.g. for new String[10][7][] this is 2.
final int nJavaArgs = SCJavaDefn.getNArguments(foreignFunctionInfo);
final Class<?> subscriptedArrayType = SCJavaDefn.getJavaReturnType(foreignFunctionInfo);
for (int i = 0; i < nJavaArgs; i++) {
final JavaTypeName argTypeName = JavaTypeName.make(SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, i));
final ExpressionContextPair pair =
generateUnboxedForeignFunctionArgument(argTypeName, basicOpExpressions.getArgument(i), variableContext);
returnContext.addStatement(pair.getContextBlock());
if (i == 0) {
//the initial array expression
returnExpression = pair.getJavaExpression();
} else {
//subscript by the next index
returnExpression = new JavaExpression.ArrayAccess(returnExpression, pair.getJavaExpression());
}
}
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(subscriptedArrayType, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
} else if (kind == ForeignFunctionInfo.JavaKind.UPDATE_ARRAY) {
//e.g. updateString3Array :: String3Array -> Int -> Int -> StringArray -> StringArray;
//for updating using 2 indices a 3-dimensional String array with a 1-dimensional String array
//note, this may be less than the dimension of the array to update
final int nJavaArgs = SCJavaDefn.getNArguments(foreignFunctionInfo);
final Class<?> updatedElementType = SCJavaDefn.getJavaReturnType(foreignFunctionInfo);
for (int i = 0; i < nJavaArgs; i++) {
final JavaTypeName argTypeName = JavaTypeName.make(SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, i));
final ExpressionContextPair pair =
generateUnboxedForeignFunctionArgument(argTypeName, basicOpExpressions.getArgument(i), variableContext);
returnContext.addStatement(pair.getContextBlock());
if (i == 0) {
//the initial array expression
returnExpression = pair.getJavaExpression();
} else if (i < nJavaArgs - 1) {
//subscript by the next index
returnExpression = new JavaExpression.ArrayAccess(returnExpression, pair.getJavaExpression());
} else if (i == nJavaArgs - 1) {
returnExpression = new JavaExpression.Assignment((JavaExpression.Nameable)returnExpression, pair.getJavaExpression());
}
}
if (boxResult) {
final ExpressionContextPair pair = returnTypeToCal(updatedElementType, returnExpression);
returnExpression = pair.getJavaExpression();
returnContext.addStatement(pair.getContextBlock());
}
return new ExpressionContextPair(returnExpression, returnContext);
}
throw new CodeGenerationException("Unrecognized foreign function kind: " + kind);
}
private boolean isArgStrict (QualifiedName qn) {
return isArgStrict (qn.getUnqualifiedName());
}
/**
* Generate code for non-recursive let.
* @param let
* @param scheme
* @param unboxBodyType
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
* Note: in the case of Scheme R (type 0), the javaExpression member will be null, and the let will be fully-expressed
* by the statements in the context.
*/
private ExpressionContextPair generateLetNonRec(Expression.LetNonRec let,
Scheme scheme,
JavaTypeName unboxBodyType,
VariableContext variableContext) throws CodeGenerationException {
// Add the let variable to the variable scope.
VarInfo.LetNonRec varInfo = variableContext.addLetNonRecVar(QualifiedName.make(currentModuleName, let.getDefn().getVar()), let.getDefn().getVarType());
// Determine if for the letvar definition can ignore laziness.
boolean canIgnoreLaziness = canIgnoreLaziness(let.getDefn().getExpr(), variableContext);
if (!canIgnoreLaziness) {
// Check to see if the def is a call to a lifted var definition
Expression defExpr = let.getDefn().getExpr();
Expression.Var defVar = defExpr.asVar();
if (defVar == null && defExpr.asAppl() != null) {
Expression[] chain = appChain(defExpr.asAppl());
defVar = chain[0].asVar();
}
if (defVar != null) {
MachineFunction mf = module.getFunction(defVar.getName());
if (mf != null && mf instanceof LECCLiftedLetVarMachineFunction) {
canIgnoreLaziness = ((LECCLiftedLetVarMachineFunction)mf).canIgnoreLaziness();
}
}
}
varInfo.setEvaluated(canIgnoreLaziness);
// Set up the references to use when referring to this variable.
// These are set up as PlaceHolder instances, since the nature of
// the reference will be determined by how the variable is used.
// i.e. single vs. multiple use, boxed vs. unboxed, strict vs. lazy
if (canIgnoreLaziness) {
if (varInfo.getUnboxedType() != null) {
JavaExpression localVar = new LocalVariable(varInfo.getJavaName()+"$U", varInfo.getUnboxedType());
varInfo.updateUnboxedReference(localVar);
JavaExpression boxedDef = SCJavaDefn.boxExpression(varInfo.getUnboxedType(), localVar);
varInfo.updateStrictReference(boxedDef);
varInfo.updateLazyReference(boxedDef);
} else {
JavaExpression localVar = new LocalVariable(varInfo.getJavaName(), JavaTypeNames.RTVALUE);
varInfo.updateStrictReference(localVar);
varInfo.updateLazyReference(localVar);
}
} else {
JavaExpression localVar = new JavaExpression.LocalVariable(varInfo.getJavaName(), JavaTypeNames.RTVALUE);
varInfo.updateLazyReference(localVar);
JavaExpression evaluatedVar = SCJavaDefn.createInvocation(localVar, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
varInfo.updateStrictReference(evaluatedVar);
if (varInfo.getUnboxedType() != null) {
varInfo.updateUnboxedReference(SCJavaDefn.unboxValue(varInfo.getUnboxedType(), evaluatedVar));
}
}
// Compile the body (i.e. the 'in' part).
ExpressionContextPair compiledBody;
if (scheme == Scheme.UNBOX_INTERNAL_SCHEME) {
compiledBody = generateUnboxedArgument(unboxBodyType, let.getBody(), variableContext);
} else if (scheme == Scheme.UNBOX_FOREIGN_SCHEME) {
compiledBody = generateUnboxedForeignFunctionArgument(unboxBodyType, let.getBody(), variableContext);
} else if (scheme == Scheme.R_SCHEME) {
// Special case for scheme R (see Javadoc for this method).
JavaStatement rStatement = genS_R(let.getBody(), variableContext);
compiledBody = new ExpressionContextPair(null, rStatement);
} else if (scheme == Scheme.E_SCHEME) {
compiledBody = genS_E(let.getBody(), variableContext);
} else if (scheme == Scheme.C_SCHEME) {
compiledBody = genS_C(let.getBody(), variableContext);
} else {
throw new CodeGenerationException("Unrecognized let type: " + scheme);
}
if (canIgnoreLaziness) {
if(varInfo.getUnboxedType() != null) {
// Compile an unboxed definition function.
// Compile the let variable definition to produce an unboxed value
JavaTypeName unboxedType = varInfo.getUnboxedType();
ExpressionContextPair compiledVarDef =
generateUnboxedArgument(unboxedType,
let.getDefn().getExpr(),
variableContext);
// Now update the unboxed var definition.
varInfo.updateUnboxedVarDef(compiledVarDef.getJavaExpression());
} else {
// Compile a strict definition function.
// Compile the let variable definition to produce a fully evaluated boxed value.
ExpressionContextPair compiledVarDef = genS_E(let.getDefn().getExpr(), variableContext);
// Now update the strict var def.
varInfo.updateStrictVarDef(compiledVarDef.getJavaExpression());
}
} else {
// Compile the let variable definition to produce a lazy program graph.
ExpressionContextPair compiledVarDef = genS_C(let.getDefn().getExpr(), variableContext);
// Update the lazy var def.
varInfo.updateLazyVarDef(compiledVarDef.getJavaExpression());
}
return compiledBody;
}
private JavaTypeName getTypeNameForForeignFunctionArg (ForeignFunctionInfo foreignFunctionInfo, int argNum) throws CodeGenerationException{
return JavaTypeName.make(SCJavaDefn.getJavaArgumentType(foreignFunctionInfo, argNum));
}
private JavaTypeName getTypeNameForPrimitiveOpArg (BasicOpTuple bot, int argNum) throws CodeGenerationException {
JavaTypeName tn = PrimOp.getTypeNameForPrimOpArgument(bot.getPrimitiveOp(), argNum);
if (tn == null) {
if (bot.getPrimitiveOp() == PrimOps.PRIMOP_FOREIGN_FUNCTION) {
tn = getTypeNameForForeignFunctionArg (bot.getForeignFunctionInfo(), argNum);
} else {
throw new CodeGenerationException ("Attempt to retrieve argument type for " + bot.getName() + ", argNum = " + argNum + ".");
}
}
return tn;
}
/**
* Generate code for recursive let variable(s).
* @param let
* @param scheme
* @param variableContext
* @return ExpressionContextPair
* Note: in the case of Scheme R , the javaExpression member will be null, and the let will be fully-expressed
* by the statements in the context.
* @throws CodeGenerationException
*/
private ExpressionContextPair generateLetRec(Expression.LetRec let, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
Expression.Let.LetDefn[] defns = let.getDefns();
VarInfo.LetRec letVars[] = new VarInfo.LetRec[defns.length];
// First enhance the variable scope with the let variable names.
for (int i = 0; i < defns.length; ++i) {
if (codeGenerationStats != null) {
codeGenerationStats.incrementLetRecCount(defns.length);
}
Expression.Let.LetDefn def = defns [i];
QualifiedName qn = QualifiedName.make(currentModuleName, def.getVar());
letVars[i] = variableContext.addLetRecVar(qn, def.getVarType());
LocalName lazyRef = new LocalName(letVars[i].getJavaName(), JavaTypeNames.RTVALUE);
letVars[i].updateLazyReference(lazyRef);
letVars[i].updateStrictReference(SCJavaDefn.createInvocation(lazyRef, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR));
}
// Now generate the definitions for the let vars.
for (int i = 0; i < defns.length; ++i) {
Expression.Let.LetDefn def = defns [i];
ExpressionContextPair varDef = genS_C (def.getExpr(), variableContext);
letVars[i].updateLazyVarDef(varDef.getJavaExpression());
}
ExpressionContextPair returnValue = null;
if (scheme == Scheme.R_SCHEME) {
// Special case for scheme R (see Javadoc for this method).
JavaStatement rStatement = genS_R (let.getBody(), variableContext);
returnValue = new ExpressionContextPair(null, rStatement);
} else if (scheme == Scheme.E_SCHEME) {
returnValue = genS_E (let.getBody(), variableContext);
} else if (scheme == Scheme.C_SCHEME) {
returnValue = genS_C (let.getBody(), variableContext);
} else {
throw new CodeGenerationException("Unrecognized let type: " + scheme);
}
return returnValue;
}
private boolean isNot (Expression e) {
if (e.asAppl() != null &&
e.asAppl().getE1().asVar() != null &&
e.asAppl().getE1().asVar().getName().equals(CAL_Prelude.Functions.not)) {
return true;
}
return false;
}
private ExpressionContextPair generateNot (Expression e, boolean boxResult, VariableContext variableContext) throws CodeGenerationException {
// Get the argument.
Expression arg = e.asAppl().getE2();
ExpressionContextPair ecp = generateUnboxedArgument(JavaTypeName.BOOLEAN, arg, variableContext);
JavaExpression not = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, ecp.getJavaExpression());
if (boxResult) {
not = createMakeKernelBooleanInvocation(not);
}
return new ExpressionContextPair (not, ecp.getContextBlock());
}
private ExpressionContextPair generateAndOr (Expression e, boolean boxResult, VariableContext variableContext) throws CodeGenerationException {
if (codeGenerationStats != null) {
codeGenerationStats.incrementOptimizedAndOr();
}
// Unpack the basic op into subexpressions
BasicOpTuple basicOpExpressions = BasicOpTuple.isAndOr (e);
// Code a basic operation
int op = basicOpExpressions.getPrimitiveOp ();
int nArgs = basicOpExpressions.getNArguments ();
if (nArgs < 0 || nArgs > 2) {
throw new CodeGenerationException("Invalid basic operator arity: " + nArgs);
}
// The arguments for primitive ops are handled as a special case.
// We want to avoid boxing/unboxing of primitive values wherever possible.
Block generatedContext = new Block();
JavaExpression args[] = new JavaExpression[nArgs];
for (int i = 0; i < nArgs; ++i) {
int argIndex = nArgs - i - 1;
ExpressionContextPair pair = generateUnboxedPrimOpArgument(basicOpExpressions, basicOpExpressions.getArgument(argIndex), argIndex, variableContext);
args[argIndex] = pair.getJavaExpression();
generatedContext.addStatement(pair.getContextBlock());
}
JavaExpression javaExpression = PrimOp.getPrimOpDefinition(op, args);
if (boxResult) {
javaExpression = boxPrimitiveOpResult(op, javaExpression);
}
return new ExpressionContextPair(javaExpression, generatedContext);
}
/**
* Generate the source code for a primitive op.
* Note: args will be strictly evaluated. Use generateAndOr() for lazy evaluation in the second arg.
* @param e
* @param boxResult
* @param scheme
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generatePrimitiveOp (Expression e, boolean boxResult, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
// Unpack the basic op into subexpressions
BasicOpTuple basicOpExpressions = BasicOpTuple.isBasicOp(e);
if (!LECCMachineConfiguration.generateDirectPrimOpCalls()) {
//when we are not generating direct primitive op calls we should not call this function,
//other than the special case of generating code for the function that calls the primitive op
//e.g. org.openquark.cal.internal.runtime.lecc.cal_Math.Sin will directly call java.lang.Math.sin but
//we will not directly call java.lang.Math.sin anywhere else.
if (!basicOpExpressions.getName().equals(getQualifiedName())) {
throw new CodeGenerationException("Should not directly call a primitive operator when GEN_DIRECT_PRIMOP_CALLS is false.");
}
}
// Code a basic operation
int op = basicOpExpressions.getPrimitiveOp ();
int nArgs = basicOpExpressions.getNArguments ();
if (nArgs < 0) {
throw new CodeGenerationException("Invalid basic operator arity: " + nArgs);
}
if (op == PrimOps.PRIMOP_FOREIGN_FUNCTION) {
// This is a foreign function
return generateForeignCall (e, boxResult, scheme, variableContext);
}
if (codeGenerationStats != null) {
codeGenerationStats.incrementOptimizedPrimOp (basicOpExpressions.getName());
}
// The primitive op Prelude.eager is a special case. We want to simply
// compile the argument in a strict scheme.
if (op == PrimOps.PRIMOP_EAGER) {
return genS_E(basicOpExpressions.getArgument(0), variableContext);
}
// The arguments for primitive ops are handled as a special case.
// We want to avoid boxing/unboxing of primitive values wherever possible.
Block generatedContext = new Block();
JavaExpression args[] = new JavaExpression[nArgs];
for (int i = 0; i < nArgs; ++i) {
int argIndex = nArgs - i - 1;
ExpressionContextPair pair;
if (op == PrimOps.PRIMOP_CAL_VALUE_TO_OBJECT) {
//Prelude.calValueToObject is an unusual primitive op in that it is non-strict in its argument
pair = genS_C(basicOpExpressions.getArgument(argIndex), variableContext);
} else
{
pair = generateUnboxedPrimOpArgument(basicOpExpressions, basicOpExpressions.getArgument(argIndex), argIndex, variableContext);
}
args[argIndex] = pair.getJavaExpression();
generatedContext.addStatement(pair.getContextBlock());
}
JavaExpression javaExpression = PrimOp.getPrimOpDefinition(op, args);
if (boxResult) {
javaExpression = boxPrimitiveOpResult(op, javaExpression);
}
return new ExpressionContextPair(javaExpression, generatedContext);
}
/**
* Construct a call that makes a new ErrorInfo object with the given parameters.
*
* @param errorInfo The information about where the error has occurred.
* @return A call that generates the new error info object.
*/
private JavaExpression getErrorInfo( Expression.ErrorInfo errorInfo ){
// Sometimes the provided Expression.ErrorInfo may be null.
// This can happen when generating code from a source model construct
// that was dynamically generated, rather than being generated from
// a CAL source file.
if (errorInfo == null) {
return LiteralWrapper.NULL;
}
QualifiedName topLevelFunctionName = errorInfo.getTopLevelFunctionName();
int line = errorInfo.getLine();
int column = errorInfo.getColumn();
// '.' in the module name gets mapped to '_'
// '_' in the module name gets mapped to "__"
String errorVarName = topLevelFunctionName.getModuleName().toSourceText().replaceAll("_", "__").replace('.', '_') + "_" + topLevelFunctionName.getUnqualifiedName() + "_" + line + "_" + column;
JavaExpression newErrorInfo = sharedValues.getStaticError(errorVarName);
if (newErrorInfo == null) {
// Initialize newErrorInfo
JavaExpression args[] = new JavaExpression[4];
args[0] = LiteralWrapper.make (topLevelFunctionName.getModuleName().toSourceText());
args[1] = LiteralWrapper.make (topLevelFunctionName.getUnqualifiedName());
args[2] = LiteralWrapper.make( Integer.valueOf(line) );
args[3] = LiteralWrapper.make( Integer.valueOf(column) );
JavaTypeName paramTypes[] = new JavaTypeName[4];
paramTypes[0] = JavaTypeName.STRING;
paramTypes[1] = JavaTypeName.STRING;
paramTypes[2] = JavaTypeName.INT;
paramTypes[3] = JavaTypeName.INT;
newErrorInfo = new ClassInstanceCreationExpression(JavaTypeName.ERRORINFO, args, paramTypes);
// Add the expression to the map of static error info objects to be created.
sharedValues.addStaticError(errorVarName, newErrorInfo);
}
return new JavaField.Static(thisTypeName, errorVarName, JavaTypeName.ERRORINFO);
}
/**
* Generate a call to RTSupercombinator.badSwitchIndex.
*
* @param errorInfo
* @return the call
*/
private JavaExpression getBadSwitchIndexCall (Expression.ErrorInfo errorInfo) {
final JavaExpression jErrorInfo;
if (errorInfo != null) {
jErrorInfo = getErrorInfo (errorInfo);
} else {
jErrorInfo = LiteralWrapper.NULL;
}
MethodInvocation mi =
new MethodInvocation.Instance (null,
"badSwitchIndex",
jErrorInfo,
JavaTypeName.ERRORINFO,
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return mi;
}
/**
* Generate a call to RTSupercombinator.unhandledSwitchIndexForIntPattern.
*
* @param errorInfo
* @return the call
*/
private JavaExpression getUnhandledSwitchIndexForIntPatternCall(Expression.ErrorInfo errorInfo) {
final JavaExpression jErrorInfo;
if (errorInfo != null) {
jErrorInfo = getErrorInfo (errorInfo);
} else {
jErrorInfo = LiteralWrapper.NULL;
}
MethodInvocation mi =
new MethodInvocation.Instance(null,
"unhandledSwitchIndexForIntPattern",
jErrorInfo,
JavaTypeName.ERRORINFO,
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return mi;
}
/**
* Generate a call to RTSupercombinator.unhandledSwitchIndexForCharPattern.
*
* @param errorInfo
* @return the call
*/
private JavaExpression getUnhandledSwitchIndexForCharPatternCall(Expression.ErrorInfo errorInfo) {
final JavaExpression jErrorInfo;
if (errorInfo != null) {
jErrorInfo = getErrorInfo (errorInfo);
} else {
jErrorInfo = LiteralWrapper.NULL;
}
MethodInvocation mi =
new MethodInvocation.Instance(null,
"unhandledSwitchIndexForCharPattern",
jErrorInfo,
JavaTypeName.ERRORINFO,
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return mi;
}
/**
* @param errorInfo The error information to pass into the badValue call.
* @param message The message associated with the bad value call.
* @return An expression that invokes the badValue call on the given target.
*/
private JavaExpression getBadValueCall(Expression.ErrorInfo errorInfo, JavaExpression message){
if (errorInfo == null){
return new MethodInvocation.Static (JavaTypeNames.RTVALUE, "badValue", message, JavaTypeName.STRING, JavaTypeNames.RTVALUE);
}
else{
JavaExpression[] args = {
getErrorInfo(errorInfo),
message
};
JavaTypeName[] paramTypes = {
JavaTypeName.ERRORINFO,
JavaTypeName.STRING
};
return new MethodInvocation.Static(JavaTypeNames.RTVALUE, "badValue", args, paramTypes, JavaTypeNames.RTVALUE);
}
}
/**
* This takes the expression for a primitive operation argument and generates the
* appropriate source to produce an unboxed (i.e. java primitive) value.
* @param op
* @param e
* @param argNum
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateUnboxedPrimOpArgument (BasicOpTuple op,
Expression e,
int argNum,
VariableContext variableContext) throws CodeGenerationException {
JavaTypeName argType = getTypeNameForPrimitiveOpArg(op, argNum);
return generateUnboxedArgument(argType, e, variableContext);
}
/**
* Verify that the desired unboxed type and the unboxed type of the expression are compatible.
* @param desiredType
* @param expressionType
* @throws CodeGenerationException
*/
private void verifyUnboxType (JavaTypeName desiredType, JavaTypeName expressionType) throws CodeGenerationException {
if (desiredType == null) {
// We don't care about the actual type we get so it doesn't matter what the
// expression type is.
return;
}
if (desiredType.equals(expressionType)) {
// If the types are the same then everything is OK.
return;
}
if (desiredType.equals(JavaTypeName.INT) && expressionType.equals(JavaTypeName.VOID)) {
// It is valid to unbox a foreign function of type void to an int since the CAL
// representation of void is the unit type (). This is handled as an int internally.
return;
}
if (desiredType instanceof JavaTypeName.Primitive != expressionType instanceof JavaTypeName.Primitive) {
// Trying to treat an object as a primitive or vice versa. This is not allowed.
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + expressionType + ", desiredType = " + desiredType);
}
if (desiredType instanceof JavaTypeName.Primitive && !desiredType.equals(expressionType)) {
// The desired and expression types are both primitives but not the same primitive. This is not allowed.
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + expressionType + ", desiredType = " + desiredType);
}
if (desiredType instanceof JavaTypeName.Reference.Object != expressionType instanceof JavaTypeName.Reference.Object) {
// This means that we are trying to treat to equate an array and a non-array.
// This is valid if we are treating an array as an object.
if (!desiredType.equals(JavaTypeName.OBJECT)) {
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + expressionType + ", desiredType = " + desiredType);
}
}
if (desiredType instanceof JavaTypeName.Reference.Object && expressionType instanceof JavaTypeName.Reference.Object) {
// Check to see that objects types are compatible.
// i.e. it is valid to treat a String as an Object, but not vice versa.
String dtn = ((JavaTypeName.Reference.Object)desiredType).getName();
String etn = ((JavaTypeName.Reference.Object)expressionType).getName();
try {
ClassLoader foreignClassLoader = module.getForeignClassLoader();
Class<?> dtc = Class.forName(dtn, true, foreignClassLoader);
Class<?> etc = Class.forName(etn, true, foreignClassLoader);
if (!dtc.isAssignableFrom(etc)) {
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + expressionType + ", desiredType = " + desiredType);
}
} catch (ClassNotFoundException e) {
throw new CodeGenerationException ("Unable to determine unboxed type compatability in " + getModuleName() + "." + getFunctionName() + " : expression type = " + expressionType + ", desiredType = " + desiredType);
}
}
// If both types are arrays verify that the element types are compatible.
if (desiredType instanceof JavaTypeName.Reference.Array && expressionType instanceof JavaTypeName.Reference.Array) {
verifyUnboxType(((JavaTypeName.Reference.Array)desiredType).getElementType(), ((JavaTypeName.Reference.Array)expressionType).getElementType());
}
}
/**
* Extract a value of the 'unboxType' from the given expression.
* These arguments are for passing between CAL functions.
* Unboxed arguments for foreign functions are handled differently.
*
* @param unboxType - the desired type of the unboxed value. Null if we don't care about the type.
* @param e
* @param variableContext
* @return The java structure to unbox the value.
* @throws CodeGenerationException
*/
private ExpressionContextPair generateUnboxedArgument (JavaTypeName unboxType, Expression e, VariableContext variableContext) throws CodeGenerationException {
// Because this is a top-level entry point for a code generation of previously unseen expression
// in a strict context we should strip eager.
e = stripEager (e);
boolean primitiveUnboxType = unboxType == null || unboxType instanceof JavaTypeName.Primitive;
// If the argType is a CalValue the various shortcuts involving directly retrieving an unboxed value don't apply.
if (unboxType == null || (!unboxType.equals(JavaTypeName.CAL_VALUE) && !unboxType.equals(JavaTypeNames.RTVALUE))) {
// First check if this is a literal.
if (e.asLiteral() != null) {
if (primitiveUnboxType ||
e.asLiteral().getLiteral() instanceof String ||
e.asLiteral().getLiteral() instanceof BigInteger) {
KernelLiteral kernelLit = getKernelLiteral (e.asLiteral().getLiteral());
return new ExpressionContextPair(kernelLit.getUnboxedReference());
} else {
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + e.asLiteral().getLiteral().getClass().getName() + ", desiredType = " + unboxType);
}
}
// If the expression is a primitive op compile it as an unboxed op.
//We would not want to directly call primitive operators when doing function tracing.
//This will have the effect of ensuring that they get traced when called.
BasicOpTuple bot;
if (LECCMachineConfiguration.generateDirectPrimOpCalls() &&
(bot = BasicOpTuple.isBasicOp(e)) != null) {
// We need to be sure that the primitiveness of the operation/foreign function and the
// desired unbox type match. For example an operation that produces an int can either unbox to
// an int or an Object.
if (bot.getPrimitiveOp() == PrimOps.PRIMOP_FOREIGN_FUNCTION) {
// We know that this is an unboxed argument to a CAL function.
// The unboxed value cannot be void. If we are calling a foreign
// function with return type of void we can't do a direct call. We
// need to call the CAL wrapper function which will return the CAL
// equivalent to void. i.e. Unit
ForeignFunctionInfo ffi = bot.getForeignFunctionInfo();
Class<?> foreignReturnType = SCJavaDefn.getJavaReturnType(ffi);
verifyUnboxType(unboxType, JavaTypeName.make(foreignReturnType));
if(foreignReturnType.equals(void.class)) {
// This is the special case of a void foreign function. The return type needs to
// be converted to the CAL Unit type.
// Compile the expression strictly, this will result in an RTValue which can be unboxed to the int equivalent
// of the unit type.
ExpressionContextPair argResult = genS_E (e, variableContext);
return new ExpressionContextPair(SCJavaDefn.unboxValue (unboxType, argResult.getJavaExpression()), argResult.getContextBlock());
} else if (bot.getPrimitiveOp() == PrimOps.PRIMOP_EAGER) {
throw new CodeGenerationException ("PRIMOP_EAGER encountered in generateUnboxedArgument.");
} else if (foreignReturnType.isPrimitive() == primitiveUnboxType){
// Using unsafeCoerce and input/output we can get CAL code that corresponds to
// treating primitives as objects and vice versa. For example the Java primitive int and
// the Java class Integer get equated.
// We need to check that the foreign type of the expression being compiled has the same
// primitiveness as the unboxed type we are trying to generate.
return generatePrimitiveOp(e, false, Scheme.E_SCHEME, variableContext);
} else {
throw new CodeGenerationException ("Primitivenes mismatch in " + getModuleName() + "." + getFunctionName() + " in generateUnboxedArgument. Expression is " + foreignReturnType.toString() + " expected type is " + unboxType);
}
} else {
if (bot.getPrimitiveOp() != PrimOps.PRIMOP_OBJECT_TO_CAL_VALUE &&
bot.getPrimitiveOp() != PrimOps.PRIMOP_CAL_VALUE_TO_OBJECT) {
verifyUnboxType (unboxType, PrimOp.getTypeNameForPrimOp(bot.getPrimitiveOp()));
return generatePrimitiveOp(e, false, Scheme.E_SCHEME, variableContext);
}
}
}
if (isNot(e)) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
return generateNot (e, false, variableContext);
}
if (BasicOpTuple.isAndOr(e) != null) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
return generateAndOr(e, false, variableContext);
}
if (e.asVar() != null) {
Expression.Var var = e.asVar();
if (variableContext.isLocalVariable(var.getName())) {
if (unboxType != null && variableContext.getUnboxedType(var.getName()) != null) {
verifyUnboxType(unboxType, variableContext.getUnboxedType(var.getName()));
}
// At this point we know that the desired unboxType is null (i.e. to be ignored) or
// is compatible with the unboxed type of the variable (since we called verifyUnboxtype).
// If the desired type is null and the variable can be unboxed we simply use the unboxed
// reference.
// If the desired unbox type is not null and is equal to the variables unboxed type
// we can simply use the unboxed reference.
// If the desired unbox type is Object we can simply use the variables unboxed reference
// since we know the variable can't be primitive (because we called verifyUnboxType) and
// all non-primitives in Java are an instance of Object.
if ((unboxType == null && variableContext.getUnboxedType(var.getName()) != null) ||
(unboxType != null &&
(unboxType.equals(variableContext.getUnboxedType(var.getName())) || unboxType.equals(JavaTypeName.OBJECT)))) {
JavaExpression unboxedRef = variableContext.getUnboxedReference(var.getName());
if (unboxedRef != null) {
return new ExpressionContextPair(unboxedRef);
}
}
}
}
// Is e an application of a saturated constructor?
if (ConstructorOpTuple.isConstructorOp(e, true) != null) {
ConstructorOpTuple constructorOpExpressions = ConstructorOpTuple.isConstructorOp(e, false);
DataConstructor dc = constructorOpExpressions.getDataConstructor ();
// If we are dealing with a data constructor for the CAL type boolean we want to optimize
// by substituting the literal boolean values.
if (isTrueOrFalseDataCons(dc)) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
LiteralWrapper boolWrapper = LiteralWrapper.make(Boolean.valueOf(isTrueDataCons(dc)));
return new ExpressionContextPair(boolWrapper);
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (SCJavaDefn.isEnumDataType(dc)) {
verifyUnboxType(unboxType, JavaTypeName.INT);
return new ExpressionContextPair (LiteralWrapper.make (Integer.valueOf(dc.getOrdinal())));
}
}
}
// Is e a dc field selection?
if (e.asDataConsSelection() != null) {
return generateUnboxedDCFieldSelection(e.asDataConsSelection(), unboxType, variableContext);
}
// Is e a non-recursive let variable.
if (e.asLetNonRec() != null) {
return generateLetNonRec(e.asLetNonRec(), Scheme.UNBOX_INTERNAL_SCHEME, unboxType, variableContext);
}
// Try generating a direct call to a supercombinators fUnboxed method.
ExpressionContextPair argResult = buildUnboxedDirectCall (e, unboxType, Scheme.UNBOX_INTERNAL_SCHEME, variableContext);
if (argResult != null) {
return new ExpressionContextPair(argResult.getJavaExpression(), argResult.getContextBlock());
}
}
// Compile the expression Strictly.
ExpressionContextPair argResult = genS_E (e, variableContext);
return new ExpressionContextPair(SCJavaDefn.unboxValue (unboxType, argResult.getJavaExpression()), argResult.getContextBlock());
}
/**
* Generate the source code for an unboxed foreign function arg.
* @param unboxType
* @param e
* @param variableContext
* @return ExpressionContextPair
* @throws CodeGenerationException
*/
private ExpressionContextPair generateUnboxedForeignFunctionArgument (JavaTypeName unboxType,
Expression e,
VariableContext variableContext) throws CodeGenerationException {
// Because this is a top-level entry point for a code generation of previously unseen expression
// in a strict context we should strip eager.
e = stripEager(e);
// If the argType is a CalValue the various shortcuts involving directly retrieving an unboxed value don't apply.
if (!unboxType.equals(JavaTypeName.CAL_VALUE) && !unboxType.equals(JavaTypeNames.RTVALUE)) {
// First check if this is a literal.
if (e.asLiteral() != null) {
if (unboxType instanceof JavaTypeName.Primitive ||
e.asLiteral().getLiteral() instanceof String ||
e.asLiteral().getLiteral() instanceof BigInteger) {
KernelLiteral kernelLit = getKernelLiteral (e.asLiteral().getLiteral());
return new ExpressionContextPair(kernelLit.getUnboxedReference());
} else {
throw new CodeGenerationException ("Unboxed type mismatch in " + getModuleName() + "." + getFunctionName() + " : expression type = " + e.asLiteral().getLiteral().getClass().getName() + ", desiredType = " + unboxType);
}
}
// If the expression is a primitive op compile it as an unboxed op.
//we don't directly call primitive operators if doing function tracing.
//This will have the effect of ensuring that they get traced when called.
BasicOpTuple bot;
if (LECCMachineConfiguration.generateDirectPrimOpCalls() &&
(bot = BasicOpTuple.isBasicOp(e)) != null) {
// Generally speaking if an argument to a foreign function is expressed as a primitive op/foreign function we can
// assume that the unboxed return value of the argument expression is of the correct type to pass.
// One exception is when dealing with an argument expression of type Prelude.CalValue. In this case we need to
// get the Prelude.CalValue, evaluate, and then unbox the actual type needed by the foreign function.
// Another is when dealing with Prelude.eager
// Finally we need to be sure that the primitiveness of the operation/foreign function and the
// desired unbox type match. For example an operation that produces an int can either unbox to
// an int or an Object.
if (bot.getPrimitiveOp() == PrimOps.PRIMOP_FOREIGN_FUNCTION) {
// If this foreign SC is of type internal value we can't just get the unboxed value and pass
final ForeignFunctionInfo ffi = bot.getForeignFunctionInfo();
final Class<?> foreignReturnType = SCJavaDefn.getJavaReturnType(ffi);
// If this foreign SC is of type Prelude.CalValue we can't just get the unboxed value and pass
final boolean hasCalValueReturnType = isCalValueClass(foreignReturnType);
if (hasCalValueReturnType){
ExpressionContextPair argResult = genS_E (e, variableContext);
return new ExpressionContextPair(unboxValue(unboxType, SCJavaDefn.createInvocation (argResult.getJavaExpression(), SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR)), argResult.getContextBlock());
}
verifyUnboxType(unboxType, JavaTypeName.make(foreignReturnType));
return generatePrimitiveOp (e, false, Scheme.E_SCHEME, variableContext);
} else
if (bot.getPrimitiveOp() == PrimOps.PRIMOP_EAGER) {
throw new CodeGenerationException ("PRIMOP_EAGER encountered in generateUnboxedForeignFunctionArgument.");
} else
if (bot.getPrimitiveOp() != PrimOps.PRIMOP_OBJECT_TO_CAL_VALUE &&
bot.getPrimitiveOp() != PrimOps.PRIMOP_CAL_VALUE_TO_OBJECT) {
verifyUnboxType(unboxType, PrimOp.getTypeNameForPrimOp(bot.getPrimitiveOp()));
return generatePrimitiveOp(e, false, Scheme.E_SCHEME, variableContext);
}
}
if (isNot(e)) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
return generateNot (e, false, variableContext);
}
if (BasicOpTuple.isAndOr(e) != null) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
return generateAndOr(e, false, variableContext);
}
if (e.asVar() != null) {
Expression.Var var = e.asVar();
if (variableContext.isLocalVariable(var.getName())) {
final JavaTypeName unboxedTypeOfVar = variableContext.getUnboxedType(var.getName());
if (unboxedTypeOfVar != null) {
verifyUnboxType(unboxType, unboxedTypeOfVar);
}
// At this point we know that the desired unboxType is compatible with the
// unboxed type of the variable (since we called verifyUnboxtype).
// If the desired unbox type is not null and is equal to the variables unboxed type
// we can simply use the unboxed reference.
// If the desired unbox type is Object we can simply use the variables unboxed reference
// since we know the variable can't be primitive (because we called verifyUnboxType) and
// all non-primitives in Java are an instance of Object.
if(unboxType.equals(unboxedTypeOfVar) ||
unboxType.equals(JavaTypeName.OBJECT)) {
JavaExpression unboxedRef = variableContext.getUnboxedReference(var.getName());
if (unboxedRef != null) {
return new ExpressionContextPair (unboxedRef);
}
} else {
if (unboxedTypeOfVar != null) {
// If the variable has an unboxed type, then in fact it should be the same as the desired unboxed type
// or should be java.lang.Object
throw new CodeGenerationException("The code generator should not be generating code where the desired unboxed type is not java.lang.Object nor the unboxed type of the variable.");
}
}
}
}
// Is e an application of a saturated constructor?
if (ConstructorOpTuple.isConstructorOp(e, true) != null) {
ConstructorOpTuple constructorOpExpressions = ConstructorOpTuple.isConstructorOp(e, false);
DataConstructor dc = constructorOpExpressions.getDataConstructor ();
// If we are dealing with a data constructor for the CAL type boolean we want to optimize
// by substituting and instance of the literal RTKernel.CAL_Boolean.
if (isTrueOrFalseDataCons(dc)) {
verifyUnboxType(unboxType, JavaTypeName.BOOLEAN);
LiteralWrapper boolWrapper = LiteralWrapper.make(Boolean.valueOf(isTrueDataCons(dc)));
return new ExpressionContextPair(boolWrapper);
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (SCJavaDefn.isEnumDataType(dc)) {
verifyUnboxType(unboxType, JavaTypeName.INT);
return new ExpressionContextPair (LiteralWrapper.make (Integer.valueOf(dc.getOrdinal())));
}
}
}
// Is e a dc field selection?
if (e.asDataConsSelection() != null) {
return generateUnboxedDCFieldSelection(e.asDataConsSelection(), unboxType, variableContext);
}
// Is e a non-recursive let variable.
if (e.asLetNonRec() != null) {
return generateLetNonRec(e.asLetNonRec(), Scheme.UNBOX_FOREIGN_SCHEME, unboxType, variableContext);
}
// Try generating a direct call to the supercombinator fUnboxed method.
ExpressionContextPair argResult = buildUnboxedDirectCall(e, unboxType, Scheme.UNBOX_FOREIGN_SCHEME, variableContext);
if (argResult != null) {
return new ExpressionContextPair(argResult.getJavaExpression(), argResult.getContextBlock());
}
}
// Compile the expression Strictly.
ExpressionContextPair argResult = genS_E (e, variableContext);
return new ExpressionContextPair(unboxValue(unboxType, argResult.getJavaExpression()), argResult.getContextBlock());
}
/**
* Generate code for a DataConsSelection expression which returns an
* unboxed form of the field
* @param dcs
* @param unboxedType - the desired type of the unboxed value. Null if the type doesn't matter.
* @param variableContext
* @return java code to generate an unboxed field value
* @throws CodeGenerationException
*/
private ExpressionContextPair generateUnboxedDCFieldSelection (DataConsSelection dcs, JavaTypeName unboxedType, VariableContext variableContext) throws CodeGenerationException {
// If the field is a strict primitive we can use
// the version of getFieldByIndex() which returns an unboxed
// value.
// Assuming of course that the expected unboxed type is compatible with
// the type of the field.
DataConstructor dc = dcs.getDataConstructor();
TypeExpr[] fieldTypes = SCJavaDefn.getFieldTypesForDC(dc);
TypeExpr fieldType = fieldTypes[dcs.getFieldIndex()];
if (unboxedType == null) {
// The type of the unboxed value is irrelevent so set it to be the same type as the field.
if (dc.isArgStrict(dcs.getFieldIndex())) {
unboxedType = SCJavaDefn.typeExprToTypeName(fieldType);
} else {
unboxedType = JavaTypeNames.RTVALUE;
}
}
// Check that the field is strict and can be unboxed.
boolean directlyRetrieveUnboxed = dc.isArgStrict(dcs.getFieldIndex()) &&
SCJavaDefn.canTypeBeUnboxed(fieldType);
// Now check compatability of the expected type and the field type.
if (directlyRetrieveUnboxed) {
verifyUnboxType(unboxedType, SCJavaDefn.typeExprToTypeName(fieldType));
}
if (directlyRetrieveUnboxed) {
ExpressionContextPair ecp = genS_E(dcs.getDCValueExpr(), variableContext);
JavaExpression target = new CastExpression (JavaTypeNames.RTCONS, ecp.getJavaExpression());
// Call the unboxed version of getFieldByIndex.
// In RTCons we declare versions of getFieldByIndex_As_...() for each of the Java primitive types,
// Object, and java.lang.String.
// At this point we need to decide which one to call.
boolean retrieveAsObject = !(unboxedType instanceof JavaTypeName.Primitive) && !unboxedType.equals(JavaTypeName.STRING);
JavaExpression mi;
if (retrieveAsObject) {
mi = new MethodInvocation.Instance(target,
"getFieldByIndex_As_Object",
new JavaExpression[] {
LiteralWrapper.make(Integer.valueOf(dcs.getDataConstructor().getOrdinal())),
LiteralWrapper.make(Integer.valueOf(dcs.getFieldIndex())),
getErrorInfo(dcs.getErrorInfo()) },
new JavaTypeName[] {
JavaTypeName.INT,
JavaTypeName.INT,
JavaTypeName.ERRORINFO },
JavaTypeName.OBJECT,
MethodInvocation.InvocationType.VIRTUAL);
if (!unboxedType.equals(JavaTypeName.OBJECT)) {
mi = new JavaExpression.CastExpression(unboxedType, mi);
}
} else {
mi = new MethodInvocation.Instance (target,
"getFieldByIndex_As_" + SCJavaDefn.getNameForPrimitive(fieldTypes[dcs.getFieldIndex()]),
new JavaExpression[] {
LiteralWrapper.make(Integer.valueOf(dcs.getDataConstructor().getOrdinal())),
LiteralWrapper.make(Integer.valueOf(dcs.getFieldIndex())),
getErrorInfo(dcs.getErrorInfo())},
new JavaTypeName[] {
JavaTypeName.INT,
JavaTypeName.INT,
JavaTypeName.ERRORINFO},
SCJavaDefn.typeExprToTypeName(fieldTypes[dcs.getFieldIndex()]),
MethodInvocation.InvocationType.VIRTUAL);
}
return new ExpressionContextPair (mi, ecp.getContextBlock());
}
// Compile the expression Strictly and then unbox the return.
ExpressionContextPair argResult = genS_E (dcs, variableContext);
return new ExpressionContextPair(unboxValue(unboxedType, argResult.getJavaExpression()), argResult.getContextBlock());
}
/**
* Determine if the ith argument is of an unboxable type.
* @param i
* @return true if argument i is unboxable.
*/
boolean isArgUnboxable(int i) throws CodeGenerationException {
if (argumentTypes[i] == null) {
return false;
}
return SCJavaDefn.canTypeBeUnboxed(argumentTypes[i]);
}
/**
* Determine if the named argument is of an unboxable type.
* @param name
* @return true if the arg is unboxable.
*/
private boolean isArgUnboxable (String name) throws CodeGenerationException {
for (int i = 0; i < argumentNames.length; ++i) {
if (argumentNames[i].equals(name)) {
return isArgUnboxable(i);
}
}
return false;
}
/**
* Determine if the named argument is of an unboxable type.
* @param name
* @return true if the arg is unboxable.
*/
private boolean isArgUnboxable (QualifiedName name) throws CodeGenerationException {
return isArgUnboxable(name.getUnqualifiedName());
}
/**
* Return the JavaTypeName for the ith argument.
* @param i
* @return JavaTypeName of the ith argument.
*/
JavaTypeName getArgumentTypeName (int i) throws CodeGenerationException {
if (i < argumentTypes.length && argumentTypes[i] != null) {
return SCJavaDefn.typeExprToTypeName(argumentTypes[i]);
}
return JavaTypeNames.RTVALUE;
}
JavaTypeName getArgumentTypeName (String name) throws CodeGenerationException {
for (int i = 0; i < argumentNames.length; ++i) {
if (argumentNames[i].equals(name)) {
return getArgumentTypeName(i);
}
}
return JavaTypeNames.RTVALUE;
}
/**
* Return the type of the ith argument.
* @param i
* @return the argument type.
*/
TypeExpr getArgumentType(int i) {
return argumentTypes[i];
}
/**
* Get a name corresponding to a primitive type.
* @param typeExpr
* @return the name
* @throws CodeGenerationException
*/
static String getNameForPrimitive (TypeExpr typeExpr) throws CodeGenerationException {
JavaTypeName typeName = typeExprToTypeName(typeExpr);
return getNameForPrimitive(typeName);
}
/**
* Get a name corresponding to a primitive type.
* @param typeName
* @return the name
* @throws CodeGenerationException
*/
static String getNameForPrimitive (JavaTypeName typeName) throws CodeGenerationException {
switch (typeName.getTag()) {
case JavaTypeName.BOOLEAN_TAG: return "Boolean";
case JavaTypeName.BYTE_TAG: return "Byte";
case JavaTypeName.SHORT_TAG: return "Short";
case JavaTypeName.CHAR_TAG: return "Character";
case JavaTypeName.INT_TAG: return "Int";
case JavaTypeName.LONG_TAG: return "Long";
case JavaTypeName.DOUBLE_TAG: return "Double";
case JavaTypeName.FLOAT_TAG: return "Float";
case JavaTypeName.OBJECT_TAG:
{
String name = typeName.getFullJavaSourceName();
return CALToJavaNames.fixupVarName(name);
}
case JavaTypeName.ARRAY_TAG:
{
JavaTypeName.Reference.Array arrayType = (JavaTypeName.Reference.Array)typeName;
int nDimensions = arrayType.getNDimensions();
JavaTypeName elementType = arrayType.getElementType();
// Get the source for the element type, and append "[]" for each dimension.
StringBuilder sb = new StringBuilder(getNameForPrimitive(elementType));
for (int i = 0; i < nDimensions; i++) {
sb.append("_Array");
}
return sb.toString();
}
default:
{
throw new CodeGenerationException("Unable to return name for primitive type: " + typeName.getName());
}
}
}
/**
* Returns true if the given TypeExpr corresponds to a foreign type that is
* not a primitive unboxed java type.
* @param typeExpr
* @return true/false
*/
static boolean doesTypeUnboxToObjectDerivative (TypeExpr typeExpr) throws CodeGenerationException {
if (typeExpr == null) {
return false;
}
TypeConsApp typeCons = typeExpr.rootTypeConsApp();
if (typeCons != null && typeCons.getNArgs() == 0) {
if (typeCons.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) {
return false;
}
// Zero arity data constructors are treated as int.
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (isEnumDataType (typeCons)) {
return false;
}
}
if(typeCons.getForeignTypeInfo() != null) {
Class<?> foreignClass = SCJavaDefn.getForeignType(typeCons.getForeignTypeInfo());
// If the class is CalValue or one of the Java primitives we don't treat
// this as corresponding to an unboxed Object type.
if (isCalValueClass(foreignClass)|| foreignClass.isPrimitive()) {
return false;
}
return true;
}
}
return false;
}
/**
* Returns true if the given TypeExpr corresponds to a type which can be
* represented internally with an unboxed value.
* Unboxed values are either a Java primitive or a Java Object (i.e. anything
* derived from java.lang.Object).
* @param typeExpr
* @return true/false
*/
static boolean canTypeBeUnboxed (TypeExpr typeExpr) throws CodeGenerationException {
if (typeExpr == null) {
return false;
}
TypeConsApp typeConsApp = typeExpr.rootTypeConsApp();
if (typeConsApp != null && typeConsApp.getNArgs() == 0) {
if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) {
return true;
}
if (typeConsApp.getForeignTypeInfo() != null && !isCalValueClass(SCJavaDefn.getForeignType(typeConsApp.getForeignTypeInfo()))) {
return true;
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (isEnumDataType (typeConsApp)) {
return true;
}
}
}
return false;
}
/**
* An enumeration type is a:
* -non parametric type (i.e. the type has 0 arity)
* -not a foreign type
* -there is at least one data constructor
* -all data constructors have 0 arity
* -it is not Prelude.Boolean
* For example, Prelude.Ordering is an enumeration type.
*
* @param typeExpr
* @return true if the given type is an enumeration, according to the above definition.
*/
static boolean isEnumDataType (TypeExpr typeExpr) {
TypeConsApp typeConsApp = typeExpr.rootTypeConsApp();
if (typeConsApp == null) {
return false;
}
return TypeExpr.isEnumType(typeConsApp.getRoot());
}
/**
* @param typeExpr
* @return true if the data type is self referential
*/
static private boolean isSelfReferentialDataType (TypeExpr typeExpr) {
TypeConsApp typeConsApp = typeExpr.rootTypeConsApp();
if (typeConsApp == null) {
return false;
}
TypeConstructor typeCons = typeConsApp.getRoot();
for (int i = 0, n = typeCons.getNDataConstructors(); i < n; ++i) {
DataConstructor dc = typeCons.getNthDataConstructor(i);
TypeExpr[] fieldTypes = SCJavaDefn.getFieldTypesForDC(dc);
for (int j = 0, k = fieldTypes.length; j < k; ++j) {
TypeExpr fieldType = fieldTypes[j];
TypeConsApp fieldTc = fieldType.rootTypeConsApp();
if (fieldTc != null &&
(typeConsApp.sameType(fieldTc) || fieldTc.getRoot().equals(typeCons))) {
return true;
}
}
}
return false;
}
/**
/**
* An enumeration type is a:
* -non parametric type (i.e. the type has 0 arity)
* -not a foreign type
* -there is at least one data constructor
* -all data constructors have 0 arity
* -it is not Prelude.Boolean
* For example, Prelude.Ordering is an enumeration type.
*
* @param dc
* @return true if the given DataConstructor belongs to an 'enumeration' data type.
*/
static boolean isEnumDataType (DataConstructor dc) {
return TypeExpr.isEnumType(dc.getTypeConstructor());
}
/**
* Go from a TypeExpr to the corresponding JavaTypeName.
* @param typeExpr
* @return a JavaTypeName
*/
static JavaTypeName typeExprToTypeName (TypeExpr typeExpr) throws CodeGenerationException {
if (typeExpr != null) {
TypeConsApp typeConsApp = typeExpr.rootTypeConsApp();
if (typeConsApp != null) {
if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) {
return JavaTypeName.BOOLEAN;
}
if(typeConsApp.getForeignTypeInfo() != null) {
ForeignTypeInfo fti = typeConsApp.getForeignTypeInfo();
return JavaTypeName.make (SCJavaDefn.getForeignType(fti));
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (isEnumDataType (typeConsApp)) {
return JavaTypeName.INT;
}
}
}
}
return JavaTypeNames.RTVALUE;
}
/**
* Given a JavaTypeName and a JavaExpression generate java code to
* box the expression in the CAL kernel type corresponding to the JavaTypeName.
* @param boxType
* @param e
* @return a JavaExpression which boxes the value of e.
*/
static JavaExpression boxExpression (JavaTypeName boxType, JavaExpression e) {
if (boxType.equals(JavaTypeName.BOOLEAN)) {
return createMakeKernelBooleanInvocation(e);
} else
if (boxType.equals(JavaTypeName.BYTE)) {
return createMakeKernelByteInvocation(e);
} else
if (boxType.equals(JavaTypeName.CHAR)) {
return createMakeKernelCharInvocation(e);
} else
if (boxType.equals(JavaTypeName.DOUBLE)) {
return createMakeKernelDoubleInvocation(e);
} else
if (boxType.equals(JavaTypeName.FLOAT)) {
return createMakeKernelFloatInvocation(e);
} else
if (boxType.equals(JavaTypeName.INT)) {
return createMakeKernelIntInvocation(e);
} else
if (boxType.equals(JavaTypeName.LONG)) {
return createMakeKernelLongInvocation(e);
} else
if (boxType.equals(JavaTypeName.SHORT)) {
return createMakeKernelShortInvocation(e);
} else
if (boxType.equals(JavaTypeName.STRING)) {
return createMakeKernelStringInvocation(e);
} else
if (boxType.equals(JavaTypeName.BIG_INTEGER)){
return createMakeKernelIntegerInvocation(e);
} else {
return createMakeKernelOpaqueInvocation(e);
}
}
/**
* Generate a java expression which wraps the given java expression in a runtime
* boxing class of the appropriate type.
* @param boxType
* @param e
* @return boxed expression
* @throws CodeGenerationException
*/
static JavaExpression boxExpression (TypeExpr boxType, JavaExpression e) throws CodeGenerationException {
if (boxType == null || e == null) {
throw new CodeGenerationException ("Attempt to box null type. ");
}
TypeConsApp typeConsApp = boxType.rootTypeConsApp();
if (typeConsApp != null) {
if (typeConsApp.isNonParametricType(CAL_Prelude.TypeConstructors.Boolean)) {
return boxExpression (JavaTypeName.BOOLEAN, e);
}
if (typeConsApp.getForeignTypeInfo() != null) {
return boxExpression (JavaTypeName.make(SCJavaDefn.getForeignType(typeConsApp.getForeignTypeInfo())), e);
}
if (LECCMachineConfiguration.TREAT_ENUMS_AS_INTS) {
if (SCJavaDefn.isEnumDataType (typeConsApp)) {
return boxExpression (JavaTypeName.INT, e);
}
}
}
throw new CodeGenerationException ("Attempt to box unhandled type: " + boxType.toString());
}
/**
* This function boxes (i.e. puts the result into a CAL type class) the
* result of a primitive op.
* @param op
* @param arg
* @return JavaExpression
* @throws CodeGenerationException
*/
private JavaExpression boxPrimitiveOpResult (int op, JavaExpression arg) throws CodeGenerationException {
switch (op) {
case PrimOps.PRIMOP_NOP:
throw new CodeGenerationException ("Attemp to box argument for PRIMOP_NOP.");
// Booleans
case PrimOps.PRIMOP_OR:
case PrimOps.PRIMOP_AND:
case PrimOps.PRIMOP_EQUALS_INT:
case PrimOps.PRIMOP_NOT_EQUALS_INT:
case PrimOps.PRIMOP_GREATER_THAN_INT:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_INT:
case PrimOps.PRIMOP_LESS_THAN_INT:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_INT:
case PrimOps.PRIMOP_EQUALS_DOUBLE:
case PrimOps.PRIMOP_NOT_EQUALS_DOUBLE:
case PrimOps.PRIMOP_GREATER_THAN_DOUBLE:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_DOUBLE:
case PrimOps.PRIMOP_LESS_THAN_DOUBLE:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_DOUBLE:
case PrimOps.PRIMOP_EQUALS_FLOAT:
case PrimOps.PRIMOP_NOT_EQUALS_FLOAT:
case PrimOps.PRIMOP_GREATER_THAN_FLOAT:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_FLOAT:
case PrimOps.PRIMOP_LESS_THAN_FLOAT:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_FLOAT:
case PrimOps.PRIMOP_EQUALS_LONG:
case PrimOps.PRIMOP_NOT_EQUALS_LONG:
case PrimOps.PRIMOP_GREATER_THAN_LONG:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_LONG:
case PrimOps.PRIMOP_LESS_THAN_LONG:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_LONG:
case PrimOps.PRIMOP_EQUALS_SHORT:
case PrimOps.PRIMOP_NOT_EQUALS_SHORT:
case PrimOps.PRIMOP_GREATER_THAN_SHORT:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_SHORT:
case PrimOps.PRIMOP_LESS_THAN_SHORT:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_SHORT:
case PrimOps.PRIMOP_EQUALS_BYTE:
case PrimOps.PRIMOP_NOT_EQUALS_BYTE:
case PrimOps.PRIMOP_GREATER_THAN_BYTE:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_BYTE:
case PrimOps.PRIMOP_LESS_THAN_BYTE:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_BYTE:
case PrimOps.PRIMOP_EQUALS_CHAR:
case PrimOps.PRIMOP_NOT_EQUALS_CHAR:
case PrimOps.PRIMOP_GREATER_THAN_CHAR:
case PrimOps.PRIMOP_GREATER_THAN_EQUALS_CHAR:
case PrimOps.PRIMOP_LESS_THAN_CHAR:
case PrimOps.PRIMOP_LESS_THAN_EQUALS_CHAR:
return createMakeKernelBooleanInvocation(arg);
// Integers
case PrimOps.PRIMOP_ADD_INT:
case PrimOps.PRIMOP_SUBTRACT_INT:
case PrimOps.PRIMOP_MULTIPLY_INT:
case PrimOps.PRIMOP_NEGATE_INT:
case PrimOps.PRIMOP_DIVIDE_INT:
case PrimOps.PRIMOP_REMAINDER_INT:
case PrimOps.PRIMOP_BITWISE_AND_INT:
case PrimOps.PRIMOP_BITWISE_OR_INT:
case PrimOps.PRIMOP_BITWISE_XOR_INT:
case PrimOps.PRIMOP_COMPLEMENT_INT:
case PrimOps.PRIMOP_SHIFTL_INT:
case PrimOps.PRIMOP_SHIFTR_INT:
case PrimOps.PRIMOP_SHIFTR_UNSIGNED_INT:
return createMakeKernelIntInvocation(arg);
// Doubles
case PrimOps.PRIMOP_ADD_DOUBLE:
case PrimOps.PRIMOP_SUBTRACT_DOUBLE:
case PrimOps.PRIMOP_MULTIPLY_DOUBLE:
case PrimOps.PRIMOP_DIVIDE_DOUBLE:
case PrimOps.PRIMOP_NEGATE_DOUBLE:
case PrimOps.PRIMOP_REMAINDER_DOUBLE:
return createMakeKernelDoubleInvocation(arg);
// Longs
case PrimOps.PRIMOP_ADD_LONG:
case PrimOps.PRIMOP_SUBTRACT_LONG:
case PrimOps.PRIMOP_MULTIPLY_LONG:
case PrimOps.PRIMOP_DIVIDE_LONG:
case PrimOps.PRIMOP_NEGATE_LONG:
case PrimOps.PRIMOP_REMAINDER_LONG:
case PrimOps.PRIMOP_BITWISE_AND_LONG:
case PrimOps.PRIMOP_BITWISE_OR_LONG:
case PrimOps.PRIMOP_BITWISE_XOR_LONG:
case PrimOps.PRIMOP_COMPLEMENT_LONG:
case PrimOps.PRIMOP_SHIFTL_LONG:
case PrimOps.PRIMOP_SHIFTR_LONG:
case PrimOps.PRIMOP_SHIFTR_UNSIGNED_LONG:
return createMakeKernelLongInvocation(arg);
// Floats
case PrimOps.PRIMOP_ADD_FLOAT:
case PrimOps.PRIMOP_SUBTRACT_FLOAT:
case PrimOps.PRIMOP_MULTIPLY_FLOAT:
case PrimOps.PRIMOP_DIVIDE_FLOAT:
case PrimOps.PRIMOP_NEGATE_FLOAT:
case PrimOps.PRIMOP_REMAINDER_FLOAT:
return createMakeKernelFloatInvocation(arg);
case PrimOps.PRIMOP_FIELD_NAMES:
case PrimOps.PRIMOP_FIELD_VALUES:
case PrimOps.PRIMOP_MAKE_ITERATOR:
case PrimOps.PRIMOP_MAKE_COMPARATOR:
case PrimOps.PRIMOP_MAKE_EQUIVALENCE_RELATION:
case PrimOps.PRIMOP_MAKE_CAL_FUNCTION:
case PrimOps.PRIMOP_EXECUTION_CONTEXT:
return createMakeKernelOpaqueInvocation(arg);
case PrimOps.PRIMOP_HAS_FIELD:
return createMakeKernelBooleanInvocation(arg);
case PrimOps.PRIMOP_RECORD_FIELD_INDEX:
return createMakeKernelIntInvocation(arg);
case PrimOps.PRIMOP_CAL_VALUE_TO_OBJECT:
case PrimOps.PRIMOP_OBJECT_TO_CAL_VALUE:
return arg;
default:
{
throw new CodeGenerationException("Unrecognized primop " + op + ".");
}
}
}
/**
* Generate the java switch corresponding to a CAL case statement.
* @param eswitch
* @param variableContext
* @return JavaStatement the switch statement. All possible code paths within this statement will result in a return statement.
* @throws CodeGenerationException
*/
private JavaStatement generateSwitch(Expression.Switch eswitch, VariableContext variableContext) throws CodeGenerationException {
if (codeGenerationStats != null) {
codeGenerationStats.incrementNCases(eswitch);
}
nestedCaseLevel++;
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eswitch.getAlts();
// Preflight: must have >0 alts.
if (alts.length <= 0) {
// This can happen if a user creates a CAL Class that
// has no instance methods.
// We simply generate an error call.
MethodInvocation errorCall =
new MethodInvocation.Instance(null,
"unhandledSwitchIndex",
new JavaExpression[]{getErrorInfo(eswitch.getErrorInfo()), LiteralWrapper.make("any value")},
new JavaTypeName[]{JavaTypeName.ERRORINFO, JavaTypeName.STRING},
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
return generateReturn(errorCall, variableContext);
}
// If there is only one alternate and it is the default we can optimize.
if (alts.length == 1 && alts[0].isDefaultAlt()) {
return generateSingleAltSwitch (eswitch, variableContext);
}
for (int i = 0; i < alts.length; ++i) {
if (!alts[i].isDefaultAlt ()) {
Object firstAltTag = alts[i].getFirstAltTag ();
if (firstAltTag instanceof DataConstructor) {
DataConstructor dc = (DataConstructor)firstAltTag;
if (dc.getTypeConstructor().getName().equals(CAL_Prelude.TypeConstructors.Boolean)) {
return generateIfThenElseFromSwitch (eswitch, variableContext);
} else {
return generateSwitchOnDataConstructor (eswitch, variableContext);
}
} else
if (firstAltTag instanceof Boolean) {
return generateIfThenElseFromSwitch (eswitch, variableContext);
} else
if (firstAltTag instanceof Integer) {
return generateSwitchOnInteger (eswitch, variableContext);
} else
if (firstAltTag instanceof Character) {
return generateSwitchOnCharacter (eswitch, variableContext);
} else {
throw new CodeGenerationException ("Unexpected tag type encountered in switch: " + firstAltTag.getClass ().getName ());
}
}
}
throw new CodeGenerationException ("Invalid switch expression encountered in: " + getFunctionName());
}
/**
* Generate a java switch for a CAL case statement on a data type.
* @param eswitch
* @param variableContext
* @return the java switch statement.
* @throws CodeGenerationException
*/
private JavaStatement generateSwitchOnDataConstructor (Expression.Switch eswitch, VariableContext variableContext) throws CodeGenerationException {
Block switchBlock = new Block();
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eswitch.getAlts();
TypeConstructor typeCons = null;
boolean isEnumDataType = false;
for (int i = 0; i < alts.length; ++i) {
if (!alts[i].isDefaultAlt()) {
DataConstructor dc = (DataConstructor)alts[i].getFirstAltTag();
typeCons = dc.getTypeConstructor();
isEnumDataType = SCJavaDefn.isEnumDataType(dc);
break;
}
}
if (typeCons == null) {
throw new CodeGenerationException ("Unable to retrieve TypeConstructor for switch in " + getFunctionName() + ".");
}
int nDataConstructorsForType = typeCons.getNDataConstructors();
if (nDataConstructorsForType == 0) {
throw new CodeGenerationException ("Encountered a data type with zero data constructors in a switch in " + getFunctionName() + ".");
}
DataConstructor[] allDCs = new DataConstructor [nDataConstructorsForType];
for (int i = 0; i < nDataConstructorsForType; ++i ) {
DataConstructor dc = typeCons.getNthDataConstructor(i);
allDCs[dc.getOrdinal()] = dc;
}
// If all the case alternatives return a boolean literal we may
// be able to optimize this.
boolean isa = true;
for (int i = 0; i < alts.length; ++i) {
SwitchAlt switchAlt = alts[i];
if (!switchAlt.isDefaultAlt() &&
!(switchAlt.getFirstAltTag() instanceof DataConstructor)) {
isa = false;
break;
}
Expression altExpr = switchAlt.getAltExpr();
if (altExpr.asLiteral() != null) {
if (!(altExpr.asLiteral().getLiteral() instanceof Boolean)) {
isa = false;
break;
}
} else if (altExpr.asVar() != null) {
DataConstructor dcv = altExpr.asVar().getDataConstructor();
if (dcv == null || !isTrueOrFalseDataCons(dcv)) {
isa = false;
break;
}
} else {
isa = false;
break;
}
}
// We either need to have a default alt or an alt for every data
// constructor for the type.
if (isa && (eswitch.hasDefaultAlt() || nDataConstructorsForType == alts.length)) {
return generateIsAFunctionFromSwitch (eswitch, variableContext);
}
// Determining if any of the alternates have alt vars that need to be extracted from the
// switch value.
boolean noAltVars = true;
for (int i = 0; i < alts.length; ++i) {
if (alts[i].hasVars()) {
noAltVars = false;
break;
}
}
if (LECCMachineConfiguration.OPTIMIZE_SINGLE_DC_CASES && nDataConstructorsForType == 1) {
// If there is only one DataConstructor we can eliminate the switch.
if (codeGenerationStats != null) {
codeGenerationStats.incrementSingleDCCases();
}
if (codeGenerationStats != null) {
codeGenerationStats.incrementSingleDCCases();
}
return generateSingleAltSwitch(eswitch, variableContext);
}
// Create a boolean array to determine which cases we have.
boolean[] caseExistsArray = new boolean[nDataConstructorsForType]; // false by default.
for (int i = 0; i < alts.length; ++i) {
if (!alts[i].isDefaultAlt()) {
List<Object> tags = alts[i].getAltTags();
for (final Object tag : tags) {
DataConstructor dc = (DataConstructor)tag;
caseExistsArray[dc.getOrdinal()] = true;
}
}
}
// Generate the switch conditional.
LocalVariable caseVar = null;
SwitchStatement switchStatement;
if (noAltVars /*&& (defaultAltProvided || !missingCases)*/) {
// If there are no alt vars and we don't have to fill in any missing cases we don't need a local
// variable holding the switchexpression. This means we can generate something like:
// switch (expression.evaluate().getOrdinal())
ExpressionContextPair ecp = generateUnboxedArgument(JavaTypeName.INT, eswitch.getSwitchExpr(), variableContext);
switchBlock.addStatement(ecp.getContextBlock());
JavaExpression conditionExpression = ecp.getJavaExpression();
switchStatement = new SwitchStatement(conditionExpression);
switchBlock.addStatement(switchStatement);
} else {
// If there are alternates that have alt vars we generate something like:
// RTValue caseVar;
// switch ((caseVar = expression.evaluate()).getIntValue())
// We do the assignment of the local in the actual switch statement
// because analysis of the generated bytecode has shown this to be
// slightly more efficient than initializing the local as part of the
// declaration.
JavaStatement caseVarDeclaration = null;
Expression switchExpression = eswitch.getSwitchExpr();
// Generate a local variable and assign the evaluated value of the expression
// we are switching on.
JavaTypeName typeClassName = isEnumDataType ? JavaTypeNames.RTVALUE : CALToJavaNames.createTypeNameFromType(typeCons, module);
caseVar = new LocalVariable("$case" + nestedCaseLevel, typeClassName);
// Add the local variable declaration.
caseVarDeclaration = new LocalVariableDeclaration(caseVar);
switchBlock.addStatement(caseVarDeclaration);
// Compile the expression we are switching on strictly.
ExpressionContextPair pair = genS_E(switchExpression, variableContext);
switchBlock.addStatement(pair.getContextBlock());
JavaExpression caseExpression = pair.getJavaExpression();
//caseExpression = releaseVarsInSwitchCondition(eswitch, caseExpression, variableContext);
// We may need to cast the result of the case expression to the type of the local variable.
caseExpression = (isEnumDataType || caseExpression instanceof ClassInstanceCreationExpression) ? caseExpression : new CastExpression(typeClassName, caseExpression);
// Assign the result of the switch expression to the local an then get the ordinal value.
JavaExpression assignLocal = new JavaExpression.Assignment(caseVar, caseExpression);
JavaExpression getOrdinal = SCJavaDefn.createInvocation(assignLocal, SCJavaDefn.GETORDINALVALUE);
switchStatement = new SwitchStatement(getOrdinal);
switchBlock.addStatement(switchStatement);
}
// Populate the switch statement with case statement groups.
for (final SwitchAlt alt : alts) {
List<Object> altTags = alt.getAltTags();
// If no variables are used, we can share the code among all data constructors for this alt.
if (!alt.hasVars()) {
Block caseBlock = new Block();
// Add a comment for the data constructors in the group if any (ie. if not the default alt).
if (alt.getFirstAltTag() instanceof DataConstructor) {
StringBuilder commentSB = new StringBuilder();
boolean firstDC = true;
for (final Object tag : altTags) {
DataConstructor tagDC = (DataConstructor)tag;
if (firstDC) {
firstDC = false;
} else {
commentSB.append(", ");
}
commentSB.append(tagDC.getName().getQualifiedName());
}
caseBlock.addStatement(new LineComment(commentSB.toString()));
}
// Create a new child variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
// Compile the body of the alternate.
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
if (alt.isDefaultAlt()) {
switchStatement.addCase(new SwitchStatement.DefaultCase(caseBlock));
} else {
int[] caseLabels = new int[altTags.size()];
int index = 0;
for (final Object tag : altTags) {
if (!(tag instanceof DataConstructor)) {
throw new CodeGenerationException ("Unknown tag type in DC case statement in " + getFunctionName() + ": " + tag.getClass().getName());
}
caseLabels[index] = ((DataConstructor)tag).getOrdinal();
index++;
}
switchStatement.addCase(new SwitchStatement.IntCaseGroup(caseLabels, caseBlock));
}
} else {
// The alts use variables.
if (alt instanceof SwitchAlt.Positional) {
// Positional notation for extracted variables.
// For now, a separate code block must be generated for each data constructor in the case.
Collection<List<DataConstructor>> tagGroups = consolidatePositionalSwitchAlt((SwitchAlt.Positional)alt);
for (final List<DataConstructor> group : tagGroups) {
// Must be a data constructor tag, since there are field names (see Expression.Switch.SwitchAlt).
Block caseBlock = new Block();
int[] caseLabels = new int[group.size()];
int index = 0;
DataConstructor firstDC = null;
// Must be a data constructor tag, since there are field names (see Expression.Switch.SwitchAlt).
for (final DataConstructor tagDC : group) {
if (firstDC == null) {
firstDC = tagDC;
} else
if (tagDC.getOrdinal() < firstDC.getOrdinal()) {
firstDC = tagDC;
}
caseBlock.addStatement(new LineComment(tagDC.getName().getQualifiedName()));
caseLabels[index] = tagDC.getOrdinal();
index++;
}
caseBlock.addStatement(new LineComment("Decompose data type to access members."));
// Create a new child variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
// Get this alternative's variables. These have to be added to the active list of scope variables
TypeExpr fieldTypes[] = SCJavaDefn.getFieldTypesForDC(firstDC);
for (final AltVarIndexPair altVarIndexPair : getAltVarIndexList(alt, firstDC)) {
String altVar = altVarIndexPair.getAltVar();
int fieldIndex = altVarIndexPair.getIndex();
QualifiedName qn = QualifiedName.make(currentModuleName, altVar);
VarInfo.DCMember vi = variableContext.addDCField(qn, fieldTypes[fieldIndex]);
boolean fieldIsStrict =
!LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS;
for (final DataConstructor tagDC : group) {
fieldIsStrict = fieldIsStrict && tagDC.isArgStrict(fieldIndex);
}
if (fieldIsStrict) {
vi.setEvaluated(true);
}
String fieldName = SCJavaDefn.getJavaFieldNameFromDC(firstDC, fieldIndex);
String fieldGetterName = "get" + fieldName;
// Generate the code defining the variable.
if (fieldIsStrict) {
if (SCJavaDefn.canTypeBeUnboxed(fieldTypes[fieldIndex])) {
// This is a strict field of a primitive type so has both a boxed and unboxed form.
JavaExpression unboxedInitializer =
new JavaExpression.MethodInvocation.Instance(caseVar,
fieldGetterName + "_As_" + SCJavaDefn.getNameForPrimitive(fieldTypes[fieldIndex]),
SCJavaDefn.typeExprToTypeName(fieldTypes[fieldIndex]),
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateUnboxedVarDef(unboxedInitializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName()+"$U", vi.getUnboxedType());
vi.updateUnboxedReference(localVar);
JavaExpression boxedDef = SCJavaDefn.boxExpression(vi.getUnboxedType(), localVar);
vi.updateStrictReference(boxedDef);
vi.updateLazyReference(boxedDef);
} else {
// RTValue altVarName = ((DCClass)caseVar).getFieldn();
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(caseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateStrictVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateStrictReference(localVar);
vi.updateLazyReference(localVar);
}
} else {
// RTValue altVarName = ((DCClass)caseVar).getFieldn();
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(caseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateLazyVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateLazyReference(localVar);
JavaExpression evaluatedVar = SCJavaDefn.createInvocation(localVar, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
vi.updateStrictReference(evaluatedVar);
if (SCJavaDefn.canTypeBeUnboxed(fieldTypes[fieldIndex])) {
vi.updateUnboxedReference(SCJavaDefn.unboxValue(vi.getUnboxedType(), evaluatedVar));
}
}
}
// Compile the actual body of the alternate.
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
switchStatement.addCase(new SwitchStatement.IntCaseGroup(caseLabels, caseBlock));
}
} else {
// Matching notation for switch alternate.
Map<FieldName, String> fieldNameToVarNameMap = ((SwitchAlt.Matching)alt).getFieldNameToVarNameMap();
Block caseBlock = new Block();
int[] caseLabels = new int[altTags.size()];
int index = 0;
DataConstructor firstDC = null;
// Must be a data constructor tag, since there are field names (see Expression.Switch.SwitchAlt).
for (final Object altTag : altTags) {
DataConstructor tagDC = (DataConstructor)altTag;
if (firstDC == null) {
firstDC = tagDC;
} else if (tagDC.getOrdinal() < firstDC.getOrdinal()) {
firstDC = tagDC;
}
caseBlock.addStatement(new LineComment(tagDC.getName().getQualifiedName()));
caseLabels[index] = tagDC.getOrdinal();
index++;
}
caseBlock.addStatement(new LineComment("Decompose data type to access members."));
// Create a new child variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
for (int iField = 0; iField < firstDC.getArity(); ++iField) {
FieldName fn = firstDC.getNthFieldName(iField);
String altVar = fieldNameToVarNameMap.get(fn);
if (altVar == null) {
continue;
}
QualifiedName qn = QualifiedName.make(currentModuleName, altVar);
TypeExpr fieldType = SCJavaDefn.getFieldTypeForDC(firstDC, fn);
VarInfo.DCMember vi = variableContext.addDCField(qn, fieldType);
boolean fieldIsStrict = !LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS;
for (final Object altTag : altTags) {
DataConstructor tagDC = (DataConstructor)altTag;
fieldIsStrict = fieldIsStrict & tagDC.isArgStrict(tagDC.getFieldIndex(fn));
}
if (fieldIsStrict) {
vi.setEvaluated(true);
}
String fieldName = SCJavaDefn.getJavaFieldNameFromFieldName(fn);
String fieldGetterName = "get" + fieldName;
// Generate the code defining the variable.
if (fieldIsStrict) {
if (SCJavaDefn.canTypeBeUnboxed(fieldType)) {
// This is a strict field of a primitive type so has both a boxed and unboxed form.
JavaExpression unboxedInitializer =
new JavaExpression.MethodInvocation.Instance(caseVar,
fieldGetterName + "_As_" + SCJavaDefn.getNameForPrimitive(fieldType),
SCJavaDefn.typeExprToTypeName(fieldType),
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateUnboxedVarDef(unboxedInitializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName()+"$U", vi.getUnboxedType());
vi.updateUnboxedReference(localVar);
JavaExpression boxedDef = SCJavaDefn.boxExpression(vi.getUnboxedType(), localVar);
vi.updateStrictReference(boxedDef);
vi.updateLazyReference(boxedDef);
} else {
// RTValue altVarName = ((DCClass)caseVar).getFieldn();
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(caseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateStrictVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateStrictReference(localVar);
vi.updateLazyReference(localVar);
}
} else {
// RTValue altVarName = ((DCClass)caseVar).getFieldn();
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(caseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateLazyVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateLazyReference(localVar);
JavaExpression evaluatedVar = SCJavaDefn.createInvocation(localVar, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
vi.updateStrictReference(evaluatedVar);
if (SCJavaDefn.canTypeBeUnboxed(fieldType)) {
vi.updateUnboxedReference(SCJavaDefn.unboxValue(vi.getUnboxedType(), evaluatedVar));
}
}
}
// Compile the actual body of the alternate.
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
switchStatement.addCase(new SwitchStatement.IntCaseGroup(caseLabels, caseBlock));
}
}
}
// If no default case is provided, add case alternates for any missing data constructors.
// Switches in java are marginally more efficient if the case tags cover a contiguous block.
JavaStatement defaultCase = switchStatement.getDefaultStatement();
if (defaultCase == null) {
// Iterate over the array. For each case not provided, treat as an error.
List<Integer> intTagList = new ArrayList<Integer>();
for (int i = 0; i < caseExistsArray.length; ++i) {
if (!caseExistsArray[i]) {
// Add the ordinal to the list.
intTagList.add(Integer.valueOf(i));
}
}
addMissingCases(intTagList, typeCons, switchStatement, eswitch);
// Create a default default case.
defaultCase = generateReturn(getBadSwitchIndexCall(eswitch.getErrorInfo()), variableContext);
switchStatement.addCase (new SwitchStatement.DefaultCase(defaultCase));
}
return switchBlock;
}
/**
* Breaks the data constructors for the switch alt into groups
* where the extracted dc fields have the same names.
* @param alt
* @return Collection of (List of DataConstructor)
*/
Collection<List<DataConstructor>> consolidatePositionalSwitchAlt (SwitchAlt.Positional alt) {
List<List<DataConstructor>> groups = new ArrayList<List<DataConstructor>> ();
Map<Integer, String> f = alt.getPositionToVarNameMap();
int[] indexes = new int[f.size()];
int i = 0;
for (final Integer key : f.keySet()) {
indexes[i++] = key.intValue();
}
for (final Object altTag : alt.getAltTags()) {
DataConstructor dc = (DataConstructor)altTag;
List<DataConstructor> group = null;
for (final List<DataConstructor> pGroup : groups) {
DataConstructor dcMatch = pGroup.get(0);
boolean match = true;
for (int j = 0, n = indexes.length; j < n; ++j) {
if (!dc.getArgumentName(j).equals(dcMatch.getArgumentName(j))) {
match = false;
break;
}
}
if (match) {
group = pGroup;
break;
}
}
if (group == null) {
group = new ArrayList<DataConstructor>();
groups.add (group);
}
group.add(dc);
}
return groups;
}
/**
* For each data constructor that is not explicitly handled, add a case to the switch that makes an
* unhandledSwitchIndex error call.
* @param intTagList the list of tags corresponding to data cons that are not handled.
* @param typeCons the associated type constructor.
* @param switchStatement the java switch statement.
* @param eSwitch the generated switch expression.
*/
private void addMissingCases(final List/*Integer*/<Integer> intTagList,
final TypeConstructor typeCons,
final SwitchStatement switchStatement,
final Expression.Switch eSwitch) {
final int nTags = intTagList.size();
for (int i = 0; i < nTags; i++) {
// Generate a case for each tag -
// Since there is no default case provided we want to treat this as an error..
final int tag = intTagList.get(i).intValue();
final Block altBody = new Block();
final String dataConsName = typeCons.getNthDataConstructor(tag).getName().getQualifiedName();
altBody.addStatement (new JavaStatement.LineComment(dataConsName));
// We generate a call to unhandledSwitchIndex with the data cons name appearing as a string literal.
final MethodInvocation errorCall =
new MethodInvocation.Instance(null,
"unhandledSwitchIndex",
new JavaExpression[]{getErrorInfo(eSwitch.getErrorInfo()), LiteralWrapper.make(dataConsName)},
new JavaTypeName[]{JavaTypeName.ERRORINFO, JavaTypeName.STRING},
JavaTypeNames.RTVALUE,
MethodInvocation.InvocationType.VIRTUAL);
altBody.addStatement(generateReturn (errorCall, null));
switchStatement.addCase(new SwitchStatement.IntCaseGroup (new int[] {tag}, altBody));
}
}
/**
* Get the indices of the alt variables with respect to a data constructor.
* @param alt the alt for which to retrieve the vars.
* @param fromDC the data constructor with respect to which the indices will be returned.
* @return (List of AltVarIndexPair) the indices of the alt's variables.
* These will be in the same order as that returned by an iterator over the alt's relevant mapping to vars.
*/
private List<AltVarIndexPair> getAltVarIndexList(SwitchAlt alt, DataConstructor fromDC) {
final List<AltVarIndexPair> altVarIndexList = new ArrayList<AltVarIndexPair>(); // List (of AltVarIndexPair)
if (alt instanceof SwitchAlt.Positional) {
SortedMap<Integer, String> positionToVarNameMap = ((SwitchAlt.Positional)alt).getPositionToVarNameMap();
for (final Map.Entry<Integer, String> entry : positionToVarNameMap.entrySet()) {
Integer indexInteger = entry.getKey();
String altVar = entry.getValue();
altVarIndexList.add(new AltVarIndexPair(altVar, indexInteger.intValue()));
}
} else {
// Must be matching.
Map<FieldName, String> fieldNameToVarNameMap = ((SwitchAlt.Matching)alt).getFieldNameToVarNameMap();
for (final Map.Entry<FieldName, String> entry : fieldNameToVarNameMap.entrySet()) {
FieldName fieldName = entry.getKey();
String altVar = entry.getValue();
int fieldIndex = fromDC.getFieldIndex(fieldName);
altVarIndexList.add(new AltVarIndexPair(altVar, fieldIndex));
}
}
Collections.<AltVarIndexPair>sort (altVarIndexList);
return altVarIndexList;
}
/**
* Transforms the statement so that the last reference to any local variables
* or function arguments is replaced by a call to RTValue.lastRef().
* @param statement
* @param releasedVars
* @return the transformed statement
* @throws CodeGenerationException
*/
private JavaStatement releaseVars (JavaStatement statement, Set<String> releasedVars) throws CodeGenerationException {
// Names of variables to be released mapped to the Java type.
Map<String, JavaTypeName> variablesOfInterest = new HashMap<String, JavaTypeName>();
if (!isTailRecursive()) {
// Add the names the function arguments.
for (int i = 0; i < getArity(); ++i) {
if (!isArgStrict(i) || !isArgUnboxable(i)) {
variablesOfInterest.put(getJavaArgumentName(i), JavaTypeNames.RTVALUE);
}
}
}
// Add the names of any locals declared in the statement.
DeclaredLocalsFinder dlf = new DeclaredLocalsFinder();
statement.accept(dlf, variablesOfInterest);
// Transform the statement.
VarReleaser vr = new VarReleaser(variablesOfInterest);
return (JavaStatement)statement.accept(vr, null);
}
/**
* Generate a java switch for a CAL case statement on an integer.
* @param eswitch
* @param variableContext
* @return the java switch statement.
* @throws CodeGenerationException
*/
private JavaStatement generateSwitchOnInteger (Expression.Switch eswitch, VariableContext variableContext) throws CodeGenerationException {
Block switchBlock = new Block();
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eswitch.getAlts();
// Generate the switch conditional.
SwitchStatement switchStatement;
// Generate code to get the int value that we are switching on.
ExpressionContextPair ecp = generateUnboxedArgument(JavaTypeName.INT, eswitch.getSwitchExpr(), variableContext);
switchBlock.addStatement(ecp.getContextBlock());
JavaExpression conditionExpression = ecp.getJavaExpression();
switchStatement = new SwitchStatement(conditionExpression);
switchBlock.addStatement(switchStatement);
// Populate the switch statement with case statement groups.
for (final SwitchAlt alt : alts) {
// Create a new child variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
Block caseBlock = new Block();
// If we are switching on an integer we should never have alt variables.
if (alt.hasVars()) {
throw new CodeGenerationException ("Alt vars encountered in integer switch in " + getFunctionName() + ".");
}
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
if (alt.isDefaultAlt()) {
switchStatement.addCase(new SwitchStatement.DefaultCase(caseBlock));
} else {
List<Object> altTags = alt.getAltTags();
int[] caseLabels = new int[altTags.size()];
int index = 0;
for (final Object tag : altTags) {
if (!(tag instanceof Integer)) {
throw new CodeGenerationException ("Unknown tag type in case statement in " + getFunctionName() + ":" + tag.getClass().getName() + ".");
}
caseLabels[index] = ((Integer)tag).intValue();
index++;
}
switchStatement.addCase(new SwitchStatement.IntCaseGroup(caseLabels, caseBlock));
}
}
// Add case alternate for default case if missing.
if (switchStatement.getDefaultStatement() == null) {
//this will mostly be a user error e.g.
//(\x -> case x of 1 -> "one";) (2 :: Int)
//However, because cases on ints are also used by internal dictionary functions, and a few other situations
//it could happen that this occurs because of an internal error.
//todoBI encode enough information into Expression.Switch so that we know which case we're in.
//todoBI pass the unhandled int value that occurred at runtime i.e. 2 in the above example, to the error message
JavaStatement defaultCase = generateReturn(getUnhandledSwitchIndexForIntPatternCall(eswitch.getErrorInfo()), variableContext);
switchStatement.addCase (new SwitchStatement.DefaultCase(defaultCase));
}
return switchBlock;
}
/**
* Generate a java switch for a CAL case statement on an character.
* @param eswitch
* @param variableContext
* @return the java switch statement.
* @throws CodeGenerationException
*/
private JavaStatement generateSwitchOnCharacter (Expression.Switch eswitch, VariableContext variableContext) throws CodeGenerationException {
Block switchBlock = new Block();
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eswitch.getAlts();
// Generate the switch conditional.
SwitchStatement switchStatement;
// Generate code to get the char value that we are switching on.
ExpressionContextPair ecp = generateUnboxedArgument(JavaTypeName.CHAR, eswitch.getSwitchExpr(), variableContext);
switchBlock.addStatement(ecp.getContextBlock());
JavaExpression conditionExpression = ecp.getJavaExpression();
switchStatement = new SwitchStatement(conditionExpression);
switchBlock.addStatement(switchStatement);
// Populate the switch statement with case statement groups.
for (final SwitchAlt alt : alts) {
// Create a new child variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
Block caseBlock = new Block();
// If we are switching on an character we should never have alt variables.
if (alt.hasVars()) {
throw new CodeGenerationException ("Alt vars encountered in character switch in " + getFunctionName() + ".");
}
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
if (alt.isDefaultAlt()) {
switchStatement.addCase(new SwitchStatement.DefaultCase(caseBlock));
} else {
List<Object> altTags = alt.getAltTags();
int[] caseLabels = new int[altTags.size()];
int index = 0;
for (final Object tag : altTags) {
if (!(tag instanceof Character)) {
throw new CodeGenerationException ("Unknown tag type in case statement in " + getFunctionName() + ":" + tag.getClass().getName() + ".");
}
caseLabels[index] = ((Character)tag).charValue();
index++;
}
switchStatement.addCase(new SwitchStatement.IntCaseGroup(caseLabels, caseBlock));
}
}
// Add case alternate for default case if missing.
if (switchStatement.getDefaultStatement() == null) {
//this will mostly be a user error e.g.
//(\x -> case x of 'a' -> "char a";) 'b'
//todoBI pass the unhandled char value that occurred at runtime i.e. 'b' in the above example, to the error message
JavaStatement defaultCase = generateReturn(getUnhandledSwitchIndexForCharPatternCall(eswitch.getErrorInfo()), variableContext);
switchStatement.addCase (new SwitchStatement.DefaultCase(defaultCase));
}
return switchBlock;
}
/**
* If all the branches in a CAL switch return a literal boolean the generated function
* can be optimized into boolean java expression.
* @param eswitch
* @param variableContext
* @return JavaStatement the switch statement. All possible code paths within this statement will result in a return statement.
* @throws CodeGenerationException
*/
private JavaStatement generateIsAFunctionFromSwitch(Expression.Switch eswitch, VariableContext variableContext) throws CodeGenerationException {
// Since this switch returns either true or false we can generate code in the form.
// int $intVal = switchExpression;
// ($intVal == a || $intVal == b)
// Where a, b, etc. are the tags for the alternates that return true.
// One wrinkle is if a default is included.
// If the default returns false code can be generated as described above.
// If the default returns true the logic needs to be reversed.
// i.e. whatever the default case returns needs to be returned by the else part of the expression.
// So:
// int $intVal = switchExpression;
// !($intVal == c || $intVal == d)
// Where c and d are the cases that return false.
Block isABlock = new Block();
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eswitch.getAlts();
boolean lookingForTrue = true;
int defaultIndex = -1;
int trueCount = 0;
// Need to determine if there is a default case and how many true and false cases.
for (int i = 0; i < alts.length; ++i) {
Expression altExpr = alts[i].getAltExpr();
Expression.Literal lit = altExpr.asLiteral();
Expression.Var var = altExpr.asVar();
boolean isTrue = false;
if (lit != null && lit.getLiteral() instanceof Boolean && ((Boolean)lit.getLiteral()).booleanValue()) {
isTrue = true;
trueCount++;
} else
if (var != null && var.getDataConstructor() != null && isTrueDataCons(var.getDataConstructor())) {
isTrue = true;
trueCount++;
}
if(alts[i].isDefaultAlt()) {
defaultIndex = i;
// If the default case returns true we want to build up our expression based
// on the cases that return false.
if (isTrue) {
lookingForTrue = false;
}
}
}
if (trueCount == alts.length) {
isABlock.addStatement(
generateReturn(
boxPrimitiveOpResult(
PrimOps.PRIMOP_OR,
JavaExpression.LiteralWrapper.make(Boolean.TRUE)), variableContext));
return isABlock;
}
if (trueCount == 0) {
isABlock.addStatement(generateReturn(boxPrimitiveOpResult(PrimOps.PRIMOP_OR, JavaExpression.LiteralWrapper.make(Boolean.FALSE)), variableContext));
return isABlock;
}
int falseCount = alts.length - trueCount;
// Generate the switch conditional.
ExpressionContextPair pair = genS_E (eswitch.getSwitchExpr(), variableContext);
isABlock.addStatement(pair.getContextBlock());
JavaExpression isAExpr = null;
JavaExpression tagValue = SCJavaDefn.createInvocation (pair.getJavaExpression(), SCJavaDefn.GETORDINALVALUE);
if ((lookingForTrue && trueCount > 1) || (!lookingForTrue && falseCount > 1)) {
LocalVariable intVal = new LocalVariable ("$intVal", JavaTypeName.INT);
LocalVariableDeclaration intValDeclaration = new LocalVariableDeclaration(intVal, tagValue);
isABlock.addStatement (intValDeclaration);
tagValue = intVal;
}
for (int i = 0; i < alts.length; ++i) {
// We arrange things so that we don't have to explicitly handle the default case.
if (i == defaultIndex) {
continue;
}
Expression altExpr = alts[i].getAltExpr();
Expression.Literal lit = altExpr.asLiteral();
Expression.Var var = altExpr.asVar();
boolean isTrue = false;
if (lit != null && lit.getLiteral() instanceof Boolean && ((Boolean)lit.getLiteral()).booleanValue()) {
isTrue = true;
} else
if (var != null && var.getDataConstructor() != null && isTrueDataCons(var.getDataConstructor())) {
isTrue = true;
}
if (isTrue == lookingForTrue) {
for (final Object altTag : alts[i].getAltTags()) {
int ord = ((DataConstructor)altTag).getOrdinal();
JavaExpression litOrd = JavaExpression.LiteralWrapper.make(Integer.valueOf(ord));
JavaExpression eqExpression = new JavaExpression.OperatorExpression.Binary(JavaOperator.EQUALS_INT, tagValue, litOrd);
if (isAExpr == null) {
isAExpr = eqExpression;
} else {
isAExpr = new JavaExpression.OperatorExpression.Binary(JavaOperator.CONDITIONAL_OR, isAExpr, eqExpression);
}
}
}
}
if (isAExpr == null) {
isAExpr = JavaExpression.LiteralWrapper.make(Boolean.valueOf(!lookingForTrue));
} else {
if (!lookingForTrue) {
isAExpr = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, isAExpr);
}
}
isABlock.addStatement(generateReturn(boxPrimitiveOpResult(PrimOps.PRIMOP_OR, isAExpr), variableContext));
return isABlock;
}
/**
* In cases where the tag in a switch is a boolean it can be optimised as an if-then-else.
* @param eSwitch
* @param variableContext
* @return JavaStatement
* @throws CodeGenerationException
*/
private JavaStatement generateIfThenElseFromSwitch (Expression.Switch eSwitch, VariableContext variableContext) throws CodeGenerationException {
// Extract the alternatives
Expression.Switch.SwitchAlt[] alts = eSwitch.getAlts();
Expression trueAlt = null;
Expression falseAlt = null;
Expression defaultAlt = null;
for (int i = 0; i < alts.length; ++i) {
SwitchAlt switchAlt = alts[i];
if (switchAlt.isDefaultAlt()) {
defaultAlt = switchAlt.getAltExpr();
} else {
if (switchAlt.getAltTags().size() > 1) {
// (True | False) - ie. always.
return genS_R(switchAlt.getAltExpr(), variableContext);
}
Object altTag = switchAlt.getFirstAltTag();
if (altTag instanceof DataConstructor) {
// This is either Prelude.True or Prelude.False
DataConstructor dc = (DataConstructor)altTag;
if (dc.getName().equals(CAL_Prelude.DataConstructors.True)) {
altTag = Boolean.TRUE;
} else
if (dc.getName().equals(CAL_Prelude.DataConstructors.False)) {
altTag = Boolean.FALSE;
} else {
// We should never get here.
throw new CodeGenerationException ("Trying to generate if-then-else from data constructor: " + dc.getName().getQualifiedName());
}
}
// Tag is a boolean.
if (((Boolean)altTag).booleanValue()) {
trueAlt = switchAlt.getAltExpr();
} else {
falseAlt = switchAlt.getAltExpr();
}
}
}
if (trueAlt == null) {
trueAlt = defaultAlt;
}
if (falseAlt == null) {
falseAlt = defaultAlt;
}
Expression condExpression = eSwitch.getSwitchExpr();
ExpressionContextPair pair = generateUnboxedArgument(JavaTypeName.BOOLEAN, condExpression, variableContext);
// Then
Block thenBlock;
if (trueAlt == null) {
LiteralWrapper badValueMessageWrapper = LiteralWrapper.make("Illegal fallthrough to default case.");
JavaExpression returnValueExpression = getBadValueCall(eSwitch.getErrorInfo(), badValueMessageWrapper);
JavaStatement badValueReturnStatement = generateReturn(returnValueExpression, variableContext);
thenBlock = new Block();
thenBlock.addStatement (badValueReturnStatement);
} else {
variableContext.pushJavaScope();
JavaStatement thenPart = genS_R (trueAlt, variableContext);
thenBlock = variableContext.popJavaScope();
thenBlock.addStatement(thenPart);
}
// Else
Block elseBlock;
if (falseAlt == null) {
LiteralWrapper badValueMessageWrapper = LiteralWrapper.make("Illegal fallthrough to default case in function.");
JavaStatement badValueReturnStatement = generateReturn(getBadValueCall(eSwitch.getErrorInfo(), badValueMessageWrapper), variableContext);
elseBlock = new Block();
elseBlock.addStatement (badValueReturnStatement);
} else {
variableContext.pushJavaScope();
JavaStatement elsePart = genS_R (falseAlt, variableContext);
elseBlock = variableContext.popJavaScope();
elseBlock.addStatement(elsePart);
}
JavaStatement ite = new IfThenElseStatement(pair.getJavaExpression(), thenBlock, elseBlock);
Block contextBlock = pair.getContextBlock();
contextBlock.addStatement(ite);
return contextBlock;
}
/**
* This method generates the java code for a switch statement when we know that the switch can be ignored.
* Examples would be cases where the data type being switched on has a single data constructor or there
* is only a default alternate.
* @param eSwitch
* @param variableContext
* @return The java code corresponding to the switch.
* @throws CodeGenerationException
*/
private JavaStatement generateSingleAltSwitch (Expression.Switch eSwitch, VariableContext variableContext) throws CodeGenerationException {
// If this is the method being called to generate a switch we know a few things:
// If there is more than one alternate we can ignore the default alternate.
// We are only going to generate code for one alternate.
// We don't need a switch statement.
// First let's figure out which alternate we're going to be compiling.
Expression.Switch.SwitchAlt[] alts = eSwitch.getAlts();
Expression.Switch.SwitchAlt alt = alts[0];
if (alts.length > 1) {
// Use the first non-default alt.
for (int i = 0; i < alts.length; ++i) {
if (!alts[i].isDefaultAlt()) {
alt = alts[i];
break;
}
}
}
// Now we want to continue with almost the same process as when dealing with a regular switch.
Block switchBlock = new Block();
Object altTag = alt.getFirstAltTag();
// Preserve the behaviour of a switch, i.e. the switch expression will be
// strictly evaluated before falling through to the single default alternate.
// This is really only needed if the switch expression can have a side effect.
boolean isSwitchingOnDC = !alt.isDefaultAlt() && (altTag instanceof DataConstructor);
boolean switchExprEvaluated = false;
Expression.Var switchVar = eSwitch.getSwitchExpr().asVar();
if (switchVar != null) {
switchExprEvaluated = variableContext.isVarPreEvaluated(switchVar.getName());
}
// Only do a local variable if the switch expression is not evaluated or we will
// be extracting member fields from the data constructor resulting from evaluating the switch expression.
LocalVariable caseVar = null;
JavaTypeName caseVarType = (isSwitchingOnDC && alt.hasVars()) ? JavaTypeNames.RTCONS : JavaTypeNames.RTVALUE;
if (!switchExprEvaluated || alt.hasVars()) {
caseVar = new LocalVariable("$case" + nestedCaseLevel, caseVarType);
ExpressionContextPair pair = genS_E(eSwitch.getSwitchExpr(), variableContext);
switchBlock.addStatement(pair.getContextBlock());
JavaExpression caseExpression = pair.getJavaExpression();
if (caseVarType.equals(JavaTypeNames.RTCONS)) {
caseExpression = new CastExpression(JavaTypeNames.RTCONS, caseExpression);
}
LocalVariableDeclaration caseVarDeclaration =
new LocalVariableDeclaration(caseVar, caseExpression);
switchBlock.addStatement(caseVarDeclaration);
}
// Populate the switch statement with case statement groups.
// Create a new let variable scope to handle the alternate and any let variables it contains.
variableContext.pushJavaScope();
Block caseBlock = new Block();
if (isSwitchingOnDC) {
caseBlock.addStatement(new LineComment(((DataConstructor)altTag).getName().getQualifiedName()));
}
// Get this alternative's variables. These have to be added to the active list of scope variables
if (alt.hasVars()) {
caseBlock.addStatement(new LineComment("Decompose data type to access members."));
if (caseVar == null) {
throw new CodeGenerationException ("Null case variable encountered in single alternate switch.");
}
// Must be a data constructor tag, since there are field names (see Expression.Switch.SwitchAlt).
DataConstructor tagDC = (DataConstructor)altTag;
TypeExpr fieldTypes[] = SCJavaDefn.getFieldTypesForDC(tagDC);
// Cast the case var to the appropriate type to call getField0, getField1, etc.
JavaTypeName dcTypeName = CALToJavaNames.createTypeNameFromDC(tagDC, module);
JavaExpression castExpression = new CastExpression(dcTypeName, caseVar);
// There is at least one field to be extracted.
// Declare a local variable that is the casted case var so that we only do the cast once.
JavaExpression castCaseVar = new LocalVariable("$dcCaseVar" + nestedCaseLevel, dcTypeName);
LocalVariableDeclaration localVarStmnt = new LocalVariableDeclaration((LocalVariable)castCaseVar, castExpression);
caseBlock.addStatement (localVarStmnt);
for (final AltVarIndexPair altVarIndexPair : getAltVarIndexList(alt, tagDC)) {
String altVar = altVarIndexPair.getAltVar();
int fieldIndex = altVarIndexPair.getIndex();
String fieldGetterName = "get" + SCJavaDefn.getJavaFieldNameFromDC(tagDC, fieldIndex);
// Build in place representation of this variable mapped to its name
QualifiedName qn = QualifiedName.make(currentModuleName, altVar);
VarInfo.DCMember vi = variableContext.addDCField(qn, fieldTypes[fieldIndex]);
boolean fieldIsStrict =
!LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS && tagDC.isArgStrict(fieldIndex);
if (fieldIsStrict) {
vi.setEvaluated(true);
}
if (fieldIsStrict) {
if (SCJavaDefn.canTypeBeUnboxed(fieldTypes[fieldIndex])) {
JavaTypeName strictType = SCJavaDefn.typeExprToTypeName(fieldTypes[fieldIndex]);
JavaExpression unboxedInitializer =
new JavaExpression.MethodInvocation.Instance(castCaseVar,
fieldGetterName + "_As_" + SCJavaDefn.getNameForPrimitive(strictType),
strictType,
JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateUnboxedVarDef(unboxedInitializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName()+"$U", vi.getUnboxedType());
vi.updateUnboxedReference(localVar);
JavaExpression boxedDef = SCJavaDefn.boxExpression(vi.getUnboxedType(), localVar);
vi.updateStrictReference(boxedDef);
vi.updateLazyReference(boxedDef);
} else {
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(castCaseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateStrictVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateStrictReference(localVar);
vi.updateLazyReference(localVar);
}
} else {
JavaExpression initializer = new JavaExpression.MethodInvocation.Instance(castCaseVar, fieldGetterName, JavaTypeNames.RTVALUE, JavaExpression.MethodInvocation.InvocationType.VIRTUAL);
vi.updateLazyVarDef (initializer);
JavaExpression localVar = new LocalVariable(vi.getJavaName(), JavaTypeNames.RTVALUE);
vi.updateLazyReference(localVar);
JavaExpression evaluatedVar = SCJavaDefn.createInvocation(localVar, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
vi.updateStrictReference(evaluatedVar);
if (SCJavaDefn.canTypeBeUnboxed(fieldTypes[fieldIndex])) {
vi.updateUnboxedReference(SCJavaDefn.unboxValue(vi.getUnboxedType(), evaluatedVar));
}
}
}
}
JavaStatement altStatement = genS_R(alt.getAltExpr(), variableContext);
caseBlock.addStatement(variableContext.popJavaScope());
caseBlock.addStatement(altStatement);
switchBlock.addStatement(caseBlock);
return switchBlock;
}
/**
* Place an application chain into a more easily manageable format.
* @param root
* @return Expression[]
*/
private Expression[] appChain (Expression.Appl root) {
// Walk down the left branch.
Expression c = root;
int nArgs = 0;
while (c instanceof Expression.Appl) {
nArgs++;
c = ((Expression.Appl)c).getE1();
}
Expression[] chain = new Expression [nArgs + 1];
chain[0] = c;
c = root;
for (int i = nArgs; i >= 1; i--) {
chain[i] = ((Expression.Appl)c).getE2();
c = ((Expression.Appl)c).getE1();
}
return chain;
}
/**
* Generate the expression context pair for a fully saturated application at the top level.
* @param chain
* @param calledArity
* @param calledArgStrictness
* @param calledArgTypes
* @param variableContext
* @return the code that creates the graph node
* @throws CodeGenerationException
*/
private ExpressionContextPair buildTopLevelApplicationChain(Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
VariableContext variableContext) throws CodeGenerationException {
boolean recursiveCallInStrictArg = false;
for (int i = 1, n = chain.length; !recursiveCallInStrictArg && i < n; ++i) {
if (calledArgStrictness[i-1]) {
recursiveCallInStrictArg = ExpressionAnalyzer.dependsOnStronglyConnectedComponent(chain[i], connectedComponents, getModuleName());
}
}
// If none of the strict arguments contain a recursive call we can build
// a strict application node or, potentially, do the call directly on the stack.
if (!recursiveCallInStrictArg) {
// Try to build a direct call.
ExpressionContextPair directCall = buildDirectCall(chain, calledArity, calledArgStrictness, calledArgTypes, Scheme.R_SCHEME, variableContext);
if (directCall != null) {
return directCall;
}
// We can't do a direct call on the java stack, but we know the strict arguments don't contain
// recursive calls, so we can build a strict application node.
return buildStrictApplicationNode(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
}
return buildLazyApplicationNode(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
}
/**
* Determine if it is safe to generate a call on the java stack instead of
* building an application graph. Generally this is concerned with making
* sure we don't break correct space behaviour.
* @param calledFunction
* @param calledArgTypes
* @param scheme
* @return - true if the specified function can be called directly on the call stack.
*/
private boolean canCallDirectlyOnJavaStack (
Expression.Var calledFunction,
TypeExpr[] calledArgTypes,
Scheme scheme) throws CodeGenerationException {
// If we are in the E scheme or one of the unboxed schemes
// it won't make any difference space-wise whether
// we do the call directly or build the graph, since we'll immediately
// reduce the graph anyway.
if (scheme == Scheme.E_SCHEME || scheme == Scheme.UNBOX_FOREIGN_SCHEME || scheme == Scheme.UNBOX_INTERNAL_SCHEME) {
return true;
}
// At this point we want to see if the call can be made directly on the java
// stack.
// We can do the call on the stack if:
// - the called function is not a strongly connected component of this function. If it is
// a strongly connected component it is too easy to fill the java call stack with mutually
// recursive calls.
// - the called function is not tail recursive or we can determine that it is a tail recursive
// function that can be called directly. The problem with directly calling a tail recursive
// function is that the roots of the function arguments get held on the java call stack.
// This can cause memory usage issues because of the optimization which converts tail
// recursion to a loop. If the loop expands/consumes an argument which is a data structure
// such as a list the consumed part will not be garbage collected because the root is still
// held on the call stack. In normal reduction this wouldn't be a problem but the loop in
// the tail recursive function changes things.
// So, if we can determine that none of the arguments to a tail recursive function have the
// potential to grow in this fashion then we can safely do a direct call to the tail recursive
// function.
// Try to collect information about the called function.
// This is used to decide how to encode the function call.
MachineFunction mf = module.getFunction(calledFunction.getName());
if (mf == null) {
return false;
}
// Start of by checking if the called function is a closely connected component of this function.
boolean safeToCallOnJavaStack =
(!mf.getQualifiedName().getModuleName().equals(this.getModuleName()) ||!connectedComponents.contains(calledFunction.getName().getUnqualifiedName()));
if (safeToCallOnJavaStack && mf.isTailRecursive()) {
// Since the called function is tail recursive we need to
// check the types of its arguments.
// At the moment we do the simple check of allowing the call if
// all arguments are primitive/foreign or an enum data type.
// This way we know that holding the roots of the arguments can safely be held on the
// java call stack since none of them can grow and be consumed in an unbounded fashion
// by the loop in the tail recursive function.
for (int i = 0, n = calledArgTypes.length; i < n && safeToCallOnJavaStack; ++i) {
TypeExpr argType = calledArgTypes[i];
safeToCallOnJavaStack = argType != null &&
(SCJavaDefn.canTypeBeUnboxed(argType) ||
SCJavaDefn.isEnumDataType(argType) ||
!SCJavaDefn.isSelfReferentialDataType(argType));
}
}
return safeToCallOnJavaStack;
}
/**
* Build a strict application node from a fully saturated application.
* @param chain
* @param calledArity
* @param calledArgStrictness
* @param calledArgTypes
* @param variableContext
* @return the code that creates the graph node
* @throws CodeGenerationException
*/
private ExpressionContextPair buildStrictApplicationNode(Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
VariableContext variableContext) throws CodeGenerationException {
Expression.Var var = (Expression.Var)chain[0];
// We want to use a type specific app node if the called function has unboxed arguments or
// the called function is a tail recursive loop.
boolean useTypeSpecificAppNode = false;
for (int i = 0; i < calledArgTypes.length; ++i) {
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
useTypeSpecificAppNode = true;
break;
}
}
MachineFunction mf = module.getFunction(var.getName());
if (mf != null && mf.isTailRecursive()) {
useTypeSpecificAppNode = true;
}
// We want to use the SC specific application node that takes unboxed
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 0; i < chain.length; ++i) {
if (i > 0 && calledArgStrictness[i-1]) {
if (SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i-1])) {
ecp[i] = generateUnboxedArgument(typeExprToTypeName(calledArgTypes[i-1]), chain[i], variableContext);
} else {
ecp[i] = genS_E(chain[i], variableContext);
}
} else {
ecp[i] = genS_C(chain[i], variableContext);
}
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
JavaExpression root = ecp[0].getJavaExpression();
if (codeGenerationStats != null) {
if (var.getForeignFunctionInfo() == null) {
codeGenerationStats.incrementStrictSCNodeCount();
} else {
codeGenerationStats.incrementStrictForeignNodeCount();
}
}
// Optimize Prelude.not to use the '!' java operator.
if (var.getName().equals(CAL_Prelude.Functions.not)) {
JavaExpression not = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, ecp[1].getJavaExpression());
return new ExpressionContextPair (not, newContext);
}
// This is a fully saturated supercombinator application that can be represented
// using an special purpose graph node.
JavaExpression args[] = new JavaExpression[calledArity + 1];
args[0] = expressionVarToJavaDef(var, Scheme.E_SCHEME, variableContext);
JavaTypeName argTypes[] = new JavaTypeName[calledArity + 1];
if (useTypeSpecificAppNode) {
argTypes[0] = CALToJavaNames.createTypeNameFromSC(var.getName(), module);
} else {
argTypes[0] = JavaTypeNames.RTSUPERCOMBINATOR;
}
for (int i = 0; i < calledArity; ++i) {
args[i+1] = ecp[i+1].getJavaExpression();
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
argTypes[i+1] = SCJavaDefn.typeExprToTypeName(calledArgTypes[i]);
} else {
argTypes[i+1] = JavaTypeNames.RTVALUE;
}
}
JavaTypeName appNodeType;
if (useTypeSpecificAppNode) {
appNodeType = CALToJavaNames.createStrictInnerTypeNameFromSC(var.getName(), module);
} else {
appNodeType = getTypeNameForApplicationNode(chain.length-1, true);
}
root = new JavaExpression.ClassInstanceCreationExpression(appNodeType, args, argTypes);
return new ExpressionContextPair(root, newContext);
}
/**
* This function generates java code to create a partial application node.
* A partial application node holds a supercombinator and the arguments it is
* applied to. The node then acts like a supercombinator of arity n where n
* is the arity of the original supercombinator less the number of applied arguments.
* We cannot generate one of these nodes if:
* 1) We cannot determine the specific supercombinator being applied
* 2) The supercombinator arity is greater than the number of args for which a special node exists
* 3) The supercombinator is tail recursive.
* @param chain
* @param mf
* @param variableContext
* @return java code to generate a partial application node if possible, null otherwise
* @throws CodeGenerationException
*/
private ExpressionContextPair buildUndersaturatedAppNode(
Expression[] chain,
MachineFunction mf,
VariableContext variableContext) throws CodeGenerationException {
/*
* This method is currently disabled due to some apparent memory usage
* issues that come up with using partial application nodes.
*/
int calledArity = mf.getArity();
/* We only have specialized application nodes for SCs of up to arity 15. */
if (calledArity > LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH) {
return null;
}
/*
* We can't use one of these nodes if the supercombinator is tail recursive.
* This is because the f/fnL/fnS method of a partial application node makes
* a direct call to the supercombinators f/fnS/fnL method. This means that
* the roots of the function arguments have a reference in the java call
* stack while the supercombinators f/fnS/fnL method is executing. In the
* case of a tail recursive function, whose body is transformed to a loop to
* optimize the recursion, this can cause memory usage issues when the
* arguments are evaluated since the root of the argument can't be released
* and garbage collected.
*/
if (mf.isTailRecursive()) {
return null;
}
// Get the JavaTypeName for the partial application node.
JavaTypeName nodeClassName = JavaTypeName.make("org.openquark.cal.internal.runtime.lecc.RTPartialApp$_"+calledArity+"$_"+(chain.length-1), false);
int length = chain.length;
// Compile each of the arguments in the partial application lazily.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 1; i < length; ++i) {
ecp[i] = genS_C(chain[i], variableContext);
}
// Build up the context from the compiled arguments.
Block newContext = ecp[1].getContextBlock();
for (int i = 2; i < length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
// Build the statement to create a new partial application node.
JavaExpression args[] = new JavaExpression[length];
JavaTypeName argTypes[] = new JavaTypeName[length];
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
args[0] = expressionVarToJavaDef(chain[0].asVar(), Scheme.E_SCHEME, variableContext);
argTypes[0] = JavaTypeNames.RTSUPERCOMBINATOR;
for (int i = 1; i < length; ++i) {
args[i] = ecp[i].getJavaExpression();
}
JavaExpression cci =
new ClassInstanceCreationExpression(nodeClassName, args, argTypes);
return new ExpressionContextPair(cci, newContext);
}
/**
* Do the necessary re-assignemnts to modify a tail recursive call into
* a loop continuation.
* @param rc
* @param variableContext
* @return a block of java statements.
* @throws CodeGenerationException
*/
private JavaStatement buildTailRecursiveLoopCall (Expression.TailRecursiveCall rc, VariableContext variableContext) throws CodeGenerationException {
if (codeGenerationStats != null) {
codeGenerationStats.incrementTailRecursiveFunctions(getArity());
}
Block newContext = new Block ();
Expression[] args = rc.getArguments();
// We want to use the SC specific application node.
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecps = new ExpressionContextPair[args.length];
for (int i = 0; i < args.length; ++i) {
if (argumentStrictness[i]) {
if (SCJavaDefn.canTypeBeUnboxed(argumentTypes[i])) {
ecps[i] = generateUnboxedArgument(typeExprToTypeName(argumentTypes[i]), args[i], variableContext);
} else {
ecps[i] = genS_E(args[i], variableContext);
}
} else {
ecps[i] = genS_C(args[i], variableContext);
}
}
for (int i = 0; i < args.length; ++i) {
newContext.addStatement(ecps[i].getContextBlock());
}
Block tempAssignment = new Block ();
Block reAssignment = new Block ();
// Do function argument re-assignment using temporaries.
// We use temporaries to avoid problems with function interdependencies.
// i.e. simply transforming: new RTAppS ($instance, n - 1, acc + n); to:
// n = n - 1; acc = acc + n; continue; will give incorrect results because
// the value of n has been re-assigned before calculating acc + n.
// Try to avoid the temporary local variable where possible.
int[] needTemp = new int[getArity()]; // 0 -> temp, 1 -> assign, 2 -> skip
Arrays.fill (needTemp, 0);
// If we are assigning an atomic entity i.e. literal or var we don't need the temporary.
for (int i = 0; i < getArity(); ++i) {
if (args[i].asLiteral() != null) {
needTemp[i] = 1;
} else
if (args[i].asVar() != null) {
QualifiedName newVarName = args[i].asVar().getName();
// If the argument is being passed unchanged to the tail recursive call we can skip the assignment.
if (newVarName.equals(QualifiedName.make(currentModuleName, argumentNames[i])) &&
variableContext.isFunctionArgument(newVarName)) {
// The argument passed to the function is being passed unchanged in the tail recursive call.
needTemp[i] = 2;
} else
// If the var is a local or an sc do a simple assignment
if (!variableContext.isLocalVariable(newVarName) ||
variableContext.isDCField(newVarName) ||
variableContext.isRecordField(newVarName)) {
needTemp[i] = 1;
} else
// If the assignment is to a following argument it is safe to do a simple assignment.
if (variableContext.isFunctionArgument(newVarName)) {
for (int j = i + 1; j < getArity(); ++j) {
if (argumentNames[j].equals(newVarName.getUnqualifiedName())) {
needTemp[i] = 1;
}
}
}
} else {
boolean dependent = false;
for (int j = 0; j < i; j++) {
QualifiedName argName = QualifiedName.make(currentModuleName, argumentNames[j]);
if (needTemp[j] != 2 && args[i].isDependentOn(argName)) {
dependent = true;
break;
}
}
if (!dependent) {
needTemp[i] = 1;
}
}
}
for (int i = 0; i < getArity(); ++i) {
JavaTypeName argType = JavaTypeNames.RTVALUE;
if (isArgUnboxable(i) && isArgStrict (i)) {
argType = typeExprToTypeName(argumentTypes[i]);
}
if (needTemp[i] == 0) {
// Assign the new argument value to a local variable.
// RTValue arg1$ = ...;
JavaStatement.LocalVariableDeclaration decl = new LocalVariableDeclaration (new LocalVariable (getJavaArgumentName(i)+"$", argType),
ecps[i].getJavaExpression());
tempAssignment.addStatement (decl);
// Assign back to the argument.
// arg1 = arg1$;
Assignment a = new Assignment(new JavaExpression.LocalName(getJavaArgumentName(i), argType),
new JavaExpression.LocalName(getJavaArgumentName(i) + "$", argType));
reAssignment.addStatement(new ExpressionStatement(a));
} else
if (needTemp[i] == 1){
// Simply assign the new definition to the argument.
// arg1 = ...;
Assignment a = new Assignment(new JavaExpression.LocalName(getJavaArgumentName(i), argType),
ecps[i].getJavaExpression());
reAssignment.addStatement(new ExpressionStatement(a));
}
}
newContext.addStatement (tempAssignment);
newContext.addStatement (reAssignment);
newContext.addStatement(new JavaStatement.LabelledContinue(TAIL_RECURSION_LOOP_LABEL));
return newContext;
}
/**
* Generate the code corresponding to a fully saturated application encountered in a strict context.
* @param chain
* @param calledArity
* @param calledArgStrictness
* @param calledArgTypes
* @param variableContext
* @return the code that creates the graph node, null if a strict application chain can't be built
* @throws CodeGenerationException
*/
private ExpressionContextPair buildStrictApplicationChain(Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
VariableContext variableContext) throws CodeGenerationException {
return buildDirectCall (chain, calledArity, calledArgStrictness, calledArgTypes, Scheme.E_SCHEME, variableContext);
}
/**
* Generated a direct call to the function body for a fully saturated application.
* @param chain
* @param calledArity
* @param calledArgStrictness
* @param calledArgTypes
* @param scheme
* @param variableContext
* @return the code that creates the graph node, null if the function cannot be called directly.
* @throws CodeGenerationException
*/
private ExpressionContextPair buildDirectCall(Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
Expression.Var var = (Expression.Var)chain[0];
if (!canCallDirectlyOnJavaStack (chain[0].asVar(), calledArgTypes, scheme)) {
return null;
}
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 0; i < chain.length; ++i) {
if (i > 0 && calledArgStrictness[i-1]) {
if (SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i-1])) {
ecp[i] = generateUnboxedArgument(typeExprToTypeName(calledArgTypes[i-1]), chain[i], variableContext);
} else {
ecp[i] = genS_E(chain[i], variableContext);
}
} else {
ecp[i] = genS_C(chain[i], variableContext);
}
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
JavaExpression root = ecp[0].getJavaExpression();
if (codeGenerationStats != null) {
if (var.getForeignFunctionInfo() != null) {
codeGenerationStats.incrementDirectForeignCalls();
} else {
codeGenerationStats.incrementDirectSCCalls();
}
}
// Optimize Prelude.not to use the '!' java operator.
if (var.getName().equals(CAL_Prelude.Functions.not)) {
JavaExpression not = new JavaExpression.OperatorExpression.Unary(JavaOperator.LOGICAL_NEGATE, ecp[1].getJavaExpression());
return new ExpressionContextPair (not, newContext);
}
// This is a fully saturated supercombinator application
// for which we can generate a direct call instead of building a suspension.
JavaExpression args[] = new JavaExpression[calledArity + 1];
for (int i = 0; i < calledArity; ++i) {
args[i] = ecp[i + 1].getJavaExpression();
}
args[calledArity] = EXECUTION_CONTEXT_VAR;
// Get the supercombinator class and do a direct application of the fn function.
// arg types are all RTValue except when strict
JavaTypeName[] argTypes = new JavaTypeName[args.length];
for (int i = 0; i < calledArgTypes.length; ++i) {
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
argTypes[i] = SCJavaDefn.typeExprToTypeName(calledArgTypes[i]);
} else {
argTypes[i] = JavaTypeNames.RTVALUE;
}
}
argTypes[calledArity] = JavaTypeNames.RTEXECUTION_CONTEXT;
root = expressionVarToJavaDef(var, Scheme.E_SCHEME, variableContext);
// Get the method name to call.
LECCModule.FunctionGroupInfo fgi = module.getFunctionGroupInfo(var.getName());
String functionName = fgi.getFnNamePrefix(var.getName().getUnqualifiedName()) + "f";
if (calledArity > 0) {
functionName = functionName + calledArity + "S";
} else {
args = new JavaExpression[]{JavaExpression.LiteralWrapper.NULL, EXECUTION_CONTEXT_VAR};
argTypes = new JavaTypeName[]{JavaTypeNames.RTVALUE, JavaTypeNames.RTEXECUTION_CONTEXT};
}
root = new MethodInvocation.Instance(root, functionName, args, argTypes, JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
if (scheme == Scheme.E_SCHEME) {
root = createInvocation (root, EVALUATE, EXECUTION_CONTEXT_VAR);
}
return new ExpressionContextPair(root, newContext);
}
/**
* Generated the expression/context pair corresponding to a fully saturated application encountered in a
* lazy context.
* @param chain
* @param calledArity
* @param calledArgStrictness
* @param calledArgTypes
* @param variableContext
* @return the code that creates the graph node
* @throws CodeGenerationException
*/
private ExpressionContextPair buildLazyApplicationChain(Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
VariableContext variableContext) throws CodeGenerationException {
if (LECCMachineConfiguration.OPTIMIZE_LAZY_APP_NODES) {
boolean useStrictAppNode = true;
// If all the strict arguments are already evaluated we should use a strict application node.
for (int i = 0; i < calledArity; ++i) {
if (calledArgStrictness[i] && !canIgnoreLaziness(chain[i+1], variableContext)) {
useStrictAppNode = false;
break;
}
}
if (useStrictAppNode) {
return buildStrictApplicationNode(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
}
}
return buildLazyApplicationNode (chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
}
/**
* Build a lazy application node from an application chain.
* @param chain
* @param calledArity
* @param variableContext
* @param calledArgStrictness
* @param calledArgTypes
* @return the generated expression/context pair.
* @throws CodeGenerationException
*/
private ExpressionContextPair buildLazyApplicationNode (Expression[] chain,
int calledArity,
boolean[] calledArgStrictness,
TypeExpr[] calledArgTypes,
VariableContext variableContext) throws CodeGenerationException {
Expression.Var var = (Expression.Var)chain[0];
MachineFunction mf = module.getFunction(var.getName());
if (mf == null) {
// Simply return null and let a different compilation path handle
// the application.
return null;
}
// At this point we need to do a check to make sure that we can
// build a lazy application using a special purpose node.
// The function must have an arity small enough to use a predefined
// lazy app node or be tail recursive. Special purpose lazy app nodes
// are generated for tail recursive functions with arity greather than
// that accomodated by the predefined lazy app nodes.
if (calledArity > LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH &&
!mf.isTailRecursive()) {
// Simply return null and let a different compilation path handle
// the application.
return null;
}
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 0; i < chain.length; ++i) {
ecp[i] = genS_C(chain[i], variableContext);
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
if (codeGenerationStats != null) {
if (var.getForeignFunctionInfo() != null) {
codeGenerationStats.incrementLazyForeignNodeCount();
} else {
codeGenerationStats.incrementLazySCNodeCount();
}
}
// This is a fully saturated supercombinator application that can be represented
// using an special purpose graph node.
JavaExpression args[] = new JavaExpression[calledArity];
for (int i = 0; i < calledArity; ++i) {
args[i] = ecp[i + 1].getJavaExpression();
}
JavaExpression scInstance = expressionVarToJavaDef(var, Scheme.C_SCHEME, variableContext);
JavaExpression constructorArgs[] = new JavaExpression [calledArity + 1];
constructorArgs[0] = scInstance;
for (int i = 0; i < args.length; ++i) {
constructorArgs[i+1] = args[i];
}
JavaTypeName constructorArgTypes[] = new JavaTypeName[calledArity + 1];
Arrays.fill(constructorArgTypes, JavaTypeNames.RTVALUE);
constructorArgTypes[0] = JavaTypeNames.RTSUPERCOMBINATOR;
JavaTypeName appNodeType;
// We want to use a type specific app node if the called function
// is a tail recursive loop with arity greater than that supported
// by the predefined lazy app nodes.
if (mf.isTailRecursive() && calledArity > LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH) {
appNodeType = CALToJavaNames.createLazyInnerTypeNameFromSC(var.getName(), module);
constructorArgTypes[0] = CALToJavaNames.createTypeNameFromSC(var.getName(), module);
} else {
appNodeType = getTypeNameForApplicationNode(chain.length-1, false);
}
return new ExpressionContextPair(new JavaExpression.ClassInstanceCreationExpression(appNodeType, constructorArgs, constructorArgTypes), newContext);
}
/**
* Get the JavaTypeName for one of the classes used to represent
* fully saturated function applications.
* @param nApplicationArguments
* @param isStrict
* @return the JavaTypeName for the fully saturated application class.
*/
private JavaTypeName getTypeNameForApplicationNode (int nApplicationArguments, boolean isStrict) {
switch (nApplicationArguments) {
case 1:
return isStrict ? JavaTypeNames.RTAPP1S : JavaTypeNames.RTAPP1L;
case 2:
return isStrict ? JavaTypeNames.RTAPP2S : JavaTypeNames.RTAPP2L;
case 3:
return isStrict ? JavaTypeNames.RTAPP3S : JavaTypeNames.RTAPP3L;
case 4:
return isStrict ? JavaTypeNames.RTAPP4S : JavaTypeNames.RTAPP4L;
case 5:
return isStrict ? JavaTypeNames.RTAPP5S : JavaTypeNames.RTAPP5L;
case 6:
return isStrict ? JavaTypeNames.RTAPP6S : JavaTypeNames.RTAPP6L;
case 7:
return isStrict ? JavaTypeNames.RTAPP7S : JavaTypeNames.RTAPP7L;
case 8:
return isStrict ? JavaTypeNames.RTAPP8S : JavaTypeNames.RTAPP8L;
case 9:
return isStrict ? JavaTypeNames.RTAPP9S : JavaTypeNames.RTAPP9L;
case 10:
return isStrict ? JavaTypeNames.RTAPP10S : JavaTypeNames.RTAPP10L;
case 11:
return isStrict ? JavaTypeNames.RTAPP11S : JavaTypeNames.RTAPP11L;
case 12:
return isStrict ? JavaTypeNames.RTAPP12S : JavaTypeNames.RTAPP12L;
case 13:
return isStrict ? JavaTypeNames.RTAPP13S : JavaTypeNames.RTAPP13L;
case 14:
return isStrict ? JavaTypeNames.RTAPP14S : JavaTypeNames.RTAPP14L;
case 15:
return isStrict ? JavaTypeNames.RTAPP15S : JavaTypeNames.RTAPP15L;
}
throw new UnsupportedOperationException("Attempt to get application node class name for " + nApplicationArguments + ".");
}
/**
* Generate the java representation that will build the given application graph.
* @param chain
* @param scheme
* @param variableContext
* @return expression context pair which builds the given application graph.
* @throws CodeGenerationException
*/
private ExpressionContextPair buildGeneralApplicationChain (Expression[] chain, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
if (chain[0].asDataConsSelection() != null && (scheme == Scheme.E_SCHEME || scheme == Scheme.R_SCHEME)) {
ecp[0] = genS_E(chain[0], variableContext);
} else {
ecp[0] = genS_C(chain[0], variableContext);
}
for (int i = 1; i < chain.length; ++i) {
ecp[i] = genS_C(chain[i], variableContext);
}
Block newContext = ecp[0].getContextBlock();
JavaExpression[] je = new JavaExpression [chain.length];
je[0] = ecp[0].getJavaExpression();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
je[i] = ecp[i].getJavaExpression();
}
// If we are in a strict context we want to generate something like:
// arg.f3L(a, b, c).evaluate($ec);
if (scheme == Scheme.E_SCHEME && chain.length - 1 <= LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH) {
JavaExpression[] args = new JavaExpression [chain.length];
System.arraycopy(je, 1, args, 0, args.length -1);
args[args.length-1] = SCJavaDefn.EXECUTION_CONTEXT_VAR;
JavaTypeName[] argTypes = new JavaTypeName[chain.length];
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
argTypes[argTypes.length-1] = JavaTypeNames.RTEXECUTION_CONTEXT;
JavaExpression root = je[0];
MethodInvocation fL = new MethodInvocation.Instance(root, "f" + (chain.length-1) + "L", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
MethodInvocation eval = SCJavaDefn.createInvocation(fL, SCJavaDefn.EVALUATE, EXECUTION_CONTEXT_VAR);
return new ExpressionContextPair(eval, newContext);
}
// Now we want to build the application chain. (As efficiently as possible).
int nApplicationArguments = chain.length - 1;
int argIndex = 1;
JavaExpression root = je[0];
while (nApplicationArguments > 0) {
if (nApplicationArguments >= 4) {
JavaExpression[] args = new JavaExpression[4];
JavaTypeName[] argTypes = new JavaTypeName[4];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(je, argIndex, args, 0, 4);
root = new MethodInvocation.Instance(root, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 4;
nApplicationArguments -= 4;
} else
if (nApplicationArguments == 3) {
JavaExpression[] args = new JavaExpression[3];
JavaTypeName[] argTypes = new JavaTypeName[3];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(je, argIndex, args, 0, 3);
root = new MethodInvocation.Instance(root, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 3;
nApplicationArguments -= 3;
} else
if (nApplicationArguments == 2) {
JavaExpression[] args = new JavaExpression[2];
JavaTypeName[] argTypes = new JavaTypeName[2];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(je, argIndex, args, 0, 2);
root = new MethodInvocation.Instance(root, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 2;
nApplicationArguments -= 2;
} else
if (nApplicationArguments == 1) {
root = new MethodInvocation.Instance(root, "apply", je[argIndex], JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex++;
nApplicationArguments--;
}
}
if (scheme == Scheme.E_SCHEME) {
// Strict application scheme. Call 'evalaute' on the RTValue.
root = createInvocation(root, EVALUATE, EXECUTION_CONTEXT_VAR);
}
return new ExpressionContextPair (root, newContext);
}
private Expression[] flattenTopLevelSeq (Expression e) throws CodeGenerationException {
Collection<Expression> c = new ArrayList<Expression>();
flattenTopLevelSeqHelper(e, c);
if (c.size() <= 1) {
return null;
}
return c.toArray(new Expression[c.size()]);
}
private void flattenTopLevelSeqHelper (Expression e, Collection<Expression> c) throws CodeGenerationException {
if (e.asAppl() == null) {
c.add(e);
return;
}
Expression[] chain = appChain (e.asAppl());
if (chain.length != 3 ||
chain[0].asVar() == null ||
!chain[0].asVar().getName().equals(SCJavaDefn.PRELUDE_SEQ)) {
c.add(e);
return;
}
// At this point we know that we have a fully saturated application of Prelude.seq.
//
// We also know that we are at a 'top' level in the generated java function.
// We want to break this into two java statements instead of calling the
// primitive function Prelude.seq.
// The first statement, corresponding to the first argument to seq, will be evaluated
// and the second statement will return the graph of the second argument to seq.
// We need to flatten the 'seq' expresion out into a collection of expressions, each
// of which will generate a statement in the Java code.
flattenTopLevelSeqHelper (chain[1], c);
flattenTopLevelSeqHelper (chain[2], c);
}
/**
* If the application chain is a fully saturated application
* of Prelude.seq occurring at the top level of the function
* we want to do a special transformation allowing the generated
* Java to have a separate Java statement for each argument to 'seq'.
* The first statement will fully evaluate the first argument to 'seq'
* and the second statement will build/return the graph of the second
* argument.
* This is applied recursively to nested applications of seq.
*
* @param applChain - the application to try to optimize.
* @param isNested - true if buildTopLevelSeq is being called recursively.
* @param variableContext - the current variable context.
* @return null if the applChain is not an application of seq, otherwise a JavaStatement
* @throws CodeGenerationException
*/
private JavaStatement buildTopLevelSeq (Expression.Appl applChain,
boolean isNested,
VariableContext variableContext) throws CodeGenerationException {
Expression expressions[] = flattenTopLevelSeq(applChain);
if (expressions == null) {
return null;
}
Block block = new Block();
// For all but the last expression we want to generate a Java statement
// which will evaluate to WHNF.
for (int i = 0, n = expressions.length - 1; i < n; ++i) {
Expression e = expressions[i];
JavaStatement js = makeStrictStatementFromSeqArg(e, variableContext);
block.addStatement(js);
}
// For the last expression we simply generate a return expression using the
// usual top level compilation scheme.
block.addStatement (genS_R (expressions[expressions.length - 1], variableContext));
return block;
}
/**
* A helper function for buildTopLevelSeq
* Takes an Expression and generates a JavaStatement that encapsulates the
* evaluation of the expression.
* @param e
* @param variableContext
* @return a JavaStatement which encapsulates evaluation of the expression.
* @throws CodeGenerationException
*/
private JavaStatement makeStrictStatementFromSeqArg (Expression e, VariableContext variableContext) throws CodeGenerationException {
// If e is a local variable which is known to have been evaluated we don't have to do
// anything.
if (e.asVar() != null) {
VarInfo vi = variableContext.getVariableInfo(e.asVar().getName());
if (vi != null && vi.isEvaluated()) {
return new Block();
}
}
ExpressionContextPair pair;
// Since we're going to ignore the result we can save ourselves a boxing step on
// the result. First check to see if the expression is a primitive operation. If
// it is we want to call generatePrimitiveOp directly with the R scheme. This will
// correctly handle primitive operations and foreign functions, including foreign
// functions that return void.
// If the expression is not a primitive op call generateUnboxedArgument() with an
// unbox type of null (indicating there is no desired type). This will handle
// things like unboxed arguments, let variables, etc.
//we don't directly call primitive operators when doing function operator tracing.
//This will have the effect of ensuring that they get traced when called.
if (LECCMachineConfiguration.generateDirectPrimOpCalls() &&
BasicOpTuple.isBasicOp(e) != null) {
//there is an assumption here that the primitive op will return a value in weak-head normal form
//The purpose of this optimization is to avoid an unnecessary boxing and unboxing
pair = generatePrimitiveOp (e, false, Scheme.R_SCHEME, variableContext);
} else {
pair = generateUnboxedArgument(null, e, variableContext);
}
Block block = pair.getContextBlock();
JavaExpression firstArg = pair.getJavaExpression();
block.addStatement(new ExpressionStatement(forceSafeForStatement(firstArg)));
return block;
}
/**
* Force a JavaExpression into a form which is safe to make into a statement.
* @param e
* @return the transformed expression
*/
private JavaExpression forceSafeForStatement(JavaExpression e) {
// If we are generating bytecode we may need to massage this expression
// so that it is also an statement.
// For example: the java expression 1 + 2 cannot be turned into a java
// statement by simply putting a semicolon at the end of it. For these cases
// we pass the expression as an argument to the overloaded function RTValue.doNothing()
// since a method invocation can be made into a statement this way.
if (!LECCMachineConfiguration.generateBytecode()) {
if (!(e instanceof JavaExpression.Assignment) &&
!(e instanceof JavaExpression.ClassInstanceCreationExpression) &&
! (e instanceof JavaExpression.MethodInvocation)) {
e = new MethodInvocation.Static(JavaTypeNames.RTVALUE, "doNothing", e, JavaTypeNames.RTVALUE, JavaTypeName.VOID);
}
}
return e;
}
/**
* If the application chain is a fully saturated application
* express it as a specialized graph node.
* @param applChain
* @param scheme
* @param variableContext
* @return the code that creates the graph node
* @throws CodeGenerationException
*/
private ExpressionContextPair buildApplicationChain (Expression.Appl applChain,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
Expression[] chain = appChain (applChain);
if (chain[0].asVar() == null) {
if (LECCMachineConfiguration.OPTIMIZE_GENERAL_APP_CHAINS) {
return buildGeneralApplicationChain (chain, scheme, variableContext);
} else {
return null;
}
}
// If the function is a supercombinator we can potentially optimize the
// representation.
Expression.Var var = (Expression.Var)chain[0];
if (var.getDataConstructor() != null) {
return null;
}
if (LECCMachineConfiguration.OPTIMIZE_TYPECLASS_DICTS) {
if (variableContext.isLocalVariable(var.getName())) {
// The left hand side of the application chain is a local variable.
// See if it is an optimizable dictionary application.
ExpressionContextPair ecp = buildLocalVarApplicationChain (applChain, scheme, variableContext);
if (ecp != null) {
return ecp;
}
}
}
if (variableContext.isLocalVariable(var.getName())) {
// This is an application of a local variable to some arguments
// and the local variable is not one we recognize/optimize.
if (LECCMachineConfiguration.OPTIMIZE_GENERAL_APP_CHAINS) {
return buildGeneralApplicationChain (chain, scheme, variableContext);
} else {
return null;
}
}
// Try to collect information about the called function.
// This is used to decide how to encode the function call.
MachineFunction mf = module.getFunction(var.getName());
if (mf == null) {
return null;
}
// arity of the called function
int calledArity = mf.getArity();
if (calledArity >= chain.length) {
// This is an undersaturated SC application.
if (codeGenerationStats != null) {
codeGenerationStats.incrementUnderSaturation(calledArity, chain.length-1);
}
ExpressionContextPair ecp = buildUndersaturatedAppNode(chain, mf, variableContext);
if (ecp != null) {
return ecp;
} else {
return buildGeneralApplicationChain (chain, scheme, variableContext);
}
}
// argument strictness of the called function
boolean[] calledArgStrictness;
if (LECCMachineConfiguration.IGNORE_STRICTNESS_ANNOTATIONS) {
Arrays.fill (calledArgStrictness, false);
} else {
calledArgStrictness = mf.getParameterStrictness();
}
// argument types of the called function
TypeExpr[] calledArgTypes = mf.getParameterTypes();
// does the called function have strict primitive arguments
boolean calledHasStrictPrimitiveArgs = false;
for (int i = 0; i < calledArity; ++i) {
if (calledArgStrictness[i] && SCJavaDefn.canTypeBeUnboxed(calledArgTypes[i])) {
calledHasStrictPrimitiveArgs = true;
break;
}
}
// is the called function tail recursive
boolean calledIsTailRecursive = mf.isTailRecursive();
// If this is a fully saturated function call we can optimize it if:
// 1) The number of arguments is > zero and <= OPTIMIZED_APP_CHAIN_LENGTH, because the runtime has special purpose application
// nodes for these size of functions.
// 2) The called function has strict primitive arguments, since this causes the generation of a function
// specific application node class.
// 3) The called function is tail recursive, since this causes the generation of a function specific application
// node class.
if (chain.length -1 == calledArity &&
(chain.length - 1 <= LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH || calledHasStrictPrimitiveArgs || calledIsTailRecursive) ) {
if (scheme == Scheme.R_SCHEME) {
return buildTopLevelApplicationChain(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
} else
if (scheme == Scheme.E_SCHEME) {
return buildStrictApplicationChain(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
} else
if (scheme == Scheme.C_SCHEME) {
return buildLazyApplicationChain(chain, calledArity, calledArgStrictness, calledArgTypes, variableContext);
} else {
throw new CodeGenerationException ("Attempt to build application chain from unknown scheme: " + scheme);
}
}
if (chain.length - 1 > calledArity) {
return buildOversaturatedApplicationChain(applChain, chain, calledArity, scheme, variableContext);
}
if (LECCMachineConfiguration.OPTIMIZE_GENERAL_APP_CHAINS) {
return buildGeneralApplicationChain(chain, scheme, variableContext);
} else {
return null;
}
}
private ExpressionContextPair buildOversaturatedApplicationChain (Expression.Appl e,
Expression[] appChain,
int calledArity,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
Expression fsRoot = e;
int diff = appChain.length - 1 - calledArity;
for (int i = 0; i < diff; ++i) {
fsRoot = fsRoot.asAppl().getE1();
}
ExpressionContextPair fsRootECP = null;
if (scheme == Scheme.E_SCHEME || scheme == Scheme.R_SCHEME) {
fsRootECP = genS_E(fsRoot, variableContext);
} else {
fsRootECP = genS_C(fsRoot, variableContext);
}
Block b = fsRootECP.getContextBlock();
JavaExpression rootExpression = fsRootECP.getJavaExpression();
JavaExpression extraArgs[] = new JavaExpression[diff];
for (int i = calledArity + 1; i < appChain.length; ++i) {
ExpressionContextPair argECP = genS_C(appChain[i], variableContext);
b.addStatement(argECP.getContextBlock());
extraArgs[i-calledArity-1] = argECP.getJavaExpression();
}
// If we are in a strict context we want to generate something like:
// root.f3L(a, b, c).evaluate($ec);
if (scheme == Scheme.E_SCHEME && diff <= LECCMachineConfiguration.OPTIMIZED_APP_CHAIN_LENGTH) {
JavaExpression[] args = new JavaExpression [diff + 1];
System.arraycopy(extraArgs, 0, args, 0, extraArgs.length);
args[args.length-1] = SCJavaDefn.EXECUTION_CONTEXT_VAR;
JavaTypeName[] argTypes = new JavaTypeName[args.length];
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
argTypes[argTypes.length-1] = JavaTypeNames.RTEXECUTION_CONTEXT;
MethodInvocation fL = new MethodInvocation.Instance(rootExpression, "f" + (diff) + "L", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
MethodInvocation eval = SCJavaDefn.createInvocation(fL, SCJavaDefn.EVALUATE, EXECUTION_CONTEXT_VAR);
return new ExpressionContextPair(eval, b);
}
// Now we want to build the application chain. (As efficiently as possible).
int nApplicationArguments = extraArgs.length;
int argIndex = 0;
while (nApplicationArguments > 0) {
if (nApplicationArguments >= 4) {
JavaExpression[] args = new JavaExpression[4];
JavaTypeName[] argTypes = new JavaTypeName[4];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(extraArgs, argIndex, args, 0, 4);
rootExpression = new MethodInvocation.Instance(rootExpression, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 4;
nApplicationArguments -= 4;
} else
if (nApplicationArguments == 3) {
JavaExpression[] args = new JavaExpression[3];
JavaTypeName[] argTypes = new JavaTypeName[3];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(extraArgs, argIndex, args, 0, 3);
rootExpression = new MethodInvocation.Instance(rootExpression, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 3;
nApplicationArguments -= 3;
} else
if (nApplicationArguments == 2) {
JavaExpression[] args = new JavaExpression[2];
JavaTypeName[] argTypes = new JavaTypeName[2];
Arrays.fill (argTypes, JavaTypeNames.RTVALUE);
System.arraycopy(extraArgs, argIndex, args, 0, 2);
rootExpression = new MethodInvocation.Instance(rootExpression, "apply", args, argTypes, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex += 2;
nApplicationArguments -= 2;
} else
if (nApplicationArguments == 1) {
rootExpression = new MethodInvocation.Instance(rootExpression, "apply", extraArgs[argIndex], JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
argIndex++;
nApplicationArguments--;
}
}
if (scheme == Scheme.E_SCHEME) {
// Strict application scheme. Call 'evalaute' on the RTValue.
rootExpression = createInvocation(rootExpression, EVALUATE, EXECUTION_CONTEXT_VAR);
}
return new ExpressionContextPair(rootExpression, b);
}
/**
* If the application chain is a fully saturated application of a dictionary function
* express it as a specialized graph node.
* @param applChain
* @param scheme 0 - R-scheme, 1 - E-scheme, 2 C-Scheme.
* @param variableContext
* @return the code that creates the graph node
* @throws CodeGenerationException
*/
private ExpressionContextPair buildLocalVarApplicationChain (Expression.Appl applChain,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
int nExpectedSecondaryArgs =
ExpressionAnalyzer.getNOversaturatedDictionaryArgs(applChain, getModule().getModuleTypeInfo());
if (nExpectedSecondaryArgs < 0) {
return null;
}
//System.out.println (dai.typeClass.getName() + " -> " + dai.classMethod.getName() + " -> " + dai.nExpectedSecondaryArgs);
Expression[] chain = appChain (applChain);
// Is this a dictionary function that is itself the function to be used.
if (chain.length - 1 == nExpectedSecondaryArgs) {
return null;
} else {
final int nArgs = chain.length - 1;
if (nArgs == 2 || nArgs == 3) {
if (codeGenerationStats != null) {
// Parse out the type class name.
// Variable name will be in the form $dictvarModule.Class#N where N is an integer
// number and where dictvarModule may contain zero or more dots.
String name = chain[0].asVar().getName().getUnqualifiedName();
name = name.substring(8);
QualifiedName qualifiedClassName = QualifiedName.makeFromCompoundName(name.substring(0, name.indexOf("#")));
codeGenerationStats.incrementOptimizedDictionaryApplication(qualifiedClassName);
}
// First generate the java expressions for the CAL expressions.
ExpressionContextPair[] ecp = new ExpressionContextPair[chain.length];
for (int i = 0; i < chain.length; ++i) {
ecp[i] = genS_C(chain[i], variableContext);
}
Block newContext = ecp[0].getContextBlock();
for (int i = 1; i < chain.length; ++i) {
newContext.addStatement(ecp[i].getContextBlock());
}
JavaExpression root = ecp[0].getJavaExpression();
JavaExpression args[] = new JavaExpression[chain.length];
for (int i = 0; i < chain.length; ++i) {
args[i] = ecp[i].getJavaExpression();
}
JavaTypeName type;
switch (nArgs) {
case 2:
type = JavaTypeNames.RTOAPP2;
break;
case 3:
type = JavaTypeNames.RTOAPP3;
break;
default:
throw new IllegalStateException();
}
JavaTypeName constructorArgTypes[] = new JavaTypeName[chain.length];
Arrays.fill(constructorArgTypes, JavaTypeNames.RTVALUE);
root = new JavaExpression.ClassInstanceCreationExpression(type, args, constructorArgTypes);
if (scheme == Scheme.E_SCHEME) {
// This is being built in a strict scheme so call evaluate on the RTValue.
root = createInvocation (root, EVALUATE, EXECUTION_CONTEXT_VAR);
}
return new ExpressionContextPair (root, newContext);
}
}
return null;
}
/**
* Generate the java expression corresponding to a variable reference.
* @param var
* @param scheme
* @param variableContext
* @return JavaExpression
* @throws CodeGenerationException
*/
private JavaExpression expressionVarToJavaDef (Expression.Var var, Scheme scheme, VariableContext variableContext) throws CodeGenerationException {
return generateVar (var.getName(), var.getDataConstructor(), var.getForeignFunctionInfo(), scheme, variableContext);
}
/**
* Generate the java expression corresponding to a variable.
* @param qualName
* @param dc - may be null
* @param foreignFunctionInfo - may be null
* @param scheme
* @param variableContext
* @return the JavaExpression
* @throws CodeGenerationException
*/
private JavaExpression generateVar (QualifiedName qualName,
DataConstructor dc,
ForeignFunctionInfo foreignFunctionInfo,
Scheme scheme,
VariableContext variableContext) throws CodeGenerationException {
if (variableContext.isLocalVariable(qualName)) {
if (scheme == Scheme.E_SCHEME) {
JavaExpression strictRef = variableContext.getStrictReference(qualName);
if (strictRef != null) {
return strictRef;
}
} else {
JavaExpression lazyRef = variableContext.getLazyReference(qualName);
if (lazyRef != null) {
return lazyRef;
}
}
throw new CodeGenerationException ("Unable to obtain reference to local variable " + qualName);
} else {
// This is not a local symbol (i.e. it is a supercombinator, foreign function, or constructor)
// Kernel may claim the symbol if its defined in the Prelude module
// First determine what kind of symbol this is
if (dc != null) {
// Constructor
// Emit symbol with factory invocation
JavaTypeName typeName = CALToJavaNames.createTypeNameFromDC(dc, module);
JavaExpression dcInstance;
dcInstance = getReferencedDC(typeName, dc);
return dcInstance;
} else {
MachineFunction mf = module.getFunction(qualName);
if (mf == null) {
throw new CodeGenerationException("Unable to retrieve MachineFunction for " + qualName.getQualifiedName());
}
LECCModule.FunctionGroupInfo fgi = module.getFunctionGroupInfo(qualName);
if (fgi == null) {
throw new CodeGenerationException("Unable to retrieve FunctionGroupInfo for " + qualName.getQualifiedName());
}
// Function
JavaTypeName fullClass = CALToJavaNames.createTypeNameFromSC(qualName, module);
if (mf.getArity() == 0) {
// This is a CAF or a zero arity function.
// Either way we need to invoke the static factory method
// 'make'.
if (fgi.getNCAFs() + fgi.getNZeroArityFunctions() <= 1) {
return new MethodInvocation.Static (
fullClass,
"make",
new JavaExpression[]{SCJavaDefn.EXECUTION_CONTEXT_VAR},
new JavaTypeName[]{JavaTypeNames.RTEXECUTION_CONTEXT},
JavaTypeNames.RTFUNCTION);
} else {
int functionIndex = fgi.getFunctionIndex(qualName.getUnqualifiedName());
return new MethodInvocation.Static (
fullClass,
"make",
new JavaExpression[]{LiteralWrapper.make(Integer.valueOf(functionIndex)), SCJavaDefn.EXECUTION_CONTEXT_VAR},
new JavaTypeName[]{JavaTypeName.INT, JavaTypeNames.RTEXECUTION_CONTEXT},
JavaTypeNames.RTFUNCTION);
}
}
// If this is a self reference or a reference to a function
// in the same function group use the '$instance....' member.
if (fullClass.equals (thisTypeName)) {
return new JavaField.Static(fullClass, CALToJavaNames.getInstanceFieldName(qualName, module), thisTypeName);
}
// Use the static 'instance' field in the class for the referenced function.
return new JavaField.Static(fullClass, CALToJavaNames.getInstanceFieldName(qualName, module), fullClass);
}
}
}
/**
* Return true if an exception handling block for the given
* exception already exists.
* @param exceptionClass
* @return boolean
*/
private boolean exceptionHandled(Class<?> exceptionClass) {
for (final JavaExceptionHandler eh : exceptionInfo) {
if (eh.getExceptionClass() == exceptionClass) {
return true;
}
}
return false;
}
/**
* Create a JavaExceptionHandler for the specified exception class.
* @param exceptionClass
*/
private void addExceptionHandler(Class<?> exceptionClass) {
// Generate a Block corresponding to:
// throw new RTForeignFunctionException(RTValue.generateForeignFunctionErrorMessage(exception, generatedClassName, supercombinatorName), null, exception);
JavaTypeName exceptionType = JavaTypeName.make(exceptionClass);
JavaExpression args[] = new JavaExpression [4];
JavaTypeName argTypes[] = new JavaTypeName [4];
args[0] = new LocalVariable("caught_exception" , exceptionType);
argTypes[0] = JavaTypeName.THROWABLE;
args[1] = LiteralWrapper.make(CALToJavaNames.createFullClassNameFromSC(getQualifiedName(), module));
argTypes[1] = JavaTypeName.STRING;
args[2] = LiteralWrapper.make(currentModuleName.toSourceText());
argTypes[2] = JavaTypeName.STRING;
args[3] = LiteralWrapper.make(getFunctionName());
argTypes[3] = JavaTypeName.STRING;
MethodInvocation genMessage = new MethodInvocation.Static (JavaTypeNames.RTVALUE,
"generateForeignFunctionErrorMessage",
args,
argTypes,
argTypes[1]);
JavaExpression constructorArgs[] = new JavaExpression [2];
JavaTypeName constructorArgTypes[] = new JavaTypeName [2];
constructorArgs[0] = genMessage;
constructorArgTypes[0] = JavaTypeName.STRING;
constructorArgs[1] = new LocalVariable("caught_exception", exceptionType);
constructorArgTypes[1] = JavaTypeName.THROWABLE;
ClassInstanceCreationExpression nc =
new ClassInstanceCreationExpression (JavaTypeNames.RTFOREIGN_FUNCTION_EXCEPTION, constructorArgs, constructorArgTypes);
ThrowStatement throwSt = new ThrowStatement (nc);
JavaExceptionHandler eh = new JavaExceptionHandler(exceptionClass, "caught_exception", throwSt);
exceptionInfo.add(eh);
}
void setInstanceName (String instanceName) {
this.instanceName = instanceName;
}
/**
* @return Returns the module.
*/
LECCModule getModule() {
return module;
}
ModuleName getModuleName() {
return module.getName();
}
String getFunctionName () {
return functionName;
}
QualifiedName getQualifiedName () {
return qualifiedName;
}
/**
* Create a zero-argument method invocation from a MethodInfo.
* @param target the target of the invocation.
* @param mi
* @return MethodInvocation the resulting method invocation
*/
static MethodInvocation createInvocation(JavaExpression target, MethodInfo mi) {
return new MethodInvocation.Instance(target, mi.getMethodName(), mi.getReturnType(), mi.getInvocationType());
}
/**
* Create a single -argument method invocation from a MethodInfo.
* @param target the target of the invocation.
* @param mi
* @param arg the argument to the invocation.
* @return MethodInvocation the resulting method invocation
*/
static MethodInvocation createInvocation(JavaExpression target, MethodInfo mi, JavaExpression arg) {
return new MethodInvocation.Instance(target, mi.getMethodName(), arg, mi.getParamTypes()[0], mi.getReturnType(), mi.getInvocationType());
}
/**
* Helper method to create a zero-argument virtual method invocation.
* @param target the target of the invocation.
* @param methodName the name of the method
* @param returnType the return type of the method
* @return MethodInvocation the resulting method invocation
*/
static MethodInvocation createVirtualInvocation(JavaExpression target, String methodName, JavaTypeName returnType) {
return new MethodInvocation.Instance(target, methodName, returnType, InvocationType.VIRTUAL);
}
/**
* Create a method invocation for one of the "apply" calls (taking an array of 1 - 4 args) in RTFunction.
* RTFunction: public RTValue apply(RTValue argument, RTValue arg2, ...);
* @param target the target of the invocation.
* @param args the args to the apply call.
* @return MethodInvocation the resulting method invocation
*/
static MethodInvocation createApplyInvocation(JavaExpression target, JavaExpression[] args) {
JavaTypeName[] argTypes = new JavaTypeName[args.length];
Arrays.fill(argTypes, JavaTypeNames.RTVALUE);
// arg types and return type of apply is RTValue
return new MethodInvocation.Instance(target, "apply", args, argTypes, JavaTypeNames.RTVALUE, InvocationType.VIRTUAL);
}
/**
* Create a method invocation for the "make" call in an SC implementation.
* eg. CAL_Short: public static final CAL_Short make(short value);
* @param calPrimitiveClass eg. RTKernel.CAL_Byte.class
* @param arg the argument to the invocation
* @return MethodInvocation the resulting method invocation
*/
static MethodInvocation createPrimitiveMakeInvocation(Class<?> calPrimitiveClass, JavaExpression arg) {
if (calPrimitiveClass == RTData.CAL_Boolean.class) {
return createMakeKernelBooleanInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Byte.class) {
return createMakeKernelByteInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Char.class) {
return createMakeKernelCharInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Double.class) {
return createMakeKernelDoubleInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Float.class) {
return createMakeKernelFloatInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Int.class) {
return createMakeKernelIntInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Integer.class) {
return createMakeKernelIntegerInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Long.class) {
return createMakeKernelLongInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Opaque.class) {
return createMakeKernelOpaqueInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_Short.class) {
return createMakeKernelShortInvocation(arg);
}
if (calPrimitiveClass == RTData.CAL_String.class) {
return createMakeKernelStringInvocation(arg);
}
throw new IllegalArgumentException("unexpected primitive type " + calPrimitiveClass + ".");
}
/**
* Creates an invocation of the static constructor function: RTData.CAL_Boolean.make(boolean b) in the Java expression model.
* It is efficient in that static constants are used for the various JavaTypeName objects. The earlier lack of use of static
* constants proved to be a significant compile-time performance penalty after benchmarking.
* @param arg
* @return MethodInvocation
*/
static MethodInvocation createMakeKernelBooleanInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_BOOLEAN, "make", arg, JavaTypeName.BOOLEAN, JavaTypeNames.RTDATA_BOOLEAN);
}
static MethodInvocation createMakeKernelByteInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_BYTE, "make", arg, JavaTypeName.BYTE, JavaTypeNames.RTDATA_BYTE);
}
static MethodInvocation createMakeKernelCharInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_CHAR, "make", arg, JavaTypeName.CHAR, JavaTypeNames.RTDATA_CHAR);
}
static MethodInvocation createMakeKernelDoubleInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_DOUBLE, "make", arg, JavaTypeName.DOUBLE, JavaTypeNames.RTDATA_DOUBLE);
}
static MethodInvocation createMakeKernelFloatInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_FLOAT, "make", arg, JavaTypeName.FLOAT, JavaTypeNames.RTDATA_FLOAT);
}
static MethodInvocation createMakeKernelIntInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_INT, "make", arg, JavaTypeName.INT, JavaTypeNames.RTDATA_INT);
}
static MethodInvocation createMakeKernelIntegerInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_INTEGER, "make", arg, JavaTypeName.BIG_INTEGER, JavaTypeNames.RTDATA_INTEGER);
}
static MethodInvocation createMakeKernelLongInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_LONG, "make", arg, JavaTypeName.LONG, JavaTypeNames.RTDATA_LONG);
}
static MethodInvocation createMakeKernelOpaqueInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_OPAQUE, "make", arg, JavaTypeName.OBJECT, JavaTypeNames.RTDATA_OPAQUE);
}
static MethodInvocation createMakeKernelShortInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_SHORT, "make", arg, JavaTypeName.SHORT, JavaTypeNames.RTDATA_SHORT);
}
static MethodInvocation createMakeKernelStringInvocation(JavaExpression arg) {
return new MethodInvocation.Static(JavaTypeNames.RTDATA_STRING, "make", arg, JavaTypeName.STRING, JavaTypeNames.RTDATA_STRING);
}
/**
* Generate a java name for the given field in the data constructor.
* @param dc
* @param index
* @return the java name.
*/
static String getJavaFieldNameFromDC (DataConstructor dc, int index) {
FieldName fn = dc.getNthFieldName(index);
String name = getJavaFieldNameFromFieldName(fn);
if (name == null) {
name = "field" + index;
}
return name;
}
/**
* Generate a java name for the given FieldName.
* @param fn
* @return the java name.
*/
static String getJavaFieldNameFromFieldName (FieldName fn) {
if (fn == null) {
return null;
}
String fieldName = CALToJavaNames.fixupVarName(fn.toString());
if (!fieldName.startsWith("_")) {
fieldName = "_" + fieldName;
}
return fieldName;
}
/**
* Use this function to get the field types for a data constructor.
* Appropriate conversions are done based on whether or not unboxed DC fields are being used
* and whether enums are treated as ints.
* @param dc
* @return an array of TypeExpr
*/
static TypeExpr[] getFieldTypesForDC (DataConstructor dc) {
TypeExpr[] fieldTypes = new TypeExpr[dc.getArity()];
TypeExpr dcType = dc.getTypeExpr();
TypeExpr[] tft = dcType.getTypePieces(dc.getArity());
System.arraycopy(tft, 0, fieldTypes, 0, fieldTypes.length);
return fieldTypes;
}
/**
* Get the TypExpr for the type of the given FieldName in the given DataConstructor
* @param dc
* @param fieldName
* @return the type of the field
*/
static private TypeExpr getFieldTypeForDC (DataConstructor dc, FieldName fieldName) {
TypeExpr[] fieldTypes = new TypeExpr[dc.getArity()];
TypeExpr dcType = dc.getTypeExpr();
TypeExpr[] tft = dcType.getTypePieces(dc.getArity());
System.arraycopy(tft, 0, fieldTypes, 0, fieldTypes.length);
return fieldTypes[dc.getFieldIndex(fieldName)];
}
/**
* This class maps from a CAL name to the current Java equivalent.
* @author RCypher
*
*/
private final class VariableContext {
/**
* A stack of Map of QualifiedName -> VarInfo. The list on the top of the stack represents the
* a java scope for the generation of let variables.
*/
private final ArrayStack<Map<QualifiedName, VarInfo>> javaScopeStack = ArrayStack.make();
/**
* CAL allows duplicate variable names in nested scopes. Since java doesn't
* we need to make sure that we never assign a duplicate java name.
*/
private final Set<String> allJavaNames = new HashSet<String>();
VariableContext () {
javaScopeStack.push (new LinkedHashMap<QualifiedName, VarInfo>());
}
VarInfo getVariableInfo (QualifiedName qn) {
for (int i = javaScopeStack.size()-1, j = 0; i >= j; i--) {
Map<QualifiedName, VarInfo> scope = javaScopeStack.get(i);
VarInfo vi = scope.get(qn);
if (vi != null) {
return vi;
}
}
return null;
}
JavaTypeName getUnboxedType (QualifiedName qn) {
VarInfo info = getVariableInfo(qn);
if (info != null) {
return info.getUnboxedType();
}
return null;
}
boolean isLocalVariable (QualifiedName qn) {
return getVariableInfo(qn) != null;
}
boolean isFunctionArgument (QualifiedName qn) {
VarInfo info = getVariableInfo(qn);
if (info != null) {
return info instanceof VarInfo.Argument;
}
return false;
}
boolean isDCField (QualifiedName qn) {
VarInfo info = getVariableInfo(qn);
if (info != null) {
return info instanceof VarInfo.DCMember;
}
return false;
}
boolean isRecordField (QualifiedName qn) {
VarInfo info = getVariableInfo(qn);
if (info != null) {
return info instanceof VarInfo.RecordField;
}
return false;
}
boolean isVarPreEvaluated (QualifiedName qn) {
VarInfo info = getVariableInfo(qn);
if (info != null) {
return info.isEvaluated();
}
return false;
}
List<VarInfo> getAllVariableInfo () {
List<VarInfo> list = new ArrayList<VarInfo>();
for (int i = javaScopeStack.size()-1, j = 0; i >= j; i--) {
Map<QualifiedName, VarInfo> scope = javaScopeStack.get(i);
list.addAll(scope.values());
}
return list;
}
/**
* Push a java scope onto the stack.
*/
public void pushJavaScope() {
javaScopeStack.push (new LinkedHashMap<QualifiedName, VarInfo>());
}
/**
* Add a mapping for a let variable to the current scope.
* @param name
* @param varTypeExpr
* @return varInfo
*/
public VarInfo.LetNonRec addLetNonRecVar (QualifiedName name, TypeExpr varTypeExpr) throws CodeGenerationException {
String javaName = "letVar_" + makeJavaName(name.getUnqualifiedName());
javaName = makeNameUnique (javaName);
VarInfo.LetNonRec info = new VarInfo.LetNonRec (name, javaName, varTypeExpr);
addVar (name, info);
if (SCJavaDefn.canTypeBeUnboxed(varTypeExpr)) {
info.setUnboxedType(SCJavaDefn.typeExprToTypeName(varTypeExpr));
}
return info;
}
/**
* Add a mapping for a letrec variable to the current scope.
* @param name
* @param varType
* @return varInfo
*/
public VarInfo.LetRec addLetRecVar (QualifiedName name, TypeExpr varType) {
String javaName = "letVar_" + makeJavaName(name.getUnqualifiedName());
javaName = makeNameUnique (javaName);
VarInfo.LetRec info = new VarInfo.LetRec (name, javaName, varType);
addVar (name, info);
return info;
}
/**
*
* @return Map of (QualifiedName -> VarInfo)
*/
Map<QualifiedName, VarInfo> getCurrentScope () {
return javaScopeStack.peek();
}
/**
* Generate the java source for any variables in the current java scope.
* @return the resulting code contribution
* @throws CodeGenerationException
*/
public Block popJavaScope () throws CodeGenerationException {
Map<QualifiedName, VarInfo> varMap = javaScopeStack.pop();
Block declarationBlock = new Block();
Block definitionBlock = new Block();
for (final Map.Entry<QualifiedName, VarInfo> entry : varMap.entrySet()) {
VarInfo info = entry.getValue();
String javaName = info.getJavaName();
if (info instanceof VarInfo.LetRec) {
JavaExpression boxedVarDef = ((VarInfo.LetRec)info).getLazyVarDef();
if (boxedVarDef == null) {
throw new CodeGenerationException ("Missing boxed definition for let variable: " + info.getCALName());
}
LocalVariable letNameVariable = new LocalVariable(javaName, JavaTypeNames.RTINDIRECTION);
// RTValue javaName = new RTIndirection();
JavaExpression newIndirection = new ClassInstanceCreationExpression(JavaTypeNames.RTINDIRECTION);
LocalVariableDeclaration letDecl = new LocalVariableDeclaration(letNameVariable, newIndirection);
declarationBlock.addStatement(letDecl);
// Emit let variable definition.
// letName.setResult(letDef);
MethodInvocation letInvocation = createInvocation(letNameVariable, SETRESULT, boxedVarDef);
definitionBlock.addStatement(new ExpressionStatement(letInvocation));
} else
if (info instanceof VarInfo.LetNonRec) {
// This variable is referenced multiple times.
// We need to declare/initialize a local java variable.
// If the variable is already evaluated we behave differently.
if (info.isEvaluated()) {
if (info.getUnboxedType() != null) {
// The definition function will return an unboxed value.
// Declare an unboxed local variable.
LocalVariable unboxedLocal = new LocalVariable(info.getJavaName()+"$U", info.getUnboxedType());
LocalVariableDeclaration unboxedVarDecl = new LocalVariableDeclaration(unboxedLocal, ((VarInfo.LetNonRec)info).getUnboxedVarDef());
declarationBlock.addStatement(unboxedVarDecl);
} else {
// All references will be boxed.
// Declare a boxed local variable an update the strict/lazy refs.
// We use the strict variable def since we know this variable is already evaluated.
LocalVariable boxedLocal = new LocalVariable(info.getJavaName(), JavaTypeNames.RTVALUE);
LocalVariableDeclaration boxedVarDecl = new LocalVariableDeclaration(boxedLocal, ((VarInfo.LetNonRec)info).getStrictVarDef());
declarationBlock.addStatement(boxedVarDecl);
}
} else {
// The variable is not pre-evaluated.
// The variable is referenced more than once.
// We want to use the lazy initializer.
LocalVariable boxedLocal = new LocalVariable(info.getJavaName(), JavaTypeNames.RTVALUE);
LocalVariableDeclaration boxedVarDecl = new LocalVariableDeclaration(boxedLocal, ((VarInfo.LetNonRec)info).getLazyVarDef());
declarationBlock.addStatement(boxedVarDecl);
}
} else
if (info instanceof VarInfo.DCMember){
// The DCMember instance of VarInfo will only have one
// varDef defined. i.e. unboxed or strict or lazy.
JavaExpression unboxedVarDef = ((VarInfo.DCMember)info).getUnboxedVarDef();
JavaExpression lazyVarDef = ((VarInfo.DCMember)info).getLazyVarDef();
JavaExpression strictVarDef = ((VarInfo.DCMember)info).getStrictVarDef();
if (unboxedVarDef != null) {
// Base declarations on unboxed definition.
// Declare a local unboxed variable and update the unboxed reference.
LocalVariable unboxedLocal = new LocalVariable(javaName+"$U", info.getUnboxedType());
LocalVariableDeclaration letDecl = new LocalVariableDeclaration(unboxedLocal, unboxedVarDef);
declarationBlock.addStatement(letDecl);
} else
if (strictVarDef != null) {
// If there is a strict variable definition we should never have unboxed use.
// Since a strict variable of an unboxable type would have an unboxed variable definition.
// Declare a boxed local and update the strict and lazy references.
LocalVariable boxedLocal = new LocalVariable (javaName, JavaTypeNames.RTVALUE);
LocalVariableDeclaration boxedDecl = new LocalVariableDeclaration (boxedLocal, strictVarDef);
declarationBlock.addStatement(boxedDecl);
info.updateStrictReference(boxedLocal);
info.updateLazyReference(boxedLocal);
} else {
// Lazy variable.
// Declare a lazy local variable.
LocalVariable lazyLocal = new LocalVariable(info.getJavaName(), JavaTypeNames.RTVALUE);
LocalVariableDeclaration lazyDecl = new LocalVariableDeclaration(lazyLocal, lazyVarDef);
declarationBlock.addStatement(lazyDecl);
}
}
}
declarationBlock.addStatement(definitionBlock);
return declarationBlock;
}
/**
* Add a mapping for a SC argument to the current scope.
* @param name
* @param isEvaluated
* @param argType
* @param argTypeExpr
* @return VarInfo
*/
public VarInfo addArgument (QualifiedName name, boolean isEvaluated, JavaTypeName argType, TypeExpr argTypeExpr) {
addVar (name, new VarInfo.Argument (name, makeJavaName(name.getUnqualifiedName()), argTypeExpr));
VarInfo varInfo = getVariableInfo(name);
// Mark evaluation state.
varInfo.setEvaluated(isEvaluated);
JavaExpression straightReference;
if (isEvaluated && argType != null && !argType.equals(JavaTypeNames.RTVALUE) && !argType.equals(JavaTypeName.CAL_VALUE)) {
straightReference= new LocalName(varInfo.getJavaName(), argType);
} else {
straightReference= new LocalName(varInfo.getJavaName(), JavaTypeNames.RTVALUE);
}
// Set up boxed/unboxed references
if (isEvaluated) {
if (argType != null && !argType.equals(JavaTypeNames.RTVALUE) && !argType.equals(JavaTypeName.CAL_VALUE)) {
varInfo.setUnboxedType(argType);
// The unboxed reference is simply the argument.
varInfo.updateUnboxedReference(straightReference);
// The boxed reference wraps the arg in one of the RTData classes.
JavaExpression boxedReference = boxExpression(argType, straightReference);
// The boxed reference can be used for both lazy and strict, since we know that it is
// already evaluated.
varInfo.updateLazyReference(boxedReference);
varInfo.updateStrictReference(boxedReference);
} else {
// Since we know that the argument is strict we can call getValue on the lazy reference
// for the strict reference.
varInfo.updateLazyReference(straightReference);
MethodInvocation getValue = new MethodInvocation.Instance(straightReference, "getValue", JavaTypeNames.RTVALUE, MethodInvocation.InvocationType.VIRTUAL);
varInfo.updateStrictReference(getValue);
}
} else {
// Without an arg type the lazy reference is just the arg and there is no unboxed ref or strict ref.
varInfo.updateLazyReference(straightReference);
varInfo.updateStrictReference(SCJavaDefn.createInvocation(straightReference, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR));
}
return varInfo;
}
/**
* Add a mapping for an extracted data constructor field to the current scope.
* @param name
* @param fieldTypeExpr
* @return VarInfo
*/
public VarInfo.DCMember addDCField (QualifiedName name, TypeExpr fieldTypeExpr) throws CodeGenerationException {
VarInfo.DCMember newInfo = new VarInfo.DCMember (name, makeJavaName(name.getUnqualifiedName()), fieldTypeExpr);
addVar (name, newInfo);
if (SCJavaDefn.canTypeBeUnboxed(fieldTypeExpr)) {
newInfo.setUnboxedType(SCJavaDefn.typeExprToTypeName(fieldTypeExpr));
}
return newInfo;
}
/**
* Add a mapping for an extracted record field to the current scope.
* @param name
* @param varType
* @return VarInfo
*/
public VarInfo.RecordField addRecordField (QualifiedName name, TypeExpr varType) {
VarInfo.RecordField newInfo = new VarInfo.RecordField (name, makeJavaName(name.getUnqualifiedName()), varType);
addVar (name, newInfo);
return newInfo;
}
private void addVar (QualifiedName name, VarInfo info) {
if (name == null || info == null) {
throw new NullPointerException("Invalid argument in VariableContext.addVar()");
}
Map<QualifiedName, VarInfo> scope = javaScopeStack.peek();
scope.put(name, info);
}
/**
* Make a Java name for a variable based on the CAL name
* (i.e. the variable name supplied by the compiler).
* @param calName
* @return a valid Java name that is unique in this context.
*/
private String makeJavaName (String calName) {
// First try to simplify the CAL name.
String javaName = simplifyCALName(calName);
// Now we need to clean the java name to make sure it is
// valid java. i.e. doesn't conflict with reserved words, etc.
javaName = CALToJavaNames.fixupVarName(javaName);
// We want to make sure that the java name is unique
// within this context.
return makeNameUnique(javaName);
}
/**
* This function tries to take a CAL variable name as
* supplied by the compiler and simplify it so that it
* is closer to the original variable name that appeared
* in the CAL code.
* @param calName
* @return a simpler version of the CAL name or the unchanged name.
*/
private String simplifyCALName (String calName) {
// First we want to try to simplify the calName.
// What starts out as a fairly simple name in CAL
// code can become quite complex by the time that
// the compiler is through with it. For example
// the argument x in the function NonCAFEntryPoint
// will be renamed to nonCAFEntryPoint$x$1.
String javaName = calName;
// Look for the last and second-to-last incidents of $
// and use what is between them.
int lastIndex = calName.lastIndexOf('$');
if (lastIndex >= 1) {
int secondLastIndex = calName.lastIndexOf('$', lastIndex - 1);
if (secondLastIndex > -1 && lastIndex - secondLastIndex > 1) {
javaName = calName.substring(secondLastIndex + 1, lastIndex);
}
}
return javaName;
}
/**
* Make sure that a proposed java name hasn't already been used for a
* java entity at this or a higher scope.
* @param baseName
* @return a unique java name.
*/
private String makeNameUnique (String baseName) {
String name = baseName;
int modifier = 1;
while (allJavaNames.contains(name)) {
name = baseName + "_" + modifier++;
}
allJavaNames.add (name);
return name;
}
JavaExpression getStrictReference (QualifiedName name) {
VarInfo vi = getVariableInfo(name);
if (vi == null) {
return null;
}
return vi.strictReference;
}
JavaExpression getLazyReference (QualifiedName name) {
VarInfo vi = getVariableInfo(name);
if (vi == null) {
return null;
}
return vi.lazyReference;
}
JavaExpression getUnboxedReference (QualifiedName name) {
VarInfo vi = getVariableInfo(name);
if (vi == null) {
return null;
}
return vi.unboxedReference;
}
}
/**
* @return Returns the isTailRecursive flag.
*/
boolean isTailRecursive() {
return isTailRecursive;
}
/**
* @return true if this function is primitive.
*/
public boolean isPrimitive() {
return isPrimitive;
}
/**
* @return true if this function is a constant applicative form.
*/
boolean isCAF () {
return getArity() == 0 && !isForeign();
}
/**
* @return true if this function is foreign.
*/
public boolean isForeign() {
return isForeign;
}
/** Return the set of names of the strongly connected component set which contains this
* entity.
* @return Set
*/
Set<String> getStronglyConnectedComponents() {
return connectedComponents;
}
/**
* Represents a value literal (kernel class and constructor expression)
* @author Raymond Cypher
*/
static final class KernelLiteral {
/** The Class of the CAL literal. */
private final Class<?> kernelTypeClass;
/** An expression to construct the literal. */
private final JavaExpression constructorExpression;
/** A suggested String identifier. */
private final String suggestedIdent;
/** The literal object this KernelLiteral represents. */
private final Object literal;
/** A string for the symbol used to refer to this literal. */
private final String symbol;
private int boxedUseCount = 0;
private int unboxedUseCount = 0;
private JavaExpression boxedReference;
private JavaExpression unboxedReference;
/**
* Construct a KernelLiteral that represents the given object.
* @param literal
* @param sharedValues
* @param containingClass
* @throws CodeGenerationException
*/
private KernelLiteral (Object literal, SharedValues sharedValues, JavaTypeName containingClass) throws CodeGenerationException {
this.literal = literal;
// RTData value types are special in that they represent the only values (primitives) that can be
// 'met' as values directly in CAL code (i.e. as literals). When the backend is asked to represent
// a literal the value has to be 'converted' to a declaration of a value in terms of RTData
// value types
// This routine performs this translation for 'known' types
// This could be done by having each primitive type class register its Java primitive
// object wrapper class with an RTData object, which could then map to the class name.
// For now, we'll just use the static map...
String proposedIdent;
Class<?> calObjectClass = literalMap.get(literal.getClass());
if (calObjectClass != null) {
kernelTypeClass = calObjectClass;
constructorExpression = LiteralWrapper.make(literal);
proposedIdent = literal.toString();
} else
if (literal instanceof String) {
kernelTypeClass = CAL_String.class;
constructorExpression = LiteralWrapper.make(literal);
proposedIdent = StringEncoder.encodeString(literal.toString());
} else
if (literal instanceof Character) {
kernelTypeClass = CAL_Char.class;
constructorExpression = LiteralWrapper.make(literal);
proposedIdent = StringEncoder.encodeChar(((Character)literal).charValue());
} else
if (literal instanceof BigInteger) {
kernelTypeClass = CAL_Integer.class;
constructorExpression = new ClassInstanceCreationExpression(JavaTypeName.BIG_INTEGER,
LiteralWrapper.make(literal.toString()),
JavaTypeName.STRING);
proposedIdent = literal.toString();
} else {
throw new CodeGenerationException ("Unknown literal type: " + literal.toString());
}
suggestedIdent = General.validJavaIdentifier(proposedIdent, false, 32);
String kernelTypeString = CALToJavaNames.fixupClassName(kernelTypeClass.getName());
int endPrefix = kernelTypeString.lastIndexOf('_');
String typeWord;
if (endPrefix != -1) {
typeWord = kernelTypeString.substring(endPrefix + 1);
} else {
typeWord = kernelTypeString;
}
symbol = LITERAL_PREFIX + (sharedValues.getNLiteralValues() + 1) + "_" + typeWord + "_" + getSuggestedIdent();
unboxedReference = constructorExpression;
boxedReference = new JavaField.Static(containingClass, getSymbol(), JavaTypeName.make(getKernelTypeClass()));
if (literal instanceof BigInteger) {
boxedReference = new JavaField.Static(containingClass, getSymbol(), JavaTypeName.make(getKernelTypeClass()));
unboxedReference = new JavaField.Static(containingClass, getSymbol()+"$U", JavaTypeName.make(literal.getClass()));
}
}
/**
* Get the RTData class to handle this literal.
* @return Class
*/
private Class<?> getKernelTypeClass() {
return kernelTypeClass;
}
/**
* Get the suggested String identifier for this literal.
* @return String
*/
private String getSuggestedIdent() {
return suggestedIdent;
}
/**
* Return true if the given literal is equivalent to this literal.
* @param otherLiteral
* @return boolean
*/
public boolean match (Object otherLiteral) {
return literal.equals(otherLiteral);
}
final Object getLiteralObject () {
return literal;
}
/**
* Return the symbol used to refer to this literal.
* @return String
*/
String getSymbol () {
return symbol;
}
/**
* @return Returns the boxedReference.
*/
JavaExpression getBoxedReference() {
boxedUseCount++;
return boxedReference;
}
/**
* @return Returns the unboxedReference.
*/
JavaExpression getUnboxedReference() {
unboxedUseCount++;
return unboxedReference;
}
public void addFieldDeclarations (JavaClassRep classRep) {
String literalName = getSymbol();
if (literal instanceof BigInteger) {
if (unboxedUseCount > 0) {
JavaFieldDeclaration unboxedDeclaration = new JavaFieldDeclaration(Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL, JavaTypeName.make(literal.getClass()), literalName + "$U", constructorExpression);
classRep.addFieldDeclaration(unboxedDeclaration);
if (boxedUseCount > 0) {
JavaExpression initializer = SCJavaDefn.createPrimitiveMakeInvocation(getKernelTypeClass(), new JavaField.Static(classRep.getClassName(), literalName + "$U", JavaTypeName.make(literal.getClass())));
JavaFieldDeclaration boxedDeclaration = new JavaFieldDeclaration(Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL, JavaTypeName.make(getKernelTypeClass()), literalName, initializer);
classRep.addFieldDeclaration(boxedDeclaration);
}
} else
if (boxedUseCount > 0) {
JavaExpression initializer = SCJavaDefn.createPrimitiveMakeInvocation(getKernelTypeClass(), constructorExpression);
JavaFieldDeclaration boxedDeclaration = new JavaFieldDeclaration(Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL, JavaTypeName.make(getKernelTypeClass()), literalName, initializer);
classRep.addFieldDeclaration(boxedDeclaration);
}
} else if (boxedUseCount > 0) {
// kernel type class: something like: "org.openquark.cal.internal.runtime.lecc.RTData$CAL_Integer";
JavaExpression initializer = SCJavaDefn.createPrimitiveMakeInvocation(getKernelTypeClass(), constructorExpression);
JavaFieldDeclaration instanceDeclaration = new JavaFieldDeclaration(Modifier.STATIC | Modifier.PRIVATE | Modifier.FINAL, JavaTypeName.make(getKernelTypeClass()), literalName, initializer);
classRep.addFieldDeclaration(instanceDeclaration);
}
}
}
/**
* This class is used to represent a Java expression plus the context statements (eg. variable declarations) needed to define it.
* For instance, a let defines a java expression after a series of java statements assigning values to the let variables.
* @author Edward Lam
*/
private static final class ExpressionContextPair {
/** The returned expression. */
private final JavaExpression javaExpression;
/** The context associated with the expression. */
private final Block context = new Block();
/**
* Constructor for an ExpressionContextPair with no associated context.
* @param javaExpression the expression
*/
ExpressionContextPair(JavaExpression javaExpression) {
this.javaExpression = javaExpression;
}
/**
* Constructor for an ExpressionContextPair.
* @param javaExpression the expression
* @param context the associated context.
*/
ExpressionContextPair(JavaExpression javaExpression, JavaStatement context) {
this(javaExpression);
this.context.addStatement(context);
}
/**
* Get the expression
* @return JavaExpression
*/
JavaExpression getJavaExpression() {
return javaExpression;
}
/**
* Get the context
* @return Block
*/
Block getContextBlock() {
return context.getCopy();
}
}
/**
* Simple wrapper class to encapsulate common info about a method.
* @author Edward Lam
*/
private static final class MethodInfo {
private final String methodName;
private final JavaTypeName[] paramTypes;
private final JavaTypeName returnType;
private final InvocationType invocationType;
MethodInfo(String methodName, JavaTypeName returnType, InvocationType invocationType) {
this(methodName, JavaExpression.EMPTY_TYPE_NAME_ARRAY, returnType, invocationType);
}
MethodInfo(String methodName, JavaTypeName paramType, JavaTypeName returnType, InvocationType invocationType) {
this(methodName, new JavaTypeName[]{paramType}, returnType, invocationType);
}
MethodInfo(String methodName, JavaTypeName[] paramTypes, JavaTypeName returnType, InvocationType invocationType) {
Assert.isNotNull(methodName);
Assert.isNotNull(paramTypes);
Assert.isNotNull(returnType);
Assert.isNotNull(invocationType);
this.methodName = methodName;
this.paramTypes = paramTypes;
this.returnType = returnType;
this.invocationType = invocationType;
}
/**
* @return the method name
*/
public String getMethodName() {
return methodName;
}
/**
* @return JavaTypeName[] the parameter types
*/
public JavaTypeName[] getParamTypes() {
return paramTypes;
}
/**
* @return JavaTypeName the return type
*/
public JavaTypeName getReturnType() {
return returnType;
}
/**
* @return InvocationType the invocation type
*/
public InvocationType getInvocationType() {
return invocationType;
}
}
/**
* ReferencedDCInfo is used to hold information about referenced
* zero arity data constructors. It is used to avoid creating duplicate
* DC instances and to generate/initialize class level fields for the
* referenced entities.
* @author rcypher
*/
static final class ReferencedDCInfo implements Comparable<ReferencedDCInfo> {
/** The java field which is the reference to the SC/DC */
private final JavaField javaField;
/** The DataConstructor if this is a zero arity DC */
private final DataConstructor dc;
/** The type of the supercombinator. */
private final JavaTypeName generatedClassType;
public ReferencedDCInfo (JavaField javaField, JavaTypeName generatedClassType, DataConstructor dc) {
this.javaField = javaField;
this.generatedClassType = generatedClassType;
this.dc = dc;
}
public JavaField getJField () {
return javaField;
}
public DataConstructor getDC () {
return dc;
}
/** {@inheritDoc} */
public int compareTo (ReferencedDCInfo o) {
return javaField.getFieldName().compareTo((o.javaField.getFieldName()));
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof ReferencedDCInfo)) {
return false;
}
return javaField.getFieldName().equals(((ReferencedDCInfo)o).javaField.getFieldName());
}
@Override
public int hashCode () {
return javaField.getFieldName().hashCode();
}
public JavaTypeName getGeneratedClassTypeName () {
return generatedClassType;
}
}
/**
* ReferencedSCInfo is used to hold information about referenced
* SCs. It is used to avoid creating duplicate
* SC instances and to generate/initialize class level fields for the
* referenced entities.
* @author rcypher
*/
static final class ReferencedSCInfo implements Comparable<ReferencedSCInfo> {
/** The java field which is the reference to the SC/DC */
private final JavaField javaField;
/** The type of the supercombinator. */
private final JavaTypeName generatedClassType;
/** The name of the supercombinator. */
private final QualifiedName scName;
public ReferencedSCInfo (JavaField javaField,
JavaTypeName generatedClassType,
QualifiedName scName) {
this.javaField = javaField;
this.generatedClassType = generatedClassType;
this.scName = scName;
}
public JavaField getJField () {
return javaField;
}
/** {@inheritDoc} */
public int compareTo (ReferencedSCInfo o) {
return javaField.getFieldName().compareTo(o.javaField.getFieldName());
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof ReferencedSCInfo)) {
return false;
}
return javaField.getFieldName().equals(((ReferencedSCInfo)o).javaField.getFieldName());
}
@Override
public int hashCode() {
return javaField.getFieldName().hashCode();
}
public JavaTypeName getGeneratedClassTypeName () {
return generatedClassType;
}
public String getSCName () { return scName.getUnqualifiedName();}
public QualifiedName getQualifiedSCName () {return scName;}
}
/**
* Information about a variable in the current CAL state.
* @author RCypher
*/
private static abstract class VarInfo implements Cloneable {
/** The java name which currently corresponds to the CAL variable. */
private final String javaName;
/** The CAL name. */
private final QualifiedName calName;
/** flag indicating that this variable has been evaluated. */
private boolean evaluated;
JavaExpression lazyReference;
JavaExpression strictReference;
JavaExpression unboxedReference;
private JavaTypeName unboxedType;
private final TypeExpr varType;
VarInfo (QualifiedName calName, String javaName, TypeExpr varType) {
this.calName = calName;
this.javaName = javaName;
this.varType = varType;
}
VarInfo (VarInfo otherInfo) {
this.calName = otherInfo.calName;
this.evaluated = otherInfo.evaluated;
this.javaName = otherInfo.javaName;
this.unboxedType = otherInfo.unboxedType;
this.varType = otherInfo.varType;
}
public String getJavaName () {
return javaName;
}
public QualifiedName getCALName () {
return calName;
}
TypeExpr getVarType () {
return varType;
}
/**
* @return Returns the evaluated.
*/
public boolean isEvaluated() {
return evaluated;
}
/**
* @param evaluated The evaluated to set.
*/
public void setEvaluated(boolean evaluated) {
this.evaluated = evaluated;
}
/**
* @return Returns the unboxedType.
*/
JavaTypeName getUnboxedType() {
return unboxedType;
}
/**
* @param unboxedType The unboxedType to set.
*/
void setUnboxedType(JavaTypeName unboxedType) {
this.unboxedType = unboxedType;
}
/**
* @param newUnboxedReference The unboxedReference to set.
*/
void updateUnboxedReference(JavaExpression newUnboxedReference) {
this.unboxedReference = newUnboxedReference;
}
public static final class Argument extends VarInfo {
Argument (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
private Argument (Argument otherArg) {
super (otherArg);
updateLazyReference(otherArg.lazyReference);
updateStrictReference(otherArg.strictReference);
updateUnboxedReference(otherArg.unboxedReference);
}
@Override
public Object clone () {
return new Argument(this);
}
}
private static abstract class LocallyDefinedVar extends VarInfo {
/** The strict definition of the variable. */
private JavaExpression strictVarDef;
/** The lazy definition of the variable. */
private JavaExpression lazyVarDef;
/** The java expression which corresponds to the unboxed form of the variable. */
private JavaExpression unboxedVarDef;
LocallyDefinedVar (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
/**
* @return Returns the unboxedDef.
*/
JavaExpression getUnboxedVarDef() {
return unboxedVarDef;
}
/**
* @param unboxedDef The unboxedDef to set.
*/
void updateUnboxedVarDef(JavaExpression unboxedDef) {
this.unboxedVarDef =unboxedDef;
}
/**
* @return Returns the lazyvarDef.
*/
JavaExpression getLazyVarDef() {
return lazyVarDef;
}
/**
* @param varDef The varDef to set.
*/
void updateLazyVarDef(JavaExpression varDef) {
this.lazyVarDef = varDef;
}
/**
* @return Returns the strictVarDef.
*/
JavaExpression getStrictVarDef() {
return strictVarDef;
}
/**
* @param varDef The varDef to set.
*/
void updateStrictVarDef(JavaExpression varDef) {
this.strictVarDef = varDef;
}
}
public static final class DCMember extends LocallyDefinedVar {
DCMember (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
}
public static final class LetNonRec extends LocallyDefinedVar {
LetNonRec (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
}
public static final class LetRec extends LocallyDefinedVar {
LetRec (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
}
public static final class RecordField extends VarInfo {
RecordField (QualifiedName calName, String javaName, TypeExpr varType) {
super (calName, javaName, varType);
}
private RecordField (RecordField other) {
super (other);
updateLazyReference(other.lazyReference);
updateStrictReference(other.strictReference);
updateUnboxedReference(other.unboxedReference);
}
@Override
public final Object clone () {
return new RecordField (this);
}
}
/**
* @param newStrictReference The strict reference to set.
*/
void updateStrictReference(JavaExpression newStrictReference) {
this.strictReference = newStrictReference;
}
/**
* @param newLazyReference The lazy reference to set.
*/
void updateLazyReference(JavaExpression newLazyReference) {
this.lazyReference = newLazyReference;
}
}
/**
* A simple container class which holds the name of an alt variable and its index with respect to
* a data constructor's arguments.
* @author Edward Lam
*/
private static final class AltVarIndexPair implements Comparable<AltVarIndexPair> {
private final int index;
private final String altVar;
AltVarIndexPair(String altVar, int index) {
this.index = index;
this.altVar = altVar;
}
public String getAltVar() {
return this.altVar;
}
public int getIndex() {
return this.index;
}
/** {@inheritDoc} */
public int compareTo (AltVarIndexPair o) {
return index - o.index;
}
@Override
public boolean equals(Object o) {
if (o == null || !(o instanceof AltVarIndexPair)) {
return false;
}
return index == ((AltVarIndexPair)o).index;
}
@Override
public int hashCode () {
return index;
}
}
/**
* Internal class to represent the three levels of compilation schemes.
* These schemes are used to determine the strictness of evaluation.
* @author rcypher
*/
static class Scheme extends Object {
// Top level.
static final Scheme R_SCHEME = new Scheme("R_Scheme");
// Strict.
static final Scheme E_SCHEME = new Scheme("E_Scheme");
// Lazy.
static final Scheme C_SCHEME = new Scheme("C_Scheme");
// Unbox for internal use. This is a kind of E scheme.
static final Scheme UNBOX_INTERNAL_SCHEME = new Scheme("Unbox_Internal_Scheme");
// Unbox for foreign use. This is a kind of E scheme.
static final Scheme UNBOX_FOREIGN_SCHEME = new Scheme("Unbox_Foreign_Scheme");
private final String description;
private Scheme (String description) {this.description = description;}
@Override
public String toString () {
return description;
}
}
static final class LiftedExpression {
private final String[] argNames;
private final JavaTypeName typeName;
private final Expression e;
private final String name;
private final String containingFunction;
LiftedExpression (String name, String[] argNames, Expression e, JavaTypeName typeName, String containingFunction) {
if (name == null || argNames == null || e == null || typeName == null) {
throw new NullPointerException ("Bad value creating: " + getClass().getName());
}
this.name = name;
this.argNames = argNames;
this.typeName = typeName;
this.e = e;
this.containingFunction = containingFunction;
}
Expression getExpression () {return e;}
JavaTypeName getTypeName () {return typeName;}
String getName () {return name;}
String[] getArgNames() {return argNames;}
int getArity () {return argNames.length;}
String getContainingFunction () {return containingFunction;}
}
/**
* @param dc
* @return true for the data constructors Prelude.True or Prelude.False.
*/
private static boolean isTrueOrFalseDataCons(DataConstructor dc) {
QualifiedName qn = dc.getName();
return qn.equals(CAL_Prelude.DataConstructors.True) || qn.equals(CAL_Prelude.DataConstructors.False);
}
/**
* @param dc
* @return true for the data constructor Prelude.True.
*/
private static boolean isTrueDataCons(DataConstructor dc) {
return dc.getName().equals(CAL_Prelude.DataConstructors.True);
}
/**
* Determine if the given data constructor will be generated as a TagDC class.
* i.e. is the data constructor zero arity and from a data type with more
* than one zero arity data constructor.
* @param dc
* @param module
* @return true if the data constructor is a TagDC
*/
static boolean isTagDC (DataConstructor dc, LECCModule module) {
TypeConstructor typeCons = dc.getTypeConstructor();
int nTagDCs = 0;
for (int i = 0; i < typeCons.getNDataConstructors(); ++i) {
DataConstructor dci = typeCons.getNthDataConstructor(i);
if (dci.getArity() == 0) {
nTagDCs++;
}
}
return nTagDCs > 1;
}
/**
* Returns the Class object corresponding to the specified foreign type info. If the Class object could not be resolved,
* a CodeGenerationException is thrown.
* @param foreignTypeInfo the foreign type info.
* @return the Class object corresponding to the foreign type info.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getForeignType(final ForeignTypeInfo foreignTypeInfo) throws CodeGenerationException {
try {
return foreignTypeInfo.getForeignType();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign type.", e);
}
}
/** The fully-qualified name of the CalValue class.*/
private static final String CalValueClassName = CalValue.class.getName();
/**
* Determine whether a given class is the CalValue class.
* We cannot simply check for object equality since the CalValue class we check against may not
* necessarily be the CalValue class loaded by this classloader.
* For instance, in Eclipse, the Module's foreign classloader (for this project in the Eclipse workspace)
* will be different from the classloader used to load the platform plugin.
*
* @param clazz a class object
* @return whether the given class is CalValue.class.
*/
private static boolean isCalValueClass(Class<?> clazz) {
return CalValueClassName.equals(clazz.getName());
}
/**
* Returns the Java type T in the expression "expr instanceof T", as represented by the specified foreign function info.
* If the Class object could not be resolved, a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @return the Java type T in the expression "expr instanceof T", as represented by the foreign function info. Will not be null.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getInstanceOfType(final ForeignFunctionInfo.InstanceOf foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getInstanceOfType();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign type for foreign instanceof function.", e);
}
}
/**
* Returns the class from which to invoke a method or field (both static and non-static) which cannot be null,
* unless this is a constructor invocation.
*
* It is sometimes necessary to invoke a method/field from a class other than which it was defined.
* For example, if package scope class A defines a static public field f, and public class B extends A,
* then B.f in a different package will not result in a compilation error but A.f will.
*
* Or for example, if package scope class A defines a non-static public method m, and public class B extends A,
* then in a different package we cannot invoke m on an object of type B if:
* - the invocation is done via reflection, or
* - the reference is first cast to the method's declared type, in this case A, i.e. ((A)b).m()
*
* @param foreignFunctionInfo the foreign function info.
* @return the class from which to invoke a method or field (both static and non-static) which cannot be null,
* unless this is a constructor invocation.
* @throws CodeGenerationException if the AccessibleObject could not be resolved.
*/
private static Class<?> getInvocationClass(final ForeignFunctionInfo.Invocation foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getInvocationClass();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign type containing foreign method, field, or constructor.", e);
}
}
/**
* Returns the Java class corresponding to the argN argument in the specified foreign function info. If the Class object could not be resolved,
* a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @param argN a zero-based argument index.
* @return the Java class corresponding to the argN argument in the foreign function info.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getJavaArgumentType(final ForeignFunctionInfo foreignFunctionInfo, int argN) throws CodeGenerationException {
try {
return foreignFunctionInfo.getJavaArgumentType(argN);
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign argument type for foreign function.", e);
}
}
/**
* Returns the field, method or constructor corresponding to the specified foreign function info. If the AccessibleObject could not be resolved,
* a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @return the field, method or constructor corresponding to the foreign function info.
* @throws CodeGenerationException if the AccessibleObject could not be resolved.
*/
private static AccessibleObject getJavaProxy(final ForeignFunctionInfo.Invocation foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getJavaProxy();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign method, field, or constructor.", e);
}
}
/**
* Returns the return type of the foreign entity corresponding to the specified foreign function info as a Java class. If the Class object could not be resolved,
* a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @return the return type of the foreign entity corresponding to the foreign function info as a Java class.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getJavaReturnType(final ForeignFunctionInfo foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getJavaReturnType();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign return type for foreign function.", e);
}
}
/**
* Returns the number of arguments of the CAL foreign function corresponding to the specified foreign function info.
* If the field/method/constructor could not be resolved, a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @return the number of arguments of the CAL foreign function corresponding to the foreign function info.
* @throws CodeGenerationException if the field/method/constructor could not be resolved, a CodeGenerationException is thrown.
*/
private static int getNArguments(final ForeignFunctionInfo foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getNArguments();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign method, field, or constructor.", e);
}
}
/**
* Returns the Class object corresponding to the cast type in a cast expression. If the Class object could not be resolved,
* a CodeGenerationException is thrown.
* @param castExpression the cast expression.
* @return the Class object corresponding to the cast type in a cast expression.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getCastType(final Expression.Cast castExpression) throws CodeGenerationException {
try {
return castExpression.getCastType();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign type for Expression.Cast.", e);
}
}
/**
* Returns the Class object corresponding to the referent type in a class literal expression. If the Class object could not be resolved,
* a CodeGenerationException is thrown.
* @param foreignFunctionInfo the foreign function info.
* @return the referent type, i.e. the Java type R where this literal corresponds to R.class.
* @throws CodeGenerationException if the Class object could not be resolved.
*/
private static Class<?> getReferentType(final ForeignFunctionInfo.ClassLiteral foreignFunctionInfo) throws CodeGenerationException {
try {
return foreignFunctionInfo.getReferentType();
} catch (UnableToResolveForeignEntityException e) {
throw new CodeGenerationException("Failed to resolve foreign class literal.", e);
}
}
/**
* Traverse the JavaModel instance and build up a map associating the names of
* declared locals to their Java type.
* @author Raymond Cypher
*
*/
private static final class DeclaredLocalsFinder extends JavaModelTraverser<Map<String, JavaTypeName>, Void>{
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitLocalVariableDeclarationStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.LocalVariableDeclaration, java.lang.Object)
*/
public Void visitLocalVariableDeclarationStatement(
final LocalVariableDeclaration localVariableDeclaration, final Map<String, JavaTypeName> arg) {
JavaTypeName variableType = localVariableDeclaration.getLocalVariable().getTypeName();
if (variableType.isObjectReference() &&
(variableType.equals(JavaTypeNames.RTVALUE) ||
variableType.equals(JavaTypeNames.RTRECORD_VALUE) ||
variableType.equals(JavaTypeNames.RTCONS) ||
variableType.equals(JavaTypeNames.RTINDIRECTION) ||
localVariableDeclaration.getLocalVariable().getName().startsWith("$case"))) {
arg.put(localVariableDeclaration.getLocalVariable().getName(), variableType);
}
return null;
}
}
/**
* This class transforms an instance of the JavaModel.
* The transformation is applied to return statements.
* Return statements are modified to return an unboxed value.
* @author rcypher
*/
final class UnboxedReturnCopier extends JavaModelCopier<Void> {
private JavaTypeName resultTypeName;
UnboxedReturnCopier (JavaTypeName resultType) {
this.resultTypeName = resultType;
}
private JavaExpression handleDataInstance(JavaExpression returnValue) {
if (returnValue instanceof MethodInvocation.Instance) {
MethodInvocation.Instance mii = (MethodInvocation.Instance)returnValue;
if (mii.getMethodName().equals("getValue") && mii.getNArgs() == 0) {
returnValue = mii.getInvocationTarget();
}
}
if (returnValue instanceof MethodInvocation.Static) {
MethodInvocation.Static mis = (MethodInvocation.Static)returnValue;
// If the return value is a call to create a boxed value we're OK
// since this means there is an unboxed value to return.
if (mis.getMethodName().equals("make")) {
// Check to see if the static type is one of the RTData sub-classes.
JavaTypeName staticClass = mis.getInvocationClass();
if (
(staticClass.equals(JavaTypeNames.RTDATA_BOOLEAN) &&
resultTypeName.equals(JavaTypeName.BOOLEAN)) ||
(staticClass.equals(JavaTypeNames.RTDATA_BYTE) &&
resultTypeName.equals(JavaTypeName.BYTE)) ||
(staticClass.equals(JavaTypeNames.RTDATA_CHAR) &&
resultTypeName.equals(JavaTypeName.CHAR)) ||
(staticClass.equals(JavaTypeNames.RTDATA_DOUBLE) &&
resultTypeName.equals(JavaTypeName.DOUBLE)) ||
(staticClass.equals(JavaTypeNames.RTDATA_FLOAT) &&
resultTypeName.equals(JavaTypeName.FLOAT)) ||
(staticClass.equals(JavaTypeNames.RTDATA_INT) &&
resultTypeName.equals(JavaTypeName.INT)) ||
(staticClass.equals(JavaTypeNames.RTDATA_INTEGER) &&
resultTypeName.equals(JavaTypeName.BIG_INTEGER)) ||
(staticClass.equals(JavaTypeNames.RTDATA_LONG) &&
resultTypeName.equals(JavaTypeName.LONG)) ||
(staticClass.equals(JavaTypeNames.RTDATA_SHORT) &&
resultTypeName.equals(JavaTypeName.SHORT)) ||
(staticClass.equals(JavaTypeNames.RTDATA_STRING) &&
resultTypeName.equals(JavaTypeName.STRING)) ||
(staticClass.equals(JavaTypeNames.RTDATA_OPAQUE) &&
resultTypeName.equals(JavaTypeName.OBJECT))) {
// The first argument to make() will be the unboxed value we
// want to return.
return (JavaExpression)mis.getArg(0).accept(this, null);
}
// If we're creating an instance of CAL_Opaque we're dealing with an
// Object. We just do a cast and return.
if (staticClass.equals(JavaTypeNames.RTDATA_OPAQUE) &&
!(resultTypeName instanceof JavaTypeName.Primitive)) {
return new CastExpression(resultTypeName, (JavaExpression)mis.getArg(0).accept(this, null));
}
}
}
return null;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.runtime.lecc.JavaModelVisitor#visitReturnStatement(org.openquark.cal.internal.runtime.lecc.JavaStatement.ReturnStatement, java.lang.Object)
*/
@Override
public JavaStatement visitReturnStatement(ReturnStatement returnStatement,
Void arg) {
JavaExpression returnValue = returnStatement.getReturnExpression();
// First we want to get rid of any indirections.
while (returnValue instanceof JavaExpression.PlaceHolder) {
returnValue = ((JavaExpression.PlaceHolder)returnValue).getActualExpression();
}
// Check for a static invocation.
if (returnValue instanceof MethodInvocation) {
MethodInvocation mis = (MethodInvocation)returnValue;
boolean isStatic = mis instanceof MethodInvocation.Static;
final JavaTypeName invocationClass;
if (isStatic) {
invocationClass = ((MethodInvocation.Static)mis).getInvocationClass();
} else {
invocationClass = JavaTypeNames.RTVALUE;
}
// If this return value is a call to one of the error functions
// we can treat it as returning an unboxed value since we know it
// will actually throw an exception. So we check if the returnValue
// is an invocation of RTValue.badValue or RTValue.errorCall
if (invocationClass.equals(JavaTypeNames.RTVALUE) &&
(mis.getMethodName().equals("errorCall") ||
mis.getMethodName().equals("badValue") ||
mis.getMethodName().equals("badSwitchIndex"))) {
// We need to create new MethodInvocation instances which will
// invoke the type specific version of 'errorCall'.
JavaTypeName[] argTypes;
JavaExpression[] argValues;
if (mis.getMethodName().equals("badSwitchIndex")) {
argTypes = new JavaTypeName[]{JavaTypeName.ERRORINFO};
argValues = new JavaExpression[]{mis.getArg(0)};
} else {
argTypes = new JavaTypeName[]{JavaTypeName.ERRORINFO, JavaTypeName.STRING};
argValues = new JavaExpression[2];
if (mis.getNArgs() == 1) {
argValues[0] = LiteralWrapper.NULL;
argValues[1] = mis.getArg(0);
} else {
argValues[0] = mis.getArg(0);
argValues[1] = mis.getArg(1);
}
}
boolean cast = false;
JavaTypeName callReturnType = resultTypeName;
String staticFunctionName = mis.getMethodName();
if (resultTypeName.equals(JavaTypeName.BOOLEAN)) {
staticFunctionName = staticFunctionName + "_boolean";
} else if (resultTypeName.equals(JavaTypeName.BYTE)) {
staticFunctionName = staticFunctionName + "_byte";
} else if (resultTypeName.equals(JavaTypeName.CHAR)) {
staticFunctionName = staticFunctionName + "_char";
} else if (resultTypeName.equals(JavaTypeName.DOUBLE)) {
staticFunctionName = staticFunctionName + "_double";
} else if (resultTypeName.equals(JavaTypeName.FLOAT)) {
staticFunctionName = staticFunctionName + "_float";
} else if (resultTypeName.equals(JavaTypeName.INT)) {
staticFunctionName = staticFunctionName + "_int";
} else if (resultTypeName.equals(JavaTypeName.LONG)) {
staticFunctionName = staticFunctionName + "_long";
} else if (resultTypeName.equals(JavaTypeName.SHORT)) {
staticFunctionName = staticFunctionName + "_short";
} else {
callReturnType = JavaTypeName.OBJECT;
staticFunctionName = staticFunctionName + "_Object";
if (!resultTypeName.equals(JavaTypeName.OBJECT)) {
cast = true;
}
}
JavaExpression newReturnValue;
if (isStatic) {
newReturnValue =
new MethodInvocation.Static(JavaTypeNames.RTVALUE, staticFunctionName, argValues, argTypes, callReturnType);
} else {
newReturnValue =
new MethodInvocation.Instance(null, staticFunctionName, argValues, argTypes, callReturnType, MethodInvocation.InvocationType.VIRTUAL);
}
if (cast) {
newReturnValue = new JavaExpression.CastExpression(resultTypeName, newReturnValue);
}
return new ReturnStatement (newReturnValue);
}
}
JavaExpression dataInstanceValue = handleDataInstance(returnValue);
if (dataInstanceValue != null) {
return new ReturnStatement(dataInstanceValue);
}
// See if we're returning a cached literal value.
//For example, this applies in the case of Prelude.signumInt and signumLong. We want the unboxed versions
//to return the literal constant values instead of evaluating and unboxing the RTValues.
if (returnValue instanceof JavaExpression.JavaField.Static) {
final JavaField.Static field = (JavaField.Static)returnValue;
final String fieldName = field.getFieldName();
if (fieldName.startsWith(SCJavaDefn.LITERAL_PREFIX)) {
for (final KernelLiteral kl : sharedValues.getLiteralValues()) {
//Prelude.Integer (i.e. java.lang.BigInteger) values are not handled here, since they do not
//have a literal unboxed representation in Java.
if (kl.getSymbol().equals(fieldName) && !kl.kernelTypeClass.equals(CAL_Integer.class)) {
return new ReturnStatement(LiteralWrapper.make(kl.getLiteralObject()));
}
}
}
}
// We want to invoke evaluate on the return value and then unbox.
JavaExpression newReturnValue = (JavaExpression)returnValue.accept(this, arg);
if (!(newReturnValue instanceof JavaExpression.MethodInvocation.Instance) ||
!((JavaExpression.MethodInvocation)newReturnValue).getMethodName().equals("evaluate")) {
// If all arguments and locals are not self referential types we can continue.
boolean problematicType = false;
for (int i = 0, n = getArity(); i < n; ++i) {
TypeExpr argType = getArgumentType(i);
if (argType != null) {
if (argType instanceof TypeVar) {
problematicType = true;
break;
} else if (argType instanceof TypeConsApp) {
if (SCJavaDefn.isSelfReferentialDataType(argType)) {
problematicType = true;
break;
}
}
}
}
if (!problematicType) {
// Check local variables.
Set<VarInfo> localVars = returnStatementToLocalVars.get(returnStatement);
if (localVars == null) {
problematicType = true;
} else {
for (final VarInfo vi : localVars) {
TypeExpr argType = vi.getVarType();
if (argType != null) {
if (argType instanceof TypeVar) {
problematicType = true;
break;
} else if (argType instanceof TypeConsApp) {
if (SCJavaDefn.isSelfReferentialDataType(argType)) {
problematicType = true;
break;
}
}
}
}
}
}
if (problematicType) {
throw new UnboxingTransformationError("Unable to transform function body.");
}
newReturnValue = createInvocation(newReturnValue, SCJavaDefn.EVALUATE, SCJavaDefn.EXECUTION_CONTEXT_VAR);
}
newReturnValue = SCJavaDefn.unboxValue(resultTypeName, newReturnValue);
return new ReturnStatement(newReturnValue);
}
}
static class SharedValues {
private final Map<Object, KernelLiteral> literalObjectToKernelLiteralMap = new LinkedHashMap<Object, KernelLiteral>();
Set<ReferencedDCInfo> referencedDCs = new TreeSet<ReferencedDCInfo>();
Map<String, JavaExpression> staticErrorInfo = new TreeMap<String, JavaExpression>();
KernelLiteral addKernelLiteral (Object literalValue, JavaTypeName containingClass) throws CodeGenerationException {
KernelLiteral kl = literalObjectToKernelLiteralMap.get(literalValue);
if (kl == null) {
kl = new KernelLiteral (literalValue, this, containingClass);
literalObjectToKernelLiteralMap.put (literalValue, kl);
}
return kl;
}
KernelLiteral getKernelLiteral (Object literalValue) {
return literalObjectToKernelLiteralMap.get(literalValue);
}
Collection<KernelLiteral> getLiteralValues () {
return literalObjectToKernelLiteralMap.values();
}
int getNLiteralValues () {
return literalObjectToKernelLiteralMap.size();
}
Set<ReferencedDCInfo> getReferencedDCs () {
return referencedDCs;
}
int getNReferencedDCs () {
return referencedDCs.size();
}
void add (ReferencedDCInfo referencedDC) {
referencedDCs.add(referencedDC);
}
JavaExpression getStaticError (String errorVarName) {
return staticErrorInfo.get(errorVarName);
}
void addStaticError (String errorVarname, JavaExpression errorInfo) {
staticErrorInfo.put (errorVarname, errorInfo);
}
Set<String> getStaticErrorInfoNames () {
return staticErrorInfo.keySet();
}
int getNStaticErrorInfo () {
return staticErrorInfo.size();
}
}
/**
* UnboxingTransformationException
* Class for raising exceptions during the process of transforming
* a function body to return an unboxed value. When thrown this exception
* indicates that a valid transformation does not exist.
* @author rcypher
* Created: March 19, 2007
*/
static class UnboxingTransformationError extends Error {
private static final long serialVersionUID = -6310507883559829154L;
/**
* @param message
*/
public UnboxingTransformationError(String message) {
super(message);
}
/**
* @param message
* @param cause
*/
public UnboxingTransformationError(String message, Throwable cause) {
super(message, cause);
}
}
/**
* This class performs a transformation on a Java Model instance which replaces the
* last reference to local variables or arguments with a call to RTValue.lastRef().
* This serves to release the local reference, allowing the garbage collector to
* potentially recover memory earlier than it would have otherwise.
* This is primarily intended to improve the space usage behavior in cases where the
* generated Java would otherwise hold a reference to the head of data structure.
* For example:
* foo :: [Maybe Double] -> String;
* foo x =
* case (List.last x) of
* Just {value} -> "True";
* Nothing -> "False";
* ;
* Would generate Java code like:
* public final RTValue f1S(RTValue x, RTExecutionContext $ec) throws CALExecutorException {
* // Top level supercombinator logic
* TYPE_Maybe $case1 =
* (((TYPE_Maybe)(java.lang.Object)Last.$instance.f1S(x.evaluate($ec), $ec).evaluate($ec)));
*
* switch ($case1.getOrdinalValue()) {
* case 0: return "False";
* case 1: return "True";
* }
* }
*
* In this scenario evaluation of 'List.last x' would normally cause the list to be fully expanded in
* memory, because the 'x' argument of f1S is holding a reference to the head of the list. Since the list
* isn't referenced anywhere else in f1S and the behavior of List.last doesn't hold onto the already
* traversed portion of the list it would be nice to avoid holding the whole list in memory.
* If the line containing the last reference to 'x' is changed to:
* (((TYPE_Maybe)(java.lang.Object)Last.$instance.f1S(RTValue.lastRef(x.evaluate($ec), x = null), $ec).evaluate($ec)));
* The effect is to release the local reference to 'x' before the evaluation of List.last, which allows the garbage
* collector to recover the traversed portion of the list. This, of course, assumes that there is not
* some other reference top the head of the list still held elsewhere.
*
* NOTE: This transformation takes advantage of the fact that in the generated code for a function body
* branches of execution never converge. i.e. the path of execution may branch due to switch or
* if-then-else but these branches will always ultimately terminate in a 'return' rather than
* popping up a level in scope. If the code generation changes so that this is no longer true
* this transformation will need to be updated.
* @author Raymond Cypher
*
*/
private static final class VarReleaser extends JavaModelCopier<Void> {
/**
* An array of JavaTypeName of size two. Both members are initialized to JavaTypeNames.RTVALUE.
* This field is used when specifying argument types when creating a method call instance.
*/
private static final JavaTypeName[] TWO_RTVALUES = new JavaTypeName[]{JavaTypeNames.RTVALUE, JavaTypeNames.RTVALUE};
/**
* The names of the variables to be released, mapped to the corresponding Java type.
*/
private final Map<String, JavaTypeName> variablesOfInterest;
VarReleaser(Map<String, JavaTypeName> variablesOfInterest) {
this.variablesOfInterest = variablesOfInterest;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitBinaryOperatorExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.OperatorExpression.Binary, java.lang.Object)
*/
public JavaExpression visitBinaryOperatorExpression(Binary binaryOperator,
Void arg) {
// Visit the second argument first, as we want to release the last reference to each
// variable.
JavaExpression arg1 = (JavaExpression)binaryOperator.getArgument(1).accept(this, arg);
JavaExpression arg0 = (JavaExpression)binaryOperator.getArgument(0).accept(this, arg);
return new Binary (
binaryOperator.getJavaOperator(),
arg0,
arg1);
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitAssignmentExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.Assignment, java.lang.Object)
*/
public JavaExpression visitAssignmentExpression(final Assignment assignment, final Void arg) {
// The 'lastRef' transformation should not be applied to the left hand side of the
// assignment.
return new Assignment (
(Nameable)assignment.getLeftHandSide().accept(new JavaModelCopier<Void>(), arg),
(JavaExpression)assignment.getValue().accept(this, arg));
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitLocalVariableDeclarationStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.LocalVariableDeclaration, java.lang.Object)
*/
public JavaStatement visitLocalVariableDeclarationStatement(
LocalVariableDeclaration localVariableDeclaration, Void arg) {
// The 'lastRef' transformation should not be applied to the left hand side of
// the declaration.
LocalVariable assignTo = (LocalVariable)localVariableDeclaration.getLocalVariable().accept(new JavaModelCopier<Void>(), null);
if (localVariableDeclaration.getInitializer() != null) {
LocalVariableDeclaration newDeclaration =
new LocalVariableDeclaration (
assignTo,
(JavaExpression)localVariableDeclaration.getInitializer().accept(this, arg),
localVariableDeclaration.isFinal());
return newDeclaration;
} else {
LocalVariableDeclaration newDeclaration =
new LocalVariableDeclaration (
assignTo,
null,
localVariableDeclaration.isFinal());
return newDeclaration;
}
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitArrayCreationExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.ArrayCreationExpression, java.lang.Object)
*/
public JavaExpression visitArrayCreationExpression(
ArrayCreationExpression arrayCreation, Void arg) {
// Visit the element values in reverse order so that the last reference to
// the variable is released.
JavaExpression[] elementValues = new JavaExpression[arrayCreation.getNElementValues()];
for (int i = elementValues.length - 1; i >= 0; --i) {
elementValues[i] = (JavaExpression)arrayCreation.getElementValue(i).accept(this, arg);
}
return new ArrayCreationExpression(arrayCreation.getArrayElementTypeName(), elementValues);
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitClassInstanceCreationExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.ClassInstanceCreationExpression, java.lang.Object)
*/
public JavaExpression visitClassInstanceCreationExpression(
ClassInstanceCreationExpression instanceCreation, Void arg) {
// Visit the arguments to this constructor call in reverse order so that
// the last reference to any variables is released.
JavaTypeName[] argTypes = new JavaTypeName[instanceCreation.getNArgs()];
JavaExpression[] argValues = new JavaExpression[instanceCreation.getNArgs()];
for (int i = argTypes.length - 1; i >= 0; --i) {
argTypes[i] = instanceCreation.getParamType(i);
argValues[i] = (JavaExpression)instanceCreation.getArg(i).accept(this, arg);
}
return new ClassInstanceCreationExpression(instanceCreation.getClassName(), argValues, argTypes);
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitAssertStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.AssertStatement, java.lang.Object)
*/
public JavaStatement visitAssertStatement(final AssertStatement assertStatement,
final Void arg) {
// Visit the 'on failure' expression before the condition expr so
// the last reference to a variable is released.
if (assertStatement.getOnFailureExpr() != null) {
assertStatement.getOnFailureExpr().accept(this, arg);
}
assertStatement.getConditionExpr().accept(this, arg);
return null;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitTernaryOperatorExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.OperatorExpression.Ternary, java.lang.Object)
*/
public JavaExpression visitTernaryOperatorExpression(Ternary ternaryOperator,
Void arg) {
// The branches of the conditional need to be handled separately, since they are mutually
// exclusive.
Map<String, JavaTypeName> variablesOfInterestBranch1 = new HashMap<String, JavaTypeName>(variablesOfInterest);
VarReleaser vr = new VarReleaser(variablesOfInterestBranch1);
JavaExpression arg1 = (JavaExpression)ternaryOperator.getArgument(1).accept(vr, arg);
Map<String, JavaTypeName> variablesOfInterestBranch2 = new HashMap<String, JavaTypeName>(variablesOfInterest);
vr = new VarReleaser(variablesOfInterestBranch2);
JavaExpression arg2 = (JavaExpression)ternaryOperator.getArgument(2).accept(vr, arg);
// Update the variables of interest.
// If a variable was released in either branch it cannot be relesed previous
// to the branching. i.e. the variables of interest are the union of the
// variables still in play from each branch.
variablesOfInterest.clear();
for(String varName : variablesOfInterestBranch1.keySet()) {
if (variablesOfInterestBranch2.containsKey(varName)) {
variablesOfInterest.put(varName, variablesOfInterestBranch1.get(varName));
}
}
JavaExpression arg0 = (JavaExpression)ternaryOperator.getArgument(0).accept(this, arg);
return new Ternary (
arg0,
arg1,
arg2);
}
/**
*
* @param mi
* @return true if the method invocation is a call to 'evaluate' on a local.
*/
private final JavaExpression.Nameable isEvaluateInvocation (MethodInvocation.Instance mi) {
if (mi.getMethodName().equals("evaluate")) {
JavaExpression target = mi.getInvocationTarget();
if (target != null) {
if (target instanceof LocalName) {
return (LocalName)target;
} else if (target instanceof LocalVariable) {
return (LocalVariable)target;
} else if (target instanceof MethodVariable) {
return (MethodVariable)target;
}
}
}
return null;
}
/**
*
* @param varName
* @return true if the named variable is of interest and has not been released.
*/
private boolean shouldRelease(String varName) {
return variablesOfInterest.containsKey(varName);
}
/**
* Generates a call to RTValue.lastRef where the
* second argument is nulled out.
* @param keep - the first argument to lastRef
* @param nullOut - the reference to be assigned null and passed as the second argument to lastRef
* @param varType
* @return a call to RTValue.lastRef
*/
private JavaExpression callLastRef(JavaExpression keep, JavaExpression.Nameable nullOut, JavaTypeName varType) {
JavaExpression release =
new MethodInvocation.Static(
JavaTypeNames.RTVALUE,
"lastRef",
new JavaExpression[]{keep, new Assignment(nullOut, LiteralWrapper.NULL)},
TWO_RTVALUES,
JavaTypeNames.RTVALUE);
// If the var type is not RTValue cast the result of the call to lastRef.
if (!varType.equals(JavaTypeNames.RTVALUE)) {
release = new JavaExpression.CastExpression(varType, release);
}
return release;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitLocalNameExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.LocalName, java.lang.Object)
*/
public JavaExpression visitLocalNameExpression(LocalName localName, Void arg) {
// If we've gotten to this point the LocalName may be a reference that needs to be nulled out.
if (shouldRelease(localName.getName())) {
JavaTypeName varType = variablesOfInterest.get(localName.getName());
variablesOfInterest.remove(localName.getName());
return callLastRef (localName, localName, varType);
} else {
return super.visitLocalNameExpression(localName, arg);
}
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitLocalVariableExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.LocalVariable, java.lang.Object)
*/
public JavaExpression visitLocalVariableExpression(LocalVariable localVariable,
Void arg) {
// If we've gotten to this point the LocalVariable may be a reference that needs to be nulled out.
if (shouldRelease(localVariable.getName())) {
JavaTypeName varType = variablesOfInterest.get(localVariable.getName());
variablesOfInterest.remove(localVariable.getName());
return callLastRef (localVariable, localVariable, varType);
} else {
return super.visitLocalVariableExpression(localVariable, arg);
}
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitMethodVariableExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.MethodVariable, java.lang.Object)
*/
public JavaExpression visitMethodVariableExpression(MethodVariable methodVariable,
Void arg) {
// If we've gotten to this point the LocalVariable may be a reference that needs to be nulled out.
if (shouldRelease(methodVariable.getName())) {
JavaTypeName varType = variablesOfInterest.get(methodVariable.getName());
variablesOfInterest.remove(methodVariable.getName());
return callLastRef (methodVariable, methodVariable, varType);
} else {
return super.visitMethodVariableExpression(methodVariable, arg);
}
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitInstanceMethodInvocationExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.MethodInvocation.Instance, java.lang.Object)
*/
public JavaExpression visitInstanceMethodInvocationExpression(
MethodInvocation.Instance instanceInvocation, Void arg) {
JavaTypeName[] argTypes = new JavaTypeName[instanceInvocation.getNArgs()];
JavaExpression[] argValues = new JavaExpression[instanceInvocation.getNArgs()];
// We want to access the method arguments in reverse order
// as we want to apply the transformation to the rightmost
// reference to a variable.
for (int i = argTypes.length - 1; i >= 0; --i) {
argTypes[i] = instanceInvocation.getParamType(i);
argValues[i] = (JavaExpression)instanceInvocation.getArg(i).accept(this, arg);
}
// If the invocation is of the form 'x.evaluate(...)', x is a local variable,
// and this is the last reference to x we want to produce:
// RTValue.lastRef(x.evaluate(...), x = null)
// instead of:
// RTValue.lastRef(x, x = null).evaluate(...);
JavaExpression.Nameable evaluateTarget = isEvaluateInvocation(instanceInvocation);
if (evaluateTarget != null ) {
JavaExpression target = null;
if (instanceInvocation.getInvocationTarget() != null) {
target = (JavaExpression)instanceInvocation.getInvocationTarget().accept(new JavaModelCopier<Void>(), arg);
}
String varName = null;
if (evaluateTarget instanceof LocalName) {
varName = ( ((LocalName)evaluateTarget).getName());
} else if (evaluateTarget instanceof LocalVariable) {
varName = ( ((LocalVariable)evaluateTarget).getName());
} else if (evaluateTarget instanceof MethodVariable) {
varName = ( ((MethodVariable)evaluateTarget).getName());
} else {
throw new NullPointerException ("Unhandled sub type of Nameable in VarReleaser.visitInstanceMethodInvocation().");
}
JavaExpression newInvocation =
new MethodInvocation.Instance(
target,
instanceInvocation.getMethodName(),
instanceInvocation.getDeclaringClass(),
argValues,
argTypes,
instanceInvocation.getReturnType(),
instanceInvocation.getInvocationType());
if (shouldRelease(varName)) {
JavaTypeName varType = variablesOfInterest.get(varName);
variablesOfInterest.remove(varName);
return callLastRef(newInvocation, evaluateTarget, varType);
}
return newInvocation;
} else {
JavaExpression target = null;
if (instanceInvocation.getInvocationTarget() != null) {
target = (JavaExpression)instanceInvocation.getInvocationTarget().accept(this, arg);
}
JavaExpression newInvocation =
new MethodInvocation.Instance(
target,
instanceInvocation.getMethodName(),
instanceInvocation.getDeclaringClass(),
argValues,
argTypes,
instanceInvocation.getReturnType(),
instanceInvocation.getInvocationType());
return newInvocation;
}
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitStaticMethodInvocationExpression(org.openquark.cal.internal.machine.lecc.JavaExpression.MethodInvocation.Static, java.lang.Object)
*/
public JavaExpression visitStaticMethodInvocationExpression(
MethodInvocation.Static staticInvocation, Void arg) {
// We want to access the method arguments in reverse order
// as we want to apply the transformation to the rightmost
// reference to a variable.
JavaTypeName[] argTypes = new JavaTypeName[staticInvocation.getNArgs()];
JavaExpression[] argValues = new JavaExpression[staticInvocation.getNArgs()];
for (int i = argTypes.length - 1; i >= 0; --i) {
argTypes[i] = staticInvocation.getParamType(i);
argValues[i] = (JavaExpression)staticInvocation.getArg(i).accept(this, arg);
}
return new MethodInvocation.Static(
staticInvocation.getInvocationClass(),
staticInvocation.getMethodName(),
argValues,
argTypes,
staticInvocation.getReturnType());
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitSwitchStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.SwitchStatement, java.lang.Object)
*/
public JavaStatement visitSwitchStatement(SwitchStatement switchStatement,
Void arg) {
List<Map<String, JavaTypeName>>remainingVars = new ArrayList<Map<String, JavaTypeName>>();
List<IntCaseGroup> cases = switchStatement.getCaseGroups();
List<IntCaseGroup> newCases = new ArrayList<IntCaseGroup>();
// We want to treat each case group independently with regards to the
// variables that need to be released.
for (int i = 0, n = cases.size(); i < n; ++i) {
IntCaseGroup group = cases.get(i);
Map<String, JavaTypeName> variablesToFindInGroup = new HashMap<String, JavaTypeName>(variablesOfInterest);
remainingVars.add(variablesToFindInGroup);
VarReleaser vr = new VarReleaser(variablesToFindInGroup);
newCases.add((IntCaseGroup)group.accept(vr, null));
}
SwitchStatement.DefaultCase newDefault = null;
Map<String, JavaTypeName> variablesOfInterestForDefault = new HashMap<String, JavaTypeName>(variablesOfInterest);
if (switchStatement.getDefaultStatement() != null) {
VarReleaser vr = new VarReleaser(variablesOfInterestForDefault);
newDefault = new SwitchStatement.DefaultCase (
(JavaStatement)switchStatement.getDefaultStatement().accept(vr, arg));
}
// Update the variables of interest.
// This will be the union of the variables remaining from each branch.
variablesOfInterest.clear();
for(String varName : variablesOfInterestForDefault.keySet()) {
boolean missing = false;
for (int i = 0, n = remainingVars.size(); i < n; ++i) {
if (!remainingVars.get(i).containsKey(varName)) {
missing = true;
}
}
if (!missing) {
variablesOfInterest.put(varName, variablesOfInterestForDefault.get(varName));
}
}
SwitchStatement newSwitch = new SwitchStatement (
(JavaExpression)switchStatement.getCondition().accept(this, arg));
for (int i = 0, n = newCases.size(); i < n; ++i) {
newSwitch.addCase(newCases.get(i));
}
if (newDefault != null) {
newSwitch.addCase(newDefault);
}
return newSwitch;
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitIfThenElseStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.IfThenElseStatement, java.lang.Object)
*/
public JavaStatement visitIfThenElseStatement(IfThenElseStatement ifThenElse,
Void arg) {
// Treat each branch independently with regards to the variables to be realeased.
Map<String, JavaTypeName> variablesOfInterestForThen = new HashMap<String, JavaTypeName>(variablesOfInterest);
VarReleaser vr = new VarReleaser(variablesOfInterestForThen);
JavaStatement newThen = (JavaStatement)ifThenElse.getThenStatement().accept(vr, null);
JavaStatement newElse = null;
Map<String, JavaTypeName> variablesOfInterestForElse = new HashMap<String, JavaTypeName>(variablesOfInterest);
if (ifThenElse.getElseStatement() != null) {
vr = new VarReleaser(variablesOfInterestForElse);
newElse = (JavaStatement)ifThenElse.getElseStatement().accept(vr, null);
}
// Update variables of interest.
variablesOfInterest.clear();
for (String varName : variablesOfInterestForThen.keySet()) {
if (variablesOfInterestForElse.containsKey(varName)) {
variablesOfInterest.put(varName, variablesOfInterestForElse.get(varName));
}
}
JavaExpression condition =
(JavaExpression)ifThenElse.getCondition().accept (this, arg);
if (newElse != null) {
return new IfThenElseStatement(condition, newThen, newElse);
}
return new IfThenElseStatement(condition, newThen);
}
/* (non-Javadoc)
* @see org.openquark.cal.internal.machine.lecc.JavaModelVisitor#visitBlockStatement(org.openquark.cal.internal.machine.lecc.JavaStatement.Block, java.lang.Object)
*/
public JavaStatement visitBlockStatement(Block block, Void arg) {
// Treat each exception handler independently with regards to the variables to
// be released, since they are mutually exclusive.
List<JavaExceptionHandler> newExceptionHandlers = new ArrayList<JavaExceptionHandler>();
List<JavaExceptionHandler> oldExceptionHandlers = block.getExceptionHandlers();
List<Map<String, JavaTypeName>> remainingVars = new ArrayList<Map<String, JavaTypeName>>();
for (int i = 0, n = oldExceptionHandlers.size(); i < n; ++i) {
Map<String, JavaTypeName> variablesOfInterestForHandler = new HashMap<String, JavaTypeName>(variablesOfInterest);
remainingVars.add(variablesOfInterestForHandler);
VarReleaser vr = new VarReleaser(variablesOfInterestForHandler);
newExceptionHandlers.add((JavaExceptionHandler)(oldExceptionHandlers.get(i)).accept(vr, arg));
}
Block newBlock = new Block(newExceptionHandlers);
// Update variables of interest.
if (remainingVars.size() > 0) {
variablesOfInterest.clear();
for (String varName : remainingVars.get(0).keySet()) {
boolean missing = false;
for (int i = 1, n = remainingVars.size(); i < n; ++i) {
if (!remainingVars.get(i).containsKey(varName)) {
missing = true;
break;
}
}
if (!missing) {
variablesOfInterest.put(varName, remainingVars.get(0).get(varName));
}
}
}
// Traverse the contained statements in reverse order, since we want to release the
// last reference to each variable.
List<JavaStatement> newStatements = new ArrayList<JavaStatement>(block.getNBlockStatements());
for (int i = block.getNBlockStatements() - 1; i >= 0; --i) {
newStatements.add(0, (JavaStatement)block.getNthBlockStatement(i).accept(this, arg));
}
for (int i = 0, n = newStatements.size(); i < n; ++i) {
newBlock.addStatement(newStatements.get(i));
}
return newBlock;
}
}
}