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